all repos — paprika @ 4fb075344b513bb312cd5d28c9b869e113fc03a8

go rewrite of taigabot

Removed convoluted flags.

It's easier to special case and stick to one-size-fits-all errors.

Errors:

  * NoReply  - Same as before.
  * IsRaw    - The plugin sent a line or lines of raw IRC commands.
  * IsNotice - The plugin is sending a NOTICE, not a PRIVMSG.
Anthony DeDominic adedomin@gmail.com
Mon, 22 Nov 2021 09:19:44 -0500
commit

4fb075344b513bb312cd5d28c9b869e113fc03a8

parent

25de5b6c6af4b418eb4cb6ef74eb26540f50ddd6

4 files changed, 58 insertions(+), 78 deletions(-)

jump to
M main.gomain.go

@@ -14,35 +14,32 @@ )

func handleChatMessage(c *irc.Client, m *irc.Message) { response, err := plugins.ProcessTrigger(m) - split := strings.Split(response, "\n") + + if errors.Is(err, plugins.NoReply) { + return + } - if plugins.IsReplyT(err) { - r := err.(*plugins.ReplyT) - r.ApplyFlags(m) + cmd := "PRIVMSG" + if errors.Is(err, plugins.IsNotice) { + err = nil + cmd = "NOTICE" + } + msg := irc.Message {Command: cmd} + target := m.Params[0] + split := strings.Split(response, "\n") + if errors.Is(err, plugins.IsRaw) { for _, line := range split { - c.WriteMessage(&irc.Message{ - Command: m.Command, - Params: []string { - m.Params[0], - line, - }, - }) + c.Write(line) } } else if err != nil { - if !errors.Is(err, plugins.NoReply) { - log.Printf("error: %v", err) - } + log.Printf("error: %v", err) } else { for _, line := range split { - c.WriteMessage(&irc.Message{ - Command: "PRIVMSG", - Params: []string{ - m.Params[0], - line, - }, - }) + msg.Params = []string{target, line} + c.WriteMessage(&msg) } + return } }
M plugins/ctcp.goplugins/ctcp.go

@@ -19,9 +19,10 @@

func (Ctcp) Execute(m *irc.Message) (string, error) { msg := m.Trailing() if msg == "\x01VERSION\x01" { - return "\x01VERSION git.icyphox.sh/paprika\x01", NewReplyT(Notice | DirectMessage) + return "\x01VERSION git.icyphox.sh/paprika\x01", IsNotice } else if strings.HasPrefix(msg, "\x01PING") { - return msg, NewReplyT(Notice | DirectMessage) + return msg, IsNotice } - return "\x01INVAL\x01", NewReplyT(Notice | DirectMessage) + + panic("Unreachable!") }
M plugins/listenbrainz.goplugins/listenbrainz.go

@@ -51,5 +51,5 @@

return np, nil } - return "", nil + panic("Unreachable!") }
M plugins/plugins.goplugins/plugins.go

@@ -2,7 +2,6 @@ package plugins

import ( "errors" - "fmt" "strings" "gopkg.in/irc.v3"

@@ -27,66 +26,49 @@ // This is useful for broad prefix matching or future regexp

// functions, other special errors may need defining // to determin priority or to "concatenate" output. var NoReply = errors.New("No Reply") - -type Flag int - -type ReplyT struct { - Flags Flag -} - -const ( - Notice Flag = 1 << iota - DirectMessage -) - -var AllFlags = [...]Flag{Notice, DirectMessage} - -func (f Flag) String() string { - switch f { - case Notice: - return "Notice" - case DirectMessage: - return "DM" - default: - return fmt.Sprintf("Invalid:%d", f) - } -} - -func NewReplyT(flags Flag) *ReplyT { - return &ReplyT{Flags: flags} -} - -func (r *ReplyT) ApplyFlags(m *irc.Message) { - for _, flag := range AllFlags { - if r.Flags & flag != 0 { - switch flag { - case Notice: - m.Command = "NOTICE" - case DirectMessage: - m.Params[0] = m.Name - } - } - } -} +// This error indicates we are sending a NOTICE instead of a PRIVMSG +var IsNotice = errors.New("Is Notice") +// This means the string(s) we are returning are raw IRC commands +// that need to be written verbatim. +var IsRaw = errors.New("Is Raw") -func (r *ReplyT) Error() string { - var reply strings.Builder - for _, flag := range AllFlags { - if r.Flags & flag != 0 { - reply.WriteString(flag.String()) - } +// Due to racey nature of the handler in main.go being invoked as a goroutine, +// it's hard to have race free way of building correct state of the IRC world. +// This is a temporary (lol) hack to check if the PRIVMSG target looks like a +// IRC nickname. We assume that any IRC nickname target would be us, unless +// the IRCd we are on is neurotic +// +// Normally one would use the ISUPPORT details to learn what prefixes are used +// on the network's valid channel names. +// +// E.G. ISUPPORT ... CHANTYPES=# ... where # would be the only valid channel name +// allowed on the IRCd. +func unlikelyDirectMessage(target string) bool { + if len(target) < 1 { + panic("Conformity Error, IRCd sent us a PRIVMSG with an empty target and message.") } - return reply.String() -} -func IsReplyT(e error) bool { - r := &ReplyT{} - return errors.As(e, &r) + sym := target[0] // we only care about the byte (ASCII) + // Is one of: !"#$%&'()*+,_./ + // or one of: ;<=>?@ + // If your IRCd uses symbols outside of this range, + // god help us. + // + // ALSO NOTE: RFC1459 defines a "nick" as + // <nick> ::= <letter> { <letter> | <number> | <special> } + // But I have seen some networks that allow special/number as the first letter. + return sym > 32 /* SPACE */ && sym < 48 /* 0 */ || + sym > 58 /* : */ && sym < 65 /* A */ || + sym == 126 /* ~ */ } // Checks for triggers in a message and executes its // corresponding plugin, returning the response/error. func ProcessTrigger(m *irc.Message) (string, error) { + if !unlikelyDirectMessage(m.Params[0]) { + m.Params[0] = m.Name + } + for trigger, plugin := range Plugins { if strings.HasPrefix(m.Trailing(), trigger) { response, err := plugin.Execute(m)