Exemple #1
0
 def send(channel, text, thread_ts=None):
     Slack.get_instance().rtm_send_message(channel, text, thread_ts)
Exemple #2
0
 def users(self):
     return Slack.get_instance().server.users
Exemple #3
0
 def channels(self):
     return Slack.get_instance().server.channels
Exemple #4
0
 def retrieve_bot_info():
     return Slack.get_instance().server.login_data['self']
Exemple #5
0
    def read_group_topic_webapi(self, channel):
        method = 'groups.info'

        kwargs = {'channel': channel, 'as_user': True}

        return Slack.get_instance().api_call(method, **kwargs)
Exemple #6
0
    def scheduled_messages(self):
        response = Slack.get_instance().api_call('chat.scheduledMessages.list')

        return response
Exemple #7
0
    def open_im(self, user):
        response = Slack.get_instance().api_call('im.open', user=user)

        return response['channel']['id']
Exemple #8
0
    def set_group_topic_webapi(self, channel, text):
        method = 'groups.setTopic'

        kwargs = {'channel': channel, 'topic': text, 'as_user': True}

        return Slack.get_instance().api_call(method, **kwargs)
Exemple #9
0
 def react(self, channel, ts, emoji):
     return Slack.get_instance().api_call('reactions.add',
                                          name=emoji,
                                          channel=channel,
                                          timestamp=ts)
Exemple #10
0
 def __init__(self, plugin_actions):
     self._client = Slack()
     self._plugin_actions = plugin_actions
     self._pool = ThreadPool()
Exemple #11
0
class EventDispatcher:
    RESPOND_MATCHER = re.compile(
        r'^(?:\<@(?P<atuser>\w+)\>:?|(?P<username>\w+):) ?(?P<text>.*)$')

    def __init__(self, plugin_actions):
        self._client = Slack()
        self._plugin_actions = plugin_actions
        self._pool = ThreadPool()

    def start(self):
        while True:
            for event in self._client.rtm_read():
                self._pool.add_task(self.handle_event, event)
            time.sleep(.1)

    def handle_event(self, event):
        # Gotta catch 'em all!
        for action in self._plugin_actions['catch_all'].values():
            action['function'](event)
        # Basic dispatch based on event type
        if 'type' in event:
            if event['type'] in self._plugin_actions['process']:
                for action in self._plugin_actions['process'][
                        event['type']].values():
                    action['function'](event)
        # Handle message listeners
        if 'type' in event and event['type'] == 'message':
            respond_to_msg = self._check_bot_mention(event)
            if respond_to_msg:
                listeners = self._find_listeners('respond_to')
                self._dispatch_listeners(listeners, respond_to_msg)
            else:
                listeners = self._find_listeners('listen_to')
                self._dispatch_listeners(listeners, event)

    def _find_listeners(self, type):
        return [action for action in self._plugin_actions[type].values()]

    def _gen_message(self, event, plugin_class_name):
        return Message(MessagingClient(), event, plugin_class_name)

    def _get_bot_id(self):
        return self._client.server.login_data['self']['id']

    def _get_bot_name(self):
        return self._client.server.login_data['self']['name']

    def _check_bot_mention(self, event):
        full_text = event.get('text', '')
        channel = event['channel']
        bot_name = self._get_bot_name()
        bot_id = self._get_bot_id()
        m = self.RESPOND_MATCHER.match(full_text)

        if channel[0] == 'C' or channel[0] == 'G':
            if not m:
                return None

            matches = m.groupdict()

            atuser = matches.get('atuser')
            username = matches.get('username')
            text = matches.get('text')

            if atuser != bot_id and username != bot_name:
                # a channel message at other user
                return None

            event['text'] = text
        else:
            if m:
                event['text'] = m.groupdict().get('text', None)
        return event

    def _dispatch_listeners(self, listeners, event):
        for l in listeners:
            matcher = l['regex']
            match = matcher.search(event.get('text', ''))
            if match:
                message = self._gen_message(event, l['class_name'])
                l['function'](message, **match.groupdict())
Exemple #12
0
class Machine:
    def __init__(self, settings=None):
        announce("Initializing Slack Machine:")

        with indent(4):
            puts("Loading settings...")
            if settings:
                self._settings = settings
                found_local_settings = True
            else:
                self._settings, found_local_settings = import_settings()
            fmt = '[%(asctime)s][%(levelname)s] %(name)s %(filename)s:%(funcName)s:%(lineno)d |' \
                  ' %(message)s'
            date_fmt = '%Y-%m-%d %H:%M:%S'
            log_level = self._settings.get('LOGLEVEL', logging.ERROR)
            logging.basicConfig(
                level=log_level,
                format=fmt,
                datefmt=date_fmt,
            )
            if not found_local_settings:
                warn(
                    "No local_settings found! Are you sure this is what you want?"
                )
            if 'SLACK_API_TOKEN' not in self._settings:
                error(
                    "No SLACK_API_TOKEN found in settings! I need that to work..."
                )
                sys.exit(1)
            self._client = Slack()
            puts("Initializing storage using backend: {}".format(
                self._settings['STORAGE_BACKEND']))
            self._storage = Storage.get_instance()
            logger.debug("Storage initialized!")

            self._plugin_actions = {
                'process': {},
                'listen_to': {},
                'respond_to': {},
                'catch_all': {}
            }
            self._help = {'human': {}, 'robot': {}}
            puts("Loading plugins...")
            self.load_plugins()
            logger.debug("The following plugin actions were registered: %s",
                         self._plugin_actions)
            self._dispatcher = EventDispatcher(self._plugin_actions,
                                               self._settings)

    def load_plugins(self):
        with indent(4):
            logger.debug("PLUGINS: %s", self._settings['PLUGINS'])
            for plugin in self._settings['PLUGINS']:
                for class_name, cls in import_string(plugin):
                    if issubclass(cls, MachineBasePlugin
                                  ) and cls is not MachineBasePlugin:
                        logger.debug(
                            "Found a Machine plugin: {}".format(plugin))
                        storage = PluginStorage(class_name)
                        instance = cls(self._settings, MessagingClient(),
                                       storage)
                        missing_settings = self._register_plugin(
                            class_name, instance)
                        if (missing_settings):
                            show_invalid(class_name)
                            with indent(4):
                                error_msg = "The following settings are missing: {}".format(
                                    ", ".join(missing_settings))
                                puts(colored.red(error_msg))
                                puts(
                                    colored.red(
                                        "This plugin will not be loaded!"))
                            del instance
                        else:
                            instance.init()
                            show_valid(class_name)
        self._storage.set('manual', dill.dumps(self._help))

    def _register_plugin(self, plugin_class, cls_instance):
        missing_settings = []
        missing_settings.extend(
            self._check_missing_settings(cls_instance.__class__))
        methods = inspect.getmembers(cls_instance, predicate=inspect.ismethod)
        for _, fn in methods:
            missing_settings.extend(self._check_missing_settings(fn))
        if missing_settings:
            return missing_settings

        if hasattr(cls_instance, 'catch_all'):
            self._plugin_actions['catch_all'][plugin_class] = {
                'class': cls_instance,
                'class_name': plugin_class,
                'function': getattr(cls_instance, 'catch_all')
            }
        if cls_instance.__doc__:
            class_help = cls_instance.__doc__.splitlines()[0]
        else:
            class_help = plugin_class
        self._help['human'][class_help] = self._help['human'].get(
            class_help, {})
        self._help['robot'][class_help] = self._help['robot'].get(
            class_help, [])
        for name, fn in methods:
            if hasattr(fn, 'metadata'):
                self._register_plugin_actions(plugin_class, fn.metadata,
                                              cls_instance, name, fn,
                                              class_help)

    def _check_missing_settings(self, fn_or_class):
        missing_settings = []
        if hasattr(fn_or_class,
                   'metadata') and 'required_settings' in fn_or_class.metadata:
            for setting in fn_or_class.metadata['required_settings']:
                if setting not in self._settings:
                    missing_settings.append(setting.upper())
        return missing_settings

    def _register_plugin_actions(self, plugin_class, metadata, cls_instance,
                                 fn_name, fn, class_help):
        fq_fn_name = "{}.{}".format(plugin_class, fn_name)
        if fn.__doc__:
            self._help['human'][class_help][
                fq_fn_name] = self._parse_human_help(fn.__doc__)
        for action, config in metadata['plugin_actions'].items():
            if action == 'process':
                event_type = config['event_type']
                event_handlers = self._plugin_actions['process'].get(
                    event_type, {})
                event_handlers[fq_fn_name] = {
                    'class': cls_instance,
                    'class_name': plugin_class,
                    'function': fn
                }
                self._plugin_actions['process'][event_type] = event_handlers
            if action == 'respond_to' or action == 'listen_to':
                for regex in config['regex']:
                    event_handler = {
                        'class': cls_instance,
                        'class_name': plugin_class,
                        'function': fn,
                        'regex': regex
                    }
                    key = "{}-{}".format(fq_fn_name, regex.pattern)
                    self._plugin_actions[action][key] = event_handler
                    self._help['robot'][class_help].append(
                        self._parse_robot_help(regex, action))
            if action == 'schedule':
                Scheduler.get_instance().add_job(fq_fn_name,
                                                 trigger='cron',
                                                 args=[cls_instance],
                                                 id=fq_fn_name,
                                                 replace_existing=True,
                                                 **config)
            if action == 'route':
                for route_config in config:
                    bottle.route(**route_config)(fn)

    @staticmethod
    def _parse_human_help(doc):
        summary = doc.splitlines()[0].split(':')
        if len(summary) > 1:
            command = summary[0].strip()
            cmd_help = summary[1].strip()
        else:
            command = "??"
            cmd_help = summary[0].strip()
        return {'command': command, 'help': cmd_help}

    @staticmethod
    def _parse_robot_help(regex, action):
        if action == 'respond_to':
            return "@botname {}".format(regex.pattern)
        else:
            return regex.pattern

    def _keepalive(self):
        while True:
            time.sleep(self._settings['KEEP_ALIVE'])
            self._client.server.send_to_websocket({'type': 'ping'})
            logger.debug("Client Ping!")

    def run(self):
        announce("\nStarting Slack Machine:")
        with indent(4):
            connected = self._client.rtm_connect()
            if not connected:
                logger.error("Could not connect to Slack! Aborting...")
                sys.exit(1)
            show_valid("Connected to Slack")
            Scheduler.get_instance().start()
            show_valid("Scheduler started")
            if not self._settings['DISABLE_HTTP']:
                self._bottle_thread = Thread(
                    target=bottle.run,
                    kwargs=dict(
                        host=self._settings['HTTP_SERVER_HOST'],
                        port=self._settings['HTTP_SERVER_PORT'],
                        server=self._settings['HTTP_SERVER_BACKEND'],
                    ))
                self._bottle_thread.daemon = True
                self._bottle_thread.start()
                show_valid("Web server started")

            if self._settings['KEEP_ALIVE']:
                self._keep_alive_thread = Thread(target=self._keepalive)
                self._keep_alive_thread.daemon = True
                self._keep_alive_thread.start()
                show_valid("Keepalive thread started [Interval: %ss]" %
                           self._settings['KEEP_ALIVE'])

            show_valid("Dispatcher started")
            self._dispatcher.start()
Exemple #13
0
class EventDispatcher:

    def __init__(self, plugin_actions, settings=None):
        self._client = Slack()
        self._plugin_actions = plugin_actions
        self._pool = ThreadPool()
        alias_regex = ''
        if settings and "ALIASES" in settings:
            logger.info("Setting aliases to {}".format(settings['ALIASES']))
            alias_regex = '|(?P<alias>{})'.format(
                '|'.join([re.escape(s) for s in settings['ALIASES'].split(',')]))
        self.RESPOND_MATCHER = re.compile(
            r"^(?:<@(?P<atuser>\w+)>:?|(?P<username>\w+):{}) ?(?P<text>.*)$".format(
                alias_regex
            ),
            re.DOTALL,
        )

    def start(self):
        while True:
            for event in self._client.rtm_read():
                self._pool.add_task(self.handle_event, event)
            time.sleep(.1)

    def handle_event(self, event):
        # Gotta catch 'em all!
        for action in self._plugin_actions['catch_all'].values():
            action['function'](event)
        # Basic dispatch based on event type
        if 'type' in event:
            if event['type'] in self._plugin_actions['process']:
                for action in self._plugin_actions['process'][event['type']].values():
                    action['function'](event)
        # Handle message listeners
        if 'type' in event and event['type'] == 'message':
            respond_to_msg = self._check_bot_mention(event)
            if respond_to_msg:
                listeners = self._find_listeners('respond_to')
                self._dispatch_listeners(listeners, respond_to_msg)
            else:
                listeners = self._find_listeners('listen_to')
                self._dispatch_listeners(listeners, event)
        if 'type' in event and event['type'] == 'pong':
            logger.debug("Server Pong!")

    def _find_listeners(self, type):
        return [action for action in self._plugin_actions[type].values()]

    @staticmethod
    def _gen_message(event, plugin_class_name):
        return Message(MessagingClient(), event, plugin_class_name)

    def _get_bot_id(self):
        return self._client.server.login_data['self']['id']

    def _get_bot_name(self):
        return self._client.server.login_data['self']['name']

    def _check_bot_mention(self, event):
        full_text = event.get('text', '')
        channel = event['channel']
        bot_name = self._get_bot_name()
        bot_id = self._get_bot_id()
        m = self.RESPOND_MATCHER.match(full_text)

        if channel[0] == 'C' or channel[0] == 'G':
            if not m:
                return None

            matches = m.groupdict()

            atuser = matches.get('atuser')
            username = matches.get('username')
            text = matches.get('text')
            alias = matches.get('alias')

            if alias:
                atuser = bot_id

            if atuser != bot_id and username != bot_name:
                # a channel message at other user
                return None

            event['text'] = text
        else:
            if m:
                event['text'] = m.groupdict().get('text', None)
        return event

    def _dispatch_listeners(self, listeners, event):
        for l in listeners:
            matcher = l['regex']
            match = matcher.search(event.get('text', ''))
            if match:
                message = self._gen_message(event, l['class_name'])
                l['function'](message, **match.groupdict())