2019-03-10 16:42:12 +01:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"html"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
2019-03-13 06:09:24 +01:00
|
|
|
|
|
|
|
"github.com/zorchenhimer/MovieNight/common"
|
2019-03-10 16:42:12 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
var colorRegex *regexp.Regexp = regexp.MustCompile(`^#[0-9A-Fa-f]{6}$`)
|
|
|
|
|
|
|
|
type CommandControl struct {
|
2019-03-11 17:05:00 +01:00
|
|
|
user map[string]Command
|
|
|
|
mod map[string]Command
|
|
|
|
admin map[string]Command
|
|
|
|
}
|
|
|
|
|
|
|
|
type Command struct {
|
|
|
|
HelpText string
|
|
|
|
Function CommandFunction
|
2019-03-10 16:42:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
type CommandFunction func(client *Client, args []string) string
|
|
|
|
|
2019-03-11 17:05:00 +01:00
|
|
|
//type HelpFunction func(client *Client) string
|
|
|
|
|
2019-03-14 02:24:14 +01:00
|
|
|
var commands = &CommandControl{
|
|
|
|
user: map[string]Command{
|
|
|
|
common.CNMe.String(): Command{
|
|
|
|
HelpText: "Display an action message.",
|
|
|
|
Function: func(client *Client, args []string) string {
|
|
|
|
client.Me(strings.Join(args, " "))
|
|
|
|
return ""
|
2019-03-10 16:42:12 +01:00
|
|
|
},
|
2019-03-14 02:24:14 +01:00
|
|
|
},
|
2019-03-11 17:05:00 +01:00
|
|
|
|
2019-03-14 02:24:14 +01:00
|
|
|
common.CNHelp.String(): Command{
|
|
|
|
HelpText: "This help text.",
|
|
|
|
Function: cmdHelp,
|
|
|
|
},
|
2019-03-11 17:05:00 +01:00
|
|
|
|
2019-03-14 02:24:14 +01:00
|
|
|
common.CNCount.String(): Command{
|
|
|
|
HelpText: "Display number of users in chat.",
|
|
|
|
Function: func(client *Client, args []string) string {
|
|
|
|
return fmt.Sprintf("Users in chat: %d", client.belongsTo.UserCount())
|
2019-03-10 16:42:12 +01:00
|
|
|
},
|
2019-03-14 02:24:14 +01:00
|
|
|
},
|
2019-03-11 17:05:00 +01:00
|
|
|
|
2019-03-14 02:24:14 +01:00
|
|
|
common.CNColor.String(): cmdColor,
|
2019-03-10 16:42:12 +01:00
|
|
|
|
2019-03-14 02:24:14 +01:00
|
|
|
common.CNWhoAmI.String(): cmdWhoAmI,
|
2019-03-10 16:42:12 +01:00
|
|
|
|
2019-03-14 02:24:14 +01:00
|
|
|
common.CNAuth.String(): Command{
|
|
|
|
HelpText: "Authenticate to admin",
|
|
|
|
Function: func(cl *Client, args []string) string {
|
|
|
|
if cl.IsAdmin {
|
|
|
|
return "You are already authenticated."
|
|
|
|
}
|
2019-03-11 17:05:00 +01:00
|
|
|
|
2019-03-14 02:24:14 +01:00
|
|
|
pw := html.UnescapeString(strings.Join(args, " "))
|
2019-03-10 16:42:12 +01:00
|
|
|
|
2019-03-14 02:24:14 +01:00
|
|
|
if settings.AdminPassword == pw {
|
|
|
|
cl.IsMod = true
|
|
|
|
cl.IsAdmin = true
|
2019-03-15 21:03:02 +01:00
|
|
|
cl.belongsTo.AddModNotice(cl.name + " used the admin password")
|
2019-03-14 02:24:14 +01:00
|
|
|
fmt.Printf("[auth] %s used the admin password\n", cl.name)
|
|
|
|
return "Admin rights granted."
|
|
|
|
}
|
2019-03-11 17:05:00 +01:00
|
|
|
|
2019-03-14 02:24:14 +01:00
|
|
|
if cl.belongsTo.redeemModPass(pw) {
|
|
|
|
cl.IsMod = true
|
2019-03-15 21:03:02 +01:00
|
|
|
cl.belongsTo.AddModNotice(cl.name + " used a mod password")
|
2019-03-14 02:24:14 +01:00
|
|
|
fmt.Printf("[auth] %s used a mod password\n", cl.name)
|
|
|
|
return "Moderator privileges granted."
|
|
|
|
}
|
2019-03-13 21:47:32 +01:00
|
|
|
|
2019-03-15 21:03:02 +01:00
|
|
|
cl.belongsTo.AddModNotice(cl.name + " attempted to auth without success")
|
2019-03-14 02:24:14 +01:00
|
|
|
fmt.Printf("[auth] %s gave an invalid password\n", cl.name)
|
|
|
|
return "Invalid password."
|
2019-03-10 16:42:12 +01:00
|
|
|
},
|
2019-03-14 02:24:14 +01:00
|
|
|
},
|
2019-03-11 17:05:00 +01:00
|
|
|
|
2019-03-14 02:24:14 +01:00
|
|
|
common.CNUsers.String(): Command{
|
|
|
|
HelpText: "Show a list of users in chat",
|
|
|
|
Function: func(cl *Client, args []string) string {
|
|
|
|
names := cl.belongsTo.GetNames()
|
|
|
|
return strings.Join(names, " ")
|
2019-03-10 16:42:12 +01:00
|
|
|
},
|
|
|
|
},
|
2019-03-14 02:24:14 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
mod: map[string]Command{
|
|
|
|
common.CNSv.String(): Command{
|
|
|
|
HelpText: "Send a server announcement message. It will show up red with a border in chat.",
|
|
|
|
Function: func(cl *Client, args []string) string {
|
|
|
|
if len(args) == 0 {
|
|
|
|
return "Missing message"
|
|
|
|
}
|
|
|
|
svmsg := formatLinks(strings.Join(ParseEmotesArray(args), " "))
|
2019-03-15 21:03:02 +01:00
|
|
|
cl.belongsTo.AddModNotice("Server message from " + cl.name)
|
2019-03-14 02:24:14 +01:00
|
|
|
cl.belongsTo.AddMsg(cl, false, true, svmsg)
|
|
|
|
return ""
|
2019-03-11 17:05:00 +01:00
|
|
|
},
|
2019-03-14 02:24:14 +01:00
|
|
|
},
|
2019-03-11 17:05:00 +01:00
|
|
|
|
2019-03-14 02:24:14 +01:00
|
|
|
common.CNPlaying.String(): Command{
|
|
|
|
HelpText: "Set the title text and info link.",
|
|
|
|
Function: func(cl *Client, args []string) string {
|
|
|
|
// Clear/hide title if sent with no arguments.
|
|
|
|
if len(args) == 0 {
|
|
|
|
cl.belongsTo.ClearPlaying()
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
link := ""
|
|
|
|
title := ""
|
|
|
|
|
|
|
|
// pickout the link (can be anywhere, as long as there are no spaces).
|
|
|
|
for _, word := range args {
|
|
|
|
word = html.UnescapeString(word)
|
|
|
|
if strings.HasPrefix(word, "http://") || strings.HasPrefix(word, "https://") {
|
|
|
|
link = word
|
|
|
|
} else {
|
|
|
|
title = title + " " + word
|
2019-03-10 16:42:12 +01:00
|
|
|
}
|
2019-03-14 02:24:14 +01:00
|
|
|
}
|
2019-03-10 16:42:12 +01:00
|
|
|
|
2019-03-15 21:03:02 +01:00
|
|
|
title = strings.TrimSpace(title)
|
|
|
|
link = strings.TrimSpace(link)
|
|
|
|
|
|
|
|
// Send a notice to the mods and admins
|
|
|
|
if len(link) == 0 {
|
|
|
|
cl.belongsTo.AddModNotice(cl.name + " set the playing title to '" + title + "' with no link")
|
|
|
|
} else {
|
|
|
|
cl.belongsTo.AddModNotice(cl.name + " set the playing title to '" + title + "' with link '" + link + "'")
|
|
|
|
}
|
|
|
|
|
2019-03-14 02:24:14 +01:00
|
|
|
cl.belongsTo.SetPlaying(title, link)
|
|
|
|
return ""
|
2019-03-10 16:42:12 +01:00
|
|
|
},
|
2019-03-14 02:24:14 +01:00
|
|
|
},
|
2019-03-10 16:42:12 +01:00
|
|
|
|
2019-03-14 02:24:14 +01:00
|
|
|
common.CNUnmod.String(): Command{
|
|
|
|
HelpText: "Revoke a user's moderator privilages. Moderators can only unmod themselves.",
|
|
|
|
Function: func(cl *Client, args []string) string {
|
|
|
|
if len(args) > 0 && !cl.IsAdmin {
|
|
|
|
return "You can only unmod yourself, not others."
|
|
|
|
}
|
2019-03-10 16:42:12 +01:00
|
|
|
|
2019-03-14 02:24:14 +01:00
|
|
|
if len(args) == 0 {
|
|
|
|
cl.Unmod()
|
2019-03-15 21:03:02 +01:00
|
|
|
cl.belongsTo.AddModNotice(cl.name + " has unmodded themselves")
|
2019-03-14 02:24:14 +01:00
|
|
|
return "You have unmodded yourself."
|
|
|
|
}
|
2019-03-10 16:42:12 +01:00
|
|
|
|
2019-03-14 02:24:14 +01:00
|
|
|
if err := cl.belongsTo.Unmod(args[0]); err != nil {
|
|
|
|
return err.Error()
|
|
|
|
}
|
2019-03-11 17:05:00 +01:00
|
|
|
|
2019-03-15 21:03:02 +01:00
|
|
|
cl.belongsTo.AddModNotice(cl.name + " has unmodded " + args[0])
|
2019-03-14 02:24:14 +01:00
|
|
|
return fmt.Sprintf(`%s has been unmodded.`, args[0])
|
2019-03-10 16:42:12 +01:00
|
|
|
},
|
2019-03-14 02:24:14 +01:00
|
|
|
},
|
2019-03-11 17:05:00 +01:00
|
|
|
|
2019-03-14 02:24:14 +01:00
|
|
|
common.CNKick.String(): Command{
|
|
|
|
HelpText: "Kick a user from chat.",
|
|
|
|
Function: func(cl *Client, args []string) string {
|
|
|
|
if len(args) == 0 {
|
|
|
|
return "Missing name to kick."
|
|
|
|
}
|
|
|
|
return cl.belongsTo.Kick(args[0])
|
2019-03-10 16:42:12 +01:00
|
|
|
},
|
2019-03-14 02:24:14 +01:00
|
|
|
},
|
2019-03-11 17:05:00 +01:00
|
|
|
|
2019-03-14 02:24:14 +01:00
|
|
|
common.CNBan.String(): Command{
|
|
|
|
HelpText: "Ban a user from chat. They will not be able to re-join chat, but will still be able to view the stream.",
|
|
|
|
Function: func(cl *Client, args []string) string {
|
|
|
|
if len(args) == 0 {
|
|
|
|
return "missing name to ban."
|
|
|
|
}
|
|
|
|
fmt.Printf("[ban] Attempting to ban %s\n", strings.Join(args, ""))
|
|
|
|
return cl.belongsTo.Ban(args[0])
|
2019-03-10 16:42:12 +01:00
|
|
|
},
|
2019-03-14 02:24:14 +01:00
|
|
|
},
|
2019-03-10 16:42:12 +01:00
|
|
|
|
2019-03-14 02:24:14 +01:00
|
|
|
common.CNUnban.String(): Command{
|
|
|
|
HelpText: "Remove a ban on a user.",
|
|
|
|
Function: func(cl *Client, args []string) string {
|
|
|
|
if len(args) == 0 {
|
|
|
|
return "missing name to unban."
|
|
|
|
}
|
|
|
|
fmt.Printf("[ban] Attempting to unban %s\n", strings.Join(args, ""))
|
2019-03-11 17:05:00 +01:00
|
|
|
|
2019-03-14 02:24:14 +01:00
|
|
|
err := settings.RemoveBan(args[0])
|
|
|
|
if err != nil {
|
|
|
|
return err.Error()
|
|
|
|
}
|
2019-03-15 21:03:02 +01:00
|
|
|
cl.belongsTo.AddModNotice(cl.name + " has unbanned " + args[0])
|
2019-03-14 02:24:14 +01:00
|
|
|
return ""
|
2019-03-10 16:42:12 +01:00
|
|
|
},
|
|
|
|
},
|
2019-03-14 02:24:14 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
admin: map[string]Command{
|
|
|
|
common.CNMod.String(): Command{
|
|
|
|
HelpText: "Grant moderator privilages to a user.",
|
|
|
|
Function: func(cl *Client, args []string) string {
|
|
|
|
if len(args) == 0 {
|
|
|
|
return "Missing user to mod."
|
|
|
|
}
|
|
|
|
if err := cl.belongsTo.Mod(args[0]); err != nil {
|
|
|
|
return err.Error()
|
|
|
|
}
|
2019-03-15 21:03:02 +01:00
|
|
|
cl.belongsTo.AddModNotice(cl.name + " has modded " + args[0])
|
2019-03-14 02:24:14 +01:00
|
|
|
return fmt.Sprintf(`%s has been modded.`, args[0])
|
2019-03-10 16:42:12 +01:00
|
|
|
},
|
2019-03-14 02:24:14 +01:00
|
|
|
},
|
2019-03-11 17:05:00 +01:00
|
|
|
|
2019-03-14 02:24:14 +01:00
|
|
|
common.CNReloadPlayer.String(): Command{
|
|
|
|
HelpText: "Reload the stream player for everybody in chat.",
|
|
|
|
Function: func(cl *Client, args []string) string {
|
2019-03-15 21:03:02 +01:00
|
|
|
cl.belongsTo.AddModNotice(cl.name + " has modded forced a player reload")
|
2019-03-14 02:24:14 +01:00
|
|
|
cl.belongsTo.AddCmdMsg(common.CmdRefreshPlayer, nil)
|
|
|
|
return "Reloading player for all chatters."
|
2019-03-10 16:42:12 +01:00
|
|
|
},
|
2019-03-14 02:24:14 +01:00
|
|
|
},
|
2019-03-10 16:42:12 +01:00
|
|
|
|
2019-03-14 02:24:14 +01:00
|
|
|
common.CNReloadEmotes.String(): Command{
|
|
|
|
HelpText: "Reload the emotes on the server.",
|
|
|
|
Function: func(cl *Client, args []string) string {
|
|
|
|
cl.ServerMessage("Reloading emotes")
|
|
|
|
num, err := LoadEmotes()
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Unbale to reload emotes: %s\n", err)
|
|
|
|
return fmt.Sprintf("ERROR: %s", err)
|
|
|
|
}
|
2019-03-11 17:05:00 +01:00
|
|
|
|
2019-03-15 21:03:02 +01:00
|
|
|
cl.belongsTo.AddModNotice(cl.name + " has reloaded emotes")
|
2019-03-14 02:24:14 +01:00
|
|
|
fmt.Printf("Loaded %d emotes\n", num)
|
|
|
|
return fmt.Sprintf("Emotes loaded: %d", num)
|
2019-03-10 16:42:12 +01:00
|
|
|
},
|
2019-03-14 02:24:14 +01:00
|
|
|
},
|
2019-03-11 17:05:00 +01:00
|
|
|
|
2019-03-14 02:24:14 +01:00
|
|
|
common.CNModpass.String(): Command{
|
|
|
|
HelpText: "Generate a single-use mod password.",
|
|
|
|
Function: func(cl *Client, args []string) string {
|
|
|
|
password := cl.belongsTo.generateModPass()
|
|
|
|
return "Single use password: " + password
|
2019-03-13 21:47:32 +01:00
|
|
|
},
|
2019-03-10 16:42:12 +01:00
|
|
|
},
|
2019-03-14 02:24:14 +01:00
|
|
|
},
|
2019-03-10 16:42:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (cc *CommandControl) RunCommand(command string, args []string, sender *Client) string {
|
2019-03-14 05:21:41 +01:00
|
|
|
// get correct command from combined commands
|
|
|
|
cmd := common.GetFullChatCommand(command)
|
|
|
|
|
2019-03-10 16:42:12 +01:00
|
|
|
// Look for user command
|
2019-03-14 05:21:41 +01:00
|
|
|
if userCmd, ok := cc.user[cmd]; ok {
|
2019-03-14 20:09:55 +01:00
|
|
|
fmt.Printf("[user] %s /%s %s\n", sender.name, command, strings.Join(args, " "))
|
2019-03-11 17:05:00 +01:00
|
|
|
return userCmd.Function(sender, args)
|
2019-03-10 16:42:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Look for mod command
|
2019-03-14 05:21:41 +01:00
|
|
|
if modCmd, ok := cc.mod[cmd]; ok {
|
2019-03-10 16:42:12 +01:00
|
|
|
if sender.IsMod || sender.IsAdmin {
|
2019-03-14 20:09:55 +01:00
|
|
|
fmt.Printf("[mod] %s /%s %s\n", sender.name, command, strings.Join(args, " "))
|
2019-03-11 17:05:00 +01:00
|
|
|
return modCmd.Function(sender, args)
|
2019-03-10 16:42:12 +01:00
|
|
|
}
|
|
|
|
|
2019-03-14 20:09:55 +01:00
|
|
|
fmt.Printf("[mod REJECTED] %s /%s %s\n", sender.name, command, strings.Join(args, " "))
|
2019-03-10 16:42:12 +01:00
|
|
|
return "You are not a mod Jebaited"
|
|
|
|
}
|
|
|
|
|
|
|
|
// Look for admin command
|
2019-03-14 05:21:41 +01:00
|
|
|
if adminCmd, ok := cc.admin[cmd]; ok {
|
2019-03-10 16:42:12 +01:00
|
|
|
if sender.IsAdmin {
|
2019-03-14 20:09:55 +01:00
|
|
|
fmt.Printf("[admin] %s /%s %s\n", sender.name, command, strings.Join(args, " "))
|
2019-03-11 17:05:00 +01:00
|
|
|
return adminCmd.Function(sender, args)
|
2019-03-10 16:42:12 +01:00
|
|
|
}
|
2019-03-14 20:09:55 +01:00
|
|
|
fmt.Printf("[admin REJECTED] %s /%s %s\n", sender.name, command, strings.Join(args, " "))
|
2019-03-10 16:42:12 +01:00
|
|
|
return "You are not the admin Jebaited"
|
|
|
|
}
|
|
|
|
|
|
|
|
// Command not found
|
2019-03-14 20:09:55 +01:00
|
|
|
fmt.Printf("[cmd] %s /%s %s\n", sender.name, command, strings.Join(args, " "))
|
2019-03-10 16:42:12 +01:00
|
|
|
return "Invalid command."
|
|
|
|
}
|
|
|
|
|
2019-03-11 17:05:00 +01:00
|
|
|
func cmdHelp(cl *Client, args []string) string {
|
|
|
|
url := "/help"
|
|
|
|
if cl.IsMod {
|
|
|
|
url = "/modhelp"
|
|
|
|
}
|
2019-03-10 16:42:12 +01:00
|
|
|
|
2019-03-11 17:05:00 +01:00
|
|
|
if cl.IsAdmin {
|
|
|
|
url = "/adminhelp"
|
|
|
|
}
|
2019-03-10 16:42:12 +01:00
|
|
|
|
2019-03-11 17:05:00 +01:00
|
|
|
return `Opening help in new window.<script>window.open("` + url + `", "_blank", "menubar=0,status=0,toolbar=0,width=300,height=600")</script>`
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return a full HTML page for the help text. This should probably be rewritten with templates.
|
|
|
|
func helpPage(ismod, isadmin bool) string {
|
|
|
|
if commands == nil {
|
|
|
|
return "No commands loaded Jebaited"
|
2019-03-10 16:42:12 +01:00
|
|
|
}
|
2019-03-11 01:47:59 +01:00
|
|
|
|
2019-03-11 17:05:00 +01:00
|
|
|
text := []string{}
|
2019-03-14 02:24:14 +01:00
|
|
|
appendText := func(group map[string]Command) {
|
|
|
|
for key, cmd := range group {
|
|
|
|
for _, k := range strings.Split(key, common.CommandNameSeparator) {
|
|
|
|
text = append(text, fmt.Sprintf(`<dl class="helptext"><dt>%s</dt><dd>%s</dd></dl>`, k, cmd.HelpText))
|
|
|
|
}
|
|
|
|
}
|
2019-03-10 16:42:12 +01:00
|
|
|
}
|
|
|
|
|
2019-03-14 02:24:14 +01:00
|
|
|
appendText(commands.user)
|
|
|
|
|
2019-03-11 17:05:00 +01:00
|
|
|
if ismod {
|
2019-03-14 02:24:14 +01:00
|
|
|
appendText(commands.mod)
|
2019-03-11 02:26:37 +01:00
|
|
|
}
|
|
|
|
|
2019-03-11 17:05:00 +01:00
|
|
|
if isadmin {
|
2019-03-14 02:24:14 +01:00
|
|
|
appendText(commands.admin)
|
2019-03-11 02:26:37 +01:00
|
|
|
}
|
|
|
|
|
2019-03-11 17:05:00 +01:00
|
|
|
// This is ugly
|
|
|
|
return `<html><head><title>Help</title><link rel="stylesheet" type="text/css" href="/static/site.css"></head><body>` + strings.Join(text, "") + `</body></html>`
|
|
|
|
}
|
|
|
|
|
|
|
|
// Commands below have more than one invoking command (aliases).
|
|
|
|
|
|
|
|
var cmdColor = Command{
|
|
|
|
HelpText: "Change user color.",
|
|
|
|
Function: func(cl *Client, args []string) string {
|
|
|
|
// If the caller is priviledged enough, they can change the color of another user
|
|
|
|
if len(args) == 2 && (cl.IsMod || cl.IsAdmin) {
|
|
|
|
color := ""
|
|
|
|
name := ""
|
|
|
|
for _, s := range args {
|
|
|
|
if strings.HasPrefix(s, "#") {
|
|
|
|
color = s
|
|
|
|
} else {
|
|
|
|
name = s
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if color == "" {
|
|
|
|
|
|
|
|
fmt.Printf("[color:mod] %s missing color\n", cl.name)
|
|
|
|
return "Missing color"
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := cl.belongsTo.ForceColorChange(name, color); err != nil {
|
|
|
|
return err.Error()
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("Color changed for user %s to %s\n", name, color)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't allow an unprivilaged user to change their color if
|
|
|
|
// it was changed by a mod
|
|
|
|
if cl.IsColorForced {
|
|
|
|
fmt.Printf("[color] %s tried to change a forced color\n", cl.name)
|
|
|
|
return "You are not allowed to change your color."
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(args) == 0 {
|
|
|
|
cl.color = randomColor()
|
|
|
|
return "Random color chosen: " + cl.color
|
|
|
|
}
|
|
|
|
|
|
|
|
// Change the color of the user
|
|
|
|
if !colorRegex.MatchString(args[0]) {
|
|
|
|
return "To choose a specific color use the format <i>/color #c029ce</i>. Hex values expected."
|
|
|
|
}
|
|
|
|
|
|
|
|
cl.color = args[0]
|
|
|
|
fmt.Printf("[color] %s new color: %s\n", cl.name, cl.color)
|
|
|
|
return "Color changed successfully."
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
var cmdWhoAmI = Command{
|
|
|
|
HelpText: "Shows debug user info",
|
|
|
|
Function: func(cl *Client, args []string) string {
|
|
|
|
return fmt.Sprintf("Name: %s IsMod: %t IsAdmin: %t", cl.name, cl.IsMod, cl.IsAdmin)
|
|
|
|
},
|
2019-03-10 16:42:12 +01:00
|
|
|
}
|