From a8d99c119eead94a2e8beeb022bd6523a2b76a8c Mon Sep 17 00:00:00 2001 From: Kufat Date: Sat, 6 Aug 2022 17:39:30 -0400 Subject: [PATCH 01/12] Initial commit with enhanced IRC join/part info --- bridge/irc/handlers.go | 73 +++++++++++++++++++++++++++++++++++++----- bridge/irc/irc.go | 38 ++++++++++++++++++++++ 2 files changed, 103 insertions(+), 8 deletions(-) diff --git a/bridge/irc/handlers.go b/bridge/irc/handlers.go index 987df2c5..613a5d91 100644 --- a/bridge/irc/handlers.go +++ b/bridge/irc/handlers.go @@ -80,33 +80,66 @@ func (b *Birc) handleInvite(client *girc.Client, event girc.Event) { } } +func isKill(quitmsg string) bool { + return strings.HasPrefix(quitmsg, "Killed") || strings.HasPrefix(quitmsg, "Local kill") || strings.HasPrefix(quitmsg[1:], "-lined") +} + func (b *Birc) handleJoinPart(client *girc.Client, event girc.Event) { if len(event.Params) == 0 { b.Log.Debugf("handleJoinPart: empty Params? %#v", event) return } channel := strings.ToLower(event.Params[0]) - if event.Command == "KICK" && event.Params[1] == b.Nick { - b.Log.Infof("Got kicked from %s by %s", channel, event.Source.Name) - time.Sleep(time.Duration(b.GetInt("RejoinDelay")) * time.Second) - b.Remote <- config.Message{Username: "system", Text: "rejoin", Channel: channel, Account: b.Account, Event: config.EventRejoinChannels} + if event.Command == "KICK" { + if event.Params[1] == b.Nick { + b.Log.Infof("Got kicked from %s by %s", channel, event.Source.Name) + time.Sleep(time.Duration(b.GetInt("RejoinDelay")) * time.Second) + b.Remote <- config.Message{Username: "system", Text: "rejoin", Channel: channel, Account: b.Account, Event: config.EventRejoinChannels} + } else { + msg := config.Message{Username: "system", + Text: event.Source.Name + " kicked " + event.Params[1] + " with message: " + event.Last(), + Channel: channel, + Account: b.Account, + Event: config.EventJoinLeave} + b.Log.Debugf("<= Message is %#v", msg) + b.Remote <- msg + } return } if event.Command == "QUIT" { if event.Source.Name == b.Nick && strings.Contains(event.Last(), "Ping timeout") { b.Log.Infof("%s reconnecting ..", b.Account) - b.Remote <- config.Message{Username: "system", Text: "reconnect", Channel: channel, Account: b.Account, Event: config.EventFailure} + b.Remote <- config.Message{Username: "system", Text: "reconnect", Channel: b.getPseudoChannel(), Account: b.Account, Event: config.EventFailure} + return + } else if b.GetBool("nosendjoinpart") { + return + } else if b.isUserActive(event.Source.Name) || isKill(event.Last()) { + verbosequit := "" + quitmsg := "" + if len(event.Params) >= 1 { + quitmsg = " with message: " + event.Last() + } + if b.GetBool("verbosejoinpart") { + verbosequit = " (" + event.Source.Ident + "@" + event.Source.Host + ")" + } + msg := config.Message{Username: "system", Text: event.Source.Name + verbosequit + " quit" + quitmsg, Channel: b.getPseudoChannel(), Account: b.Account, Event: config.EventJoinLeave} + b.Log.Debugf("<= Message is %#v", msg) + b.Remote <- msg return } } if event.Source.Name != b.Nick { - if b.GetBool("nosendjoinpart") { + if b.GetBool("nosendjoinpart") || !b.isUserActive(event.Source.Name) { return } - msg := config.Message{Username: "system", Text: event.Source.Name + " " + strings.ToLower(event.Command) + "s", Channel: channel, Account: b.Account, Event: config.EventJoinLeave} + partmsg := "" + if event.Command == "PART" && len(event.Params) >= 2 { + partmsg = " with message: " + event.Last() + } + msg := config.Message{Username: "system", Text: event.Source.Name + " " + strings.ToLower(event.Command) + "s" + partmsg, Channel: channel, Account: b.Account, Event: config.EventJoinLeave} if b.GetBool("verbosejoinpart") { b.Log.Debugf("<= Sending verbose JOIN_LEAVE event from %s to gateway", b.Account) - msg = config.Message{Username: "system", Text: event.Source.Name + " (" + event.Source.Ident + "@" + event.Source.Host + ") " + strings.ToLower(event.Command) + "s", Channel: channel, Account: b.Account, Event: config.EventJoinLeave} + msg = config.Message{Username: "system", Text: event.Source.Name + " (" + event.Source.Ident + "@" + event.Source.Host + ") " + strings.ToLower(event.Command) + "s" + partmsg, Channel: channel, Account: b.Account, Event: config.EventJoinLeave} } else { b.Log.Debugf("<= Sending JOIN_LEAVE event from %s to gateway", b.Account) } @@ -130,9 +163,29 @@ func (b *Birc) handleNewConnection(client *girc.Client, event girc.Event) { i.Handlers.AddBg("PART", b.handleJoinPart) i.Handlers.AddBg("QUIT", b.handleJoinPart) i.Handlers.AddBg("KICK", b.handleJoinPart) + i.Handlers.AddBg("NICK", b.handleNick) i.Handlers.Add("INVITE", b.handleInvite) } +func (b *Birc) handleNick(client *girc.Client, event girc.Event) { + if len(event.Params) != 1 { + b.Log.Debugf("handleJoinPart: malformed nick change? %#v", event) + return + } else if b.isUserActive(event.Source.Name) { + msg := config.Message{Username: "system", + Text: event.Source.Name + " changed nick to " + event.Params[0], + Channel: b.getPseudoChannel(), + Account: b.Account, + Event: config.EventJoinLeave} + b.Log.Debugf("<= Message is %#v", msg) + b.Remote <- msg + if b.ActivityTimeout != 0 { + // This doesn't count as new activity, but it does preserve the value + b.activeUsers[event.Params[0]] = b.activeUsers[event.Source.Name] + } + } +} + func (b *Birc) handleNickServ() { if !b.GetBool("UseSASL") && b.GetString("NickServNick") != "" && b.GetString("NickServPassword") != "" { b.Log.Debugf("Sending identify to nickserv %s", b.GetString("NickServNick")) @@ -238,6 +291,10 @@ func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) { } b.Log.Debugf("<= Sending message from %s on %s to gateway", event.Params[0], b.Account) + if b.ActivityTimeout > 0 { + b.Log.Debugf("<= Updating last-active time for user %s", event.Source.Name) + b.activeUsers[event.Source.Name] = time.Now().Unix() + } b.Remote <- rmsg } diff --git a/bridge/irc/irc.go b/bridge/irc/irc.go index 7202df5e..c7ada599 100644 --- a/bridge/irc/irc.go +++ b/bridge/irc/irc.go @@ -27,10 +27,12 @@ type Birc struct { i *girc.Client Nick string names map[string][]string + activeUsers map[string]int64 connected chan error Local chan config.Message // local queue for flood control FirstConnection, authDone bool MessageDelay, MessageQueue, MessageLength int + ActivityTimeout int64 channels map[string]bool *bridge.Config @@ -41,6 +43,7 @@ func New(cfg *bridge.Config) bridge.Bridger { b.Config = cfg b.Nick = b.GetString("Nick") b.names = make(map[string][]string) + b.activeUsers = make(map[string]int64) b.connected = make(chan error) b.channels = make(map[string]bool) @@ -59,6 +62,15 @@ func New(cfg *bridge.Config) bridge.Bridger { } else { b.MessageLength = b.GetInt("MessageLength") } + if b.GetBool("ShowActiveUserEvents") { + if b.GetInt("ActivityTimeout") == 0 { + b.ActivityTimeout = 1800 // 30 minutes + } else { + b.ActivityTimeout = int64(b.GetInt("ActivityTimeout")) + } + } else { + b.ActivityTimeout = 0 // Disable + } b.FirstConnection = true return b } @@ -413,3 +425,29 @@ func (b *Birc) getTLSConfig() (*tls.Config, error) { return tlsConfig, nil } + +func (b *Birc) isUserActive(nick string) bool { + b.Log.Debugf("checking activity for %s", nick) + if b.ActivityTimeout == 0 { + return true + } else if activeTime, ok := b.activeUsers[nick]; ok { + now := time.Now().Unix() + b.Log.Debugf("last activity for %s was %d, currently %d", nick, activeTime, now) + if now < activeTime { + b.Log.Errorf("User %s has active time in the future: %d", nick, activeTime) + return true // err on the side of caution + } + return (now - activeTime) < b.ActivityTimeout + } + return false +} + +func (b *Birc) getPseudoChannel() string { + for channelname, active := range b.channels { + if active { + return channelname + } + } + b.Log.Warningf("Bot not active in any channels!") + return "" +} From 5a6d2abc2359085b8f0f40f1a815195c42a69558 Mon Sep 17 00:00:00 2001 From: Kufat Date: Sat, 6 Aug 2022 18:01:28 -0400 Subject: [PATCH 02/12] Add documentation and periodic cleanup. --- bridge/irc/handlers.go | 1 + bridge/irc/irc.go | 19 +++++++++++++++++++ matterbridge.toml.sample | 8 ++++++++ 3 files changed, 28 insertions(+) diff --git a/bridge/irc/handlers.go b/bridge/irc/handlers.go index 613a5d91..886e20df 100644 --- a/bridge/irc/handlers.go +++ b/bridge/irc/handlers.go @@ -296,6 +296,7 @@ func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) { b.activeUsers[event.Source.Name] = time.Now().Unix() } b.Remote <- rmsg + b.cleanActiveMap() } func (b *Birc) handleRunCommands() { diff --git a/bridge/irc/irc.go b/bridge/irc/irc.go index c7ada599..886ad6a0 100644 --- a/bridge/irc/irc.go +++ b/bridge/irc/irc.go @@ -28,6 +28,7 @@ type Birc struct { Nick string names map[string][]string activeUsers map[string]int64 + activeUsersLastCleaned int64 connected chan error Local chan config.Message // local queue for flood control FirstConnection, authDone bool @@ -68,6 +69,7 @@ func New(cfg *bridge.Config) bridge.Bridger { } else { b.ActivityTimeout = int64(b.GetInt("ActivityTimeout")) } + b.activeUsersLastCleaned = time.Now().Unix() } else { b.ActivityTimeout = 0 // Disable } @@ -89,6 +91,10 @@ func (b *Birc) Connect() error { return errors.New("you can't enable SASL and TLSClientCertificate at the same time") } + if b.GetBool("NoSendJoinPart") && b.GetBool("ShowActiveUserEvents") { + return errors.New("you must disable NoSendJoinPart to use ShowActiveUserEvents") + } + b.Local = make(chan config.Message, b.MessageQueue+10) b.Log.Infof("Connecting %s", b.GetString("Server")) @@ -451,3 +457,16 @@ func (b *Birc) getPseudoChannel() string { b.Log.Warningf("Bot not active in any channels!") return "" } + +func (b *Birc) cleanActiveMap() { + now := time.Now().Unix() + if b.ActivityTimeout == 0 || (b.activeUsersLastCleaned-now < b.ActivityTimeout) { + return + } + for nick, activeTime := range b.activeUsers { + if now-activeTime > b.ActivityTimeout { + b.Log.Debugf("last activity for %s was %d, currently %d. Deleting.", nick, activeTime, now) + delete(b.activeUsers, nick) + } + } +} diff --git a/matterbridge.toml.sample b/matterbridge.toml.sample index 41ab44bf..0d7e78a1 100644 --- a/matterbridge.toml.sample +++ b/matterbridge.toml.sample @@ -191,6 +191,14 @@ RemoteNickFormat="[{PROTOCOL}] <{NICK}> " #OPTIONAL (default false) ShowJoinPart=false +#Only show join/part/quit information for users who have been active recently. +#OPTIONAL (default false) +ShowActiveUserEvents=false + +#A user is considered active for ShowActiveUserEvents if they've spoken within this number of seconds. +#OPTIONAL (default 1800, which is 30 minutes) +ActivityTimeout=1800 + #Enable to show verbose users joins/parts (ident@host) from other bridges #Currently works for messages from the following bridges: irc #OPTIONAL (default false) From 21577ddf746ce3b0901d5f93cc7e81ea48e409b2 Mon Sep 17 00:00:00 2001 From: Kufat Date: Sat, 6 Aug 2022 18:39:09 -0400 Subject: [PATCH 03/12] Add thread safety --- bridge/irc/handlers.go | 10 +++++----- bridge/irc/irc.go | 34 ++++++++++++++++++++++++---------- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/bridge/irc/handlers.go b/bridge/irc/handlers.go index 886e20df..e3900f3a 100644 --- a/bridge/irc/handlers.go +++ b/bridge/irc/handlers.go @@ -113,7 +113,7 @@ func (b *Birc) handleJoinPart(client *girc.Client, event girc.Event) { return } else if b.GetBool("nosendjoinpart") { return - } else if b.isUserActive(event.Source.Name) || isKill(event.Last()) { + } else if isActive, _ := b.isUserActive(event.Source.Name); isActive || isKill(event.Last()) { verbosequit := "" quitmsg := "" if len(event.Params) >= 1 { @@ -129,7 +129,7 @@ func (b *Birc) handleJoinPart(client *girc.Client, event girc.Event) { } } if event.Source.Name != b.Nick { - if b.GetBool("nosendjoinpart") || !b.isUserActive(event.Source.Name) { + if isActive, _ := b.isUserActive(event.Source.Name); !isActive || b.GetBool("nosendjoinpart") { return } partmsg := "" @@ -171,7 +171,7 @@ func (b *Birc) handleNick(client *girc.Client, event girc.Event) { if len(event.Params) != 1 { b.Log.Debugf("handleJoinPart: malformed nick change? %#v", event) return - } else if b.isUserActive(event.Source.Name) { + } else if isActive, activeTime := b.isUserActive(event.Source.Name); isActive { msg := config.Message{Username: "system", Text: event.Source.Name + " changed nick to " + event.Params[0], Channel: b.getPseudoChannel(), @@ -181,7 +181,7 @@ func (b *Birc) handleNick(client *girc.Client, event girc.Event) { b.Remote <- msg if b.ActivityTimeout != 0 { // This doesn't count as new activity, but it does preserve the value - b.activeUsers[event.Params[0]] = b.activeUsers[event.Source.Name] + b.markUserActive(event.Params[0], activeTime) } } } @@ -293,7 +293,7 @@ func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) { b.Log.Debugf("<= Sending message from %s on %s to gateway", event.Params[0], b.Account) if b.ActivityTimeout > 0 { b.Log.Debugf("<= Updating last-active time for user %s", event.Source.Name) - b.activeUsers[event.Source.Name] = time.Now().Unix() + b.markUserActive(event.Source.Name, time.Now().Unix()) } b.Remote <- rmsg b.cleanActiveMap() diff --git a/bridge/irc/irc.go b/bridge/irc/irc.go index 886ad6a0..db4134fb 100644 --- a/bridge/irc/irc.go +++ b/bridge/irc/irc.go @@ -10,6 +10,7 @@ import ( "sort" "strconv" "strings" + "sync" "time" "github.com/42wim/matterbridge/bridge" @@ -29,6 +30,7 @@ type Birc struct { names map[string][]string activeUsers map[string]int64 activeUsersLastCleaned int64 + activeUsersMutex sync.RWMutex connected chan error Local chan config.Message // local queue for flood control FirstConnection, authDone bool @@ -432,20 +434,24 @@ func (b *Birc) getTLSConfig() (*tls.Config, error) { return tlsConfig, nil } -func (b *Birc) isUserActive(nick string) bool { +func (b *Birc) isUserActive(nick string) (bool, int64) { b.Log.Debugf("checking activity for %s", nick) if b.ActivityTimeout == 0 { - return true - } else if activeTime, ok := b.activeUsers[nick]; ok { - now := time.Now().Unix() - b.Log.Debugf("last activity for %s was %d, currently %d", nick, activeTime, now) - if now < activeTime { - b.Log.Errorf("User %s has active time in the future: %d", nick, activeTime) - return true // err on the side of caution + return true, 0 + } else { + b.activeUsersMutex.RLock() + defer b.activeUsersMutex.RUnlock() + if activeTime, ok := b.activeUsers[nick]; ok { + now := time.Now().Unix() + b.Log.Debugf("last activity for %s was %d, currently %d", nick, activeTime, now) + if now < activeTime { + b.Log.Errorf("User %s has active time in the future: %d", nick, activeTime) + return true, now // err on the side of caution + } + return (now - activeTime) < b.ActivityTimeout, activeTime } - return (now - activeTime) < b.ActivityTimeout } - return false + return false, 0 } func (b *Birc) getPseudoChannel() string { @@ -463,6 +469,8 @@ func (b *Birc) cleanActiveMap() { if b.ActivityTimeout == 0 || (b.activeUsersLastCleaned-now < b.ActivityTimeout) { return } + b.activeUsersMutex.Lock() + defer b.activeUsersMutex.Unlock() for nick, activeTime := range b.activeUsers { if now-activeTime > b.ActivityTimeout { b.Log.Debugf("last activity for %s was %d, currently %d. Deleting.", nick, activeTime, now) @@ -470,3 +478,9 @@ func (b *Birc) cleanActiveMap() { } } } + +func (b *Birc) markUserActive(nick string, activeTime int64) { + b.activeUsersMutex.Lock() + defer b.activeUsersMutex.Unlock() + b.activeUsers[nick] = activeTime +} From 37d3721c282bc1fd3eb22f15a05a494556554bef Mon Sep 17 00:00:00 2001 From: Kufat Date: Sat, 6 Aug 2022 19:43:30 -0400 Subject: [PATCH 04/12] Use separate handleQuit() method --- bridge/irc/handlers.go | 57 +++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/bridge/irc/handlers.go b/bridge/irc/handlers.go index e3900f3a..990636ba 100644 --- a/bridge/irc/handlers.go +++ b/bridge/irc/handlers.go @@ -106,28 +106,6 @@ func (b *Birc) handleJoinPart(client *girc.Client, event girc.Event) { } return } - if event.Command == "QUIT" { - if event.Source.Name == b.Nick && strings.Contains(event.Last(), "Ping timeout") { - b.Log.Infof("%s reconnecting ..", b.Account) - b.Remote <- config.Message{Username: "system", Text: "reconnect", Channel: b.getPseudoChannel(), Account: b.Account, Event: config.EventFailure} - return - } else if b.GetBool("nosendjoinpart") { - return - } else if isActive, _ := b.isUserActive(event.Source.Name); isActive || isKill(event.Last()) { - verbosequit := "" - quitmsg := "" - if len(event.Params) >= 1 { - quitmsg = " with message: " + event.Last() - } - if b.GetBool("verbosejoinpart") { - verbosequit = " (" + event.Source.Ident + "@" + event.Source.Host + ")" - } - msg := config.Message{Username: "system", Text: event.Source.Name + verbosequit + " quit" + quitmsg, Channel: b.getPseudoChannel(), Account: b.Account, Event: config.EventJoinLeave} - b.Log.Debugf("<= Message is %#v", msg) - b.Remote <- msg - return - } - } if event.Source.Name != b.Nick { if isActive, _ := b.isUserActive(event.Source.Name); !isActive || b.GetBool("nosendjoinpart") { return @@ -155,16 +133,16 @@ func (b *Birc) handleNewConnection(client *girc.Client, event girc.Event) { i := b.i b.Nick = event.Params[0] - i.Handlers.AddBg("PRIVMSG", b.handlePrivMsg) - i.Handlers.AddBg("CTCP_ACTION", b.handlePrivMsg) - i.Handlers.Add(girc.RPL_TOPICWHOTIME, b.handleTopicWhoTime) - i.Handlers.AddBg(girc.NOTICE, b.handleNotice) + i.Handlers.Add("INVITE", b.handleInvite) i.Handlers.AddBg("JOIN", b.handleJoinPart) i.Handlers.AddBg("PART", b.handleJoinPart) - i.Handlers.AddBg("QUIT", b.handleJoinPart) i.Handlers.AddBg("KICK", b.handleJoinPart) i.Handlers.AddBg("NICK", b.handleNick) - i.Handlers.Add("INVITE", b.handleInvite) + i.Handlers.AddBg(girc.NOTICE, b.handleNotice) + i.Handlers.AddBg("PRIVMSG", b.handlePrivMsg) + i.Handlers.AddBg("CTCP_ACTION", b.handlePrivMsg) + i.Handlers.AddBg("QUIT", b.handleQuit) + i.Handlers.Add(girc.RPL_TOPICWHOTIME, b.handleTopicWhoTime) } func (b *Birc) handleNick(client *girc.Client, event girc.Event) { @@ -298,7 +276,28 @@ func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) { b.Remote <- rmsg b.cleanActiveMap() } - +func (b *Birc) handleQuit(client *girc.Client, event girc.Event) { + if event.Source.Name == b.Nick && strings.Contains(event.Last(), "Ping timeout") { + b.Log.Infof("%s reconnecting ..", b.Account) + b.Remote <- config.Message{Username: "system", Text: "reconnect", Channel: b.getPseudoChannel(), Account: b.Account, Event: config.EventFailure} + return + } else if b.GetBool("nosendjoinpart") { + return + } else if isActive, _ := b.isUserActive(event.Source.Name); isActive || isKill(event.Last()) { + verbosequit := "" + quitmsg := "" + if len(event.Params) >= 1 { + quitmsg = " with message: " + event.Last() + } + if b.GetBool("verbosejoinpart") { + verbosequit = " (" + event.Source.Ident + "@" + event.Source.Host + ")" + } + msg := config.Message{Username: "system", Text: event.Source.Name + verbosequit + " quit" + quitmsg, Channel: b.getPseudoChannel(), Account: b.Account, Event: config.EventJoinLeave} + b.Log.Debugf("<= Message is %#v", msg) + b.Remote <- msg + return + } +} func (b *Birc) handleRunCommands() { for _, cmd := range b.GetStringSlice("RunCommands") { if err := b.i.Cmd.SendRaw(cmd); err != nil { From e275bbfb2ea03595374448c9f34c2bdbc3af5091 Mon Sep 17 00:00:00 2001 From: Kufat Date: Mon, 8 Aug 2022 10:39:35 -0400 Subject: [PATCH 05/12] Add simple functionality to disable URLs in part/quit msgs --- bridge/irc/handlers.go | 10 ++++++++++ bridge/irc/irc.go | 2 ++ 2 files changed, 12 insertions(+) diff --git a/bridge/irc/handlers.go b/bridge/irc/handlers.go index 990636ba..88716514 100644 --- a/bridge/irc/handlers.go +++ b/bridge/irc/handlers.go @@ -84,6 +84,10 @@ func isKill(quitmsg string) bool { return strings.HasPrefix(quitmsg, "Killed") || strings.HasPrefix(quitmsg, "Local kill") || strings.HasPrefix(quitmsg[1:], "-lined") } +func suppressUrls(msg *string) { + *msg = strings.ReplaceAll(*msg, "://", ": //") +} + func (b *Birc) handleJoinPart(client *girc.Client, event girc.Event) { if len(event.Params) == 0 { b.Log.Debugf("handleJoinPart: empty Params? %#v", event) @@ -114,6 +118,9 @@ func (b *Birc) handleJoinPart(client *girc.Client, event girc.Event) { if event.Command == "PART" && len(event.Params) >= 2 { partmsg = " with message: " + event.Last() } + if len(partmsg) > 0 && b.SuppressPartQuitURLs { + suppressUrls(&partmsg) + } msg := config.Message{Username: "system", Text: event.Source.Name + " " + strings.ToLower(event.Command) + "s" + partmsg, Channel: channel, Account: b.Account, Event: config.EventJoinLeave} if b.GetBool("verbosejoinpart") { b.Log.Debugf("<= Sending verbose JOIN_LEAVE event from %s to gateway", b.Account) @@ -289,6 +296,9 @@ func (b *Birc) handleQuit(client *girc.Client, event girc.Event) { if len(event.Params) >= 1 { quitmsg = " with message: " + event.Last() } + if len(quitmsg) > 0 && b.SuppressPartQuitURLs { + suppressUrls(&quitmsg) + } if b.GetBool("verbosejoinpart") { verbosequit = " (" + event.Source.Ident + "@" + event.Source.Host + ")" } diff --git a/bridge/irc/irc.go b/bridge/irc/irc.go index db4134fb..ed356e00 100644 --- a/bridge/irc/irc.go +++ b/bridge/irc/irc.go @@ -34,6 +34,7 @@ type Birc struct { connected chan error Local chan config.Message // local queue for flood control FirstConnection, authDone bool + SuppressPartQuitURLs bool MessageDelay, MessageQueue, MessageLength int ActivityTimeout int64 channels map[string]bool @@ -75,6 +76,7 @@ func New(cfg *bridge.Config) bridge.Bridger { } else { b.ActivityTimeout = 0 // Disable } + b.SuppressPartQuitURLs = b.GetBool("SuppressPartQuitURLs") b.FirstConnection = true return b } From b82b7b67a8981a5bd034d9999bb8fbe4e7828c59 Mon Sep 17 00:00:00 2001 From: Kufat Date: Mon, 8 Aug 2022 13:00:06 -0400 Subject: [PATCH 06/12] Switch to better solution for disabling URL embeds. --- bridge/discord/discord.go | 13 +++++++++++++ bridge/discord/helpers.go | 5 +++++ bridge/irc/handlers.go | 10 ---------- bridge/irc/irc.go | 2 -- matterbridge.toml.sample | 9 +++++++++ 5 files changed, 27 insertions(+), 12 deletions(-) diff --git a/bridge/discord/discord.go b/bridge/discord/discord.go index 5ae6c572..14f6a98b 100644 --- a/bridge/discord/discord.go +++ b/bridge/discord/discord.go @@ -36,6 +36,9 @@ type Bdiscord struct { userMemberMap map[string]*discordgo.Member nickMemberMap map[string]*discordgo.Member + noEmbedPartUrls bool + noEmbedUrls bool + // Webhook specific logic useAutoWebhooks bool transmitter *transmitter.Transmitter @@ -57,6 +60,12 @@ func New(cfg *bridge.Config) bridge.Bridger { b.nickMemberMap = make(map[string]*discordgo.Member) b.channelInfoMap = make(map[string]*config.ChannelInfo) + b.noEmbedPartUrls = b.GetBool(("NoEmbedPartUrls")) + b.noEmbedUrls = b.GetBool(("NoEmbedUrls")) + if b.noEmbedPartUrls && b.noEmbedUrls { + b.Log.Info("NoEmbedUrls supersedes NoEmbedPartUrls") + } + b.useAutoWebhooks = b.GetBool("AutoWebhooks") if b.useAutoWebhooks { b.Log.Debug("Using automatic webhooks") @@ -269,6 +278,10 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) { msg.Text = "_" + msg.Text + "_" } + if b.noEmbedUrls || (msg.Event == config.EventJoinLeave && b.noEmbedPartUrls) { + disableEmbedUrls(&msg.Text) + } + // Handle prefix hint for unthreaded messages. if msg.ParentNotFound() { msg.ParentID = "" diff --git a/bridge/discord/helpers.go b/bridge/discord/helpers.go index 2e18f46c..2a62595a 100644 --- a/bridge/discord/helpers.go +++ b/bridge/discord/helpers.go @@ -233,6 +233,11 @@ func (b *Bdiscord) splitURL(url string) (string, string, bool) { return webhookURLSplit[webhookIdxID], webhookURLSplit[webhookIdxToken], true } +func disableEmbedUrls(msg *string) { + regex := regexp.MustCompile(`(\w+://\S+)`) + *msg = regex.ReplaceAllString(*msg, "<$1>") +} + func enumerateUsernames(s string) []string { onlySpace := true for _, r := range s { diff --git a/bridge/irc/handlers.go b/bridge/irc/handlers.go index 88716514..990636ba 100644 --- a/bridge/irc/handlers.go +++ b/bridge/irc/handlers.go @@ -84,10 +84,6 @@ func isKill(quitmsg string) bool { return strings.HasPrefix(quitmsg, "Killed") || strings.HasPrefix(quitmsg, "Local kill") || strings.HasPrefix(quitmsg[1:], "-lined") } -func suppressUrls(msg *string) { - *msg = strings.ReplaceAll(*msg, "://", ": //") -} - func (b *Birc) handleJoinPart(client *girc.Client, event girc.Event) { if len(event.Params) == 0 { b.Log.Debugf("handleJoinPart: empty Params? %#v", event) @@ -118,9 +114,6 @@ func (b *Birc) handleJoinPart(client *girc.Client, event girc.Event) { if event.Command == "PART" && len(event.Params) >= 2 { partmsg = " with message: " + event.Last() } - if len(partmsg) > 0 && b.SuppressPartQuitURLs { - suppressUrls(&partmsg) - } msg := config.Message{Username: "system", Text: event.Source.Name + " " + strings.ToLower(event.Command) + "s" + partmsg, Channel: channel, Account: b.Account, Event: config.EventJoinLeave} if b.GetBool("verbosejoinpart") { b.Log.Debugf("<= Sending verbose JOIN_LEAVE event from %s to gateway", b.Account) @@ -296,9 +289,6 @@ func (b *Birc) handleQuit(client *girc.Client, event girc.Event) { if len(event.Params) >= 1 { quitmsg = " with message: " + event.Last() } - if len(quitmsg) > 0 && b.SuppressPartQuitURLs { - suppressUrls(&quitmsg) - } if b.GetBool("verbosejoinpart") { verbosequit = " (" + event.Source.Ident + "@" + event.Source.Host + ")" } diff --git a/bridge/irc/irc.go b/bridge/irc/irc.go index ed356e00..db4134fb 100644 --- a/bridge/irc/irc.go +++ b/bridge/irc/irc.go @@ -34,7 +34,6 @@ type Birc struct { connected chan error Local chan config.Message // local queue for flood control FirstConnection, authDone bool - SuppressPartQuitURLs bool MessageDelay, MessageQueue, MessageLength int ActivityTimeout int64 channels map[string]bool @@ -76,7 +75,6 @@ func New(cfg *bridge.Config) bridge.Bridger { } else { b.ActivityTimeout = 0 // Disable } - b.SuppressPartQuitURLs = b.GetBool("SuppressPartQuitURLs") b.FirstConnection = true return b } diff --git a/matterbridge.toml.sample b/matterbridge.toml.sample index 0d7e78a1..2cf139fd 100644 --- a/matterbridge.toml.sample +++ b/matterbridge.toml.sample @@ -947,6 +947,15 @@ IgnoreNicks="" # IgnoreMessages="^~~ badword" IgnoreMessages="" +# Prevent URL embeds by encasing URLs in <> angle brackets. +# Useful if trolls are a problem on the other end of your bridge. +NoEmbedUrls=false + +# Prevent URL embeds in part/quit messages by encasing URLs in <> angle brackets. +# Useful if trolls spam in their quit messages or you're tired of seeing embeds for +# IRC client homepages. +NoEmbedPartUrls=false + # ReplaceMessages replaces substrings of messages in outgoing messages. # Regular expressions are supported. # From 5c82f187bc5e3cc173fc8f374daf2a9e13bb6ccd Mon Sep 17 00:00:00 2001 From: Kufat Date: Mon, 8 Aug 2022 18:07:17 -0400 Subject: [PATCH 07/12] Run gofumpt, remove unneeded 'else' --- bridge/irc/handlers.go | 26 ++++++++++++++++---------- bridge/irc/irc.go | 21 ++++++++++----------- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/bridge/irc/handlers.go b/bridge/irc/handlers.go index 990636ba..da617ebc 100644 --- a/bridge/irc/handlers.go +++ b/bridge/irc/handlers.go @@ -96,11 +96,13 @@ func (b *Birc) handleJoinPart(client *girc.Client, event girc.Event) { time.Sleep(time.Duration(b.GetInt("RejoinDelay")) * time.Second) b.Remote <- config.Message{Username: "system", Text: "rejoin", Channel: channel, Account: b.Account, Event: config.EventRejoinChannels} } else { - msg := config.Message{Username: "system", - Text: event.Source.Name + " kicked " + event.Params[1] + " with message: " + event.Last(), - Channel: channel, - Account: b.Account, - Event: config.EventJoinLeave} + msg := config.Message{ + Username: "system", + Text: event.Source.Name + " kicked " + event.Params[1] + " with message: " + event.Last(), + Channel: channel, + Account: b.Account, + Event: config.EventJoinLeave, + } b.Log.Debugf("<= Message is %#v", msg) b.Remote <- msg } @@ -150,11 +152,13 @@ func (b *Birc) handleNick(client *girc.Client, event girc.Event) { b.Log.Debugf("handleJoinPart: malformed nick change? %#v", event) return } else if isActive, activeTime := b.isUserActive(event.Source.Name); isActive { - msg := config.Message{Username: "system", - Text: event.Source.Name + " changed nick to " + event.Params[0], - Channel: b.getPseudoChannel(), - Account: b.Account, - Event: config.EventJoinLeave} + msg := config.Message{ + Username: "system", + Text: event.Source.Name + " changed nick to " + event.Params[0], + Channel: b.getPseudoChannel(), + Account: b.Account, + Event: config.EventJoinLeave, + } b.Log.Debugf("<= Message is %#v", msg) b.Remote <- msg if b.ActivityTimeout != 0 { @@ -276,6 +280,7 @@ func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) { b.Remote <- rmsg b.cleanActiveMap() } + func (b *Birc) handleQuit(client *girc.Client, event girc.Event) { if event.Source.Name == b.Nick && strings.Contains(event.Last(), "Ping timeout") { b.Log.Infof("%s reconnecting ..", b.Account) @@ -298,6 +303,7 @@ func (b *Birc) handleQuit(client *girc.Client, event girc.Event) { return } } + func (b *Birc) handleRunCommands() { for _, cmd := range b.GetStringSlice("RunCommands") { if err := b.i.Cmd.SendRaw(cmd); err != nil { diff --git a/bridge/irc/irc.go b/bridge/irc/irc.go index db4134fb..35c9f5f1 100644 --- a/bridge/irc/irc.go +++ b/bridge/irc/irc.go @@ -438,18 +438,17 @@ func (b *Birc) isUserActive(nick string) (bool, int64) { b.Log.Debugf("checking activity for %s", nick) if b.ActivityTimeout == 0 { return true, 0 - } else { - b.activeUsersMutex.RLock() - defer b.activeUsersMutex.RUnlock() - if activeTime, ok := b.activeUsers[nick]; ok { - now := time.Now().Unix() - b.Log.Debugf("last activity for %s was %d, currently %d", nick, activeTime, now) - if now < activeTime { - b.Log.Errorf("User %s has active time in the future: %d", nick, activeTime) - return true, now // err on the side of caution - } - return (now - activeTime) < b.ActivityTimeout, activeTime + } + b.activeUsersMutex.RLock() + defer b.activeUsersMutex.RUnlock() + if activeTime, ok := b.activeUsers[nick]; ok { + now := time.Now().Unix() + b.Log.Debugf("last activity for %s was %d, currently %d", nick, activeTime, now) + if now < activeTime { + b.Log.Errorf("User %s has active time in the future: %d", nick, activeTime) + return true, now // err on the side of caution } + return (now - activeTime) < b.ActivityTimeout, activeTime } return false, 0 } From a6d5ea520c744dde7f923d4d31b50033080ed346 Mon Sep 17 00:00:00 2001 From: Kufat Date: Tue, 9 Aug 2022 17:02:10 -0400 Subject: [PATCH 08/12] Fix failure on empty quit --- bridge/irc/handlers.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bridge/irc/handlers.go b/bridge/irc/handlers.go index da617ebc..0f96c2bc 100644 --- a/bridge/irc/handlers.go +++ b/bridge/irc/handlers.go @@ -81,7 +81,9 @@ func (b *Birc) handleInvite(client *girc.Client, event girc.Event) { } func isKill(quitmsg string) bool { - return strings.HasPrefix(quitmsg, "Killed") || strings.HasPrefix(quitmsg, "Local kill") || strings.HasPrefix(quitmsg[1:], "-lined") + return (strings.HasPrefix(quitmsg, "Killed") || + strings.HasPrefix(quitmsg, "Local kill") || + (len(quitmsg) > 7 && strings.HasPrefix(quitmsg[1:], "-lined"))) } func (b *Birc) handleJoinPart(client *girc.Client, event girc.Event) { From 26bda54e164b406ab81d0b81d66cfeae285e3c29 Mon Sep 17 00:00:00 2001 From: Kufat Date: Mon, 5 Sep 2022 18:47:03 -0400 Subject: [PATCH 09/12] Code review changes --- bridge/irc/handlers.go | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/bridge/irc/handlers.go b/bridge/irc/handlers.go index 0f96c2bc..80c793ee 100644 --- a/bridge/irc/handlers.go +++ b/bridge/irc/handlers.go @@ -97,17 +97,20 @@ func (b *Birc) handleJoinPart(client *girc.Client, event girc.Event) { b.Log.Infof("Got kicked from %s by %s", channel, event.Source.Name) time.Sleep(time.Duration(b.GetInt("RejoinDelay")) * time.Second) b.Remote <- config.Message{Username: "system", Text: "rejoin", Channel: channel, Account: b.Account, Event: config.EventRejoinChannels} - } else { - msg := config.Message{ - Username: "system", - Text: event.Source.Name + " kicked " + event.Params[1] + " with message: " + event.Last(), - Channel: channel, - Account: b.Account, - Event: config.EventJoinLeave, - } - b.Log.Debugf("<= Message is %#v", msg) - b.Remote <- msg + return } + if b.GetBool("nosendjoinpart") { + return + } + msg := config.Message{ + Username: "system", + Text: event.Source.Name + " kicked " + event.Params[1] + " with message: " + event.Last(), + Channel: channel, + Account: b.Account, + Event: config.EventJoinLeave, + } + b.Log.Debugf("<= Message is %#v", msg) + b.Remote <- msg return } if event.Source.Name != b.Nick { @@ -153,7 +156,11 @@ func (b *Birc) handleNick(client *girc.Client, event girc.Event) { if len(event.Params) != 1 { b.Log.Debugf("handleJoinPart: malformed nick change? %#v", event) return - } else if isActive, activeTime := b.isUserActive(event.Source.Name); isActive { + } + if b.GetBool("nosendjoinpart") { + return + } + if isActive, activeTime := b.isUserActive(event.Source.Name); isActive { msg := config.Message{ Username: "system", Text: event.Source.Name + " changed nick to " + event.Params[0], From edbf9c310bfbce819b77270f73df46a4ea266ced Mon Sep 17 00:00:00 2001 From: Kufat Date: Mon, 5 Sep 2022 20:36:53 -0400 Subject: [PATCH 10/12] Change activity to per-channel --- bridge/discord/discord.go | 13 --------- bridge/discord/helpers.go | 5 ---- bridge/irc/handlers.go | 55 +++++++++++++++++++++--------------- bridge/irc/irc.go | 59 ++++++++++++++++++++++++++++----------- matterbridge.toml.sample | 9 ------ 5 files changed, 76 insertions(+), 65 deletions(-) diff --git a/bridge/discord/discord.go b/bridge/discord/discord.go index 14f6a98b..5ae6c572 100644 --- a/bridge/discord/discord.go +++ b/bridge/discord/discord.go @@ -36,9 +36,6 @@ type Bdiscord struct { userMemberMap map[string]*discordgo.Member nickMemberMap map[string]*discordgo.Member - noEmbedPartUrls bool - noEmbedUrls bool - // Webhook specific logic useAutoWebhooks bool transmitter *transmitter.Transmitter @@ -60,12 +57,6 @@ func New(cfg *bridge.Config) bridge.Bridger { b.nickMemberMap = make(map[string]*discordgo.Member) b.channelInfoMap = make(map[string]*config.ChannelInfo) - b.noEmbedPartUrls = b.GetBool(("NoEmbedPartUrls")) - b.noEmbedUrls = b.GetBool(("NoEmbedUrls")) - if b.noEmbedPartUrls && b.noEmbedUrls { - b.Log.Info("NoEmbedUrls supersedes NoEmbedPartUrls") - } - b.useAutoWebhooks = b.GetBool("AutoWebhooks") if b.useAutoWebhooks { b.Log.Debug("Using automatic webhooks") @@ -278,10 +269,6 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) { msg.Text = "_" + msg.Text + "_" } - if b.noEmbedUrls || (msg.Event == config.EventJoinLeave && b.noEmbedPartUrls) { - disableEmbedUrls(&msg.Text) - } - // Handle prefix hint for unthreaded messages. if msg.ParentNotFound() { msg.ParentID = "" diff --git a/bridge/discord/helpers.go b/bridge/discord/helpers.go index 2a62595a..2e18f46c 100644 --- a/bridge/discord/helpers.go +++ b/bridge/discord/helpers.go @@ -233,11 +233,6 @@ func (b *Bdiscord) splitURL(url string) (string, string, bool) { return webhookURLSplit[webhookIdxID], webhookURLSplit[webhookIdxToken], true } -func disableEmbedUrls(msg *string) { - regex := regexp.MustCompile(`(\w+://\S+)`) - *msg = regex.ReplaceAllString(*msg, "<$1>") -} - func enumerateUsernames(s string) []string { onlySpace := true for _, r := range s { diff --git a/bridge/irc/handlers.go b/bridge/irc/handlers.go index 80c793ee..efe44344 100644 --- a/bridge/irc/handlers.go +++ b/bridge/irc/handlers.go @@ -114,7 +114,8 @@ func (b *Birc) handleJoinPart(client *girc.Client, event girc.Event) { return } if event.Source.Name != b.Nick { - if isActive, _ := b.isUserActive(event.Source.Name); !isActive || b.GetBool("nosendjoinpart") { + if isActive, _ := b.isUserActive(event.Source.Name, channel); !isActive || + b.GetBool("nosendjoinpart") { return } partmsg := "" @@ -160,19 +161,21 @@ func (b *Birc) handleNick(client *girc.Client, event girc.Event) { if b.GetBool("nosendjoinpart") { return } - if isActive, activeTime := b.isUserActive(event.Source.Name); isActive { - msg := config.Message{ - Username: "system", - Text: event.Source.Name + " changed nick to " + event.Params[0], - Channel: b.getPseudoChannel(), - Account: b.Account, - Event: config.EventJoinLeave, - } - b.Log.Debugf("<= Message is %#v", msg) - b.Remote <- msg - if b.ActivityTimeout != 0 { - // This doesn't count as new activity, but it does preserve the value - b.markUserActive(event.Params[0], activeTime) + if activeChannels := b.getActiveChannels(event.Source.Name); len(activeChannels) > 0 { + for _, activityInfo := range activeChannels { + msg := config.Message{ + Username: "system", + Text: event.Source.Name + " changed nick to " + event.Params[0], + Channel: activityInfo.channel, + Account: b.Account, + Event: config.EventJoinLeave, + } + b.Log.Debugf("<= Message is %#v", msg) + b.Remote <- msg + if b.ActivityTimeout != 0 { + // This doesn't count as new activity, but it does preserve the value + b.markUserActive(event.Params[0], activityInfo.channel, activityInfo.activeTime) + } } } } @@ -229,9 +232,10 @@ func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) { return } + channel := strings.ToLower(event.Params[0]) rmsg := config.Message{ Username: event.Source.Name, - Channel: strings.ToLower(event.Params[0]), + Channel: channel, Account: b.Account, UserID: event.Source.Ident + "@" + event.Source.Host, } @@ -284,7 +288,7 @@ func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) { b.Log.Debugf("<= Sending message from %s on %s to gateway", event.Params[0], b.Account) if b.ActivityTimeout > 0 { b.Log.Debugf("<= Updating last-active time for user %s", event.Source.Name) - b.markUserActive(event.Source.Name, time.Now().Unix()) + b.markUserActive(event.Source.Name, channel, time.Now().Unix()) } b.Remote <- rmsg b.cleanActiveMap() @@ -293,23 +297,30 @@ func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) { func (b *Birc) handleQuit(client *girc.Client, event girc.Event) { if event.Source.Name == b.Nick && strings.Contains(event.Last(), "Ping timeout") { b.Log.Infof("%s reconnecting ..", b.Account) - b.Remote <- config.Message{Username: "system", Text: "reconnect", Channel: b.getPseudoChannel(), Account: b.Account, Event: config.EventFailure} + for mychan := range b.channels { + b.Remote <- config.Message{Username: "system", Text: "reconnect", Channel: mychan, Account: b.Account, Event: config.EventFailure} + } return } else if b.GetBool("nosendjoinpart") { return - } else if isActive, _ := b.isUserActive(event.Source.Name); isActive || isKill(event.Last()) { + } else if activeChannels, found := b.activeUsers[event.Source.Name]; found { + userWasKilled := isKill(event.Last()) verbosequit := "" quitmsg := "" + nowTime := time.Now().Unix() if len(event.Params) >= 1 { quitmsg = " with message: " + event.Last() } if b.GetBool("verbosejoinpart") { verbosequit = " (" + event.Source.Ident + "@" + event.Source.Host + ")" } - msg := config.Message{Username: "system", Text: event.Source.Name + verbosequit + " quit" + quitmsg, Channel: b.getPseudoChannel(), Account: b.Account, Event: config.EventJoinLeave} - b.Log.Debugf("<= Message is %#v", msg) - b.Remote <- msg - return + for channel, activeTime := range activeChannels { + if userWasKilled || b.isActive(activeTime, nowTime) { + msg := config.Message{Username: "system", Text: event.Source.Name + verbosequit + " quit" + quitmsg, Channel: channel, Account: b.Account, Event: config.EventJoinLeave} + b.Log.Debugf("<= Message is %#v", msg) + b.Remote <- msg + } + } } } diff --git a/bridge/irc/irc.go b/bridge/irc/irc.go index 35c9f5f1..14a62c79 100644 --- a/bridge/irc/irc.go +++ b/bridge/irc/irc.go @@ -28,7 +28,7 @@ type Birc struct { i *girc.Client Nick string names map[string][]string - activeUsers map[string]int64 + activeUsers map[string]map[string]int64 activeUsersLastCleaned int64 activeUsersMutex sync.RWMutex connected chan error @@ -41,12 +41,17 @@ type Birc struct { *bridge.Config } +type ActivityInfo struct { + channel string + activeTime int64 +} + func New(cfg *bridge.Config) bridge.Bridger { b := &Birc{} b.Config = cfg b.Nick = b.GetString("Nick") b.names = make(map[string][]string) - b.activeUsers = make(map[string]int64) + b.activeUsers = make(map[string]map[string]int64) b.connected = make(chan error) b.channels = make(map[string]bool) @@ -434,33 +439,45 @@ func (b *Birc) getTLSConfig() (*tls.Config, error) { return tlsConfig, nil } -func (b *Birc) isUserActive(nick string) (bool, int64) { +func (b *Birc) isActive(activityTime int64, nowTime int64) bool { + return (nowTime - activityTime) < b.ActivityTimeout +} + +func (b *Birc) isUserActive(nick string, channel string) (bool, int64) { b.Log.Debugf("checking activity for %s", nick) if b.ActivityTimeout == 0 { return true, 0 } b.activeUsersMutex.RLock() defer b.activeUsersMutex.RUnlock() - if activeTime, ok := b.activeUsers[nick]; ok { + if activeTime, ok := b.activeUsers[nick][channel]; ok { now := time.Now().Unix() b.Log.Debugf("last activity for %s was %d, currently %d", nick, activeTime, now) if now < activeTime { b.Log.Errorf("User %s has active time in the future: %d", nick, activeTime) return true, now // err on the side of caution } - return (now - activeTime) < b.ActivityTimeout, activeTime + return b.isActive(now, activeTime), activeTime } return false, 0 } -func (b *Birc) getPseudoChannel() string { - for channelname, active := range b.channels { - if active { - return channelname +func (b *Birc) getActiveChannels(nick string) []ActivityInfo { + retval := make([]ActivityInfo, 0) + if channels, found := b.activeUsers[nick]; found { + now := time.Now().Unix() + for channel, activeTime := range channels { + if now < activeTime { + b.Log.Errorf("User %s has active time for channel %s in the future: %d", + nick, + channel, + activeTime) + } else if (now - activeTime) < b.ActivityTimeout { + retval = append(retval, ActivityInfo{channel, activeTime}) + } } } - b.Log.Warningf("Bot not active in any channels!") - return "" + return retval } func (b *Birc) cleanActiveMap() { @@ -470,16 +487,26 @@ func (b *Birc) cleanActiveMap() { } b.activeUsersMutex.Lock() defer b.activeUsersMutex.Unlock() - for nick, activeTime := range b.activeUsers { - if now-activeTime > b.ActivityTimeout { - b.Log.Debugf("last activity for %s was %d, currently %d. Deleting.", nick, activeTime, now) + for nick, activeChannels := range b.activeUsers { + for channel, activeTime := range activeChannels { + if now-activeTime > b.ActivityTimeout { + b.Log.Debugf("last activity for %s was %d, currently %d. Deleting.", nick, activeTime, now) + delete(activeChannels, channel) + } + } + if 0 == len(activeChannels) { delete(b.activeUsers, nick) } } } -func (b *Birc) markUserActive(nick string, activeTime int64) { +func (b *Birc) markUserActive(nick string, channel string, activeTime int64) { b.activeUsersMutex.Lock() defer b.activeUsersMutex.Unlock() - b.activeUsers[nick] = activeTime + nickActivity, found := b.activeUsers[nick] + if !found { + b.activeUsers[nick] = make(map[string]int64) + nickActivity = b.activeUsers[nick] + } + nickActivity[channel] = activeTime } diff --git a/matterbridge.toml.sample b/matterbridge.toml.sample index 2cf139fd..0d7e78a1 100644 --- a/matterbridge.toml.sample +++ b/matterbridge.toml.sample @@ -947,15 +947,6 @@ IgnoreNicks="" # IgnoreMessages="^~~ badword" IgnoreMessages="" -# Prevent URL embeds by encasing URLs in <> angle brackets. -# Useful if trolls are a problem on the other end of your bridge. -NoEmbedUrls=false - -# Prevent URL embeds in part/quit messages by encasing URLs in <> angle brackets. -# Useful if trolls spam in their quit messages or you're tired of seeing embeds for -# IRC client homepages. -NoEmbedPartUrls=false - # ReplaceMessages replaces substrings of messages in outgoing messages. # Regular expressions are supported. # From 47219a7393cccef978cbdd829fb54b826543947b Mon Sep 17 00:00:00 2001 From: Kufat Date: Mon, 5 Sep 2022 20:57:20 -0400 Subject: [PATCH 11/12] Tweak logging for marking users active --- bridge/irc/handlers.go | 1 - bridge/irc/irc.go | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/bridge/irc/handlers.go b/bridge/irc/handlers.go index efe44344..2506fec5 100644 --- a/bridge/irc/handlers.go +++ b/bridge/irc/handlers.go @@ -287,7 +287,6 @@ func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) { b.Log.Debugf("<= Sending message from %s on %s to gateway", event.Params[0], b.Account) if b.ActivityTimeout > 0 { - b.Log.Debugf("<= Updating last-active time for user %s", event.Source.Name) b.markUserActive(event.Source.Name, channel, time.Now().Unix()) } b.Remote <- rmsg diff --git a/bridge/irc/irc.go b/bridge/irc/irc.go index 14a62c79..41ff613f 100644 --- a/bridge/irc/irc.go +++ b/bridge/irc/irc.go @@ -501,6 +501,7 @@ func (b *Birc) cleanActiveMap() { } func (b *Birc) markUserActive(nick string, channel string, activeTime int64) { + b.Log.Debugf("<= Updating last-active time for user %s in channel %s to %d", nick, channel, activeTime) b.activeUsersMutex.Lock() defer b.activeUsersMutex.Unlock() nickActivity, found := b.activeUsers[nick] From 0cd8db8a55eb5197e12954a66b3739fedfa908b6 Mon Sep 17 00:00:00 2001 From: Kufat Date: Tue, 6 Sep 2022 07:37:25 -0400 Subject: [PATCH 12/12] Fix isActive() call arg order --- bridge/irc/irc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridge/irc/irc.go b/bridge/irc/irc.go index 41ff613f..64273f83 100644 --- a/bridge/irc/irc.go +++ b/bridge/irc/irc.go @@ -457,7 +457,7 @@ func (b *Birc) isUserActive(nick string, channel string) (bool, int64) { b.Log.Errorf("User %s has active time in the future: %d", nick, activeTime) return true, now // err on the side of caution } - return b.isActive(now, activeTime), activeTime + return b.isActive(activeTime, now), activeTime } return false, 0 }