From 22f1b3e4896d5372500c37d23e191d07e2ebf6a1 Mon Sep 17 00:00:00 2001 From: Marco Gavelli Date: Sat, 17 Mar 2018 19:32:45 +0100 Subject: [PATCH 1/6] fix FetchUnread --- .gitignore | 5 +++-- fbchat/client.py | 33 ++++++++++++++++++++++++--------- fbchat/utils.py | 3 ++- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 39a0b5b4..f429dd31 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,8 @@ develop-eggs # Sphinx documentation docs/_build/ -# Data for tests +# Scripts and data for tests +my_tests.py my_test_data.json my_data.json -tests.data \ No newline at end of file +tests.data diff --git a/fbchat/client.py b/fbchat/client.py index cdcfb0fa..9dcd1ea4 100644 --- a/fbchat/client.py +++ b/fbchat/client.py @@ -797,17 +797,31 @@ def fetchUnread(self): :raises: FBchatException if request failed """ form = { - 'client': 'mercury_sync', 'folders[0]': 'inbox', + 'client': 'mercury', 'last_action_timestamp': now() - 60*1000 # 'last_action_timestamp': 0 } - j = self._post(self.req_url.THREAD_SYNC, form, fix_request=True, as_json=True) + j = self._post(self.req_url.UNREAD_THREADS, form, fix_request=True, as_json=True) return { - "message_counts": j['payload']['message_counts'], - "unseen_threads": j['payload']['unseen_thread_ids'] + "unread_threads": j['payload']['unread_thread_fbids'] + } + + def fetchUnseen(self): + """ + .. todo:: + Documenting this + + :raises: FBchatException if request failed + """ + form = {} + + j = self._post(self.req_url.UNSEEN_THREADS, form, fix_request=True, as_json=True) + + return { + "unseen_threads": j['payload']['unseen_thread_fbids'] } def fetchImageUrl(self, image_id): @@ -1230,28 +1244,29 @@ def setTypingStatus(self, status, thread_id=None, thread_type=None): END SEND METHODS """ - def markAsDelivered(self, userID, threadID): + def markAsDelivered(self, threadID, messageID): """ .. todo:: Documenting this """ data = { - "message_ids[0]": threadID, - "thread_ids[%s][0]" % userID: threadID + "message_ids[0]": messageID, + "thread_ids[%s][0]" % threadID: messageID } r = self._post(self.req_url.DELIVERED, data) return r.ok - def markAsRead(self, userID): + def markAsRead(self, threadID): """ .. todo:: Documenting this """ data = { + "ids[%s]" % threadID: True, "watermarkTimestamp": now(), "shouldSendReadReceipt": True, - "ids[%s]" % userID: True + "commerce_last_message_type": "", } r = self._post(self.req_url.READ_STATUS, data) diff --git a/fbchat/utils.py b/fbchat/utils.py index f9670fc5..b26bf473 100644 --- a/fbchat/utils.py +++ b/fbchat/utils.py @@ -87,7 +87,8 @@ class ReqUrl(object): SEARCH = "https://www.facebook.com/ajax/typeahead/search.php" LOGIN = "https://m.facebook.com/login.php?login_attempt=1" SEND = "https://www.facebook.com/messaging/send/" - THREAD_SYNC = "https://www.facebook.com/ajax/mercury/thread_sync.php" + UNREAD_THREADS = "https://www.facebook.com/ajax/mercury/unread_threads.php" + UNSEEN_THREADS = "https://www.facebook.com/mercury/unseen_thread_ids/" THREADS = "https://www.facebook.com/ajax/mercury/threadlist_info.php" MESSAGES = "https://www.facebook.com/ajax/mercury/thread_info.php" READ_STATUS = "https://www.facebook.com/ajax/mercury/change_read_status.php" From 1c1438e9bc0aa3f9b0f486a60e6343f18cdec525 Mon Sep 17 00:00:00 2001 From: Marco Gavelli Date: Sun, 18 Mar 2018 11:18:46 +0100 Subject: [PATCH 2/6] fix for markAsRead, fetchUnread --- fbchat/client.py | 23 +++++++++-------------- fbchat/utils.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/fbchat/client.py b/fbchat/client.py index 9dcd1ea4..729c7649 100644 --- a/fbchat/client.py +++ b/fbchat/client.py @@ -20,6 +20,8 @@ class Client(object): See https://fbchat.readthedocs.io for complete documentation of the API. """ + ssl_verify = True + """Verify ssl certificate, set to False to allow debugging with Fiddler""" listening = False """Whether the client is listening. Used when creating an external event loop to determine when to stop listening""" uid = None @@ -105,7 +107,7 @@ def _fix_fb_errors(self, error_code): def _get(self, url, query=None, timeout=30, fix_request=False, as_json=False, error_retries=3): payload = self._generatePayload(query) - r = self._session.get(url, headers=self._header, params=payload, timeout=timeout) + r = self._session.get(url, headers=self._header, params=encode_params(payload), timeout=timeout, verify=self.ssl_verify) if not fix_request: return r try: @@ -117,7 +119,7 @@ def _get(self, url, query=None, timeout=30, fix_request=False, as_json=False, er def _post(self, url, query=None, timeout=30, fix_request=False, as_json=False, error_retries=3): payload = self._generatePayload(query) - r = self._session.post(url, headers=self._header, data=payload, timeout=timeout) + r = self._session.post(url, headers=self._header, data=encode_params(payload), timeout=timeout, verify=self.ssl_verify) if not fix_request: return r try: @@ -137,11 +139,11 @@ def _graphql(self, payload, error_retries=3): raise e def _cleanGet(self, url, query=None, timeout=30): - return self._session.get(url, headers=self._header, params=query, timeout=timeout) + return self._session.get(url, headers=self._header, params=encode_params(query), timeout=timeout, verify=self.ssl_verify) def _cleanPost(self, url, query=None, timeout=30): self.req_counter += 1 - return self._session.post(url, headers=self._header, data=query, timeout=timeout) + return self._session.post(url, headers=self._header, data=encode_params(query), timeout=timeout, verify=self.ssl_verify) def _postFile(self, url, files=None, query=None, timeout=30, fix_request=False, as_json=False, error_retries=3): payload=self._generatePayload(query) @@ -805,9 +807,7 @@ def fetchUnread(self): j = self._post(self.req_url.UNREAD_THREADS, form, fix_request=True, as_json=True) - return { - "unread_threads": j['payload']['unread_thread_fbids'] - } + return j['payload']['unread_thread_fbids'][0]['other_user_fbids'] def fetchUnseen(self): """ @@ -816,13 +816,9 @@ def fetchUnseen(self): :raises: FBchatException if request failed """ - form = {} - - j = self._post(self.req_url.UNSEEN_THREADS, form, fix_request=True, as_json=True) + j = self._post(self.req_url.UNSEEN_THREADS, None, fix_request=True, as_json=True) - return { - "unseen_threads": j['payload']['unseen_thread_fbids'] - } + return j['payload']['unseen_thread_fbids'][0]['other_user_fbids'] def fetchImageUrl(self, image_id): """Fetches the url to the original image from an image attachment ID @@ -1266,7 +1262,6 @@ def markAsRead(self, threadID): "ids[%s]" % threadID: True, "watermarkTimestamp": now(), "shouldSendReadReceipt": True, - "commerce_last_message_type": "", } r = self._post(self.req_url.READ_STATUS, data) diff --git a/fbchat/utils.py b/fbchat/utils.py index b26bf473..72bbcfea 100644 --- a/fbchat/utils.py +++ b/fbchat/utils.py @@ -9,6 +9,11 @@ import logging from .models import * +try: + from urllib.parse import urlencode +except ImportError: + from urllib import urlencode + # Python 2's `input` executes the input, whereas `raw_input` just returns the input try: input = raw_input @@ -226,3 +231,35 @@ def get_emojisize_from_tags(tags): except (KeyError, IndexError): log.exception('Could not determine emoji size from {} - {}'.format(tags, tmp)) return None + +def encode_params(data): + """Encode parameters in a piece of data. + + Will successfully encode parameters when passed as a dict or a list of + 2-tuples. Order is retained if data is a list of 2-tuples but arbitrary + if parameters are supplied as a dict. + """ + + if isinstance(data, (str, bytes)): + return data + elif hasattr(data, 'read'): + return data + elif hasattr(data, '__iter__'): + result = [] + for k, vs in list(data.items()): + if isinstance(vs, basestring) or not hasattr(vs, '__iter__'): + vs = [vs] + for v in vs: + if v is not None: + if isinstance(v, bool): + result.append( + (k.encode('utf-8') if isinstance(k, str) else k, + str(v).lower())) + else: + result.append( + (k.encode('utf-8') if isinstance(k, str) else k, + v.encode('utf-8') if isinstance(v, str) else v)) + return urlencode(result, doseq=True) + else: + return data + From 99c6884681741eebc756a879aecd781962a0d6a7 Mon Sep 17 00:00:00 2001 From: Marco Gavelli Date: Mon, 19 Mar 2018 16:29:26 +0100 Subject: [PATCH 3/6] added documentation to fetchUnread --- fbchat/client.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/fbchat/client.py b/fbchat/client.py index 729c7649..452e467a 100644 --- a/fbchat/client.py +++ b/fbchat/client.py @@ -21,7 +21,7 @@ class Client(object): """ ssl_verify = True - """Verify ssl certificate, set to False to allow debugging with Fiddler""" + """Verify ssl certificate, set to False to allow debugging with a proxy""" listening = False """Whether the client is listening. Used when creating an external event loop to determine when to stop listening""" uid = None @@ -149,7 +149,7 @@ def _postFile(self, url, files=None, query=None, timeout=30, fix_request=False, payload=self._generatePayload(query) # Removes 'Content-Type' from the header headers = dict((i, self._header[i]) for i in self._header if i != 'Content-Type') - r = self._session.post(url, headers=headers, data=payload, timeout=timeout, files=files) + r = self._session.post(url, headers=headers, data=encode_params(payload), timeout=timeout, files=files) if not fix_request: return r try: @@ -793,9 +793,10 @@ def fetchThreadList(self, offset=None, limit=20, thread_location=ThreadLocation. def fetchUnread(self): """ - .. todo:: - Documenting this + Get the unread thread list + :return: List of unread thread ids + :rtype: list :raises: FBchatException if request failed """ form = { @@ -811,9 +812,10 @@ def fetchUnread(self): def fetchUnseen(self): """ - .. todo:: - Documenting this + Get the unseen (new) thread list + :return: List of unseen thread ids + :rtype: list :raises: FBchatException if request failed """ j = self._post(self.req_url.UNSEEN_THREADS, None, fix_request=True, as_json=True) From 57ee68b0e07d7d184d77fb8134ea2441e414629d Mon Sep 17 00:00:00 2001 From: Marco Gavelli Date: Mon, 19 Mar 2018 16:38:19 +0100 Subject: [PATCH 4/6] added documentation to markAsRead --- fbchat/client.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/fbchat/client.py b/fbchat/client.py index 452e467a..9c942e20 100644 --- a/fbchat/client.py +++ b/fbchat/client.py @@ -1242,26 +1242,34 @@ def setTypingStatus(self, status, thread_id=None, thread_type=None): END SEND METHODS """ - def markAsDelivered(self, threadID, messageID): + def markAsDelivered(self, thread_id, message_id): """ - .. todo:: - Documenting this + Mark a message as delivered + + :param thread_id: User/Group ID to which the message belongs. See :ref:`intro_threads` + :param message_id: Message ID to set as delivered. See :ref:`intro_threads` + :return: Whether the request was successful + :raises: FBchatException if request failed """ data = { - "message_ids[0]": messageID, - "thread_ids[%s][0]" % threadID: messageID + "message_ids[0]": message_id, + "thread_ids[%s][0]" % thread_id: message_id } r = self._post(self.req_url.DELIVERED, data) return r.ok - def markAsRead(self, threadID): + def markAsRead(self, thread_id): """ - .. todo:: - Documenting this + Mark a thread as read + All messages inside the thread will be marked as read + + :param thread_id: User/Group ID to set as read. See :ref:`intro_threads` + :return: Whether the request was successful + :raises: FBchatException if request failed """ data = { - "ids[%s]" % threadID: True, + "ids[%s]" % thread_id: True, "watermarkTimestamp": now(), "shouldSendReadReceipt": True, } From 4fdd145d1e86c29fd012787d26f3bdd18407a24b Mon Sep 17 00:00:00 2001 From: Marco Gavelli Date: Mon, 19 Mar 2018 16:52:22 +0100 Subject: [PATCH 5/6] verify in _postFile --- fbchat/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fbchat/client.py b/fbchat/client.py index 9c942e20..46f50f54 100644 --- a/fbchat/client.py +++ b/fbchat/client.py @@ -149,7 +149,7 @@ def _postFile(self, url, files=None, query=None, timeout=30, fix_request=False, payload=self._generatePayload(query) # Removes 'Content-Type' from the header headers = dict((i, self._header[i]) for i in self._header if i != 'Content-Type') - r = self._session.post(url, headers=headers, data=encode_params(payload), timeout=timeout, files=files) + r = self._session.post(url, headers=headers, data=encode_params(payload), timeout=timeout, files=files, verify=self.ssl_verify) if not fix_request: return r try: From 63ea899605e5a956da0dbed4012861c202640f8b Mon Sep 17 00:00:00 2001 From: Marco Gavelli Date: Mon, 19 Mar 2018 20:47:41 +0100 Subject: [PATCH 6/6] fix for python3 --- fbchat/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fbchat/utils.py b/fbchat/utils.py index 72bbcfea..6a9535fd 100644 --- a/fbchat/utils.py +++ b/fbchat/utils.py @@ -11,8 +11,10 @@ try: from urllib.parse import urlencode + basestring = (str, bytes) except ImportError: from urllib import urlencode + basestring = basestring # Python 2's `input` executes the input, whereas `raw_input` just returns the input try: