From: Fl_GUI Date: Thu, 9 May 2024 18:36:26 +0000 (+0200) Subject: fixes and client X-Git-Url: https://git.openfl.eu/?a=commitdiff_plain;h=7a4ca6103825203f248523745d2a885e5f66bf3a;p=twitch-chat.git fixes and client --- diff --git a/irc/string.go b/irc/string.go index aed67a4..9482c07 100644 --- a/irc/string.go +++ b/irc/string.go @@ -1,6 +1,7 @@ package irc import ( + "bytes" "io" "strings" ) @@ -15,11 +16,16 @@ func (c Message) String() string { // Writes the message to a io.Writer, using WriteString if implemented. // Returns total number of bytes written and first error encountered func (c Message) WriteTo(w io.Writer) (n int, err error) { + buff := new(bytes.Buffer) + + defer buff.Write([]byte{'\r', '\n'}) + defer io.Copy(w, buff) + n = 0 m := 0 write := func(s string) { if err == nil { - m, err = io.WriteString(w, s) + m, err = io.WriteString(buff, s) n += m } } diff --git a/twitch/core/auth.go b/twitch/core/auth.go index 9f1b4ae..452f531 100644 --- a/twitch/core/auth.go +++ b/twitch/core/auth.go @@ -26,14 +26,16 @@ func (c *Conn) Authenticate(nickname, access_token string, msgs <-chan messages. case "375": fallthrough case "372": - fallthrough + authResp <- msg case "376": authResp <- msg + close(authResp) case commands.Notice: err := msg.ToNoticeError() if errors.Is(err, messages.LoginFailedError) || errors.Is(err, messages.InvalidAuthError) { authResp <- msg + close(authResp) } else { res <- msg } diff --git a/twitch/core/capabilities.go b/twitch/core/capabilities.go index 6b4dbb6..e80ca60 100644 --- a/twitch/core/capabilities.go +++ b/twitch/core/capabilities.go @@ -22,19 +22,42 @@ func (c *Conn) WithCapability(msgs <-chan messages.Message, capabilities ...stri return msgs, fmt.Errorf("TagsCapability is unavailable: %v", errors.ErrUnsupported) } + res := make(chan messages.Message) + capResp := make(chan messages.Message, 2) + + go func() { + requestedCount := len(capabilities) + receivedCount := 0 + for msg := range msgs { + if requestedCount == receivedCount || !messages.IsCapAckNak(msg) { + res <- msg + continue + } + capResp <- msg + + if msg.Params.Trailing != "" { + receivedCount += strings.Count(msg.Params.Trailing, " ") + 1 + if receivedCount == requestedCount { + close(capResp) + } + } + } + }() + c.WriteMessage(messages.Capability(caps)) - msg := <-msgs - - if messages.IsAck(msg) { - fmt.Fprintln(DebugLogger, "Capability request acknowledged") - } else if messages.IsNak(msg) { - badCaps := strings.Split(msg.Params.Trailing, " ") - return msgs, unsupportedCapabilities(badCaps) - } else { - // code error - fmt.Fprintf(DebugLogger, "Unexpected message after capability request: %v\n", msg) - return msgs, UnexpectedAnswerError + + for msg := range capResp { + if messages.IsCapAck(msg) { + fmt.Fprintln(DebugLogger, "Capability request acknowledged") + } else if messages.IsCapNak(msg) { + badCaps := strings.Split(msg.Params.Trailing, " ") + return res, UnsupportedCapabilities(badCaps) + } else { + // code error + fmt.Fprintf(DebugLogger, "Unexpected message after capability request: %v\n", msg) + return res, UnexpectedAnswerError + } } - return msgs, nil + return res, nil } diff --git a/twitch/core/commands/commands.go b/twitch/core/commands/commands.go index 1f47e7d..5769dff 100644 --- a/twitch/core/commands/commands.go +++ b/twitch/core/commands/commands.go @@ -14,8 +14,6 @@ const ( // not documented on the main page Cap = "CAP" - Ack = "ACK" - Nak = "NAK" // twitch specific IRC messages. These are all receive only ClearChat = "CLEARCHAT" diff --git a/twitch/core/connection.go b/twitch/core/connection.go index aea4ea6..149b680 100644 --- a/twitch/core/connection.go +++ b/twitch/core/connection.go @@ -24,18 +24,13 @@ func Dial(origin string) (*Conn, error) { } func DialNoSsl(origin string) (*Conn, error) { - c, err := irc.Dial("wss://irc-ws.chat.twitch.tv:80", origin) + c, err := irc.Dial("ws://irc-ws.chat.twitch.tv:80", origin) return (*Conn)(c), err } func (w *Conn) WriteMessage(m messages.Message) error { - fmt.Fprintf(DebugLogger, "Writing %v\n", m) - if _, err := irc.Message(m).WriteTo(w); err != nil { - return err - } - - // termination bytes - _, err := w.Write([]byte{'\r', '\n'}) + fmt.Fprintf(DebugLogger, "Writing %#v\n", irc.Message(m).String()) + _, err := irc.Message(m).WriteTo(w) return err } @@ -53,6 +48,8 @@ func (w *Conn) ReadMessages() <-chan messages.Message { msg, err := irc.ParseBytes(bytes) if err != nil { fmt.Fprintf(DebugLogger, "Could not parse %#v\n", bytes) + } else { + fmt.Fprintf(DebugLogger, "Received %s\n", irc.Message(msg)) } res <- messages.Message(msg) } diff --git a/twitch/core/errors.go b/twitch/core/errors.go index 0e25328..3acc5aa 100644 --- a/twitch/core/errors.go +++ b/twitch/core/errors.go @@ -6,19 +6,19 @@ import ( "strings" ) -type unsupportedCapabilities []string +type UnsupportedCapabilities []string var UnexpectedAnswerError = errors.New("Unexpected result") var UnsupportedCapabilitiesError = errors.New("unsupported capabilities") -func (u unsupportedCapabilities) Unwrap() error { +func (u UnsupportedCapabilities) Unwrap() error { return UnsupportedCapabilitiesError } -func (u unsupportedCapabilities) Error() string { +func (u UnsupportedCapabilities) Error() string { return fmt.Sprintf("cannot request capabilities %v: %v", strings.Join(u, ", "), UnsupportedCapabilitiesError) } -func (u unsupportedCapabilities) Is(target error) bool { +func (u UnsupportedCapabilities) Is(target error) bool { return target == UnsupportedCapabilitiesError } diff --git a/twitch/core/join.go b/twitch/core/join.go index 88bd7d0..a82baa4 100644 --- a/twitch/core/join.go +++ b/twitch/core/join.go @@ -1,6 +1,9 @@ package core -import "twitchchat/twitch/core/messages" +import ( + "fmt" + "twitchchat/twitch/core/messages" +) type ChannelMembers map[string][]string @@ -8,10 +11,11 @@ func (w *Conn) Join(msgs <-chan messages.Message, channels ...string) (<-chan me w.WriteMessage(messages.Join(channels...)) - var channelMembers ChannelMembers + channelMembers := make(ChannelMembers) for _, c := range channels { resp := <-msgs + fmt.Fprintf(DebugLogger, "Reading channel members for %s\n", resp) if messages.IsNotice(resp) { return msgs, channelMembers, resp.ToNoticeError() } else if !messages.IsJoin(resp) { diff --git a/twitch/core/messages/acknak.go b/twitch/core/messages/acknak.go deleted file mode 100644 index b30bf4b..0000000 --- a/twitch/core/messages/acknak.go +++ /dev/null @@ -1,15 +0,0 @@ -package messages - -import "twitchchat/twitch/core/commands" - -func IsAckNak(m Message) bool { - return IsAck(m) || IsNak(m) -} - -func IsAck(m Message) bool { - return m.Command == commands.Ack -} - -func IsNak(m Message) bool { - return m.Command == commands.Nak -} diff --git a/twitch/core/messages/auth.go b/twitch/core/messages/auth.go index ddab51c..6993819 100644 --- a/twitch/core/messages/auth.go +++ b/twitch/core/messages/auth.go @@ -11,7 +11,7 @@ func Pass(nickname string) Message { irc.Prefix{}, irc.Command(commands.Pass), irc.Params{ - []string{fmt.Sprintf("oath:%s", nickname)}, + []string{fmt.Sprintf("oauth:%s", nickname)}, "", }, } diff --git a/twitch/core/messages/capability.go b/twitch/core/messages/capability.go index 0a472e1..cff9ce8 100644 --- a/twitch/core/messages/capability.go +++ b/twitch/core/messages/capability.go @@ -10,8 +10,26 @@ func Capability(capability string) Message { irc.Prefix{}, irc.Command(commands.Cap), irc.Params{ - []string{}, + []string{"REQ"}, capability, }, } } + +func IsCapAckNak(m Message) bool { + return IsCapAck(m) || IsCapNak(m) +} + +func IsCapAck(m Message) bool { + return m.Command == commands.Cap && + len(m.Params.Params) == 2 && + m.Params.Params[0] == "*" && + m.Params.Params[1] == "ACK" +} + +func IsCapNak(m Message) bool { + return m.Command == commands.Cap && + len(m.Params.Params) == 2 && + m.Params.Params[0] == "*" && + m.Params.Params[1] == "NAK" +} diff --git a/twitch/core/messages/messages.go b/twitch/core/messages/messages.go index 872f0b4..6605b72 100644 --- a/twitch/core/messages/messages.go +++ b/twitch/core/messages/messages.go @@ -3,3 +3,7 @@ package messages import "twitchchat/irc" type Message irc.Message + +func (m Message) String() string { + return irc.Message(m).String() +} diff --git a/twitch/core/messages/ping.go b/twitch/core/messages/ping.go index 220f95f..0e5d963 100644 --- a/twitch/core/messages/ping.go +++ b/twitch/core/messages/ping.go @@ -13,10 +13,18 @@ func (p Message) Text() string { return irc.Message(p).Params.Trailing } +func Ping(s string) Message { + return Message{ + irc.Prefix{}, + irc.Command(commands.Ping), + irc.Params{[]string{s}, ""}, + } +} + func (p Message) ToPong() Message { return Message{ irc.Prefix{}, irc.Command(commands.Pong), - irc.Params{[]string{}, p.Text()}, + irc.Params{[]string{p.Text()}, ""}, } } diff --git a/x/clichat/go.mod b/x/clichat/go.mod new file mode 100644 index 0000000..2caf2f3 --- /dev/null +++ b/x/clichat/go.mod @@ -0,0 +1,5 @@ +module clichat + +go 1.22.3 + +require golang.org/x/net v0.25.0 diff --git a/x/clichat/go.sum b/x/clichat/go.sum new file mode 100644 index 0000000..ec14cb3 --- /dev/null +++ b/x/clichat/go.sum @@ -0,0 +1,2 @@ +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= diff --git a/x/corechatclient/corechatclient b/x/corechatclient/corechatclient new file mode 100755 index 0000000..468bc3d Binary files /dev/null and b/x/corechatclient/corechatclient differ diff --git a/x/corechatclient/main.go b/x/corechatclient/main.go new file mode 100644 index 0000000..a4745f5 --- /dev/null +++ b/x/corechatclient/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "errors" + "fmt" + "os" + "twitchchat/twitch/core" + "twitchchat/twitch/core/messages" +) + +func main() { + conn, err := core.Dial("http://localhost") + if err != nil { + panic(err) + } + defer conn.Close() + + core.DebugLogger = os.Stdout + + msgs := conn.ReadMessages() + + conn.WriteMessage(messages.Ping("foobar")) + <-msgs + + if msgs, err = conn.Authenticate("fl_gui", "511j9db3a4xqk8jdukj11nh1eok2d2", msgs); err != nil { + panic(err) + } + + if msgs, err = conn.WithCapability(msgs, core.MembershipCapability, "Unknown", "foobar"); err != nil { + if errors.Is(err, core.UnsupportedCapabilitiesError) { + fmt.Printf("%#v\n", err.(core.UnsupportedCapabilities)) + } else { + panic(err) + } + } + + var members core.ChannelMembers + if msgs, members, err = conn.Join(msgs, "fl_gui"); err != nil { + panic(err) + } + fmt.Printf("current members: %s", members) + + for m := range msgs { + fmt.Println(m) + } +} diff --git a/x/corechatclient/out b/x/corechatclient/out new file mode 100644 index 0000000..35e4cf6 --- /dev/null +++ b/x/corechatclient/out @@ -0,0 +1 @@ +signal: interrupt