diff --git a/examples/fetch.py b/examples/fetch.py index e59ec63..878300d 100644 --- a/examples/fetch.py +++ b/examples/fetch.py @@ -8,8 +8,8 @@ client = Client('', '') # Fetches a list of all users you're currently chatting with, as `User` objects users = client.fetchAllUsers() -print("users' IDs: {}".format(user.uid for user in users)) -print("users' names: {}".format(user.name for user in users)) +print("users' IDs: {}".format([user.uid for user in users])) +print("users' names: {}".format([user.name for user in users])) # If we have a user id, we can use `fetchUserInfo` to fetch a `User` object @@ -18,7 +18,7 @@ user = client.fetchUserInfo('')[''] users = client.fetchUserInfo('<1st user id>', '<2nd user id>', '<3rd user id>') print("user's name: {}".format(user.name)) -print("users' names: {}".format(users[k].name for k in users)) +print("users' names: {}".format([users[k].name for k in users])) # `searchForUsers` searches for the user and gives us a list of the results, diff --git a/fbchat/__init__.py b/fbchat/__init__.py index 8b048df..25dd5d4 100644 --- a/fbchat/__init__.py +++ b/fbchat/__init__.py @@ -15,7 +15,7 @@ from __future__ import unicode_literals from .client import * __title__ = 'fbchat' -__version__ = '1.3.9' +__version__ = '1.4.0' __description__ = 'Facebook Chat (Messenger) for Python' __copyright__ = 'Copyright 2015 - 2018 by Taehoon Kim' diff --git a/fbchat/client.py b/fbchat/client.py index e943200..69c858d 100644 --- a/fbchat/client.py +++ b/fbchat/client.py @@ -544,7 +544,6 @@ class Client(object): return [graphql_to_page(node) for node in j[name]['pages']['nodes']] - # TODO intergrate Rooms def searchForGroups(self, name, limit=1): """ Find and get group thread by its name @@ -585,7 +584,6 @@ class Client(object): elif node['__typename'] == 'Group': # We don't handle Facebook "Groups" pass - # TODO Add Rooms else: log.warning('Unknown __typename: {} in {}'.format(repr(node['__typename']), node)) @@ -818,9 +816,6 @@ class Client(object): if entry.get('thread_type') == 'GROUP': _id = entry['thread_key']['thread_fbid'] rtn[_id] = graphql_to_group(entry) - elif entry.get('thread_type') == 'ROOM': - _id = entry['thread_key']['thread_fbid'] - rtn[_id] = graphql_to_room(entry) elif entry.get('thread_type') == 'ONE_TO_ONE': _id = entry['thread_key']['other_user_id'] if pages_and_users.get(_id) is None: @@ -1976,7 +1971,7 @@ class Client(object): 'sticky_token': sticky, 'sticky_pool': pool, 'viewer_uid': self.uid, - 'state': 'active' + 'state': 'active', } self._get(self.req_url.PING, data, fix_request=True, as_json=False) @@ -1996,7 +1991,7 @@ class Client(object): return j['lb_info']['sticky'], j['lb_info']['pool'] - def _pullMessage(self, sticky, pool): + def _pullMessage(self, sticky, pool, markAlive=True): """Call pull api with seq value to get message data.""" data = { @@ -2004,6 +1999,7 @@ class Client(object): "sticky_token": sticky, "sticky_pool": pool, "clientid": self.client_id, + 'state': 'active' if markAlive else 'offline', } j = self._get(ReqUrl.STICKY, data, fix_request=True, as_json=True) @@ -2374,7 +2370,7 @@ class Client(object): try: if markAlive: self._ping(self.sticky, self.pool) - content = self._pullMessage(self.sticky, self.pool) + content = self._pullMessage(self.sticky, self.pool, markAlive) if content: self._parseMessage(content) except KeyboardInterrupt: diff --git a/fbchat/graphql.py b/fbchat/graphql.py index ba73903..f023a16 100644 --- a/fbchat/graphql.py +++ b/fbchat/graphql.py @@ -42,7 +42,7 @@ def get_customization_info(thread): 'emoji': info.get('emoji'), 'color': graphql_color_to_enum(info.get('outgoing_bubble_color')) } - if thread.get('thread_type') in ('GROUP', 'ROOM') or thread.get('is_group_thread') or thread.get('thread_key', {}).get('thread_fbid'): + if thread.get('thread_type') == 'GROUP' or thread.get('is_group_thread') or thread.get('thread_key', {}).get('thread_fbid'): rtn['nicknames'] = {} for k in info.get('participant_customizations', []): rtn['nicknames'][k['participant_id']] = k.get('nickname') @@ -220,7 +220,9 @@ def graphql_to_user(user): if user.get('profile_picture') is None: user['profile_picture'] = {} c_info = get_customization_info(user) - plan = graphql_to_plan(user['event_reminders']['nodes'][0]) if user.get('event_reminders', dict()).get('nodes') else None + plan = None + if user.get('event_reminders'): + plan = graphql_to_plan(user['event_reminders']['nodes'][0]) if user['event_reminders'].get('nodes') else None return User( user['id'], url=user.get('url'), @@ -258,7 +260,9 @@ def graphql_to_thread(thread): else: last_name = user.get('name').split(first_name, 1).pop().strip() - plan = graphql_to_plan(thread['event_reminders']['nodes'][0]) if thread.get('event_reminders', dict()).get('nodes') else None + plan = None + if thread.get('event_reminders'): + plan = graphql_to_plan(thread['event_reminders']['nodes'][0]) if thread['event_reminders'].get('nodes') else None return User( user['id'], @@ -288,13 +292,19 @@ def graphql_to_group(group): last_message_timestamp = None if 'last_message' in group: last_message_timestamp = group['last_message']['nodes'][0]['timestamp_precise'] - plan = graphql_to_plan(group['event_reminders']['nodes'][0]) if group.get('event_reminders', dict()).get('nodes') else None + plan = None + if group.get('event_reminders'): + plan = graphql_to_plan(group['event_reminders']['nodes'][0]) if group['event_reminders'].get('nodes') else None return Group( group['thread_key']['thread_fbid'], participants=set([node['messaging_actor']['id'] for node in group['all_participants']['nodes']]), nicknames=c_info.get('nicknames'), color=c_info.get('color'), emoji=c_info.get('emoji'), + admins = set([node.get('id') for node in group.get('thread_admins')]), + approval_mode = bool(group.get('approval_mode')), + approval_requests = set(node["requester"]['id'] for node in group['group_approval_queue']['nodes']), + join_link = group['joinable_mode'].get('link'), photo=group['image'].get('uri'), name=group.get('name'), message_count=group.get('messages_count'), @@ -302,34 +312,14 @@ def graphql_to_group(group): plan=plan, ) -def graphql_to_room(room): - if room.get('image') is None: - room['image'] = {} - c_info = get_customization_info(room) - plan = graphql_to_plan(room['event_reminders']['nodes'][0]) if room.get('event_reminders', dict()).get('nodes') else None - return Room( - room['thread_key']['thread_fbid'], - participants=set([node['messaging_actor']['id'] for node in room['all_participants']['nodes']]), - nicknames=c_info.get('nicknames'), - color=c_info.get('color'), - emoji=c_info.get('emoji'), - photo=room['image'].get('uri'), - name=room.get('name'), - message_count=room.get('messages_count'), - admins = set([node.get('id') for node in room.get('thread_admins')]), - approval_mode = bool(room.get('approval_mode')), - approval_requests = set(node.get('id') for node in room['thread_queue_metadata'].get('approval_requests', {}).get('nodes')), - join_link = room['joinable_mode'].get('link'), - privacy_mode = bool(room.get('privacy_mode')), - plan=plan, - ) - def graphql_to_page(page): if page.get('profile_picture') is None: page['profile_picture'] = {} if page.get('city') is None: page['city'] = {} - plan = graphql_to_plan(page['event_reminders']['nodes'][0]) if page.get('event_reminders', dict()).get('nodes') else None + plan = None + if page.get('event_reminders'): + plan = graphql_to_plan(page['event_reminders']['nodes'][0]) if page['event_reminders'].get('nodes') else None return Page( page['id'], url=page.get('url'), @@ -433,6 +423,40 @@ class GraphQL(object): }, outgoing_bubble_color, emoji + }, + thread_admins { + id + }, + group_approval_queue { + nodes { + requester { + id + } + } + }, + approval_mode, + joinable_mode { + mode, + link + }, + event_reminders { + nodes { + id, + lightweight_event_creator { + id + }, + time, + location_name, + event_title, + event_reminder_members { + edges { + node { + id + }, + guest_list_state + } + } + } } } """ diff --git a/fbchat/models.py b/fbchat/models.py index 5fce5a7..cb4f678 100644 --- a/fbchat/models.py +++ b/fbchat/models.py @@ -102,8 +102,16 @@ class Group(Thread): color = None #: The groups's default emoji emoji = None + # Set containing user IDs of thread admins + admins = None + # True if users need approval to join + approval_mode = None + # Set containing user IDs requesting to join + approval_requests = None + # Link for joining group + join_link = None - def __init__(self, uid, participants=None, nicknames=None, color=None, emoji=None, **kwargs): + def __init__(self, uid, participants=None, nicknames=None, color=None, emoji=None, admins=None, approval_mode=None, approval_requests=None, join_link=None, privacy_mode=None, **kwargs): """Represents a Facebook group. Inherits `Thread`""" super(Group, self).__init__(ThreadType.GROUP, uid, **kwargs) if participants is None: @@ -114,24 +122,6 @@ class Group(Thread): self.nicknames = nicknames self.color = color self.emoji = emoji - - -class Room(Group): - # Set containing user IDs of thread admins - admins = None - # True if users need approval to join - approval_mode = None - # Set containing user IDs requesting to join - approval_requests = None - # Link for joining room - join_link = None - # True is room is not discoverable - privacy_mode = None - - def __init__(self, uid, admins=None, approval_mode=None, approval_requests=None, join_link=None, privacy_mode=None, **kwargs): - """Represents a Facebook room. Inherits `Group`""" - super(Room, self).__init__(uid, **kwargs) - self.type = ThreadType.ROOM if admins is None: admins = set() self.admins = admins @@ -140,6 +130,16 @@ class Room(Group): approval_requests = set() self.approval_requests = approval_requests self.join_link = join_link + + +class Room(Group): + # True is room is not discoverable + privacy_mode = None + + def __init__(self, uid, privacy_mode=None, **kwargs): + """Deprecated. Use :class:`Group` instead""" + super(Room, self).__init__(uid, **kwargs) + self.type = ThreadType.ROOM self.privacy_mode = privacy_mode @@ -530,8 +530,8 @@ class ThreadType(Enum): """Used to specify what type of Facebook thread is being used. See :ref:`intro_threads` for more info""" USER = 1 GROUP = 2 + ROOM = 2 PAGE = 3 - ROOM = 4 class ThreadLocation(Enum): """Used to specify where a thread is located (inbox, pending, archived, other)."""