Compare commits
21 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
38f66147cb | ||
|
ffa26c20b5 | ||
|
430ada7f84 | ||
|
988e37eb42 | ||
|
1938b90bce | ||
|
f61d1403f3 | ||
|
d228f34f64 | ||
|
97049556ed | ||
|
b64c6a94cc | ||
|
edc655bae7 | ||
|
884af48270 | ||
|
95f018fad3 | ||
|
b44758a195 | ||
|
f1c20d490e | ||
|
04372d498e | ||
|
63ea899605 | ||
|
4fdd145d1e | ||
|
57ee68b0e0 | ||
|
99c6884681 | ||
|
1c1438e9bc | ||
|
22f1b3e489 |
8
.gitignore
vendored
8
.gitignore
vendored
@@ -24,7 +24,11 @@ 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
|
||||
tests.data
|
||||
|
||||
# Virtual environment
|
||||
venv/
|
||||
|
@@ -5,8 +5,8 @@ from fbchat import log, Client
|
||||
# Subclass fbchat.Client and override required methods
|
||||
class EchoBot(Client):
|
||||
def onMessage(self, author_id, message_object, thread_id, thread_type, **kwargs):
|
||||
self.markAsDelivered(author_id, thread_id)
|
||||
self.markAsRead(author_id)
|
||||
self.markAsDelivered(thread_id, message_object.uid)
|
||||
self.markAsRead(thread_id)
|
||||
|
||||
log.info("{} from {} in {}".format(message_object, thread_id, thread_type.name))
|
||||
|
||||
|
@@ -17,7 +17,7 @@ from .client import *
|
||||
|
||||
|
||||
__copyright__ = 'Copyright 2015 - {} by Taehoon Kim'.format(datetime.now().year)
|
||||
__version__ = '1.3.4'
|
||||
__version__ = '1.3.7'
|
||||
__license__ = 'BSD'
|
||||
__author__ = 'Taehoon Kim; Moreels Pieter-Jan; Mads Marquart'
|
||||
__email__ = 'carpedm20@gmail.com'
|
||||
|
@@ -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 a proxy"""
|
||||
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 @@ class Client(object):
|
||||
|
||||
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=payload, timeout=timeout, verify=self.ssl_verify)
|
||||
if not fix_request:
|
||||
return r
|
||||
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):
|
||||
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=payload, timeout=timeout, verify=self.ssl_verify)
|
||||
if not fix_request:
|
||||
return r
|
||||
try:
|
||||
@@ -136,18 +138,19 @@ class Client(object):
|
||||
return self._graphql(payload, error_retries=error_retries-1)
|
||||
raise e
|
||||
|
||||
def _cleanGet(self, url, query=None, timeout=30):
|
||||
return self._session.get(url, headers=self._header, params=query, timeout=timeout)
|
||||
def _cleanGet(self, url, query=None, timeout=30, allow_redirects=True):
|
||||
return self._session.get(url, headers=self._header, params=query, timeout=timeout, verify=self.ssl_verify,
|
||||
allow_redirects=allow_redirects)
|
||||
|
||||
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=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)
|
||||
# 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=payload, timeout=timeout, files=files, verify=self.ssl_verify)
|
||||
if not fix_request:
|
||||
return r
|
||||
try:
|
||||
@@ -207,8 +210,18 @@ class Client(object):
|
||||
|
||||
r = self._get(self.req_url.BASE)
|
||||
soup = bs(r.text, "lxml")
|
||||
self.fb_dtsg = soup.find("input", {'name':'fb_dtsg'})['value']
|
||||
self.fb_h = soup.find("input", {'name':'h'})['value']
|
||||
|
||||
fb_dtsg_element = soup.find("input", {'name': 'fb_dtsg'})
|
||||
if fb_dtsg_element:
|
||||
self.fb_dtsg = fb_dtsg_element['value']
|
||||
else:
|
||||
self.fb_dtsg = re.search(r'name="fb_dtsg" value="(.*?)"', r.text).group(1)
|
||||
|
||||
|
||||
fb_h_element = soup.find("input", {'name':'h'})
|
||||
if fb_h_element:
|
||||
self.fb_h = fb_h_element['value']
|
||||
|
||||
for i in self.fb_dtsg:
|
||||
self.ttstamp += str(ord(i))
|
||||
self.ttstamp += '2'
|
||||
@@ -323,8 +336,8 @@ class Client(object):
|
||||
:rtype: bool
|
||||
"""
|
||||
# Send a request to the login url, to see if we're directed to the home page
|
||||
r = self._cleanGet(self.req_url.LOGIN)
|
||||
return 'home' in r.url
|
||||
r = self._cleanGet(self.req_url.LOGIN, allow_redirects=False)
|
||||
return 'Location' in r.headers and 'home' in r.headers['Location']
|
||||
|
||||
def getSession(self):
|
||||
"""Retrieves session cookies
|
||||
@@ -398,6 +411,11 @@ class Client(object):
|
||||
:return: True if the action was successful
|
||||
:rtype: bool
|
||||
"""
|
||||
|
||||
if not hasattr(self, 'fb_h'):
|
||||
h_r = self._post(self.req_url.MODERN_SETTINGS_MENU, {'pmid': '4'})
|
||||
self.fb_h = re.search(r'name=\\"h\\" value=\\"(.*?)\\"', h_r.text).group(1)
|
||||
|
||||
data = {
|
||||
'ref': "mb",
|
||||
'h': self.fb_h
|
||||
@@ -791,24 +809,34 @@ class Client(object):
|
||||
|
||||
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 = {
|
||||
'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']
|
||||
}
|
||||
return j['payload']['unread_thread_fbids'][0]['other_user_fbids']
|
||||
|
||||
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):
|
||||
"""Fetches the url to the original image from an image attachment ID
|
||||
@@ -1230,28 +1258,36 @@ class Client(object):
|
||||
END SEND METHODS
|
||||
"""
|
||||
|
||||
def markAsDelivered(self, userID, threadID):
|
||||
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]": threadID,
|
||||
"thread_ids[%s][0]" % userID: threadID
|
||||
"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, userID):
|
||||
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]" % thread_id: 'true',
|
||||
"watermarkTimestamp": now(),
|
||||
"shouldSendReadReceipt": True,
|
||||
"ids[%s]" % userID: True
|
||||
"shouldSendReadReceipt": 'true',
|
||||
}
|
||||
|
||||
r = self._post(self.req_url.READ_STATUS, data)
|
||||
|
@@ -190,7 +190,7 @@ def graphql_to_thread(thread):
|
||||
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(),
|
||||
last_name=user.get('name').split(user.get('short_name'),1).pop().strip(),
|
||||
is_friend=user.get('is_viewer_friend'),
|
||||
gender=GENDERS.get(user.get('gender')),
|
||||
affinity=user.get('affinity'),
|
||||
|
@@ -469,7 +469,7 @@ class EmojiSize(Enum):
|
||||
|
||||
class ThreadColor(Enum):
|
||||
"""Used to specify a thread colors"""
|
||||
MESSENGER_BLUE = ''
|
||||
MESSENGER_BLUE = '#0084ff'
|
||||
VIKING = '#44bec7'
|
||||
GOLDEN_POPPY = '#ffc300'
|
||||
RADICAL_RED = '#fa3c4c'
|
||||
|
@@ -9,6 +9,13 @@ import warnings
|
||||
import logging
|
||||
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
|
||||
try:
|
||||
input = raw_input
|
||||
@@ -87,7 +94,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"
|
||||
@@ -113,6 +121,7 @@ class ReqUrl(object):
|
||||
GRAPHQL = "https://www.facebook.com/api/graphqlbatch/"
|
||||
ATTACHMENT_PHOTO = "https://www.facebook.com/mercury/attachments/photo/"
|
||||
EVENT_REMINDER = "https://www.facebook.com/ajax/eventreminder/create"
|
||||
MODERN_SETTINGS_MENU = "https://www.facebook.com/bluebar/modern_settings_menu/"
|
||||
|
||||
pull_channel = 0
|
||||
|
||||
|
2
tests.py
2
tests.py
@@ -114,7 +114,7 @@ class TestFbchat(unittest.TestCase):
|
||||
self.assertIsNotNone(client.send(Message(sticker=Sticker(test_sticker_id))))
|
||||
|
||||
def test_sendImages(self):
|
||||
image_url = 'https://cdn4.iconfinder.com/data/icons/ionicons/512/icon-image-128.png'
|
||||
image_url = 'https://github.com/carpedm20/fbchat/raw/master/tests/image.png'
|
||||
image_local_url = path.join(path.dirname(__file__), 'tests/image.png')
|
||||
for thread in threads:
|
||||
client.setDefaultThread(thread_id=thread['id'], thread_type=thread['type'])
|
||||
|
Reference in New Issue
Block a user