diff --git a/bridge/irc/irc.go b/bridge/irc/irc.go index dea2a3db..86ed6974 100644 --- a/bridge/irc/irc.go +++ b/bridge/irc/irc.go @@ -3,13 +3,13 @@ package birc import ( "crypto/tls" "fmt" + "github.com/42wim/go-ircevent" "github.com/42wim/matterbridge/bridge/config" log "github.com/Sirupsen/logrus" "github.com/paulrosania/go-charset/charset" _ "github.com/paulrosania/go-charset/data" "github.com/saintfish/chardet" ircm "github.com/sorcix/irc" - "github.com/thoj/go-ircevent" "io" "io/ioutil" "regexp" diff --git a/vendor/github.com/thoj/go-ircevent/LICENSE b/vendor/github.com/42wim/go-ircevent/LICENSE similarity index 100% rename from vendor/github.com/thoj/go-ircevent/LICENSE rename to vendor/github.com/42wim/go-ircevent/LICENSE diff --git a/vendor/github.com/thoj/go-ircevent/irc.go b/vendor/github.com/42wim/go-ircevent/irc.go similarity index 84% rename from vendor/github.com/thoj/go-ircevent/irc.go rename to vendor/github.com/42wim/go-ircevent/irc.go index eb9174d3..bcfe9ac2 100644 --- a/vendor/github.com/thoj/go-ircevent/irc.go +++ b/vendor/github.com/42wim/go-ircevent/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] @@ -430,26 +461,84 @@ func (irc *Connection) Connect(server string) error { irc.pwrite <- fmt.Sprintf("PASS %s\r\n", irc.Password) } - resChan := make(chan *SASLResult) + err = irc.negotiateCaps() + if err != nil { + return err + } + + irc.pwrite <- fmt.Sprintf("NICK %s\r\n", irc.nick) + irc.pwrite <- fmt.Sprintf("USER %s 0.0.0.0 0.0.0.0 :%s\r\n", irc.user, irc.user) + return nil +} + +// Negotiate IRCv3 capabilities +func (irc *Connection) negotiateCaps() error { + saslResChan := make(chan *SASLResult) + if irc.UseSASL { + irc.RequestCaps = append(irc.RequestCaps, "sasl") + irc.setupSASLCallbacks(saslResChan) + } + + if len(irc.RequestCaps) == 0 { + return nil + } + + cap_chan := make(chan bool, len(irc.RequestCaps)) + irc.AddCallback("CAP", func(e *Event) { + if len(e.Arguments) != 3 { + return + } + command := e.Arguments[1] + + if command == "LS" { + missing_caps := len(irc.RequestCaps) + for _, cap_name := range strings.Split(e.Arguments[2], " ") { + for _, req_cap := range irc.RequestCaps { + if cap_name == req_cap { + irc.pwrite <- fmt.Sprintf("CAP REQ :%s\r\n", cap_name) + missing_caps-- + } + } + } + + for i := 0; i < missing_caps; i++ { + cap_chan <- true + } + } else if command == "ACK" || command == "NAK" { + for _, cap_name := range strings.Split(strings.TrimSpace(e.Arguments[2]), " ") { + if cap_name == "" { + continue + } + + if command == "ACK" { + irc.AcknowledgedCaps = append(irc.AcknowledgedCaps, cap_name) + } + cap_chan <- true + } + } + }) + + irc.pwrite <- "CAP LS\r\n" + if irc.UseSASL { - irc.setupSASLCallbacks(resChan) - irc.pwrite <- fmt.Sprintf("CAP LS\r\n") - // request SASL - irc.pwrite <- fmt.Sprintf("CAP REQ :sasl\r\n") - // if sasl request doesn't complete in 15 seconds, close chan and timeout select { - case res := <-resChan: + case res := <-saslResChan: if res.Failed { - close(resChan) + close(saslResChan) return res.Err } case <-time.After(time.Second * 15): - close(resChan) + close(saslResChan) return errors.New("SASL setup timed out. This shouldn't happen.") } } - irc.pwrite <- fmt.Sprintf("NICK %s\r\n", irc.nick) - irc.pwrite <- fmt.Sprintf("USER %s 0.0.0.0 0.0.0.0 :%s\r\n", irc.user, irc.user) + + // Wait for all capabilities to be ACKed or NAKed before ending negotiation + for i := 0; i < len(irc.RequestCaps); i++ { + <-cap_chan + } + irc.pwrite <- fmt.Sprintf("CAP END\r\n") + return nil } diff --git a/vendor/github.com/thoj/go-ircevent/irc_callback.go b/vendor/github.com/42wim/go-ircevent/irc_callback.go similarity index 100% rename from vendor/github.com/thoj/go-ircevent/irc_callback.go rename to vendor/github.com/42wim/go-ircevent/irc_callback.go diff --git a/vendor/github.com/thoj/go-ircevent/irc_sasl.go b/vendor/github.com/42wim/go-ircevent/irc_sasl.go similarity index 98% rename from vendor/github.com/thoj/go-ircevent/irc_sasl.go rename to vendor/github.com/42wim/go-ircevent/irc_sasl.go index e5ff9e38..fd9df7ba 100644 --- a/vendor/github.com/thoj/go-ircevent/irc_sasl.go +++ b/vendor/github.com/42wim/go-ircevent/irc_sasl.go @@ -43,7 +43,6 @@ func (irc *Connection) setupSASLCallbacks(result chan<- *SASLResult) { result <- &SASLResult{true, errors.New(e.Arguments[1])} }) irc.AddCallback("903", func(e *Event) { - irc.SendRaw("CAP END") result <- &SASLResult{false, nil} }) irc.AddCallback("904", func(e *Event) { diff --git a/vendor/github.com/thoj/go-ircevent/irc_struct.go b/vendor/github.com/42wim/go-ircevent/irc_struct.go similarity index 73% rename from vendor/github.com/thoj/go-ircevent/irc_struct.go rename to vendor/github.com/42wim/go-ircevent/irc_struct.go index a188d9d9..c064cb80 100644 --- a/vendor/github.com/thoj/go-ircevent/irc_struct.go +++ b/vendor/github.com/42wim/go-ircevent/irc_struct.go @@ -15,20 +15,22 @@ import ( type Connection struct { sync.Mutex sync.WaitGroup - Debug bool - Error chan error - Password string - UseTLS bool - UseSASL bool - SASLLogin string - SASLPassword string - SASLMech string - TLSConfig *tls.Config - Version string - Timeout time.Duration - PingFreq time.Duration - KeepAlive time.Duration - Server string + Debug bool + Error chan error + Password string + UseTLS bool + UseSASL bool + RequestCaps []string + AcknowledgedCaps []string + SASLLogin string + SASLPassword string + SASLMech string + TLSConfig *tls.Config + Version string + Timeout time.Duration + PingFreq time.Duration + KeepAlive time.Duration + Server string socket net.Conn pwrite chan string @@ -59,6 +61,7 @@ type Event struct { Source string // User string // Arguments []string + Tags map[string]string Connection *Connection } diff --git a/vendor/github.com/thoj/go-ircevent/irc_test_fuzz.go b/vendor/github.com/42wim/go-ircevent/irc_test_fuzz.go similarity index 100% rename from vendor/github.com/thoj/go-ircevent/irc_test_fuzz.go rename to vendor/github.com/42wim/go-ircevent/irc_test_fuzz.go diff --git a/vendor/github.com/thoj/go-ircevent/examples/simple/simple.go b/vendor/github.com/thoj/go-ircevent/examples/simple/simple.go deleted file mode 100644 index f2c27c60..00000000 --- a/vendor/github.com/thoj/go-ircevent/examples/simple/simple.go +++ /dev/null @@ -1,27 +0,0 @@ -package main - -import ( - "github.com/thoj/go-ircevent" - "crypto/tls" - "fmt" -) - -const channel = "#go-eventirc-test"; -const serverssl = "irc.freenode.net:7000" - -func main() { - ircnick1 := "blatiblat" - irccon := irc.IRC(ircnick1, "IRCTestSSL") - irccon.VerboseCallbackHandler = true - irccon.Debug = true - irccon.UseTLS = true - irccon.TLSConfig = &tls.Config{InsecureSkipVerify: true} - irccon.AddCallback("001", func(e *irc.Event) { irccon.Join(channel) }) - irccon.AddCallback("366", func(e *irc.Event) { }) - err := irccon.Connect(serverssl) - if err != nil { - fmt.Printf("Err %s", err ) - return - } - irccon.Loop() -} diff --git a/vendor/manifest b/vendor/manifest index 3c94d8f7..abd7c121 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -1,6 +1,14 @@ { "version": 0, "dependencies": [ + { + "importpath": "github.com/42wim/go-ircevent", + "repository": "https://github.com/42wim/go-ircevent", + "vcs": "git", + "revision": "d3aec637ae2f2a4f9ff95df55091894d80fa3112", + "branch": "ircv3", + "notests": true + }, { "importpath": "github.com/BurntSushi/toml", "repository": "https://github.com/BurntSushi/toml", @@ -499,14 +507,6 @@ "branch": "master", "notests": true }, - { - "importpath": "github.com/thoj/go-ircevent", - "repository": "https://github.com/thoj/go-ircevent", - "vcs": "git", - "revision": "1b0acb5f2f1b615cfbd4b9f91abb14cb39a18769", - "branch": "master", - "notests": true - }, { "importpath": "github.com/tylerb/graceful", "repository": "https://github.com/tylerb/graceful",