diff options
Diffstat (limited to 'warmachine/addons/standup.py')
| -rw-r--r-- | warmachine/addons/standup.py | 190 |
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 |