From 3debc34c3d659f5f4849b585917513a8eaea4138 Mon Sep 17 00:00:00 2001 From: jason Date: Tue, 9 Aug 2016 15:58:03 -0600 Subject: add standup messaging - message all users in a channel asking for their standup - continue messaging the users until they reply to the standup --- warmachine/addons/standup.py | 83 ++++++++++++++++++++++++++++++++++++++--- warmachine/connections/slack.py | 44 ++++++++++++++-------- 2 files changed, 105 insertions(+), 22 deletions(-) diff --git a/warmachine/addons/standup.py b/warmachine/addons/standup.py index 336177e..ebc4cac 100644 --- a/warmachine/addons/standup.py +++ b/warmachine/addons/standup.py @@ -1,6 +1,7 @@ import asyncio from datetime import datetime, timedelta import functools +from pprint import pformat from .base import WarMachinePlugin @@ -18,8 +19,42 @@ class StandUpPlugin(WarMachinePlugin): self.standup_schedules = {} + # 'DM_CHANNEL': { + # 'user': 'UID', + # 'for_channel': 'CHID', + # } + self.users_awaiting_reply = {} + async def recv_msg(self, connection, message): if not message['message'].startswith('!standup'): + if message['channel'] in self.users_awaiting_reply: + self.log.debug("Probable reply recvd from {}: {}".format( + message['channel'], + message['message'] + )) + data = self.users_awaiting_reply[message['channel']] + for_channel = data['for_channel'] + + try: + user_nick = connection.user_map[data['user']]['name'] + except KeyError: + user_nick = data['user'] + + if 'pester_task' in data: + self.log.debug('Stopping pester for {}'.format(user_nick)) + data['pester_task'].cancel() + + announce_message = '{}: {}'.format( + user_nick, + message['message'] + ) + + await connection.say( + announce_message, + for_channel) + + del data + del self.users_awaiting_reply[message['channel']] return self.log.debug('standup recv: {}'.format(message)) @@ -36,7 +71,7 @@ class StandUpPlugin(WarMachinePlugin): next_standup_secs = pretty_next_standup.seconds ### DEBUG - next_standup_secs = 5 + # next_standup_secs = 5 ### f = self._loop.call_later( next_standup_secs, functools.partial( @@ -54,6 +89,10 @@ class StandUpPlugin(WarMachinePlugin): def standup_schedule_func(self, connection, channel): asyncio.ensure_future(self.start_standup(connection, channel)) + def pester_schedule_func(self, connection, user_id, channel, pester): + asyncio.ensure_future(self.standup_priv_msg( + connection, user_id, channel, pester)) + async def start_standup(self, connection, channel): await connection.say('@channel Time for standup', channel) users = connection.get_users_by_channel(channel) @@ -62,12 +101,44 @@ class StandUpPlugin(WarMachinePlugin): if u == connection.my_id: continue - self.log.debug('Messaging user: {} ({})'.format( - connection.user_map[u], u)) + await self.standup_priv_msg(connection, u, channel) + + async def standup_priv_msg(self, connection, user_id, channel, pester=600): + """ + Send a private message to ``user_id`` asking for their standup update. + + Args: + connection (:class:`warmachine.base.Connection'): Connection object + to use. + user_id (str): User name or id to send the message to. + channel (str): The channel the standup is for + pester (int): Number of seconds to wait until asking the user again. + Use 0 to disable + """ + dm_id = connection.get_dm_id_by_user(user_id) + + self.log.debug('Messaging user: {} ({})'.format( + connection.user_map[user_id], user_id)) + + self.users_awaiting_reply[dm_id] = { + 'for_channel': channel, + 'user': user_id + } + + self.log.debug('Adding to list of users waiting on a reply for: ' + '{}'.format(pformat(self.users_awaiting_reply[dm_id]))) + + await connection.say('What did you do yesterday? What will you ' + 'do today? do you have any blockers? ' + '(standup for:{})'.format(channel), dm_id) + + if pester > 0: + f = self._loop.call_later( + pester, functools.partial( + self.pester_schedule_func, connection, user_id, channel, + pester)) + self.users_awaiting_reply[dm_id]['pester_task'] = f - await connection.say('What did you do yesterday? What will you ' - 'do today? do you have any blockers? ' - '(standup for:{})'.format(channel), u) @classmethod def get_next_standup_secs(cls, time24h): diff --git a/warmachine/connections/slack.py b/warmachine/connections/slack.py index 845b5c8..0ef39dc 100644 --- a/warmachine/connections/slack.py +++ b/warmachine/connections/slack.py @@ -78,24 +78,9 @@ class SlackWS(Connection): """ Say something in the provided channel or IM by id """ - # If the destination is a user, figure out the DM channel id if destination_id.startswith('U'): - url = 'https://slack.com/api/im.open?{}'.format(urlencode({ - 'token': self.token, - 'user': destination_id, - })) - - req = urllib.request.Request(url) - r = urllib.request.urlopen(req).read().decode('utf-8') - - data = json.loads(r) - - if not data['ok']: - raise Exception(data) - return - - destination_id = data['channel']['id'] + destination_id = self.get_dm_id_by_user(destination_id) await self._send(json.dumps({ 'id': 1, # TODO: this should be a get_msgid call or something @@ -224,6 +209,33 @@ class SlackWS(Connection): )) self.user_map[msg['user']]['presence'] = msg['presence'] + def get_dm_id_by_user(self, user_id): + """ + Return the channel id for a direct message to a specific user. + + Args: + user_id (str): slack user id + + Return: + str: DM channel id for the provided user. None on error + """ + url = 'https://slack.com/api/im.open?{}'.format(urlencode({ + 'token': self.token, + 'user': user_id, + })) + + req = urllib.request.Request(url) + r = urllib.request.urlopen(req).read().decode('utf-8') + + data = json.loads(r) + + if not data['ok']: + raise Exception(data) + return + + return data['channel']['id'] + + def get_users_by_channel(self, channel): url = 'https://slack.com/api/groups.info?{}'.format(urlencode( { -- cgit v1.2.1