summaryrefslogtreecommitdiff
path: root/xS/irc.go
diff options
context:
space:
mode:
authorPřemysl Eric Janouch <p@janouch.name>2024-04-02 16:44:01 +0200
committerPřemysl Eric Janouch <p@janouch.name>2024-04-03 15:56:33 +0200
commit53ba996ec9c5c8fc64f66934d8c98509bd7ed06d (patch)
treefe396b25631e5a325c0e7d470dbb3e7e045d66b4 /xS/irc.go
parentd450c6cc5fe4caa2f9a1db3f0ec76c84b6a110af (diff)
downloadxK-53ba996ec9c5c8fc64f66934d8c98509bd7ed06d.tar.gz
xK-53ba996ec9c5c8fc64f66934d8c98509bd7ed06d.tar.xz
xK-53ba996ec9c5c8fc64f66934d8c98509bd7ed06d.zip
Add a simple IRC notifier utility
Diffstat (limited to 'xS/irc.go')
-rw-r--r--xS/irc.go132
1 files changed, 132 insertions, 0 deletions
diff --git a/xS/irc.go b/xS/irc.go
new file mode 100644
index 0000000..71bf961
--- /dev/null
+++ b/xS/irc.go
@@ -0,0 +1,132 @@
+package main
+
+import (
+ "path/filepath"
+ "regexp"
+ "strings"
+)
+
+func ircToLower(c byte) byte {
+ switch c {
+ case '[':
+ return '{'
+ case ']':
+ return '}'
+ case '\\':
+ return '|'
+ case '~':
+ return '^'
+ }
+ if c >= 'A' && c <= 'Z' {
+ return c + ('a' - 'A')
+ }
+ return c
+}
+
+func ircToUpper(c byte) byte {
+ switch c {
+ case '{':
+ return '['
+ case '}':
+ return ']'
+ case '|':
+ return '\\'
+ case '^':
+ return '~'
+ }
+ if c >= 'a' && c <= 'z' {
+ return c - ('a' - 'A')
+ }
+ return c
+}
+
+// Convert identifier to a canonical form for case-insensitive comparisons.
+// ircToUpper is used so that statically initialized maps can be in uppercase.
+func ircToCanon(ident string) string {
+ var canon []byte
+ for _, c := range []byte(ident) {
+ canon = append(canon, ircToUpper(c))
+ }
+ return string(canon)
+}
+
+func ircEqual(s1, s2 string) bool {
+ return ircToCanon(s1) == ircToCanon(s2)
+}
+
+func ircFnmatch(pattern string, s string) bool {
+ pattern, s = ircToCanon(pattern), ircToCanon(s)
+ // FIXME: This should not support [] ranges and handle '/' specially.
+ // We could translate the pattern to a regular expression.
+ matched, _ := filepath.Match(pattern, s)
+ return matched
+}
+
+var reMsg = regexp.MustCompile(
+ `^(?:@([^ ]*) +)?(?::([^! ]*)(?:!([^@]*)@([^ ]*))? +)?([^ ]+)(.*)?$`)
+var reArgs = regexp.MustCompile(`:.*| [^: ][^ ]*`)
+
+type message struct {
+ tags map[string]string // IRC 3.2 message tags
+ nick string // optional nickname
+ user string // optional username
+ host string // optional hostname or IP address
+ command string // command name
+ params []string // arguments
+}
+
+func ircUnescapeMessageTag(value string) string {
+ var buf []byte
+ escape := false
+ for i := 0; i < len(value); i++ {
+ if escape {
+ switch value[i] {
+ case ':':
+ buf = append(buf, ';')
+ case 's':
+ buf = append(buf, ' ')
+ case 'r':
+ buf = append(buf, '\r')
+ case 'n':
+ buf = append(buf, '\n')
+ default:
+ buf = append(buf, value[i])
+ }
+ escape = false
+ } else if value[i] == '\\' {
+ escape = true
+ } else {
+ buf = append(buf, value[i])
+ }
+ }
+ return string(buf)
+}
+
+func ircParseMessageTags(tags string, out map[string]string) {
+ for _, tag := range strings.Split(tags, ";") {
+ if tag == "" {
+ // Ignore empty.
+ } else if equal := strings.IndexByte(tag, '='); equal < 0 {
+ out[tag] = ""
+ } else {
+ out[tag[:equal]] = ircUnescapeMessageTag(tag[equal+1:])
+ }
+ }
+}
+
+func ircParseMessage(line string) *message {
+ m := reMsg.FindStringSubmatch(line)
+ if m == nil {
+ return nil
+ }
+
+ msg := message{nil, m[2], m[3], m[4], m[5], nil}
+ if m[1] != "" {
+ msg.tags = make(map[string]string)
+ ircParseMessageTags(m[1], msg.tags)
+ }
+ for _, x := range reArgs.FindAllString(m[6], -1) {
+ msg.params = append(msg.params, x[1:])
+ }
+ return &msg
+}