diff options
Diffstat (limited to 'xS/irc.go')
-rw-r--r-- | xS/irc.go | 132 |
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 +} |