Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit eef02fb

Browse files
rhttimabbott
authored andcommitted
Slack bridge: Implement multiple channels bridges.
1 parent 41ec1a9 commit eef02fb

File tree

2 files changed

+55
-22
lines changed

2 files changed

+55
-22
lines changed

zulip/integrations/bridge_with_slack/bridge_with_slack_config.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,20 @@
33
"email": "[email protected]",
44
"api_key": "put api key here",
55
"site": "https://chat.zulip.org",
6-
"stream": "test here",
7-
"topic": "<- slack-bridge",
86
},
97
"slack": {
108
"username": "slack_username",
119
"token": "xoxb-your-slack-token",
12-
"channel": "C5Z5N7R8A -- must be channel id",
10+
},
11+
# Mapping between Slack channels and Zulip stream-topic's.
12+
# You can specify multiple pairs.
13+
"channel_mapping": {
14+
# Slack channel; must be channel ID
15+
"C5Z5N7R8A": {
16+
# Zulip stream
17+
"stream": "test here",
18+
# Zulip topic
19+
"topic": "<- slack-bridge",
20+
},
1321
},
1422
}

zulip/integrations/bridge_with_slack/run-slack-bridge

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import os
55
import sys
66
import threading
77
import traceback
8-
from typing import Any, Callable, Dict
8+
from typing import Any, Callable, Dict, Optional, Tuple
99

1010
import bridge_with_slack_config
1111
import slack_sdk
@@ -17,18 +17,28 @@ import zulip
1717
ZULIP_MESSAGE_TEMPLATE = "**{username}**: {message}"
1818
SLACK_MESSAGE_TEMPLATE = "<{username}> {message}"
1919

20+
StreamTopicT = Tuple[str, str]
2021

21-
def check_zulip_message_validity(msg: Dict[str, Any], config: Dict[str, Any]) -> bool:
22+
23+
def get_slack_channel_for_zulip_message(
24+
msg: Dict[str, Any], zulip_to_slack_map: Dict[StreamTopicT, Any], bot_email: str
25+
) -> Optional[str]:
2226
is_a_stream = msg["type"] == "stream"
23-
in_the_specified_stream = msg["display_recipient"] == config["stream"]
24-
at_the_specified_subject = msg["subject"] == config["topic"]
27+
if not is_a_stream:
28+
return None
29+
30+
stream_name = msg["display_recipient"]
31+
topic_name = msg["subject"]
32+
stream_topic: StreamTopicT = (stream_name, topic_name)
33+
if stream_topic not in zulip_to_slack_map:
34+
return None
2535

26-
# We do this to identify the messages generated from Matrix -> Zulip
27-
# and we make sure we don't forward it again to the Matrix.
28-
not_from_zulip_bot = msg["sender_email"] != config["email"]
29-
if is_a_stream and not_from_zulip_bot and in_the_specified_stream and at_the_specified_subject:
30-
return True
31-
return False
36+
# We do this to identify the messages generated from Slack -> Zulip
37+
# and we make sure we don't forward it again to the Slack.
38+
from_zulip_bot = msg["sender_email"] == bot_email
39+
if from_zulip_bot:
40+
return None
41+
return zulip_to_slack_map[stream_topic]
3242

3343

3444
class SlackBridge:
@@ -37,14 +47,17 @@ class SlackBridge:
3747
self.zulip_config = config["zulip"]
3848
self.slack_config = config["slack"]
3949

50+
self.slack_to_zulip_map: Dict[str, Dict[str, str]] = config["channel_mapping"]
51+
self.zulip_to_slack_map: Dict[StreamTopicT, str] = {
52+
(z["stream"], z["topic"]): s for s, z in config["channel_mapping"].items()
53+
}
54+
4055
# zulip-specific
4156
self.zulip_client = zulip.Client(
4257
email=self.zulip_config["email"],
4358
api_key=self.zulip_config["api_key"],
4459
site=self.zulip_config["site"],
4560
)
46-
self.zulip_stream = self.zulip_config["stream"]
47-
self.zulip_subject = self.zulip_config["topic"]
4861

4962
# slack-specific
5063
self.channel = self.slack_config["channel"]
@@ -68,14 +81,16 @@ class SlackBridge:
6881

6982
def zulip_to_slack(self) -> Callable[[Dict[str, Any]], None]:
7083
def _zulip_to_slack(msg: Dict[str, Any]) -> None:
71-
message_valid = check_zulip_message_validity(msg, self.zulip_config)
72-
if message_valid:
84+
slack_channel = get_slack_channel_for_zulip_message(
85+
msg, self.zulip_to_slack_map, self.zulip_config["email"]
86+
)
87+
if slack_channel is not None:
7388
self.wrap_slack_mention_with_bracket(msg)
7489
slack_text = SLACK_MESSAGE_TEMPLATE.format(
7590
username=msg["sender_full_name"], message=msg["content"]
7691
)
7792
self.slack_webclient.chat_postMessage(
78-
channel=self.channel,
93+
channel=slack_channel,
7994
text=slack_text,
8095
)
8196

@@ -91,7 +106,7 @@ class SlackBridge:
91106

92107
@rtm.on("message")
93108
def slack_to_zulip(client: RTMClient, event: Dict[str, Any]) -> None:
94-
if event["channel"] != self.channel:
109+
if event["channel"] not in self.slack_to_zulip_map:
95110
return
96111
user_id = event["user"]
97112
user = self.slack_id_to_name[user_id]
@@ -100,8 +115,12 @@ class SlackBridge:
100115
return
101116
self.replace_slack_id_with_name(event)
102117
content = ZULIP_MESSAGE_TEMPLATE.format(username=user, message=event["text"])
118+
zulip_endpoint = self.slack_to_zulip_map[event["channel"]]
103119
msg_data = dict(
104-
type="stream", to=self.zulip_stream, subject=self.zulip_subject, content=content
120+
type="stream",
121+
to=zulip_endpoint["stream"],
122+
subject=zulip_endpoint["topic"],
123+
content=content,
105124
)
106125
self.zulip_client.send_message(msg_data)
107126

@@ -118,11 +137,17 @@ if __name__ == "__main__":
118137
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
119138
parser = argparse.ArgumentParser(usage=usage)
120139

140+
config: Dict[str, Any] = bridge_with_slack_config.config
141+
if "channel_mapping" not in config:
142+
print(
143+
'The key "channel_mapping" is not found in bridge_with_slack_config.py.\n'
144+
"Your config file may be outdated."
145+
)
146+
exit(1)
147+
121148
print("Starting slack mirroring bot")
122149
print("MAKE SURE THE BOT IS SUBSCRIBED TO THE RELEVANT ZULIP STREAM")
123150

124-
config = bridge_with_slack_config.config
125-
126151
# We have to define rtm outside of SlackBridge because the rtm variable is used as a method decorator.
127152
rtm = RTMClient(token=config["slack"]["token"])
128153

0 commit comments

Comments
 (0)