// +build js,wasm package main import ( "encoding/json" "fmt" "sort" "strings" "time" "syscall/js" "github.com/zorchenhimer/MovieNight/common" ) var ( timestamp bool color string auth common.CommandLevel global js.Value ) func getElement(s string) js.Value { return global.Get("document").Call("getElementById", s) } func join(v []js.Value) { color := global.Call("getCookie", "color").String() if color == "" { // If a color is not set, do a random color color = common.RandomColor() } else if !common.IsValidColor(color) { // Don't show the user the error, just clear the cookie common.LogInfof("%#v is not a valid color, clearing cookie", color) global.Call("deleteCookie", "color") } joinData, err := json.Marshal(common.JoinData{ Name: getElement("name").Get("value").String(), Color: color, }) if err != nil { notify("Error prepping data for join") common.LogErrorf("Could not prep data: %#v\n", err) } data, err := json.Marshal(common.ClientData{ Type: common.CdJoin, Message: string(joinData), }) if err != nil { common.LogErrorf("Could not marshal data: %v", err) } global.Call("websocketSend", string(data)) } func recieve(v []js.Value) { if len(v) == 0 { fmt.Println("No data received") return } chatJSON, err := common.DecodeData(v[0].String()) if err != nil { fmt.Printf("Error decoding data: %s\n", err) global.Call("appendMessages", fmt.Sprintf("
%v
", v)) return } chat, err := chatJSON.ToData() if err != nil { fmt.Printf("Error converting ChatDataJSON to ChatData of type %d: %v", chatJSON.Type, err) } switch chat.Type { case common.DTHidden: h := chat.Data.(common.HiddenMessage) switch h.Type { case common.CdUsers: names = nil for _, i := range h.Data.([]interface{}) { names = append(names, i.(string)) } sort.Strings(names) case common.CdAuth: auth = h.Data.(common.CommandLevel) case common.CdColor: color = h.Data.(string) global.Get("document").Set("cookie", fmt.Sprintf("color=%s; expires=Fri, 31 Dec 9999 23:59:59 GMT", color)) case common.CdEmote: data := h.Data.(map[string]interface{}) emoteNames = make([]string, 0, len(data)) emotes = make(map[string]string) for k, v := range data { emoteNames = append(emoteNames, k) emotes[k] = v.(string) } sort.Strings(emoteNames) case common.CdJoin: notify("") global.Call("openChat") case common.CdNotify: notify(h.Data.(string)) } case common.DTEvent: d := chat.Data.(common.DataEvent) // A server message is the only event that doesn't deal with names. if d.Event != common.EvServerMessage { websocketSend("", common.CdUsers) } // on join or leave, update list of possible user names fallthrough case common.DTChat: msg := chat.Data.HTML() if d, ok := chat.Data.(common.DataMessage); ok { if timestamp && (d.Type == common.MsgChat || d.Type == common.MsgAction) { h, m, _ := time.Now().Clock() msg = fmt.Sprintf(`%02d:%02d %s`, h, m, msg) } } appendMessage(msg) case common.DTCommand: d := chat.Data.(common.DataCommand) switch d.Command { case common.CmdPlaying: if d.Arguments == nil || len(d.Arguments) == 0 { global.Call("setPlaying", "", "") } else if len(d.Arguments) == 1 { global.Call("setPlaying", d.Arguments[0], "") } else if len(d.Arguments) == 2 { global.Call("setPlaying", d.Arguments[0], d.Arguments[1]) } case common.CmdRefreshPlayer: global.Call("initPlayer", nil) case common.CmdPurgeChat: global.Call("purgeChat", nil) appendMessage(d.HTML()) case common.CmdHelp: url := "/help" if d.Arguments != nil && len(d.Arguments) > 0 { url = d.Arguments[0] } appendMessage(d.HTML()) global.Get("window").Call("open", url, "_blank", "menubar=0,status=0,toolbar=0,width=300,height=600") case common.CmdEmotes: url := "/emotes" appendMessage(d.HTML()) global.Get("window").Call("open", url, "_blank", "menubar=0,status=0,toolbar=0,width=300,height=600") } } } func appendMessage(msg string) { global.Call("appendMessages", "
"+msg+"
") } func websocketSend(msg string, dataType common.ClientDataType) error { if strings.TrimSpace(msg) == "" && dataType == common.CdMessage { return nil } data, err := json.Marshal(common.ClientData{ Type: dataType, Message: msg, }) if err != nil { return fmt.Errorf("could not marshal data: %v", err) } global.Call("websocketSend", string(data)) return nil } func send(this js.Value, v []js.Value) interface{} { if len(v) != 1 { showChatError(fmt.Errorf("expected 1 parameter, got %d", len(v))) return false } err := websocketSend(v[0].String(), common.CdMessage) if err != nil { showChatError(err) return false } return true } func showChatError(err error) { if err != nil { fmt.Printf("Could not send: %v\n", err) global.Call("appendMessages", `
Could not send message
`) } } func notify(msg string) { global.Call("setNotifyBox", msg) } func showTimestamp(v []js.Value) { if len(v) != 1 { // Don't bother with returning a value return } timestamp = v[0].Bool() } func isValidColor(this js.Value, v []js.Value) interface{} { if len(v) != 1 { return false } return common.IsValidColor(v[0].String()) } func debugValues(v []js.Value) { for k, v := range map[string]interface{}{ "timestamp": timestamp, "auth": auth, "color": color, "current suggestion": currentSug, "current suggestion type": currentSugType, "filtered suggestions": filteredSug, "user names": names, "emote names": emoteNames, } { fmt.Printf("%s: %#v\n", k, v) } } func main() { global = js.Global() common.SetupLogging(common.LLDebug, "") global.Set("processMessageKey", js.FuncOf(processMessageKey)) global.Set("sendMessage", js.FuncOf(send)) global.Set("isValidColor", js.FuncOf(isValidColor)) global.Set("recieveMessage", jsCallbackOf(recieve)) global.Set("processMessage", jsCallbackOf(processMessage)) global.Set("debugValues", jsCallbackOf(debugValues)) global.Set("showTimestamp", jsCallbackOf(showTimestamp)) global.Set("join", jsCallbackOf(join)) go func() { time.Sleep(time.Second * 1) inner := `` for _, c := range common.Colors { inner += fmt.Sprintf(`\n`, c, c) } global.Get("colorSelect").Set("innerHTML", inner) }() // This is needed so the goroutine does not end for { // heatbeat to keep connection alive to deal with nginx if global.Get("inChat").Bool() { websocketSend("", common.CdPing) } time.Sleep(time.Second * 10) } } func jsCallbackOf(fnc func(v []js.Value)) js.Func { return js.FuncOf(func(this js.Value, refs []js.Value) interface{} { vals := make([]js.Value, 0, len(refs)) for _, ref := range refs { vals = append(vals, ref) } fnc(vals) return nil }) }