Merge branch 'master' into room-access-restrictions
# Conflicts: # main.go # settings.go
This commit is contained in:
commit
857974aef3
|
@ -12,6 +12,9 @@
|
||||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
*.out
|
*.out
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
*.log
|
||||||
|
|
||||||
# GoCode debug file
|
# GoCode debug file
|
||||||
debug
|
debug
|
||||||
|
|
||||||
|
@ -29,3 +32,6 @@ settings.json
|
||||||
|
|
||||||
# Autobuilt wasm files
|
# Autobuilt wasm files
|
||||||
static/main.wasm
|
static/main.wasm
|
||||||
|
|
||||||
|
# tags for vim
|
||||||
|
tags
|
||||||
|
|
24
Makefile
24
Makefile
|
@ -1,15 +1,22 @@
|
||||||
.PHONY: fmt vet get clean
|
TAGS=
|
||||||
|
|
||||||
all: fmt vet MovieNight MovieNight.exe static/main.wasm
|
.PHONY: fmt vet get clean dev setdev test
|
||||||
|
|
||||||
|
all: fmt vet test MovieNight MovieNight.exe static/main.wasm
|
||||||
|
|
||||||
|
setdev:
|
||||||
|
$(eval export TAGS=-tags "dev")
|
||||||
|
|
||||||
|
dev: setdev all
|
||||||
|
|
||||||
MovieNight.exe: *.go common/*.go
|
MovieNight.exe: *.go common/*.go
|
||||||
GOOS=windows GOARCH=amd64 go build -o MovieNight.exe
|
GOOS=windows GOARCH=amd64 go build -o MovieNight.exe $(TAGS)
|
||||||
|
|
||||||
MovieNight: *.go common/*.go
|
MovieNight: *.go common/*.go
|
||||||
GOOS=linux GOARCH=386 go build -o MovieNight
|
GOOS=linux GOARCH=386 go build -o MovieNight $(TAGS)
|
||||||
|
|
||||||
static/main.wasm: wasm/*.go common/*.go
|
static/main.wasm: wasm/*.go common/*.go
|
||||||
GOOS=js GOARCH=wasm go build -o ./static/main.wasm wasm/*.go
|
GOOS=js GOARCH=wasm go build -o ./static/main.wasm $(TAGS) wasm/*.go
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
-rm MovieNight.exe MovieNight ./static/main.wasm
|
-rm MovieNight.exe MovieNight ./static/main.wasm
|
||||||
|
@ -23,5 +30,8 @@ get:
|
||||||
go get golang.org/x/tools/cmd/goimports
|
go get golang.org/x/tools/cmd/goimports
|
||||||
|
|
||||||
vet:
|
vet:
|
||||||
go vet ./...
|
go vet $(TAGS) ./...
|
||||||
GOOS=js GOARCH=wasm go vet ./...
|
GOOS=js GOARCH=wasm go vet $(TAGS) ./...
|
||||||
|
|
||||||
|
test:
|
||||||
|
go test $(TAGS) ./...
|
|
@ -10,22 +10,34 @@ import (
|
||||||
"github.com/zorchenhimer/MovieNight/common"
|
"github.com/zorchenhimer/MovieNight/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
regexSpoiler = regexp.MustCompile(`\|\|(.*?)\|\|`)
|
||||||
|
spoilerStart = `<span class="spoiler" onclick='$(this).removeClass("spoiler").addClass("spoiler-active")'>`
|
||||||
|
spoilerEnd = `</span>`
|
||||||
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
name string // Display name
|
name string // Display name
|
||||||
conn *chatConnection
|
conn *chatConnection
|
||||||
belongsTo *ChatRoom
|
belongsTo *ChatRoom
|
||||||
color string
|
color string
|
||||||
IsMod bool
|
CmdLevel common.CommandLevel
|
||||||
IsAdmin bool
|
|
||||||
IsColorForced bool
|
IsColorForced bool
|
||||||
IsNameForced bool
|
IsNameForced bool
|
||||||
|
regexName *regexp.Regexp
|
||||||
}
|
}
|
||||||
|
|
||||||
//Client has a new message to broadcast
|
//Client has a new message to broadcast
|
||||||
func (cl *Client) NewMsg(data common.ClientData) {
|
func (cl *Client) NewMsg(data common.ClientData) {
|
||||||
switch data.Type {
|
switch data.Type {
|
||||||
|
case common.CdAuth:
|
||||||
|
common.LogChatf("[chat|hidden] <%s> get auth level\n", cl.name)
|
||||||
|
err := cl.SendChatData(common.NewChatHiddenMessage(data.Type, cl.CmdLevel))
|
||||||
|
if err != nil {
|
||||||
|
common.LogErrorf("Error sending auth level to client: %v\n", err)
|
||||||
|
}
|
||||||
case common.CdUsers:
|
case common.CdUsers:
|
||||||
fmt.Printf("[chat|hidden] <%s> get list of users\n", cl.name)
|
common.LogChatf("[chat|hidden] <%s> get list of users\n", cl.name)
|
||||||
|
|
||||||
names := chat.GetNames()
|
names := chat.GetNames()
|
||||||
idx := -1
|
idx := -1
|
||||||
|
@ -37,13 +49,17 @@ func (cl *Client) NewMsg(data common.ClientData) {
|
||||||
|
|
||||||
err := cl.SendChatData(common.NewChatHiddenMessage(data.Type, append(names[:idx], names[idx+1:]...)))
|
err := cl.SendChatData(common.NewChatHiddenMessage(data.Type, append(names[:idx], names[idx+1:]...)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error sending chat data: %v\n", err)
|
common.LogErrorf("Error sending chat data: %v\n", err)
|
||||||
}
|
}
|
||||||
case common.CdMessage:
|
case common.CdMessage:
|
||||||
msg := html.EscapeString(data.Message)
|
msg := html.EscapeString(data.Message)
|
||||||
msg = removeDumbSpaces(msg)
|
msg = removeDumbSpaces(msg)
|
||||||
msg = strings.Trim(msg, " ")
|
msg = strings.Trim(msg, " ")
|
||||||
|
|
||||||
|
// Add the spoiler tag outside of the command vs message statement
|
||||||
|
// because the /me command outputs to the messages
|
||||||
|
msg = addSpoilerTags(msg)
|
||||||
|
|
||||||
// Don't send zero-length messages
|
// Don't send zero-length messages
|
||||||
if len(msg) == 0 {
|
if len(msg) == 0 {
|
||||||
return
|
return
|
||||||
|
@ -60,10 +76,10 @@ func (cl *Client) NewMsg(data common.ClientData) {
|
||||||
if response != "" {
|
if response != "" {
|
||||||
err := cl.SendChatData(common.NewChatMessage("", "",
|
err := cl.SendChatData(common.NewChatMessage("", "",
|
||||||
common.ParseEmotes(response),
|
common.ParseEmotes(response),
|
||||||
common.CmdUser,
|
common.CmdlUser,
|
||||||
common.MsgCommandResponse))
|
common.MsgCommandResponse))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error command results %v\n", err)
|
common.LogErrorf("Error command results %v\n", err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -74,10 +90,10 @@ func (cl *Client) NewMsg(data common.ClientData) {
|
||||||
msg = msg[0:400]
|
msg = msg[0:400]
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("[chat] <%s> %q\n", cl.name, msg)
|
common.LogChatf("[chat] <%s> %q\n", cl.name, msg)
|
||||||
|
|
||||||
// Enable links for mods and admins
|
// Enable links for mods and admins
|
||||||
if cl.IsMod || cl.IsAdmin {
|
if cl.CmdLevel >= common.CmdlMod {
|
||||||
msg = formatLinks(msg)
|
msg = formatLinks(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,7 +105,11 @@ func (cl *Client) NewMsg(data common.ClientData) {
|
||||||
func (cl *Client) SendChatData(data common.ChatData) error {
|
func (cl *Client) SendChatData(data common.ChatData) error {
|
||||||
// Colorize name on chat messages
|
// Colorize name on chat messages
|
||||||
if data.Type == common.DTChat {
|
if data.Type == common.DTChat {
|
||||||
data = replaceColorizedName(data, cl)
|
var err error
|
||||||
|
data = cl.replaceColorizedName(data)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not colorize name: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cd, err := data.ToJSON()
|
cd, err := data.ToJSON()
|
||||||
|
@ -108,7 +128,7 @@ func (cl *Client) Send(data common.ChatDataJSON) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *Client) SendServerMessage(s string) error {
|
func (cl *Client) SendServerMessage(s string) error {
|
||||||
err := cl.SendChatData(common.NewChatMessage("", ColorServerMessage, s, common.CmdUser, common.MsgServer))
|
err := cl.SendChatData(common.NewChatMessage("", ColorServerMessage, s, common.CmdlUser, common.MsgServer))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could send server message to %s: message - %#v: %v", cl.name, s, err)
|
return fmt.Errorf("could send server message to %s: message - %#v: %v", cl.name, s, err)
|
||||||
}
|
}
|
||||||
|
@ -146,17 +166,37 @@ func (cl *Client) Me(msg string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *Client) Mod() {
|
func (cl *Client) Mod() {
|
||||||
cl.IsMod = true
|
if cl.CmdLevel < common.CmdlMod {
|
||||||
|
cl.CmdLevel = common.CmdlMod
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *Client) Unmod() {
|
func (cl *Client) Unmod() {
|
||||||
cl.IsMod = false
|
cl.CmdLevel = common.CmdlUser
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *Client) Host() string {
|
func (cl *Client) Host() string {
|
||||||
return cl.conn.Host()
|
return cl.conn.Host()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cl *Client) setName(s string) error {
|
||||||
|
regex, err := regexp.Compile(fmt.Sprintf("(%s|@%s)", s, s))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not compile regex: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cl.name = s
|
||||||
|
cl.regexName = regex
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *Client) replaceColorizedName(chatData common.ChatData) common.ChatData {
|
||||||
|
data := chatData.Data.(common.DataMessage)
|
||||||
|
data.Message = cl.regexName.ReplaceAllString(data.Message, `<span class="mention">$1</span>`)
|
||||||
|
chatData.Data = data
|
||||||
|
return chatData
|
||||||
|
}
|
||||||
|
|
||||||
var dumbSpaces = []string{
|
var dumbSpaces = []string{
|
||||||
"\n",
|
"\n",
|
||||||
"\t",
|
"\t",
|
||||||
|
@ -180,12 +220,6 @@ func removeDumbSpaces(msg string) string {
|
||||||
return newMsg
|
return newMsg
|
||||||
}
|
}
|
||||||
|
|
||||||
func replaceColorizedName(chatData common.ChatData, client *Client) common.ChatData {
|
func addSpoilerTags(msg string) string {
|
||||||
data := chatData.Data.(common.DataMessage)
|
return regexSpoiler.ReplaceAllString(msg, fmt.Sprintf(`%s$1%s`, spoilerStart, spoilerEnd))
|
||||||
|
|
||||||
data.Message = regexp.MustCompile(fmt.Sprintf(`(%s|@%s)`, client.name, client.name)).
|
|
||||||
ReplaceAllString(data.Message, `<span class="mention">$1</span>`)
|
|
||||||
|
|
||||||
chatData.Data = data
|
|
||||||
return chatData
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestClient_addSpoilerTag(t *testing.T) {
|
||||||
|
data := [][]string{
|
||||||
|
{"||||", spoilerStart + spoilerEnd},
|
||||||
|
{"|||||", spoilerStart + spoilerEnd + "|"},
|
||||||
|
{"||||||", spoilerStart + spoilerEnd + "||"},
|
||||||
|
{"|||||||", spoilerStart + spoilerEnd + "|||"},
|
||||||
|
{"||||||||", spoilerStart + spoilerEnd + spoilerStart + spoilerEnd},
|
||||||
|
{"||test||", spoilerStart + "test" + spoilerEnd},
|
||||||
|
{"|| ||", spoilerStart + " " + spoilerEnd},
|
||||||
|
{"|s|||", "|s|||"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range data {
|
||||||
|
s := addSpoilerTags(data[i][0])
|
||||||
|
if s != data[i][1] {
|
||||||
|
t.Errorf("expected %#v, got %#v with %#v", data[i][1], s, data[i][0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
184
chatcommands.go
184
chatcommands.go
|
@ -52,29 +52,28 @@ var commands = &CommandControl{
|
||||||
common.CNAuth.String(): Command{
|
common.CNAuth.String(): Command{
|
||||||
HelpText: "Authenticate to admin",
|
HelpText: "Authenticate to admin",
|
||||||
Function: func(cl *Client, args []string) string {
|
Function: func(cl *Client, args []string) string {
|
||||||
if cl.IsAdmin {
|
if cl.CmdLevel == common.CmdlAdmin {
|
||||||
return "You are already authenticated."
|
return "You are already authenticated."
|
||||||
}
|
}
|
||||||
|
|
||||||
pw := html.UnescapeString(strings.Join(args, " "))
|
pw := html.UnescapeString(strings.Join(args, " "))
|
||||||
|
|
||||||
if settings.AdminPassword == pw {
|
if settings.AdminPassword == pw {
|
||||||
cl.IsMod = true
|
cl.CmdLevel = common.CmdlAdmin
|
||||||
cl.IsAdmin = true
|
|
||||||
cl.belongsTo.AddModNotice(cl.name + " used the admin password")
|
cl.belongsTo.AddModNotice(cl.name + " used the admin password")
|
||||||
fmt.Printf("[auth] %s used the admin password\n", cl.name)
|
common.LogInfof("[auth] %s used the admin password\n", cl.name)
|
||||||
return "Admin rights granted."
|
return "Admin rights granted."
|
||||||
}
|
}
|
||||||
|
|
||||||
if cl.belongsTo.redeemModPass(pw) {
|
if cl.belongsTo.redeemModPass(pw) {
|
||||||
cl.IsMod = true
|
cl.CmdLevel = common.CmdlMod
|
||||||
cl.belongsTo.AddModNotice(cl.name + " used a mod password")
|
cl.belongsTo.AddModNotice(cl.name + " used a mod password")
|
||||||
fmt.Printf("[auth] %s used a mod password\n", cl.name)
|
common.LogInfof("[auth] %s used a mod password\n", cl.name)
|
||||||
return "Moderator privileges granted."
|
return "Moderator privileges granted."
|
||||||
}
|
}
|
||||||
|
|
||||||
cl.belongsTo.AddModNotice(cl.name + " attempted to auth without success")
|
cl.belongsTo.AddModNotice(cl.name + " attempted to auth without success")
|
||||||
fmt.Printf("[auth] %s gave an invalid password\n", cl.name)
|
common.LogInfof("[auth] %s gave an invalid password\n", cl.name)
|
||||||
return "Invalid password."
|
return "Invalid password."
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -94,20 +93,22 @@ var commands = &CommandControl{
|
||||||
return "Missing name to change to."
|
return "Missing name to change to."
|
||||||
}
|
}
|
||||||
|
|
||||||
newName := args[0]
|
newName := strings.TrimLeft(args[0], "@")
|
||||||
oldName := cl.name
|
oldName := cl.name
|
||||||
forced := false
|
forced := false
|
||||||
|
|
||||||
|
// Two arguments to force a name change on another user: `/nick OldName NewName`
|
||||||
if len(args) == 2 {
|
if len(args) == 2 {
|
||||||
if !cl.IsAdmin {
|
if cl.CmdLevel != common.CmdlAdmin {
|
||||||
return "Only admins can do that PeepoSus"
|
return "Only admins can do that PeepoSus"
|
||||||
}
|
}
|
||||||
|
|
||||||
oldName = args[0]
|
oldName = strings.TrimLeft(args[0], "@")
|
||||||
newName = args[1]
|
newName = strings.TrimLeft(args[1], "@")
|
||||||
forced = true
|
forced = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(args) == 1 && cl.IsNameForced && !cl.IsAdmin {
|
if len(args) == 1 && cl.IsNameForced && cl.CmdLevel != common.CmdlAdmin {
|
||||||
return "You cannot change your name once it has been changed by an admin."
|
return "You cannot change your name once it has been changed by an admin."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,22 +179,23 @@ var commands = &CommandControl{
|
||||||
common.CNUnmod.String(): Command{
|
common.CNUnmod.String(): Command{
|
||||||
HelpText: "Revoke a user's moderator privilages. Moderators can only unmod themselves.",
|
HelpText: "Revoke a user's moderator privilages. Moderators can only unmod themselves.",
|
||||||
Function: func(cl *Client, args []string) string {
|
Function: func(cl *Client, args []string) string {
|
||||||
if len(args) > 0 && !cl.IsAdmin && cl.name != args[0] {
|
if len(args) > 0 && cl.CmdLevel != common.CmdlAdmin && cl.name != args[0] {
|
||||||
return "You can only unmod yourself, not others."
|
return "You can only unmod yourself, not others."
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(args) == 0 || (len(args) == 1 && args[0] == cl.name) {
|
if len(args) == 0 || (len(args) == 1 && strings.TrimLeft(args[0], "@") == cl.name) {
|
||||||
cl.Unmod()
|
cl.Unmod()
|
||||||
cl.belongsTo.AddModNotice(cl.name + " has unmodded themselves")
|
cl.belongsTo.AddModNotice(cl.name + " has unmodded themselves")
|
||||||
return "You have unmodded yourself."
|
return "You have unmodded yourself."
|
||||||
}
|
}
|
||||||
|
name := strings.TrimLeft(args[0], "@")
|
||||||
|
|
||||||
if err := cl.belongsTo.Unmod(args[0]); err != nil {
|
if err := cl.belongsTo.Unmod(name); err != nil {
|
||||||
return err.Error()
|
return err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
cl.belongsTo.AddModNotice(cl.name + " has unmodded " + args[0])
|
cl.belongsTo.AddModNotice(cl.name + " has unmodded " + name)
|
||||||
return fmt.Sprintf(`%s has been unmodded.`, args[0])
|
return ""
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -203,7 +205,7 @@ var commands = &CommandControl{
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return "Missing name to kick."
|
return "Missing name to kick."
|
||||||
}
|
}
|
||||||
return cl.belongsTo.Kick(args[0])
|
return cl.belongsTo.Kick(strings.TrimLeft(args[0], "@"))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -213,8 +215,10 @@ var commands = &CommandControl{
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return "missing name to ban."
|
return "missing name to ban."
|
||||||
}
|
}
|
||||||
fmt.Printf("[ban] Attempting to ban %s\n", strings.Join(args, ""))
|
|
||||||
return cl.belongsTo.Ban(args[0])
|
name := strings.TrimLeft(args[0], "@")
|
||||||
|
common.LogInfof("[ban] Attempting to ban %s\n", name)
|
||||||
|
return cl.belongsTo.Ban(name)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -224,13 +228,14 @@ var commands = &CommandControl{
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return "missing name to unban."
|
return "missing name to unban."
|
||||||
}
|
}
|
||||||
fmt.Printf("[ban] Attempting to unban %s\n", strings.Join(args, ""))
|
name := strings.TrimLeft(args[0], "@")
|
||||||
|
common.LogInfof("[ban] Attempting to unban %s\n", name)
|
||||||
|
|
||||||
err := settings.RemoveBan(args[0])
|
err := settings.RemoveBan(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err.Error()
|
return err.Error()
|
||||||
}
|
}
|
||||||
cl.belongsTo.AddModNotice(cl.name + " has unbanned " + args[0])
|
cl.belongsTo.AddModNotice(cl.name + " has unbanned " + name)
|
||||||
return ""
|
return ""
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -238,7 +243,7 @@ var commands = &CommandControl{
|
||||||
common.CNPurge.String(): Command{
|
common.CNPurge.String(): Command{
|
||||||
HelpText: "Purge the chat.",
|
HelpText: "Purge the chat.",
|
||||||
Function: func(cl *Client, args []string) string {
|
Function: func(cl *Client, args []string) string {
|
||||||
fmt.Println("[purge] clearing chat")
|
common.LogInfoln("[purge] clearing chat")
|
||||||
cl.belongsTo.AddCmdMsg(common.CmdPurgeChat, nil)
|
cl.belongsTo.AddCmdMsg(common.CmdPurgeChat, nil)
|
||||||
return ""
|
return ""
|
||||||
},
|
},
|
||||||
|
@ -265,11 +270,13 @@ var commands = &CommandControl{
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return "Missing user to mod."
|
return "Missing user to mod."
|
||||||
}
|
}
|
||||||
if err := cl.belongsTo.Mod(args[0]); err != nil {
|
|
||||||
|
name := strings.TrimLeft(args[0], "@")
|
||||||
|
if err := cl.belongsTo.Mod(name); err != nil {
|
||||||
return err.Error()
|
return err.Error()
|
||||||
}
|
}
|
||||||
cl.belongsTo.AddModNotice(cl.name + " has modded " + args[0])
|
cl.belongsTo.AddModNotice(cl.name + " has modded " + name)
|
||||||
return fmt.Sprintf(`%s has been modded.`, args[0])
|
return ""
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -288,12 +295,12 @@ var commands = &CommandControl{
|
||||||
cl.SendServerMessage("Reloading emotes")
|
cl.SendServerMessage("Reloading emotes")
|
||||||
num, err := common.LoadEmotes()
|
num, err := common.LoadEmotes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Unbale to reload emotes: %s\n", err)
|
common.LogErrorf("Unbale to reload emotes: %s\n", err)
|
||||||
return fmt.Sprintf("ERROR: %s", err)
|
return fmt.Sprintf("ERROR: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cl.belongsTo.AddModNotice(cl.name + " has reloaded emotes")
|
cl.belongsTo.AddModNotice(cl.name + " has reloaded emotes")
|
||||||
fmt.Printf("Loaded %d emotes\n", num)
|
common.LogInfof("Loaded %d emotes\n", num)
|
||||||
return fmt.Sprintf("Emotes loaded: %d", num)
|
return fmt.Sprintf("Emotes loaded: %d", num)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -367,17 +374,17 @@ var commands = &CommandControl{
|
||||||
},
|
},
|
||||||
|
|
||||||
common.CNIP.String(): Command{
|
common.CNIP.String(): Command{
|
||||||
HelpText: "list users and IP in the server console",
|
HelpText: "List users and IP in the server console. Requires logging level to be set to info or above.",
|
||||||
Function: func(cl *Client, args []string) string {
|
Function: func(cl *Client, args []string) string {
|
||||||
cl.belongsTo.clientsMtx.Lock()
|
cl.belongsTo.clientsMtx.Lock()
|
||||||
fmt.Println("Clients:")
|
common.LogInfoln("Clients:")
|
||||||
for uuid, client := range cl.belongsTo.clients {
|
for uuid, client := range cl.belongsTo.clients {
|
||||||
fmt.Printf(" [%s] %s %s\n", uuid, client.name, client.conn.Host())
|
common.LogInfof(" [%s] %s %s\n", uuid, client.name, client.conn.Host())
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("TmpConn:")
|
common.LogInfoln("TmpConn:")
|
||||||
for uuid, conn := range cl.belongsTo.tempConn {
|
for uuid, conn := range cl.belongsTo.tempConn {
|
||||||
fmt.Printf(" [%s] %s\n", uuid, conn.Host())
|
common.LogInfof(" [%s] %s\n", uuid, conn.Host())
|
||||||
}
|
}
|
||||||
cl.belongsTo.clientsMtx.Unlock()
|
cl.belongsTo.clientsMtx.Unlock()
|
||||||
return "see console for output"
|
return "see console for output"
|
||||||
|
@ -392,44 +399,45 @@ func (cc *CommandControl) RunCommand(command string, args []string, sender *Clie
|
||||||
|
|
||||||
// Look for user command
|
// Look for user command
|
||||||
if userCmd, ok := cc.user[cmd]; ok {
|
if userCmd, ok := cc.user[cmd]; ok {
|
||||||
fmt.Printf("[user] %s /%s %s\n", sender.name, command, strings.Join(args, " "))
|
common.LogInfof("[user] %s /%s %s\n", sender.name, command, strings.Join(args, " "))
|
||||||
return userCmd.Function(sender, args)
|
return userCmd.Function(sender, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for mod command
|
// Look for mod command
|
||||||
if modCmd, ok := cc.mod[cmd]; ok {
|
if modCmd, ok := cc.mod[cmd]; ok {
|
||||||
if sender.IsMod || sender.IsAdmin {
|
if sender.CmdLevel >= common.CmdlMod {
|
||||||
fmt.Printf("[mod] %s /%s %s\n", sender.name, command, strings.Join(args, " "))
|
common.LogInfof("[mod] %s /%s %s\n", sender.name, command, strings.Join(args, " "))
|
||||||
return modCmd.Function(sender, args)
|
return modCmd.Function(sender, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("[mod REJECTED] %s /%s %s\n", sender.name, command, strings.Join(args, " "))
|
common.LogInfof("[mod REJECTED] %s /%s %s\n", sender.name, command, strings.Join(args, " "))
|
||||||
return "You are not a mod Jebaited"
|
return "You are not a mod Jebaited"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for admin command
|
// Look for admin command
|
||||||
if adminCmd, ok := cc.admin[cmd]; ok {
|
if adminCmd, ok := cc.admin[cmd]; ok {
|
||||||
if sender.IsAdmin {
|
if sender.CmdLevel == common.CmdlAdmin {
|
||||||
fmt.Printf("[admin] %s /%s %s\n", sender.name, command, strings.Join(args, " "))
|
common.LogInfof("[admin] %s /%s %s\n", sender.name, command, strings.Join(args, " "))
|
||||||
return adminCmd.Function(sender, args)
|
return adminCmd.Function(sender, args)
|
||||||
}
|
}
|
||||||
fmt.Printf("[admin REJECTED] %s /%s %s\n", sender.name, command, strings.Join(args, " "))
|
common.LogInfof("[admin REJECTED] %s /%s %s\n", sender.name, command, strings.Join(args, " "))
|
||||||
return "You are not the admin Jebaited"
|
return "You are not the admin Jebaited"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Command not found
|
// Command not found
|
||||||
fmt.Printf("[cmd] %s /%s %s\n", sender.name, command, strings.Join(args, " "))
|
common.LogInfof("[cmd] %s /%s %s\n", sender.name, command, strings.Join(args, " "))
|
||||||
return "Invalid command."
|
return "Invalid command."
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdHelp(cl *Client, args []string) string {
|
func cmdHelp(cl *Client, args []string) string {
|
||||||
url := "/help"
|
url := "/help"
|
||||||
if cl.IsMod {
|
|
||||||
url = "/help?mod=1"
|
if cl.CmdLevel >= common.CmdlMod {
|
||||||
|
url += "?mod=1"
|
||||||
}
|
}
|
||||||
|
|
||||||
if cl.IsAdmin {
|
if cl.CmdLevel == common.CmdlAdmin {
|
||||||
url = "/help?mod=1&admin=1"
|
url += "&admin=1"
|
||||||
}
|
}
|
||||||
|
|
||||||
cl.SendChatData(common.NewChatCommand(common.CmdHelp, []string{url}))
|
cl.SendChatData(common.NewChatCommand(common.CmdHelp, []string{url}))
|
||||||
|
@ -439,11 +447,11 @@ func cmdHelp(cl *Client, args []string) string {
|
||||||
func getHelp(lvl common.CommandLevel) map[string]string {
|
func getHelp(lvl common.CommandLevel) map[string]string {
|
||||||
var cmdList map[string]Command
|
var cmdList map[string]Command
|
||||||
switch lvl {
|
switch lvl {
|
||||||
case common.CmdUser:
|
case common.CmdlUser:
|
||||||
cmdList = commands.user
|
cmdList = commands.user
|
||||||
case common.CmdMod:
|
case common.CmdlMod:
|
||||||
cmdList = commands.mod
|
cmdList = commands.mod
|
||||||
case common.CmdAdmin:
|
case common.CmdlAdmin:
|
||||||
cmdList = commands.admin
|
cmdList = commands.admin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -459,22 +467,71 @@ func getHelp(lvl common.CommandLevel) map[string]string {
|
||||||
var cmdColor = Command{
|
var cmdColor = Command{
|
||||||
HelpText: "Change user color.",
|
HelpText: "Change user color.",
|
||||||
Function: func(cl *Client, args []string) string {
|
Function: func(cl *Client, args []string) string {
|
||||||
|
if len(args) > 2 {
|
||||||
|
return "Too many arguments!"
|
||||||
|
}
|
||||||
|
|
||||||
// If the caller is priviledged enough, they can change the color of another user
|
// If the caller is priviledged enough, they can change the color of another user
|
||||||
if len(args) == 2 && (cl.IsMod || cl.IsAdmin) {
|
if len(args) == 2 {
|
||||||
color := ""
|
if cl.CmdLevel == common.CmdlUser {
|
||||||
name := ""
|
return "You cannot change someone else's color. PeepoSus"
|
||||||
for _, s := range args {
|
}
|
||||||
if common.IsValidColor(s) {
|
|
||||||
color = s
|
name, color := "", ""
|
||||||
} else {
|
|
||||||
name = s
|
if strings.ToLower(args[0]) == strings.ToLower(args[1]) ||
|
||||||
}
|
(common.IsValidColor(args[0]) && common.IsValidColor(args[1])) {
|
||||||
|
return "Name and color are ambiguous. Prefix the name with '@' or color with '#'"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for explicit name
|
||||||
|
if strings.HasPrefix(args[0], "@") {
|
||||||
|
name = strings.TrimLeft(args[0], "@")
|
||||||
|
color = args[1]
|
||||||
|
common.LogDebugln("[color:mod] Found explicit name: ", name)
|
||||||
|
} else if strings.HasPrefix(args[1], "@") {
|
||||||
|
name = strings.TrimLeft(args[1], "@")
|
||||||
|
color = args[0]
|
||||||
|
common.LogDebugln("[color:mod] Found explicit name: ", name)
|
||||||
|
|
||||||
|
// Check for explicit color
|
||||||
|
} else if strings.HasPrefix(args[0], "#") {
|
||||||
|
name = strings.TrimPrefix(args[1], "@") // this shouldn't be needed, but just in case.
|
||||||
|
color = args[0]
|
||||||
|
common.LogDebugln("[color:mod] Found explicit color: ", color)
|
||||||
|
} else if strings.HasPrefix(args[1], "#") {
|
||||||
|
name = strings.TrimPrefix(args[0], "@") // this shouldn't be needed, but just in case.
|
||||||
|
color = args[1]
|
||||||
|
common.LogDebugln("[color:mod] Found explicit color: ", color)
|
||||||
|
|
||||||
|
// Guess
|
||||||
|
} else if common.IsValidColor(args[0]) {
|
||||||
|
name = strings.TrimPrefix(args[1], "@")
|
||||||
|
color = args[0]
|
||||||
|
common.LogDebugln("[color:mod] Guessed name: ", name, " and color: ", color)
|
||||||
|
} else if common.IsValidColor(args[1]) {
|
||||||
|
name = strings.TrimPrefix(args[0], "@")
|
||||||
|
color = args[1]
|
||||||
|
common.LogDebugln("[color:mod] Guessed name: ", name, " and color: ", color)
|
||||||
|
}
|
||||||
|
|
||||||
|
if name == "" {
|
||||||
|
return "Cannot determine name. Prefix name with @."
|
||||||
}
|
}
|
||||||
if color == "" {
|
if color == "" {
|
||||||
fmt.Printf("[color:mod] %s missing color\n", cl.name)
|
return "Cannot determine color. Prefix name with @."
|
||||||
|
}
|
||||||
|
|
||||||
|
if color == "" {
|
||||||
|
common.LogInfof("[color:mod] %s missing color\n", cl.name)
|
||||||
return "Missing color"
|
return "Missing color"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if name == "" {
|
||||||
|
common.LogInfof("[color:mod] %s missing name\n", cl.name)
|
||||||
|
return "Missing name"
|
||||||
|
}
|
||||||
|
|
||||||
if err := cl.belongsTo.ForceColorChange(name, color); err != nil {
|
if err := cl.belongsTo.ForceColorChange(name, color); err != nil {
|
||||||
return err.Error()
|
return err.Error()
|
||||||
}
|
}
|
||||||
|
@ -484,7 +541,7 @@ var cmdColor = Command{
|
||||||
// Don't allow an unprivilaged user to change their color if
|
// Don't allow an unprivilaged user to change their color if
|
||||||
// it was changed by a mod
|
// it was changed by a mod
|
||||||
if cl.IsColorForced {
|
if cl.IsColorForced {
|
||||||
fmt.Printf("[color] %s tried to change a forced color\n", cl.name)
|
common.LogInfof("[color] %s tried to change a forced color\n", cl.name)
|
||||||
return "You are not allowed to change your color."
|
return "You are not allowed to change your color."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -499,7 +556,7 @@ var cmdColor = Command{
|
||||||
}
|
}
|
||||||
|
|
||||||
cl.color = args[0]
|
cl.color = args[0]
|
||||||
fmt.Printf("[color] %s new color: %s\n", cl.name, cl.color)
|
common.LogInfof("[color] %s new color: %s\n", cl.name, cl.color)
|
||||||
return "Color changed successfully."
|
return "Color changed successfully."
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -507,6 +564,9 @@ var cmdColor = Command{
|
||||||
var cmdWhoAmI = Command{
|
var cmdWhoAmI = Command{
|
||||||
HelpText: "Shows debug user info",
|
HelpText: "Shows debug user info",
|
||||||
Function: func(cl *Client, args []string) string {
|
Function: func(cl *Client, args []string) string {
|
||||||
return fmt.Sprintf("Name: %s IsMod: %t IsAdmin: %t", cl.name, cl.IsMod, cl.IsAdmin)
|
return fmt.Sprintf("Name: %s IsMod: %t IsAdmin: %t",
|
||||||
|
cl.name,
|
||||||
|
cl.CmdLevel >= common.CmdlMod,
|
||||||
|
cl.CmdLevel == common.CmdlAdmin)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
103
chatroom.go
103
chatroom.go
|
@ -45,7 +45,7 @@ func newChatRoom() (*ChatRoom, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error loading emotes: %s", err)
|
return nil, fmt.Errorf("error loading emotes: %s", err)
|
||||||
}
|
}
|
||||||
fmt.Printf("Loaded %d emotes\n", num)
|
common.LogInfof("Loaded %d emotes\n", num)
|
||||||
|
|
||||||
//the "heartbeat" for broadcasting messages
|
//the "heartbeat" for broadcasting messages
|
||||||
go cr.Broadcast()
|
go cr.Broadcast()
|
||||||
|
@ -85,7 +85,7 @@ func (cr *ChatRoom) Join(name, uid string) (*Client, error) {
|
||||||
return nil, errors.New("connection is missing from temp connections")
|
return nil, errors.New("connection is missing from temp connections")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !common.IsValidName(name) || common.IsValidColor(name) {
|
if !common.IsValidName(name) {
|
||||||
return nil, UserFormatError{Name: name}
|
return nil, UserFormatError{Name: name}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,12 +98,16 @@ func (cr *ChatRoom) Join(name, uid string) (*Client, error) {
|
||||||
|
|
||||||
conn.clientName = name
|
conn.clientName = name
|
||||||
client := &Client{
|
client := &Client{
|
||||||
name: name,
|
|
||||||
conn: conn,
|
conn: conn,
|
||||||
belongsTo: cr,
|
belongsTo: cr,
|
||||||
color: common.RandomColor(),
|
color: common.RandomColor(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err := client.setName(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not set client name to %#v: %v", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
host := client.Host()
|
host := client.Host()
|
||||||
|
|
||||||
if banned, names := settings.IsBanned(host); banned {
|
if banned, names := settings.IsBanned(host); banned {
|
||||||
|
@ -113,10 +117,10 @@ func (cr *ChatRoom) Join(name, uid string) (*Client, error) {
|
||||||
cr.clients[uid] = client
|
cr.clients[uid] = client
|
||||||
delete(cr.tempConn, uid)
|
delete(cr.tempConn, uid)
|
||||||
|
|
||||||
fmt.Printf("[join] %s %s\n", host, name)
|
common.LogChatf("[join] %s %s\n", host, name)
|
||||||
playingCommand, err := common.NewChatCommand(common.CmdPlaying, []string{cr.playing, cr.playingLink}).ToJSON()
|
playingCommand, err := common.NewChatCommand(common.CmdPlaying, []string{cr.playing, cr.playingLink}).ToJSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Unable to encode playing command on join: %s\n", err)
|
common.LogErrorf("Unable to encode playing command on join: %s\n", err)
|
||||||
} else {
|
} else {
|
||||||
client.Send(playingCommand)
|
client.Send(playingCommand)
|
||||||
}
|
}
|
||||||
|
@ -132,7 +136,7 @@ func (cr *ChatRoom) Leave(name, color string) {
|
||||||
|
|
||||||
client, suid, err := cr.getClient(name)
|
client, suid, err := cr.getClient(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("[leave] Unable to get client suid %v\n", err)
|
common.LogErrorf("[leave] Unable to get client suid %v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
host := client.Host()
|
host := client.Host()
|
||||||
|
@ -141,7 +145,7 @@ func (cr *ChatRoom) Leave(name, color string) {
|
||||||
cr.delClient(suid)
|
cr.delClient(suid)
|
||||||
|
|
||||||
cr.AddEventMsg(common.EvLeave, name, color)
|
cr.AddEventMsg(common.EvLeave, name, color)
|
||||||
fmt.Printf("[leave] %s %s\n", host, name)
|
common.LogChatf("[leave] %s %s\n", host, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// kicked from the chatroom
|
// kicked from the chatroom
|
||||||
|
@ -154,11 +158,11 @@ func (cr *ChatRoom) Kick(name string) string {
|
||||||
return "Unable to get client for name " + name
|
return "Unable to get client for name " + name
|
||||||
}
|
}
|
||||||
|
|
||||||
if client.IsMod {
|
if client.CmdLevel == common.CmdlMod {
|
||||||
return "You cannot kick another mod."
|
return "You cannot kick another mod."
|
||||||
}
|
}
|
||||||
|
|
||||||
if client.IsAdmin {
|
if client.CmdLevel == common.CmdlAdmin {
|
||||||
return "Jebaited No."
|
return "Jebaited No."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,7 +172,7 @@ func (cr *ChatRoom) Kick(name string) string {
|
||||||
cr.delClient(suid)
|
cr.delClient(suid)
|
||||||
|
|
||||||
cr.AddEventMsg(common.EvKick, name, color)
|
cr.AddEventMsg(common.EvKick, name, color)
|
||||||
fmt.Printf("[kick] %s %s has been kicked\n", host, name)
|
common.LogInfof("[kick] %s %s has been kicked\n", host, name)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,11 +182,11 @@ func (cr *ChatRoom) Ban(name string) string {
|
||||||
|
|
||||||
client, suid, err := cr.getClient(name)
|
client, suid, err := cr.getClient(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("[ban] Unable to get client for name %q\n", name)
|
common.LogErrorf("[ban] Unable to get client for name %q\n", name)
|
||||||
return "Cannot find that name"
|
return "Cannot find that name"
|
||||||
}
|
}
|
||||||
|
|
||||||
if client.IsAdmin {
|
if client.CmdLevel == common.CmdlAdmin {
|
||||||
return "You cannot ban an admin Jebaited"
|
return "You cannot ban an admin Jebaited"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,7 +210,7 @@ func (cr *ChatRoom) Ban(name string) string {
|
||||||
|
|
||||||
err = settings.AddBan(host, names)
|
err = settings.AddBan(host, names)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("[BAN] Error banning %q: %s\n", name, err)
|
common.LogErrorf("[BAN] Error banning %q: %s\n", name, err)
|
||||||
cr.AddEventMsg(common.EvKick, name, color)
|
cr.AddEventMsg(common.EvKick, name, color)
|
||||||
} else {
|
} else {
|
||||||
cr.AddEventMsg(common.EvBan, name, color)
|
cr.AddEventMsg(common.EvBan, name, color)
|
||||||
|
@ -226,18 +230,10 @@ func (cr *ChatRoom) AddMsg(from *Client, isAction, isServer bool, msg string) {
|
||||||
t = common.MsgServer
|
t = common.MsgServer
|
||||||
}
|
}
|
||||||
|
|
||||||
lvl := common.CmdUser
|
|
||||||
if from.IsMod {
|
|
||||||
lvl = common.CmdMod
|
|
||||||
}
|
|
||||||
if from.IsAdmin {
|
|
||||||
lvl = common.CmdAdmin
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case cr.queue <- common.NewChatMessage(from.name, from.color, msg, lvl, t):
|
case cr.queue <- common.NewChatMessage(from.name, from.color, msg, from.CmdLevel, t):
|
||||||
default:
|
default:
|
||||||
fmt.Println("Unable to queue chat message. Channel full.")
|
common.LogErrorln("Unable to queue chat message. Channel full.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,15 +241,15 @@ func (cr *ChatRoom) AddCmdMsg(command common.CommandType, args []string) {
|
||||||
select {
|
select {
|
||||||
case cr.queue <- common.NewChatCommand(command, args):
|
case cr.queue <- common.NewChatCommand(command, args):
|
||||||
default:
|
default:
|
||||||
fmt.Println("Unable to queue command message. Channel full.")
|
common.LogErrorln("Unable to queue command message. Channel full.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cr *ChatRoom) AddModNotice(message string) {
|
func (cr *ChatRoom) AddModNotice(message string) {
|
||||||
select {
|
select {
|
||||||
case cr.modqueue <- common.NewChatMessage("", "", message, common.CmdUser, common.MsgNotice):
|
case cr.modqueue <- common.NewChatMessage("", "", message, common.CmdlUser, common.MsgNotice):
|
||||||
default:
|
default:
|
||||||
fmt.Println("Unable to queue notice. Channel full.")
|
common.LogErrorln("Unable to queue notice. Channel full.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,7 +257,7 @@ func (cr *ChatRoom) AddEventMsg(event common.EventType, name, color string) {
|
||||||
select {
|
select {
|
||||||
case cr.queue <- common.NewChatEvent(event, name, color):
|
case cr.queue <- common.NewChatEvent(event, name, color):
|
||||||
default:
|
default:
|
||||||
fmt.Println("Unable to queue event message. Channel full.")
|
common.LogErrorln("Unable to queue event message. Channel full.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,8 +284,10 @@ func (cr *ChatRoom) Mod(name string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
client.IsMod = true
|
if client.CmdLevel < common.CmdlMod {
|
||||||
client.SendServerMessage(`You have been modded.`)
|
client.CmdLevel = common.CmdlMod
|
||||||
|
client.SendServerMessage(`You have been modded.`)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,7 +314,7 @@ func (cr *ChatRoom) Broadcast() {
|
||||||
send := func(data common.ChatData, client *Client) {
|
send := func(data common.ChatData, client *Client) {
|
||||||
err := client.SendChatData(data)
|
err := client.SendChatData(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error sending data to client: %v\n", err)
|
common.LogErrorf("Error sending data to client: %v\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,26 +326,36 @@ func (cr *ChatRoom) Broadcast() {
|
||||||
go send(msg, client)
|
go send(msg, client)
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := msg.ToJSON()
|
// Only send Chat and Event stuff to temp clients
|
||||||
if err != nil {
|
if msg.Type != common.DTChat && msg.Type != common.DTEvent {
|
||||||
fmt.Printf("Error converting ChatData to ChatDataJSON: %v\n", err)
|
// Put this here instead of having two lock/unlock blocks. We want
|
||||||
} else {
|
// to avoid a case where a client is removed from the temp users
|
||||||
for uuid, conn := range cr.tempConn {
|
// and added to the clients between the two blocks.
|
||||||
go func(c *chatConnection, suid string) {
|
cr.clientsMtx.Unlock()
|
||||||
err = c.WriteData(data)
|
break
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error writing data to connection: %v\n", err)
|
|
||||||
delete(cr.tempConn, suid)
|
|
||||||
}
|
|
||||||
}(conn, uuid)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data, err := msg.ToJSON()
|
||||||
|
if err != nil {
|
||||||
|
common.LogErrorf("Error converting ChatData to ChatDataJSON: %v\n", err)
|
||||||
|
cr.clientsMtx.Unlock()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
for uuid, conn := range cr.tempConn {
|
||||||
|
go func(c *chatConnection, suid string) {
|
||||||
|
err = c.WriteData(data)
|
||||||
|
if err != nil {
|
||||||
|
common.LogErrorf("Error writing data to connection: %v\n", err)
|
||||||
|
delete(cr.tempConn, suid)
|
||||||
|
}
|
||||||
|
}(conn, uuid)
|
||||||
|
}
|
||||||
cr.clientsMtx.Unlock()
|
cr.clientsMtx.Unlock()
|
||||||
case msg := <-cr.modqueue:
|
case msg := <-cr.modqueue:
|
||||||
cr.clientsMtx.Lock()
|
cr.clientsMtx.Lock()
|
||||||
for _, client := range cr.clients {
|
for _, client := range cr.clients {
|
||||||
if client.IsMod || client.IsAdmin {
|
if client.CmdLevel >= common.CmdlMod {
|
||||||
send(msg, client)
|
send(msg, client)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -479,8 +487,11 @@ func (cr *ChatRoom) changeName(oldName, newName string, forced bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if currentClient != nil {
|
if currentClient != nil {
|
||||||
currentClient.name = newName
|
err := currentClient.setName(newName)
|
||||||
fmt.Printf("%q -> %q\n", oldName, newName)
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not set client name to %#v: %v", newName, err)
|
||||||
|
}
|
||||||
|
common.LogDebugf("%q -> %q\n", oldName, newName)
|
||||||
|
|
||||||
if forced {
|
if forced {
|
||||||
cr.AddEventMsg(common.EvNameChangeForced, oldName+":"+newName, currentClient.color)
|
cr.AddEventMsg(common.EvNameChangeForced, oldName+":"+newName, currentClient.color)
|
||||||
|
|
|
@ -111,9 +111,9 @@ func (dc DataMessage) HTML() string {
|
||||||
default:
|
default:
|
||||||
badge := ""
|
badge := ""
|
||||||
switch dc.Level {
|
switch dc.Level {
|
||||||
case CmdMod:
|
case CmdlMod:
|
||||||
badge = `<img src="/static/img/mod.png" class="badge" />`
|
badge = `<img src="/static/img/mod.png" class="badge" />`
|
||||||
case CmdAdmin:
|
case CmdlAdmin:
|
||||||
badge = `<img src="/static/img/admin.png" class="badge" />`
|
badge = `<img src="/static/img/admin.png" class="badge" />`
|
||||||
}
|
}
|
||||||
return `<div>` + badge + `<span class="name" style="color:` + dc.Color + `">` + dc.From +
|
return `<div>` + badge + `<span class="name" style="color:` + dc.Color + `">` + dc.From +
|
||||||
|
|
|
@ -42,6 +42,10 @@ var colors = []string{
|
||||||
"whitesmoke", "yellow", "yellowgreen",
|
"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.
|
// IsValidColor takes a string s and compares it against a list of css color names.
|
||||||
// It also accepts hex codes in the form of #000 (RGB), to #00000000 (RRGGBBAA), with A
|
// It also accepts hex codes in the form of #000 (RGB), to #00000000 (RRGGBBAA), with A
|
||||||
// being the alpha value
|
// being the alpha value
|
||||||
|
@ -53,7 +57,7 @@ func IsValidColor(s string) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if regexp.MustCompile(`^#([0-9A-Fa-f]{3}){1,2}$`).MatchString(s) {
|
if regexColor.MatchString(s) {
|
||||||
c, err := colorful.Hex(s)
|
c, err := colorful.Hex(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -7,7 +7,7 @@ const (
|
||||||
CdMessage ClientDataType = iota // a normal message from the client meant to be broadcast
|
CdMessage ClientDataType = iota // a normal message from the client meant to be broadcast
|
||||||
CdUsers // get a list of users
|
CdUsers // get a list of users
|
||||||
CdPing // ping the server to keep the connection alive
|
CdPing // ping the server to keep the connection alive
|
||||||
CdHelp // tells server to send help data again for buttons
|
CdAuth // get the auth levels of the user
|
||||||
)
|
)
|
||||||
|
|
||||||
type DataType int
|
type DataType int
|
||||||
|
@ -36,9 +36,9 @@ type CommandLevel int
|
||||||
|
|
||||||
// Command access levels
|
// Command access levels
|
||||||
const (
|
const (
|
||||||
CmdUser CommandLevel = iota
|
CmdlUser CommandLevel = iota
|
||||||
CmdMod
|
CmdlMod
|
||||||
CmdAdmin
|
CmdlAdmin
|
||||||
)
|
)
|
||||||
|
|
||||||
type EventType int
|
type EventType int
|
||||||
|
|
|
@ -47,14 +47,15 @@ func LoadEmotes() (int, error) {
|
||||||
globbed_files := []string(emotePNGs)
|
globbed_files := []string(emotePNGs)
|
||||||
globbed_files = append(globbed_files, emoteGIFs...)
|
globbed_files = append(globbed_files, emoteGIFs...)
|
||||||
|
|
||||||
fmt.Println("Loading emotes...")
|
LogInfoln("Loading emotes...")
|
||||||
|
emInfo := []string{}
|
||||||
for _, file := range globbed_files {
|
for _, file := range globbed_files {
|
||||||
file = filepath.Base(file)
|
file = filepath.Base(file)
|
||||||
key := file[0 : len(file)-4]
|
key := file[0 : len(file)-4]
|
||||||
newEmotes[key] = file
|
newEmotes[key] = file
|
||||||
fmt.Printf("%s ", key)
|
emInfo = append(emInfo, key)
|
||||||
}
|
}
|
||||||
Emotes = newEmotes
|
Emotes = newEmotes
|
||||||
fmt.Println("")
|
LogInfoln(strings.Join(emInfo, " "))
|
||||||
return len(Emotes), nil
|
return len(Emotes), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -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...)
|
||||||
|
}
|
|
@ -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...)
|
||||||
|
}
|
|
@ -12,5 +12,5 @@ var usernameRegex *regexp.Regexp = regexp.MustCompile(`^[0-9a-zA-Z_-]+$`)
|
||||||
// and is not a valid color name
|
// and is not a valid color name
|
||||||
func IsValidName(name string) bool {
|
func IsValidName(name string) bool {
|
||||||
return 3 <= len(name) && len(name) <= 36 &&
|
return 3 <= len(name) && len(name) <= 36 &&
|
||||||
usernameRegex.MatchString(name) && !IsValidColor(name)
|
usernameRegex.MatchString(name)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
|
"github.com/zorchenhimer/MovieNight/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type chatConnection struct {
|
type chatConnection struct {
|
||||||
|
@ -31,7 +32,7 @@ func (cc *chatConnection) WriteData(data interface{}) error {
|
||||||
err := cc.WriteJSON(data)
|
err := cc.WriteJSON(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if operr, ok := err.(*net.OpError); ok {
|
if operr, ok := err.(*net.OpError); ok {
|
||||||
fmt.Println("OpError: " + operr.Err.Error())
|
common.LogDebugln("OpError: " + operr.Err.Error())
|
||||||
}
|
}
|
||||||
return fmt.Errorf("Error writing data to %s %s: %v", cc.clientName, cc.Host(), err)
|
return fmt.Errorf("Error writing data to %s %s: %v", cc.clientName, cc.Host(), err)
|
||||||
}
|
}
|
||||||
|
|
49
handlers.go
49
handlers.go
|
@ -58,7 +58,7 @@ func wsStaticFiles(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
goodPath := r.URL.Path[8:len(r.URL.Path)]
|
goodPath := r.URL.Path[8:len(r.URL.Path)]
|
||||||
fmt.Printf("[static] serving %q from folder ./static/\n", goodPath)
|
common.LogDebugf("[static] serving %q from folder ./static/\n", goodPath)
|
||||||
|
|
||||||
http.ServeFile(w, r, "./static/"+goodPath)
|
http.ServeFile(w, r, "./static/"+goodPath)
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ func wsWasmFile(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
func wsImages(w http.ResponseWriter, r *http.Request) {
|
func wsImages(w http.ResponseWriter, r *http.Request) {
|
||||||
base := filepath.Base(r.URL.Path)
|
base := filepath.Base(r.URL.Path)
|
||||||
fmt.Println("[img] ", base)
|
common.LogDebugln("[img] ", base)
|
||||||
http.ServeFile(w, r, "./static/img/"+base)
|
http.ServeFile(w, r, "./static/img/"+base)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ func wsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
conn, err := upgrader.Upgrade(w, r, nil)
|
conn, err := upgrader.Upgrade(w, r, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error upgrading to websocket:", err)
|
common.LogErrorln("Error upgrading to websocket:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +107,7 @@ func wsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
uid, err := chat.JoinTemp(chatConn)
|
uid, err := chat.JoinTemp(chatConn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("[handler] could not do a temp join, %v\n", err)
|
common.LogErrorf("[handler] could not do a temp join, %v\n", err)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@ func wsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
var data common.ClientData
|
var data common.ClientData
|
||||||
err := chatConn.ReadData(&data)
|
err := chatConn.ReadData(&data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("[handler] Client closed connection: %s\n", conn.RemoteAddr().String())
|
common.LogInfof("[handler] Client closed connection: %s\n", conn.RemoteAddr().String())
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -126,14 +126,14 @@ func wsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case UserFormatError, UserTakenError:
|
case UserFormatError, UserTakenError:
|
||||||
fmt.Printf("[handler|%s] %v\n", errorName(err), err)
|
common.LogInfof("[handler|%s] %v\n", errorName(err), err)
|
||||||
case BannedUserError:
|
case BannedUserError:
|
||||||
fmt.Printf("[handler|%s] %v\n", errorName(err), err)
|
common.LogInfof("[handler|%s] %v\n", errorName(err), err)
|
||||||
// close connection since banned users shouldn't be connecting
|
// close connection since banned users shouldn't be connecting
|
||||||
conn.Close()
|
conn.Close()
|
||||||
default:
|
default:
|
||||||
// for now all errors not caught need to be warned
|
// for now all errors not caught need to be warned
|
||||||
fmt.Printf("[handler|uncaught] %v\n", err)
|
common.LogErrorf("[handler|uncaught] %v\n", err)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -243,7 +243,7 @@ func handlePinTemplate(w http.ResponseWriter, r *http.Request, errorMessage stri
|
||||||
func handleHelpTemplate(w http.ResponseWriter, r *http.Request) {
|
func handleHelpTemplate(w http.ResponseWriter, r *http.Request) {
|
||||||
t, err := template.ParseFiles("./static/base.html", "./static/help.html")
|
t, err := template.ParseFiles("./static/base.html", "./static/help.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error parsing template file, %v\n", err)
|
common.LogErrorf("Error parsing template file, %v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,20 +256,20 @@ func handleHelpTemplate(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
data := Data{
|
data := Data{
|
||||||
Title: "Help",
|
Title: "Help",
|
||||||
Commands: getHelp(common.CmdUser),
|
Commands: getHelp(common.CmdlUser),
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(r.URL.Query().Get("mod")) > 0 {
|
if len(r.URL.Query().Get("mod")) > 0 {
|
||||||
data.ModCommands = getHelp(common.CmdMod)
|
data.ModCommands = getHelp(common.CmdlMod)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(r.URL.Query().Get("admin")) > 0 {
|
if len(r.URL.Query().Get("admin")) > 0 {
|
||||||
data.AdminCommands = getHelp(common.CmdAdmin)
|
data.AdminCommands = getHelp(common.CmdlAdmin)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = t.Execute(w, data)
|
err = t.Execute(w, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error executing file, %v", err)
|
common.LogErrorf("Error executing file, %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,7 +306,7 @@ func handleIndexTemplate(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
t, err := template.ParseFiles("./static/base.html", "./static/main.html")
|
t, err := template.ParseFiles("./static/base.html", "./static/main.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error parsing template file, %v\n", err)
|
common.LogErrorf("Error parsing template file, %v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -337,7 +337,7 @@ func handleIndexTemplate(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
err = t.Execute(w, data)
|
err = t.Execute(w, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error executing file, %v", err)
|
common.LogErrorf("Error executing file, %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -345,22 +345,22 @@ func handlePublish(conn *rtmp.Conn) {
|
||||||
streams, _ := conn.Streams()
|
streams, _ := conn.Streams()
|
||||||
|
|
||||||
l.Lock()
|
l.Lock()
|
||||||
fmt.Println("request string->", conn.URL.RequestURI())
|
common.LogDebugln("request string->", conn.URL.RequestURI())
|
||||||
urlParts := strings.Split(strings.Trim(conn.URL.RequestURI(), "/"), "/")
|
urlParts := strings.Split(strings.Trim(conn.URL.RequestURI(), "/"), "/")
|
||||||
fmt.Println("urlParts->", urlParts)
|
common.LogDebugln("urlParts->", urlParts)
|
||||||
|
|
||||||
if len(urlParts) > 2 {
|
if len(urlParts) > 2 {
|
||||||
fmt.Println("Extra garbage after stream key")
|
common.LogErrorln("Extra garbage after stream key")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(urlParts) != 2 {
|
if len(urlParts) != 2 {
|
||||||
fmt.Println("Missing stream key")
|
common.LogErrorln("Missing stream key")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if urlParts[1] != settings.GetStreamKey() {
|
if urlParts[1] != settings.GetStreamKey() {
|
||||||
fmt.Println("Due to key not match, denied stream")
|
common.LogErrorln("Stream key is incorrect. Denying stream.")
|
||||||
return //If key not match, deny stream
|
return //If key not match, deny stream
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -376,13 +376,13 @@ func handlePublish(conn *rtmp.Conn) {
|
||||||
}
|
}
|
||||||
l.Unlock()
|
l.Unlock()
|
||||||
if ch == nil {
|
if ch == nil {
|
||||||
fmt.Println("Unable to start stream, channel is nil.")
|
common.LogErrorln("Unable to start stream, channel is nil.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Stream started")
|
common.LogInfoln("Stream started")
|
||||||
avutil.CopyPackets(ch.que, conn)
|
avutil.CopyPackets(ch.que, conn)
|
||||||
fmt.Println("Stream finished")
|
common.LogInfoln("Stream finished")
|
||||||
|
|
||||||
l.Lock()
|
l.Lock()
|
||||||
delete(channels, streamPath)
|
delete(channels, streamPath)
|
||||||
|
@ -420,7 +420,8 @@ func handleDefault(w http.ResponseWriter, r *http.Request) {
|
||||||
avutil.CopyFile(muxer, cursor)
|
avutil.CopyFile(muxer, cursor)
|
||||||
} else {
|
} else {
|
||||||
if r.URL.Path != "/" {
|
if r.URL.Path != "/" {
|
||||||
fmt.Println("[http 404] ", r.URL.Path)
|
// not really an error for the server, but for the client.
|
||||||
|
common.LogInfoln("[http 404] ", r.URL.Path)
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
} else {
|
} else {
|
||||||
handleIndexTemplate(w, r)
|
handleIndexTemplate(w, r)
|
||||||
|
|
84
main.go
84
main.go
|
@ -1,41 +1,97 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
|
||||||
|
"github.com/gorilla/sessions"
|
||||||
"github.com/nareix/joy4/format"
|
"github.com/nareix/joy4/format"
|
||||||
"github.com/nareix/joy4/format/rtmp"
|
"github.com/nareix/joy4/format/rtmp"
|
||||||
|
"github.com/zorchenhimer/MovieNight/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
addr string
|
addr string
|
||||||
sKey string
|
sKey string
|
||||||
stats streamStats
|
stats = newStreamStats()
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func setupSettings() error {
|
||||||
format.RegisterAll()
|
var err error
|
||||||
|
settings, err = LoadSettings("settings.json")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unable to load settings: %s", err)
|
||||||
|
}
|
||||||
|
if len(settings.StreamKey) == 0 {
|
||||||
|
return fmt.Errorf("Missing stream key is settings.json")
|
||||||
|
}
|
||||||
|
|
||||||
flag.StringVar(&addr, "l", ":8089", "host:port of the MovieNight")
|
if err = settings.SetupLogging(); err != nil {
|
||||||
flag.StringVar(&sKey, "k", "", "Stream key, to protect your stream")
|
return fmt.Errorf("Unable to setup logger: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
stats = newStreamStats()
|
// Is this a good way to do this? Probably not...
|
||||||
|
if len(settings.SessionKey) == 0 {
|
||||||
|
out := ""
|
||||||
|
large := big.NewInt(int64(1 << 60))
|
||||||
|
large = large.Add(large, large)
|
||||||
|
for len(out) < 50 {
|
||||||
|
num, err := rand.Int(rand.Reader, large)
|
||||||
|
if err != nil {
|
||||||
|
panic("Error generating session key: " + err.Error())
|
||||||
|
}
|
||||||
|
out = fmt.Sprintf("%s%X", out, num)
|
||||||
|
}
|
||||||
|
settings.SessionKey = out
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(settings.RoomAccess) == 0 {
|
||||||
|
settings.RoomAccess = AccessOpen
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.RoomAccess != AccessOpen && len(settings.RoomAccessPin) == 0 {
|
||||||
|
settings.RoomAccessPin = "1234"
|
||||||
|
}
|
||||||
|
|
||||||
|
sstore = sessions.NewCookieStore([]byte(settings.SessionKey))
|
||||||
|
sstore.Options = &sessions.Options{
|
||||||
|
Path: "/",
|
||||||
|
MaxAge: 60 * 60 * 24, // one day
|
||||||
|
SameSite: http.SameSiteStrictMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save admin password to file
|
||||||
|
if err = settings.Save(); err != nil {
|
||||||
|
return fmt.Errorf("Unable to save settings: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
flag.StringVar(&addr, "l", ":8089", "host:port of the MovieNight")
|
||||||
|
flag.StringVar(&sKey, "k", "", "Stream key, to protect your stream")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
format.RegisterAll()
|
||||||
|
|
||||||
|
if err := setupSettings(); err != nil {
|
||||||
|
fmt.Printf("Error loading settings: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
exit := make(chan bool)
|
exit := make(chan bool)
|
||||||
go handleInterrupt(exit)
|
go handleInterrupt(exit)
|
||||||
|
|
||||||
// Load emotes before starting server.
|
// Load emotes before starting server.
|
||||||
var err error
|
var err error
|
||||||
if chat, err = newChatRoom(); err != nil {
|
if chat, err = newChatRoom(); err != nil {
|
||||||
fmt.Println(err)
|
common.LogErrorln(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,11 +105,11 @@ func main() {
|
||||||
settings.SetTempKey(sKey)
|
settings.SetTempKey(sKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Stream key: ", settings.GetStreamKey())
|
common.LogInfoln("Stream key: ", settings.GetStreamKey())
|
||||||
fmt.Println("Admin password: ", settings.AdminPassword)
|
common.LogInfoln("Admin password: ", settings.AdminPassword)
|
||||||
|
common.LogInfoln("Listen and serve ", addr)
|
||||||
fmt.Println("RoomAccess: ", settings.RoomAccess)
|
fmt.Println("RoomAccess: ", settings.RoomAccess)
|
||||||
fmt.Println("RoomAccessPin: ", settings.RoomAccessPin)
|
fmt.Println("RoomAccessPin: ", settings.RoomAccessPin)
|
||||||
fmt.Println("Listen and serve ", addr)
|
|
||||||
|
|
||||||
go startServer()
|
go startServer()
|
||||||
go startRmtpServer()
|
go startRmtpServer()
|
||||||
|
@ -68,7 +124,8 @@ func startRmtpServer() {
|
||||||
}
|
}
|
||||||
err := server.ListenAndServe()
|
err := server.ListenAndServe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error trying to start server: %v\n", err)
|
// If the server cannot start, don't pretend we can continue.
|
||||||
|
panic("Error trying to start rtmp server: " + err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +147,8 @@ func startServer() {
|
||||||
|
|
||||||
err := http.ListenAndServe(addr, nil)
|
err := http.ListenAndServe(addr, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error trying to start rmtp server: %v\n", err)
|
// If the server cannot start, don't pretend we can continue.
|
||||||
|
panic("Error trying to start chat/http server: " + err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +156,7 @@ func handleInterrupt(exit chan bool) {
|
||||||
ch := make(chan os.Signal)
|
ch := make(chan os.Signal)
|
||||||
signal.Notify(ch, os.Interrupt)
|
signal.Notify(ch, os.Interrupt)
|
||||||
<-ch
|
<-ch
|
||||||
fmt.Println("Closing server")
|
common.LogInfoln("Closing server")
|
||||||
if settings.StreamStats {
|
if settings.StreamStats {
|
||||||
stats.Print()
|
stats.Print()
|
||||||
}
|
}
|
||||||
|
|
70
settings.go
70
settings.go
|
@ -6,12 +6,12 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net/http"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
|
"github.com/zorchenhimer/MovieNight/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
var settings *Settings
|
var settings *Settings
|
||||||
|
@ -28,10 +28,12 @@ type Settings struct {
|
||||||
MaxMessageCount int
|
MaxMessageCount int
|
||||||
TitleLength int // maximum length of the title that can be set with the /playing
|
TitleLength int // maximum length of the title that can be set with the /playing
|
||||||
AdminPassword string
|
AdminPassword string
|
||||||
Bans []BanInfo
|
|
||||||
StreamKey string
|
StreamKey string
|
||||||
ListenAddress string
|
ListenAddress string
|
||||||
SessionKey string // key for session data
|
SessionKey string // key for session data
|
||||||
|
Bans []BanInfo
|
||||||
|
LogLevel common.LogLevel
|
||||||
|
LogFile string
|
||||||
RoomAccess AccessMode
|
RoomAccess AccessMode
|
||||||
RoomAccessPin string // auto generate this,
|
RoomAccessPin string // auto generate this,
|
||||||
}
|
}
|
||||||
|
@ -50,56 +52,6 @@ type BanInfo struct {
|
||||||
When time.Time
|
When time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
var err error
|
|
||||||
settings, err = LoadSettings("settings.json")
|
|
||||||
if err != nil {
|
|
||||||
panic("Unable to load settings: " + err.Error())
|
|
||||||
}
|
|
||||||
if len(settings.StreamKey) == 0 {
|
|
||||||
panic("Missing stream key is settings.json")
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.TitleLength <= 0 {
|
|
||||||
settings.TitleLength = 50
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is this a good way to do this? Probably not...
|
|
||||||
if len(settings.SessionKey) == 0 {
|
|
||||||
out := ""
|
|
||||||
large := big.NewInt(int64(1 << 60))
|
|
||||||
large = large.Add(large, large)
|
|
||||||
for len(out) < 50 {
|
|
||||||
num, err := rand.Int(rand.Reader, large)
|
|
||||||
if err != nil {
|
|
||||||
panic("Error generating session key: " + err.Error())
|
|
||||||
}
|
|
||||||
out = fmt.Sprintf("%s%X", out, num)
|
|
||||||
}
|
|
||||||
settings.SessionKey = out
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(settings.RoomAccess) == 0 {
|
|
||||||
settings.RoomAccess = AccessOpen
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.RoomAccess != AccessOpen && len(settings.RoomAccessPin) == 0 {
|
|
||||||
settings.RoomAccessPin = "1234"
|
|
||||||
}
|
|
||||||
|
|
||||||
sstore = sessions.NewCookieStore([]byte(settings.SessionKey))
|
|
||||||
sstore.Options = &sessions.Options{
|
|
||||||
Path: "/",
|
|
||||||
MaxAge: 60 * 60 * 24, // one day
|
|
||||||
SameSite: http.SameSiteStrictMode,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save admin password to file
|
|
||||||
if err = settings.Save(); err != nil {
|
|
||||||
panic("Unable to save settings: " + err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadSettings(filename string) (*Settings, error) {
|
func LoadSettings(filename string) (*Settings, error) {
|
||||||
raw, err := ioutil.ReadFile(filename)
|
raw, err := ioutil.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -124,8 +76,14 @@ func LoadSettings(filename string) (*Settings, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to generate admin password: %s", err)
|
return nil, fmt.Errorf("unable to generate admin password: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't use LogInfof() here. Log isn't setup yet when LoadSettings() is called from init().
|
||||||
fmt.Printf("Settings reloaded. New admin password: %s\n", s.AdminPassword)
|
fmt.Printf("Settings reloaded. New admin password: %s\n", s.AdminPassword)
|
||||||
|
|
||||||
|
if s.TitleLength <= 0 {
|
||||||
|
s.TitleLength = 50
|
||||||
|
}
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,7 +125,7 @@ func (s *Settings) AddBan(host string, names []string) error {
|
||||||
}
|
}
|
||||||
settings.Bans = append(settings.Bans, b)
|
settings.Bans = append(settings.Bans, b)
|
||||||
|
|
||||||
fmt.Printf("[BAN] %q (%s) has been banned.\n", strings.Join(names, ", "), host)
|
common.LogInfof("[BAN] %q (%s) has been banned.\n", strings.Join(names, ", "), host)
|
||||||
|
|
||||||
return settings.Save()
|
return settings.Save()
|
||||||
}
|
}
|
||||||
|
@ -181,7 +139,7 @@ func (s *Settings) RemoveBan(name string) error {
|
||||||
for _, b := range s.Bans {
|
for _, b := range s.Bans {
|
||||||
for _, n := range b.Names {
|
for _, n := range b.Names {
|
||||||
if n == name {
|
if n == name {
|
||||||
fmt.Printf("[ban] Removed ban for %s [%s]\n", b.IP, n)
|
common.LogInfof("[ban] Removed ban for %s [%s]\n", b.IP, n)
|
||||||
} else {
|
} else {
|
||||||
newBans = append(newBans, b)
|
newBans = append(newBans, b)
|
||||||
}
|
}
|
||||||
|
@ -220,6 +178,10 @@ func (s *Settings) GetStreamKey() string {
|
||||||
return s.StreamKey
|
return s.StreamKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Settings) SetupLogging() error {
|
||||||
|
return common.SetupLogging(s.LogLevel, s.LogFile)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Settings) generateNewPin() (string, error) {
|
func (s *Settings) generateNewPin() (string, error) {
|
||||||
num, err := rand.Int(rand.Reader, big.NewInt(int64(9999)))
|
num, err := rand.Int(rand.Reader, big.NewInt(int64(9999)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -116,6 +116,22 @@ span.svmsg {
|
||||||
color: var(--var-contrast-color);
|
color: var(--var-contrast-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spoiler {
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 0px 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spoiler *,
|
||||||
|
.spoiler {
|
||||||
|
background: var(--var-popout-color);
|
||||||
|
color: var(--var-popout-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.spoiler-active {
|
||||||
|
background: var(--var-background-color);
|
||||||
|
color: aqua;
|
||||||
|
}
|
||||||
|
|
||||||
.range-div {
|
.range-div {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
148
wasm/main.go
148
wasm/main.go
|
@ -10,150 +10,7 @@ import (
|
||||||
"github.com/zorchenhimer/MovieNight/common"
|
"github.com/zorchenhimer/MovieNight/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
var auth common.CommandLevel
|
||||||
keyTab = 9
|
|
||||||
keyEnter = 13
|
|
||||||
keyUp = 38
|
|
||||||
keyDown = 40
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
currentName string
|
|
||||||
names []string
|
|
||||||
filteredNames []string
|
|
||||||
)
|
|
||||||
|
|
||||||
// The returned value is a bool deciding to prevent the event from propagating
|
|
||||||
func processMessageKey(this js.Value, v []js.Value) interface{} {
|
|
||||||
if len(filteredNames) == 0 || currentName == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
startIdx := v[0].Get("target").Get("selectionStart").Int()
|
|
||||||
keyCode := v[0].Get("keyCode").Int()
|
|
||||||
switch keyCode {
|
|
||||||
case keyUp, keyDown:
|
|
||||||
newidx := 0
|
|
||||||
for i, n := range filteredNames {
|
|
||||||
if n == currentName {
|
|
||||||
newidx = i
|
|
||||||
if keyCode == keyDown {
|
|
||||||
newidx = i + 1
|
|
||||||
if newidx == len(filteredNames) {
|
|
||||||
newidx--
|
|
||||||
}
|
|
||||||
} else if keyCode == keyUp {
|
|
||||||
newidx = i - 1
|
|
||||||
if newidx < 0 {
|
|
||||||
newidx = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
currentName = filteredNames[newidx]
|
|
||||||
case keyTab, keyEnter:
|
|
||||||
msg := js.Get("msg")
|
|
||||||
val := msg.Get("value").String()
|
|
||||||
newval := val[:startIdx]
|
|
||||||
|
|
||||||
if i := strings.LastIndex(newval, "@"); i != -1 {
|
|
||||||
newval = newval[:i+1] + currentName
|
|
||||||
}
|
|
||||||
|
|
||||||
endVal := val[startIdx:]
|
|
||||||
if len(val) == startIdx || val[startIdx:][0] != ' ' {
|
|
||||||
// insert a space into val so selection indexing can be one line
|
|
||||||
endVal = " " + endVal
|
|
||||||
}
|
|
||||||
msg.Set("value", newval+endVal)
|
|
||||||
msg.Set("selectionStart", len(newval)+1)
|
|
||||||
msg.Set("selectionEnd", len(newval)+1)
|
|
||||||
|
|
||||||
// Clear out filtered names since it is no longer needed
|
|
||||||
filteredNames = nil
|
|
||||||
default:
|
|
||||||
// We only want to handle the caught keys, so return early
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
updateSuggestionDiv()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func processMessage(v []js.Value) {
|
|
||||||
msg := js.Get("msg")
|
|
||||||
text := strings.ToLower(msg.Get("value").String())
|
|
||||||
startIdx := msg.Get("selectionStart").Int()
|
|
||||||
|
|
||||||
filteredNames = nil
|
|
||||||
if len(text) != 0 {
|
|
||||||
if len(names) > 0 {
|
|
||||||
var caretIdx int
|
|
||||||
textParts := strings.Split(text, " ")
|
|
||||||
|
|
||||||
for i, word := range textParts {
|
|
||||||
// Increase caret index at beginning if not first word to account for spaces
|
|
||||||
if i != 0 {
|
|
||||||
caretIdx++
|
|
||||||
}
|
|
||||||
|
|
||||||
// It is possible to have a double space " ", which will lead to an
|
|
||||||
// empty string element in the slice. Also check that the index of the
|
|
||||||
// cursor is between the start of the word and the end
|
|
||||||
if len(word) > 0 && word[0] == '@' &&
|
|
||||||
caretIdx <= startIdx && startIdx <= caretIdx+len(word) {
|
|
||||||
// fill filtered first so the "modifier" keys can modify it
|
|
||||||
for _, n := range names {
|
|
||||||
if len(word) == 1 || strings.HasPrefix(strings.ToLower(n), word[1:]) {
|
|
||||||
filteredNames = append(filteredNames, n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(filteredNames) > 0 {
|
|
||||||
currentName = ""
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
caretIdx += len(word)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateSuggestionDiv()
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateSuggestionDiv() {
|
|
||||||
const selectedClass = ` class="selectedName"`
|
|
||||||
|
|
||||||
var divs []string
|
|
||||||
if len(filteredNames) > 0 {
|
|
||||||
// set current name to first if not set already
|
|
||||||
if currentName == "" {
|
|
||||||
currentName = filteredNames[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
var hasCurrentName bool
|
|
||||||
divs = make([]string, len(filteredNames))
|
|
||||||
|
|
||||||
// Create inner body of html
|
|
||||||
for i := range filteredNames {
|
|
||||||
divs[i] = "<div"
|
|
||||||
if filteredNames[i] == currentName {
|
|
||||||
hasCurrentName = true
|
|
||||||
divs[i] += selectedClass
|
|
||||||
}
|
|
||||||
divs[i] += ">" + filteredNames[i] + "</div>"
|
|
||||||
}
|
|
||||||
|
|
||||||
if !hasCurrentName {
|
|
||||||
divs[0] = divs[0][:4] + selectedClass + divs[0][4:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// The \n is so it's easier to read th source in web browsers for the dev
|
|
||||||
js.Get("suggestions").Set("innerHTML", strings.Join(divs, "\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func recieve(v []js.Value) {
|
func recieve(v []js.Value) {
|
||||||
if len(v) == 0 {
|
if len(v) == 0 {
|
||||||
|
@ -182,6 +39,8 @@ func recieve(v []js.Value) {
|
||||||
for _, i := range h.Data.([]interface{}) {
|
for _, i := range h.Data.([]interface{}) {
|
||||||
names = append(names, i.(string))
|
names = append(names, i.(string))
|
||||||
}
|
}
|
||||||
|
case common.CdAuth:
|
||||||
|
auth = h.Data.(common.CommandLevel)
|
||||||
}
|
}
|
||||||
case common.DTEvent:
|
case common.DTEvent:
|
||||||
d := chat.Data.(common.DataEvent)
|
d := chat.Data.(common.DataEvent)
|
||||||
|
@ -277,6 +136,7 @@ func isValidName(this js.Value, v []js.Value) interface{} {
|
||||||
|
|
||||||
func debugValues(v []js.Value) {
|
func debugValues(v []js.Value) {
|
||||||
fmt.Printf("currentName %#v\n", currentName)
|
fmt.Printf("currentName %#v\n", currentName)
|
||||||
|
fmt.Printf("auth %#v\n", auth)
|
||||||
fmt.Printf("names %#v\n", names)
|
fmt.Printf("names %#v\n", names)
|
||||||
fmt.Printf("filteredNames %#v\n", filteredNames)
|
fmt.Printf("filteredNames %#v\n", filteredNames)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,152 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/dennwc/dom/js"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
keyTab = 9
|
||||||
|
keyEnter = 13
|
||||||
|
keyUp = 38
|
||||||
|
keyDown = 40
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
currentName string
|
||||||
|
names []string
|
||||||
|
filteredNames []string
|
||||||
|
)
|
||||||
|
|
||||||
|
// The returned value is a bool deciding to prevent the event from propagating
|
||||||
|
func processMessageKey(this js.Value, v []js.Value) interface{} {
|
||||||
|
if len(filteredNames) == 0 || currentName == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
startIdx := v[0].Get("target").Get("selectionStart").Int()
|
||||||
|
keyCode := v[0].Get("keyCode").Int()
|
||||||
|
switch keyCode {
|
||||||
|
case keyUp, keyDown:
|
||||||
|
newidx := 0
|
||||||
|
for i, n := range filteredNames {
|
||||||
|
if n == currentName {
|
||||||
|
newidx = i
|
||||||
|
if keyCode == keyDown {
|
||||||
|
newidx = i + 1
|
||||||
|
if newidx == len(filteredNames) {
|
||||||
|
newidx--
|
||||||
|
}
|
||||||
|
} else if keyCode == keyUp {
|
||||||
|
newidx = i - 1
|
||||||
|
if newidx < 0 {
|
||||||
|
newidx = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentName = filteredNames[newidx]
|
||||||
|
case keyTab, keyEnter:
|
||||||
|
msg := js.Get("msg")
|
||||||
|
val := msg.Get("value").String()
|
||||||
|
newval := val[:startIdx]
|
||||||
|
|
||||||
|
if i := strings.LastIndex(newval, "@"); i != -1 {
|
||||||
|
newval = newval[:i+1] + currentName
|
||||||
|
}
|
||||||
|
|
||||||
|
endVal := val[startIdx:]
|
||||||
|
if len(val) == startIdx || val[startIdx:][0] != ' ' {
|
||||||
|
// insert a space into val so selection indexing can be one line
|
||||||
|
endVal = " " + endVal
|
||||||
|
}
|
||||||
|
msg.Set("value", newval+endVal)
|
||||||
|
msg.Set("selectionStart", len(newval)+1)
|
||||||
|
msg.Set("selectionEnd", len(newval)+1)
|
||||||
|
|
||||||
|
// Clear out filtered names since it is no longer needed
|
||||||
|
filteredNames = nil
|
||||||
|
default:
|
||||||
|
// We only want to handle the caught keys, so return early
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSuggestionDiv()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func processMessage(v []js.Value) {
|
||||||
|
msg := js.Get("msg")
|
||||||
|
text := strings.ToLower(msg.Get("value").String())
|
||||||
|
startIdx := msg.Get("selectionStart").Int()
|
||||||
|
|
||||||
|
filteredNames = nil
|
||||||
|
if len(text) != 0 {
|
||||||
|
if len(names) > 0 {
|
||||||
|
var caretIdx int
|
||||||
|
textParts := strings.Split(text, " ")
|
||||||
|
|
||||||
|
for i, word := range textParts {
|
||||||
|
// Increase caret index at beginning if not first word to account for spaces
|
||||||
|
if i != 0 {
|
||||||
|
caretIdx++
|
||||||
|
}
|
||||||
|
|
||||||
|
// It is possible to have a double space " ", which will lead to an
|
||||||
|
// empty string element in the slice. Also check that the index of the
|
||||||
|
// cursor is between the start of the word and the end
|
||||||
|
if len(word) > 0 && word[0] == '@' &&
|
||||||
|
caretIdx <= startIdx && startIdx <= caretIdx+len(word) {
|
||||||
|
// fill filtered first so the "modifier" keys can modify it
|
||||||
|
for _, n := range names {
|
||||||
|
if len(word) == 1 || strings.HasPrefix(strings.ToLower(n), word[1:]) {
|
||||||
|
filteredNames = append(filteredNames, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(filteredNames) > 0 {
|
||||||
|
currentName = ""
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
caretIdx += len(word)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSuggestionDiv()
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateSuggestionDiv() {
|
||||||
|
const selectedClass = ` class="selectedName"`
|
||||||
|
|
||||||
|
var divs []string
|
||||||
|
if len(filteredNames) > 0 {
|
||||||
|
// set current name to first if not set already
|
||||||
|
if currentName == "" {
|
||||||
|
currentName = filteredNames[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasCurrentName bool
|
||||||
|
divs = make([]string, len(filteredNames))
|
||||||
|
|
||||||
|
// Create inner body of html
|
||||||
|
for i := range filteredNames {
|
||||||
|
divs[i] = "<div"
|
||||||
|
if filteredNames[i] == currentName {
|
||||||
|
hasCurrentName = true
|
||||||
|
divs[i] += selectedClass
|
||||||
|
}
|
||||||
|
divs[i] += ">" + filteredNames[i] + "</div>"
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasCurrentName {
|
||||||
|
divs[0] = divs[0][:4] + selectedClass + divs[0][4:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The \n is so it's easier to read th source in web browsers for the dev
|
||||||
|
js.Get("suggestions").Set("innerHTML", strings.Join(divs, "\n"))
|
||||||
|
}
|
Loading…
Reference in New Issue