This commit is contained in:
bel
2021-09-14 06:30:17 -06:00
commit 7ab1723a5e
327 changed files with 127104 additions and 0 deletions

View 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 ""
}

View 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
}

View 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
}

View 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)
}
}
}

View 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
)

View 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, " ")
}

View 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)
}
}
}

View 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...)
}

View 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...)
}

View 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)
}

View 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)
}