diff --git a/socketserver/broadcaster/subscriptions.go b/socketserver/broadcaster/subscriptions.go new file mode 100644 index 00000000..ee810828 --- /dev/null +++ b/socketserver/broadcaster/subscriptions.go @@ -0,0 +1,2 @@ +package broadcaster + diff --git a/socketserver/cmd/socketserver/socketserver.go b/socketserver/cmd/socketserver/socketserver.go index b898617e..fb75c2e9 100644 --- a/socketserver/cmd/socketserver/socketserver.go +++ b/socketserver/cmd/socketserver/socketserver.go @@ -2,7 +2,7 @@ package main // import "bitbucket.org/stendec/frankerfacez/socketserver/cmd/sock import ( "flag" - "../../lib" + "../../listener" "log" "net/http" ) @@ -25,7 +25,7 @@ func main() { log.Fatalln("Either both --crt and --key can be provided, or neither.") } - conf := &lib.Config { + conf := &listener.Config { SSLKeyFile: *privateKeyFile, SSLCertificateFile: *certificateFile, UseSSL: *certificateFile != "", @@ -33,7 +33,7 @@ func main() { Origin: *origin, } - lib.SetupServerAndHandle(conf) + listener.SetupServerAndHandle(conf) var err error if conf.UseSSL { diff --git a/socketserver/lib/commands.go b/socketserver/lib/commands.go deleted file mode 100644 index bdbe2b7b..00000000 --- a/socketserver/lib/commands.go +++ /dev/null @@ -1,91 +0,0 @@ -package lib -import ( - "golang.org/x/net/websocket" - "github.com/satori/go.uuid" - "log" -) - -var EmptyClientMessage ClientMessage = ClientMessage{} - -func HandleHello(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { - version, clientId, err := msg.ArgumentsAsTwoStrings() - if err != nil { - return EmptyClientMessage, nil - } - - client.Version = version - client.ClientID = uuid.FromStringOrNil(clientId) - if client.ClientID == uuid.Nil { - client.ClientID = uuid.NewV4() - } - - return ClientMessage{ - Arguments: client.ClientID.String(), - }, nil -} - -func HandleSetUser(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { - username, err := msg.ArgumentsAsString() - - return ClientMessage{}, nil -} - -func HandleSub(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { - - return ClientMessage{}, nil -} - -func HandleUnsub(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { - - return ClientMessage{}, nil -} - -func HandleSubChannel(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { - - return ClientMessage{}, nil -} - -func HandleUnsubChannel(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { - - return ClientMessage{}, nil -} - -func HandleChatHistory(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { - // Ignore, send empty history - return ClientMessage{Arguments: []string{}}, nil -} - -func HandleSurvey(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { - log.Println("Ignoring survey response from", client.ClientID) - return ClientMessage{}, nil -} - -func HandleUpdateFollowButtons(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { - - return ClientMessage{}, nil -} - -func HandleTrackFollow(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { - - return ClientMessage{}, nil -} - -func HandleEmoticonUses(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { - - return ClientMessage{}, nil -} - -func HandleTwitchEmote(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { - - return ClientMessage{}, nil -} - -func HandleGetLink(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { - - return ClientMessage{}, nil -} - -func HandleGetDisplayName(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { - - return ClientMessage{}, nil -} diff --git a/socketserver/lib/utils.go b/socketserver/lib/utils.go new file mode 100644 index 00000000..1b9af86c --- /dev/null +++ b/socketserver/lib/utils.go @@ -0,0 +1,34 @@ +package lib + +import ( +) + +func AddToSliceS(ary *[]string, val string) { + slice := *ary + for _, v := range slice { + if v == val { + return + } + } + + slice = append(slice, val) + *ary = slice +} + +func RemoveFromSliceS(ary *[]string, val string) { + slice := *ary + var idx int = -1 + for i, v := range slice { + if v == val { + idx = i + break + } + } + if idx == -1 { + return + } + + slice[idx] = slice[len(slice) - 1] + slice = slice[:len(slice) - 1] + *ary = slice +} diff --git a/socketserver/listener/commands.go b/socketserver/listener/commands.go new file mode 100644 index 00000000..06595612 --- /dev/null +++ b/socketserver/listener/commands.go @@ -0,0 +1,194 @@ +package listener + +import ( + "golang.org/x/net/websocket" + "github.com/satori/go.uuid" + "log" + "../lib" +) + +var ResponseSuccess = ClientMessage{Command: SuccessCommand} +var ResponseFailure = ClientMessage{Command: "False"} + +func HandleHello(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { + version, clientId, err := msg.ArgumentsAsTwoStrings() + if err != nil { + return + } + + client.Version = version + client.ClientID = uuid.FromStringOrNil(clientId) + if client.ClientID == uuid.Nil { + client.ClientID = uuid.NewV4() + } + + return ClientMessage{ + Arguments: client.ClientID.String(), + }, nil +} + +func HandleSetUser(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { + username, err := msg.ArgumentsAsString() + if err != nil { + return + } + + client.TwitchUsername = username + client.UsernameValidated = false + + return ResponseSuccess, nil +} + +func HandleSub(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { + channel, err := msg.ArgumentsAsString() + + lib.AddToSliceS(&client.CurrentChannels, channel) + + return ResponseSuccess, nil +} + +func HandleUnsub(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { + channel, err := msg.ArgumentsAsString() + + lib.RemoveFromSliceS(&client.CurrentChannels, channel) + + return ResponseSuccess, nil +} + +func HandleSubChannel(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { + channel, err := msg.ArgumentsAsString() + + lib.AddToSliceS(&client.WatchingChannels, channel) + + return ResponseSuccess, nil +} + +func HandleUnsubChannel(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { + channel, err := msg.ArgumentsAsString() + + lib.RemoveFromSliceS(&client.WatchingChannels, channel) + + return ResponseSuccess, nil +} + +func HandleChatHistory(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { + channel, count, err := msg.ArgumentsAsStringAndInt() + + _ = channel + _ = count + + // Ignore, send empty history + return ClientMessage{Arguments: []string{}}, nil +} + +func HandleSurvey(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { + log.Println("Ignoring survey response from", client.ClientID) + return ResponseSuccess, nil +} + +func HandleUpdateFollowButtons(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { + // TODO + return ResponseFailure, nil +} + +func HandleTrackFollow(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { + + return ResponseSuccess, nil +} + +func HandleEmoticonUses(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { + + return ResponseSuccess, nil +} + +type EmoteData struct { + +} + +func HandleTwitchEmote(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { + + return ResponseSuccess, nil +} + +type LinkResponse struct { + // If true, the link will be colored red. + Unsafe bool `json:unsafe` + // If present, the provided HTML will be shown as the link tooltip. + TooltipHTML string `json:tooltip,omitempty` + + // Each of the LinkTypes have a special rendering on the client. + Type LinkType `json:type,omitempty` + + // A URL to an image to embed. + // Recognized by several LinkTypes, as well as the empty LinkType + Full string `json:full,omitempty` + + // For LinkTypeYoutube, LinkTypeStrawpoll, LinkTypeTwitchVod + Title string `json:title,omitempty` + // For LinkTypeYoutube + Channel string `json:channel,omitempty` + // For LinkTypeYoutube + // Seconds + Duration int `json:duration,omitempty` + // For LinkTypeYoutube, LinkTypeTwitch, LinkTypeTwitchVod + Views int `json:views,omitempty` + // For LinkTypeYoutube + Likes int `json:likes,omitempty` + // For LinkTypeStrawpoll + Items map[string]int `json:items,omitempty` + // For LinkTypeStrawpoll + Total int `json:total,omitempty` + // For LinkTypeStrawpoll + // TODO - what time format is this + Fetched string `json:fetched,omitempty` + // For LinkTypeTwitch, LinkTypeTwitchVod + DisplayName string `json:display_name,omitempty` + // For LinkTypeTwitch + // TODO - what time format is this + Since string `json:since,omitempty` + // For LinkTypeTwitch + Followers int `json:followers,omitempty` + // For LinkTypeTwitchVod + BroadcastType string `json:broadcast_type,omitempty` + // For LinkTypeTwitchVod + Game string `json:game,omitempty` + // For LinkTypeTwitchVod + // Seconds + Length int `json:length,omitempty` + // For LinkTypeTwitter + User string `json:user,omitempty` + // For LinkTypeTwitter + Tweet string `json:tweet,omitempty` + // For LinkTypeReputation + Trust int `json:trust,omitempty` + // For LinkTypeReputation + Safety int `json:safety,omitempty` +} + +type LinkType string + +const ( + LinkTypeYoutube = "youtube" + LinkTypeStrawpoll = "strawpoll" + LinkTypeTwitch = "twitch" + LinkTypeTwitchVod = "twitch_vod" + LinkTypeTwitter = "twitter" + LinkTypeReputation = "reputation" + LinkTypeShortened = "shortened" // proposed +) +const ( + BroadcastTypeHighlight = "highlight" + BroadcastTypeFull = "broadcast" +) + +func HandleGetLink(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { + + // TODO + return ResponseFailure, nil +} + +func HandleGetDisplayName(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { + + // TODO + return ResponseFailure, nil +} diff --git a/socketserver/lib/handlecore.go b/socketserver/listener/handlecore.go similarity index 94% rename from socketserver/lib/handlecore.go rename to socketserver/listener/handlecore.go index 4b9537b5..1bb1a1c3 100644 --- a/socketserver/lib/handlecore.go +++ b/socketserver/listener/handlecore.go @@ -1,4 +1,4 @@ -package lib // import "bitbucket.org/stendec/frankerfacez/socketserver/lib" +package listener // import "bitbucket.org/stendec/frankerfacez/socketserver/listener" import ( "net/http" @@ -63,6 +63,10 @@ type ClientInfo struct { // Protected by Mutex CurrentChannels []string + // This list of channels this client needs UI updates for. + // Protected by Mutex + WatchingChannels []string + // Server-initiated messages should be sent here MessageChannel chan <- ClientMessage } @@ -89,7 +93,11 @@ var CommandHandlers = map[Command]CommandHandler{ // Sent by the server in ClientMessage.Command to indicate success. const SuccessCommand Command = "True" +// This must be the first command sent by the client once the connection is established. const HelloCommand Command = "hello" +// A handler returning a ClientMessage with this Command will prevent replying to the client. +// It signals that the work has been handed off to a background goroutine. +const AsyncResponseCommand Command = "_async" // A websocket.Codec that translates the protocol into ClientMessage objects. var FFZCodec websocket.Codec = websocket.Codec{ @@ -219,6 +227,9 @@ func HandleSocketConnection(conn *websocket.Conn) { if err == nil { response.MessageID = msg.MessageID FFZCodec.Send(conn, response) + } else if response.Command == AsyncResponseCommand { + // Don't send anything + // The response will be delivered over client.MessageChannel / serverMessageChan } else { FFZCodec.Send(conn, ClientMessage{ MessageID: msg.MessageID, @@ -331,7 +342,6 @@ func NewClientMessage(arguments interface{}) ClientMessage { } } - // Convenience method: Parse the arguments of the ClientMessage as a single string. func (cm *ClientMessage) ArgumentsAsString() (string1 string, err error) { var ok bool diff --git a/socketserver/lib/handlecore_test.go b/socketserver/listener/handlecore_test.go similarity index 97% rename from socketserver/lib/handlecore_test.go rename to socketserver/listener/handlecore_test.go index a96cdbfa..f5a0648f 100644 --- a/socketserver/lib/handlecore_test.go +++ b/socketserver/listener/handlecore_test.go @@ -1,4 +1,4 @@ -package lib +package listener import ( "golang.org/x/net/websocket" diff --git a/src/tokenize.js b/src/tokenize.js index e24c4b46..b702eb83 100644 --- a/src/tokenize.js +++ b/src/tokenize.js @@ -201,7 +201,7 @@ var FFZ = window.FrankerFaceZ, return; this._link_data[href] = data; - data.unsafe = false; + //data.unsafe = false; var tooltip = build_link_tooltip.bind(this)(href), links, no_trail = href.charAt(href.length-1) == "/" ? href.substr(0, href.length-1) : null;