From c12dcd9263002424cbdadf4fda291da688d230b7 Mon Sep 17 00:00:00 2001 From: Marco Gavelli Date: Sat, 17 Feb 2018 14:29:31 +0100 Subject: [PATCH 1/3] Added GraphQL alternative to fetchThreadList; fixes #241 --- fbchat/client.py | 30 ++++++++++++++++++++++++++++++ fbchat/graphql.py | 20 ++++++++++++++++++++ tests.py | 4 ++++ 3 files changed, 54 insertions(+) diff --git a/fbchat/client.py b/fbchat/client.py index 71f99d3..6471543 100644 --- a/fbchat/client.py +++ b/fbchat/client.py @@ -754,6 +754,36 @@ class Client(object): return list(reversed([graphql_to_message(message) for message in j['message_thread']['messages']['nodes']])) + def fetchThreadListGraphQL(self, limit=20, thread_location=ThreadLocation.INBOX, before=None): + """Get thread list of your facebook account + + :param limit: Max. number of threads to retrieve. Capped at 20 + :param thread_location: models.ThreadLocation: INBOX, PENDING, ARCHIVED or OTHER + :param before: A timestamp, indicating from which point to retrieve messages + :type limit: int + :type before: int + :return: :class:`models.Thread` objects + :rtype: list + :raises: FBchatException if request failed + """ + + if limit > 20 or limit < 1: + raise FBchatUserError('`limit` should be between 1 and 20') + + if thread_location in ThreadLocation: + loc_str = thread_location.value + else: + raise FBchatUserError('"thread_location" must be a value of ThreadLocation') + + j = self.graphql_request(GraphQL(doc_id='1349387578499440', params={ + 'limit': limit, + 'tags': [loc_str], + 'before': before, + 'includeDeliveryReceipts': True, + 'includeSeqID': False})) + + return [graphql_to_thread(node) for node in j['viewer']['message_threads']['nodes']] + def fetchThreadList(self, offset=0, limit=20, thread_location=ThreadLocation.INBOX): """Get thread list of your facebook account diff --git a/fbchat/graphql.py b/fbchat/graphql.py index 33c3e6b..75df1b4 100644 --- a/fbchat/graphql.py +++ b/fbchat/graphql.py @@ -172,6 +172,26 @@ def graphql_to_user(user): message_count=user.get('messages_count') ) +def graphql_to_thread(thread): + if thread['thread_type'] == 'GROUP': + return graphql_to_group(thread) + elif thread['thread_type'] == 'ONE_TO_ONE': + if thread.get('image') is None: + thread['image'] = {} + c_info = get_customization_info(thread) + return Group( + thread['thread_key']['other_user_id'], + participants=set([node['messaging_actor']['id'] for node in thread['all_participants']['nodes']]), + nicknames=c_info.get('nicknames'), + color=c_info.get('color'), + emoji=c_info.get('emoji'), + photo=thread['image'].get('uri'), + name=thread.get('name'), + message_count=thread.get('messages_count') + ) + else: + raise FBchatException('Unknown thread type: {}, with data: {}'.format(thread.get('thread_type'), thread)) + def graphql_to_group(group): if group.get('image') is None: group['image'] = {} diff --git a/tests.py b/tests.py index 08a101b..a02f9fe 100644 --- a/tests.py +++ b/tests.py @@ -122,6 +122,10 @@ class TestFbchat(unittest.TestCase): self.assertTrue(client.sendRemoteImage(image_url, Message(text='test_send_image_remote_to_@you★', mentions=mentions))) self.assertTrue(client.sendLocalImage(image_local_url, Message(text='test_send_image_local_to__@you★', mentions=mentions))) + def test_fetchThreadListGraphQL(self): + threads = client.fetchThreadListGraphQL(limit=10) + self.assertEqual(len(threads), 10) + def test_fetchThreadList(self): client.fetchThreadList(offset=0, limit=20) From 8268445f0b1d96a2b1273f8b278949e44156f76f Mon Sep 17 00:00:00 2001 From: Marco Gavelli Date: Sun, 18 Feb 2018 22:49:47 +0100 Subject: [PATCH 2/3] Changed return type for ONE_TO_ONE to User --- fbchat/graphql.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/fbchat/graphql.py b/fbchat/graphql.py index 75df1b4..f4ee001 100644 --- a/fbchat/graphql.py +++ b/fbchat/graphql.py @@ -176,17 +176,26 @@ def graphql_to_thread(thread): if thread['thread_type'] == 'GROUP': return graphql_to_group(thread) elif thread['thread_type'] == 'ONE_TO_ONE': - if thread.get('image') is None: - thread['image'] = {} + if thread.get('big_image_src') is None: + thread['big_image_src'] = {} c_info = get_customization_info(thread) - return Group( - thread['thread_key']['other_user_id'], - participants=set([node['messaging_actor']['id'] for node in thread['all_participants']['nodes']]), - nicknames=c_info.get('nicknames'), + participants = [node['messaging_actor'] for node in thread['all_participants']['nodes']] + user = next(p for p in participants if p['id'] == thread['thread_key']['other_user_id']) + + return User( + user['id'], + url=user.get('url'), + name=user.get('name'), + first_name=user.get('short_name'), + last_name=user.get('name').split(user.get('short_name'),1)[1].strip(), + is_friend=user.get('is_viewer_friend'), + gender=GENDERS.get(user.get('gender')), + affinity=user.get('affinity'), + nickname=c_info.get('nickname'), color=c_info.get('color'), emoji=c_info.get('emoji'), - photo=thread['image'].get('uri'), - name=thread.get('name'), + own_nickname=c_info.get('own_nickname'), + photo=user['big_image_src'].get('uri'), message_count=thread.get('messages_count') ) else: From 2642788bc1824f204e4afdbafcaf2066b166a57b Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sun, 18 Feb 2018 22:32:12 +0100 Subject: [PATCH 3/3] Merged `fetchThreadListGraphQL ` into `fetchThreadList ` --- fbchat/client.py | 79 +++++------------------------------------------- tests.py | 7 ++--- 2 files changed, 10 insertions(+), 76 deletions(-) diff --git a/fbchat/client.py b/fbchat/client.py index 6471543..2f6b1da 100644 --- a/fbchat/client.py +++ b/fbchat/client.py @@ -754,12 +754,13 @@ class Client(object): return list(reversed([graphql_to_message(message) for message in j['message_thread']['messages']['nodes']])) - def fetchThreadListGraphQL(self, limit=20, thread_location=ThreadLocation.INBOX, before=None): + def fetchThreadList(self, offset=0, limit=20, thread_location=ThreadLocation.INBOX, before=None): """Get thread list of your facebook account + :param offset: Deprecated. Do not use! :param limit: Max. number of threads to retrieve. Capped at 20 :param thread_location: models.ThreadLocation: INBOX, PENDING, ARCHIVED or OTHER - :param before: A timestamp, indicating from which point to retrieve messages + :param before: A timestamp (in milliseconds), indicating from which point to retrieve threads :type limit: int :type before: int :return: :class:`models.Thread` objects @@ -767,6 +768,9 @@ class Client(object): :raises: FBchatException if request failed """ + if offset is not None: + log.warning('Using `offset` in `fetchThreadList` is no longer supported, since Facebook migrated to the use of GraphQL in this request. Use `before` instead') + if limit > 20 or limit < 1: raise FBchatUserError('`limit` should be between 1 and 20') @@ -780,78 +784,11 @@ class Client(object): 'tags': [loc_str], 'before': before, 'includeDeliveryReceipts': True, - 'includeSeqID': False})) + 'includeSeqID': False + })) return [graphql_to_thread(node) for node in j['viewer']['message_threads']['nodes']] - def fetchThreadList(self, offset=0, limit=20, thread_location=ThreadLocation.INBOX): - """Get thread list of your facebook account - - :param offset: The offset, from where in the list to recieve threads from - :param limit: Max. number of threads to retrieve. Capped at 20 - :param thread_location: models.ThreadLocation: INBOX, PENDING, ARCHIVED or OTHER - :type offset: int - :type limit: int - :return: :class:`models.Thread` objects - :rtype: list - :raises: FBchatException if request failed - """ - - if limit > 20 or limit < 1: - raise FBchatUserError('`limit` should be between 1 and 20') - - if thread_location in ThreadLocation: - loc_str = thread_location.value - else: - raise FBchatUserError('"thread_location" must be a value of ThreadLocation') - - data = { - 'client' : self.client, - loc_str + '[offset]' : offset, - loc_str + '[limit]' : limit, - } - - j = self._post(self.req_url.THREADS, data, fix_request=True, as_json=True) - if j.get('payload') is None: - raise FBchatException('Missing payload: {}, with data: {}'.format(j, data)) - - participants = {} - if 'participants' in j['payload']: - for p in j['payload']['participants']: - if p['type'] == 'page': - participants[p['fbid']] = Page(p['fbid'], url=p['href'], photo=p['image_src'], name=p['name']) - elif p['type'] == 'user': - participants[p['fbid']] = User(p['fbid'], url=p['href'], first_name=p['short_name'], is_friend=p['is_friend'], gender=GENDERS.get(p['gender']), photo=p['image_src'], name=p['name']) - else: - raise FBchatException('A participant had an unknown type {}: {}'.format(p['type'], p)) - - entries = [] - if 'threads' in j['payload']: - for k in j['payload']['threads']: - if k['thread_type'] == 1: - if k['other_user_fbid'] not in participants: - raise FBchatException('The thread {} was not in participants: {}'.format(k, j['payload'])) - participants[k['other_user_fbid']].message_count = k['message_count'] - entries.append(participants[k['other_user_fbid']]) - elif k['thread_type'] == 2: - entries.append(Group(k['thread_fbid'], participants=set([p.strip('fbid:') for p in k['participants']]), photo=k['image_src'], name=k['name'], message_count=k['message_count'])) - elif k['thread_type'] == 3: - entries.append(Room( - k['thread_fbid'], - participants = set(p.lstrip('fbid:') for p in k['participants']), - photo = k['image_src'], - name = k['name'], - message_count = k['message_count'], - admins = set(p.lstrip('fbid:') for p in k['admin_ids']), - approval_mode = k['approval_mode'], - approval_requests = set(p.lstrip('fbid:') for p in k['approval_queue_ids']), - join_link = k['joinable_mode']['link'] - )) - else: - raise FBchatException('A thread had an unknown thread type: {}'.format(k)) - - return entries - def fetchUnread(self): """ .. todo:: diff --git a/tests.py b/tests.py index a02f9fe..033a259 100644 --- a/tests.py +++ b/tests.py @@ -122,12 +122,9 @@ class TestFbchat(unittest.TestCase): self.assertTrue(client.sendRemoteImage(image_url, Message(text='test_send_image_remote_to_@you★', mentions=mentions))) self.assertTrue(client.sendLocalImage(image_local_url, Message(text='test_send_image_local_to__@you★', mentions=mentions))) - def test_fetchThreadListGraphQL(self): - threads = client.fetchThreadListGraphQL(limit=10) - self.assertEqual(len(threads), 10) - def test_fetchThreadList(self): - client.fetchThreadList(offset=0, limit=20) + threads = client.fetchThreadList(limit=2) + self.assertEqual(len(threads), 2) def test_fetchThreadMessages(self): for thread in threads: