]> git.openfl.eu Git - twitch-chat.git/commitdiff
reconnecting twitch client
authorFl_GUI <flor.guilini@hotmail.com>
Mon, 24 Jun 2024 18:15:59 +0000 (20:15 +0200)
committerFl_GUI <flor.guilini@hotmail.com>
Mon, 24 Jun 2024 18:15:59 +0000 (20:15 +0200)
twitch/chatclient.go [new file with mode: 0644]
twitch/core/messages/messages.go
twitch/core/pingpong.go
twitch/join.go [new file with mode: 0644]
x/corechatclient/main.go
x/rechat/main.go

diff --git a/twitch/chatclient.go b/twitch/chatclient.go
new file mode 100644 (file)
index 0000000..2cca6d1
--- /dev/null
@@ -0,0 +1,121 @@
+package twitch
+
+import (
+       "fmt"
+       "io"
+
+       "fl-gui.name/twitchchat/twitch/core"
+       "fl-gui.name/twitchchat/twitch/core/messages"
+)
+
+type ChatClient struct {
+       Connection *core.Conn
+       Messages   <-chan messages.Message
+
+       // if empty then anonymous login and disregard accessToken
+       nickname    string
+       accessToken string
+
+       capabilities map[string]struct{}
+
+       logger io.Writer
+}
+
+func NewChatClient() ChatClient {
+       return ChatClient{nil, nil, "", "", make(map[string]struct{}), io.Discard}
+}
+
+func (c ChatClient) Close() {
+       c.Connection.Close()
+}
+
+func (c *ChatClient) WithAuthentication(nickname, accessToken string) {
+       c.nickname = nickname
+       accessToken = accessToken
+}
+
+func (c *ChatClient) WithoutAuthentication() {
+       c.nickname = ""
+}
+
+func (c *ChatClient) WithCapability(capabilities ...string) {
+       for _, capa := range capabilities {
+               c.capabilities[capa] = struct{}{}
+       }
+}
+
+func (c *ChatClient) WithLogging(out io.Writer, trace bool) {
+       c.logger = out
+       if trace {
+               core.DebugLogger = c.logger
+       }
+}
+
+func (c *ChatClient) Connect() error {
+       conn, err := core.Dial("http://localhost")
+       if err != nil {
+               return err
+       }
+       c.Connection = conn
+       defer func() {
+               if err != nil {
+                       conn.Close()
+               }
+       }()
+
+       c.Messages = conn.ReadMessages()
+       if err = c.authenticate(); err != nil {
+               return err
+       }
+       c.pingPong()
+       if err = c.askCapabilities(); err != nil {
+               return err
+       }
+
+       fmt.Fprintf(c.logger, "connected\n")
+       return nil
+}
+
+func (c *ChatClient) authenticate() error {
+       if c.nickname == "" {
+               msgs, err := c.Connection.AuthenticateAnonymous(c.Messages)
+               if err != nil {
+                       return err
+               } else {
+                       c.Messages = msgs
+               }
+       } else {
+               msgs, err := c.Connection.Authenticate(c.nickname, c.accessToken, c.Messages)
+               if err != nil {
+                       return err
+               } else {
+                       c.Messages = msgs
+               }
+       }
+       return nil
+}
+
+func (c *ChatClient) pingPong() {
+       msgs, errs := c.Connection.PingPong(c.Messages)
+       go func() {
+               err := <-errs
+               fmt.Fprintf(c.logger, "ping pong game dropped: %s\n", err)
+               fmt.Fprintf(c.logger, "reconnecting\n")
+               c.Connect()
+       }()
+
+       c.Messages = msgs
+}
+
+func (c *ChatClient) askCapabilities() error {
+       var caps = make([]string, 0, len(c.capabilities))
+       for c, _ := range c.capabilities {
+               caps = append(caps, c)
+       }
+       msgs, err := c.Connection.WithCapability(c.Messages, caps...)
+       if err != nil {
+               return err
+       }
+       c.Messages = msgs
+       return nil
+}
index 78532257c7142ffc83944e19c8531005809d9144..017694c833e3106b03631bd990f74a9d9746980b 100644 (file)
@@ -22,7 +22,7 @@ func (m Message) Channel() string {
        if len(m.Params.Params) == 0 {
                return ""
        }
-       if m.Params.Params[0] != "#" {
+       if m.Params.Params[0][0] != '#' {
                return ""
        }
        return m.Params.Params[0][1:]
index 68e42657fb702bbe37e906cbb73bf934eea34e95..dc8577bd1dbb24d4869f34e39b4a811b6c543985 100644 (file)
@@ -8,16 +8,20 @@ import (
        "fl-gui.name/twitchchat/twitch/core/messages"
 )
 
+var IncorrectPongResponse = errors.New("incorrect twitch pong response")
+var NoPongResponse = errors.New("lost pong response")
+
 // Intercept received ping messages and respond to them with an appropriate pong
-func (c *Conn) PingPong(msgs <-chan messages.Message) <-chan messages.Message {
+func (c *Conn) PingPong(msgs <-chan messages.Message) (<-chan messages.Message, <-chan error) {
        res := make(chan messages.Message)
+       errChan := make(chan error)
 
        var pingMessage string
        // send ping
        go func() {
                for _ = range time.Tick(time.Minute * 2) {
                        if pingMessage != "" {
-                               panic(errors.New("Ping message ignored by twitch."))
+                               errChan <- NoPongResponse
                        }
                        pingMessage = randomString(10, AlphaNum)
                        c.WriteMessage(messages.Ping(pingMessage))
@@ -33,6 +37,7 @@ func (c *Conn) PingPong(msgs <-chan messages.Message) <-chan messages.Message {
                                pongText := msg.Text()
                                if pongText != pingMessage {
                                        fmt.Fprintf(DebugLogger, "Error: send PING %s but received a PONG %s\n", pingMessage, pongText)
+                                       errChan <- IncorrectPongResponse
                                }
                                pingMessage = ""
                        } else {
@@ -41,5 +46,5 @@ func (c *Conn) PingPong(msgs <-chan messages.Message) <-chan messages.Message {
                }
        }()
 
-       return res
+       return res, errChan
 }
diff --git a/twitch/join.go b/twitch/join.go
new file mode 100644 (file)
index 0000000..ac46716
--- /dev/null
@@ -0,0 +1,30 @@
+package twitch
+
+import (
+       "strings"
+
+       "fl-gui.name/twitchchat/twitch/core"
+       "fl-gui.name/twitchchat/twitch/core/messages"
+)
+
+func (c ChatClient) Join(channels ...string) (<-chan messages.Message, core.ChannelMembers, error) {
+       allMesgs, members, err := c.Connection.Join(c.Messages, channels...)
+       if err != nil {
+               return nil, members, err
+       }
+       channelMessages := make(chan messages.Message)
+       channelFilter := make(map[string]struct{})
+       for _, c := range channels {
+               lower_c := strings.ToLower(c)
+               channelFilter[lower_c] = struct{}{}
+       }
+       go func() {
+               for msg := range allMesgs {
+                       _, ok := channelFilter[msg.Channel()]
+                       if ok {
+                               channelMessages <- msg
+                       }
+               }
+       }()
+       return channelMessages, members, nil
+}
index 7ba156a365b099a5603585de534eff0d6cac25cd..ca273149f8ce30c5221466c605c10fa36c304832 100644 (file)
@@ -35,7 +35,7 @@ func main() {
                panic(err)
        }
 
-       msgs = conn.PingPong(msgs)
+       msgs, _ = conn.PingPong(msgs)
 
        if msgs, err = conn.WithCapability(msgs, core.TagsCapability, core.MembershipCapability); err != nil {
                if errors.Is(err, core.UnsupportedCapabilitiesError) {
index 7e4b068cbc2a856b509be4a58a1b106da1cba405..bc4b396d3efd8e062b34ebd4c86cbb1f4807c00d 100644 (file)
@@ -8,6 +8,7 @@ import (
        "os"
        "strings"
 
+       "fl-gui.name/twitchchat/twitch"
        "fl-gui.name/twitchchat/twitch/core"
        "fl-gui.name/twitchchat/twitch/core/messages"
 )
@@ -24,29 +25,20 @@ func openFile() (io.WriteCloser, error) {
        return os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_APPEND, fs.ModePerm)
 }
 
-func openChat() (conn *core.Conn, msgs <-chan messages.Message, err error) {
-       conn, err = core.Dial("http://localhost")
-       if err != nil {
-               return
-       }
-
+func openChat() (conn twitch.ChatClient, msgs <-chan messages.Message, err error) {
+       cc := twitch.NewChatClient()
        if *debug {
-               core.DebugLogger = os.Stdout
+               cc.WithLogging(os.Stdout, true)
        }
 
-       msgs = conn.ReadMessages()
-       msgs, err = conn.AuthenticateAnonymous(msgs)
-       if err != nil {
-               return
-       }
-       msgs = conn.PingPong(msgs)
-       msgs, err = conn.WithCapability(msgs, core.MembershipCapability)
+       cc.WithCapability(core.MembershipCapability)
+
+       err = cc.Connect()
        if err != nil {
-               return
+               return cc, nil, err
        }
-
-       msgs, _, err = conn.Join(msgs, *channel)
-       return conn, msgs, err
+       msgs, _, err = cc.Join(*channel)
+       return cc, msgs, err
 }
 
 func main() {
@@ -58,11 +50,11 @@ func main() {
        }
        defer out.Close()
 
-       conn, messages, err := openChat()
+       cc, messages, err := openChat()
        if err != nil {
                panic(err)
        }
-       defer conn.Close()
+       defer cc.Close()
 
        enc := json.NewEncoder(out)
        if *outFile == "" {