From a9f89dbc645aafc68daa9fc8d589f55104b535c7 Mon Sep 17 00:00:00 2001 From: James Lu Date: Wed, 30 Dec 2020 09:21:32 -0800 Subject: Add support for stateless bridging via draft/relaymsg (irc) (#1339) * irc: add support for stateless bridging via draft/relaymsg As discussed at https://github.com/42wim/matterbridge/issues/667#issuecomment-634214165 * irc: handle the draft/relaymsg tag in spoofed messages too * Apply suggestions from code review Co-authored-by: Wim * Run gofmt on irc.go * Document relaymsg in matterbridge.toml.sample Co-authored-by: Wim --- bridge/irc/irc.go | 73 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 57 insertions(+), 16 deletions(-) (limited to 'bridge/irc/irc.go') diff --git a/bridge/irc/irc.go b/bridge/irc/irc.go index 2352fcd7..4ccdee95 100644 --- a/bridge/irc/irc.go +++ b/bridge/irc/irc.go @@ -205,27 +205,58 @@ func (b *Birc) doConnect() { } } +// Sanitize nicks for RELAYMSG: replace IRC characters with special meanings with "-" +func sanitizeNick(nick string) string { + sanitize := func(r rune) rune { + if strings.ContainsRune("!+%@&#$:'\"?*,. ", r) { + return '-' + } + return r + } + return strings.Map(sanitize, nick) +} + func (b *Birc) doSend() { rate := time.Millisecond * time.Duration(b.MessageDelay) throttle := time.NewTicker(rate) for msg := range b.Local { <-throttle.C username := msg.Username - if b.GetBool("Colornicks") && len(username) > 1 { - checksum := crc32.ChecksumIEEE([]byte(msg.Username)) - colorCode := checksum%14 + 2 // quick fix - prevent white or black color codes - username = fmt.Sprintf("\x03%02d%s\x0F", colorCode, msg.Username) - } + // Optional support for the proposed RELAYMSG extension, described at + // https://github.com/jlu5/ircv3-specifications/blob/master/extensions/relaymsg.md + // nolint:nestif + if (b.i.HasCapability("overdrivenetworks.com/relaymsg") || b.i.HasCapability("draft/relaymsg")) && + b.GetBool("UseRelayMsg") { + username = sanitizeNick(username) + text := msg.Text + + // Work around girc chomping leading commas on single word messages? + if strings.HasPrefix(text, ":") && !strings.ContainsRune(text, ' ') { + text = ":" + text + } - switch msg.Event { - case config.EventUserAction: - b.i.Cmd.Action(msg.Channel, username+msg.Text) - case config.EventNoticeIRC: - b.Log.Debugf("Sending notice to channel %s", msg.Channel) - b.i.Cmd.Notice(msg.Channel, username+msg.Text) - default: - b.Log.Debugf("Sending to channel %s", msg.Channel) - b.i.Cmd.Message(msg.Channel, username+msg.Text) + if msg.Event == config.EventUserAction { + b.i.Cmd.SendRawf("RELAYMSG %s %s :\x01ACTION %s\x01", msg.Channel, username, text) //nolint:errcheck + } else { + b.Log.Debugf("Sending RELAYMSG to channel %s: nick=%s", msg.Channel, username) + b.i.Cmd.SendRawf("RELAYMSG %s %s :%s", msg.Channel, username, text) //nolint:errcheck + } + } else { + if b.GetBool("Colornicks") { + checksum := crc32.ChecksumIEEE([]byte(msg.Username)) + colorCode := checksum%14 + 2 // quick fix - prevent white or black color codes + username = fmt.Sprintf("\x03%02d%s\x0F", colorCode, msg.Username) + } + switch msg.Event { + case config.EventUserAction: + b.i.Cmd.Action(msg.Channel, username+msg.Text) + case config.EventNoticeIRC: + b.Log.Debugf("Sending notice to channel %s", msg.Channel) + b.i.Cmd.Notice(msg.Channel, username+msg.Text) + default: + b.Log.Debugf("Sending to channel %s", msg.Channel) + b.i.Cmd.Message(msg.Channel, username+msg.Text) + } } } } @@ -273,8 +304,9 @@ func (b *Birc) getClient() (*girc.Client, error) { TLSConfig: &tls.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), ServerName: server}, //nolint:gosec PingDelay: pingDelay, // skip gIRC internal rate limiting, since we have our own throttling - AllowFlood: true, - Debug: debug, + AllowFlood: true, + Debug: debug, + SupportedCaps: map[string][]string{"overdrivenetworks.com/relaymsg": nil, "draft/relaymsg": nil}, }) return i, nil } @@ -311,6 +343,15 @@ func (b *Birc) skipPrivMsg(event girc.Event) bool { if event.Source.Name == b.Nick { return true } + // don't forward messages we sent via RELAYMSG + if relayedNick, ok := event.Tags.Get("draft/relaymsg"); ok && relayedNick == b.Nick { + return true + } + // This is the old name of the cap sent in spoofed messages; I've kept this in + // for compatibility reasons + if relayedNick, ok := event.Tags.Get("relaymsg"); ok && relayedNick == b.Nick { + return true + } return false } -- cgit v1.2.3