From e1d00ae799013d174fc3504fc7493436a3fdb263 Mon Sep 17 00:00:00 2001 From: Russ Garrett Date: Wed, 25 Jan 2017 15:39:59 +0000 Subject: [PATCH] Add support for parsing IRCv3 tags in received messages --- irc.go | 31 +++++++++++++++++++++++++++++++ irc_parse_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ irc_struct.go | 1 + 3 files changed, 79 insertions(+) create mode 100644 irc_parse_test.go diff --git a/irc.go b/irc.go index eb9174d..af50c74 100644 --- a/irc.go +++ b/irc.go @@ -87,6 +87,17 @@ func (irc *Connection) readLoop() { } } +// Unescape tag values as defined in the IRCv3.2 message tags spec +// http://ircv3.net/specs/core/message-tags-3.2.html +func unescapeTagValue(value string) string { + value = strings.Replace(value, "\\:", ";", -1) + value = strings.Replace(value, "\\s", " ", -1) + value = strings.Replace(value, "\\\\", "\\", -1) + value = strings.Replace(value, "\\r", "\r", -1) + value = strings.Replace(value, "\\n", "\n", -1) + return value +} + //Parse raw irc messages func parseToEvent(msg string) (*Event, error) { msg = strings.TrimSuffix(msg, "\n") //Remove \r\n @@ -95,6 +106,26 @@ func parseToEvent(msg string) (*Event, error) { if len(msg) < 5 { return nil, errors.New("Malformed msg from server") } + + if msg[0] == '@' { + // IRCv3 Message Tags + if i := strings.Index(msg, " "); i > -1 { + event.Tags = make(map[string]string) + tags := strings.Split(msg[1:i], ";") + for _, data := range tags { + parts := strings.SplitN(data, "=", 2) + if len(parts) == 1 { + event.Tags[parts[0]] = "" + } else { + event.Tags[parts[0]] = unescapeTagValue(parts[1]) + } + } + msg = msg[i+1 : len(msg)] + } else { + return nil, errors.New("Malformed msg from server") + } + } + if msg[0] == ':' { if i := strings.Index(msg, " "); i > -1 { event.Source = msg[1:i] diff --git a/irc_parse_test.go b/irc_parse_test.go new file mode 100644 index 0000000..447a590 --- /dev/null +++ b/irc_parse_test.go @@ -0,0 +1,47 @@ +package irc + +import ( + "fmt" + "testing" +) + +func checkResult(t *testing.T, event *Event) { + if event.Nick != "nick" { + t.Fatal("Parse failed: nick") + } + if event.User != "~user" { + t.Fatal("Parse failed: user") + } + if event.Code != "PRIVMSG" { + t.Fatal("Parse failed: code") + } + if event.Arguments[0] != "#channel" { + t.Fatal("Parse failed: channel") + } + if event.Arguments[1] != "message text" { + t.Fatal("Parse failed: message") + } +} + +func TestParse(t *testing.T) { + event, err := parseToEvent(":nick!~user@host PRIVMSG #channel :message text") + if err != nil { + t.Fatal("Parse PRIVMSG failed") + } + checkResult(t, event) +} + +func TestParseTags(t *testing.T) { + event, err := parseToEvent("@tag;+tag2=raw+:=,escaped\\:\\s\\\\ :nick!~user@host PRIVMSG #channel :message text") + if err != nil { + t.Fatal("Parse PRIVMSG with tags failed") + } + checkResult(t, event) + fmt.Printf("%s", event.Tags) + if _, ok := event.Tags["tag"]; !ok { + t.Fatal("Parsing value-less tag failed") + } + if event.Tags["+tag2"] != "raw+:=,escaped; \\" { + t.Fatal("Parsing tag failed") + } +} diff --git a/irc_struct.go b/irc_struct.go index a188d9d..e509dbb 100644 --- a/irc_struct.go +++ b/irc_struct.go @@ -59,6 +59,7 @@ type Event struct { Source string // User string // Arguments []string + Tags map[string]string Connection *Connection }