Esempio n. 1
0
class Bot:
    def __init__(self, config):
        self.s = SlackSocket(config['slack'].get('token'))

        # Is this a good way to handle mongo connection? Seems weird.
        # TODO check for connection timeout
        self.mongo = mongoengine
        self.mongo.connect(config['mongo'].get('db'),
                           host=config['mongo'].get('host'),
                           port=config['mongo'].getint('port'))

    def start(self):
        for event in self.s.events():
            if event.type == 'message':
                # TODO Pass when message has subtype, eg. message_changed.
                # Maybe do this in a nicer way. Use event.event (dict)?
                if 'subtype' in event.json:
                    pass
                else:
                    event_json = json.loads(event.json)
                    message = Message(user=event_json['user'],
                                      channel=event_json['channel'],
                                      text=event_json['text'].strip(),
                                      timestamp=datetime.datetime.utcnow())
                    message.save()

    def quit(self):
        self.s.close()
        self.mongo.connection.disconnect()
        sys.exit(0)
Esempio n. 2
0
    def start(self):
        socket = SlackSocket(config_provider.token, translate=True)

        for event in socket.events():
            message = MessageWrapper(json.loads(event.json))

            if message.type == "message":
                pykka.ActorRegistry.broadcast(message.__dict__)
Esempio n. 3
0
class SlackBot(ChatBot):
    """
    params:
     - slack_token(str):
    """

    def __init__(self, slack_token, redis_host, redis_port):
        print('Starting Slackbot')

        self.slacksocket = SlackSocket(slack_token, event_filters=['message'])
        self.me = self.slacksocket.user

        print('Connected to Slack as %s' % self.me)

        super().__init__(redis_host, redis_port)

    @property
    def messages(self):
        for event in self.slacksocket.events():
            log.debug('saw event %s' % event.json)
            if self.me in event.mentions:
                yield self._parse(event)

    def reply(self, msg, channel):
        # skip any empty messages
        if not msg or msg == 'EOF':
            return

        # make codeblock if message is multiline
        if isinstance(msg, list):
            msg = '```' + '\n'.join(msg) + '```'
        else:
            msg = '`' + msg + '`'

        self.slacksocket.send_msg(msg, channel_name=channel, confirm=False)
        log.debug('sent "%s" to "%s"' % (msg, channel))

    @staticmethod
    def _parse(event):
        """
        Parse slack event, removing @ mention
        """
        words = event.event['text'].split(' ')
        words.pop(0)

        return (' '.join(words), event.event['user'], event.event['channel'])
class SlackInterface:

    MESSAGES = {
        'hello_world': (
            "Hello, world. I'm posting this to @channel in response to {user}."),
    }

    # Get any sequence of word-characters, possibly including the @ before them to indicate a username,
    # OR,
    # Get ANYTHING that starts and ends with ``.
    TOKENIZER_RE = re.compile(r'(?:@?\w+|`[^`]*`)')

    FAKE_PM_CHANNEL_NAME = '___private_message___'
    def __init__(self, responder=None):
        assert isinstance(responder, Responder)
        self.responder = responder

        self.slack = slacker.Slacker(SLACK_TOKEN)
        self.socket = SlackSocket(SLACK_TOKEN, translate=False)
        self.user_id_to_user_name = {}
        self.chan_id_to_chan_name = {}
        self.user_id_to_im_chan_id = {}
        self._update_cache()

    def _update_cache(self):
        self.user_id_to_user_name = {
            u['id']:u['name']
            for u in self.slack.users.list().body['members']}
        self.chan_id_to_chan_name = {
            c['id']:c['name']
            for c in self.slack.channels.list().body['channels']}
        self.chan_id_to_chan_name.update({
            g['id']:g['name']
            for g in self.slack.groups.list().body['groups']})
        self.user_id_to_im_chan_id = {
            i['user']:i['id']
            for i in self.slack.im.list().body['ims']}

    def _parse_message(self, e):
        if e.type not in ('message',) or e.event['user'] == BOT_USER_ID:
            return None

        # Extract and preprocess text.
        text = e.event['text']
        for u_id, u_name in self.user_id_to_user_name.iteritems():
            text = text.replace("<@{}>".format(u_id),
                                "@{}".format(u_name))
        tokenized = self.TOKENIZER_RE.findall(text.lower())

        # Extract user and channel names from cache.
        u_name = self._get_user_name(e.event['user'])
        chan_name, is_im = self._get_channel_name_and_type(e.event['channel'])

        # if we aren't called out by name and this isn't a direct message to us,
        # we absolutely do not care
        if '@{}'.format(BOT_USER_NAME) not in tokenized and not is_im: return

        print (text, u_name, chan_name, is_im)

        return Message(
            text=text,                  tokenized=tokenized,
            user_id=e.event['user'],    user_name=u_name,
            chan_id=e.event['channel'], chan_name=chan_name,
            im=is_im)

    def _get_channel_name_and_type(self, chan_id):
        # Check channel_id and im_channel caches.
        for loop in (True, False):
            if chan_id in self.chan_id_to_chan_name:
                return self.chan_id_to_chan_name[chan_id], False
            elif chan_id in self.user_id_to_im_chan_id.values():
                return self.FAKE_PM_CHANNEL_NAME, True
            if loop: self._update_cache()
        raise ValueError, "Could not find channel_id."

    def _get_user_name(self, user_id):
        for loop in (True, False):
            if user_id in self.user_id_to_user_name:
                return self.user_id_to_user_name[user_id]
            if loop: self._update_cache()
        raise ValueError, "Could not find user id."

    def _send_response(self, response):
        assert isinstance(response, Response)

        message = response.text.replace("@channel", "<!channel|@channel>")
        if not message: return
        channel = response.chan_id
        if not channel and response.im:
            if not response.im in self.user_id_to_im_chan_id:
                self._update_cache()
            channel = self.user_id_to_im_chan_id[response.im]
        if not channel:
            raise StandardError, "No channel, explicit or actual, in response {}".format(response)
        self.slack.chat.post_message(channel, message, as_user=True)

    def listen(self):
        print "Happy birthday!"
        for e in self.socket.events():
            # Convert socket event to Message object, if possible.
            try:
                msg = self._parse_message(e)
            except StandardError, err:
                print err
                continue
            if not msg:
                continue

            responses = self.responder.respond_to_message(msg)
            if not responses: continue
            if not isinstance(responses, (set, list, tuple)):
                responses = [responses]
            for response in responses:
                try:
                    self._send_response(response)
                except StandardError, err:
                    print err
                    continue
Esempio n. 5
0
class SimpleSlackBot:
    """Simplifies interacting with the Slack API. Allows users to register functions to specific events, get those
    functions called when those specific events are triggered and run their business code
    """
    def __init__(self, debug=False):
        """Initializes our Slack bot and slack bot token. Will exit if the required environment
        variable is not set.
        """

        self._SLACK_BOT_TOKEN = os.environ.get("SLACK_BOT_TOKEN")
        if self._SLACK_BOT_TOKEN is None:
            sys.exit("ERROR: environment variable SLACK_BOT_TOKEN is not set")

        self._slacker = Slacker(self._SLACK_BOT_TOKEN)
        self._slackSocket = SlackSocket(self._SLACK_BOT_TOKEN, translate=False)
        self._BOT_ID = self._slacker.auth.test().body["user_id"]
        self._registrations = {
        }  # our dictionary of event_types to a list of callbacks

        if debug:
            print("DEBUG!")
            logger.removeHandler(null_handler)
            logger.addHandler(StreamHandler())
            logger.setLevel(logging.DEBUG)

        logger.info(
            f"set bot id to {self._BOT_ID} with name {self.helper_user_id_to_user_name(self._BOT_ID)}"
        )
        logger.info("initialized")

    def register(self, event_type):
        """Registers a callback function to a a event type. All supported even types are defined here
        https://api.slack.com/events-api
        """
        def function_wrapper(callback):
            logger.info(
                f"registering callback {callback.__name__} to event type {event_type}"
            )

            if event_type not in self._registrations:
                self._registrations[event_type] = []  # create an empty list
            self._registrations[event_type].append(callback)

        return function_wrapper

    def route_request_to_callbacks(self, request):
        """Routes the request to the correct notify
        """

        logger.info(
            f"received an event of type {request.type} and slack event {request._slack_event.event}"
        )

        if request.type in self._registrations:
            for callback in self._registrations[request.type]:
                callback(request)

    def listen(self):
        """Listens forever for Slack events, triggering appropriately callbacks when respective events are received
        """

        READ_WEBSOCKET_DELAY = 1  # 1 second delay between reading from firehose

        logger.info("began listening!")

        for slack_event in self._slackSocket.events():
            if slack_event:
                if slack_event.event and "bot_id" not in slack_event.event:  # We don't reply to bots
                    request = SlackRequest(self._slacker, slack_event)
                    self.route_request_to_callbacks(request)

            time.sleep(READ_WEBSOCKET_DELAY)

        logger.info("Keyboard interrupt received. Gracefully shutting down")
        sys.exit(0)

    def start(self):
        """Connect the Slack bot to the chatroom and begin listening
        """

        ok = self._slacker.rtm.start().body["ok"]

        if ok:
            logger.info("started!")
            self.listen()
        else:
            logger.error(
                "Connection failed. Are you connected to the internet? Potentially invalid Slack token? "
                "Check environment variable and \"SLACK_BOT_TOKEN\"")

    def get_slacker(self):
        """Returns SimpleSlackBot's SlackClient.

        This is useful if you are writing a more advanced bot and want complete access to all SlackClient has to offer.
        """

        return self._slacker

    def get_slack_socket(self):
        """Returns SimpleSlackBot's SlackSocket.

        This is useful if you are writing a more advanced bot and want complete access to all SlackSocket has to offer.
        """

        return self._slackSocket

    def helper_get_public_channel_ids(self):
        """Helper function that gets all public channel ids
        """

        public_channel_ids = []

        public_channels = self._slacker.channels.list().body["channels"]
        for channel in public_channels:
            public_channel_ids.append(channel["id"])

        if len(public_channel_ids) == 0:
            logger.warning("got no public channel ids")
        else:
            logger.debug(f"got public channel ids {public_channel_ids}")

        return public_channel_ids

    def helper_get_private_channel_ids(self):
        """Helper function that gets all private channel ids
        """

        private_channel_ids = []

        private_channels = self._slacker.groups.list().body["groups"]

        for private_channel in private_channels:
            private_channels.append(private_channel["id"])

        if len(private_channel_ids) == 0:
            logger.warning("got no private channel ids")
        else:
            logger.debug(f"got private channel ids {private_channel_ids}")

        return private_channel_ids

    def helper_get_user_ids(self):
        """Helper function that gets all user ids
        """

        user_ids = []

        users = self._slacker.users.list().body["members"]
        for user in users:
            user_ids.append(user["id"])

        if len(user_ids) == 0:
            logger.warning("got no user ids")
        else:
            logger.debug(f"got user ids {user_ids}")

        return user_ids

    def helper_get_user_names(self):
        """Helper function that gets all user names
        """

        user_names = []

        users = self._slacker.users.list().body["members"]
        for user in users:
            user_names.append(user["name"])

        if len(user_names) == 0:
            logger.warning("got no user names")
        else:
            logger.debug(f"got user names {user_names}")

        return user_names

    def helper_get_users_in_channel(self, channel_id):
        """Helper function that gets all users in a given channel id
        """

        user_ids = []

        channels_list = self._slacker.channels.list().body["channels"]
        for channel in channels_list["channels"]:
            if channel["id"] == channel_id:
                for user_id in channel["members"]:
                    user_ids.append(user_id)

        if len(user_ids) == 0:
            logger.warning(f"got no user ids for channel {channel_id}")
        else:
            logger.debug(f"got user ids {user_ids}")

        return user_ids

    def helper_public_channel_name_to_channel_id(self, name):
        """Helper function that converts a channel name to its respected channel id
        """

        channels_list = self._slacker.channels.list().body["channels"]

        for channel in channels_list["channels"]:
            if channel["name"] == name:
                logger.debug(f"converted {channel['name']} to {channel['id']}")
                return channel["id"]

        logger.warning(f"could not convert channel name {name} to an id")

    def helper_private_channel_name_to_channel_id(self, name):
        """Helper function that converts a channel name to its respected channel id
        """
        channels_list = self._slacker.groups.list().body["groups"]
        logger.info(str(channels_list))

        for channel in channels_list["groups"]:
            if channel["name"] == name:
                logger.debug(f"converted {channel['name']} to {channel['id']}")
                return channel["id"]

        logger.warning(f"could not convert channel name {name} to an id")

    def helper_user_name_to_user_id(self, name):
        """Helper function that converts a user name to its respected user id
        """

        users = self._slacker.users.list().body["members"]

        for user in users:
            if user["name"] == name:
                logger.debug(f"converted {name} to {user['id']}")
                return user["id"]

        logger.warning(f"could not convert user name {name} to a user id")

    def helper_channel_id_to_channel_name(self, channel_id):
        """Helper function that converts a channel id to its respected channel name
        """

        channels_list = self._slacker.channels.list().body["channels"]

        for channel in channels_list["channels"]:
            if channel["id"] == channel_id:
                logger.debug("converted {} to {}".format(
                    channel_id, channel["name"]))
                return channel["name"]

        logger.warning(f"could not convert channel id {channel_id} to a name")

    def helper_user_id_to_user_name(self, user_id):
        """Helper function that converts a user id to its respected user name
        """

        users_list = self._slacker.users.list()

        for user in users_list.body["members"]:
            if user["id"] == user_id:
                logger.debug(f"converted {user_id} to {user['name']}")
                return user["name"]

        logger.warning(f"could not convert user id {user_id} to a name")

    def helper_user_id_to_user_real_name(self, user_id):
        """Helper function that converts a user id to its respected user name
        """

        users_list = self._slacker.users.list()

        for user in users_list.body["members"]:
            if user["id"] == user_id:
                logger.debug(f"converted {user_id} to {user['real_name']}")
                return user["real_name"]

        logger.warning(f"could not convert user id {user_id} to a name")

    def helper_user_id_to_tz_offset(self, user_id):
        """Helper function that converts a user id to its respected time zone offset
        """

        users_list = self._slacker.users.list()

        for user in users_list.body["members"]:
            if user["id"] == user_id:
                logger.debug(f"converted {user_id} to {user['tz_offset']}")
                return int(user["tz_offset"])

        logger.warning(
            f"could not get time zone offset from user id {user_id}")
        return 0

    def helper_user_id_to_tz_label(self, user_id):
        """Helper function that converts a user id to its respected time zone
        """

        users_list = self._slacker.users.list()

        for user in users_list.body["members"]:
            if user["id"] == user_id:
                logger.debug(f"converted {user_id} to {user['tz']}")
                return user["tz"]

        logger.warning(f"could not get time zone from user id {user_id}")
        return "Unknown"
class SimpleSlackBot:
    """Simplifies interacting with the Slack API. Allows users to register functions to specific events, get those
    functions called when those specific events are triggered and run their business code
    """

    KEYBOARD_INTERRUPT_EXCEPTION_LOG_MESSAGE = "KeyboardInterrupt exception caught."
    SYSTEM_INTERRUPT_EXCEPTION_LOG_MESSAGE = "SystemExit exception caught."

    @staticmethod
    def peek(iterable):
        """Allows us to look at the next yield in an Iterable.
        From: https://stackoverflow.com/a/664239/1983957

        :param iterable: some Iterable to peek at
        :return: the first and rest of the yielded items
        """

        try:
            first = next(iterable)
        except StopIteration:
            return None
        return first, itertools.chain([first], iterable)

    @staticmethod
    def log_gracefully_shutdown(prefix_str):
        """Just a convenient way to log multiple messages in a similar way

        :param prefix_str: String to log in the begging
        :return: None
        """
        logger.info(f"{prefix_str} Gracefully shutting down")

    def __init__(self, debug=False):
        """Initializes our Slack bot and slack bot token. Will exit if the required environment
        variable is not set.

        :param debug: Whether or not to use default a Logging config
        """

        self._SLACK_BOT_TOKEN = os.environ.get("SLACK_BOT_TOKEN")
        if self._SLACK_BOT_TOKEN is None:
            sys.exit("ERROR: environment variable SLACK_BOT_TOKEN is not set")

        self._slacker = Slacker(self._SLACK_BOT_TOKEN)
        self._slackSocket = SlackSocket(self._SLACK_BOT_TOKEN, translate=False)
        self._BOT_ID = self._slacker.auth.test().body["user_id"]
        self._registrations = {
        }  # our dictionary of event_types to a list of callbacks

        if debug:
            # Enable logging for our users
            logger.addHandler(StreamHandler())
            logger.setLevel(logging.DEBUG)

        logger.info(
            f"set bot id to {self._BOT_ID} with name {self.helper_user_id_to_user_name(self._BOT_ID)}"
        )
        logger.info("initialized")

    def register(self, event_type):
        """Registers a callback function to a a event type. All supported even types are defined here
        https://api.slack.com/events-api

        :param event_type: the type of the event to register
        :return: reference to wrapped function
        """
        def function_wrapper(callback):
            """Registers event before executing wrapped function, referred to as callback

            :param callback: function to execute after runnign wrapped code
            :return: None
            """

            logger.info(
                f"registering callback {callback.__name__} to event type {event_type}"
            )

            if event_type not in self._registrations:
                self._registrations[event_type] = []  # create an empty list
            self._registrations[event_type].append(callback)

        return function_wrapper

    def route_request_to_callbacks(self, request):
        """Routes the request to the correct notify

        :param request: request to be routed
        :return: None
        """

        logger.info(
            f"received an event of type {request.type} and slack event {request._slack_event.event}"
        )

        if request.type in self._registrations:
            for callback in self._registrations[request.type]:
                try:
                    callback(request)
                except Exception as ex:
                    logger.exception(
                        f'exception processing event {request.type}')

    def listen(self):
        """Listens forever for Slack events, triggering appropriately callbacks when respective events are received.
        Catches and logs all Exceptions except for KeyboardInterrupt or SystemExit, which gracefully shuts down program.

        The following function is crucial to Simple Slack Bot and looks a little messy. This is do partly to the way
        that our dependency SlackSocket is written. They do not re-raise any caught KeyboardInterrupt exceptions and
        instead we have to infer one was caught based on what their generator returns. This is incredibly unfortunate,
        but this currently works. Since most of Simple Slack Bot's time is spent blocked on waiting for events from
        SlackSocket, a solution was needed to deal with this. Otherwise our application would not respond to a request
        from the user to stop the program with a CTRL + C.
        """

        READ_WEBSOCKET_DELAY = 1  # 1 second delay between reading from fire hose
        running = True

        logger.info("began listening!")

        while running:  # required to continue to run after experiencing an unexpected exception
            res = self.peek(self._slackSocket.events())
            if res is None:
                self.log_gracefully_shutdown(
                    self.KEYBOARD_INTERRUPT_EXCEPTION_LOG_MESSAGE)
                running = False
                break
            else:
                slack_event, mysequence = res

                if slack_event.event and "bot_id" not in slack_event.event:  # We don't reply to bots
                    try:
                        request = SlackRequest(self._slacker, slack_event)
                        self.route_request_to_callbacks(request)

                        time.sleep(READ_WEBSOCKET_DELAY)
                    except KeyboardInterrupt:
                        self.log_gracefully_shutdown(
                            self.KEYBOARD_INTERRUPT_EXCEPTION_LOG_MESSAGE)
                        running = False
                        break
                    except SystemExit:
                        self.log_gracefully_shutdown(
                            self.SYSTEM_INTERRUPT_EXCEPTION_LOG_MESSAGE)
                        running = False
                        break
                    except Exception as e:
                        logging.warning(
                            f"Unexpected exception caught, but we will keep listening. Exception: {e}"
                        )
                        logging.warning(traceback.format_stack())
                        continue  # ensuring the loop continues

        logger.info("stopped listening!")

    def start(self):
        """Connect the Slack bot to the chatroom and begin listening
        """

        ok = self._slacker.rtm.start().body["ok"]

        if ok:
            logger.info("started!")
            self.listen()
        else:
            logger.error(
                "Connection failed. Are you connected to the internet? Potentially invalid Slack token? "
                "Check environment variable and \"SLACK_BOT_TOKEN\"")

        logger.info("stopped!")

    def get_slacker(self):
        """Returns SimpleSlackBot's SlackClient.

        This is useful if you are writing a more advanced bot and want complete access to all SlackClient has to offer.
        """

        return self._slacker

    def get_slack_socket(self):
        """Returns SimpleSlackBot's SlackSocket.

        This is useful if you are writing a more advanced bot and want complete access to all SlackSocket has to offer.
        """

        return self._slackSocket

    def helper_get_public_channel_ids(self):
        """Helper function that gets all public channel ids
        """

        public_channel_ids = []

        public_channels = self._slacker.channels.list().body["channels"]
        for channel in public_channels:
            public_channel_ids.append(channel["id"])

        if len(public_channel_ids) == 0:
            logger.warning("got no public channel ids")
        else:
            logger.debug(f"got public channel ids {public_channel_ids}")

        return public_channel_ids

    def helper_get_private_channel_ids(self):
        """Helper function that gets all private channel ids
        """

        private_channel_ids = []

        private_channels = self._slacker.groups.list().body["groups"]

        for private_channel in private_channels:
            private_channels.append(private_channel["id"])

        if len(private_channel_ids) == 0:
            logger.warning("got no private channel ids")
        else:
            logger.debug(f"got private channel ids {private_channel_ids}")

        return private_channel_ids

    def helper_get_user_ids(self):
        """Helper function that gets all user ids
        """

        user_ids = []

        users = self._slacker.users.list().body["members"]
        for user in users:
            user_ids.append(user["id"])

        if len(user_ids) == 0:
            logger.warning("got no user ids")
        else:
            logger.debug(f"got user ids {user_ids}")

        return user_ids

    def helper_get_user_names(self):
        """Helper function that gets all user names
        """

        user_names = []

        users = self._slacker.users.list().body["members"]
        for user in users:
            user_names.append(user["name"])

        if len(user_names) == 0:
            logger.warning("got no user names")
        else:
            logger.debug(f"got user names {user_names}")

        return user_names

    def helper_get_users_in_channel(self, channel_id):
        """Helper function that gets all users in a given channel id

        :param channel_id: channel id to get all user ids in it
        :return: list of user ids
        """

        user_ids = []

        channels_list = self._slacker.channels.list().body["channels"]
        for channel in channels_list:
            if channel["id"] == channel_id:
                for user_id in channel["members"]:
                    user_ids.append(user_id)

        if len(user_ids) == 0:
            logger.warning(f"got no user ids for channel {channel_id}")
        else:
            logger.debug(f"got user ids {user_ids}")

        return user_ids

    def helper_channel_name_to_channel_id(self, name):
        """Helper function that converts a channel name to its respected channel id

        :param name: name of channel to convert to id
        :return: id representation of original channel name
        """

        channels_list = self._slacker.channels.list().body["channels"]

        for channel in channels_list:
            if channel["name"] == name:
                logger.debug(f"converted {channel['name']} to {channel['id']}")
                return channel["id"]

        logger.warning(f"could not convert channel name {name} to an id")

    def helper_user_name_to_user_id(self, name):
        """Helper function that converts a user name to its respected user id

        :param name: name of user to convert to id
        :return: id representation of original user name
        """

        users = self._slacker.users.list().body["members"]

        for user in users:
            if user["name"] == name:
                logger.debug(f"converted {name} to {user['id']}")
                return user["id"]

        logger.warning(f"could not convert user name {name} to a user id")

    def helper_channel_id_to_channel_name(self, channel_id):
        """Helper function that converts a channel id to its respected channel name

        :param channel_id: id of channel to convert to name
        :return: name representation of original channel id
        """

        channels_list = self._slacker.channels.list().body["channels"]

        for channel in channels_list:
            if channel["id"] == channel_id:
                logger.debug("converted {} to {}".format(
                    channel_id, channel["name"]))
                return channel["name"]

        logger.warning(f"could not convert channel id {channel_id} to a name")

    def helper_user_id_to_user_name(self, user_id):
        """Helper function that converts a user id to its respected user name

        :param user_id: id of user to convert to name
        :return: name representation of original user id
        """

        users_list = self._slacker.users.list()

        for user in users_list.body["members"]:
            if user["id"] == user_id:
                logger.debug(f"converted {user_id} to {user['name']}")
                return user["name"]

        logger.warning(f"could not convert user id {user_id} to a name")
Esempio n. 7
0
        s = SlackSocket(slackToken)
    except:
        import sys
        import tools.shenanigans
        if shenanigansState:
            tools.shenanigans.enable()
            tools.shenanigans.reboot()
        else:
            print "That token was invalid!"
            sys.exit(2)

    setPresence('auto')

    s.send_msg("I'm combat ready!", debugChannel)

    for event in s.events():

        eventString = str(event.json)

        if (eventString == '{}'):
            reboot()

        if (debugMode):
            s.send_msg(eventString, debugChannel)
            print(eventString)

        if shenanigansState and('_left' in eventString):
            import json
            import tools.shenanigans
            eventParsed = json.loads(eventString)
            channel = eventParsed['channel'].strip()
Esempio n. 8
0
class GameMaster(object):
    """
    Manages user sessions.
    """
    def __init__(self, config):
        #  Slack config
        slack_api_token = config.get('slack', 'api_token')
        self._slack_username = config.get('slack', 'bot_username')
        self._slack = SlackSocket(slack_api_token, translate=True)
        self._slack_events = self._slack.events()
        self._slack_sessions = {}
        self._slack_event_handler = Thread(target=self._handle_slack_events,
                                           name='event_handler')

        #  Frotz config
        self._frotz_binary = config.get('frotz', 'path')
        self._frotz_story_file = config.get('frotz', 'story')

        #  Logging config
        self._logs_dir = config.get('frotzlack', 'logs_dir')
        error_log_path = os.path.join(self._logs_dir, 'frotzlack.log')
        self._global_handler = RotatingFileHandler(error_log_path)
        self._global_handler.setLevel(logging.WARNING)

        #  Other config
        self._admins = config.get('frotzlack', 'admins').split(',')

        self._stop_requested = False
        self._slack_event_handler.start()

    def _event_is_game_input(self, event):
        event_attrs = event.event
        is_game_input = 'type' in event_attrs.keys() and \
            event_attrs['type'] == 'message' and \
            event_attrs['user'] in self._slack_sessions and \
            event_attrs['user'] != self._slack_username and \
            self._slack_username not in event.mentions and \
            event_attrs['channel'] == event_attrs['user']
        return is_game_input

    def _event_is_command(self, event):
        event_attrs = event.event
        return 'type' in event_attrs.keys() and \
               event_attrs['type'] == 'message' and \
               self._slack_username in event.mentions

    def _handle_game_input(self, user, game_input):
        session = self._slack_sessions[user]
        if game_input.strip() == 'save' or game_input.strip() == 'load':
            session.send("Sorry, I can't save or load games yet.")
        elif game_input.strip() == 'quit':
            session.send("Sorry, I can't quit the game yet.")
        else:
            session.put(game_input)

    def _start_session(self, username):
        def send_msg(msg):
            self._slack.send_msg(msg, channel_id=channel_id, confirm=False)

        channel_id = self._slack.get_im_channel(username)['id']

        session_logger = logging.getLogger(username)
        session_logger.setLevel(logging.INFO)
        session_log_path = os.path.join(self._logs_dir, username + '.log')
        session_handler = RotatingFileHandler(session_log_path)
        session_handler.setLevel(logging.INFO)
        session_logger.addHandler(session_handler)
        session_logger.addHandler(self._global_handler)

        slack_session = SlackSession(send_msg, channel_id)
        frotz_session = FrotzSession(self._frotz_binary,
                                     self._frotz_story_file, session_logger)
        self._slack_sessions[username] = slack_session
        Session(slack_session, frotz_session)

    def _reject_command(self, username, command):
        channel = self._slack.get_im_channel(username)
        message = "Sorry, I don't recognize the command `{0}`"
        self._slack.send_msg(message.format(command), channel_id=channel['id'])

    def _stop_server(self):
        pool = ThreadPool()
        for session in self._slack_sessions.values():

            def polite_stop():
                session.send("Sorry, the server is shutting down now.")
                session.kill()

            pool.apply_async(polite_stop)
        pool.close()
        pool.join()
        self._stop_requested = True

    def _handle_slack_events(self):
        while not self._stop_requested:
            event = self._slack_events.next()
            event_attrs = event.event
            if self._event_is_game_input(event):
                msg = event_attrs['text']
                self._handle_game_input(event_attrs['user'], msg)
            elif self._event_is_command(event):
                command = event_attrs['text']
                user = event_attrs['user']
                if 'stop' in command and user in self._admins:
                    self._stop_server()
                elif 'play' in command:
                    self._start_session(user)
                else:
                    self._reject_command(event_attrs['user'], command)
class SimpleSlackBot:
    """Simplifie interacting with the Slack API.

    Allows users to register functions to specific events, get those functions
    called when those specific events are triggered and run their business code
    """

    KEYBOARD_INTERRUPT_EXCEPTION_LOG_MESSAGE = "KeyboardInterrupt exception caught."
    SYSTEM_INTERRUPT_EXCEPTION_LOG_MESSAGE = "SystemExit exception caught."

    @staticmethod
    def peek(
        iterator: typing.Iterator,
    ) -> typing.Union[None, typing.Tuple[typing.Any, typing.Iterator]]:
        """Allow us to look at the next yield in an Iterator.

        From: https://stackoverflow.com/a/664239/1983957

        :param iterator: some Iterator to peek at
        :return: the first and rest of the yielded items
        """

        try:
            first = next(iterator)
        except StopIteration:
            return None
        return first, itertools.chain([first], iterator)

    def __init__(self, slack_bot_token: str = None, debug: bool = False):
        """Initialize our Slack bot and slack bot token.

        Will exit if the required environment variable is not set.

        :param slack_bot_token: The token given by Slack for API authentication
        :param debug: Whether or not to use default a Logging config
        """

        # fetch a slack_bot_token first checking params, then environment variable otherwise
        # raising a SystemExit exception as this is required for execution
        if slack_bot_token is None:
            self._slack_bot_token = os.environ.get("SLACK_BOT_TOKEN")
        else:
            self._slack_bot_token = slack_bot_token
        if self._slack_bot_token is None or self._slack_bot_token == "":
            sys.exit(
                "ERROR: SLACK_BOT_TOKEN not passed to constructor or set as environment variable"
            )

        if debug:
            # enable logging additional debug logging
            logger.addHandler(StreamHandler())
            logger.setLevel(logging.DEBUG)

        logger.info("initialized. Ready to connect")

    def connect(self):
        """Connect to underlying SlackSocket.

        Additionally stores conections for future usage.
        """
        # Disable all the attribute-defined-out-init in this function
        # pylint: disable=attribute-defined-outside-init

        logger.info("Connecting...")

        self._python_slackclient = WebClient(self._slack_bot_token)
        self._slack_socket = SlackSocket(self._slack_bot_token)
        self._bot_id = self._python_slackclient.auth_test()["bot_id"]

        logger.info(
            "Connected. Set bot id to %s with name %s",
            self._bot_id,
            self.helper_user_id_to_user_name(self._bot_id),
        )

    def register(self, event_type: str) -> typing.Callable[..., typing.Any]:
        """Register a callback function to a a event type.

        All supported even types are defined here https://api.slack.com/events-api

        :param event_type: the type of the event to register
        :return: reference to wrapped function
        """
        def function_wrapper(callback: typing.Callable):
            """Register event before executing wrapped function, referred to as callback.

            :param callback: function to execute after runnign wrapped code
            """
            # Disable all the attribute-defined-out-init in this function
            # pylint: disable=attribute-defined-outside-init

            # first time initialization
            if not hasattr(self, "_registrations"):
                self._registrations: typing.Dict[
                    str, typing.List[typing.Callable]] = {}

            if event_type not in self._registrations:
                # first registration of this type
                self._registrations[event_type] = []
            self._registrations[event_type].append(callback)

        return function_wrapper

    def route_request_to_callbacks(self, request: SlackRequest):
        """Route the request to the correct notify.

        :param request: request to be routed
        """
        logger.info(
            "received an event of type %s and slack event type of %s with content %s",
            request.type,
            request.slack_event.type,
            request,
        )

        # we ignore subtypes to ensure thread messages don't go to the channel as well, as two events are created
        # i'm totally confident this will have unexpected consequences but have not discovered any at the time of
        # writing this
        if request.type in self._registrations and request.subtype is None:
            for callback in self._registrations[request.type]:
                try:
                    callback(request)
                except Exception:  # pylint: disable=broad-except
                    logger.exception(
                        "exception processing event %s . Exception %s",
                        request.type,
                        traceback.format_exc(),
                    )

    def extract_slack_socket_response(self) -> typing.Union[SlackEvent, None]:
        """Extract a useable response from the underlying _slack_socket.

        Catch all SlackSocket exceptions except forExitError, treating those as warnings.
        """
        try:
            return self.peek(self._slack_socket.events())
        except (
                slacksocket.errors.APIError,
                slacksocket.errors.ConfigError,
                slacksocket.errors.APINameError,
                slacksocket.errors.ConnectionError,
                slacksocket.errors.TimeoutError,
        ):
            logging.warning(
                "Unexpected exception caught, but we will keep listening. Exception: %s",
                traceback.format_exc(),
            )
            return None  # ensuring the loop continues and execution ends

    def listen(self):
        """Listen forever for Slack events, triggering appropriately callbacks when respective events are received.

        Catches and logs all Exceptions except for KeyboardInterrupt or SystemExit, which gracefully shuts down program.

        The following function is crucial to Simple Slack Bot and looks a little messy. Since most of Simple Slack Bot's
        time is spent blocked on waiting for events from SlackSocket, a solution was needed to deal with this. Otherwise
        our application would not respond to a request from the user to stop the program with a CTRL + C.
        """

        read_websocket_delay = 1  # 1 second delay between reading from fire hose
        running = True

        logger.info("began listening!")

        # required to continue to run after experiencing an unexpected exception
        while running:
            response = None

            try:
                while response is None:
                    response = self.extract_slack_socket_response()
            except slacksocket.errors.ExitError:
                logging.info(self.KEYBOARD_INTERRUPT_EXCEPTION_LOG_MESSAGE)
                running = False
                break  # ensuring the loop stops and execution ceases

            slack_event, _ = response
            try:
                self.route_request_to_callbacks(
                    SlackRequest(self._python_slackclient, slack_event))

                time.sleep(read_websocket_delay)
            except Exception:  # pylint: disable=broad-except
                logging.warning(
                    "Unexpected exception caught, but we will keep listening. Exception: %s",
                    traceback.format_exc(),
                )
                continue  # ensuring the loop continues

    logger.info("stopped listening!")

    def start(self):
        """Connect the Slack bot to the chatroom and begin listening."""

        self.connect()
        ok_reponse = self._python_slackclient.rtm_start()

        if ok_reponse:
            logger.info("started!")
            self.listen()
        else:
            logger.error(
                "Connection failed. Are you connected to the internet? Potentially invalid Slack token? "
                'Check environment variable and "SLACK_BOT_TOKEN"')

        logger.info("stopped!")

    def helper_get_public_channel_ids(self) -> typing.List[str]:
        """Get all public channel ids.

        :return: list of public channel ids
        """

        public_channel_ids = []

        if self._python_slackclient and self._python_slackclient.channels_list(
        ):
            public_channels = self._python_slackclient.channels_list(
            )["channels"]
            for channel in public_channels:
                public_channel_ids.append(channel["id"])

            if len(public_channel_ids) == 0:
                logger.warning("got no public channel ids")
            else:
                logger.debug("got public channel ids %s", public_channel_ids)

        return public_channel_ids

    def helper_get_private_channel_ids(self) -> typing.List[str]:
        """Get all private channel ids.

        :return: list of private channel ids
        """

        private_channel_ids: typing.List[str] = []

        for private_channel in self._python_slackclient.groups_list(
        )["groups"]:
            private_channel_ids.append(private_channel["id"])

        if len(private_channel_ids) == 0:
            logger.warning("got no private channel ids")
        else:
            logger.debug("got private channel ids %s", private_channel_ids)

        return private_channel_ids

    def helper_get_user_ids(self) -> typing.List[str]:
        """Get all user ids.

        :return: list of user ids
        """

        user_ids = []

        for user in self._python_slackclient.users_list()["members"]:
            user_ids.append(user["id"])

        if len(user_ids) == 0:
            logger.warning("got no user ids")
        else:
            logger.debug("got user ids %s", user_ids)

        return user_ids

    def helper_get_user_names(self) -> typing.List[str]:
        """Get all user names.

        :return: list of user names
        """

        user_names = []

        for user in self._python_slackclient.users_list()["members"]:
            user_names.append(user["name"])

        if len(user_names) == 0:
            logger.warning("got no user names")
        else:
            logger.debug("got user names %s", user_names)

        return user_names

    def helper_get_users_in_channel(self, channel_id: str) -> typing.List[str]:
        """Get all users in a given channel id.

        :param channel_id: channel id to get all user ids in it
        :return: list of user ids
        """

        user_ids = []

        for channel in self._python_slackclient.channels_list()["channels"]:
            if channel["id"] == channel_id:
                for user_id in channel["members"]:
                    user_ids.append(user_id)

        if len(user_ids) == 0:
            logger.warning("got no user ids for channel %s", channel_id)
        else:
            logger.debug("got user ids %s", user_ids)

        return user_ids

    def helper_channel_name_to_channel_id(
            self, name: str) -> typing.Union[str, None]:
        """Convert a channel name to its respected channel id.

        :param name: name of channel to convert to id
        :return: id representation of original channel name
        """

        for channel in self._python_slackclient.channels_list()["channels"]:
            if channel["name"] == name:
                logger.debug("converted %s to %s", channel["name"],
                             channel["id"])
                return channel["id"]

        logger.warning("could not convert channel name %s to an id", name)
        return None

    def helper_user_name_to_user_id(self,
                                    name: str) -> typing.Union[str, None]:
        """Convert a user name to its respected user id.

        :param name: name of user to convert to id
        :return: id representation of original user name
        """

        for user in self._python_slackclient.users_list()["members"]:
            if user["name"] == name:
                logger.debug("converted %s to %s", name, user["id"])
                return user["id"]

        logger.warning("could not convert user name %s to a user id", name)
        return None

    def helper_channel_id_to_channel_name(
            self, channel_id: str) -> typing.Union[str, None]:
        """Convert a channel id to its respected channel name.

        :param channel_id: id of channel to convert to name
        :return: name representation of original channel id
        """

        for channel in self._python_slackclient.channels_list()["channels"]:
            if channel["id"] == channel_id:
                logger.debug("converted %s to %s", channel_id, channel["name"])
                return channel["name"]

        logger.warning("could not convert channel id %s to a name", channel_id)
        return None

    def helper_user_id_to_user_name(self,
                                    user_id: str) -> typing.Union[str, None]:
        """Convert a user id to its respected user name.

        :param user_id: id of user to convert to name
        :return: name representation of original user id
        """

        for user in self._python_slackclient.users_list()["members"]:
            if user["id"] == user_id:
                logger.debug("converted %s to %s", user_id, user["name"])
                return user["name"]

        logger.warning("could not convert user id %s to a name", user_id)
        return None
Esempio n. 10
0
class GameMaster(object):
    """
    Manages user sessions.
    """
    def __init__(self, config):
        #  Slack config
        slack_api_token = config.get('slack', 'api_token')
        self._slack_username = config.get('slack', 'bot_username')
        self._slack = SlackSocket(slack_api_token, translate=True)
        self._slack_events = self._slack.events()
        self._slack_sessions = {}
        self._slack_event_handler = Thread(target=self._handle_slack_events,
                                           name='event_handler')

        #  Frotz config
        self._frotz_binary = config.get('frotz', 'path')
        self._frotz_story_file = config.get('frotz', 'story')

        #  Logging config
        self._logs_dir = config.get('frotzlack', 'logs_dir')
        error_log_path = os.path.join(self._logs_dir, 'frotzlack.log')
        self._global_handler = RotatingFileHandler(error_log_path)
        self._global_handler.setLevel(logging.WARNING)

        #  Other config
        self._admins = config.get('frotzlack', 'admins').split(',')

        self._stop_requested = False
        self._slack_event_handler.start()

    def _event_is_game_input(self, event):
        event_attrs = event.event
        is_game_input = 'type' in event_attrs.keys() and \
            event_attrs['type'] == 'message' and \
            event_attrs['user'] in self._slack_sessions and \
            event_attrs['user'] != self._slack_username and \
            self._slack_username not in event.mentions and \
            event_attrs['channel'] == event_attrs['user']
        return is_game_input

    def _event_is_command(self, event):
        event_attrs = event.event
        return 'type' in event_attrs.keys() and \
               event_attrs['type'] == 'message' and \
               self._slack_username in event.mentions

    def _handle_game_input(self, user, game_input):
        session = self._slack_sessions[user]
        if game_input.strip() == 'save' or game_input.strip() == 'load':
            session.send("Sorry, I can't save or load games yet.")
        elif game_input.strip() == 'quit':
            session.send("Sorry, I can't quit the game yet.")
        else:
            session.put(game_input)

    def _start_session(self, username):
        def send_msg(msg):
            self._slack.send_msg(msg, channel_id=channel_id, confirm=False)

        channel_id = self._slack.get_im_channel(username)['id']

        session_logger = logging.getLogger(username)
        session_logger.setLevel(logging.INFO)
        session_log_path = os.path.join(self._logs_dir, username + '.log')
        session_handler = RotatingFileHandler(session_log_path)
        session_handler.setLevel(logging.INFO)
        session_logger.addHandler(session_handler)
        session_logger.addHandler(self._global_handler)

        slack_session = SlackSession(send_msg, channel_id)
        frotz_session = FrotzSession(self._frotz_binary,
                                     self._frotz_story_file,
                                     session_logger)
        self._slack_sessions[username] = slack_session
        Session(slack_session, frotz_session)

    def _reject_command(self, username, command):
        channel = self._slack.get_im_channel(username)
        message = "Sorry, I don't recognize the command `{0}`"
        self._slack.send_msg(message.format(command), channel_id=channel['id'])

    def _stop_server(self):
        pool = ThreadPool()
        for session in self._slack_sessions.values():
            def polite_stop():
                session.send("Sorry, the server is shutting down now.")
                session.kill()
            pool.apply_async(polite_stop)
        pool.close()
        pool.join()
        self._stop_requested = True

    def _handle_slack_events(self):
        while not self._stop_requested:
            event = self._slack_events.next()
            event_attrs = event.event
            if self._event_is_game_input(event):
                msg = event_attrs['text']
                self._handle_game_input(event_attrs['user'], msg)
            elif self._event_is_command(event):
                command = event_attrs['text']
                user = event_attrs['user']
                if 'stop' in command and user in self._admins:
                    self._stop_server()
                elif 'play' in command:
                    self._start_session(user)
                else:
                    self._reject_command(event_attrs['user'], command)
class SimpleSlackBot:
    """Simplifies interacting with the Slack API. Allows users to register functions to specific events, get those
    functions called when those specific events are triggered and run their business code
    """

    def __init__(self, debug=False):
        """Initializes our Slack bot and slack bot token. Will exit if the required environment
        variable is not set.
        """

        self._SLACK_BOT_TOKEN = os.environ.get("SLACK_BOT_TOKEN")
        if self._SLACK_BOT_TOKEN is None:
            sys.exit("ERROR: environment variable SLACK_BOT_TOKEN is not set")

        self._slacker = Slacker(self._SLACK_BOT_TOKEN)
        self._slackSocket = SlackSocket(self._SLACK_BOT_TOKEN, translate=False)
        self._BOT_ID = self._slacker.auth.test().body["user_id"]
        self._registrations = {}  # our dictionary of event_types to a list of callbacks

        if debug:
            print("DEBUG!")
            logger.removeHandler(null_handler)
            logger.addHandler(StreamHandler())
            logger.setLevel(logging.DEBUG)

        logger.info(f"set bot id to {self._BOT_ID} with name {self.helper_user_id_to_user_name(self._BOT_ID)}")
        logger.info("initialized")

    def register(self, event_type):
        """Registers a callback function to a a event type. All supported even types are defined here
        https://api.slack.com/events-api
        """

        def function_wrapper(callback):
            logger.info(f"registering callback {callback.__name__} to event type {event_type}")

            if event_type not in self._registrations:
                self._registrations[event_type] = []  # create an empty list
            self._registrations[event_type].append(callback)

        return function_wrapper

    def route_request_to_callbacks(self, request):
        """Routes the request to the correct notify
        """

        logger.info(f"received an event of type {request.type} and slack event {request._slack_event.event}")

        if request.type in self._registrations:
            for callback in self._registrations[request.type]:
                callback(request)

    def listen(self):
        """Listens forever for Slack events, triggering appropriately callbacks when respective events are received
        """

        READ_WEBSOCKET_DELAY = 1  # 1 second delay between reading from firehose

        logger.info("began listening!")

        for slack_event in self._slackSocket.events():
            if slack_event:
                if slack_event.event and "bot_id" not in slack_event.event:  # We don't reply to bots
                    request = SlackRequest(self._slacker, slack_event)
                    self.route_request_to_callbacks(request)

            time.sleep(READ_WEBSOCKET_DELAY)

        logger.info("Keyboard interrupt received. Gracefully shutting down")
        sys.exit(0)

    def start(self):
        """Connect the Slack bot to the chatroom and begin listening
        """

        ok = self._slacker.rtm.start().body["ok"]

        if ok:
            logger.info("started!")
            self.listen()
        else:
            logger.error("Connection failed. Are you connected to the internet? Potentially invalid Slack token? "
                               "Check environment variable and \"SLACK_BOT_TOKEN\"")

    def get_slacker(self):
        """Returns SimpleSlackBot's SlackClient.

        This is useful if you are writing a more advanced bot and want complete access to all SlackClient has to offer.
        """

        return self._slacker

    def get_slack_socket(self):
        """Returns SimpleSlackBot's SlackSocket.

        This is useful if you are writing a more advanced bot and want complete access to all SlackSocket has to offer.
        """

        return self._slackSocket

    def helper_get_public_channel_ids(self):
        """Helper function that gets all public channel ids
        """

        public_channel_ids = []

        public_channels = self._slacker.channels.list().body["channels"]
        for channel in public_channels:
            public_channel_ids.append(channel["id"])

        if len(public_channel_ids) == 0:
            logger.warning("got no public channel ids")
        else:
            logger.debug(f"got public channel ids {public_channel_ids}")

        return public_channel_ids

    def helper_get_private_channel_ids(self):
        """Helper function that gets all private channel ids
        """

        private_channel_ids = []

        private_channels = self._slacker.groups.list().body["groups"]

        for private_channel in private_channels:
            private_channels.append(private_channel["id"])

        if len(private_channel_ids) == 0:
            logger.warning("got no private channel ids")
        else:
            logger.debug(f"got private channel ids {private_channel_ids}")

        return private_channel_ids

    def helper_get_user_ids(self):
        """Helper function that gets all user ids
        """

        user_ids = []

        users = self._slacker.users.list().body["members"]
        for user in users:
            user_ids.append(user["id"])

        if len(user_ids) == 0:
            logger.warning("got no user ids")
        else:
            logger.debug(f"got user ids {user_ids}")

        return user_ids

    def helper_get_user_names(self):
        """Helper function that gets all user names
        """

        user_names = []

        users = self._slacker.users.list().body["members"]
        for user in users:
            user_names.append(user["name"])

        if len(user_names) == 0:
            logger.warning("got no user names")
        else:
            logger.debug(f"got user names {user_names}")

        return user_names

    def helper_get_users_in_channel(self, channel_id):
        """Helper function that gets all users in a given channel id
        """

        user_ids = []

        channels_list = self._slacker.channels.list().body["channels"]
        for channel in channels_list["channels"]:
            if channel["id"] == channel_id:
                for user_id in channel["members"]:
                    user_ids.append(user_id)

        if len(user_ids) == 0:
            logger.warning(f"got no user ids for channel {channel_id}")
        else:
            logger.debug(f"got user ids {user_ids}")

        return user_ids

    def helper_channel_name_to_channel_id(self, name):
        """Helpfer function that converts a channel name to its respected channel id
        """

        channels_list = self._slacker.channels.list().body["channels"]

        for channel in channels_list["channels"]:
            if channel["name"] == name:
                logger.debug(f"converted {channel['name']} to {channel['id']}")
                return channel["id"]

        logger.warning(f"could not convert channel name {name} to an id")

    def helper_user_name_to_user_id(self, name):
        """Helper function that converts a user name to its respected user id
        """

        users = self._slacker.users.list().body["members"]

        for user in users:
            if user["name"] == name:
                logger.debug(f"converted {name} to {user['id']}")
                return user["id"]

        logger.warning(f"could not convert user name {name} to a user id")

    def helper_channel_id_to_channel_name(self, channel_id):
        """Helper function that converts a channel id to its respected channel name
        """

        channels_list = self._slacker.channels.list().body["channels"]

        for channel in channels_list["channels"]:
            if channel["id"] == channel_id:
                logger.debug("converted {} to {}".format(channel_id, channel["name"]))
                return channel["name"]

        logger.warning(f"could not convert channel id {channel_id} to a name")

    def helper_user_id_to_user_name(self, user_id):
        """Helper function that converts a user id to its respected user name
        """

        users_list = self._slacker.users.list()

        for user in users_list.body["members"]:
            if user["id"] == user_id:
                logger.debug(f"converted {user_id} to {user['name']}")
                return user["name"]

        logger.warning(f"could not convert user id {user_id} to a name")
Esempio n. 12
0
File: joy.py Progetto: canzhiye/joy
def start_joy(team_id, bot_id):
    SLACK_TOKEN = ''
    with open('tokens.pickle', 'rb') as f:
        tokens = pickle.load(f)
        SLACK_TOKEN = tokens[team_id][0]

    slack_socket = SlackSocket(SLACK_TOKEN, translate=True)
    slack = Slacker(SLACK_TOKEN)

    response = slack.channels.list()
    channels = {}
    for c in [u for u in response.body['channels']]:
        name = c['name']
        user_id = c['id']

        channels[name] = Channel(name, user_id)

    response = slack.users.list()
    people = {}
    for p in [u for u in response.body['members']]:
        name = p['name']
        user_id = p['id']

        person = User(name, user_id)
        if 'is_admin' in p:
            person.manager = p['is_admin']
        else:
            person.manager = False

        people[name] = person

    print('starting joy on ' + team_id)

    for event in slack_socket.events():
        res = json.loads(event.json)

        if 'team' in res and res['team'] == team_id and res['type'] == 'message' and 'user' in res and res['user'] != 'joy':
            print(res)
            team_id = res['team']
            message = res['text']
            user = res['user']
            channel = res['channel']
            timestamp = res['ts']

            if bot_id in message:
                if 'get morale' in message.lower():
                    t = message.lower().split('get morale ')
                    person = ''
                    if len(t) > 1:
                        person = t[1]
                        # print(person)
                        slack_socket.send_msg(str(compute_person_channel_morale(slack, people, channels, person)), channel_name=channel)
                    else:
                        slack_socket.send_msg(str(compute_team_morale(people)), channel_name=channel)
                continue

            res = tone_analyzer.tone(text=message)
            emotional_tone = res['children'][0]
            writing_tone = res['children'][1]
            social_tone = res['children'][2]

            cheerfulness = float(emotional_tone['children'][0]['normalized_score'])
            negative = float(emotional_tone['children'][1]['normalized_score'])
            anger = float(emotional_tone['children'][2]['normalized_score'])

            analytical = float(writing_tone['children'][0]['normalized_score'])
            confident = float(writing_tone['children'][1]['normalized_score'])
            tentative = float(writing_tone['children'][2]['normalized_score'])
           
            openness = float(social_tone['children'][0]['normalized_score'])
            agreeableness = float(social_tone['children'][1]['normalized_score'])
            conscientiousness = float(social_tone['children'][2]['normalized_score'])

            sentiment = {
                'cheerfulness' : [cheerfulness],
                'negative' : [negative],
                'anger' : [anger],
                'analytical' : [analytical],
                'confident' : [confident],
                'tentative' : [tentative],
                'openness' : [openness],
                'agreeableness' : [agreeableness],
                'conscientiousness' : [conscientiousness]         
            }

            
            teams = {}
            try:
                with open('teams.pickle', 'rb') as f:
                    teams = pickle.load(f)
                    # print('load current teams: ' + str(teams.keys()))
            except:
                pass

            if team_id in teams:
                if channel in channels:
                    teams[team_id]['channels'][channel].add_sentiment(sentiment)
                    channels = teams[team_id]['channels']

                if user in people:
                    teams[team_id]['people'][user].add_sentiment(sentiment)
                    people = teams[team_id]['people']
            else:
                d = {'channels' : channels, 'people' : people}
                teams[team_id] = d

            # print('saving ' + team_id)
            # print('current teams: ' + str(teams.keys()))

            with open('teams.pickle', 'wb') as f:
                pickle.dump(teams, f)