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 bootstrap(self): # Bootstrap must provide a way to to have: # a) self.normalize_incoming_event fired, or incoming events put into self.incoming_queue # b) any necessary threads running for a) # c) self.me (Person) defined, with Will's info # d) self.people (dict of People) defined, with everyone in an organization/backend # e) self.channels (dict of Channels) defined, with all available channels/rooms. # Note that Channel asks for members, a list of People. # f) A way for self.handle, self.me, self.people, and self.channels to be kept accurate, # with a maximum lag of 60 seconds. self.people = {} self.channels = {} self.me = Person( id="will", handle="will", mention_handle="@will", source=Bunch(), name="William T. Botterton", ) # Do this to get the first "you" prompt. self.pubsub.publish('message.incoming.stdin', (Message(content="", type="message.incoming", is_direct=True, is_private_chat=True, is_group_chat=False, backend=self.internal_name, sender=self.partner, will_is_mentioned=False, will_said_it=False, backend_supports_acl=False, original_incoming_event={})))
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 _person(fields): required_fields = { "id": "TBD", "mention_handle": "TBD", "source": "TBD", "handle": "TBD", "name": "TBD", "first_name": "TDB" } required_fields.update(fields) return Person(**required_fields)
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 update_will_roster_and_rooms(self): people = self.load('will_hipchat_people', {}) # Loop through the connected rooms (self.roster comes from ClientXMPP) for roster_id in self.roster: cur_roster = self.roster[roster_id] # Loop through the users in a given room for user_id in cur_roster: user_data = cur_roster[user_id] if user_data["name"] != "": # If we don't have this user in the people, add them. if not user_id in people: people[user_id] = Person() hipchat_id = user_id.split("@")[0].split("_")[1] # Update their info people[user_id].update({ "name": user_data["name"], "jid": user_id, "hipchat_id": hipchat_id, }) # If we don't have a nick yet, pull it and mention_name off the master user list. if not hasattr(people[user_id], "nick") and hipchat_id in self.people: user_data = self.get_user_list[hipchat_id] people[user_id].nick = user_data["mention_name"] people[user_id].mention_name = user_data[ "mention_name"] # If it's me, save that info! if people[user_id].get("name", "") == self.nick: self.me = people[user_id] self.save("will_hipchat_people", people) self.update_available_rooms()
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
class ShellBackend(StdInOutIOBackend): friendly_name = "Interactive Shell" internal_name = "will.backends.io_adapters.shell" partner = Person( id="you", handle="shelluser", mention_handle="@shelluser", source=Bunch(), name="Friend", ) def send_direct_message(self, message_body, **kwargs): print("Will: %s" % html_to_text(message_body)) def send_room_message(self, room_id, message_body, html=False, color="green", notify=False, **kwargs): print("Will: %s" % html_to_text(message_body)) def set_room_topic(self, topic): print("Will: Let's talk about %s" % (topic, )) def normalize_incoming_event(self, event): if event["type"] == "message.incoming.stdin": m = Message(content=event.data.content.strip(), type=event.type, is_direct=True, is_private_chat=True, is_group_chat=False, backend=self.internal_name, sender=self.partner, will_is_mentioned=False, will_said_it=False, backend_supports_acl=False, original_incoming_event=event) return m else: # An event type the shell has no idea how to handle. return None def handle_outgoing_event(self, event): # Print any replies. if event.type in ["say", "reply"]: self.send_direct_message(event.content) if event.type in [ "topic_change", ]: self.set_room_topic(event.content) elif event.type == "message.no_response": if event.data and hasattr( event.data, "original_incoming_event") and len( event.data.original_incoming_event.data.content) > 0: self.send_direct_message(random.choice(UNSURE_REPLIES)) # Regardless of whether or not we had something to say, # give the user a new prompt. sys.stdout.write("You: ") sys.stdout.flush() def bootstrap(self): # Bootstrap must provide a way to to have: # a) self.normalize_incoming_event fired, or incoming events put into self.incoming_queue # b) any necessary threads running for a) # c) self.me (Person) defined, with Will's info # d) self.people (dict of People) defined, with everyone in an organization/backend # e) self.channels (dict of Channels) defined, with all available channels/rooms. # Note that Channel asks for members, a list of People. # f) A way for self.handle, self.me, self.people, and self.channels to be kept accurate, # with a maximum lag of 60 seconds. self.people = {} self.channels = {} self.me = Person( id="will", handle="will", mention_handle="@will", source=Bunch(), name="William T. Botterton", ) # Do this to get the first "you" prompt. self.pubsub.publish('message.incoming.stdin', (Message(content="", type="message.incoming", is_direct=True, is_private_chat=True, is_group_chat=False, backend=self.internal_name, sender=self.partner, will_is_mentioned=False, will_said_it=False, backend_supports_acl=False, original_incoming_event={})))
def normalize_incoming_event(self, event): if ("type" in event and event["type"] == "message" and ("subtype" not in event or event["subtype"] != "message_changed") and # Ignore thread summary events (for now.) # TODO: We should stack these into the history. ("subtype" not in event or ("message" in event and "thread_ts" not in event["message"]) or event["subtype"] == "bot_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'} if event.get("subtype") == "bot_message": bot = self.get_bot(event["bot_id"]) sender = Person(id=event["bot_id"], mention_handle="<@%s>" % event["bot_id"], name=bot['name'], handle=bot['name'], source=event) event["text"] = event["attachments"][0]["fallback"] else: 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.get("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