@@ -8,13 +8,11 @@ import traceback
8
8
from typing import Any , Callable , Dict , Optional , Tuple
9
9
10
10
import bridge_with_slack_config
11
- import slack_sdk
12
- from slack_sdk .rtm_v2 import RTMClient
11
+ from slack_sdk .web .client import WebClient
13
12
14
13
import zulip
15
14
16
15
# change these templates to change the format of displayed message
17
- ZULIP_MESSAGE_TEMPLATE = "**{username}**: {message}"
18
16
SLACK_MESSAGE_TEMPLATE = "<{username}> {message}"
19
17
20
18
StreamTopicT = Tuple [str , str ]
@@ -41,15 +39,26 @@ def get_slack_channel_for_zulip_message(
41
39
return zulip_to_slack_map [stream_topic ]
42
40
43
41
42
+ def check_token_access (token : str ) -> None :
43
+ if token .startswith ("xoxp-" ):
44
+ print (
45
+ "--- Warning! ---\n "
46
+ "You entered a Slack user token, please copy the token under\n "
47
+ "'Bot User OAuth Token' which starts with 'xoxb-...'."
48
+ )
49
+ sys .exit (1 )
50
+ elif token .startswith ("xoxb-" ):
51
+ return
52
+
53
+
44
54
class SlackBridge :
45
55
def __init__ (self , config : Dict [str , Any ]) -> None :
46
56
self .config = config
47
57
self .zulip_config = config ["zulip" ]
48
58
self .slack_config = config ["slack" ]
49
59
50
- self .slack_to_zulip_map : Dict [str , Dict [str , str ]] = config ["channel_mapping" ]
51
60
self .zulip_to_slack_map : Dict [StreamTopicT , str ] = {
52
- (z ["stream " ], z ["topic" ]): s for s , z in config ["channel_mapping" ].items ()
61
+ (z ["channel " ], z ["topic" ]): s for s , z in config ["channel_mapping" ].items ()
53
62
}
54
63
55
64
# zulip-specific
@@ -65,25 +74,16 @@ class SlackBridge:
65
74
# https://github.com/zulip/python-zulip-api/issues/761 is fixed.
66
75
self .zulip_client_constructor = zulip_client_constructor
67
76
68
- # slack-specific
69
- self .slack_client = rtm
70
77
# Spawn a non-websocket client for getting the users
71
78
# list and for posting messages in Slack.
72
- self .slack_webclient = slack_sdk . WebClient (token = self .slack_config ["token" ])
79
+ self .slack_webclient = WebClient (token = self .slack_config ["token" ])
73
80
74
81
def wrap_slack_mention_with_bracket (self , zulip_msg : Dict [str , Any ]) -> None :
75
82
words = zulip_msg ["content" ].split (" " )
76
83
for w in words :
77
84
if w .startswith ("@" ):
78
85
zulip_msg ["content" ] = zulip_msg ["content" ].replace (w , "<" + w + ">" )
79
86
80
- def replace_slack_id_with_name (self , msg : Dict [str , Any ]) -> None :
81
- words = msg ["text" ].split (" " )
82
- for w in words :
83
- if w .startswith ("<@" ) and w .endswith (">" ):
84
- _id = w [2 :- 1 ]
85
- msg ["text" ] = msg ["text" ].replace (_id , self .slack_id_to_name [_id ])
86
-
87
87
def zulip_to_slack (self ) -> Callable [[Dict [str , Any ]], None ]:
88
88
def _zulip_to_slack (msg : Dict [str , Any ]) -> None :
89
89
slack_channel = get_slack_channel_for_zulip_message (
@@ -101,36 +101,6 @@ class SlackBridge:
101
101
102
102
return _zulip_to_slack
103
103
104
- def run_slack_listener (self ) -> None :
105
- members = self .slack_webclient .users_list ()["members" ]
106
- # See also https://api.slack.com/changelog/2017-09-the-one-about-usernames
107
- self .slack_id_to_name : Dict [str , str ] = {
108
- u ["id" ]: u ["profile" ].get ("display_name" , u ["profile" ]["real_name" ]) for u in members
109
- }
110
- self .slack_name_to_id = {v : k for k , v in self .slack_id_to_name .items ()}
111
-
112
- @rtm .on ("message" )
113
- def slack_to_zulip (client : RTMClient , event : Dict [str , Any ]) -> None :
114
- if event ["channel" ] not in self .slack_to_zulip_map :
115
- return
116
- user_id = event ["user" ]
117
- user = self .slack_id_to_name [user_id ]
118
- from_bot = user == self .slack_config ["username" ]
119
- if from_bot :
120
- return
121
- self .replace_slack_id_with_name (event )
122
- content = ZULIP_MESSAGE_TEMPLATE .format (username = user , message = event ["text" ])
123
- zulip_endpoint = self .slack_to_zulip_map [event ["channel" ]]
124
- msg_data = dict (
125
- type = "stream" ,
126
- to = zulip_endpoint ["stream" ],
127
- subject = zulip_endpoint ["topic" ],
128
- content = content ,
129
- )
130
- self .zulip_client_constructor ().send_message (msg_data )
131
-
132
- self .slack_client .start ()
133
-
134
104
135
105
if __name__ == "__main__" :
136
106
usage = """run-slack-bridge
@@ -142,6 +112,8 @@ if __name__ == "__main__":
142
112
sys .path .append (os .path .join (os .path .dirname (__file__ ), ".." ))
143
113
parser = argparse .ArgumentParser (usage = usage )
144
114
115
+ args = parser .parse_args ()
116
+
145
117
config : Dict [str , Any ] = bridge_with_slack_config .config
146
118
if "channel_mapping" not in config :
147
119
print (
@@ -150,12 +122,11 @@ if __name__ == "__main__":
150
122
)
151
123
sys .exit (1 )
152
124
125
+ check_token_access (config ["slack" ]["token" ])
126
+
153
127
print ("Starting slack mirroring bot" )
154
128
print ("MAKE SURE THE BOT IS SUBSCRIBED TO THE RELEVANT ZULIP STREAM(S) & SLACK CHANNEL(S)!" )
155
129
156
- # We have to define rtm outside of SlackBridge because the rtm variable is used as a method decorator.
157
- rtm = RTMClient (token = config ["slack" ]["token" ])
158
-
159
130
backoff = zulip .RandomExponentialBackoff (timeout_success_equivalent = 300 )
160
131
while backoff .keep_going ():
161
132
try :
@@ -164,14 +135,14 @@ if __name__ == "__main__":
164
135
zp = threading .Thread (
165
136
target = sb .zulip_client .call_on_each_message , args = (sb .zulip_to_slack (),)
166
137
)
167
- sp = threading .Thread (target = sb .run_slack_listener , args = ())
168
138
print ("Starting message handler on Zulip client" )
169
139
zp .start ()
170
- print ("Starting message handler on Slack client" )
171
- sp .start ()
172
140
141
+ print (
142
+ "Make sure your Slack Webhook integration is running\n "
143
+ "to receive messages from Slack."
144
+ )
173
145
zp .join ()
174
- sp .join ()
175
146
except Exception :
176
147
traceback .print_exc ()
177
148
backoff .fail ()
0 commit comments