archive
This commit is contained in:
87
MovieNight/common/chatcommands.go
Executable file
87
MovieNight/common/chatcommands.go
Executable file
@@ -0,0 +1,87 @@
|
||||
package common
|
||||
|
||||
import "strings"
|
||||
|
||||
const CommandNameSeparator = ","
|
||||
|
||||
type ChatCommandNames []string
|
||||
|
||||
func (c ChatCommandNames) String() string {
|
||||
return strings.Join(c, CommandNameSeparator)
|
||||
}
|
||||
|
||||
// Names for commands
|
||||
var (
|
||||
// User Commands
|
||||
CNMe ChatCommandNames = []string{"me"}
|
||||
CNHelp ChatCommandNames = []string{"help"}
|
||||
CNCount ChatCommandNames = []string{"count"}
|
||||
CNColor ChatCommandNames = []string{"color", "colour"}
|
||||
CNWhoAmI ChatCommandNames = []string{"w", "whoami"}
|
||||
CNAuth ChatCommandNames = []string{"auth"}
|
||||
CNUsers ChatCommandNames = []string{"users"}
|
||||
CNNick ChatCommandNames = []string{"nick", "name"}
|
||||
CNStats ChatCommandNames = []string{"stats"}
|
||||
CNPin ChatCommandNames = []string{"pin", "password"}
|
||||
CNEmotes ChatCommandNames = []string{"emotes"}
|
||||
// Mod Commands
|
||||
CNSv ChatCommandNames = []string{"sv"}
|
||||
CNPlaying ChatCommandNames = []string{"playing"}
|
||||
CNUnmod ChatCommandNames = []string{"unmod"}
|
||||
CNKick ChatCommandNames = []string{"kick"}
|
||||
CNBan ChatCommandNames = []string{"ban"}
|
||||
CNUnban ChatCommandNames = []string{"unban"}
|
||||
CNPurge ChatCommandNames = []string{"purge"}
|
||||
// Admin Commands
|
||||
CNMod ChatCommandNames = []string{"mod"}
|
||||
CNReloadPlayer ChatCommandNames = []string{"reloadplayer"}
|
||||
CNReloadEmotes ChatCommandNames = []string{"reloademotes"}
|
||||
CNModpass ChatCommandNames = []string{"modpass"}
|
||||
CNIP ChatCommandNames = []string{"iplist"}
|
||||
CNAddEmotes ChatCommandNames = []string{"addemotes"}
|
||||
CNRoomAccess ChatCommandNames = []string{"changeaccess", "hodor"}
|
||||
)
|
||||
|
||||
var ChatCommands = []ChatCommandNames{
|
||||
// User
|
||||
CNMe,
|
||||
CNHelp,
|
||||
CNCount,
|
||||
CNColor,
|
||||
CNWhoAmI,
|
||||
CNAuth,
|
||||
CNUsers,
|
||||
CNNick,
|
||||
CNStats,
|
||||
CNPin,
|
||||
CNEmotes,
|
||||
|
||||
// Mod
|
||||
CNSv,
|
||||
CNPlaying,
|
||||
CNUnmod,
|
||||
CNKick,
|
||||
CNBan,
|
||||
CNUnban,
|
||||
CNPurge,
|
||||
|
||||
// Admin
|
||||
CNMod,
|
||||
CNReloadPlayer,
|
||||
CNReloadEmotes,
|
||||
CNModpass,
|
||||
CNIP,
|
||||
CNAddEmotes,
|
||||
CNRoomAccess,
|
||||
}
|
||||
|
||||
func GetFullChatCommand(c string) string {
|
||||
for _, names := range ChatCommands {
|
||||
for _, n := range names {
|
||||
if c == n {
|
||||
return names.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
249
MovieNight/common/chatdata.go
Executable file
249
MovieNight/common/chatdata.go
Executable file
@@ -0,0 +1,249 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type DataInterface interface {
|
||||
HTML() string
|
||||
}
|
||||
|
||||
type ChatData struct {
|
||||
Type DataType
|
||||
Data DataInterface
|
||||
}
|
||||
|
||||
func (c ChatData) ToJSON() (ChatDataJSON, error) {
|
||||
rawData, err := json.Marshal(c.Data)
|
||||
return ChatDataJSON{
|
||||
Type: c.Type,
|
||||
Data: rawData,
|
||||
}, err
|
||||
}
|
||||
|
||||
type ChatDataJSON struct {
|
||||
Type DataType
|
||||
Data json.RawMessage
|
||||
}
|
||||
|
||||
func (c ChatDataJSON) ToData() (ChatData, error) {
|
||||
data, err := c.GetData()
|
||||
return ChatData{
|
||||
Type: c.Type,
|
||||
Data: data,
|
||||
}, err
|
||||
}
|
||||
|
||||
func (c ChatDataJSON) GetData() (DataInterface, error) {
|
||||
var data DataInterface
|
||||
var err error
|
||||
|
||||
switch c.Type {
|
||||
case DTInvalid:
|
||||
return nil, errors.New("data type is invalid")
|
||||
case DTChat:
|
||||
d := DataMessage{}
|
||||
err = json.Unmarshal(c.Data, &d)
|
||||
data = d
|
||||
case DTCommand:
|
||||
d := DataCommand{}
|
||||
err = json.Unmarshal(c.Data, &d)
|
||||
data = d
|
||||
case DTEvent:
|
||||
d := DataEvent{}
|
||||
err = json.Unmarshal(c.Data, &d)
|
||||
data = d
|
||||
case DTClient:
|
||||
d := ClientData{}
|
||||
err = json.Unmarshal(c.Data, &d)
|
||||
data = d
|
||||
case DTHidden:
|
||||
d := HiddenMessage{}
|
||||
err = json.Unmarshal(c.Data, &d)
|
||||
data = d
|
||||
default:
|
||||
err = fmt.Errorf("unhandled data type: %d", c.Type)
|
||||
}
|
||||
|
||||
return data, err
|
||||
}
|
||||
|
||||
type ClientData struct {
|
||||
Type ClientDataType
|
||||
Message string
|
||||
}
|
||||
|
||||
func (c ClientData) HTML() string {
|
||||
// Client data is for client to server communication only, so clients should not see this
|
||||
return `<span style="color: red;">The developer messed up. You should not be seeing this.</span>`
|
||||
}
|
||||
|
||||
type DataMessage struct {
|
||||
From string
|
||||
Color string
|
||||
Message string
|
||||
Level CommandLevel
|
||||
Type MessageType
|
||||
}
|
||||
|
||||
// TODO: Read this HTML from a template somewhere
|
||||
func (dc DataMessage) HTML() string {
|
||||
switch dc.Type {
|
||||
case MsgAction:
|
||||
return `<span style="color:` + dc.Color + `"><span class="name">` + dc.From +
|
||||
`</span> <span class="cmdme">` + dc.Message + `</span></span>`
|
||||
|
||||
case MsgServer:
|
||||
return `<span class="announcement">` + dc.Message + `</span>`
|
||||
|
||||
case MsgError:
|
||||
return `<span class="error">` + dc.Message + `</span>`
|
||||
|
||||
case MsgNotice:
|
||||
return `<span class="notice">` + dc.Message + `</span>`
|
||||
|
||||
case MsgCommandResponse:
|
||||
return `<span class="command">` + dc.Message + `</span>`
|
||||
|
||||
case MsgCommandError:
|
||||
return `<span class="commanderror">` + dc.Message + `</span>`
|
||||
|
||||
default:
|
||||
badge := ""
|
||||
switch dc.Level {
|
||||
case CmdlMod:
|
||||
badge = `<img src="/static/img/mod.png" class="badge" />`
|
||||
case CmdlAdmin:
|
||||
badge = `<img src="/static/img/admin.png" class="badge" />`
|
||||
}
|
||||
return `<span>` + badge + `<span class="name" style="color:` + dc.Color + `">` + dc.From +
|
||||
`</span><b>:</b> <span class="msg">` + dc.Message + `</span></span>`
|
||||
}
|
||||
}
|
||||
|
||||
func NewChatMessage(name, color, msg string, lvl CommandLevel, msgtype MessageType) ChatData {
|
||||
return ChatData{
|
||||
Type: DTChat,
|
||||
Data: DataMessage{
|
||||
From: name,
|
||||
Color: color,
|
||||
Message: msg,
|
||||
Type: msgtype,
|
||||
Level: lvl,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type DataCommand struct {
|
||||
Command CommandType
|
||||
Arguments []string
|
||||
}
|
||||
|
||||
func (de DataCommand) HTML() string {
|
||||
switch de.Command {
|
||||
case CmdPurgeChat:
|
||||
return `<span class="notice">Chat has been purged by a moderator.</span>`
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func NewChatCommand(command CommandType, args []string) ChatData {
|
||||
return ChatData{
|
||||
Type: DTCommand,
|
||||
Data: DataCommand{
|
||||
Command: command,
|
||||
Arguments: args,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type DataEvent struct {
|
||||
Event EventType
|
||||
User string
|
||||
Color string
|
||||
}
|
||||
|
||||
func (de DataEvent) HTML() string {
|
||||
switch de.Event {
|
||||
case EvKick:
|
||||
return `<span class="event"><span class="name" style="color:` + de.Color + `">` +
|
||||
de.User + `</span> has been kicked.</span>`
|
||||
case EvLeave:
|
||||
return `<span class="event"><span class="name" style="color:` + de.Color + `">` +
|
||||
de.User + `</span> has left the chat.</span>`
|
||||
case EvBan:
|
||||
return `<span class="event"><span class="name" style="color:` + de.Color + `">` +
|
||||
de.User + `</span> has been banned.</span>`
|
||||
case EvJoin:
|
||||
return `<span class="event"><span class="name" style="color:` + de.Color + `">` +
|
||||
de.User + `</span> has joined the chat.</span>`
|
||||
case EvNameChange:
|
||||
names := strings.Split(de.User, ":")
|
||||
if len(names) != 2 {
|
||||
return `<span class="event">Somebody changed their name, but IDK who ` +
|
||||
ParseEmotes("Jebaited") + `.</span>`
|
||||
}
|
||||
|
||||
return `<span class="event"><span class="name" style="color:` + de.Color + `">` +
|
||||
names[0] + `</span> has changed their name to <span class="name" style="color:` +
|
||||
de.Color + `">` + names[1] + `</span>.</span>`
|
||||
case EvNameChangeForced:
|
||||
names := strings.Split(de.User, ":")
|
||||
if len(names) != 2 {
|
||||
return `<span class="event">An admin changed somebody's name, but IDK who ` +
|
||||
ParseEmotes("Jebaited") + `.</span>`
|
||||
}
|
||||
|
||||
return `<span class="event"><span class="name" style="color:` + de.Color + `">` +
|
||||
names[0] + `</span> has had their name changed to <span class="name" style="color:` +
|
||||
de.Color + `">` + names[1] + `</span> by an admin.</span>`
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func NewChatEvent(event EventType, name, color string) ChatData {
|
||||
return ChatData{
|
||||
Type: DTEvent,
|
||||
Data: DataEvent{
|
||||
Event: event,
|
||||
User: name,
|
||||
Color: color,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DataHidden is for the server to send instructions and data
|
||||
// to the client without the purpose of outputting it on the chat
|
||||
type HiddenMessage struct {
|
||||
Type ClientDataType
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
func (h HiddenMessage) HTML() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func NewChatHiddenMessage(clientType ClientDataType, data interface{}) ChatData {
|
||||
return ChatData{
|
||||
Type: DTHidden,
|
||||
Data: HiddenMessage{
|
||||
Type: clientType,
|
||||
Data: data,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func DecodeData(rawjson string) (ChatDataJSON, error) {
|
||||
var data ChatDataJSON
|
||||
err := json.Unmarshal([]byte(rawjson), &data)
|
||||
return data, err
|
||||
}
|
||||
|
||||
type JoinData struct {
|
||||
Name string
|
||||
Color string
|
||||
}
|
||||
135
MovieNight/common/colors.go
Executable file
135
MovieNight/common/colors.go
Executable file
@@ -0,0 +1,135 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(int64(time.Now().Nanosecond()))
|
||||
}
|
||||
|
||||
// Colors holds all the valid html color names for MovieNight
|
||||
// the values in colors must be lowercase so it matches with the color input
|
||||
// this saves from having to call strings.ToLower(color) every time to check
|
||||
var Colors = []string{
|
||||
"aliceblue", "antiquewhite", "aqua", "aquamarine", "azure",
|
||||
"beige", "bisque", "blanchedalmond", "blueviolet", "brown",
|
||||
"burlywood", "cadetblue", "chartreuse", "chocolate", "coral",
|
||||
"cornflowerblue", "cornsilk", "crimson", "cyan", "darkcyan",
|
||||
"darkgoldenrod", "darkgray", "darkkhaki", "darkmagenta", "darkolivegreen",
|
||||
"darkorange", "darkorchid", "darksalmon", "darkseagreen", "darkslateblue",
|
||||
"darkslategray", "darkturquoise", "darkviolet", "deeppink", "deepskyblue",
|
||||
"dimgray", "dodgerblue", "firebrick", "floralwhite", "forestgreen",
|
||||
"fuchsia", "gainsboro", "ghostwhite", "gold", "goldenrod",
|
||||
"gray", "greenyellow", "honeydew", "hotpink", "indigo",
|
||||
"ivory", "khaki", "lavender", "lavenderblush", "lawngreen",
|
||||
"lemonchiffon", "lightblue", "lightcoral", "lightcyan", "lightgoldenrodyellow",
|
||||
"lightgrey", "lightgreen", "lightpink", "lightsalmon", "lightseagreen",
|
||||
"lightskyblue", "lightslategray", "lightsteelblue", "lightyellow", "lime",
|
||||
"limegreen", "linen", "magenta", "mediumaquamarine", "mediumorchid",
|
||||
"mediumpurple", "mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise",
|
||||
"mediumvioletred", "mintcream", "mistyrose", "moccasin", "navajowhite",
|
||||
"oldlace", "olive", "olivedrab", "orange", "orangered",
|
||||
"orchid", "palegoldenrod", "palegreen", "paleturquoise", "palevioletred",
|
||||
"papayawhip", "peachpuff", "peru", "pink", "plum",
|
||||
"powderblue", "purple", "rebeccapurple", "red", "rosybrown",
|
||||
"royalblue", "saddlebrown", "salmon", "sandybrown", "seagreen",
|
||||
"seashell", "sienna", "silver", "skyblue", "slateblue",
|
||||
"slategray", "snow", "springgreen", "steelblue", "tan",
|
||||
"teal", "thistle", "tomato", "turquoise", "violet",
|
||||
"wheat", "white", "whitesmoke", "yellow", "yellowgreen",
|
||||
}
|
||||
|
||||
var (
|
||||
regexColor = regexp.MustCompile(`^([0-9A-Fa-f]{3}){1,2}$`)
|
||||
)
|
||||
|
||||
// IsValidColor takes a string s and compares it against a list of css color names.
|
||||
// It also accepts hex codes in the form of #RGB and #RRGGBB
|
||||
func IsValidColor(s string) bool {
|
||||
s = strings.TrimLeft(strings.ToLower(s), "#")
|
||||
for _, c := range Colors {
|
||||
if s == c {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if regexColor.MatchString(s) {
|
||||
r, g, b, err := hex(s)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
total := float32(r + g + b)
|
||||
return total > 0.7 && float32(b)/total < 0.7
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RandomColor returns a hex color code
|
||||
func RandomColor() string {
|
||||
var color string
|
||||
for !IsValidColor(color) {
|
||||
color = ""
|
||||
for i := 0; i < 3; i++ {
|
||||
s := strconv.FormatInt(rand.Int63n(255), 16)
|
||||
if len(s) == 1 {
|
||||
s = "0" + s
|
||||
}
|
||||
color += s
|
||||
}
|
||||
}
|
||||
return "#" + color
|
||||
}
|
||||
|
||||
// hex returns R, G, B as values
|
||||
func hex(s string) (int, int, int, error) {
|
||||
// Make the string just the base16 numbers
|
||||
s = strings.TrimLeft(s, "#")
|
||||
|
||||
if len(s) == 3 {
|
||||
var err error
|
||||
s, err = hexThreeToSix(s)
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(s) == 6 {
|
||||
R64, err := strconv.ParseInt(s[0:2], 16, 32)
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
|
||||
G64, err := strconv.ParseInt(s[2:4], 16, 32)
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
|
||||
B64, err := strconv.ParseInt(s[4:6], 16, 32)
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
|
||||
return int(R64), int(G64), int(B64), nil
|
||||
}
|
||||
return 0, 0, 0, errors.New("incorrect format")
|
||||
}
|
||||
|
||||
func hexThreeToSix(s string) (string, error) {
|
||||
if len(s) != 3 {
|
||||
return "", fmt.Errorf("%d is the incorrect length of string for convertsion", len(s))
|
||||
}
|
||||
|
||||
h := ""
|
||||
for i := 0; i < 3; i++ {
|
||||
h += string(s[i])
|
||||
h += string(s[i])
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
42
MovieNight/common/colors_test.go
Executable file
42
MovieNight/common/colors_test.go
Executable file
@@ -0,0 +1,42 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestColorHexThreeToSix(t *testing.T) {
|
||||
expected := "RRGGBB"
|
||||
result, _ := hexThreeToSix("RGB")
|
||||
if result != expected {
|
||||
t.Errorf("expected %#v, got %#v", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHex(t *testing.T) {
|
||||
// The testing data layout is inputer, Expected Red, Exp Green, Exp Blue, expect error
|
||||
data := [][]interface{}{
|
||||
[]interface{}{"010203", 1, 2, 3, false},
|
||||
[]interface{}{"100", 17, 0, 0, false},
|
||||
[]interface{}{"100", 1, 0, 0, true},
|
||||
[]interface{}{"1000", 0, 0, 0, true},
|
||||
[]interface{}{"010203", 1, 2, 4, true},
|
||||
[]interface{}{"0102GG", 1, 2, 4, true},
|
||||
}
|
||||
|
||||
for i := range data {
|
||||
input := data[i][0].(string)
|
||||
r, g, b, err := hex(input)
|
||||
if err != nil {
|
||||
if !data[i][4].(bool) {
|
||||
t.Errorf("with input %#v: %v", input, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
rr, rg, rb := data[i][1].(int), data[i][2].(int), data[i][3].(int)
|
||||
|
||||
if !data[i][4].(bool) && (r != rr || g != rg || b != rb) {
|
||||
t.Errorf("expected %d, %d, %d - got %d, %d, %d", r, g, b, rr, rg, rb)
|
||||
}
|
||||
}
|
||||
}
|
||||
73
MovieNight/common/constants.go
Executable file
73
MovieNight/common/constants.go
Executable file
@@ -0,0 +1,73 @@
|
||||
package common
|
||||
|
||||
type ClientDataType int
|
||||
|
||||
// Data types for communicating with the client
|
||||
const (
|
||||
CdMessage ClientDataType = iota // a normal message from the client meant to be broadcast
|
||||
CdUsers // get a list of users
|
||||
CdPing // ping the server to keep the connection alive
|
||||
CdAuth // get the auth levels of the user
|
||||
CdColor // get the users color
|
||||
CdEmote // get a list of emotes
|
||||
CdJoin // a message saying the client wants to join
|
||||
CdNotify // a notify message for the client to show
|
||||
)
|
||||
|
||||
type DataType int
|
||||
|
||||
// Data types for command messages
|
||||
const (
|
||||
DTInvalid DataType = iota
|
||||
DTChat // chat message
|
||||
DTCommand // non-chat function
|
||||
DTEvent // join/leave/kick/ban events
|
||||
DTClient // a message coming from the client
|
||||
DTHidden // a message that is purely instruction and data, not shown to user
|
||||
)
|
||||
|
||||
type CommandType int
|
||||
|
||||
// Command Types
|
||||
const (
|
||||
CmdPlaying CommandType = iota
|
||||
CmdRefreshPlayer
|
||||
CmdPurgeChat
|
||||
CmdHelp
|
||||
CmdEmotes
|
||||
)
|
||||
|
||||
type CommandLevel int
|
||||
|
||||
// Command access levels
|
||||
const (
|
||||
CmdlUser CommandLevel = iota
|
||||
CmdlMod
|
||||
CmdlAdmin
|
||||
)
|
||||
|
||||
type EventType int
|
||||
|
||||
// Event Types
|
||||
const (
|
||||
EvJoin EventType = iota
|
||||
EvLeave
|
||||
EvKick
|
||||
EvBan
|
||||
EvServerMessage
|
||||
EvNameChange
|
||||
EvNameChangeForced
|
||||
)
|
||||
|
||||
type MessageType int
|
||||
|
||||
// Message Types
|
||||
const (
|
||||
MsgChat MessageType = iota // standard chat
|
||||
MsgAction // /me command
|
||||
MsgServer // server message
|
||||
MsgError // something went wrong
|
||||
MsgNotice // Like MsgServer, but for mods and admins only.
|
||||
MsgCommandResponse // The response from command
|
||||
MsgCommandError // The error response from command
|
||||
)
|
||||
74
MovieNight/common/emotes.go
Executable file
74
MovieNight/common/emotes.go
Executable file
@@ -0,0 +1,74 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type EmotesMap map[string]string
|
||||
|
||||
var Emotes EmotesMap
|
||||
|
||||
var reStripStatic = regexp.MustCompile(`^(\\|/)?static`)
|
||||
|
||||
func init() {
|
||||
Emotes = NewEmotesMap()
|
||||
}
|
||||
|
||||
func NewEmotesMap() EmotesMap {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (em EmotesMap) Add(fullpath string) EmotesMap {
|
||||
fullpath = reStripStatic.ReplaceAllLiteralString(fullpath, "")
|
||||
|
||||
base := filepath.Base(fullpath)
|
||||
code := base[0 : len(base)-len(filepath.Ext(base))]
|
||||
|
||||
_, exists := em[code]
|
||||
|
||||
num := 0
|
||||
for exists {
|
||||
num += 1
|
||||
_, exists = em[fmt.Sprintf("%s-%d", code, num)]
|
||||
}
|
||||
|
||||
if num > 0 {
|
||||
code = fmt.Sprintf("%s-%d", code, num)
|
||||
}
|
||||
|
||||
em[code] = fullpath
|
||||
//fmt.Printf("Added emote %s at path %q\n", code, fullpath)
|
||||
return em
|
||||
}
|
||||
|
||||
func EmoteToHtml(file, title string) string {
|
||||
return fmt.Sprintf(`<img src="%s" height="28px" title="%s" />`, file, title)
|
||||
}
|
||||
|
||||
func ParseEmotesArray(words []string) []string {
|
||||
newWords := []string{}
|
||||
for _, word := range words {
|
||||
// make :emote: and [emote] valid for replacement.
|
||||
wordTrimmed := strings.Trim(word, ":[]")
|
||||
|
||||
found := false
|
||||
for key, val := range Emotes {
|
||||
if key == wordTrimmed {
|
||||
newWords = append(newWords, EmoteToHtml(val, key))
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
newWords = append(newWords, word)
|
||||
}
|
||||
}
|
||||
return newWords
|
||||
}
|
||||
|
||||
func ParseEmotes(msg string) string {
|
||||
words := ParseEmotesArray(strings.Split(msg, " "))
|
||||
return strings.Join(words, " ")
|
||||
}
|
||||
44
MovieNight/common/emotes_test.go
Executable file
44
MovieNight/common/emotes_test.go
Executable file
@@ -0,0 +1,44 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var data_good = map[string]string{
|
||||
"one": `<img src="/emotes/one.png" height="28px" title="one" />`,
|
||||
"two": `<img src="/emotes/two.png" height="28px" title="two" />`,
|
||||
"three": `<img src="/emotes/three.gif" height="28px" title="three" />`,
|
||||
|
||||
":one:": `<img src="/emotes/one.png" height="28px" title="one" />`,
|
||||
":two:": `<img src="/emotes/two.png" height="28px" title="two" />`,
|
||||
":three:": `<img src="/emotes/three.gif" height="28px" title="three" />`,
|
||||
|
||||
"[one]": `<img src="/emotes/one.png" height="28px" title="one" />`,
|
||||
"[two]": `<img src="/emotes/two.png" height="28px" title="two" />`,
|
||||
"[three]": `<img src="/emotes/three.gif" height="28px" title="three" />`,
|
||||
|
||||
":one: two [three]": `<img src="/emotes/one.png" height="28px" title="one" /> <img src="/emotes/two.png" height="28px" title="two" /> <img src="/emotes/three.gif" height="28px" title="three" />`,
|
||||
|
||||
"nope one what": `nope <img src="/emotes/one.png" height="28px" title="one" /> what`,
|
||||
"nope :two: what": `nope <img src="/emotes/two.png" height="28px" title="two" /> what`,
|
||||
"nope [three] what": `nope <img src="/emotes/three.gif" height="28px" title="three" /> what`,
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
Emotes = map[string]string{
|
||||
"one": "/emotes/one.png",
|
||||
"two": "/emotes/two.png",
|
||||
"three": "/emotes/three.gif",
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestEmotes_ParseEmotes(t *testing.T) {
|
||||
for input, expected := range data_good {
|
||||
got := ParseEmotes(input)
|
||||
if got != expected {
|
||||
t.Errorf("%s failed to parse into %q. Received: %q", input, expected, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
200
MovieNight/common/logging.go
Executable file
200
MovieNight/common/logging.go
Executable file
@@ -0,0 +1,200 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
var loglevel LogLevel
|
||||
|
||||
type LogLevel string
|
||||
|
||||
const (
|
||||
LLError LogLevel = "error" // only log errors
|
||||
LLChat LogLevel = "chat" // log chat and commands
|
||||
LLInfo LogLevel = "info" // log info messages (not quite debug, but not chat)
|
||||
LLDebug LogLevel = "debug" // log everything
|
||||
)
|
||||
|
||||
const (
|
||||
logPrefixError string = "[ERROR] "
|
||||
logPrefixChat string = "[CHAT] "
|
||||
logPrefixInfo string = "[INFO] "
|
||||
logPrefixDebug string = "[DEBUG] "
|
||||
)
|
||||
|
||||
var (
|
||||
logError *log.Logger
|
||||
logChat *log.Logger
|
||||
logInfo *log.Logger
|
||||
logDebug *log.Logger
|
||||
)
|
||||
|
||||
func SetupLogging(level LogLevel, file string) error {
|
||||
switch level {
|
||||
case LLDebug:
|
||||
if file == "" {
|
||||
logError = log.New(os.Stderr, logPrefixError, log.LstdFlags)
|
||||
logChat = log.New(os.Stdout, logPrefixChat, log.LstdFlags)
|
||||
logDebug = log.New(os.Stdout, logPrefixDebug, log.LstdFlags)
|
||||
logInfo = log.New(os.Stdout, logPrefixInfo, log.LstdFlags)
|
||||
} else {
|
||||
f, err := os.OpenFile(file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to open log file for writing: %s", err)
|
||||
}
|
||||
logError = log.New(io.MultiWriter(os.Stderr, f), logPrefixError, log.LstdFlags)
|
||||
logChat = log.New(io.MultiWriter(os.Stdout, f), logPrefixChat, log.LstdFlags)
|
||||
logInfo = log.New(io.MultiWriter(os.Stdout, f), logPrefixInfo, log.LstdFlags)
|
||||
logDebug = log.New(io.MultiWriter(os.Stdout, f), logPrefixDebug, log.LstdFlags)
|
||||
}
|
||||
case LLChat:
|
||||
logDebug = nil
|
||||
if file == "" {
|
||||
logError = log.New(os.Stderr, logPrefixError, log.LstdFlags)
|
||||
logChat = log.New(os.Stdout, logPrefixChat, log.LstdFlags)
|
||||
logInfo = log.New(os.Stdout, logPrefixInfo, log.LstdFlags)
|
||||
} else {
|
||||
f, err := os.OpenFile(file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to open log file for writing: %s", err)
|
||||
}
|
||||
logError = log.New(io.MultiWriter(os.Stderr, f), logPrefixError, log.LstdFlags)
|
||||
logChat = log.New(io.MultiWriter(os.Stdout, f), logPrefixChat, log.LstdFlags)
|
||||
logInfo = log.New(io.MultiWriter(os.Stdout, f), logPrefixInfo, log.LstdFlags)
|
||||
}
|
||||
|
||||
case LLInfo:
|
||||
logDebug = nil
|
||||
logChat = nil
|
||||
if file == "" {
|
||||
logError = log.New(os.Stderr, logPrefixError, log.LstdFlags)
|
||||
logInfo = log.New(os.Stdout, logPrefixInfo, log.LstdFlags)
|
||||
} else {
|
||||
f, err := os.OpenFile(file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to open log file for writing: %s", err)
|
||||
}
|
||||
logError = log.New(io.MultiWriter(os.Stderr, f), logPrefixError, log.LstdFlags)
|
||||
logInfo = log.New(io.MultiWriter(os.Stdout, f), logPrefixInfo, log.LstdFlags)
|
||||
}
|
||||
|
||||
// Default to error
|
||||
default:
|
||||
logChat = nil
|
||||
logDebug = nil
|
||||
logInfo = nil
|
||||
if file == "" {
|
||||
logError = log.New(os.Stderr, logPrefixError, log.LstdFlags)
|
||||
} else {
|
||||
f, err := os.OpenFile(file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to open log file for writing: %s", err)
|
||||
}
|
||||
logError = log.New(io.MultiWriter(os.Stderr, f), logPrefixError, log.LstdFlags)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func LogErrorf(format string, v ...interface{}) {
|
||||
if logError == nil {
|
||||
panic("Logging not setup!")
|
||||
}
|
||||
|
||||
logError.Printf(format, v...)
|
||||
}
|
||||
|
||||
func LogErrorln(v ...interface{}) {
|
||||
if logError == nil {
|
||||
panic("Logging not setup!")
|
||||
}
|
||||
|
||||
logError.Println(v...)
|
||||
}
|
||||
|
||||
func LogChatf(format string, v ...interface{}) {
|
||||
// if logError isn't set to something, logging wasn't setup.
|
||||
if logError == nil {
|
||||
panic("Logging not setup!")
|
||||
}
|
||||
|
||||
// logging chat and commands is turned off.
|
||||
if logChat == nil {
|
||||
return
|
||||
}
|
||||
|
||||
logChat.Printf(format, v...)
|
||||
}
|
||||
|
||||
func LogChatln(v ...interface{}) {
|
||||
// if logError isn't set to something, logging wasn't setup.
|
||||
if logError == nil {
|
||||
panic("Logging not setup!")
|
||||
}
|
||||
|
||||
// logging chat and commands is turned off.
|
||||
if logChat == nil {
|
||||
return
|
||||
}
|
||||
|
||||
logChat.Println(v...)
|
||||
}
|
||||
|
||||
func LogInfof(format string, v ...interface{}) {
|
||||
// if logError isn't set to something, logging wasn't setup.
|
||||
if logError == nil {
|
||||
panic("Logging not setup!")
|
||||
}
|
||||
|
||||
// logging info is turned off.
|
||||
if logInfo == nil {
|
||||
return
|
||||
}
|
||||
|
||||
logInfo.Printf(format, v...)
|
||||
}
|
||||
|
||||
func LogInfoln(v ...interface{}) {
|
||||
// if logError isn't set to something, logging wasn't setup.
|
||||
if logError == nil {
|
||||
panic("Logging not setup!")
|
||||
}
|
||||
|
||||
// logging info is turned off.
|
||||
if logInfo == nil {
|
||||
return
|
||||
}
|
||||
|
||||
logInfo.Println(v...)
|
||||
}
|
||||
|
||||
func LogDebugf(format string, v ...interface{}) {
|
||||
// if logError isn't set to something, logging wasn't setup.
|
||||
if logError == nil {
|
||||
panic("Logging not setup!")
|
||||
}
|
||||
|
||||
// logging debug is turned off.
|
||||
if logDebug == nil {
|
||||
return
|
||||
}
|
||||
|
||||
logDebug.Printf(format, v...)
|
||||
}
|
||||
|
||||
func LogDebugln(v ...interface{}) {
|
||||
// if logError isn't set to something, logging wasn't setup.
|
||||
if logError == nil {
|
||||
panic("Logging not setup!")
|
||||
}
|
||||
|
||||
// logging debug is turned off.
|
||||
if logDebug == nil {
|
||||
return
|
||||
}
|
||||
|
||||
logDebug.Println(v...)
|
||||
}
|
||||
18
MovieNight/common/logging_dev.go
Executable file
18
MovieNight/common/logging_dev.go
Executable file
@@ -0,0 +1,18 @@
|
||||
// +build dev
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
var logDev *log.Logger = log.New(os.Stdout, "[DEV]", log.LstdFlags)
|
||||
|
||||
func LogDevf(format string, v ...interface{}) {
|
||||
logDev.Printf(format, v...)
|
||||
}
|
||||
|
||||
func LogDevln(v ...interface{}) {
|
||||
logDev.Println(v...)
|
||||
}
|
||||
90
MovieNight/common/templates.go
Executable file
90
MovieNight/common/templates.go
Executable file
@@ -0,0 +1,90 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
html "html/template"
|
||||
"net/http"
|
||||
"strings"
|
||||
text "text/template"
|
||||
)
|
||||
|
||||
// Holds the server's templates
|
||||
var serverTemplates map[string]*html.Template
|
||||
|
||||
// Holds the client's chat templates
|
||||
var chatTemplates map[string]*text.Template
|
||||
|
||||
var isServer bool = false
|
||||
|
||||
// keys and files to load for that template
|
||||
var serverTemplateDefs map[string][]string = map[string][]string{
|
||||
"pin": []string{"./static/base.html", "./static/thedoor.html"},
|
||||
"main": []string{"./static/base.html", "./static/main.html"},
|
||||
"help": []string{"./static/base.html", "./static/help.html"},
|
||||
"emotes": []string{"./static/base.html", "./static/emotes.html"},
|
||||
}
|
||||
|
||||
var chatTemplateDefs map[string]string = map[string]string{
|
||||
fmt.Sprint(DTInvalid, 0): "wot",
|
||||
|
||||
fmt.Sprint(DTChat, MsgChat): `<span>{{.Badge}} <span class="name" style="color:{{.Color}}">{{.From}}` +
|
||||
`</span><b>:</b> <span class="msg">{{.Message}}</span></span>`,
|
||||
fmt.Sprint(DTChat, MsgAction): `<span style="color:{{.Color}}"><span class="name">{{.From}}` +
|
||||
`</span> <span class="cmdme">{{.Message}}</span></span>`,
|
||||
}
|
||||
|
||||
// Called from the server
|
||||
func InitTemplates() error {
|
||||
isServer = true
|
||||
serverTemplates = make(map[string]*html.Template)
|
||||
chatTemplates = make(map[string]*text.Template)
|
||||
|
||||
// Parse server templates
|
||||
for key, files := range serverTemplateDefs {
|
||||
t, err := html.ParseFiles(files...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to parse templates for %s: %v", key, err)
|
||||
}
|
||||
|
||||
serverTemplates[key] = t
|
||||
}
|
||||
|
||||
// Parse client templates
|
||||
//for key, def := range chatTemplateDefs {
|
||||
// t := text.New(key)
|
||||
// err, _ := t.Parse(def)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("Unabel to parse chat template %q: %v", key, err)
|
||||
// }
|
||||
|
||||
// chatTemplates[key] = t
|
||||
//}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO
|
||||
func LoadChatTemplates() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ExecuteChatTemplate(typeA, typeB int, data interface{}) (string, error) {
|
||||
key := fmt.Sprint(typeA, typeB)
|
||||
t := chatTemplates[key]
|
||||
builder := &strings.Builder{}
|
||||
|
||||
if err := t.Execute(builder, data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return builder.String(), nil
|
||||
}
|
||||
|
||||
func ExecuteServerTemplate(w http.ResponseWriter, key string, data interface{}) error {
|
||||
t, ok := serverTemplates[key]
|
||||
if !ok {
|
||||
return fmt.Errorf("Template with the key %q does not exist", key)
|
||||
}
|
||||
|
||||
return t.Execute(w, data)
|
||||
}
|
||||
18
MovieNight/common/utils.go
Executable file
18
MovieNight/common/utils.go
Executable file
@@ -0,0 +1,18 @@
|
||||
package common
|
||||
|
||||
// Misc utils
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var usernameRegex *regexp.Regexp = regexp.MustCompile(`^[0-9a-zA-Z_-]*[a-zA-Z0-9]+[0-9a-zA-Z_-]*$`)
|
||||
|
||||
const InvalidNameError string = `Invalid name.<br />Name must be between 3 and 36 characters in length; contain only numbers, letters, underscores or dashes; and contain at least one number or letter.<br />Names cannot contain spaces.`
|
||||
|
||||
// IsValidName checks that name is within the correct ranges, follows the regex defined
|
||||
// and is not a valid color name
|
||||
func IsValidName(name string) bool {
|
||||
return 3 <= len(name) && len(name) <= 36 &&
|
||||
usernameRegex.MatchString(name)
|
||||
}
|
||||
Reference in New Issue
Block a user