aboutsummaryrefslogtreecommitdiffstats
path: root/warmachine/addons
diff options
context:
space:
mode:
Diffstat (limited to 'warmachine/addons')
-rw-r--r--warmachine/addons/standup.py190
1 files changed, 110 insertions, 80 deletions
diff --git a/warmachine/addons/standup.py b/warmachine/addons/standup.py
index dfcb6ab..0d46408 100644
--- a/warmachine/addons/standup.py
+++ b/warmachine/addons/standup.py
@@ -32,9 +32,10 @@ class StandUpPlugin(WarMachinePlugin):
32 32
33 # 'DM_CHANNEL': { 33 # 'DM_CHANNEL': {
34 # 'user': 'UID', 34 # 'user': 'UID',
35 # 'for_channel': 'CHID', 35 # 'for_channels': ['CHID',],
36 # } 36 # }
37 self.users_awaiting_reply = {} 37 self.users_awaiting_reply = {}
38 self.log.info('Loaded standup plugin')
38 39
39 def on_connect(self, connection): 40 def on_connect(self, connection):
40 self.load_schedule(connection) 41 self.load_schedule(connection)
@@ -48,40 +49,52 @@ class StandUpPlugin(WarMachinePlugin):
48 connection (Connection): the warmachine connection object 49 connection (Connection): the warmachine connection object
49 message (dict): the warmachine formatted message 50 message (dict): the warmachine formatted message
50 """ 51 """
51 if not message['message'].startswith('!standup'): 52 if not message['message'].startswith('!standup') \
52 if message['channel'] in self.users_awaiting_reply: 53 and not message['channel'] \
53 self.log.debug("Probable reply recvd from {}: {}".format( 54 and message['sender'] in self.users_awaiting_reply:
54 message['channel'], 55 self.log.debug("Probable standup reply recvd from {}: {}".format(
55 message['message'] 56 message['sender'], message['message']))
56 )) 57
57 data = self.users_awaiting_reply[message['channel']] 58 user_nick = message['sender']
58 for_channel = data['for_channel'] 59
59 60 data = self.users_awaiting_reply[user_nick]
60 try: 61
61 user_nick = connection.user_map[data['user']]['name'] 62 for_channels = data['for_channels']
62 except KeyError: 63
63 user_nick = data['user'] 64 if 'pester_task' in data:
64 65 self.log.debug('Stopping pester for {}'.format(user_nick))
65 if 'pester_task' in data: 66 data['pester_task'].cancel()
66 self.log.debug('Stopping pester for {}'.format(user_nick)) 67 data['pester_task'] = None
67 data['pester_task'].cancel() 68
68 69 announce_message = '{}: {}'.format(
69 announce_message = '{}: {}'.format( 70 user_nick,
70 user_nick, 71 message['message']
71 message['message'] 72 )
72 ) 73
73 74 self.users_awaiting_reply[user_nick]['standup_msg'] = \
74 await connection.say( 75 message['message']
75 announce_message, 76
76 for_channel) 77 f = self._loop.call_later(
77 78 16*(60*60), # 16 hours
78 del data 79 self.clear_old_standup_message_schedule_func, user_nick
79 del self.users_awaiting_reply[message['channel']] 80 )
81
82 self.users_awaiting_reply[user_nick]['clear_standup_msg_f'] = f
83
84 for i in range(0, len(for_channels)):
85 c = self.users_awaiting_reply[user_nick]['for_channels'].pop()
86 await connection.say(announce_message, c)
87
88 del data
89 # del self.users_awaiting_reply[user_nick]
80 return 90 return
81 91
92 # Otherwise parse for the commands:
93
82 cmd = message['message'].split(' ')[0] 94 cmd = message['message'].split(' ')[0]
83 parts = message['message'].split(' ')[1:] 95 parts = message['message'].split(' ')[1:]
84 channel = message['channel'] 96 channel = message['channel']
97 user_nick = message['sender']
85 98
86 # ====================================================================== 99 # ======================================================================
87 # !standup-add <24h time> 100 # !standup-add <24h time>
@@ -89,7 +102,7 @@ class StandUpPlugin(WarMachinePlugin):
89 # Add (or update if one exists) a schedule for standup at the given 24h 102 # Add (or update if one exists) a schedule for standup at the given 24h
90 # time M-F 103 # time M-F
91 # ====================================================================== 104 # ======================================================================
92 if cmd == '!standup-add' and not channel.startswith('D'): 105 if cmd == '!standup-add' and channel:
93 # If there is already a schedule, kill the task for the old one. 106 # If there is already a schedule, kill the task for the old one.
94 if channel in self.standup_schedules: 107 if channel in self.standup_schedules:
95 self.standup_schedules[channel]['future'].cancel() 108 self.standup_schedules[channel]['future'].cancel()
@@ -100,12 +113,13 @@ class StandUpPlugin(WarMachinePlugin):
100 113
101 self.schedule_standup(connection, channel, parts[0]) 114 self.schedule_standup(connection, channel, parts[0])
102 self.save_schedule(connection) 115 self.save_schedule(connection)
116
103 # ====================================================================== 117 # ======================================================================
104 # !standup-remove 118 # !standup-remove
105 # 119 #
106 # Remove an existing schedule from the channel 120 # Remove an existing schedule from the channel
107 # ====================================================================== 121 # ======================================================================
108 elif cmd == '!standup-remove' and not channel.startswith('D'): 122 elif cmd == '!standup-remove' and channel:
109 if channel in self.standup_schedules: 123 if channel in self.standup_schedules:
110 self.standup_schedules[channel]['future'].cancel() 124 self.standup_schedules[channel]['future'].cancel()
111 del self.standup_schedules[channel] 125 del self.standup_schedules[channel]
@@ -120,15 +134,17 @@ class StandUpPlugin(WarMachinePlugin):
120 # questions. 134 # questions.
121 # If no users are provided, display the users currently being ignored 135 # If no users are provided, display the users currently being ignored
122 # ====================================================================== 136 # ======================================================================
123 elif cmd == '!standup-ignore' and not channel.startswith('D') \ 137 elif cmd == '!standup-ignore' and channel \
124 and channel in self.standup_schedules: 138 and channel in self.standup_schedules:
125 if parts: 139 if parts:
126 users = ''.join(parts).split(',') 140 users_to_ignore = ''.join(parts).split(',')
127 for u in users: 141 for u in users_to_ignore:
128 if u not in self.standup_schedules[channel]['ignoring']: 142 if u not in self.standup_schedules[channel]['ignoring']:
129 self.log.info('Ignoring {} in channel {}'.format( 143 self.log.info('Ignoring {} in channel {}'.format(
130 u, channel)) 144 u, channel))
131 self.standup_schedules[channel]['ignoring'].append(u) 145 self.standup_schedules[channel]['ignoring'].append(u)
146
147 # Save the new users to ignore for this channel
132 self.save_schedule(connection) 148 self.save_schedule(connection)
133 149
134 ignoring = ', '.join( 150 ignoring = ', '.join(
@@ -138,22 +154,26 @@ class StandUpPlugin(WarMachinePlugin):
138 154
139 await connection.say('Currently ignoring {}'.format(ignoring), 155 await connection.say('Currently ignoring {}'.format(ignoring),
140 channel) 156 channel)
157 elif cmd == '!standup-unignore' and channel \
158 and channel in self.standup_schedules:
159 if not parts:
160 return
141 161
142 # ====================================================================== 162 # ======================================================================
143 # !standup-schedules 163 # !standup-schedules
144 # 164 #
145 # Report the current standup schedule dict to the requesting user 165 # Report the current standup schedule dict to the requesting user
146 # ====================================================================== 166 # ======================================================================
147 elif channel.startswith('D') and cmd == '!standup-schedules': 167 elif not channel and cmd == '!standup-schedules':
148 self.log.info('Reporting standup schedules to DM {}'.format( 168 self.log.info('Reporting standup schedules to {}'.format(
149 channel)) 169 user_nick))
150 await connection.say('Standup Schedules', channel) 170 await connection.say('Standup Schedules', user_nick)
151 await connection.say('-----------------', channel) 171 await connection.say('-----------------', user_nick)
152 await connection.say( 172 await connection.say(
153 'Current Loop Time: {}'.format(self._loop.time()), channel) 173 'Current Loop Time: {}'.format(self._loop.time()), user_nick)
154 await connection.say( 174 await connection.say(
155 'Current Time: {}'.format(datetime.now()), channel) 175 'Current Time: {}'.format(datetime.now()), user_nick)
156 await connection.say(pformat(self.standup_schedules), channel) 176 await connection.say(pformat(self.standup_schedules), user_nick)
157 177
158 # ====================================================================== 178 # ======================================================================
159 # !standup-waiting_replies 179 # !standup-waiting_replies
@@ -161,14 +181,13 @@ class StandUpPlugin(WarMachinePlugin):
161 # Report the data struct of users we are waiting on a reply from to the 181 # Report the data struct of users we are waiting on a reply from to the
162 # requesting user. 182 # requesting user.
163 # ====================================================================== 183 # ======================================================================
164 elif channel.startswith('D') and \ 184 elif not channel and cmd == '!standup-waiting_replies':
165 cmd == '!standup-waiting_replies': 185 self.log.info('Reporting who we are waiting on replies for to '
166 self.log.info('Reporting who we are waiting on replies for to DM ' 186 ' {}'.format(user_nick))
167 ' {}'.format(channel)) 187 await connection.say('Waiting for Replies From', user_nick)
168 await connection.say('Waiting for Replies From', channel) 188 await connection.say('------------------------', user_nick)
169 await connection.say('------------------------', channel)
170 await connection.say( 189 await connection.say(
171 pformat(self.users_awaiting_reply), channel) 190 pformat(self.users_awaiting_reply), user_nick)
172 191
173 def schedule_standup(self, connection, channel, time24h): 192 def schedule_standup(self, connection, channel, time24h):
174 """ 193 """
@@ -191,9 +210,7 @@ class StandUpPlugin(WarMachinePlugin):
191 } 210 }
192 211
193 self.log.info('New schedule added to channel {} for {}'.format( 212 self.log.info('New schedule added to channel {} for {}'.format(
194 connection.channel_map[channel]['name'], 213 channel, time24h))
195 time24h
196 ))
197 214
198 def standup_schedule_func(self, connection, channel): 215 def standup_schedule_func(self, connection, channel):
199 """ 216 """
@@ -201,75 +218,88 @@ class StandUpPlugin(WarMachinePlugin):
201 218
202 See :meth:`start_standup` 219 See :meth:`start_standup`
203 """ 220 """
204 self.log.info('Executing standup for channel {}'.format( 221 self.log.info('Executing standup for channel {}'.format(channel))
205 connection.channel_map[channel]['name']
206 ))
207 asyncio.ensure_future(self.start_standup(connection, channel)) 222 asyncio.ensure_future(self.start_standup(connection, channel))
208 223
209 def pester_schedule_func(self, connection, user_id, channel, pester): 224 def pester_schedule_func(self, connection, user, channel, pester):
210 """ 225 """
211 Non-async function used to schedule pesters for a user. 226 Non-async function used to schedule pesters for a user.
212 227
213 See :meth:`standup_priv_msg` 228 See :meth:`standup_priv_msg`
214 """ 229 """
215 self.log.info('Pestering user {} to give a standup for channel ' 230 self.log.info('Pestering user {} to give a standup for channel '
216 '{} (interval: {}s)'.format( 231 '{} (interval: {}s)'.format(user, channel, pester))
217 connection.user_map[user_id]['name'],
218 connection.channel_map[channel]['name'],
219 pester))
220 asyncio.ensure_future(self.standup_priv_msg( 232 asyncio.ensure_future(self.standup_priv_msg(
221 connection, user_id, channel, pester)) 233 connection, user, channel, pester))
234
235 def clear_old_standup_message_schedule_func(self, user):
236 """
237 This function is scheduled to remove old standup messages so that the
238 user is asked about standup the following day.
239 """
240 del self.users_awaiting_reply[user]['clear_standup_msg_f']
241 del self.users_awaiting_reply[user]['standup_msg']
222 242
223 async def start_standup(self, connection, channel): 243 async def start_standup(self, connection, channel):
224 """ 244 """
225 Notify the channel that the standup is about to begin, then loop through 245 Notify the channel that the standup is about to begin, then loop through
226 all the users in the channel asking them report their standup. 246 all the users in the channel asking them report their standup.
227 """ 247 """
228 await connection.say('@channel Time for standup', channel)
229 users = connection.get_users_by_channel(channel) 248 users = connection.get_users_by_channel(channel)
249 if not users:
250 self.log.error('Unable to get_users_by_channel for channel '
251 '{}. Skipping standup.'.format(channel))
252 return
253 await connection.say('@channel Time for standup', channel)
230 254
231 for u in users: 255 for u in users:
232 if u == connection.my_id or \ 256 if u == connection.nick or \
233 u in self.standup_schedules[channel]['ignoring']: 257 u in self.standup_schedules[channel]['ignoring']:
234 continue 258 continue
235 259
236 await self.standup_priv_msg(connection, u, channel) 260 if u in self.users_awaiting_reply and \
261 'standup_msg' in self.users_awaiting_reply[u]:
262 await connection.say('{}: {}'.format(
263 u, self.users_awaiting_reply[u]['standup_msg']), channel)
264 else:
265 await self.standup_priv_msg(connection, u, channel)
237 266
238 async def standup_priv_msg(self, connection, user_id, channel, pester=600): 267 async def standup_priv_msg(self, connection, user, channel, pester=600):
239 """ 268 """
240 Send a private message to ``user_id`` asking for their standup update. 269 Send a private message to ``user`` asking for their standup update.
241 270
242 Args: 271 Args:
243 connection (:class:`warmachine.base.Connection'): Connection object 272 connection (:class:`warmachine.base.Connection'): Connection object
244 to use. 273 to use.
245 user_id (str): User name or id to send the message to. 274 user (str): User to send the message to.
246 channel (str): The channel the standup is for 275 channel (str): The channel the standup is for
247 pester (int): Number of seconds to wait until asking the user again. 276 pester (int): Number of seconds to wait until asking the user again.
248 Use 0 to disable 277 Use 0 to disable
249 """ 278 """
250 dm_id = connection.get_dm_id_by_user(user_id) 279 self.log.debug('Messaging user: {}'.format(user))
251 280
252 self.log.debug('Messaging user: {} ({})'.format( 281 if user in self.users_awaiting_reply:
253 connection.user_map[user_id], user_id)) 282 self.users_awaiting_reply[user]['for_channels'].append(channel)
254 283
255 self.users_awaiting_reply[dm_id] = { 284 self.log.debug('Adding to list of users waiting on a reply for: '
256 'for_channel': channel, 285 '{}'.format(
257 'user': user_id 286 self.users_awaiting_reply[user]))
258 } 287 else:
288 self.users_awaiting_reply[user] = {
289 'for_channels': [channel, ],
290 }
259 291
260 self.log.debug('Adding to list of users waiting on a reply for: '
261 '{}'.format(pformat(self.users_awaiting_reply[dm_id])))
262 292
263 await connection.say('What did you do yesterday? What will you ' 293 await connection.say('What did you do yesterday? What will you '
264 'do today? do you have any blockers? ' 294 'do today? do you have any blockers? '
265 '(standup for:{})'.format(channel), dm_id) 295 '(standup for:{})'.format(channel), user)
266 296
267 if pester > 0: 297 if pester > 0:
268 f = self._loop.call_later( 298 f = self._loop.call_later(
269 pester, functools.partial( 299 pester, functools.partial(
270 self.pester_schedule_func, connection, user_id, channel, 300 self.pester_schedule_func, connection, user, channel,
271 pester)) 301 pester))
272 self.users_awaiting_reply[dm_id]['pester_task'] = f 302 self.users_awaiting_reply[user]['pester_task'] = f
273 303
274 304
275 @classmethod 305 @classmethod