class SlackHelper: def __init__(self, bot_token, app_token, channel): self.bot_token = bot_token self.app_token = app_token self.channel = channel self.message_queue = Queue() self.last_connect_try: Optional[float] = None print("Initializing SlackHelper") self.socket_client = SocketModeClient( app_token=self.app_token, web_client=WebClient(token=self.bot_token)) self.socket_client.socket_mode_request_listeners.append( lambda client, req: self._process_event(client, req)) self.check_connected() print("Finished initializing SlackHelper") def __del__(self): self.socket_client.close() def check_connected(self): if not self.socket_client.is_connected(): now = time.time() if self.last_connect_try is not None and self.last_connect_try + 10 > now: return # We've tried in the last 10 seconds print("Going to try to connect") try: self.socket_client.connect() print("Connected") except URLError: print("Could not connect") self.last_connect_try = now def _process_event(self, client: SocketModeClient, req: SocketModeRequest): if req.type == "events_api": # Acknowledge the request anyway response = SocketModeResponse(envelope_id=req.envelope_id) client.send_socket_mode_response(response) # Add a reaction to the message if it's a new message message = req.payload["event"] if message["type"] == "message" and message.get("subtype") is None: self.message_queue.put(message) # This doesn't work for some reason # print(client.web_client.reactions_add( # name="eyes", # channel=message["channel"], # timestamp=message["ts"], # )) def new_messages(self) -> list: self.check_connected() # We don't really care if this blocks messages = [] while not self.message_queue.empty(): messages.append(self.message_queue.get()) return messages
def main(): parser = argparse.ArgumentParser( description='Simple Bugzilla triage helper bot for Slack.') parser.add_argument('-c', '--config', metavar='FILE', default='~/.triagebot', help='config file') parser.add_argument('-d', '--database', metavar='FILE', default='~/.triagebot-db', help='database file') args = parser.parse_args() # Read config with open(os.path.expanduser(args.config)) as fh: config = DottedDict(yaml.safe_load(fh)) config.database = os.path.expanduser(args.database) env_map = (('TRIAGEBOT_SLACK_APP_TOKEN', 'slack-app-token'), ('TRIAGEBOT_SLACK_TOKEN', 'slack-token'), ('TRIAGEBOT_BUGZILLA_KEY', 'bugzilla-key')) for env, config_key in env_map: v = os.environ.get(env) if v: setattr(config, config_key, v) # Connect to services client = WebClient(token=config.slack_token) # store our user ID config.bot_id = client.auth_test()['user_id'] bzapi = bugzilla.Bugzilla(config.bugzilla, api_key=config.bugzilla_key, force_rest=True) if not bzapi.logged_in: raise Exception('Did not authenticate') db = Database(config) # Start socket-mode listener in the background socket_client = SocketModeClient( app_token=config.slack_app_token, web_client=WebClient(token=config.slack_token)) socket_client.socket_mode_request_listeners.append( lambda socket_client, req: process_event(config, socket_client, req)) socket_client.connect() # Run scheduler Scheduler(config, client, bzapi, db).run()
def main(): parser = argparse.ArgumentParser( description='Slack bot to send periodic 1:1 invitations.') parser.add_argument('-c', '--config', metavar='FILE', default='~/.11bot', help='config file') parser.add_argument('-d', '--database', metavar='FILE', default='~/.11bot-db', help='database file') args = parser.parse_args() # Self-test Grouping.selftest() # Read config with open(os.path.expanduser(args.config)) as fh: config = DottedDict(yaml.safe_load(fh)) config.database = os.path.expanduser(args.database) env_map = ( ('ELEVENBOT_APP_TOKEN', 'app-token'), ('ELEVENBOT_TOKEN', 'token'), ) for env, config_key in env_map: v = os.environ.get(env) if v: setattr(config, config_key, v) # Connect to services client = WebClient(token=config.token) # store our user ID config.bot_id = client.auth_test()['user_id'] db = Database(config) # Start socket-mode listener in the background socket_client = SocketModeClient(app_token=config.app_token, web_client=WebClient(token=config.token)) socket_client.socket_mode_request_listeners.append( lambda socket_client, req: process_event(config, socket_client, req)) socket_client.connect() # Run scheduler Scheduler(config, client, db).run()
def test_send_message_while_disconnection(self): if is_ci_unstable_test_skip_enabled(): return t = Thread(target=start_socket_mode_server(self, 3011)) t.daemon = True t.start() time.sleep(2) # wait for the server try: self.reset_sever_state() client = SocketModeClient( app_token="xapp-A111-222-xyz", web_client=self.web_client, auto_reconnect_enabled=False, trace_enabled=True, ) client.wss_uri = "ws://0.0.0.0:3011/link" client.connect() time.sleep(1) # wait for the connection client.send_message("foo") client.disconnect() time.sleep(1) # wait for the connection try: client.send_message("foo") self.fail("SlackClientNotConnectedError is expected here") except SlackClientNotConnectedError as _: pass client.connect() time.sleep(1) # wait for the connection client.send_message("foo") finally: client.close() self.server.stop() self.server.close()
web_client=WebClient(token=bot_token) # xoxb-111-222-xyz ) from slack_sdk.socket_mode.response import SocketModeResponse from slack_sdk.socket_mode.request import SocketModeRequest def process(client: SocketModeClient, req: SocketModeRequest): if req.type == "events_api": # Acknowledge the request anyway response = SocketModeResponse(envelope_id=req.envelope_id) client.send_socket_mode_response(response) # Add a reaction to the message if it's a new message if req.payload["event"]["type"] == "message" \ and req.payload["event"].get("subtype") is None: client.web_client.reactions_add( name="eyes", channel=req.payload["event"]["channel"], timestamp=req.payload["event"]["ts"], ) # Add a new listener to receive messages from Slack # You can add more listeners like this client.socket_mode_request_listeners.append(process) # Establish a WebSocket connection to the Socket Mode servers client.connect() # Just not to stop this process from threading import Event Event().wait()
class SlackBot(): def __init__(self, setting): xoxb_token = setting['slack']['xoxb_token'] xapp_token = setting['slack']['xapp_token'] self._web_client = WebClient(token=xoxb_token) self._sm_client = SocketModeClient(app_token=xapp_token, web_client=self._web_client) self.plugins_setting = setting['plugins'] self.plugins_path = setting['bot']['plugins_dir'] self.plugin_modules = [] self.plugin_classes = [] self.plugin_instances = [] self._self = None self._team = None self._users_list = {} # [ 'id' ] => SlackUser self._channels_list = {} self._data = None def _get_rtm_client(self): return self._rtm_client def _set_rtm_client(self, rc): self._rtm_client = rc rtm_client = property(_get_rtm_client, _set_rtm_client) def _get_web_client(self): return self._web_client def _set_web_client(self, rc): self._web_client = rc web_client = property(_get_web_client, _set_web_client) # plugin loader def load_plugins(self): for ps in self.plugins_setting: mod = importlib.import_module(ps['module']) klass_name = ps['name'] klass = getattr(mod, klass_name) self.plugin_classes.append(klass) self.plugin_instances.append(klass(self, ps)) def load_plugins_filename_based(self): plugins_dir = os.listdir(self.plugins_path) # current_dir = os.path.dirname( os.path.abspath( __file__ ) ) for filename in plugins_dir: if filename.endswith('.py'): if filename == "__init__.py": continue klass_name = os.path.splitext(filename)[0] klass_name = klass_name[0].upper() + klass_name[1:] modulePath = self.plugins_path + '/' + filename cpath = os.path.splitext(modulePath)[0].replace( os.path.sep, '.') try: mod = importlib.import_module(cpath) self.plugin_modules.append(mod) klass = getattr(mod, klass_name) self.plugin_classes.append(klass) self.plugin_instances.append(klass(self, klass_name)) except ModuleNotFoundError: print('Module not found') except AttributeError: print('Method not found') def unload_plugins(self): for ins in self.plugin_instances: del (ins) self.plugin_instances = [] for cls in self.plugin_classes: del (cls) self.plugin_classes = [] for mod in self.plugin_modules: del (mod) self.plugin_modules = [] def reload_plugins(self): self.unload_plugins() self.load_plugins() # bot information def self_user(self): return self._self def self_id(self): u = self.self_user() return u.id def self_name(self): return self.self_user().name def team_info(self): return self._team def team_id(self): return self.team_info()['id'] def team_name(self): return self.team_info()['name'] def update_self_user(self, user): self._self = user def update_team_info(self, info): self._team = info def update_users_list(self, users): for user in users: self._users_list[user['id']] = SlackUser(user) def update_groups_list(self, groups): for group in groups: self._channels_list[group['id']] = SlackGroup(group) def update_ims_list(self, ims): for im in ims: self._channels_list[im['id']] = SlackIM(im) def update_channels_list(self, channels): for channel in channels: self._channels_list[channel['id']] = SlackChannel(channel) def resolve_channel_id_from_name(self, name): pass # plugin commands def send_message(self, channel, message, attachments_json=None): self._web_client.chat_postMessage(channel=channel.id, text=message, attachments=attachments_json) def send_mention_message(self, channel, user, message, attachments_json=None): mention_message = "<@" + user.id + "> " + message self._web_client.chat_postMessage(channel=channel.id, text=mention_message, attachments=attachments_json) def send_kick(self, channel, user): self._web_client.channels_kick(channel=channel.id, user=user.id) # plugin events def on_server_connect(self): for plugin in self.plugin_instances: plugin.on_server_connect() def process_message(self, data): channel = self._channels_list[data['channel']] user = self._users_list[data['user']] text = data['text'] # if user.id != self.self_id(): # ignore own message for plugin in self.plugin_instances: plugin.on_message(channel, user, text) def process_message_changed(self, data): channel = self._channels_list[data['channel']] user = self._users_list[data['message']['user']] text = data['message']['text'] prev_user = data['previous_message']['user'] prev_text = data['previous_message']['text'] # if user.id != self.self_id(): # ignore own message for plugin in self.plugin_instances: plugin.on_message_changed(channel, user, text, prev_user, prev_text) def on_message(self, payload): data = payload if 'bot_id' in data: return if 'subtype' in data: if data['subtype'] == 'message_changed': self.process_message_changed(data) else: self.process_message(data) def on_channel_joined(self, **payload): data = payload channel = data['channel'] self._channels_list[channel['id']] = SlackChannel(channel) def on_channel_left(self, **payload): data = payload del self._channels_list[data['channel']] # TODO: It should be not delete the channel and It must be update the status such as a 'is_member'. # self._channels_list[ data[ 'channel' ] ].is_member = False def on_member_joined_channel(self, **payload): data = payload channel = self._channels_list[data['channel']] user = self._users_list[data['user']] for plugin in self.plugin_instances: plugin.on_joined(channel, user) def on_member_left_channel(self, **payload): data = payload channel = self._channels_list[data['channel']] user = self._users_list[data['user']] for plugin in self.plugin_instances: plugin.on_left(channel, user) # process slack rtm def on_socket_mode_request(self, client: SocketModeClient, req: SocketModeRequest): if req.type == "events_api": # Acknowledge the request anyway response = SocketModeResponse(envelope_id=req.envelope_id) client.send_socket_mode_response(response) if req.payload['event']['type'] == 'open': self.on_open(req.payload['event']) elif req.payload['event']['type'] == 'message': self.on_message(req.payload['event']) elif req.payload['event']['type'] == 'channel_joined': self.on_channel_joined(req.payload['event']) elif req.payload['event']['type'] == 'channel_left': self.on_channel_left(req.payload['event']) elif req.payload['event']['type'] == 'member_joined_channel': self.on_member_joined_channel(req.payload['event']) elif req.payload['event']['type'] == 'member_left_channel': self.on_member_left_channel(req.payload['event']) def start(self): self._sm_client.socket_mode_request_listeners.append( self.on_socket_mode_request) self._sm_client.connect() response = self._web_client.users_list() self.update_users_list(response['members']) response = self._web_client.conversations_list() self.update_channels_list(response['channels']) response = self._web_client.team_info() self.update_team_info(response['team']) response = self._web_client.auth_test() self_id = response['user_id'] self.update_self_user(self._users_list[self_id]) self.load_plugins() self.on_server_connect() from threading import Event Event().wait()
def test_interactions(self): if is_ci_unstable_test_skip_enabled(): return t = Thread(target=start_socket_mode_server(self, 3011)) t.daemon = True t.start() time.sleep(2) # wait for the server try: buffer_size_list = [1024, 9000, 35, 49] + list( [randint(16, 128) for _ in range(10)]) for buffer_size in buffer_size_list: self.reset_sever_state() received_messages = [] received_socket_mode_requests = [] def message_handler(message): self.logger.info(f"Raw Message: {message}") time.sleep(randint(50, 200) / 1000) received_messages.append(message) def socket_mode_request_handler(client: BaseSocketModeClient, request: SocketModeRequest): self.logger.info(f"Socket Mode Request: {request}") time.sleep(randint(50, 200) / 1000) received_socket_mode_requests.append(request) self.logger.info( f"Started testing with buffer size: {buffer_size}") client = SocketModeClient( app_token="xapp-A111-222-xyz", web_client=self.web_client, on_message_listeners=[message_handler], receive_buffer_size=buffer_size, auto_reconnect_enabled=False, trace_enabled=True, ) try: client.socket_mode_request_listeners.append( socket_mode_request_handler) client.wss_uri = "ws://0.0.0.0:3011/link" client.connect() self.assertTrue(client.is_connected()) time.sleep(2) # wait for the message receiver repeat = 2 for _ in range(repeat): client.send_message("foo") client.send_message("bar") client.send_message("baz") self.assertTrue(client.is_connected()) expected = (socket_mode_envelopes + [socket_mode_hello_message] + ["foo", "bar", "baz"] * repeat) expected.sort() count = 0 while count < 5 and len(received_messages) < len(expected): time.sleep(0.1) self.logger.debug( f"Received messages: {len(received_messages)}") count += 0.1 received_messages.sort() self.assertEqual(len(received_messages), len(expected)) self.assertEqual(received_messages, expected) self.assertEqual(len(socket_mode_envelopes), len(received_socket_mode_requests)) finally: pass # client.close() self.logger.info(f"Passed with buffer size: {buffer_size}") finally: client.close() self.server.stop() self.server.close() self.logger.info(f"Passed with buffer size: {buffer_size_list}")