@@ -5,7 +5,7 @@ import os
5
5
import sys
6
6
import threading
7
7
import traceback
8
- from typing import Any , Callable , Dict
8
+ from typing import Any , Callable , Dict , Optional , Tuple
9
9
10
10
import bridge_with_slack_config
11
11
import slack_sdk
@@ -17,18 +17,28 @@ import zulip
17
17
ZULIP_MESSAGE_TEMPLATE = "**{username}**: {message}"
18
18
SLACK_MESSAGE_TEMPLATE = "<{username}> {message}"
19
19
20
+ StreamTopicT = Tuple [str , str ]
20
21
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 ]:
22
26
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
25
35
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 ]
32
42
33
43
34
44
class SlackBridge :
@@ -37,14 +47,17 @@ class SlackBridge:
37
47
self .zulip_config = config ["zulip" ]
38
48
self .slack_config = config ["slack" ]
39
49
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
+
40
55
# zulip-specific
41
56
self .zulip_client = zulip .Client (
42
57
email = self .zulip_config ["email" ],
43
58
api_key = self .zulip_config ["api_key" ],
44
59
site = self .zulip_config ["site" ],
45
60
)
46
- self .zulip_stream = self .zulip_config ["stream" ]
47
- self .zulip_subject = self .zulip_config ["topic" ]
48
61
49
62
# slack-specific
50
63
self .channel = self .slack_config ["channel" ]
@@ -68,14 +81,16 @@ class SlackBridge:
68
81
69
82
def zulip_to_slack (self ) -> Callable [[Dict [str , Any ]], None ]:
70
83
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 :
73
88
self .wrap_slack_mention_with_bracket (msg )
74
89
slack_text = SLACK_MESSAGE_TEMPLATE .format (
75
90
username = msg ["sender_full_name" ], message = msg ["content" ]
76
91
)
77
92
self .slack_webclient .chat_postMessage (
78
- channel = self . channel ,
93
+ channel = slack_channel ,
79
94
text = slack_text ,
80
95
)
81
96
@@ -91,7 +106,7 @@ class SlackBridge:
91
106
92
107
@rtm .on ("message" )
93
108
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 :
95
110
return
96
111
user_id = event ["user" ]
97
112
user = self .slack_id_to_name [user_id ]
@@ -100,8 +115,12 @@ class SlackBridge:
100
115
return
101
116
self .replace_slack_id_with_name (event )
102
117
content = ZULIP_MESSAGE_TEMPLATE .format (username = user , message = event ["text" ])
118
+ zulip_endpoint = self .slack_to_zulip_map [event ["channel" ]]
103
119
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 ,
105
124
)
106
125
self .zulip_client .send_message (msg_data )
107
126
@@ -118,11 +137,17 @@ if __name__ == "__main__":
118
137
sys .path .append (os .path .join (os .path .dirname (__file__ ), ".." ))
119
138
parser = argparse .ArgumentParser (usage = usage )
120
139
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
+
121
148
print ("Starting slack mirroring bot" )
122
149
print ("MAKE SURE THE BOT IS SUBSCRIBED TO THE RELEVANT ZULIP STREAM" )
123
150
124
- config = bridge_with_slack_config .config
125
-
126
151
# We have to define rtm outside of SlackBridge because the rtm variable is used as a method decorator.
127
152
rtm = RTMClient (token = config ["slack" ]["token" ])
128
153
0 commit comments