示例#1
0
 def _board(self, board_name):
     logger.debug("Looking up board {}".format(board_name))
     board = [b for b in self.boards if b.name == board_name]
     try:
         return board[0]
     except IndexError as e:
         raise NoBoardError from e
示例#2
0
 def store_event(self, event_id, data):
     logger.debug("Storing event {}".format(event_id))
     data = json.dumps(data)
     sql = "INSERT INTO events (event_id, data) VALUES ('{0}', $${1}$$) ON CONFLICT (event_id) DO UPDATE SET " \
           "data=$${1}$$;".format(event_id, data)
     cur = self.conn.cursor()
     cur.execute(sql)
     self.conn.commit()
示例#3
0
 def userid_info(self, user_id):
     logger.debug("Looking for user {}".format(user_id))
     info = self.sc.api_call("users.info", user=user_id)
     logger.debug("user info: {}".format(info))
     if info["ok"]:
         return info["user"]
     else:
         logger.warn(info["error"])
示例#4
0
    def _handle_rsvps(self, event):
        event_id = event["id"]
        event_name = event["name"]
        venue = event["venue"]["name"]
        channel_for_venue = {
            "STORG Clubhouse": "#storg-south",
            "STORG Northern Clubhouse": "#storg-north"
        }
        channel = channel_for_venue.get(venue)
        newcomers = []
        cancels = []

        for rsvp in self.storg.rsvps(event_id):
            member_name = rsvp["member"]["name"]
            member_id = rsvp["member"]["member_id"]
            try:
                known_participants = self.stored_events[event_id][
                    "participants"]
            except KeyError:
                logger.error("No key {}".format(event_id))
                logger.error("Known events: {}".format(self.stored_events))

            if member_name not in known_participants and rsvp[
                    "response"] == "yes":
                self.trello.add_rsvp(name=member_name,
                                     member_id=member_id,
                                     board_name=event_name)
                # self.trello.add_contact(member_name=member_name, member_id=member_id)
                newcomers.append(member_name)
                sleep(0.2)
            elif member_name in known_participants and rsvp["response"] == "no":
                self.trello.cancel_rsvp(member_id, board_name=event_name)
                cancels.append(member_name)

        if newcomers or cancels:
            spots_left = int(event["rsvp_limit"]) - int(
                event["yes_rsvp_count"]) if event["rsvp_limit"] else 'Unknown'

            if newcomers:
                logger.info("Newcomers found: {}".format(newcomers))
                self.chat.new_rsvp(', '.join(newcomers), "yes", event_name,
                                   spots_left, channel)
                self.stored_events[event_id]["participants"] += newcomers
                logger.debug("Participant list: {}".format(
                    self.stored_events[event_id]["participants"]))

            if cancels:
                logger.info("Cancellations found: {}".format(cancels))
                self.chat.new_rsvp(', '.join(cancels), "no", event_name,
                                   spots_left, channel)
                self.stored_events[event_id]["participants"] = [
                    p for p in self.stored_events[event_id]["participants"]
                    if p not in cancels
                ]
                logger.debug("Participant list: {}".format(
                    self.stored_events[event_id]["participants"]))
        else:
            logger.info("No changes for {}".format(event_name))
示例#5
0
 def retrieve_event(self, event_id):
     logger.debug("Retrieving event {}".format(event_id))
     if not event_id:
         return {}
     sql = "SELECT data FROM events WHERE event_id='{}';".format(event_id)
     cur = self.conn.cursor()
     cur.execute(sql)
     resp = cur.fetchone()
     return json.dumps(resp)
示例#6
0
    def add_rsvp(self, name, member_id, board_name):
        logger.debug("Adding rsvp {} to {}".format(name, board_name))
        member_id = str(member_id)
        board = self._board(board_name)
        if not board:
            return None

        if not self._member(member_id, board_name):
            rsvp_list = board.list_lists(list_filter="open")[0]
            rsvp_list.add_card(name=name, desc=member_id)
示例#7
0
 def _warmup_caches(self):
     logger.debug("Warming up the caches")
     ids = self.addressbook
     try:
         for meetup_name, slack_name in [(n["name"], n["slack"])
                                         for n in ids.values()]:
             _ = self.contact_by_name(meetup_name)
             if slack_name:
                 _ = self.contact_by_slack_name(slack_name)
     except Exception as e:
         logger.warning("Exception {} when warming up caches".format(e))
示例#8
0
 def create_board(self, board_name, team_name=None):
     logger.debug("Checking for board {} on {} team".format(
         board_name, team_name))
     template = self._board("Meetup Template")
     org_id = self._org_id(team_name=team_name)
     try:
         self._board(board_name)
     except NoBoardError:
         self.tc.add_board(board_name=board_name,
                           source_board=template,
                           organization_id=org_id,
                           permission_level="public")
示例#9
0
    def retrieve_all_events(self):
        logger.debug("Retrieving all events {}")
        resp = {}
        sql = "SELECT event_id, data FROM events;"

        cur = self.conn.cursor()
        cur.execute(sql)
        all_events = self.cur.fetchall()
        self.conn.commit()
        for event_id, data in all_events:
            resp[event_id] = json.loads(data)
        return resp
示例#10
0
    def _tables_info(self,
                     channel,
                     request=None,
                     detail=False,
                     table_number=None):
        logger.debug("Got {} and {}".format(channel, request))
        if not request and channel:
            request = ' '.join(channel.split("_"))

        logger.debug("Request {}".format(request))
        logger.debug("Channel {}".format(channel))
        events = self.event_names
        logger.debug("Events {}".format(events))
        event_name = process.extractOne(request, events)[0]
        logger.debug("Chose {}".format(event_name))

        table_info = self.trello.tables_detail(event_name)

        tables = []

        for table, details in table_info.items():
            color = "b20000" if table.lower().endswith("full") else "#36a64f"
            if detail and table[0].isdigit():
                text = details["blurb"]
                title = "Joining ({} out of {} max)".format(
                    len(details["members"]) - 1, details["players"])
            elif table[0].isdigit():
                text = "_Ask *table {}* to get details for this table " \
                       "or *detailed table status* to get details for all tables_".format(table[0])
                title = "Joining ({} out of {} max)".format(
                    len(details["members"]) - 1, details["players"])
            else:
                text = ""
                title = "{} left".format(len(details["members"]))
                color = ""

            attachment = {
                "title":
                table.upper(),
                "text":
                text,
                "color":
                color,
                "fields": [{
                    "title": title,
                    "value": ', '.join(details["members"])
                }]
            }
            if not table_number or table.startswith(str(table_number)):
                tables.append(attachment)

        return json.dumps(tables)
示例#11
0
    def create_board(self, board_name, team_name=None):
        logger.debug("Checking for board {} on {} team".format(
            board_name, team_name))
        template = self._board("Meetup Template")
        board = self._board(board_name)
        org_id = self._org_id(team_name=team_name)

        if not board:
            logger.debug("Adding board {}".format(board_name))
            self.tc.add_board(board_name=board_name,
                              source_board=template,
                              organization_id=org_id,
                              permission_level="public")
示例#12
0
 def retrieve_events(self, event_ids):
     logger.debug("Retrieving events {}".format(event_ids))
     resp = {}
     if not event_ids:
         return resp
     event_ids = ["$${}$$".format(e) for e in event_ids]
     sql = "SELECT event_id, data FROM events WHERE event_id IN ({});".format(','.join(event_ids))
     cur = self.conn.cursor()
     cur.execute(sql)
     all_events = cur.fetchall()
     for event_id, data in all_events:
         resp[event_id] = json.loads(data)
     return resp
示例#13
0
 def contact_by_name(self, member_name):
     logger.debug("Checking {}".format(member_name))
     if self._ab_name_cache.get(member_name):
         return self._ab_name_cache[member_name]
     else:
         board = self._board("Address Book")
         for l in board.list_lists(list_filter="open"):
             for card in l.list_cards():
                 desc = yaml.load(card.desc)
                 if card.name == member_name and desc["slack"]:
                     logger.debug("Desc: {}".format(desc))
                     self._ab_name_cache[member_name] = yaml.load(card.desc)
                     return self._ab_name_cache[member_name]
示例#14
0
 def _get(self, path: str, params: dict) -> list:
     """ Do a GET towards the Meetup API
     :param path: (str) The path to GET
     :param params: (dict) Extra parameters to pass to the request
     :return: (list) The "response" list contained in the Meetup API response
     """
     url = self.api_url + path
     req = requests.get(url, params)
     try:
         return req.json()["results"]
     except Exception:
         logger.debug("GET {} failed: {}".format(self.api_url + path,
                                                 req.headers))
         return []
示例#15
0
    def message(self, content, channel, attachments=None):
        """Sends a simple message containing :content: to :channel:

        :param list attachments:
        :param content: (str) The, well, content of the message
        :param channel: (str) The channel where to make the announcement. Needs a leading #
        :return: None
        """
        logger.debug("Sending {} to {}".format(content[0:10], channel))
        self.sc.api_call(
            "chat.postMessage",
            as_user=True,
            channel=channel,
            text=content,
            attachments=attachments)
示例#16
0
    def rtm(self, queue, read_delay=1):
        """Creates a Real Time Messaging connection to Slack and listens for events
        https://api.slack.com/rtm

        :param queue: (queue) A Multiprocess Queue where it'll put the incoming events
        :param read_delay: (int) How often to check for events. Default: 1s
        :return: None
        """
        if self.sc.rtm_connect():
            logger.info("Slack RTM connected")
            while True:
                command, channel, user_id = self._parse_slack_output(self.sc.rtm_read())
                if command and channel and user_id:
                    logger.debug("command found text: {}, channel: {}, user_id: {}".format(command, channel, user_id))
                    queue.put((command, channel, user_id))
                sleep(read_delay)
示例#17
0
 def contact_by_slack_name(self, slack_name):
     if self._ab_slack_cache.get(slack_name):
         return self._ab_slack_cache[slack_name]
     else:
         board = self._board("Address Book")
         try:
             for l in board.list_lists(list_filter="open"):
                 for card in l.list_cards():
                     desc = yaml.load(card.desc)
                     if desc["slack"] == slack_name:
                         self._ab_slack_cache[slack_name] = {
                             "name": card.name,
                             "id": desc["id"]
                         }
                         return self._ab_slack_cache[slack_name]
         except:
             logger.debug("Nothing found for {}".format(slack_name))
示例#18
0
    def _parse_slack_output(self, slack_rtm_output):
        """Parse the :slack_rtm_output: received from Slack and return everything after the bot's @-name
        or None if it wasn't directed at the bot.

        :param slack_rtm_output: (str) Slack message to parse
        :return: (tuple) A tuple of the striped message and channel id
        """
        output_list = slack_rtm_output
        if output_list and len(output_list) > 0:
            for output in output_list:
                if output and 'text' in output and self.at_bot in output[
                        'text'] and output["user"] != 'USLACKBOT' and output[
                            "ts"]:
                    # return text excluding the @ mention, whitespace removed
                    logger.debug(output)
                    command = ' '.join([
                        t.strip() for t in output["text"].split(self.at_bot)
                        if t
                    ])
                    return command, output["channel"], output["user"], output[
                        "ts"]
                elif output and "channel" in output and "text" in output \
                        and self._is_im(output["channel"]) and output["user"] != self.bot_id and \
                        output["user"] != 'USLACKBOT' and output["ts"]:
                    logger.debug(output)
                    return output["text"], output["channel"], output[
                        "user"], output["ts"]
                else:
                    logger.debug(output)
        return None, None, None, None
示例#19
0
    def add_rsvp(self, name, member_id, board_name):
        logger.debug("Adding rsvp {} to {}".format(name, board_name))
        try:
            board = self._board(board_name)
        except NoBoardError:
            logger.debug("Board {} not found".format(board_name))
            return

        if not self._member(member_id, board_name):
            logger.debug("Member {} does not exist in {}. Adding them.".format(
                member_id, board_name))
            rsvp_list = board.list_lists(list_filter="open")[0]
            logger.debug("RSVP list for {}: {}".format(board_name, rsvp_list))
            rsvp_list.add_card(name=name, desc=str(member_id))
示例#20
0
 def cancel_rsvp(self, member_id, board_name):
     logger.debug("Cancelling RSVP for members id {} at {}".format(
         member_id, board_name))
     card = self._member(member_id, board_name)
     logger.debug("Card for member id {} is {}".format(member_id, card))
     canceled = self._label("Canceled", board_name)
     logger.debug("Canceled tag is {}".format(canceled))
     if card:
         card.add_label(canceled)
示例#21
0
    def __init__(self):
        meetup_key = environ.get('MEETUP_API_KEY')
        group_id = environ.get('MEETUP_GROUP_ID')
        slack_token = environ["SLACK_API_TOKEN"]
        trello_key = environ["TRELLO_API_KEY"]
        trello_token = environ["TRELLO_TOKEN"]
        bot_id = environ.get("BOT_ID")
        self.lab_channel_id = environ.get("LAB_CHANNEL_ID")
        self.team_name = environ["TRELLO_TEAM"]
        self.storg = MeetupGroup(meetup_key, group_id)
        self.chat = Slack(slack_token, bot_id)
        self.trello = TrelloBoard(api_key=trello_key, token=trello_token)
        self.ds = Store()
        with open("dave/resources/phrases.json", "r") as phrases:
            self._phrases = json.loads(phrases.read())
        if self.storg.upcoming_events:
            current_event_ids = [e["id"] for e in self.storg.upcoming_events]
            self.stored_events = self.ds.retrieve_events(current_event_ids)
        else:
            self.stored_events = {}

        logger.debug("Known events: {}".format(self.stored_events))
        logger.debug("Env: {}".format(environ.items()))
        self.chat.message("Reporting for duty!", environ.get("LAB_CHANNEL_ID"))
示例#22
0
 def conversation(self, task_queue):
     unknown_responses = self._phrases["responses"]["unknown"]
     while True:
         try:
             command, channel_id, user_id, thread = task_queue.get()
             attachments = None
             if command.startswith("help"):
                 response = "Hold on tight, I'm coming!\nJust kidding!\n\n{}".format(
                     self._phrases["responses"]["help"])
                 thread = None
             elif command.lower().startswith("table status"):
                 response = "Open tables"
                 attachments = self._tables_info(
                     channel=self.chat.channel_name(channel_id),
                     request=command.split('table status')[-1],
                     only_available=False)
             elif command.lower().startswith("available tables"):
                 response = "Available tables"
                 attachments = self._tables_info(
                     channel=self.chat.channel_name(channel_id),
                     request=command.split('available tables')[-1],
                     only_available=True)
             elif command.lower().startswith("detailed table status"):
                 response = "Available tables"
                 attachments = self._tables_info(
                     channel=self.chat.channel_name(channel_id),
                     request=command.split('table status')[-1],
                     detail=True,
                     only_available=False)
             elif command.lower().startswith("table"):
                 full_req = command.split('table')[-1].strip()
                 split_req = full_req.split(" ", 1)
                 table_number = int(split_req[0])
                 if len(split_req) == 2:
                     request = split_req[1]
                 else:
                     request = None
                 logger.debug("Table {}".format(table_number))
                 response = "Details for table {}".format(table_number)
                 attachments = self._tables_info(
                     channel=self.chat.channel_name(channel_id),
                     request=request,
                     detail=True,
                     table_number=table_number)
             elif "next event" in command.lower(
             ) and "events" not in command.lower():
                 response = self._next_event_info()
                 thread = None
             elif "events" in command.lower():
                 thread = None
                 response = self._all_events_info()
             elif "thanks" in command.lower(
             ) or "thank you" in command.lower():
                 thread = None
                 response = random.choice(
                     self._phrases["responses"]["thanks"])
             elif command.lower().startswith(
                     "what can you do") or command.lower() == "man":
                 thread = None
                 response = self._phrases["responses"]["help"]
             elif "admin info" in command.lower():
                 response = self._phrases["responses"]["admin_info"]
             elif "add table" == command.lower():
                 response = "Sure thing. Just send me a message in the following format:\n" \
                            "add table <TABLE TITLE>: <BLURB>, Players: <MAX NUMBER OF PLAYERS>, e.g.\n" \
                            "```add table Rat Queens (Fate): One more awesome Rat Queens adventure, Players: 5```"
             elif command.lower().startswith("add table"):
                 response = self._add_table(command, channel_id)
             else:
                 thread = None
                 response = self._check_for_greeting(
                     command) if self._check_for_greeting(
                         command) else random.choice(unknown_responses)
             self.respond(response,
                          channel_id,
                          attachments=attachments,
                          thread=thread)
         except Exception as e:
             self.chat.message(
                 "Swallowed exception at conversation: {}".format(e),
                 self.lab_channel_id)
             logger.error(
                 "Swallowed exception at conversation: {}".format(e))
示例#23
0
    def _tables_info(self,
                     channel,
                     request=None,
                     detail=False,
                     only_available=False,
                     table_number=None):
        logger.debug("Got {} and {}".format(channel, request))
        if not request and channel:
            request = ' '.join(channel.split("_"))

        logger.debug("Request {}".format(request))
        logger.debug("Channel {}".format(channel))
        events = self.storg.event_names
        logger.debug("Events {}".format(events))
        event_name = process.extractOne(request, events)[0]
        logger.debug("Chose {}".format(event_name))

        try:
            tables_for_event = self.trello.tables_for_event(event_name)
        except NoBoardError:
            return "I didn't find anything :disappointed:"

        tables = []

        for table in tables_for_event.values():
            color = "b20000" if table.is_full else "#36a64f"
            title = None
            if only_available and table.is_full:
                continue

            if table_number and table.number != table_number:
                continue

            if detail and table.number != 9999:
                table_title = "{}. {}".format(table.number, table.title)
                text = table.blurb
                joining = "*GM:* {}; *Joining ({} out of {} max):* {}".format(
                    table.gm, len(table.players), table.max_players,
                    self._natural_join(table.players, ' '))
            elif table.number != 9999:
                table_title = "{}. {}".format(table.number, table.title)
                text = "_Ask *table {}* to get details for this table " \
                       "or *detailed table status* to get details for all tables_".format(table.number)
                joining = "*GM:* {}; *Joining ({} out of {} max):* {}".format(
                    table.gm, len(table.players), table.max_players,
                    self._natural_join(table.players, ' '))
            else:
                table_title = table.title
                text = ""
                color = ""
                joining = "*{} left:* {}".format(
                    len(table.players), self._natural_join(table.players, ' '))

            attachment = {
                "title": table_title,
                "text": text,
                "color": color,
                "short": True,
                "fields": [{
                    "title": title,
                    "value": joining
                }],
                "mrkdwn_in": ["text", "pretext", "fields"]
            }
            tables.append(attachment)
        return json.dumps(tables)
示例#24
0
 def _board(self, board_name):
     logger.debug("Looking up board {}".format(board_name))
     board = [b for b in self.boards if b.name == board_name]
     if board:
         return board[0]
示例#25
0
 def save_events(self):
     logger.debug("Saving events")
     self.ds.store_events(self.stored_events)
示例#26
0
 def store_events(self, events):
     logger.debug("Storing events {}".format(events))
     for event_id, data in events.items():
         self.store_event(event_id, data)