diff options
| author | jason | 2016-08-17 15:52:43 -0600 |
|---|---|---|
| committer | jason | 2016-08-17 15:52:43 -0600 |
| commit | b5322db38973661b08be27185420163291a1766a (patch) | |
| tree | 91198f948756ad98c0cb09f1cb959d9dbfc2bf33 /warmachine/connections/slack.py | |
| parent | 1c123d6f199e1d0b88019d56a4bb1da082bdc79f (diff) | |
| download | warmachine-ng-b5322db38973661b08be27185420163291a1766a.tar.gz warmachine-ng-b5322db38973661b08be27185420163291a1766a.zip | |
Commit updates that i haven't commited yet
Diffstat (limited to 'warmachine/connections/slack.py')
| -rw-r--r-- | warmachine/connections/slack.py | 81 |
1 files changed, 52 insertions, 29 deletions
diff --git a/warmachine/connections/slack.py b/warmachine/connections/slack.py index 8ae52da..384a1f3 100644 --- a/warmachine/connections/slack.py +++ b/warmachine/connections/slack.py | |||
| @@ -27,6 +27,7 @@ class SlackWS(Connection): | |||
| 27 | self.reconnect_url = '' | 27 | self.reconnect_url = '' |
| 28 | 28 | ||
| 29 | self.channel_map = {} # channel and im info keyed by the slack id | 29 | self.channel_map = {} # channel and im info keyed by the slack id |
| 30 | self.channel_name_to_id = {} # slack channel/group name mapped to the id | ||
| 30 | self.user_map = {} # user info keyed by their slack id | 31 | self.user_map = {} # user info keyed by their slack id |
| 31 | self.user_nick_to_id = {} # slack user id mapped to the (nick)name | 32 | self.user_nick_to_id = {} # slack user id mapped to the (nick)name |
| 32 | 33 | ||
| @@ -46,21 +47,26 @@ class SlackWS(Connection): | |||
| 46 | self.host = self.authenticate() | 47 | self.host = self.authenticate() |
| 47 | self.log.info('Connecting to {}'.format(self.host)) | 48 | self.log.info('Connecting to {}'.format(self.host)) |
| 48 | self.ws = await websockets.connect(self.host) | 49 | self.ws = await websockets.connect(self.host) |
| 50 | self.STATUS = CONNECTED | ||
| 51 | |||
| 52 | return True | ||
| 49 | 53 | ||
| 50 | async def read(self): | 54 | async def read(self): |
| 51 | if self.ws: | 55 | if self.ws: |
| 52 | message = json.loads(await self.ws.recv()) | 56 | message = json.loads(await self.ws.recv()) |
| 53 | # Slack is acknowledging a message was sent. Do nothing | 57 | # Slack is acknowledging a message was sent. Do nothing |
| 54 | if 'type' not in message and 'reply_to' in message: | 58 | if 'reply_to' in message: |
| 55 | # {'ok': True, | 59 | # {'ok': True, |
| 56 | # 'reply_to': 1, | 60 | # 'reply_to': 1, |
| 57 | # 'text': "['!whois', 'synic']", | 61 | # 'text': "['!whois', 'synic']", |
| 58 | # 'ts': '1469743355.000150'} | 62 | # 'ts': '1469743355.000150'} |
| 63 | self.log.debug('Ignoring reply_to message: {}'.format( | ||
| 64 | pformat(message))) | ||
| 59 | return | 65 | return |
| 60 | 66 | ||
| 61 | self.log.debug('new message parsed: {}'.format(message)) | 67 | self.log.debug('new slack message: {}'.format(pformat(message))) |
| 62 | # Handle actual messages | ||
| 63 | if message['type'] == 'message' and 'subtype' not in message: | 68 | if message['type'] == 'message' and 'subtype' not in message: |
| 69 | # Handle text messages from users | ||
| 64 | return await self.process_message(message) | 70 | return await self.process_message(message) |
| 65 | else: | 71 | else: |
| 66 | if 'subtype' in message: | 72 | if 'subtype' in message: |
| @@ -69,6 +75,8 @@ class SlackWS(Connection): | |||
| 69 | msgtype = '{}_{}'.format( | 75 | msgtype = '{}_{}'.format( |
| 70 | message['type'], message['subtype']) | 76 | message['type'], message['subtype']) |
| 71 | else: | 77 | else: |
| 78 | # This is a non-message event from slack. | ||
| 79 | # https://api.slack.com/events | ||
| 72 | msgtype = message['type'] | 80 | msgtype = message['type'] |
| 73 | 81 | ||
| 74 | # Look for on_{type} methods to pass the dictionary to for | 82 | # Look for on_{type} methods to pass the dictionary to for |
| @@ -80,18 +88,21 @@ class SlackWS(Connection): | |||
| 80 | self.log.debug('{} does not exist for message: {}'.format( | 88 | self.log.debug('{} does not exist for message: {}'.format( |
| 81 | func_name, message)) | 89 | func_name, message)) |
| 82 | 90 | ||
| 83 | async def say(self, message, destination_id): | 91 | async def say(self, message, destination): |
| 84 | """ | 92 | """ |
| 85 | Say something in the provided channel or IM by id | 93 | Say something in the provided channel or IM by id |
| 86 | """ | 94 | """ |
| 87 | # If the destination is a user, figure out the DM channel id | 95 | # If the destination is a user, figure out the DM channel id |
| 88 | if destination_id.startswith('U'): | 96 | if destination and destination.startswith('#'): |
| 89 | destination_id = self.get_dm_id_by_user(destination_id) | 97 | destination = self.channel_name_to_id[destination.replace('#','')] |
| 98 | else: | ||
| 99 | _user = self.user_nick_to_id[destination] | ||
| 100 | destination = self.get_dm_id_by_user(_user) | ||
| 90 | 101 | ||
| 91 | message = { | 102 | message = { |
| 92 | 'id': 1, # TODO: this should be a get_msgid call or something | 103 | 'id': 1, # TODO: this should be a get_msgid call or something |
| 93 | 'type': 'message', | 104 | 'type': 'message', |
| 94 | 'channel': destination_id, | 105 | 'channel': destination, |
| 95 | 'text': str(message) | 106 | 'text': str(message) |
| 96 | } | 107 | } |
| 97 | self.log.debug("Saying {}".format(message)) | 108 | self.log.debug("Saying {}".format(message)) |
| @@ -124,25 +135,24 @@ class SlackWS(Connection): | |||
| 124 | raise Exception('Slack Error: {}'.format( | 135 | raise Exception('Slack Error: {}'.format( |
| 125 | self._info.get('error', 'Unknown Error'))) | 136 | self._info.get('error', 'Unknown Error'))) |
| 126 | 137 | ||
| 127 | self.process_connect_info() | 138 | # Slack returns a huge json struct with a bunch of information |
| 139 | self.process_connect_info(self._info) | ||
| 128 | 140 | ||
| 129 | self.log.debug('Got websocket url: {}'.format(self._info.get('url'))) | 141 | self.log.debug('Got websocket url: {}'.format(self._info.get('url'))) |
| 130 | return self._info.get('url') | 142 | return self._info.get('url') |
| 131 | 143 | ||
| 132 | def process_connect_info(self): | 144 | def process_connect_info(self, info): |
| 133 | """ | 145 | """ |
| 134 | Processes the connection info provided by slack | 146 | Processes the connection info provided by slack |
| 135 | """ | 147 | """ |
| 136 | if not self._info: | 148 | # If there is nothing to process then return |
| 149 | if not info: | ||
| 137 | return | 150 | return |
| 138 | with open('slack_info.json', 'w') as f: | ||
| 139 | f.write(pformat(self._info)) | ||
| 140 | |||
| 141 | self.status = CONNECTED | ||
| 142 | 151 | ||
| 143 | # Save the bot's id | 152 | # Save the bot's id |
| 144 | try: | 153 | try: |
| 145 | self.my_id = self._info['self'].get('id', '000') | 154 | self.my_id = self._info['self'].get('id', '000') |
| 155 | self.nick = self._info['self'].get('name', None) | ||
| 146 | except KeyError: | 156 | except KeyError: |
| 147 | self.log.error('Unable to read self section of connect info') | 157 | self.log.error('Unable to read self section of connect info') |
| 148 | 158 | ||
| @@ -158,26 +168,35 @@ class SlackWS(Connection): | |||
| 158 | # Map Channels | 168 | # Map Channels |
| 159 | for c in self._info.get('channels', []): | 169 | for c in self._info.get('channels', []): |
| 160 | self.channel_map[c['id']] = c | 170 | self.channel_map[c['id']] = c |
| 171 | self.channel_name_to_id[c['name']] = c['id'] | ||
| 161 | 172 | ||
| 162 | for g in self._info.get('groups', []): | 173 | for g in self._info.get('groups', []): |
| 163 | self.channel_map[g['id']] = g | 174 | self.channel_map[g['id']] = g |
| 175 | self.channel_name_to_id[g['name']] = g['id'] | ||
| 164 | 176 | ||
| 165 | async def process_message(self, msg): | 177 | async def process_message(self, msg): |
| 166 | # Built-in !whois action | ||
| 167 | if 'text' not in msg: | 178 | if 'text' not in msg: |
| 168 | raise Exception(msg) | 179 | raise Exception(msg) |
| 180 | |||
| 181 | # Built-in !whois command. Return information about a particular user. | ||
| 169 | if msg['text'].startswith('!whois'): | 182 | if msg['text'].startswith('!whois'): |
| 170 | nicknames = msg['text'].split(' ')[1:] | 183 | nicknames = msg['text'].split(' ')[1:] |
| 171 | for n in nicknames: | 184 | for n in nicknames: |
| 172 | await self.say(pformat(self.user_map[self.user_nick_to_id[n]]), | 185 | await self.say(pformat(self.user_map[self.user_nick_to_id[n]]), |
| 173 | msg['channel']) | 186 | msg['channel']) |
| 174 | return | 187 | return |
| 175 | elif msg['text'].startswith('!looptime'): | 188 | |
| 176 | await self.say(self._loop.time(), msg['channel']) | 189 | # Map the slack ids to usernames and channels/groups names |
| 190 | user_nickname = self.user_map[msg['user']]['name'] | ||
| 191 | if msg['channel'].startswith('D'): | ||
| 192 | # This is a private message | ||
| 193 | channel = None | ||
| 194 | else: | ||
| 195 | channel = '#{}'.format(self.channel_map[msg['channel']]['name']) | ||
| 177 | 196 | ||
| 178 | retval = { | 197 | retval = { |
| 179 | 'sender': msg['user'], | 198 | 'sender': user_nickname, |
| 180 | 'channel': msg['channel'], | 199 | 'channel': channel, |
| 181 | 'message': msg['text'] | 200 | 'message': msg['text'] |
| 182 | } | 201 | } |
| 183 | return retval | 202 | return retval |
| @@ -191,16 +210,15 @@ class SlackWS(Connection): | |||
| 191 | https://api.slack.com/events/user_change | 210 | https://api.slack.com/events/user_change |
| 192 | """ | 211 | """ |
| 193 | user_info = msg['user'] | 212 | user_info = msg['user'] |
| 213 | |||
| 214 | self.user_map[user_info['id']] = user_info | ||
| 215 | |||
| 216 | # Update the nick mapping if the user changed their nickname | ||
| 194 | try: | 217 | try: |
| 195 | old_nick = self.user_map[user_info['id']]['nick'] | 218 | old_nick = self.user_map[user_info['id']]['nick'] |
| 196 | except KeyError as e: | 219 | except KeyError as e: |
| 197 | old_nick = None | 220 | old_nick = None |
| 198 | self.log.exception('KeyError: {}'.format(e)) | ||
| 199 | self.log.exception('{}'.format(msg)) | ||
| 200 | |||
| 201 | self.user_map[user_info['id']] = user_info | ||
| 202 | 221 | ||
| 203 | # Update the nick mapping if the user changed their nickname | ||
| 204 | if old_nick and old_nick != user_info['nick']: | 222 | if old_nick and old_nick != user_info['nick']: |
| 205 | del self.user_nick_to_id[old_nick] | 223 | del self.user_nick_to_id[old_nick] |
| 206 | self.user_nick_to_id[user_info['nick']] = user_info['id'] | 224 | self.user_nick_to_id[user_info['nick']] = user_info['id'] |
| @@ -225,6 +243,7 @@ class SlackWS(Connection): | |||
| 225 | )) | 243 | )) |
| 226 | self.user_map[msg['user']]['presence'] = msg['presence'] | 244 | self.user_map[msg['user']]['presence'] = msg['presence'] |
| 227 | 245 | ||
| 246 | @memoize # the dm id should never change | ||
| 228 | def get_dm_id_by_user(self, user_id): | 247 | def get_dm_id_by_user(self, user_id): |
| 229 | """ | 248 | """ |
| 230 | Return the channel id for a direct message to a specific user. | 249 | Return the channel id for a direct message to a specific user. |
| @@ -251,8 +270,9 @@ class SlackWS(Connection): | |||
| 251 | 270 | ||
| 252 | return data['channel']['id'] | 271 | return data['channel']['id'] |
| 253 | 272 | ||
| 254 | |||
| 255 | def get_users_by_channel(self, channel): | 273 | def get_users_by_channel(self, channel): |
| 274 | channel = self.channel_name_to_id[channel.replace('#', '')] | ||
| 275 | |||
| 256 | if channel.startswith('G'): | 276 | if channel.startswith('G'): |
| 257 | key = 'group' | 277 | key = 'group' |
| 258 | elif channel.startswith('C'): | 278 | elif channel.startswith('C'): |
| @@ -267,14 +287,17 @@ class SlackWS(Connection): | |||
| 267 | 'channel': channel, | 287 | 'channel': channel, |
| 268 | })) | 288 | })) |
| 269 | 289 | ||
| 270 | self.log.debug(url) | 290 | self.log.debug('Gathering list of users for channel {} from: {}'.format( |
| 291 | channel, url)) | ||
| 271 | req = urllib.request.Request(url) | 292 | req = urllib.request.Request(url) |
| 272 | r = json.loads(urllib.request.urlopen(req).read().decode('utf-8')) | 293 | r = json.loads(urllib.request.urlopen(req).read().decode('utf-8')) |
| 273 | 294 | ||
| 274 | self.log.debug(r) | 295 | users = [] |
| 296 | for u_id in r[key]['members']: | ||
| 297 | users.append(self.user_map[u_id]['name']) | ||
| 275 | 298 | ||
| 276 | self.log.debug(pformat(r[key]['members'])) | 299 | self.log.debug(pformat(users)) |
| 277 | return r[key]['members'] | 300 | return users |
| 278 | 301 | ||
| 279 | async def on_group_join(self, channel): | 302 | async def on_group_join(self, channel): |
| 280 | """ | 303 | """ |