]> git.openfl.eu Git - twitch-chat.git/commitdiff
generate all messages
authorFl_GUI <flor.guilini@hotmail.com>
Sat, 18 May 2024 14:51:25 +0000 (16:51 +0200)
committerFl_GUI <flor.guilini@hotmail.com>
Sat, 18 May 2024 14:51:25 +0000 (16:51 +0200)
40 files changed:
twitch/core/auth.go
twitch/core/clearchat/clearchat.go [deleted file]
twitch/core/clearchat/clearchattags.go [deleted file]
twitch/core/clearchat/message.go [new file with mode: 0644]
twitch/core/clearmsg/clearmsg.go [deleted file]
twitch/core/clearmsg/message.go [new file with mode: 0644]
twitch/core/globaluserstate/globaluserstate.go [deleted file]
twitch/core/globaluserstate/globaluserstatetags.go [deleted file]
twitch/core/globaluserstate/message.go [new file with mode: 0644]
twitch/core/hosttarget/hosttarget.go [deleted file]
twitch/core/hosttarget/message.go [new file with mode: 0644]
twitch/core/join.go
twitch/core/messages.go [new file with mode: 0644]
twitch/core/messages/join.go
twitch/core/notice/errors.go [moved from twitch/core/messages/notice.go with 61% similarity]
twitch/core/notice/message.go [new file with mode: 0644]
twitch/core/notice/notice.go [deleted file]
twitch/core/notice/noticetags.go [deleted file]
twitch/core/privmsg/message.go [new file with mode: 0644]
twitch/core/privmsg/privmsg.go [deleted file]
twitch/core/privmsg/privmsgtags.go [deleted file]
twitch/core/reconnect/message.go [new file with mode: 0644]
twitch/core/roomstate/message.go [new file with mode: 0644]
twitch/core/roomstate/roomstate.go [deleted file]
twitch/core/roomstate/roomstatetags.go [deleted file]
twitch/core/usernotice/message.go [new file with mode: 0644]
twitch/core/usernotice/usernotice.go [deleted file]
twitch/core/usernotice/usernoticetags.go [deleted file]
twitch/core/userstate/message.go [new file with mode: 0644]
twitch/core/userstate/userstate.go [deleted file]
twitch/core/userstate/userstatetags.go [deleted file]
twitch/core/whisper/message.go [new file with mode: 0644]
twitch/core/whisper/whisper.go [deleted file]
twitch/core/whisper/whispertags.go [deleted file]
x/corechatclient/main.go
x/generate/message/clearchat/message.go [new file with mode: 0644]
x/generate/message/convert.go [new file with mode: 0644]
x/generate/message/convert_test.go [new file with mode: 0644]
x/generate/message/generateMessage.go [new file with mode: 0644]
x/generate/message/main.go [new file with mode: 0644]

index 452f53156fe7bf25226af885bbb791f4dd6c52ae..edde3fff04c9bf584a1ca77127bd484a34afc911 100644 (file)
@@ -4,6 +4,7 @@ import (
        "errors"
        "twitchchat/twitch/core/commands"
        "twitchchat/twitch/core/messages"
+       "twitchchat/twitch/core/notice"
 )
 
 // see https://dev.twitch.tv/docs/irc/authenticate-bot/#sending-the-pass-and-nick-messages.
@@ -31,9 +32,9 @@ func (c *Conn) Authenticate(nickname, access_token string, msgs <-chan messages.
                                authResp <- msg
                                close(authResp)
                        case commands.Notice:
-                               err := msg.ToNoticeError()
-                               if errors.Is(err, messages.LoginFailedError) ||
-                                       errors.Is(err, messages.InvalidAuthError) {
+                               err := notice.Notice{msg}.ToNoticeError()
+                               if errors.Is(err, notice.LoginFailedError) ||
+                                       errors.Is(err, notice.InvalidAuthError) {
                                        authResp <- msg
                                        close(authResp)
                                } else {
@@ -49,8 +50,8 @@ func (c *Conn) Authenticate(nickname, access_token string, msgs <-chan messages.
        c.WriteMessage(messages.Nick(nickname))
 
        for m := range authResp {
-               if messages.IsNotice(m) {
-                       return res, m.ToNoticeError()
+               if notice.IsNotice(m) {
+                       return res, notice.Notice{m}.ToNoticeError()
                }
        }
 
diff --git a/twitch/core/clearchat/clearchat.go b/twitch/core/clearchat/clearchat.go
deleted file mode 100644 (file)
index f1dc74f..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-package clearchat
-
-import (
-       "twitchchat/irc"
-       "twitchchat/twitch/core/commands"
-       "twitchchat/twitch/core/messages"
-)
-
-//go:generate tagprototype -out clearchattags.go -package clearchat -prototype @ban-duration=<duration>;room-id=<room-id>;target-user-id=<user-id>;tmi-sent-ts=<timestamp> -type ClearChatTag
-
-type ClearChat struct {
-       messages.Message
-}
-
-func IsClearChat(m messages.Message) bool {
-       return string(m.Command) == commands.ClearChat
-}
-
-func (c ClearChat) GetTag(key ClearChatTag) *irc.Tag {
-       return c.Message.GetTag(string(key))
-}
-
-func (c ClearChat) User() string {
-       return c.Params.Trailing
-}
diff --git a/twitch/core/clearchat/clearchattags.go b/twitch/core/clearchat/clearchattags.go
deleted file mode 100755 (executable)
index 6d1e9ae..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-//Generated by twitch-chat/x/generate/tagprototype
-
-package clearchat
-
-type ClearChatTag string
-
-const (
-       BanDuration  ClearChatTag = "ban-duration"
-       RoomId       ClearChatTag = "room-id"
-       TargetUserId ClearChatTag = "target-user-id"
-       TmiSentTs    ClearChatTag = "tmi-sent-ts"
-)
diff --git a/twitch/core/clearchat/message.go b/twitch/core/clearchat/message.go
new file mode 100644 (file)
index 0000000..1aac42c
--- /dev/null
@@ -0,0 +1,54 @@
+// generated by message
+
+package clearchat
+
+import (
+       "strings"
+       "twitchchat/twitch/core/messages"
+)
+
+type Clearchat struct {
+       messages.Message
+}
+
+func IsClearchat(m messages.Message) bool { return string(m.Command) == "CLEARCHAT" }
+
+func (m Clearchat) Channel() string { return m.Params.Params[0][1:] }
+
+func (m Clearchat) User() string { return strings.Split(m.Params.Trailing, " ")[0][0:] }
+
+func (m Clearchat) Duration() string {
+       for _, t := range m.Tags {
+               if t.Key == "ban-duration" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Clearchat) RoomId() string {
+       for _, t := range m.Tags {
+               if t.Key == "room-id" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Clearchat) UserId() string {
+       for _, t := range m.Tags {
+               if t.Key == "target-user-id" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Clearchat) Timestamp() string {
+       for _, t := range m.Tags {
+               if t.Key == "tmi-sent-ts" {
+                       return t.Value
+               }
+       }
+       return ""
+}
diff --git a/twitch/core/clearmsg/clearmsg.go b/twitch/core/clearmsg/clearmsg.go
deleted file mode 100644 (file)
index ae64d6f..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-package clearmsg
-
-import (
-       "twitchchat/twitch/core/commands"
-       "twitchchat/twitch/core/messages"
-)
-
-type ClearMsg struct {
-       messages.Message
-}
-
-func IsClearMsg(m messages.Message) bool {
-       return string(m.Command) == commands.ClearMsg
-}
-
-func (c ClearMsg) ChatMessage() string {
-       return c.Params.Trailing
-}
diff --git a/twitch/core/clearmsg/message.go b/twitch/core/clearmsg/message.go
new file mode 100644 (file)
index 0000000..2962967
--- /dev/null
@@ -0,0 +1,54 @@
+// generated by message
+
+package clearmsg
+
+import (
+       "strings"
+       "twitchchat/twitch/core/messages"
+)
+
+type Clearmsg struct {
+       messages.Message
+}
+
+func IsClearmsg(m messages.Message) bool { return string(m.Command) == "CLEARMSG" }
+
+func (m Clearmsg) Channel() string { return m.Params.Params[0][1:] }
+
+func (m Clearmsg) UserMessage() string { return strings.Split(m.Params.Trailing, " ")[0][0:] }
+
+func (m Clearmsg) Login() string {
+       for _, t := range m.Tags {
+               if t.Key == "login" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Clearmsg) RoomId() string {
+       for _, t := range m.Tags {
+               if t.Key == "room-id" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Clearmsg) TargetMsgId() string {
+       for _, t := range m.Tags {
+               if t.Key == "target-msg-id" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Clearmsg) Timestamp() string {
+       for _, t := range m.Tags {
+               if t.Key == "tmi-sent-ts" {
+                       return t.Value
+               }
+       }
+       return ""
+}
diff --git a/twitch/core/globaluserstate/globaluserstate.go b/twitch/core/globaluserstate/globaluserstate.go
deleted file mode 100644 (file)
index 8c64a35..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-package globaluserstate
-
-import (
-       "twitchchat/twitch/core/commands"
-       "twitchchat/twitch/core/messages"
-)
-
-//go:generate tagprototype -out globaluserstatetags.go -package globaluserstate -type GlobalUserStateTag -prototype @badge-info=<badge-info>;badges=<badges>;color=<color>;display-name=<display-name>;emote-sets=<emote-sets>;turbo=<turbo>;user-id=<user-id>;user-type=<user-type>
-
-func IsGlobalUserState(m messages.Message) bool {
-       return m.Command == commands.GlobalUserState
-}
diff --git a/twitch/core/globaluserstate/globaluserstatetags.go b/twitch/core/globaluserstate/globaluserstatetags.go
deleted file mode 100755 (executable)
index e8861de..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-//Generated by twitch-chat/x/generate/tagprototype
-
-package globaluserstate
-
-type GlobalUserStateTag string
-
-const (
-       BadgeInfo   GlobalUserStateTag = "badge-info"
-       Badges      GlobalUserStateTag = "badges"
-       Color       GlobalUserStateTag = "color"
-       DisplayName GlobalUserStateTag = "display-name"
-       EmoteSets   GlobalUserStateTag = "emote-sets"
-       Turbo       GlobalUserStateTag = "turbo"
-       UserId      GlobalUserStateTag = "user-id"
-       UserType    GlobalUserStateTag = "user-type"
-)
diff --git a/twitch/core/globaluserstate/message.go b/twitch/core/globaluserstate/message.go
new file mode 100644 (file)
index 0000000..8c985e4
--- /dev/null
@@ -0,0 +1,83 @@
+// generated by message
+
+package globaluserstate
+
+import "twitchchat/twitch/core/messages"
+
+type Globaluserstate struct {
+       messages.Message
+}
+
+func IsGlobaluserstate(m messages.Message) bool { return string(m.Command) == "GLOBALUSERSTATE" }
+
+func (m Globaluserstate) BadgeInfo() string {
+       for _, t := range m.Tags {
+               if t.Key == "badge-info" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Globaluserstate) Badges() string {
+       for _, t := range m.Tags {
+               if t.Key == "badges" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Globaluserstate) Color() string {
+       for _, t := range m.Tags {
+               if t.Key == "color" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Globaluserstate) DisplayName() string {
+       for _, t := range m.Tags {
+               if t.Key == "display-name" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Globaluserstate) EmoteSets() string {
+       for _, t := range m.Tags {
+               if t.Key == "emote-sets" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Globaluserstate) Turbo() string {
+       for _, t := range m.Tags {
+               if t.Key == "turbo" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Globaluserstate) UserId() string {
+       for _, t := range m.Tags {
+               if t.Key == "user-id" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Globaluserstate) UserType() string {
+       for _, t := range m.Tags {
+               if t.Key == "user-type" {
+                       return t.Value
+               }
+       }
+       return ""
+}
diff --git a/twitch/core/hosttarget/hosttarget.go b/twitch/core/hosttarget/hosttarget.go
deleted file mode 100644 (file)
index b174960..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-package hosttarget
-
-import (
-       "strconv"
-       "strings"
-       "twitchchat/twitch/core/commands"
-       "twitchchat/twitch/core/messages"
-)
-
-type HostTarget struct {
-       messages.Message
-}
-
-func IsHostTarget(m messages.Message) bool {
-       return m.Command == commands.HostTarget
-}
-
-func (h HostTarget) ChannelTarget() string {
-       parts := strings.Split(h.Params.Trailing, " ")
-       if len(parts) != 2 {
-               return ""
-       }
-       return parts[0]
-}
-
-func (h HostTarget) NumberOfViewers() int {
-       parts := strings.Split(h.Params.Trailing, " ")
-       if len(parts) != 2 {
-               return 0
-       }
-       if i, err := strconv.Atoi(parts[1]); err != nil {
-               return 0
-       } else {
-               return i
-       }
-}
diff --git a/twitch/core/hosttarget/message.go b/twitch/core/hosttarget/message.go
new file mode 100644 (file)
index 0000000..a020ba7
--- /dev/null
@@ -0,0 +1,20 @@
+// generated by message
+
+package hosttarget
+
+import (
+       "strings"
+       "twitchchat/twitch/core/messages"
+)
+
+type Hosttarget struct {
+       messages.Message
+}
+
+func IsHosttarget(m messages.Message) bool { return string(m.Command) == "HOSTTARGET" }
+
+func (m Hosttarget) HostingChannel() string { return m.Params.Params[0][1:] }
+
+func (m Hosttarget) Channel() string { return strings.Split(m.Params.Trailing, " ")[0][0:] }
+
+func (m Hosttarget) NumberOfViewers() string { return strings.Split(m.Params.Trailing, " ")[1][0:] }
index a82baa4a8f972b86ac7bb9c737548e7efb92a236..1edc257f18633d9c51d201195a80f221540b85dd 100644 (file)
@@ -3,6 +3,7 @@ package core
 import (
        "fmt"
        "twitchchat/twitch/core/messages"
+       "twitchchat/twitch/core/notice"
 )
 
 type ChannelMembers map[string][]string
@@ -16,8 +17,8 @@ func (w *Conn) Join(msgs <-chan messages.Message, channels ...string) (<-chan me
                resp := <-msgs
 
                fmt.Fprintf(DebugLogger, "Reading channel members for %s\n", resp)
-               if messages.IsNotice(resp) {
-                       return msgs, channelMembers, resp.ToNoticeError()
+               if notice.IsNotice(resp) {
+                       return msgs, channelMembers, notice.Notice{resp}.ToNoticeError()
                } else if !messages.IsJoin(resp) {
                        return msgs, channelMembers, UnexpectedAnswerError
                }
diff --git a/twitch/core/messages.go b/twitch/core/messages.go
new file mode 100644 (file)
index 0000000..423fc3c
--- /dev/null
@@ -0,0 +1,23 @@
+package core
+
+//go:generate message -proto "@badge-info=;badges=<badges>;client-nonce=<client-nonce>;color=<color>;display-name=<display-name>;emote-only=<emote-only>;emotes=<emotes>;first-msg=<first-msg>;flags=;id=<id>;mod=<mod>;room-id=<room-id>;subscriber=<subscriber>;tmi-sent-ts=<tmi-sent-ts>;turbo=<turbo>;user-id=<user-id>;user-type= :<user>!<user>@<user>.tmi.twitch.tv PRIVMSG #<channel> :<user-message>"
+
+//go:generate message -proto "@ban-duration=<duration>;room-id=<room-id>;target-user-id=<user-id>;tmi-sent-ts=<timestamp> :tmi.twitch.tv CLEARCHAT #<channel> :<user>"
+
+//go:generate message -proto "@login=<login>;room-id=<room-id>;target-msg-id=<target-msg-id>;tmi-sent-ts=<timestamp> :tmi.twitch.tv CLEARMSG #<channel> :<user-message>"
+
+//go:generate message -proto "@badge-info=<badge-info>;badges=<badges>;color=<color>;display-name=<display-name>;emote-sets=<emote-sets>;turbo=<turbo>;user-id=<user-id>;user-type=<user-type> :tmi.twitch.tv GLOBALUSERSTATE"
+
+//go:generate message -proto ":tmi.twitch.tv HOSTTARGET #<hosting-channel> :[-|<channel>] <number-of-viewers>"
+
+//go:generate message -proto "@msg-id=<msg-id>;target-user-id=<user-id> :tmi.twitch.tv NOTICE #<channel> :<notice-message>"
+
+//go:generate message -proto ":tmi.twitch.tv RECONNECT"
+
+//go:generate message -proto "@emote-only=<emote-only>;followers-only=<followers-only>;r9k=<r9k>;rituals=<rituals>;room-id=<room-id>;slow=<slow>;subs-only=<subs-only> :tmi.twitch.tv ROOMSTATE #<channel>"
+
+//go:generate message -proto "@badge-info=<badge-info>;badges=<badges>;color=<color>;display-name=<display-name>;emotes=<emotes>;id=<id-of-msg>;login=<user>;mod=<mod>;msg-id=<msg-id>;room-id=<room-id>;subscriber=<subscriber>;system-msg=<system-msg>;tmi-sent-ts=<timestamp>;turbo=<turbo>;user-id=<user-id>;user-type=<user-type> :tmi.twitch.tv USERNOTICE #<channel> :[<user-message>]"
+
+//go:generate message -proto "@badge-info=<badge-info>;badges=<badges>;color=<color>;display-name=<display-name>;emote-sets=<emote-sets>;id=<id>;mod=<mod>;subscriber=<subscriber>;turbo=<turbo>;user-type=<user-type> :tmi.twitch.tv USERSTATE #<channel>"
+
+//go:generate message -proto "@badges=<badges>;color=<color>;display-name=<display-name>;emotes=<emotes>;message-id=<msg-id>;thread-id=<thread-id>;turbo=<turbo>;user-id=<user-id>;user-type=<user-type> :<to-user>!<to-user>@<to-user>.tmi.twitch.tv WHISPER <from-user> :<user-message>"
index a5c6439ac01785793891eadb725c34f7d891b35f..bbc816576a1b88dcc773b4cf07ba6fd15ffb0cc0 100644 (file)
@@ -10,9 +10,9 @@ func Join(channels ...string) Message {
        b := strings.Builder{}
        for n, c := range channels {
                if n != 0 {
-                       b.WriteString(",")
+                       b.WriteByte(',')
                }
-               b.WriteString("#")
+               b.WriteByte('#')
                b.WriteString(c)
        }
        return Message{
similarity index 61%
rename from twitch/core/messages/notice.go
rename to twitch/core/notice/errors.go
index 650a4e9796c64b0128730fb57738f73715d80062..dc464538ad5c9fec819889044dfcd0c04829e354 100644 (file)
@@ -1,22 +1,11 @@
-package messages
+package notice
 
-import (
-       "errors"
-       "twitchchat/twitch/core/commands"
-)
+import "errors"
 
 var LoginFailedError = errors.New("login authentication failed")
 var InvalidAuthError = errors.New("improperly formatted auth")
 
-func IsNotice(m Message) bool {
-       return m.Command == commands.Notice
-}
-
-func (m Message) ToNoticeError() error {
-       if !IsNotice(m) {
-               return nil
-       }
-
+func (m Notice) ToNoticeError() error {
        // known errors by string value
        switch m.Params.Trailing {
        case "Login authentication failed":
diff --git a/twitch/core/notice/message.go b/twitch/core/notice/message.go
new file mode 100644 (file)
index 0000000..7ae9435
--- /dev/null
@@ -0,0 +1,36 @@
+// generated by message
+
+package notice
+
+import (
+       "strings"
+       "twitchchat/twitch/core/messages"
+)
+
+type Notice struct {
+       messages.Message
+}
+
+func IsNotice(m messages.Message) bool { return string(m.Command) == "NOTICE" }
+
+func (m Notice) Channel() string { return m.Params.Params[0][1:] }
+
+func (m Notice) NoticeMessage() string { return strings.Split(m.Params.Trailing, " ")[0][0:] }
+
+func (m Notice) MsgId() string {
+       for _, t := range m.Tags {
+               if t.Key == "msg-id" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Notice) UserId() string {
+       for _, t := range m.Tags {
+               if t.Key == "target-user-id" {
+                       return t.Value
+               }
+       }
+       return ""
+}
diff --git a/twitch/core/notice/notice.go b/twitch/core/notice/notice.go
deleted file mode 100644 (file)
index 49afc93..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-package notice
-
-import (
-       "twitchchat/irc"
-       "twitchchat/twitch/core/commands"
-       "twitchchat/twitch/core/messages"
-)
-
-//go:generate tagprototype -out noticetags.go -package notice -type NoticeTag -prototype @msg-id=<msg-id>;target-user-id=<user-id>
-
-type Notice struct {
-       messages.Message
-}
-
-func IsNotice(m messages.Message) bool {
-       return m.Command == commands.Notice
-}
-
-func (n Notice) GetTag(tag NoticeTag) *irc.Tag {
-       return n.Message.GetTag(string(tag))
-}
-
-func (n Notice) NoticeMessage() string {
-       return n.Params.Trailing
-}
diff --git a/twitch/core/notice/noticetags.go b/twitch/core/notice/noticetags.go
deleted file mode 100755 (executable)
index c1de781..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-//Generated by twitch-chat/x/generate/tagprototype
-
-package notice
-
-type NoticeTag string
-
-const (
-       MsgId        NoticeTag = "msg-id"
-       TargetUserId NoticeTag = "target-user-id"
-)
diff --git a/twitch/core/privmsg/message.go b/twitch/core/privmsg/message.go
new file mode 100644 (file)
index 0000000..03d84c2
--- /dev/null
@@ -0,0 +1,146 @@
+// generated by message
+
+package privmsg
+
+import (
+       "strings"
+       "twitchchat/twitch/core/messages"
+)
+
+type Privmsg struct {
+       messages.Message
+}
+
+func IsPrivmsg(m messages.Message) bool { return string(m.Command) == "PRIVMSG" }
+
+func (m Privmsg) User() string { return m.Prefix.User }
+
+func (m Privmsg) Channel() string { return m.Params.Params[0][1:] }
+
+func (m Privmsg) UserMessage() string { return strings.Split(m.Params.Trailing, " ")[0][0:] }
+
+func (m Privmsg) Badges() string {
+       for _, t := range m.Tags {
+               if t.Key == "badges" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Privmsg) ClientNonce() string {
+       for _, t := range m.Tags {
+               if t.Key == "client-nonce" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Privmsg) Color() string {
+       for _, t := range m.Tags {
+               if t.Key == "color" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Privmsg) DisplayName() string {
+       for _, t := range m.Tags {
+               if t.Key == "display-name" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Privmsg) EmoteOnly() string {
+       for _, t := range m.Tags {
+               if t.Key == "emote-only" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Privmsg) Emotes() string {
+       for _, t := range m.Tags {
+               if t.Key == "emotes" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Privmsg) FirstMsg() string {
+       for _, t := range m.Tags {
+               if t.Key == "first-msg" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Privmsg) Id() string {
+       for _, t := range m.Tags {
+               if t.Key == "id" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Privmsg) Mod() string {
+       for _, t := range m.Tags {
+               if t.Key == "mod" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Privmsg) RoomId() string {
+       for _, t := range m.Tags {
+               if t.Key == "room-id" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Privmsg) Subscriber() string {
+       for _, t := range m.Tags {
+               if t.Key == "subscriber" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Privmsg) TmiSentTs() string {
+       for _, t := range m.Tags {
+               if t.Key == "tmi-sent-ts" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Privmsg) Turbo() string {
+       for _, t := range m.Tags {
+               if t.Key == "turbo" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Privmsg) UserId() string {
+       for _, t := range m.Tags {
+               if t.Key == "user-id" {
+                       return t.Value
+               }
+       }
+       return ""
+}
diff --git a/twitch/core/privmsg/privmsg.go b/twitch/core/privmsg/privmsg.go
deleted file mode 100644 (file)
index 496c30e..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-package privmsg
-
-import (
-       "twitchchat/irc"
-       "twitchchat/twitch/core/commands"
-       "twitchchat/twitch/core/messages"
-)
-
-//go:generate tagprototype -out "privmsgtags.go" -type PrivMsgTag -prototype "@badge-info=<badge-info>;badges=<badges>;bits=<bits>client-nonce=<nonce>;color=<color>;display-name=<display-name>;emotes=<emotes>;first-msg=<first-msg>;flags=<flags>;id=<msg-id>;mod=<mod>;room-id=<room-id>;subscriber=<subscriber>;tmi-sent-ts=<timestamp>;turbo=<turbo>;user-id=<user-id>;user-type=<user-type>;reply-parent-msg-id=<reply-parent-msg-id>;reply-parent-user-id=<reply-parent-user-id>;reply-parent-user-login=<reply-parent-user-login>;reply-parent-display-name=<reply-parent-display-name>;reply-parent-msg-body=<reply-parent-msg-body>;reply-thread-parent-msg-id=<reply-thread-parent-msg-id>;reply-thread-parent-user-login=<reply-thread-parent-user-login>;vip=<vip>" -package privmsg
-
-type PrivMsg struct {
-       messages.Message
-}
-
-func IsPrivateMessage(m messages.Message) bool {
-       return string(m.Command) == commands.PrivMsg
-}
-
-func (p PrivMsg) User() string {
-       return p.Prefix.Name
-}
-
-func (p PrivMsg) Text() string {
-       return p.Params.Trailing
-}
-
-func (p PrivMsg) GetTag(key PrivMsgTag) *irc.Tag {
-       return p.Message.GetTag(string(key))
-}
diff --git a/twitch/core/privmsg/privmsgtags.go b/twitch/core/privmsg/privmsgtags.go
deleted file mode 100755 (executable)
index f9e3571..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-//Generated by twitch-chat/x/generate/tagprototype
-
-package privmsg
-
-type PrivMsgTag string
-
-const (
-       BadgeInfo                  PrivMsgTag = "badge-info"
-       Badges                     PrivMsgTag = "badges"
-       Bits                       PrivMsgTag = "bits"
-       Color                      PrivMsgTag = "color"
-       DisplayName                PrivMsgTag = "display-name"
-       Emotes                     PrivMsgTag = "emotes"
-       FirstMsg                   PrivMsgTag = "first-msg"
-       Flags                      PrivMsgTag = "flags"
-       Id                         PrivMsgTag = "id"
-       Mod                        PrivMsgTag = "mod"
-       RoomId                     PrivMsgTag = "room-id"
-       Subscriber                 PrivMsgTag = "subscriber"
-       TmiSentTs                  PrivMsgTag = "tmi-sent-ts"
-       Turbo                      PrivMsgTag = "turbo"
-       UserId                     PrivMsgTag = "user-id"
-       UserType                   PrivMsgTag = "user-type"
-       ReplyParentMsgId           PrivMsgTag = "reply-parent-msg-id"
-       ReplyParentUserId          PrivMsgTag = "reply-parent-user-id"
-       ReplyParentUserLogin       PrivMsgTag = "reply-parent-user-login"
-       ReplyParentDisplayName     PrivMsgTag = "reply-parent-display-name"
-       ReplyParentMsgBody         PrivMsgTag = "reply-parent-msg-body"
-       ReplyThreadParentMsgId     PrivMsgTag = "reply-thread-parent-msg-id"
-       ReplyThreadParentUserLogin PrivMsgTag = "reply-thread-parent-user-login"
-       Vip                        PrivMsgTag = "vip"
-)
diff --git a/twitch/core/reconnect/message.go b/twitch/core/reconnect/message.go
new file mode 100644 (file)
index 0000000..632c90b
--- /dev/null
@@ -0,0 +1,11 @@
+// generated by message
+
+package reconnect
+
+import "twitchchat/twitch/core/messages"
+
+type Reconnect struct {
+       messages.Message
+}
+
+func IsReconnect(m messages.Message) bool { return string(m.Command) == "RECONNECT" }
diff --git a/twitch/core/roomstate/message.go b/twitch/core/roomstate/message.go
new file mode 100644 (file)
index 0000000..61997b4
--- /dev/null
@@ -0,0 +1,76 @@
+// generated by message
+
+package roomstate
+
+import "twitchchat/twitch/core/messages"
+
+type Roomstate struct {
+       messages.Message
+}
+
+func IsRoomstate(m messages.Message) bool { return string(m.Command) == "ROOMSTATE" }
+
+func (m Roomstate) Channel() string { return m.Params.Params[0][1:] }
+
+func (m Roomstate) EmoteOnly() string {
+       for _, t := range m.Tags {
+               if t.Key == "emote-only" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Roomstate) FollowersOnly() string {
+       for _, t := range m.Tags {
+               if t.Key == "followers-only" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Roomstate) R9k() string {
+       for _, t := range m.Tags {
+               if t.Key == "r9k" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Roomstate) Rituals() string {
+       for _, t := range m.Tags {
+               if t.Key == "rituals" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Roomstate) RoomId() string {
+       for _, t := range m.Tags {
+               if t.Key == "room-id" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Roomstate) Slow() string {
+       for _, t := range m.Tags {
+               if t.Key == "slow" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Roomstate) SubsOnly() string {
+       for _, t := range m.Tags {
+               if t.Key == "subs-only" {
+                       return t.Value
+               }
+       }
+       return ""
+}
diff --git a/twitch/core/roomstate/roomstate.go b/twitch/core/roomstate/roomstate.go
deleted file mode 100644 (file)
index 6817a70..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-package roomstate
-
-import (
-       "twitchchat/irc"
-       "twitchchat/twitch/core/commands"
-       "twitchchat/twitch/core/messages"
-)
-
-//go:generate tagprototype -out roomstatetags.go -package roomstate -type RoomStateTag -prototype @emote-only=<emote-only>;followers-only=<followers-only>;r9k=<r9k>;rituals=<rituals>;room-id=<room-id>;slow=<slow>;subs-only=<subs-only>
-
-type RoomState struct {
-       messages.Message
-}
-
-func IsRoomState(m messages.Message) bool {
-       return m.Command == commands.RoomState
-}
-
-func (r RoomState) GetTag(tag RoomStateTag) *irc.Tag {
-       return r.Message.GetTag(string(tag))
-}
diff --git a/twitch/core/roomstate/roomstatetags.go b/twitch/core/roomstate/roomstatetags.go
deleted file mode 100755 (executable)
index 8e5e16d..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-//Generated by twitch-chat/x/generate/tagprototype
-
-package roomstate
-
-type RoomStateTag string
-
-const (
-       EmoteOnly     RoomStateTag = "emote-only"
-       FollowersOnly RoomStateTag = "followers-only"
-       R9k           RoomStateTag = "r9k"
-       Rituals       RoomStateTag = "rituals"
-       RoomId        RoomStateTag = "room-id"
-       Slow          RoomStateTag = "slow"
-       SubsOnly      RoomStateTag = "subs-only"
-)
diff --git a/twitch/core/usernotice/message.go b/twitch/core/usernotice/message.go
new file mode 100644 (file)
index 0000000..0a981cb
--- /dev/null
@@ -0,0 +1,162 @@
+// generated by message
+
+package usernotice
+
+import (
+       "strings"
+       "twitchchat/twitch/core/messages"
+)
+
+type Usernotice struct {
+       messages.Message
+}
+
+func IsUsernotice(m messages.Message) bool { return string(m.Command) == "USERNOTICE" }
+
+func (m Usernotice) Channel() string { return m.Params.Params[0][1:] }
+
+func (m Usernotice) UserMessage() string { return strings.Split(m.Params.Trailing, " ")[0][0:] }
+
+func (m Usernotice) BadgeInfo() string {
+       for _, t := range m.Tags {
+               if t.Key == "badge-info" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Usernotice) Badges() string {
+       for _, t := range m.Tags {
+               if t.Key == "badges" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Usernotice) Color() string {
+       for _, t := range m.Tags {
+               if t.Key == "color" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Usernotice) DisplayName() string {
+       for _, t := range m.Tags {
+               if t.Key == "display-name" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Usernotice) Emotes() string {
+       for _, t := range m.Tags {
+               if t.Key == "emotes" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Usernotice) IdOfMsg() string {
+       for _, t := range m.Tags {
+               if t.Key == "id" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Usernotice) User() string {
+       for _, t := range m.Tags {
+               if t.Key == "login" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Usernotice) Mod() string {
+       for _, t := range m.Tags {
+               if t.Key == "mod" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Usernotice) MsgId() string {
+       for _, t := range m.Tags {
+               if t.Key == "msg-id" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Usernotice) RoomId() string {
+       for _, t := range m.Tags {
+               if t.Key == "room-id" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Usernotice) Subscriber() string {
+       for _, t := range m.Tags {
+               if t.Key == "subscriber" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Usernotice) SystemMsg() string {
+       for _, t := range m.Tags {
+               if t.Key == "system-msg" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Usernotice) Timestamp() string {
+       for _, t := range m.Tags {
+               if t.Key == "tmi-sent-ts" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Usernotice) Turbo() string {
+       for _, t := range m.Tags {
+               if t.Key == "turbo" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Usernotice) UserId() string {
+       for _, t := range m.Tags {
+               if t.Key == "user-id" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Usernotice) UserType() string {
+       for _, t := range m.Tags {
+               if t.Key == "user-type" {
+                       return t.Value
+               }
+       }
+       return ""
+}
diff --git a/twitch/core/usernotice/usernotice.go b/twitch/core/usernotice/usernotice.go
deleted file mode 100644 (file)
index e6cc1b8..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-package usernotice
-
-import (
-       "twitchchat/twitch/core/commands"
-       "twitchchat/twitch/core/messages"
-)
-
-//go:generate tagprototype -out usernoticetags.go -package usernotice -type UserNoticeTag -prototype @badge-info=<badge-info>;badges=<badges>;color=<color>;display-name=<display-name>;emotes=<emotes>;id=<id-of-msg>;login=<user>;mod=<mod>;msg-id=<msg-id>;room-id=<room-id>;subscriber=<subscriber>;system-msg=<system-msg>;tmi-sent-ts=<timestamp>;turbo=<turbo>;user-id=<user-id>;user-type=<user-type>
-
-type UserNotice struct {
-       messages.Message
-}
-
-func IsUserNotice(m messages.Message) bool {
-       return m.Command == commands.UserNotice
-}
-
-func (u UserNotice) UserMessage() string {
-       return u.Params.Trailing
-}
diff --git a/twitch/core/usernotice/usernoticetags.go b/twitch/core/usernotice/usernoticetags.go
deleted file mode 100755 (executable)
index 94b5380..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-//Generated by twitch-chat/x/generate/tagprototype
-
-package usernotice
-
-type UserNoticeTag string
-
-const (
-       BadgeInfo   UserNoticeTag = "badge-info"
-       Badges      UserNoticeTag = "badges"
-       Color       UserNoticeTag = "color"
-       DisplayName UserNoticeTag = "display-name"
-       Emotes      UserNoticeTag = "emotes"
-       Id          UserNoticeTag = "id"
-       Login       UserNoticeTag = "login"
-       Mod         UserNoticeTag = "mod"
-       MsgId       UserNoticeTag = "msg-id"
-       RoomId      UserNoticeTag = "room-id"
-       Subscriber  UserNoticeTag = "subscriber"
-       SystemMsg   UserNoticeTag = "system-msg"
-       TmiSentTs   UserNoticeTag = "tmi-sent-ts"
-       Turbo       UserNoticeTag = "turbo"
-       UserId      UserNoticeTag = "user-id"
-       UserType    UserNoticeTag = "user-type"
-)
diff --git a/twitch/core/userstate/message.go b/twitch/core/userstate/message.go
new file mode 100644 (file)
index 0000000..975ecab
--- /dev/null
@@ -0,0 +1,103 @@
+// generated by message
+
+package userstate
+
+import "twitchchat/twitch/core/messages"
+
+type Userstate struct {
+       messages.Message
+}
+
+func IsUserstate(m messages.Message) bool { return string(m.Command) == "USERSTATE" }
+
+func (m Userstate) Channel() string { return m.Params.Params[0][1:] }
+
+func (m Userstate) BadgeInfo() string {
+       for _, t := range m.Tags {
+               if t.Key == "badge-info" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Userstate) Badges() string {
+       for _, t := range m.Tags {
+               if t.Key == "badges" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Userstate) Color() string {
+       for _, t := range m.Tags {
+               if t.Key == "color" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Userstate) DisplayName() string {
+       for _, t := range m.Tags {
+               if t.Key == "display-name" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Userstate) EmoteSets() string {
+       for _, t := range m.Tags {
+               if t.Key == "emote-sets" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Userstate) Id() string {
+       for _, t := range m.Tags {
+               if t.Key == "id" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Userstate) Mod() string {
+       for _, t := range m.Tags {
+               if t.Key == "mod" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Userstate) Subscriber() string {
+       for _, t := range m.Tags {
+               if t.Key == "subscriber" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Userstate) Turbo() string {
+       for _, t := range m.Tags {
+               if t.Key == "turbo" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Userstate) UserType() string {
+       for _, t := range m.Tags {
+               if t.Key == "user-type" {
+                       return t.Value
+               }
+       }
+       return ""
+}
diff --git a/twitch/core/userstate/userstate.go b/twitch/core/userstate/userstate.go
deleted file mode 100644 (file)
index 32160c8..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-package userstate
-
-import (
-       "twitchchat/irc"
-       "twitchchat/twitch/core/commands"
-       "twitchchat/twitch/core/messages"
-)
-
-//go:generate tagprototype -out userstatetags.go -package userstate -type UserStateTag -prototype @badge-info=;badges=staff/1;color=#0D4200;display-name=ronni;emote-sets=0,33,50,237,793,2126,3517,4578,5569,9400,10337,12239;mod=1;subscriber=1;turbo=1;user-type=staff :tmi.twitch.tv USERSTATE #dallas
-
-type UserState struct {
-       messages.Message
-}
-
-func IsUserState(m messages.Message) bool {
-       return m.Command == commands.UserState
-}
-
-func (u UserState) GetTag(tag UserStateTag) *irc.Tag {
-       return u.Message.GetTag(string(tag))
-}
diff --git a/twitch/core/userstate/userstatetags.go b/twitch/core/userstate/userstatetags.go
deleted file mode 100755 (executable)
index 2dec248..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-//Generated by twitch-chat/x/generate/tagprototype
-
-package userstate
-
-type UserStateTag string
-
-const (
-       BadgeInfo   UserStateTag = "badge-info"
-       Badges      UserStateTag = "badges"
-       Color       UserStateTag = "color"
-       DisplayName UserStateTag = "display-name"
-       EmoteSets   UserStateTag = "emote-sets"
-       Mod         UserStateTag = "mod"
-       Subscriber  UserStateTag = "subscriber"
-       Turbo       UserStateTag = "turbo"
-       UserType    UserStateTag = "user-type"
-)
diff --git a/twitch/core/whisper/message.go b/twitch/core/whisper/message.go
new file mode 100644 (file)
index 0000000..9ed16a5
--- /dev/null
@@ -0,0 +1,101 @@
+// generated by message
+
+package whisper
+
+import (
+       "strings"
+       "twitchchat/twitch/core/messages"
+)
+
+type Whisper struct {
+       messages.Message
+}
+
+func IsWhisper(m messages.Message) bool { return string(m.Command) == "WHISPER" }
+
+func (m Whisper) ToUser() string { return m.Prefix.User }
+
+func (m Whisper) FromUser() string { return m.Params.Params[0][0:] }
+
+func (m Whisper) UserMessage() string { return strings.Split(m.Params.Trailing, " ")[0][0:] }
+
+func (m Whisper) Badges() string {
+       for _, t := range m.Tags {
+               if t.Key == "badges" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Whisper) Color() string {
+       for _, t := range m.Tags {
+               if t.Key == "color" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Whisper) DisplayName() string {
+       for _, t := range m.Tags {
+               if t.Key == "display-name" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Whisper) Emotes() string {
+       for _, t := range m.Tags {
+               if t.Key == "emotes" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Whisper) MsgId() string {
+       for _, t := range m.Tags {
+               if t.Key == "message-id" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Whisper) ThreadId() string {
+       for _, t := range m.Tags {
+               if t.Key == "thread-id" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Whisper) Turbo() string {
+       for _, t := range m.Tags {
+               if t.Key == "turbo" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Whisper) UserId() string {
+       for _, t := range m.Tags {
+               if t.Key == "user-id" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Whisper) UserType() string {
+       for _, t := range m.Tags {
+               if t.Key == "user-type" {
+                       return t.Value
+               }
+       }
+       return ""
+}
diff --git a/twitch/core/whisper/whisper.go b/twitch/core/whisper/whisper.go
deleted file mode 100644 (file)
index 3fd0197..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-package whisper
-
-import (
-       "twitchchat/twitch/core/commands"
-       "twitchchat/twitch/core/messages"
-)
-
-//go:generate tagprototype -out whispertags.go -package whisper -type WhisperTag -prototype @badges=<badges>;color=<color>;display-name=<display-name>;emotes=<emotes>;message-id=<msg-id>;thread-id=<thread-id>;turbo=<turbo>;user-id=<user-id>;user-type=<user-type>
-
-type Whisper struct {
-       messages.Message
-}
-
-func IsWhisper(m messages.Message) bool {
-       return m.Command == commands.Whisper
-}
-
-func (w Whisper) ToUser() string {
-       return w.Prefix.User
-}
-
-func (w Whisper) FromUser() string {
-       if len(w.Params.Params) != 1 {
-               return ""
-       }
-       return w.Params.Params[0]
-}
-
-func (w Whisper) WhisperMessage() string {
-       return w.Params.Trailing
-}
diff --git a/twitch/core/whisper/whispertags.go b/twitch/core/whisper/whispertags.go
deleted file mode 100755 (executable)
index 159018e..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-//Generated by twitch-chat/x/generate/tagprototype
-
-package whisper
-
-type WhisperTag string
-
-const (
-       Badges      WhisperTag = "badges"
-       Color       WhisperTag = "color"
-       DisplayName WhisperTag = "display-name"
-       Emotes      WhisperTag = "emotes"
-       MessageId   WhisperTag = "message-id"
-       ThreadId    WhisperTag = "thread-id"
-       Turbo       WhisperTag = "turbo"
-       UserId      WhisperTag = "user-id"
-       UserType    WhisperTag = "user-type"
-)
index 81123187fdcf5a7bbfa6c5896f8ebc1b432b864b..56055e9470a908b96cef870b824e5a2251c4f476 100644 (file)
@@ -51,8 +51,8 @@ func main() {
        var format string
        rmax := NewRunningMax(50)
        for m := range msgs {
-               if privmsg.IsPrivateMessage(m) {
-                       priv := privmsg.PrivMsg{m}
+               if privmsg.IsPrivmsg(m) {
+                       priv := privmsg.Privmsg{m}
                        u := priv.User()
                        mx := rmax.Push(len(u))
                        format = fmt.Sprintf("%%%ds: %%s\n", mx)
diff --git a/x/generate/message/clearchat/message.go b/x/generate/message/clearchat/message.go
new file mode 100644 (file)
index 0000000..1856896
--- /dev/null
@@ -0,0 +1,54 @@
+// generated by /tmp/go-build2868507172/b001/exe/message
+
+package clearchat
+
+import (
+       "strings"
+       "twitchchat/irc"
+)
+
+type Clearchat struct {
+       irc.Message
+}
+
+func IsClearchat(m irc.Message) bool { return string(m.Command) == "CLEARCHAT" }
+
+func (m Clearchat) Channel() string { return m.Params.Params[0][1:] }
+
+func (m Clearchat) User() string { return strings.Split(m.Params.Trailing, " ")[0][0:] }
+
+func (m Clearchat) Duration() string {
+       for _, t := range m.Tags {
+               if t.Key == "ban-duration" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Clearchat) RoomId() string {
+       for _, t := range m.Tags {
+               if t.Key == "room-id" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Clearchat) UserId() string {
+       for _, t := range m.Tags {
+               if t.Key == "target-user-id" {
+                       return t.Value
+               }
+       }
+       return ""
+}
+
+func (m Clearchat) Timestamp() string {
+       for _, t := range m.Tags {
+               if t.Key == "tmi-sent-ts" {
+                       return t.Value
+               }
+       }
+       return ""
+}
diff --git a/x/generate/message/convert.go b/x/generate/message/convert.go
new file mode 100644 (file)
index 0000000..dcf17ad
--- /dev/null
@@ -0,0 +1,86 @@
+package main
+
+import (
+       "strings"
+       "unicode"
+)
+
+func convertCommandToName(command string) string {
+       b := strings.Builder{}
+       upper := true
+       for _, c := range command {
+               if upper {
+                       b.WriteRune(unicode.ToUpper(c))
+                       upper = false
+               } else {
+                       b.WriteRune(unicode.ToLower(c))
+               }
+       }
+       return b.String()
+}
+
+func convertCommandToDir(command string) string {
+       return strings.ToLower(command)
+}
+
+func convertCommandToPackage(command string) string {
+       return strings.ToLower(command)
+}
+
+func isParam(word string) bool {
+       left := strings.Index(word, "<")
+       right := strings.Index(word, ">")
+       return left != -1 && right != -1
+}
+
+func convertWordToName(word string) (prefix, name string) {
+       prefB := strings.Builder{}
+       nameB := strings.Builder{}
+
+       if !isParam(word) {
+               return word, ""
+       }
+
+       const (
+               s_init int = iota
+               s_optional
+               s_param
+       )
+
+       part := true
+       state := s_init
+       for _, c := range word {
+               switch state {
+
+               case s_init:
+                       switch c {
+                       case '[':
+                               state = s_optional
+                       case '<':
+                               state = s_param
+                       default:
+                               prefB.WriteRune(c)
+                       }
+
+               case s_optional:
+                       switch c {
+                       case '<':
+                               state = s_param
+                       }
+
+               case s_param:
+                       if c == '>' || c == ']' {
+                               continue
+                       } else if part {
+                               nameB.WriteRune(unicode.ToUpper(c))
+                               part = false
+                       } else if c == '-' {
+                               part = true
+                       } else {
+                               nameB.WriteRune(c)
+                       }
+
+               }
+       }
+       return prefB.String(), nameB.String()
+}
diff --git a/x/generate/message/convert_test.go b/x/generate/message/convert_test.go
new file mode 100644 (file)
index 0000000..1e22026
--- /dev/null
@@ -0,0 +1,21 @@
+package main
+
+import "testing"
+
+func TestConvertWordToName(t *testing.T) {
+       data := []struct {
+               in, prefix, name string
+       }{
+               {"<to-user>", "", "ToUser"},
+               {"#<channel>", "#", "Channel"},
+               {"#<hosting-channel>", "#", "HostingChannel"},
+               {"[-|<channel>]", "", "Channel"},
+               {"[<message>]", "", "Message"},
+       }
+       for _, d := range data {
+               prefix, name := convertWordToName(d.in)
+               if prefix != d.prefix || name != d.name {
+                       t.Errorf("convertWordToName(%#v) = %#v, %#v but expected %#v, %#v.\n", d.in, prefix, name, d.prefix, d.name)
+               }
+       }
+}
diff --git a/x/generate/message/generateMessage.go b/x/generate/message/generateMessage.go
new file mode 100644 (file)
index 0000000..46500f8
--- /dev/null
@@ -0,0 +1,87 @@
+package main
+
+import (
+       "fmt"
+       "io"
+       "os"
+       "strings"
+       "twitchchat/irc"
+)
+
+func generateMessage(msg irc.Message, w io.Writer) (n int, err error) {
+       write := func(s string) {
+               if err == nil {
+                       var m int
+                       m, err = io.WriteString(w, s)
+                       n += m
+               }
+       }
+
+       // header
+       write(fmt.Sprintf("// generated by %s\n", os.Args[0]))
+       write("\n")
+       write(fmt.Sprintf("package %s\n", convertCommandToPackage(string(msg.Command))))
+       write("\n")
+
+       // imports
+       if msg.Params.Trailing != "" {
+               write("import \"strings\"\n")
+               write("\n")
+       }
+
+       // Potential optimization is to have all properties from the message as strings
+       // in the type and lazy load them
+
+       var typeDef = convertCommandToName(string(msg.Command))
+       // type definition
+       write(fmt.Sprintf("type %s struct {\n", typeDef))
+       write(fmt.Sprintf("\tmessages.Message\n"))
+       write(fmt.Sprintf("}\n"))
+       write("\n")
+
+       // command checking
+
+       write(fmt.Sprintf("func Is%s(m messages.Message) bool { return string(m.Command) == %#v }\n", typeDef, msg.Command))
+       write("\n")
+
+       // from the user value
+       if isParam(msg.Prefix.User) {
+               // there shouldn't be a prefix here
+               _, n := convertWordToName(msg.Prefix.User)
+               write(fmt.Sprintf("func (m %s) %s() string { return m.Prefix.User }\n", typeDef, n))
+               write("\n")
+       }
+
+       // all params
+       for d, p := range msg.Params.Params {
+               p, n := convertWordToName(p)
+               write(fmt.Sprintf("func (m %s) %s() string { return m.Params.Params[%d][%d:] }\n", typeDef, n, d, len(p)))
+               write("\n")
+       }
+
+       // words in trailing
+       if msg.Params.Trailing != "" {
+               for d, p := range strings.Split(msg.Params.Trailing, " ") {
+                       p, n := convertWordToName(p)
+                       write(fmt.Sprintf("func (m %s) %s() string { return strings.Split(m.Params.Trailing, \" \")[%d][%d:] }\n", typeDef, n, d, len(p)))
+                       write("\n")
+               }
+       }
+
+       // tags
+       for _, t := range msg.Tags {
+               // shouldn't have a prefx
+               if isParam(t.Value) {
+                       _, n := convertWordToName(t.Value)
+                       write(fmt.Sprintf("func (m %s) %s() string {\n", typeDef, n))
+                       write(fmt.Sprintf("\tfor _, t := range m.Tags {\n"))
+                       write(fmt.Sprintf("\t\tif t.Key == %#v { return t.Value }\n", t.Key))
+                       write(fmt.Sprintf("\t}\n"))
+                       write(fmt.Sprintf("\treturn \"\"\n"))
+                       write(fmt.Sprintf("}\n"))
+                       write("\n")
+               }
+       }
+
+       return n, err
+}
diff --git a/x/generate/message/main.go b/x/generate/message/main.go
new file mode 100644 (file)
index 0000000..6c76020
--- /dev/null
@@ -0,0 +1,55 @@
+package main
+
+import (
+       "errors"
+       "flag"
+       "fmt"
+       "io/fs"
+       "os"
+       "path"
+       "twitchchat/irc"
+)
+
+var prototype = flag.String("proto", "", "The prototype of the tags + message as a valid IRC message")
+
+func flags() {
+       flag.Usage = func() {
+               fmt.Fprintf(flag.CommandLine.Output(), "%s: generate code from a twitch message prototype\n", os.Args[0])
+               fmt.Fprintln(flag.CommandLine.Output(), "Usage:")
+               flag.PrintDefaults()
+       }
+
+       flag.Parse()
+       if *prototype == "" {
+               flag.Usage()
+               os.Exit(-1)
+       }
+}
+
+func main() {
+       flags()
+
+       msg, err := irc.ParseBytes([]byte(*prototype))
+       if err != nil {
+               fmt.Printf("Invalid prototype message: %s\n", err)
+               os.Exit(-1)
+       }
+
+       dir := convertCommandToDir(string(msg.Command))
+
+       if err := os.Mkdir(dir, fs.ModeDir|0755); err != nil {
+               if !errors.Is(err, fs.ErrExist) {
+                       fmt.Printf("Could now create a directory: %s\n", err)
+                       os.Exit(-1)
+               }
+       }
+
+       file, err := os.Create(path.Join(dir, "message.go"))
+       if err != nil {
+               fmt.Printf("Could not write out: %s\n", err)
+               os.Exit(-1)
+       }
+
+       generateMessage(msg, file)
+       fmt.Printf("Generated %s\n", msg)
+}