Thumbnail

rani/matterbridge.git

Clone URL: https://git.buni.party/rani/matterbridge.git

commit 3ef0b8e9b8ab04db2c35e87a6d6e211c30305744 Author: Patrick Connolly <patrick.c.connolly@gmail.com> Date: Mon Nov 26 17:47:04 2018 +0000 Sync channel topics between Slack bridges (#585) Added logic to allow for configurable synchronisation of topics and purposes of channels between Slack bridges. diff --git a/bridge/config/config.go b/bridge/config/config.go index 21010db..eb34912 100644 --- a/bridge/config/config.go +++ b/bridge/config/config.go @@ -1176 +1177 @@ type Protocol struct {   ShowEmbeds bool // discord   SkipTLSVerify bool // IRC, mattermost   StripNick bool // all protocols + SyncTopic bool // slack   Team string // mattermost   Token string // gitter, slack, discord, api   Topic string // zulip diff --git a/bridge/slack/handlers.go b/bridge/slack/handlers.go index 035c5af..89c800d 100644 --- a/bridge/slack/handlers.go +++ b/bridge/slack/handlers.go @@ -1166 +11611 @@ func (b *Bslack) skipMessageEvent(ev *slack.MessageEvent) bool {   return b.GetBool(noSendJoinConfig)   case sPinnedItem, sUnpinnedItem:   return true + case sChannelTopic, sChannelPurpose: + // Skip the event if our bot/user account changed the topic/purpose + if ev.User == b.si.User.ID { + return true + }   }     // Skip any messages that we made ourselves or from 'slackbot' (see #527). @@ -1367 +1416 @@ func (b *Bslack) skipMessageEvent(ev *slack.MessageEvent) bool {   if len(ev.Files) > 0 {   return b.filesCached(ev.Files)   } -   return false  }   @@ -2016 +2057 @@ func (b *Bslack) handleStatusEvent(ev *slack.MessageEvent, rmsg *config.Message)   rmsg.Username = sSystemUser   rmsg.Event = config.EventJoinLeave   case sChannelTopic, sChannelPurpose: + b.populateChannels()   rmsg.Event = config.EventTopicChange   case sMessageChanged:   rmsg.Text = ev.SubMessage.Text diff --git a/bridge/slack/helpers.go b/bridge/slack/helpers.go index b0fdaba..39fbcea 100644 --- a/bridge/slack/helpers.go +++ b/bridge/slack/helpers.go @@ -26212 +26228 @@ func (b *Bslack) populateMessageWithBotInfo(ev *slack.MessageEvent, rmsg *config  }    var ( - mentionRE = regexp.MustCompile(`<@([a-zA-Z0-9]+)>`) - channelRE = regexp.MustCompile(`<#[a-zA-Z0-9]+\|(.+?)>`) - variableRE = regexp.MustCompile(`<!((?:subteam\^)?[a-zA-Z0-9]+)(?:\|@?(.+?))?>`) - urlRE = regexp.MustCompile(`<(.*?)(\|.*?)?>`) + mentionRE = regexp.MustCompile(`<@([a-zA-Z0-9]+)>`) + channelRE = regexp.MustCompile(`<#[a-zA-Z0-9]+\|(.+?)>`) + variableRE = regexp.MustCompile(`<!((?:subteam\^)?[a-zA-Z0-9]+)(?:\|@?(.+?))?>`) + urlRE = regexp.MustCompile(`<(.*?)(\|.*?)?>`) + topicOrPurposeRE = regexp.MustCompile(`(?s)(@.+) (cleared|set)(?: the)? channel (topic|purpose)(?:: (.*))?`)  )   +func (b *Bslack) extractTopicOrPurpose(text string) (string, string) { + r := topicOrPurposeRE.FindStringSubmatch(text) + if len(r) == 5 { + action, updateType, extracted := r[2], r[3], r[4] + switch action { + case "set": + return updateType, extracted + case "cleared": + return updateType, "" + } + } + b.Log.Warnf("Encountered channel topic or purpose change message with unexpected format: %s", text) + return "unknown", "" +} +  // @see https://api.slack.com/docs/message-formatting#linking_to_channels_and_users  func (b *Bslack) replaceMention(text string) string {   replaceFunc := func(match string) string { diff --git a/bridge/slack/helpers_test.go b/bridge/slack/helpers_test.go new file mode 100644 index 0000000..c9ff647 --- /dev/null +++ b/bridge/slack/helpers_test.go @@ -00 +136 @@ +package bslack + +import ( + "io/ioutil" + "testing" + + "github.com/42wim/matterbridge/bridge" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" +) + +func TestExtractTopicOrPurpose(t *testing.T) { + testcases := map[string]struct { + input string + wantChangeType string + wantOutput string + }{ + "success - topic type": {"@someone set channel topic: foo bar", "topic", "foo bar"}, + "success - purpose type": {"@someone set channel purpose: foo bar", "purpose", "foo bar"}, + "success - one line": {"@someone set channel topic: foo bar", "topic", "foo bar"}, + "success - multi-line": {"@someone set channel topic: foo\nbar", "topic", "foo\nbar"}, + "success - cleared": {"@someone cleared channel topic", "topic", ""}, + "error - unhandled": {"some unmatched message", "unknown", ""}, + } + + logger := logrus.New() + logger.SetOutput(ioutil.Discard) + cfg := &bridge.Config{Log: logger.WithFields(nil)} + b := newBridge(cfg) + for name, tc := range testcases { + gotChangeType, gotOutput := b.extractTopicOrPurpose(tc.input) + + assert.Equalf(t, tc.wantChangeType, gotChangeType, "This testcase failed: %s", name) + assert.Equalf(t, tc.wantOutput, gotOutput, "This testcase failed: %s", name) + } +} diff --git a/bridge/slack/slack.go b/bridge/slack/slack.go index d054ae8..a38bbb5 100644 --- a/bridge/slack/slack.go +++ b/bridge/slack/slack.go @@ -2818 +28114 @@ func (b *Bslack) sendRTM(msg config.Message) (string, error) {   return "", nil   }   - // Handle message deletions.   var handled bool + + // Handle topic/purpose updates. + if handled, err = b.handleTopicOrPurpose(&msg, channelInfo); handled { + return "", err + } + + // Handle message deletions.   if handled, err = b.deleteMessage(&msg, channelInfo); handled {   return msg.ID, err   } @@ -3156 +32149 @@ func (b *Bslack) sendRTM(msg config.Message) (string, error) {   return b.postMessage(&msg, messageParameters, channelInfo)  }   +func (b *Bslack) updateTopicOrPurpose(msg *config.Message, channelInfo *slack.Channel) (bool, error) { + var updateFunc func(channelID string, value string) (*slack.Channel, error) + + incomingChangeType, text := b.extractTopicOrPurpose(msg.Text) + switch incomingChangeType { + case "topic": + updateFunc = b.rtm.SetTopicOfConversation + case "purpose": + updateFunc = b.rtm.SetPurposeOfConversation + default: + b.Log.Errorf("Unhandled type received from extractTopicOrPurpose: %s", incomingChangeType) + return true, nil + } + for { + _, err := updateFunc(channelInfo.ID, text) + if err == nil { + return true, nil + } + if err = b.handleRateLimit(err); err != nil { + return true, err + } + } +} + +// handles updating topic/purpose and determining whether to further propagate update messages. +func (b *Bslack) handleTopicOrPurpose(msg *config.Message, channelInfo *slack.Channel) (bool, error) { + if msg.Event != config.EventTopicChange { + return false, nil + } + + if b.GetBool("SyncTopic") { + return b.updateTopicOrPurpose(msg, channelInfo) + } + + // Pass along to normal message handlers. + if b.GetBool("ShowTopicChange") { + return false, nil + } + + // Swallow message as handled no-op. + return true, nil +} +  func (b *Bslack) deleteMessage(msg *config.Message, channelInfo *slack.Channel) (bool, error) {   if msg.Event != config.EventMsgDelete {   return false, nil diff --git a/gateway/gateway.go b/gateway/gateway.go index 72a0f6a..2b8bdfa 100644 --- a/gateway/gateway.go +++ b/gateway/gateway.go @@ -2678 +26710 @@ func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) []*BrM   return brMsgIDs   }   - // only relay topic change when configured - if msg.Event == config.EventTopicChange && !gw.Bridges[dest.Account].GetBool("ShowTopicChange") { + // only relay topic change when used in some way on other side + if msg.Event == config.EventTopicChange && + !gw.Bridges[dest.Account].GetBool("ShowTopicChange") && + !gw.Bridges[dest.Account].GetBool("SyncTopic") {   return brMsgIDs   }   diff --git a/matterbridge.toml.sample b/matterbridge.toml.sample index b51f351..0d9a8a8 100644 --- a/matterbridge.toml.sample +++ b/matterbridge.toml.sample @@ -76511 +76516 @@ ShowJoinPart=false  #OPTIONAL (default false)  StripNick=false   -#Enable to show topic changes from other bridges +#Enable to show topic/purpose changes from other bridges  #Only works hiding/show topic changes from slack bridge for now  #OPTIONAL (default false)  ShowTopicChange=false   +#Enable to sync topic/purpose changes from other bridges +#Only works syncing topic changes from slack bridge for now +#OPTIONAL (default false) +SyncTopic=false +  ###################################################################  #telegram section  ###################################################################