aboutsummaryrefslogtreecommitdiffstats
path: root/warmachine
diff options
context:
space:
mode:
authorjason2016-08-09 15:58:03 -0600
committerjason2016-08-09 15:58:03 -0600
commit3debc34c3d659f5f4849b585917513a8eaea4138 (patch)
treedfa5a0c2ef0216d59767e0a2cc02970c92221779 /warmachine
parentb915ef41c19a6eb4cb5f80e2f5f52b835afa9a53 (diff)
downloadwarmachine-ng-3debc34c3d659f5f4849b585917513a8eaea4138.tar.gz
warmachine-ng-3debc34c3d659f5f4849b585917513a8eaea4138.zip
add standup messaging
- message all users in a channel asking for their standup - continue messaging the users until they reply to the standup
Diffstat (limited to 'warmachine')
-rw-r--r--warmachine/addons/standup.py83
-rw-r--r--warmachine/connections/slack.py44
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 @@
1import asyncio 1import asyncio
2from datetime import datetime, timedelta 2from datetime import datetime, timedelta
3import functools 3import functools
4from pprint import pformat
4 5
5from .base import WarMachinePlugin 6from .base import WarMachinePlugin
6 7
@@ -18,8 +19,42 @@ class StandUpPlugin(WarMachinePlugin):
18 19
19 self.standup_schedules = {} 20 self.standup_schedules = {}
20 21
22 # 'DM_CHANNEL': {
23 # 'user': 'UID',
24 # 'for_channel': 'CHID',
25 # }
26 self.users_awaiting_reply = {}
27
21 async def recv_msg(self, connection, message): 28 async def recv_msg(self, connection, message):
22 if not message['message'].startswith('!standup'): 29 if not message['message'].startswith('!standup'):
30 if message['channel'] in self.users_awaiting_reply:
31 self.log.debug("Probable reply recvd from {}: {}".format(
32 message['channel'],
33 message['message']
34 ))
35 data = self.users_awaiting_reply[message['channel']]
36 for_channel = data['for_channel']
37
38 try:
39 user_nick = connection.user_map[data['user']]['name']
40 except KeyError:
41 user_nick = data['user']
42
43 if 'pester_task' in data:
44 self.log.debug('Stopping pester for {}'.format(user_nick))
45 data['pester_task'].cancel()
46
47 announce_message = '{}: {}'.format(
48 user_nick,
49 message['message']
50 )
51
52 await connection.say(
53 announce_message,
54 for_channel)
55
56 del data
57 del self.users_awaiting_reply[message['channel']]
23 return 58 return
24 59
25 self.log.debug('standup recv: {}'.format(message)) 60 self.log.debug('standup recv: {}'.format(message))
@@ -36,7 +71,7 @@ class StandUpPlugin(WarMachinePlugin):
36 next_standup_secs = pretty_next_standup.seconds 71 next_standup_secs = pretty_next_standup.seconds
37 72
38 ### DEBUG 73 ### DEBUG
39 next_standup_secs = 5 74 # next_standup_secs = 5
40 ### 75 ###
41 f = self._loop.call_later( 76 f = self._loop.call_later(
42 next_standup_secs, functools.partial( 77 next_standup_secs, functools.partial(
@@ -54,6 +89,10 @@ class StandUpPlugin(WarMachinePlugin):
54 def standup_schedule_func(self, connection, channel): 89 def standup_schedule_func(self, connection, channel):
55 asyncio.ensure_future(self.start_standup(connection, channel)) 90 asyncio.ensure_future(self.start_standup(connection, channel))
56 91
92 def pester_schedule_func(self, connection, user_id, channel, pester):
93 asyncio.ensure_future(self.standup_priv_msg(
94 connection, user_id, channel, pester))
95
57 async def start_standup(self, connection, channel): 96 async def start_standup(self, connection, channel):
58 await connection.say('@channel Time for standup', channel) 97 await connection.say('@channel Time for standup', channel)
59 users = connection.get_users_by_channel(channel) 98 users = connection.get_users_by_channel(channel)
@@ -62,12 +101,44 @@ class StandUpPlugin(WarMachinePlugin):
62 if u == connection.my_id: 101 if u == connection.my_id:
63 continue 102 continue
64 103
65 self.log.debug('Messaging user: {} ({})'.format( 104 await self.standup_priv_msg(connection, u, channel)
66 connection.user_map[u], u)) 105
106 async def standup_priv_msg(self, connection, user_id, channel, pester=600):
107 """
108 Send a private message to ``user_id`` asking for their standup update.
109
110 Args:
111 connection (:class:`warmachine.base.Connection'): Connection object
112 to use.
113 user_id (str): User name or id to send the message to.
114 channel (str): The channel the standup is for
115 pester (int): Number of seconds to wait until asking the user again.
116 Use 0 to disable
117 """
118 dm_id = connection.get_dm_id_by_user(user_id)
119
120 self.log.debug('Messaging user: {} ({})'.format(
121 connection.user_map[user_id], user_id))
122
123 self.users_awaiting_reply[dm_id] = {
124 'for_channel': channel,
125 'user': user_id
126 }
127
128 self.log.debug('Adding to list of users waiting on a reply for: '
129 '{}'.format(pformat(self.users_awaiting_reply[dm_id])))
130
131 await connection.say('What did you do yesterday? What will you '
132 'do today? do you have any blockers? '
133 '(standup for:{})'.format(channel), dm_id)
134
135 if pester > 0:
136 f = self._loop.call_later(
137 pester, functools.partial(
138 self.pester_schedule_func, connection, user_id, channel,
139 pester))
140 self.users_awaiting_reply[dm_id]['pester_task'] = f
67 141
68 await connection.say('What did you do yesterday? What will you '
69 'do today? do you have any blockers? '
70 '(standup for:{})'.format(channel), u)
71 142
72 @classmethod 143 @classmethod
73 def get_next_standup_secs(cls, time24h): 144 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):
78 """ 78 """
79 Say something in the provided channel or IM by id 79 Say something in the provided channel or IM by id
80 """ 80 """
81
82 # If the destination is a user, figure out the DM channel id 81 # If the destination is a user, figure out the DM channel id
83 if destination_id.startswith('U'): 82 if destination_id.startswith('U'):
84 url = 'https://slack.com/api/im.open?{}'.format(urlencode({ 83 destination_id = self.get_dm_id_by_user(destination_id)
85 'token': self.token,
86 'user': destination_id,
87 }))
88
89 req = urllib.request.Request(url)
90 r = urllib.request.urlopen(req).read().decode('utf-8')
91
92 data = json.loads(r)
93
94 if not data['ok']:
95 raise Exception(data)
96 return
97
98 destination_id = data['channel']['id']
99 84
100 await self._send(json.dumps({ 85 await self._send(json.dumps({
101 'id': 1, # TODO: this should be a get_msgid call or something 86 'id': 1, # TODO: this should be a get_msgid call or something
@@ -224,6 +209,33 @@ class SlackWS(Connection):
224 )) 209 ))
225 self.user_map[msg['user']]['presence'] = msg['presence'] 210 self.user_map[msg['user']]['presence'] = msg['presence']
226 211
212 def get_dm_id_by_user(self, user_id):
213 """
214 Return the channel id for a direct message to a specific user.
215
216 Args:
217 user_id (str): slack user id
218
219 Return:
220 str: DM channel id for the provided user. None on error
221 """
222 url = 'https://slack.com/api/im.open?{}'.format(urlencode({
223 'token': self.token,
224 'user': user_id,
225 }))
226
227 req = urllib.request.Request(url)
228 r = urllib.request.urlopen(req).read().decode('utf-8')
229
230 data = json.loads(r)
231
232 if not data['ok']:
233 raise Exception(data)
234 return
235
236 return data['channel']['id']
237
238
227 def get_users_by_channel(self, channel): 239 def get_users_by_channel(self, channel):
228 url = 'https://slack.com/api/groups.info?{}'.format(urlencode( 240 url = 'https://slack.com/api/groups.info?{}'.format(urlencode(
229 { 241 {