Add infrastructure for the client to send formatted data to the server

This commit is contained in:
joeyak 2019-03-14 15:21:53 -04:00
parent 6a2cb3f906
commit 60dd98a1e7
5 changed files with 192 additions and 180 deletions

View File

@ -53,7 +53,8 @@ func ParseEmotes(msg string) string {
}
//Client has a new message to broadcast
func (cl *Client) NewMsg(msg string) {
func (cl *Client) NewMsg(data common.ClientData) {
msg := data.Message
msg = html.EscapeString(msg)
msg = removeDumbSpaces(msg)
msg = strings.Trim(msg, " ")

52
common/chatcommands.go Normal file
View File

@ -0,0 +1,52 @@
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"}
// 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"}
// Admin Commands
CNMod ChatCommandNames = []string{"mod"}
CNReloadPlayer ChatCommandNames = []string{"reloadplayer"}
CNReloadEmotes ChatCommandNames = []string{"reloademotes"}
CNModpass ChatCommandNames = []string{"modpass"}
)
var ChatCommands = []ChatCommandNames{
CNMe, CNHelp, CNCount, CNColor, CNWhoAmI, CNAuth, CNUsers,
CNSv, CNPlaying, CNUnmod, CNKick, CNBan, CNUnban,
CNMod, CNReloadPlayer, CNReloadEmotes, CNModpass,
}
func GetFullChatCommand(c string) string {
for _, names := range ChatCommands {
for _, n := range names {
if c == n {
return names.String()
}
}
}
return ""
}

View File

@ -2,58 +2,60 @@ package common
import (
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
)
const CommandNameSeparator = ","
type ChatCommandNames []string
func (c ChatCommandNames) String() string {
return strings.Join(c, CommandNameSeparator)
type DataInterface interface {
HTML() string
}
// 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"}
// 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"}
// Admin Commands
CNMod ChatCommandNames = []string{"mod"}
CNReloadPlayer ChatCommandNames = []string{"reloadplayer"}
CNReloadEmotes ChatCommandNames = []string{"reloademotes"}
CNModpass ChatCommandNames = []string{"modpass"}
)
var ChatCommands = []ChatCommandNames{
CNMe, CNHelp, CNCount, CNColor, CNWhoAmI, CNAuth, CNUsers,
CNSv, CNPlaying, CNUnmod, CNKick, CNBan, CNUnban,
CNMod, CNReloadPlayer, CNReloadEmotes, CNModpass,
type ChatData struct {
Hidden bool
Type DataType
Data json.RawMessage
}
func GetFullChatCommand(c string) string {
for _, names := range ChatCommands {
for _, n := range names {
if c == n {
return names.String()
}
}
func (c ChatData) 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 DTError:
d := DataError{}
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
}
return ""
return data, err
}
func newChatData(hidden bool, dtype DataType, d DataInterface) (ChatData, error) {
rawData, err := json.Marshal(d)
return ChatData{
Hidden: hidden,
Type: dtype,
Data: rawData,
}, err
}
type ClientData struct {
@ -61,11 +63,6 @@ type ClientData struct {
Message string
}
type ChatData struct {
Type DataType
Data DataInterface
}
type DataError struct {
Message string
}
@ -88,31 +85,6 @@ type DataEvent struct {
Color string
}
type DataInterface interface {
GetType() DataType
HTML() string
}
func (c ClientData) GetType() DataType {
return DTClient
}
func (d DataMessage) GetType() DataType {
return DTChat
}
func (d DataError) GetType() DataType {
return DTError
}
func (d DataCommand) GetType() DataType {
return DTCommand
}
func (d DataEvent) GetType() DataType {
return DTEvent
}
type ClientDataType int
const (
@ -136,8 +108,7 @@ func ParseDataType(token json.Token) (DataType, error) {
d := fmt.Sprintf("%.0f", token)
val, err := strconv.ParseInt(d, 10, 32)
if err != nil {
fmt.Printf("Invalid data type value: %q\n", d)
return DTInvalid, err
return DTInvalid, fmt.Errorf("invalid data type value: %q\n", d)
}
return DataType(val), nil
}
@ -224,46 +195,45 @@ func (de DataCommand) HTML() string {
}
func EncodeMessage(name, color, msg string, msgtype MessageType) (string, error) {
d := ChatData{
Type: DTChat,
Data: DataMessage{
From: name,
Color: color,
Message: msg,
Type: msgtype,
},
d, err := newChatData(false, DTChat, DataMessage{
From: name,
Color: color,
Message: msg,
Type: msgtype,
})
if err != nil {
return "", err
}
j, err := jsonifyChatData(d)
return j, err
return jsonifyChatData(d)
}
func EncodeError(message string) (string, error) {
d := ChatData{
Type: DTError,
Data: DataError{Message: message},
d, err := newChatData(false, DTError, DataError{Message: message})
if err != nil {
return "", err
}
return jsonifyChatData(d)
}
func EncodeCommand(command CommandType, args []string) (string, error) {
d := ChatData{
Type: DTCommand,
Data: DataCommand{
Command: command,
Arguments: args,
},
d, err := newChatData(false, DTCommand, DataCommand{
Command: command,
Arguments: args,
})
if err != nil {
return "", err
}
return jsonifyChatData(d)
}
func EncodeEvent(event EventType, name, color string) (string, error) {
d := ChatData{
Type: DTEvent,
Data: DataEvent{
Event: event,
User: name,
Color: color,
},
d, err := newChatData(false, DTEvent, DataEvent{
Event: event,
User: name,
Color: color,
})
if err != nil {
return "", err
}
return jsonifyChatData(d)
}
@ -276,64 +246,8 @@ func jsonifyChatData(data ChatData) (string, error) {
return string(raw), nil
}
func DecodeData(rawjson string) (DataInterface, error) {
data := ChatData{}
decoder := json.NewDecoder(strings.NewReader(rawjson))
// Open bracket
_, err := decoder.Token()
if err != nil {
return nil, fmt.Errorf("Open bracket token error: %s", err)
}
for decoder.More() {
key, err := decoder.Token()
if err != nil {
return nil, fmt.Errorf("Error decoding token: %s", err)
}
if key.(string) == "Type" {
value, err := decoder.Token()
if err != nil {
return nil, fmt.Errorf("Error decoding data value: %q", err)
}
data.Type, err = ParseDataType(value)
if err != nil {
return nil, fmt.Errorf("Error parsing data type: %d", data.Type)
}
} else {
switch DataType(data.Type) {
case DTChat:
d := DataMessage{}
if err := decoder.Decode(&d); err != nil {
return nil, fmt.Errorf("Unable to decode DataMessage: %s", err)
}
return d, nil
case DTError:
d := DataError{}
if err := decoder.Decode(&d); err != nil {
return nil, fmt.Errorf("Unable to decode DataError: %s", err)
}
return d, nil
case DTCommand:
d := DataCommand{}
if err := decoder.Decode(&d); err != nil {
return nil, fmt.Errorf("Unable to decode DataCommand: %s", err)
}
return d, nil
case DTEvent:
d := DataEvent{}
if err := decoder.Decode(&d); err != nil {
return nil, fmt.Errorf("Unable to decode DataEvent: %s", err)
}
return d, nil
default:
return nil, fmt.Errorf("Invalid data type: %d", data.Type)
}
}
}
return nil, fmt.Errorf("Incomplete data")
func DecodeData(rawjson string) (ChatData, error) {
var data ChatData
err := json.Unmarshal([]byte(rawjson), &data)
return data, err
}

View File

@ -9,6 +9,8 @@ import (
"strings"
"sync"
"github.com/zorchenhimer/MovieNight/common"
"github.com/gorilla/websocket"
"github.com/nareix/joy4/av/avutil"
"github.com/nareix/joy4/av/pubsub"
@ -106,15 +108,15 @@ func wsHandler(w http.ResponseWriter, r *http.Request) {
//first message has to be the name
// loop through name since websocket is opened once
for client == nil {
_, msg, err := conn.ReadMessage()
var data common.ClientData
err := conn.ReadJSON(&data)
if err != nil {
fmt.Printf("[handler] Client closed connection: %s\n", conn.RemoteAddr().String())
conn.Close()
return
}
name := string(msg)
client, err = chat.Join(name, uid)
client, err = chat.Join(data.Message, uid)
if err != nil {
switch err.(type) {
case UserFormatError, UserTakenError:
@ -133,12 +135,13 @@ func wsHandler(w http.ResponseWriter, r *http.Request) {
//then watch for incoming messages
for {
_, msg, err := conn.ReadMessage()
var data common.ClientData
err := conn.ReadJSON(&data)
if err != nil { //if error then assuming that the connection is closed
client.Exit()
return
}
client.NewMsg(string(msg))
client.NewMsg(data)
}
}()
@ -173,6 +176,9 @@ func handleIndexTemplate(w http.ResponseWriter, r *http.Request) {
data.Title += " - video"
}
// Force browser to replace cache since file was not changed
w.Header().Set("Cache-Control", "no-cache, must-revalidate")
err = t.Execute(w, data)
if err != nil {
fmt.Printf("[ERR] could not execute file, %v", err)

View File

@ -1,6 +1,7 @@
package main
import (
"encoding/json"
"fmt"
"time"
@ -10,20 +11,31 @@ import (
func recieve(v []js.Value) {
if len(v) == 0 {
fmt.Printf("No data received")
fmt.Println("No data received")
return
}
data, err := common.DecodeData(v[0].String())
chat, err := common.DecodeData(v[0].String())
if err != nil {
fmt.Printf("Error decoding data: %s\n", err)
js.Call("appendMessages", v)
return
}
switch data.GetType() {
case common.DTChat, common.DTError, common.DTEvent:
js.Call("appendMessages", data.HTML())
data, err := chat.GetData()
if err != nil {
fmt.Printf("Error parsing DataInterface: %v", err)
js.Call("appendMessages", v)
}
switch chat.Type {
case common.DTEvent:
// on join or leave, update list of possible user names
fallthrough
case common.DTChat, common.DTError:
if !chat.Hidden {
js.Call("appendMessages", data.HTML())
}
case common.DTCommand:
dc := data.(common.DataCommand)
@ -43,25 +55,52 @@ func recieve(v []js.Value) {
case common.CmdPurgeChat:
fmt.Println("//TODO: chat purge command received.")
case common.CmdHelp:
js.Call("appendMesages", data.HTML())
if !chat.Hidden {
js.Call("appendMessages", data.HTML())
}
// TODO: open window
//js.Call("")
}
return
}
}
func send(v []js.Value) {
if len(v) != 1 {
fmt.Printf("expected 1 parameter, got %d", len(v))
return
func websocketSend(msg string, dataType common.ClientDataType) error {
data, err := json.Marshal(common.ClientData{
Type: dataType,
Message: msg,
})
if err != nil {
return fmt.Errorf("could not marshal data: %v", err)
}
js.Call("websocketSend", string(data))
return nil
}
func send(this js.Value, v []js.Value) interface{} {
if len(v) != 1 {
showSendError(fmt.Errorf("expected 1 parameter, got %d", len(v)))
return false
}
err := websocketSend(v[0].String(), common.CdMessage)
if err != nil {
showSendError(err)
return false
}
return true
}
func showSendError(err error) {
if err != nil {
fmt.Printf("Could not send: %v\n", err)
js.Call("appendMessages", `<div><span style="color: red;">Could not send message</span></div>`)
}
js.Call("websocketSend", v)
}
func main() {
js.Set("recieveMessage", js.CallbackOf(recieve))
js.Set("sendMessage", js.CallbackOf(send))
js.Set("sendMessage", js.FuncOf(send))
// This is needed so the goroutine does not end
for {