Example #1
0
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
Example #2
0
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()
Example #3
0
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()
Example #5
0
    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()
Example #6
0
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}")