all repos — paprika @ eb2f22649405dd676a6c6c3cf1eedfa325fa8bef

go rewrite of taigabot

plugins/plugins.go (view raw)

 1package plugins
 2
 3import (
 4	"errors"
 5	"strings"
 6
 7	"gopkg.in/irc.v3"
 8)
 9
10type Plugin interface {
11	Triggers() []string
12	Execute(m *irc.Message) (string, error)
13}
14
15var Plugins = make(map[string]Plugin)
16
17func Register(p Plugin) {
18	for _, t := range p.Triggers() {
19		Plugins[t] = p
20	}
21}
22
23// This Error is used to signal a NAK so other plugins
24// can attempt to parse the result
25// This is useful for broad prefix matching or future regexp
26// functions, other special errors may need defining
27// to determin priority or to "concatenate" output.
28var NoReply = errors.New("No Reply")
29// This error indicates we are sending a NOTICE instead of a PRIVMSG
30var IsNotice = errors.New("Is Notice")
31// This means the string(s) we are returning are raw IRC commands
32// that need to be written verbatim.
33var IsRaw = errors.New("Is Raw")
34
35// Due to racey nature of the handler in main.go being invoked as a goroutine,
36// it's hard to have race free way of building correct state of the IRC world.
37// This is a temporary (lol) hack to check if the PRIVMSG target looks like a
38// IRC nickname. We assume that any IRC nickname target would be us, unless
39// the IRCd we are on is neurotic
40//
41// Normally one would use the ISUPPORT details to learn what prefixes are used
42// on the network's valid channel names.
43//
44// E.G. ISUPPORT ... CHANTYPES=# ... where # would be the only valid channel name
45// allowed on the IRCd.
46func unlikelyDirectMessage(target string) bool {
47	if len(target) < 1 {
48		panic("Conformity Error, IRCd sent us a PRIVMSG with an empty target and message.")
49	}
50
51	sym := target[0] // we only care about the byte (ASCII)
52	return likelyInvalidNickChr(sym)
53}
54
55func likelyInvalidNickChr(sym byte) bool {
56	// Is one of: !"#$%&'()*+,_./
57	// or one of: ;<=>?@
58	// If your IRCd uses symbols outside of this range,
59	// god help us.
60	//
61	// ALSO NOTE: RFC1459 defines a "nick" as
62	// <nick> ::= <letter> { <letter> | <number> | <special> }
63	// But I have seen some networks that allow special/number as the first letter.
64	return sym > 32 /* SPACE */ && sym < 48 /* 0 */ ||
65	       sym > 58 /* : */ && sym < 65 /* A */ ||
66	       sym == 126 /* ~ */
67}
68
69// Checks for triggers in a message and executes its
70// corresponding plugin, returning the response/error.
71func ProcessTrigger(m *irc.Message) (string, error) {
72	if !unlikelyDirectMessage(m.Params[0]) {
73		m.Params[0] = m.Name
74	}
75
76	for trigger, plugin := range Plugins {
77		if strings.HasPrefix(m.Trailing(), trigger) {
78			response, err := plugin.Execute(m)
79			if !errors.Is(err, NoReply) {
80				return response, err
81			}
82		}
83	}
84	return "", NoReply // No plugin matched, so we need to Ignore.
85}