diff --git a/chatcommands.go b/chatcommands.go index ec75766..d83f0cf 100644 --- a/chatcommands.go +++ b/chatcommands.go @@ -11,158 +11,213 @@ var commands *CommandControl var colorRegex *regexp.Regexp = regexp.MustCompile(`^#[0-9A-Fa-f]{6}$`) type CommandControl struct { - user map[string]CommandFunction - mod map[string]CommandFunction - admin map[string]CommandFunction + user map[string]Command + mod map[string]Command + admin map[string]Command +} + +type Command struct { + HelpText string + Function CommandFunction } type CommandFunction func(client *Client, args []string) string +//type HelpFunction func(client *Client) string + func init() { commands = &CommandControl{ - user: map[string]CommandFunction{ - "me": func(client *Client, args []string) string { - client.Me(strings.Join(args, " ")) - return "" + user: map[string]Command{ + "me": Command{ + HelpText: "Display an action message.", + Function: func(client *Client, args []string) string { + client.Me(strings.Join(args, " ")) + return "" + }, }, - "help": func(client *Client, args []string) string { - return "I haven't written this yet LUL" + + "help": Command{ + HelpText: "This help text.", + Function: cmdHelp, }, - "count": func(client *Client, args []string) string { - return fmt.Sprintf("Users in chat: %d", client.belongsTo.UserCount()) + + "count": 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()) + }, }, + "color": cmdColor, "colour": cmdColor, - "w": func(cl *Client, args []string) string { - return fmt.Sprintf("Name: %s IsMod: %t IsAdmin: %t", cl.name, cl.IsMod, cl.IsAdmin) - }, - "whoami": func(cl *Client, args []string) string { - return fmt.Sprintf("Name: %s IsMod: %t IsAdmin: %t", cl.name, cl.IsMod, cl.IsAdmin) - }, - "auth": func(cl *Client, args []string) string { - if cl.IsAdmin { - return "You are already authenticated." - } - pw := html.UnescapeString(strings.Join(args, " ")) + "w": cmdWhoAmI, + "whoami": cmdWhoAmI, - //fmt.Printf("/auth from %s. expecting %q [%X], received %q [%X]\n", cl.name, settings.AdminPassword, settings.AdminPassword, pw, pw) - if settings.AdminPassword == pw { - cl.IsMod = true - cl.IsAdmin = true - return "Admin rights granted." - } - - // Don't let on that this command exists. Not the most secure, but should be "good enough" LUL. - return "Invalid command." - }, - "users": func(cl *Client, args []string) string { - names := cl.belongsTo.GetNames() - return strings.Join(names, " ") - }, - }, - - mod: map[string]CommandFunction{ - "sv": func(cl *Client, args []string) string { - if len(args) == 0 { - return "Missing message" - } - svmsg := formatLinks(ParseEmotes(strings.Join(args, " "))) - cl.belongsTo.AddCmdMsg(fmt.Sprintf(`
%s
`, svmsg)) - return "" - }, - "playing": func(cl *Client, args []string) string { - // Clear/hide title if sent with no arguments. - if len(args) == 1 { - cl.belongsTo.ClearPlaying() - //cl.belongsTo.AddMsg(``) - 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 + "auth": Command{ + HelpText: "Authenticate to admin", + Function: func(cl *Client, args []string) string { + if cl.IsAdmin { + return "You are already authenticated." } - } - cl.belongsTo.SetPlaying(title, link) - //cl.belongsTo.AddMsg(fmt.Sprintf(``, title, link)) - return "" + pw := html.UnescapeString(strings.Join(args, " ")) + + //fmt.Printf("/auth from %s. expecting %q [%X], received %q [%X]\n", cl.name, settings.AdminPassword, settings.AdminPassword, pw, pw) + if settings.AdminPassword == pw { + cl.IsMod = true + cl.IsAdmin = true + return "Admin rights granted." + } + + // Don't let on that this command exists. Not the most secure, but should be "good enough" LUL. + return "Invalid command." + }, }, - "unmod": func(cl *Client, args []string) string { - if len(args) > 0 && !cl.IsAdmin { - return "You can only unmod yourself, not others." - } - if len(args) == 0 { - cl.Unmod() - return "You have unmodded yourself." - } - - if err := cl.belongsTo.Unmod(args[0]); err != nil { - return err.Error() - } - - return fmt.Sprintf(`%s has been unmodded.`, args[0]) - }, - "kick": func(cl *Client, args []string) string { - if len(args) == 0 { - return "Missing name to kick." - } - return cl.belongsTo.Kick(args[0]) - }, - "ban": 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]) - }, - "unban": 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, "")) - - err := settings.RemoveBan(args[0]) - if err != nil { - return err.Error() - } - return "" + "users": Command{ + HelpText: "Show a list of users in chat", + Function: func(cl *Client, args []string) string { + names := cl.belongsTo.GetNames() + return strings.Join(names, " ") + }, }, }, - admin: map[string]CommandFunction{ - "mod": 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() - } - return fmt.Sprintf(`%s has been modded.`, args[0]) + mod: map[string]Command{ + "sv": 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(ParseEmotes(strings.Join(args, " "))) + cl.belongsTo.AddCmdMsg(fmt.Sprintf(`
%s
`, svmsg)) + return "" + }, }, - "reloadplayer": func(cl *Client, args []string) string { - cl.belongsTo.AddCmdMsg(`[SERVER] Video player reload forced.
`) - return "Reloading player for all chatters." - }, - "reloademotes": 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) - } - fmt.Printf("Loaded %d emotes\n", num) - return fmt.Sprintf("Emotes loaded: %d", num) + "playing": 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) == 1 { + 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 + } + } + + cl.belongsTo.SetPlaying(title, link) + return "" + }, }, + + "unmod": 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." + } + + if len(args) == 0 { + cl.Unmod() + return "You have unmodded yourself." + } + + if err := cl.belongsTo.Unmod(args[0]); err != nil { + return err.Error() + } + + return fmt.Sprintf(`%s has been unmodded.`, args[0]) + }, + }, + + "kick": 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]) + }, + }, + + "ban": 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]) + }, + }, + + "unban": 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, "")) + + err := settings.RemoveBan(args[0]) + if err != nil { + return err.Error() + } + return "" + }, + }, + }, + + admin: map[string]Command{ + "mod": 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() + } + return fmt.Sprintf(`%s has been modded.`, args[0]) + }, + }, + + "reloadplayer": Command{ + HelpText: "Reload the stream player for everybody in chat.", + Function: func(cl *Client, args []string) string { + cl.belongsTo.AddCmdMsg(`[SERVER] Video player reload forced.
`) + return "Reloading player for all chatters." + }, + }, + + "reloademotes": 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) + } + + fmt.Printf("Loaded %d emotes\n", num) + return fmt.Sprintf("Emotes loaded: %d", num) + }, + }, + //"reloadsettings": func(cl *Client, args []string) string { // return "" //}, @@ -174,14 +229,14 @@ func (cc *CommandControl) RunCommand(command string, args []string, sender *Clie // Look for user command if userCmd, ok := cc.user[command]; ok { fmt.Printf("[user] %s /%s %s\n", sender.name, command, strings.Join(args, " ")) - return userCmd(sender, args) + return userCmd.Function(sender, args) } // Look for mod command if modCmd, ok := cc.mod[command]; ok { if sender.IsMod || sender.IsAdmin { fmt.Printf("[mod] %s /%s %s\n", sender.name, command, strings.Join(args, " ")) - return modCmd(sender, args) + return modCmd.Function(sender, args) } fmt.Printf("[mod REJECTED] %s /%s %s\n", sender.name, command, strings.Join(args, " ")) @@ -192,7 +247,7 @@ func (cc *CommandControl) RunCommand(command string, args []string, sender *Clie if adminCmd, ok := cc.admin[command]; ok { if sender.IsAdmin { fmt.Printf("[admin] %s /%s %s\n", sender.name, command, strings.Join(args, " ")) - return adminCmd(sender, args) + return adminCmd.Function(sender, args) } fmt.Printf("[admin REJECTED] %s /%s %s\n", sender.name, command, strings.Join(args, " ")) return "You are not the admin Jebaited" @@ -203,48 +258,102 @@ func (cc *CommandControl) RunCommand(command string, args []string, sender *Clie return "Invalid command." } -func cmdColor(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) +func cmdHelp(cl *Client, args []string) string { + url := "/help" + if cl.IsMod { + url = "/modhelp" } - // 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 cl.IsAdmin { + url = "/adminhelp" } - 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 /color #c029ce. Hex values expected." - } - - cl.color = args[0] - fmt.Printf("[color] %s new color: %s\n", cl.name, cl.color) - return "Color changed successfully." + return `Opening help in new window.` +} + +var hlpTemplate = `
%s
%s
` + +// 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" + } + + text := []string{} + for key, cmd := range commands.user { + text = append(text, fmt.Sprintf(hlpTemplate, key, cmd.HelpText)) + } + + if ismod { + for key, cmd := range commands.mod { + text = append(text, fmt.Sprintf(hlpTemplate, key, cmd.HelpText)) + } + } + + if isadmin { + for key, cmd := range commands.mod { + text = append(text, fmt.Sprintf(hlpTemplate, key, cmd.HelpText)) + } + } + + // This is ugly + return `Help` + strings.Join(text, "") + `` +} + +// 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 /color #c029ce. 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) + }, } diff --git a/chathandler.go b/chathandler.go index a4464d4..4fe056c 100644 --- a/chathandler.go +++ b/chathandler.go @@ -24,6 +24,17 @@ func wsStaticFiles(w http.ResponseWriter, r *http.Request) { case "/justvideo": http.ServeFile(w, r, "./static/justvideo.html") return + + // TODO: use a template for this, lol. + case "/help": + w.Write([]byte(helpPage(false, false))) + return + case "/modhelp": + w.Write([]byte(helpPage(true, false))) + return + case "/adminhelp": + w.Write([]byte(helpPage(true, true))) + return } goodPath := r.URL.Path[8:len(r.URL.Path)] diff --git a/main.go b/main.go index 5bc806a..d30ff27 100644 --- a/main.go +++ b/main.go @@ -96,6 +96,9 @@ func main() { http.HandleFunc("/favicon.ico", wsStaticFiles) http.HandleFunc("/justchat", wsStaticFiles) http.HandleFunc("/justvideo", wsStaticFiles) + http.HandleFunc("/help", wsStaticFiles) + http.HandleFunc("/modhelp", wsStaticFiles) + http.HandleFunc("/adminhelp", wsStaticFiles) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { l.RLock() diff --git a/static/site.css b/static/site.css index 58425d7..ccd6947 100644 --- a/static/site.css +++ b/static/site.css @@ -84,3 +84,11 @@ span.svmsg { #playing { font-size: x-Large; } + +.helptext { + color: white; +} + +dt { + font-weight: bold; +}