forked from jshiffer/matterbridge
Sync channel topics between Slack bridges (#585)
Added logic to allow for configurable synchronisation of topics and purposes of channels between Slack bridges.
This commit is contained in:
parent
5ed7abdbeb
commit
f5659d455d
@ -117,6 +117,7 @@ type Protocol struct {
|
|||||||
ShowEmbeds bool // discord
|
ShowEmbeds bool // discord
|
||||||
SkipTLSVerify bool // IRC, mattermost
|
SkipTLSVerify bool // IRC, mattermost
|
||||||
StripNick bool // all protocols
|
StripNick bool // all protocols
|
||||||
|
SyncTopic bool // slack
|
||||||
Team string // mattermost
|
Team string // mattermost
|
||||||
Token string // gitter, slack, discord, api
|
Token string // gitter, slack, discord, api
|
||||||
Topic string // zulip
|
Topic string // zulip
|
||||||
|
@ -116,6 +116,11 @@ func (b *Bslack) skipMessageEvent(ev *slack.MessageEvent) bool {
|
|||||||
return b.GetBool(noSendJoinConfig)
|
return b.GetBool(noSendJoinConfig)
|
||||||
case sPinnedItem, sUnpinnedItem:
|
case sPinnedItem, sUnpinnedItem:
|
||||||
return true
|
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).
|
// Skip any messages that we made ourselves or from 'slackbot' (see #527).
|
||||||
@ -136,7 +141,6 @@ func (b *Bslack) skipMessageEvent(ev *slack.MessageEvent) bool {
|
|||||||
if len(ev.Files) > 0 {
|
if len(ev.Files) > 0 {
|
||||||
return b.filesCached(ev.Files)
|
return b.filesCached(ev.Files)
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,6 +205,7 @@ func (b *Bslack) handleStatusEvent(ev *slack.MessageEvent, rmsg *config.Message)
|
|||||||
rmsg.Username = sSystemUser
|
rmsg.Username = sSystemUser
|
||||||
rmsg.Event = config.EventJoinLeave
|
rmsg.Event = config.EventJoinLeave
|
||||||
case sChannelTopic, sChannelPurpose:
|
case sChannelTopic, sChannelPurpose:
|
||||||
|
b.populateChannels()
|
||||||
rmsg.Event = config.EventTopicChange
|
rmsg.Event = config.EventTopicChange
|
||||||
case sMessageChanged:
|
case sMessageChanged:
|
||||||
rmsg.Text = ev.SubMessage.Text
|
rmsg.Text = ev.SubMessage.Text
|
||||||
|
@ -262,12 +262,28 @@ func (b *Bslack) populateMessageWithBotInfo(ev *slack.MessageEvent, rmsg *config
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
mentionRE = regexp.MustCompile(`<@([a-zA-Z0-9]+)>`)
|
mentionRE = regexp.MustCompile(`<@([a-zA-Z0-9]+)>`)
|
||||||
channelRE = regexp.MustCompile(`<#[a-zA-Z0-9]+\|(.+?)>`)
|
channelRE = regexp.MustCompile(`<#[a-zA-Z0-9]+\|(.+?)>`)
|
||||||
variableRE = regexp.MustCompile(`<!((?:subteam\^)?[a-zA-Z0-9]+)(?:\|@?(.+?))?>`)
|
variableRE = regexp.MustCompile(`<!((?:subteam\^)?[a-zA-Z0-9]+)(?:\|@?(.+?))?>`)
|
||||||
urlRE = regexp.MustCompile(`<(.*?)(\|.*?)?>`)
|
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
|
// @see https://api.slack.com/docs/message-formatting#linking_to_channels_and_users
|
||||||
func (b *Bslack) replaceMention(text string) string {
|
func (b *Bslack) replaceMention(text string) string {
|
||||||
replaceFunc := func(match string) string {
|
replaceFunc := func(match string) string {
|
||||||
|
36
bridge/slack/helpers_test.go
Normal file
36
bridge/slack/helpers_test.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -281,8 +281,14 @@ func (b *Bslack) sendRTM(msg config.Message) (string, error) {
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle message deletions.
|
|
||||||
var handled bool
|
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 {
|
if handled, err = b.deleteMessage(&msg, channelInfo); handled {
|
||||||
return msg.ID, err
|
return msg.ID, err
|
||||||
}
|
}
|
||||||
@ -315,6 +321,49 @@ func (b *Bslack) sendRTM(msg config.Message) (string, error) {
|
|||||||
return b.postMessage(&msg, messageParameters, channelInfo)
|
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) {
|
func (b *Bslack) deleteMessage(msg *config.Message, channelInfo *slack.Channel) (bool, error) {
|
||||||
if msg.Event != config.EventMsgDelete {
|
if msg.Event != config.EventMsgDelete {
|
||||||
return false, nil
|
return false, nil
|
||||||
|
@ -267,8 +267,10 @@ func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) []*BrM
|
|||||||
return brMsgIDs
|
return brMsgIDs
|
||||||
}
|
}
|
||||||
|
|
||||||
// only relay topic change when configured
|
// only relay topic change when used in some way on other side
|
||||||
if msg.Event == config.EventTopicChange && !gw.Bridges[dest.Account].GetBool("ShowTopicChange") {
|
if msg.Event == config.EventTopicChange &&
|
||||||
|
!gw.Bridges[dest.Account].GetBool("ShowTopicChange") &&
|
||||||
|
!gw.Bridges[dest.Account].GetBool("SyncTopic") {
|
||||||
return brMsgIDs
|
return brMsgIDs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -765,11 +765,16 @@ ShowJoinPart=false
|
|||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
StripNick=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
|
#Only works hiding/show topic changes from slack bridge for now
|
||||||
#OPTIONAL (default false)
|
#OPTIONAL (default false)
|
||||||
ShowTopicChange=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
|
#telegram section
|
||||||
###################################################################
|
###################################################################
|
||||||
|
Loading…
Reference in New Issue
Block a user