Merge pull request #269 from gave92/FetchUnread
Fix `markAsRead` and `fetchUnread`; fixes #261 Added the `ssl_verify` instance variable, which allows disabling SSL varification for proxies
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -24,7 +24,8 @@ develop-eggs
|
|||||||
# Sphinx documentation
|
# Sphinx documentation
|
||||||
docs/_build/
|
docs/_build/
|
||||||
|
|
||||||
# Data for tests
|
# Scripts and data for tests
|
||||||
|
my_tests.py
|
||||||
my_test_data.json
|
my_test_data.json
|
||||||
my_data.json
|
my_data.json
|
||||||
tests.data
|
tests.data
|
@@ -20,6 +20,8 @@ class Client(object):
|
|||||||
See https://fbchat.readthedocs.io for complete documentation of the API.
|
See https://fbchat.readthedocs.io for complete documentation of the API.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
ssl_verify = True
|
||||||
|
"""Verify ssl certificate, set to False to allow debugging with a proxy"""
|
||||||
listening = False
|
listening = False
|
||||||
"""Whether the client is listening. Used when creating an external event loop to determine when to stop listening"""
|
"""Whether the client is listening. Used when creating an external event loop to determine when to stop listening"""
|
||||||
uid = None
|
uid = None
|
||||||
@@ -105,7 +107,7 @@ class Client(object):
|
|||||||
|
|
||||||
def _get(self, url, query=None, timeout=30, fix_request=False, as_json=False, error_retries=3):
|
def _get(self, url, query=None, timeout=30, fix_request=False, as_json=False, error_retries=3):
|
||||||
payload = self._generatePayload(query)
|
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:
|
if not fix_request:
|
||||||
return r
|
return r
|
||||||
try:
|
try:
|
||||||
@@ -117,7 +119,7 @@ class Client(object):
|
|||||||
|
|
||||||
def _post(self, url, query=None, timeout=30, fix_request=False, as_json=False, error_retries=3):
|
def _post(self, url, query=None, timeout=30, fix_request=False, as_json=False, error_retries=3):
|
||||||
payload = self._generatePayload(query)
|
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:
|
if not fix_request:
|
||||||
return r
|
return r
|
||||||
try:
|
try:
|
||||||
@@ -137,17 +139,17 @@ class Client(object):
|
|||||||
raise e
|
raise e
|
||||||
|
|
||||||
def _cleanGet(self, url, query=None, timeout=30):
|
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):
|
def _cleanPost(self, url, query=None, timeout=30):
|
||||||
self.req_counter += 1
|
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):
|
def _postFile(self, url, files=None, query=None, timeout=30, fix_request=False, as_json=False, error_retries=3):
|
||||||
payload=self._generatePayload(query)
|
payload=self._generatePayload(query)
|
||||||
# Removes 'Content-Type' from the header
|
# Removes 'Content-Type' from the header
|
||||||
headers = dict((i, self._header[i]) for i in self._header if i != 'Content-Type')
|
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, verify=self.ssl_verify)
|
||||||
if not fix_request:
|
if not fix_request:
|
||||||
return r
|
return r
|
||||||
try:
|
try:
|
||||||
@@ -791,24 +793,34 @@ class Client(object):
|
|||||||
|
|
||||||
def fetchUnread(self):
|
def fetchUnread(self):
|
||||||
"""
|
"""
|
||||||
.. todo::
|
Get the unread thread list
|
||||||
Documenting this
|
|
||||||
|
|
||||||
|
:return: List of unread thread ids
|
||||||
|
:rtype: list
|
||||||
:raises: FBchatException if request failed
|
:raises: FBchatException if request failed
|
||||||
"""
|
"""
|
||||||
form = {
|
form = {
|
||||||
'client': 'mercury_sync',
|
|
||||||
'folders[0]': 'inbox',
|
'folders[0]': 'inbox',
|
||||||
|
'client': 'mercury',
|
||||||
'last_action_timestamp': now() - 60*1000
|
'last_action_timestamp': now() - 60*1000
|
||||||
# 'last_action_timestamp': 0
|
# '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 {
|
return j['payload']['unread_thread_fbids'][0]['other_user_fbids']
|
||||||
"message_counts": j['payload']['message_counts'],
|
|
||||||
"unseen_threads": j['payload']['unseen_thread_ids']
|
def fetchUnseen(self):
|
||||||
}
|
"""
|
||||||
|
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)
|
||||||
|
|
||||||
|
return j['payload']['unseen_thread_fbids'][0]['other_user_fbids']
|
||||||
|
|
||||||
def fetchImageUrl(self, image_id):
|
def fetchImageUrl(self, image_id):
|
||||||
"""Fetches the url to the original image from an image attachment ID
|
"""Fetches the url to the original image from an image attachment ID
|
||||||
@@ -1230,28 +1242,36 @@ class Client(object):
|
|||||||
END SEND METHODS
|
END SEND METHODS
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def markAsDelivered(self, userID, threadID):
|
def markAsDelivered(self, thread_id, message_id):
|
||||||
"""
|
"""
|
||||||
.. todo::
|
Mark a message as delivered
|
||||||
Documenting this
|
|
||||||
|
: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 = {
|
data = {
|
||||||
"message_ids[0]": threadID,
|
"message_ids[0]": message_id,
|
||||||
"thread_ids[%s][0]" % userID: threadID
|
"thread_ids[%s][0]" % thread_id: message_id
|
||||||
}
|
}
|
||||||
|
|
||||||
r = self._post(self.req_url.DELIVERED, data)
|
r = self._post(self.req_url.DELIVERED, data)
|
||||||
return r.ok
|
return r.ok
|
||||||
|
|
||||||
def markAsRead(self, userID):
|
def markAsRead(self, thread_id):
|
||||||
"""
|
"""
|
||||||
.. todo::
|
Mark a thread as read
|
||||||
Documenting this
|
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 = {
|
data = {
|
||||||
|
"ids[%s]" % thread_id: True,
|
||||||
"watermarkTimestamp": now(),
|
"watermarkTimestamp": now(),
|
||||||
"shouldSendReadReceipt": True,
|
"shouldSendReadReceipt": True,
|
||||||
"ids[%s]" % userID: True
|
|
||||||
}
|
}
|
||||||
|
|
||||||
r = self._post(self.req_url.READ_STATUS, data)
|
r = self._post(self.req_url.READ_STATUS, data)
|
||||||
|
@@ -9,6 +9,13 @@ import warnings
|
|||||||
import logging
|
import logging
|
||||||
from .models import *
|
from .models import *
|
||||||
|
|
||||||
|
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
|
# Python 2's `input` executes the input, whereas `raw_input` just returns the input
|
||||||
try:
|
try:
|
||||||
input = raw_input
|
input = raw_input
|
||||||
@@ -87,7 +94,8 @@ class ReqUrl(object):
|
|||||||
SEARCH = "https://www.facebook.com/ajax/typeahead/search.php"
|
SEARCH = "https://www.facebook.com/ajax/typeahead/search.php"
|
||||||
LOGIN = "https://m.facebook.com/login.php?login_attempt=1"
|
LOGIN = "https://m.facebook.com/login.php?login_attempt=1"
|
||||||
SEND = "https://www.facebook.com/messaging/send/"
|
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"
|
THREADS = "https://www.facebook.com/ajax/mercury/threadlist_info.php"
|
||||||
MESSAGES = "https://www.facebook.com/ajax/mercury/thread_info.php"
|
MESSAGES = "https://www.facebook.com/ajax/mercury/thread_info.php"
|
||||||
READ_STATUS = "https://www.facebook.com/ajax/mercury/change_read_status.php"
|
READ_STATUS = "https://www.facebook.com/ajax/mercury/change_read_status.php"
|
||||||
@@ -225,3 +233,35 @@ def get_emojisize_from_tags(tags):
|
|||||||
except (KeyError, IndexError):
|
except (KeyError, IndexError):
|
||||||
log.exception('Could not determine emoji size from {} - {}'.format(tags, tmp))
|
log.exception('Could not determine emoji size from {} - {}'.format(tags, tmp))
|
||||||
return None
|
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
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user