def normalize_incoming_event(self, event): logging.debug("hipchat: normalize_incoming_event - %s" % event) if event["type"] in ("chat", "normal", "groupchat") and ("from_jid" in event or "from" in event): sender = self.get_user_from_message(event) interpolated_handle = "@%s" % self.me.handle will_is_mentioned = False will_said_it = False channel = None if "xmpp_jid" in event and event["xmpp_jid"]: channel = clean_for_pickling(self.channels[event["xmpp_jid"]]) is_private_chat = False else: if event["type"] in ("chat", "normal"): is_private_chat = True is_direct = False if is_private_chat or event["body"].startswith( interpolated_handle): is_direct = True if event["body"].startswith(interpolated_handle): event["body"] = event["body"][len(interpolated_handle):].strip( ) if interpolated_handle in event["body"]: will_is_mentioned = True if sender and self.me and sender.id == self.me.id: will_said_it = True m = Message( content=event["body"], is_direct=is_direct, is_private_chat=is_private_chat, is_group_chat=not is_private_chat, backend=self.internal_name, sender=sender, channel=channel, will_is_mentioned=will_is_mentioned, will_said_it=will_said_it, backend_supports_acl=True, original_incoming_event=clean_for_pickling(event), ) # print("normalized:") # print(m.__dict__) return m else: # print("Unknown event type") # print(event) return None
def _update_channels(self, client=None): "Updates our internal list of channels. Kind of expensive." channels = {} if client: for page in client.conversations_list( limit=self.PAGE_LIMIT, exclude_archived=True, types="public_channel,private_channel,mpim,im", ): for channel in page["channels"]: members = {} for m in channel.get("members", list()): if m in self.people: members[m] = self.people[m] channels[channel["id"]] = Channel( id=channel["id"], name=channel.get("name", channel["id"]), source=clean_for_pickling(channel), members=members, ) if len(channels.keys()) == 0: # Server isn't set up yet, and we're likely in a processing thread, if self.load("slack_channel_cache", None): self._channels = self.load("slack_channel_cache", None) else: self._channels = channels self.save("slack_channel_cache", channels)
def _update_people(self, client=None): "Updates our internal list of Slack users. Kind of expensive." people = {} if client: for page in client.users_list(limit=self.PAGE_LIMIT): for member in page["members"]: if member["deleted"]: continue member_id = member["id"] user_timezone = member.get("tz") people[member_id] = Person( id=member_id, mention_handle=member.get("mention_handle", ""), handle=member["name"], source=clean_for_pickling(member), name=member.get("real_name", ""), ) if member["name"] == self.handle: self.me = people[member_id] if user_timezone and user_timezone != "unknown": people[member_id].timezone = user_timezone if len(people.keys()) == 0: # Server isn't set up yet, and we're likely in a processing thread, if self.load("slack_people_cache", None): self._people = self.load("slack_people_cache", None) if self.me is None: self.me = self.load("slack_me_cache", None) if self.handle is None: self.handle = self.load("slack_handle_cache", None) else: self._people = people self.save("slack_people_cache", people) self.save("slack_me_cache", self.me) self.save("slack_handle_cache", self.handle)
def _rest_channels_list(self): logging.debug('Getting channel list from Rocket.Chat') # Remember to paginate. ;) count = 50 passes = 0 headers = {'X-Auth-Token': self.token, 'X-User-Id': self.userid} fetched = 0 total = 0 channels = {} while fetched <= total: r = requests.get('{}channels.list'.format(self.rocketchat_api_url), headers=headers) resp_json = r.json() total = resp_json['total'] for channel in resp_json['channels']: members = {} for username in channel['usernames']: userid = self._get_userid_from_username(username) members[userid] = self.people[userid] channels[channel['_id']] = Channel( id=channel['_id'], name=channel['name'], source=clean_for_pickling(channel), members=members) passes += 1 fetched = count * passes self.channels = channels
def _update_people(self): people = {} self.handle = self.client.server.username for k, v in self.client.server.users.items(): user_timezone = None if v.tz: user_timezone = v.tz people[k] = Person( id=v.id, mention_handle="<@%s>" % v.id, handle=v.name, source=clean_for_pickling(v), name=v.real_name, ) if v.name == self.handle: self.me = Person( id=v.id, mention_handle="<@%s>" % v.id, handle=v.name, source=clean_for_pickling(v), name=v.real_name, ) if user_timezone and user_timezone != 'unknown': people[k].timezone = user_timezone if v.name == self.handle: self.me.timezone = user_timezone if len(people.keys()) == 0: # Server isn't set up yet, and we're likely in a processing thread, if self.load("slack_people_cache", None): self._people = self.load("slack_people_cache", None) if not hasattr(self, "me") or not self.me: self.me = self.load("slack_me_cache", None) if not hasattr(self, "handle") or not self.handle: self.handle = self.load("slack_handle_cache", None) else: self._people = people self.save("slack_people_cache", people) self.save("slack_me_cache", self.me) self.save("slack_handle_cache", self.handle)
def people(self): if not hasattr(self, "_people"): full_roster = {} # Grab the first roster page, and populate full_roster url = ALL_USERS_URL % { "server": settings.HIPCHAT_SERVER, "token": settings.HIPCHAT_V2_TOKEN, "start_index": 0, "max_results": 1000 } r = requests.get(url, **settings.REQUESTS_OPTIONS) for user in r.json()['items']: full_roster["%s" % (user['id'], )] = Person( id=user["id"], handle=user["mention_name"], mention_handle="@%s" % user["mention_name"], source=clean_for_pickling(user), name=user["name"], ) # Keep going through the next pages until we're out of pages. while 'next' in r.json()['links']: url = "%s&auth_token=%s" % (r.json()['links']['next'], settings.HIPCHAT_V2_TOKEN) r = requests.get(url, **settings.REQUESTS_OPTIONS) for user in r.json()['items']: full_roster["%s" % (user['id'], )] = Person( id=user["id"], handle=user["mention_name"], mention_handle="@%s" % user["mention_name"], source=clean_for_pickling(user), name=user["name"], ) self._people = full_roster for k, u in full_roster.items(): if u.handle == settings.HIPCHAT_HANDLE: self.me = u return self._people
def _rest_users_list(self): logging.debug('Getting users list from Rocket.Chat') # Remember to paginate. ;) count = 50 passes = 0 headers = {'X-Auth-Token': self.token, 'X-User-Id': self.userid} fetched = 0 total = 0 self.handle = settings.ROCKETCHAT_USERNAME self.mention_handle = "@%s" % settings.ROCKETCHAT_USERNAME people = {} while fetched <= total: params = {'count': count, 'offset': fetched} r = requests.get('{}users.list'.format(self.rocketchat_api_url), headers=headers, params=params) resp_json = r.json() if resp_json['success'] is False: logging.exception('resp_json: {}'.format(resp_json)) total = resp_json['total'] for user in resp_json['users']: # TODO: Unlike slack.py, no timezone support at present. # RC returns utcOffset, but this isn't enough to # determine timezone. # TODO: Pickle error if timezone set to UTC, and I didn't # have a chance to report it. Using GMT as a poor substitute. person = Person( id=user['_id'], handle=user['username'], mention_handle="@%s" % user["username"], source=clean_for_pickling(user)['username'], name=user['name'], timezone='GMT' ) people[user['_id']] = person if user['username'] == self.handle: self.me = person passes += 1 fetched = count * passes self.people = people
def channels(self): if not hasattr(self, "_channels"): all_rooms = {} # Grab the first roster page, and populate all_rooms url = ALL_ROOMS_URL % { "server": settings.HIPCHAT_SERVER, "token": settings.HIPCHAT_V2_TOKEN, "start_index": 0, "max_results": 1000 } r = requests.get(url, **settings.REQUESTS_OPTIONS) for room in r.json()['items']: # print(room) all_rooms["%s" % (room['xmpp_jid'], )] = Channel( id=room["id"], name=room["name"], source=clean_for_pickling(room), members={}, ) # Keep going through the next pages until we're out of pages. while 'next' in r.json()['links']: url = "%s&auth_token=%s" % (r.json()['links']['next'], settings.HIPCHAT_V2_TOKEN) r = requests.get(url, **settings.REQUESTS_OPTIONS) for room in r.json()['items']: all_rooms["%s" % (room['xmpp_jid'], )] = Channel( id=room["id"], name=room["name"], source=clean_for_pickling(room), members={}) self._channels = all_rooms return self._channels
def _update_channels(self): channels = {} for c in self.client.server.channels: members = {} for m in c.members: members[m] = self.people[m] channels[c.id] = Channel(id=c.id, name=c.name, source=clean_for_pickling(c), members=members) if len(channels.keys()) == 0: # Server isn't set up yet, and we're likely in a processing thread, if self.load("slack_channel_cache", None): self._channels = self.load("slack_channel_cache", None) else: self._channels = channels self.save("slack_channel_cache", channels)
def normalize_incoming_event(self, event): "Makes a Slack event look like all the other events we handle" event_type = event.get("type") event_subtype = event.get("subtype") logging.debug("event type: %s, subtype: %s", event_type, event_subtype) if ((event_subtype is None and event_type not in ["message_changed", "message.incoming"]) # Ignore thread summary events (for now.) and (event_subtype is None or ("message" in event and "thread_ts" not in event["message"]))): # print("slack: normalize_incoming_event - %s" % event) # Sample of group message # {u'source_team': u'T5ACF70KV', u'text': u'test', # u'ts': u'1495661121.838366', u'user': u'U5ACF70RH', # u'team': u'T5ACF70KV', u'type': u'message', u'channel': u'C5JDAR2S3'} # Sample of 1-1 message # {u'source_team': u'T5ACF70KV', u'text': u'test', # u'ts': u'1495662397.335424', u'user': u'U5ACF70RH', # u'team': u'T5ACF70KV', u'type': u'message', u'channel': u'D5HGP0YE7'} # Threaded message # {u'event_ts': u'1507601477.000073', u'ts': u'1507601477.000073', # u'subtype': u'message_replied', u'message': # {u'thread_ts': u'1507414046.000010', u'text': u'hello!', # u'ts': u'1507414046.000010', u'unread_count': 2, # u'reply_count': 2, u'user': u'U5GUL9D9N', u'replies': # [{u'user': u'U5ACF70RH', u'ts': u'1507601449.000007'}, { # u'user': u'U5ACF70RH', u'ts': u'1507601477.000063'}], # u'type': u'message', u'bot_id': u'B5HL9ABFE'}, # u'type': u'message', u'hidden': True, u'channel': u'D5HGP0YE7'} logging.debug("we like that event!") sender = self.people[event["user"]] channel = self.get_channel_from_name(event["channel"]) is_private_chat = getattr(channel, "is_private", False) is_direct = getattr(getattr(channel, "source", None), 'is_im', False) channel = clean_for_pickling(channel) # print "channel: %s" % channel interpolated_handle = "<@%s>" % self.me.id real_handle = "@%s" % self.me.handle will_is_mentioned = False will_said_it = False thread = None if "thread_ts" in event: thread = event["thread_ts"] if interpolated_handle in event["text"] or real_handle in event[ "text"]: will_is_mentioned = True if event["text"].startswith(interpolated_handle): event["text"] = event["text"][len(interpolated_handle):] if event["text"].startswith(real_handle): event["text"] = event["text"][len(real_handle):] # sometimes autocomplete adds a : to the usename, but it's certainly extraneous. if will_is_mentioned and event["text"][0] == ":": event["text"] = event["text"][1:] if event["user"] == self.me.id: will_said_it = True m = Message( content=event["text"].strip(), type=event_type, is_direct=is_direct or will_is_mentioned, is_private_chat=is_private_chat, is_group_chat=not (is_private_chat or is_direct), backend=self.internal_name, sender=sender, channel=channel, thread=thread, will_is_mentioned=will_is_mentioned, will_said_it=will_said_it, backend_supports_acl=True, original_incoming_event=clean_for_pickling(event), ) return m # An event type the slack ba has no idea how to handle. return None
def normalize_incoming_event(self, event): if ( "type" in event and event["type"] == "message" and ("subtype" not in event or event["subtype"] != "message_changed") # Ignore thread summary events (for now.) # TODO: We should stack these into the history. and ("subtype" not in event or ("message" in event and "thread_ts" not in event["message"])) ): # print("slack: normalize_incoming_event - %s" % event) # Sample of group message # {u'source_team': u'T5ACF70KV', u'text': u'test', # u'ts': u'1495661121.838366', u'user': u'U5ACF70RH', # u'team': u'T5ACF70KV', u'type': u'message', u'channel': u'C5JDAR2S3'} # Sample of 1-1 message # {u'source_team': u'T5ACF70KV', u'text': u'test', # u'ts': u'1495662397.335424', u'user': u'U5ACF70RH', # u'team': u'T5ACF70KV', u'type': u'message', u'channel': u'D5HGP0YE7'} # Threaded message # {u'event_ts': u'1507601477.000073', u'ts': u'1507601477.000073', # u'subtype': u'message_replied', u'message': # {u'thread_ts': u'1507414046.000010', u'text': u'hello!', # u'ts': u'1507414046.000010', u'unread_count': 2, # u'reply_count': 2, u'user': u'U5GUL9D9N', u'replies': # [{u'user': u'U5ACF70RH', u'ts': u'1507601449.000007'}, { # u'user': u'U5ACF70RH', u'ts': u'1507601477.000063'}], # u'type': u'message', u'bot_id': u'B5HL9ABFE'}, # u'type': u'message', u'hidden': True, u'channel': u'D5HGP0YE7'} sender = self.people[event["user"]] channel = clean_for_pickling(self.channels[event["channel"]]) # print "channel: %s" % channel interpolated_handle = "<@%s>" % self.me.id real_handle = "@%s" % self.me.handle will_is_mentioned = False will_said_it = False is_private_chat = False thread = None if "thread_ts" in event: thread = event["thread_ts"] # If the parent thread is a 1-1 between Will and I, also treat that as direct. # Since members[] still comes in on the thread event, we can trust this, even if we're # in a thread. if channel.id == channel.name: is_private_chat = True # <@U5GUL9D9N> hi # TODO: if there's a thread with just will and I on it, treat that as direct. is_direct = False if is_private_chat or event["text"].startswith(interpolated_handle) or event["text"].startswith(real_handle): is_direct = True if event["text"].startswith(interpolated_handle): event["text"] = event["text"][len(interpolated_handle):].strip() if event["text"].startswith(real_handle): event["text"] = event["text"][len(real_handle):].strip() if interpolated_handle in event["text"] or real_handle in event["text"]: will_is_mentioned = True if event["user"] == self.me.id: will_said_it = True m = Message( content=event["text"], type=event["type"], is_direct=is_direct, is_private_chat=is_private_chat, is_group_chat=not is_private_chat, backend=self.internal_name, sender=sender, channel=channel, thread=thread, will_is_mentioned=will_is_mentioned, will_said_it=will_said_it, backend_supports_acl=True, original_incoming_event=clean_for_pickling(event), ) return m else: # An event type the slack ba has no idea how to handle. pass
def normalize_incoming_event(self, event): logging.info('Normalizing incoming Rocket.Chat event') logging.debug('event: {}'.format(self.pp.pformat(event))) if event["type"] == "message": # Were we mentioned? will_is_mentioned = False for mention in event['mentions']: if mention['username'] == self.me.handle: will_is_mentioned = True break # Handle direct messages, which in Rocket.Chat are a rid # made up of both users' _ids. is_private_chat = False if self.me.id in event["rid"]: is_private_chat = True # Create a "Channel" to align with Rocket.Chat DM # paradigm. There might well be a better way of doing # this. See TODO in _rest_channels_list. sender_id = event['u']['_id'] ids = [sender_id, self.me.id] ids.sort() channel_id = '{}{}'.format(*ids) sender = self.people[sender_id] channel_members = {} channel_members[sender_id] = sender channel_members[self.me.id] = self.me channel = Channel(id=channel_id, name=channel_id, source=clean_for_pickling(channel_id), members=channel_members) else: if "rid" in event and event["rid"] in self.channels: channel = clean_for_pickling(self.channels[event["rid"]]) else: # Private channel, unknown members. Just do our best and try to route it. if "rid" in event: channel = Channel(id=event["rid"], name=event["rid"], source=clean_for_pickling( event["rid"]), members={}) logging.debug('channel: {}'.format(channel)) # Set various variables depending on whether @handle was # part of the message. interpolated_handle = "@%s " % self.handle logging.debug( 'interpolated_handle: {}'.format(interpolated_handle)) is_direct = False if is_private_chat or event['msg'].startswith(interpolated_handle): is_direct = True # Strip my handle from the start. NB Won't strip it from # elsewhere in the text, and won't strip other mentions. # This will stop regexes from working, not sure if it's a # feature or a bug. if event['msg'].startswith(interpolated_handle): event['msg'] = event['msg'][len(interpolated_handle):].strip() if interpolated_handle in event['msg']: will_is_mentioned = True # Determine if Will said it. logging.debug('self.people: {}'.format(self.pp.pformat( self.people))) sender = self.people[event['u']['_id']] logging.debug('sender: {}'.format(sender)) if sender['handle'] == self.me.handle: logging.debug('Will said it') will_said_it = True else: logging.debug('Will didnt say it') will_said_it = False m = Message(content=event['msg'], type=event.type, is_direct=is_direct, is_private_chat=is_private_chat, is_group_chat=not is_private_chat, backend=self.internal_name, sender=sender, channel=channel, will_is_mentioned=will_is_mentioned, will_said_it=will_said_it, backend_supports_acl=True, original_incoming_event=clean_for_pickling(event)) return m else: logging.debug( 'Passing, I dont know how to normalize this event of type ', event["type"]) pass