Compare commits
42 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
c51a332560 | ||
|
a73d2feed6 | ||
|
6929193e9d | ||
|
fea4ad9e89 | ||
|
68099049d4 | ||
|
44cf08bdfd | ||
|
9e32cf17a4 | ||
|
0661367ebb | ||
|
3c07e42ba2 | ||
|
2cd6376818 | ||
|
5e7f7750de | ||
|
2a223ec6db | ||
|
a99108fff6 | ||
|
8de4698cc4 | ||
|
637319ec2c | ||
|
f9398564cd | ||
|
b57f423eb4 | ||
|
3093f1f2b6 | ||
|
961777e0c1 | ||
|
d7139701f7 | ||
|
c6bac17d48 | ||
|
3638fc5356 | ||
|
aca9176f7f | ||
|
0d5e4f6d3f | ||
|
92a5ffdef8 | ||
|
b3359fccdb | ||
|
d8f7366d1f | ||
|
ff94dc20af | ||
|
a8df0a548f | ||
|
13d0dc4ba4 | ||
|
64125a1aca | ||
|
4feae03092 | ||
|
5f993c2bf8 | ||
|
35bbcbffba | ||
|
5faca54d67 | ||
|
82496b8e04 | ||
|
2d74ec7823 | ||
|
1d42c4d3a6 | ||
|
4a8ef00442 | ||
|
add06ffa7a | ||
|
fbb8d8e24a | ||
|
cd0e001219 |
@@ -9,7 +9,7 @@ fbchat: Facebook Chat (Messenger) for Python
|
|||||||
:target: https://pypi.python.org/pypi/fbchat
|
:target: https://pypi.python.org/pypi/fbchat
|
||||||
:alt: Supported python versions: 2.7, 3.4, 3.5 and 3.6
|
:alt: Supported python versions: 2.7, 3.4, 3.5 and 3.6
|
||||||
|
|
||||||
.. image:: https://readthedocs.org/projects/fbchat/badge/
|
.. image:: https://readthedocs.org/projects/fbchat/badge/?version=master
|
||||||
:target: https://fbchat.readthedocs.io
|
:target: https://fbchat.readthedocs.io
|
||||||
:alt: Documentation
|
:alt: Documentation
|
||||||
|
|
||||||
|
@@ -52,12 +52,12 @@ A thread can refer to two things: A Messenger group chat or a single Facebook us
|
|||||||
These will specify whether the thread is a single user chat or a group chat.
|
These will specify whether the thread is a single user chat or a group chat.
|
||||||
This is required for many of `fbchat`'s functions, since Facebook differetiates between these two internally
|
This is required for many of `fbchat`'s functions, since Facebook differetiates between these two internally
|
||||||
|
|
||||||
Searching for group chats and finding their ID is not yet possible with `fbchat`,
|
Searching for group chats and finding their ID can be done via. :func:`Client.searchForGroups`,
|
||||||
but searching for users is possible via. :func:`Client.searchForUsers`. See :ref:`intro_fetching`
|
and searching for users is possible via. :func:`Client.searchForUsers`. See :ref:`intro_fetching`
|
||||||
|
|
||||||
You can get your own user ID by using :any:`Client.uid`
|
You can get your own user ID by using :any:`Client.uid`
|
||||||
|
|
||||||
Getting the ID of a group chat is fairly trivial though, since you only need to navigate to `<https://www.facebook.com/messages/>`_,
|
Getting the ID of a group chat is fairly trivial otherwise, since you only need to navigate to `<https://www.facebook.com/messages/>`_,
|
||||||
click on the group you want to find the ID of, and then read the id from the address bar.
|
click on the group you want to find the ID of, and then read the id from the address bar.
|
||||||
The URL will look something like this: ``https://www.facebook.com/messages/t/1234567890``, where ``1234567890`` would be the ID of the group.
|
The URL will look something like this: ``https://www.facebook.com/messages/t/1234567890``, where ``1234567890`` would be the ID of the group.
|
||||||
An image to illustrate this is shown below:
|
An image to illustrate this is shown below:
|
||||||
|
@@ -12,7 +12,7 @@ print("users' IDs: {}".format(user.uid for user in users))
|
|||||||
print("users' names: {}".format(user.name for user in users))
|
print("users' names: {}".format(user.name for user in users))
|
||||||
|
|
||||||
|
|
||||||
# If we have a user id, we can use `getUserInfo` to fetch a `User` object
|
# If we have a user id, we can use `fetchUserInfo` to fetch a `User` object
|
||||||
user = client.fetchUserInfo('<user id>')['<user id>']
|
user = client.fetchUserInfo('<user id>')['<user id>']
|
||||||
# We can also query both mutiple users together, which returns list of `User` objects
|
# We can also query both mutiple users together, which returns list of `User` objects
|
||||||
users = client.fetchUserInfo('<1st user id>', '<2nd user id>', '<3rd user id>')
|
users = client.fetchUserInfo('<1st user id>', '<2nd user id>', '<3rd user id>')
|
||||||
@@ -49,4 +49,16 @@ for message in messages:
|
|||||||
print(message.text)
|
print(message.text)
|
||||||
|
|
||||||
|
|
||||||
|
# If we have a thread id, we can use `fetchThreadInfo` to fetch a `Thread` object
|
||||||
|
thread = client.fetchThreadInfo('<thread id>')['<thread id>']
|
||||||
|
print("thread's name: {}".format(thread.name))
|
||||||
|
print("thread's type: {}".format(thread.type))
|
||||||
|
|
||||||
|
|
||||||
|
# `searchForThreads` searches works like `searchForUsers`, but gives us a list of threads instead
|
||||||
|
thread = client.searchForThreads('<name of thread>')[0]
|
||||||
|
print("thread's name: {}".format(thread.name))
|
||||||
|
print("thread's type: {}".format(thread.type))
|
||||||
|
|
||||||
|
|
||||||
# Here should be an example of `getUnread`
|
# Here should be an example of `getUnread`
|
||||||
|
@@ -1,5 +1,10 @@
|
|||||||
# -*- coding: UTF-8 -*-
|
# -*- coding: UTF-8 -*-
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
from datetime import datetime
|
||||||
|
from .client import *
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
fbchat
|
fbchat
|
||||||
~~~~~~
|
~~~~~~
|
||||||
@@ -10,11 +15,9 @@
|
|||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
from .client import *
|
|
||||||
|
|
||||||
__copyright__ = 'Copyright 2015 - {} by Taehoon Kim'.format(datetime.now().year)
|
__copyright__ = 'Copyright 2015 - {} by Taehoon Kim'.format(datetime.now().year)
|
||||||
__version__ = '1.0.0'
|
__version__ = '1.0.19'
|
||||||
__license__ = 'BSD'
|
__license__ = 'BSD'
|
||||||
__author__ = 'Taehoon Kim; Moreels Pieter-Jan; Mads Marquart'
|
__author__ = 'Taehoon Kim; Moreels Pieter-Jan; Mads Marquart'
|
||||||
__email__ = 'carpedm20@gmail.com'
|
__email__ = 'carpedm20@gmail.com'
|
||||||
|
220
fbchat/client.py
220
fbchat/client.py
@@ -71,6 +71,9 @@ class Client(object):
|
|||||||
# If session cookies aren't set, not properly loaded or gives us an invalid session, then do the login
|
# If session cookies aren't set, not properly loaded or gives us an invalid session, then do the login
|
||||||
if not session_cookies or not self.setSession(session_cookies) or not self.isLoggedIn():
|
if not session_cookies or not self.setSession(session_cookies) or not self.isLoggedIn():
|
||||||
self.login(email, password, max_tries)
|
self.login(email, password, max_tries)
|
||||||
|
else:
|
||||||
|
self.email = email
|
||||||
|
self.password = password
|
||||||
|
|
||||||
"""
|
"""
|
||||||
INTERNAL REQUEST METHODS
|
INTERNAL REQUEST METHODS
|
||||||
@@ -109,7 +112,7 @@ class Client(object):
|
|||||||
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')
|
||||||
return self._session.post(url, headers=headers, data=payload, timeout=timeout, files=files)
|
return self._session.post(url, headers=headers, data=payload, timeout=timeout, files=files)
|
||||||
|
|
||||||
def graphql_request(self, *queries):
|
def graphql_requests(self, *queries):
|
||||||
"""
|
"""
|
||||||
.. todo::
|
.. todo::
|
||||||
Documenting this
|
Documenting this
|
||||||
@@ -124,11 +127,16 @@ class Client(object):
|
|||||||
|
|
||||||
j = graphql_response_to_json(checkRequest(self._post(ReqUrl.GRAPHQL, payload), do_json_check=False))
|
j = graphql_response_to_json(checkRequest(self._post(ReqUrl.GRAPHQL, payload), do_json_check=False))
|
||||||
|
|
||||||
if len(j) == 1:
|
|
||||||
return j[0]
|
|
||||||
else:
|
|
||||||
return tuple(j)
|
return tuple(j)
|
||||||
|
|
||||||
|
def graphql_request(self, query):
|
||||||
|
"""
|
||||||
|
Shorthand for `graphql_requests(query)[0]`
|
||||||
|
|
||||||
|
:raises: Exception if request failed
|
||||||
|
"""
|
||||||
|
return self.graphql_requests(query)[0]
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
END INTERNAL REQUEST METHODS
|
END INTERNAL REQUEST METHODS
|
||||||
@@ -196,7 +204,8 @@ class Client(object):
|
|||||||
r = self._cleanPost(ReqUrl.LOGIN, data)
|
r = self._cleanPost(ReqUrl.LOGIN, data)
|
||||||
|
|
||||||
# Usually, 'Checkpoint' will refer to 2FA
|
# Usually, 'Checkpoint' will refer to 2FA
|
||||||
if 'checkpoint' in r.url and 'Enter Security Code to Continue' in r.text:
|
if ('checkpoint' in r.url and
|
||||||
|
('Enter Security Code to Continue' in r.text or 'Enter Login Code to Continue' in r.text)):
|
||||||
r = self._2FA(r)
|
r = self._2FA(r)
|
||||||
|
|
||||||
# Sometimes Facebook tries to show the user a "Save Device" dialog
|
# Sometimes Facebook tries to show the user a "Save Device" dialog
|
||||||
@@ -420,7 +429,11 @@ class Client(object):
|
|||||||
|
|
||||||
for key in j['payload']:
|
for key in j['payload']:
|
||||||
k = j['payload'][key]
|
k = j['payload'][key]
|
||||||
users.append(User(k['id'], first_name=k['firstName'], url=k['uri'], photo=k['thumbSrc'], name=k['name'], is_friend=k['is_friend'], gender=GENDERS[k['gender']]))
|
if k['type'] in ['user', 'friend']:
|
||||||
|
if k['id'] in ['0', 0]:
|
||||||
|
# Skip invalid users
|
||||||
|
pass
|
||||||
|
users.append(User(k['id'], first_name=k.get('firstName'), url=k.get('uri'), photo=k.get('thumbSrc'), name=k.get('name'), is_friend=k.get('is_friend'), gender=GENDERS[k.get('gender')]))
|
||||||
|
|
||||||
return users
|
return users
|
||||||
|
|
||||||
@@ -453,9 +466,6 @@ class Client(object):
|
|||||||
|
|
||||||
return [graphql_to_page(node) for node in j[name]['pages']['nodes']]
|
return [graphql_to_page(node) for node in j[name]['pages']['nodes']]
|
||||||
|
|
||||||
#entries = self._searchFor(name)
|
|
||||||
#return [k for k in entries if k.type == ThreadType.PAGE]
|
|
||||||
|
|
||||||
def searchForGroups(self, name, limit=1):
|
def searchForGroups(self, name, limit=1):
|
||||||
"""
|
"""
|
||||||
Find and get group thread by its name
|
Find and get group thread by its name
|
||||||
@@ -508,35 +518,56 @@ class Client(object):
|
|||||||
j = checkRequest(self._post(ReqUrl.INFO, data))
|
j = checkRequest(self._post(ReqUrl.INFO, data))
|
||||||
|
|
||||||
if not j['payload']['profiles']:
|
if not j['payload']['profiles']:
|
||||||
raise Exception('No users returned')
|
raise Exception('No users/pages returned')
|
||||||
|
|
||||||
entries = {}
|
entries = {}
|
||||||
for _id in j['payload']['profiles']:
|
for _id in j['payload']['profiles']:
|
||||||
k = j['payload']['profiles'][_id]
|
k = j['payload']['profiles'][_id]
|
||||||
if k['type'] in ['user', 'friend']:
|
if k['type'] in ['user', 'friend']:
|
||||||
entries[_id] = User(_id, url=k['uri'], first_name=k['firstName'], is_friend=k['is_friend'], gender=GENDERS[k['gender']], photo=k['thumbSrc'], name=k['name'])
|
entries[_id] = {
|
||||||
|
'id': _id,
|
||||||
|
'type': ThreadType.USER,
|
||||||
|
'url': k.get('uri'),
|
||||||
|
'first_name': k.get('firstName'),
|
||||||
|
'is_viewer_friend': k.get('is_friend'),
|
||||||
|
'gender': k.get('gender'),
|
||||||
|
'profile_picture': {'uri': k.get('thumbSrc')},
|
||||||
|
'name': k.get('name')
|
||||||
|
}
|
||||||
elif k['type'] == 'page':
|
elif k['type'] == 'page':
|
||||||
entries[_id] = Page(_id, url=k['uri'], photo=k['thumbSrc'], name=k['name'])
|
entries[_id] = {
|
||||||
|
'id': _id,
|
||||||
|
'type': ThreadType.PAGE,
|
||||||
|
'url': k.get('uri'),
|
||||||
|
'profile_picture': {'uri': k.get('thumbSrc')},
|
||||||
|
'name': k.get('name')
|
||||||
|
}
|
||||||
else:
|
else:
|
||||||
raise Exception('{} had an unknown thread type: {}'.format(_id, k))
|
raise Exception('{} had an unknown thread type: {}'.format(_id, k))
|
||||||
|
|
||||||
|
log.debug(entries)
|
||||||
return entries
|
return entries
|
||||||
|
|
||||||
def fetchUserInfo(self, *user_ids):
|
def fetchUserInfo(self, *user_ids):
|
||||||
"""
|
"""
|
||||||
Get users' info from IDs, unordered
|
Get users' info from IDs, unordered
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
Sends two requests, to fetch all available info!
|
||||||
|
|
||||||
:param user_ids: One or more user ID(s) to query
|
:param user_ids: One or more user ID(s) to query
|
||||||
:return: :class:`models.User` objects, labeled by their ID
|
:return: :class:`models.User` objects, labeled by their ID
|
||||||
:rtype: dict
|
:rtype: dict
|
||||||
:raises: Exception if request failed
|
:raises: Exception if request failed
|
||||||
"""
|
"""
|
||||||
|
|
||||||
entries = self._fetchInfo(*user_ids)
|
threads = self.fetchThreadInfo(*user_ids)
|
||||||
users = {}
|
users = {}
|
||||||
for k in entries:
|
for k in threads:
|
||||||
if entries[k].type == ThreadType.USER:
|
if threads[k].type == ThreadType.USER:
|
||||||
users[k] = entries[k]
|
users[k] = threads[k]
|
||||||
|
else:
|
||||||
|
raise Exception('Thread {} was not a user'.format(threads[k]))
|
||||||
|
|
||||||
return users
|
return users
|
||||||
|
|
||||||
@@ -544,19 +575,104 @@ class Client(object):
|
|||||||
"""
|
"""
|
||||||
Get pages' info from IDs, unordered
|
Get pages' info from IDs, unordered
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
Sends two requests, to fetch all available info!
|
||||||
|
|
||||||
:param page_ids: One or more page ID(s) to query
|
:param page_ids: One or more page ID(s) to query
|
||||||
:return: :class:`models.Page` objects, labeled by their ID
|
:return: :class:`models.Page` objects, labeled by their ID
|
||||||
:rtype: dict
|
:rtype: dict
|
||||||
:raises: Exception if request failed
|
:raises: Exception if request failed
|
||||||
"""
|
"""
|
||||||
|
|
||||||
entries = self._fetchInfo(*user_ids)
|
threads = self.fetchThreadInfo(*page_ids)
|
||||||
users = {}
|
pages = {}
|
||||||
for k in entries:
|
for k in threads:
|
||||||
if entries[k].type == ThreadType.PAGE:
|
if threads[k].type == ThreadType.PAGE:
|
||||||
users[k] = entries[k]
|
pages[k] = threads[k]
|
||||||
|
else:
|
||||||
|
raise Exception('Thread {} was not a page'.format(threads[k]))
|
||||||
|
|
||||||
return users
|
return pages
|
||||||
|
|
||||||
|
def fetchGroupInfo(self, *group_ids):
|
||||||
|
"""
|
||||||
|
Get groups' info from IDs, unordered
|
||||||
|
|
||||||
|
:param group_ids: One or more group ID(s) to query
|
||||||
|
:return: :class:`models.Group` objects, labeled by their ID
|
||||||
|
:rtype: dict
|
||||||
|
:raises: Exception if request failed
|
||||||
|
"""
|
||||||
|
|
||||||
|
threads = self.fetchThreadInfo(*group_ids)
|
||||||
|
groups = {}
|
||||||
|
for k in threads:
|
||||||
|
if threads[k].type == ThreadType.GROUP:
|
||||||
|
groups[k] = threads[k]
|
||||||
|
else:
|
||||||
|
raise Exception('Thread {} was not a group'.format(threads[k]))
|
||||||
|
|
||||||
|
return groups
|
||||||
|
|
||||||
|
def fetchThreadInfo(self, *thread_ids):
|
||||||
|
"""
|
||||||
|
Get threads' info from IDs, unordered
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
Sends two requests if users or pages are present, to fetch all available info!
|
||||||
|
|
||||||
|
:param thread_ids: One or more thread ID(s) to query
|
||||||
|
:return: :class:`models.Thread` objects, labeled by their ID
|
||||||
|
:rtype: dict
|
||||||
|
:raises: Exception if request failed
|
||||||
|
"""
|
||||||
|
|
||||||
|
queries = []
|
||||||
|
for thread_id in thread_ids:
|
||||||
|
queries.append(GraphQL(doc_id='1386147188135407', params={
|
||||||
|
'id': thread_id,
|
||||||
|
'message_limit': 0,
|
||||||
|
'load_messages': False,
|
||||||
|
'load_read_receipts': False,
|
||||||
|
'before': None
|
||||||
|
}))
|
||||||
|
|
||||||
|
j = self.graphql_requests(*queries)
|
||||||
|
|
||||||
|
for i, entry in enumerate(j):
|
||||||
|
if entry.get('message_thread') is None:
|
||||||
|
# If you don't have an existing thread with this person, attempt to retrieve user data anyways
|
||||||
|
j[i]['message_thread'] = {
|
||||||
|
'thread_key': {
|
||||||
|
'other_user_id': thread_ids[i]
|
||||||
|
},
|
||||||
|
'thread_type': 'ONE_TO_ONE'
|
||||||
|
}
|
||||||
|
|
||||||
|
pages_and_user_ids = [k['message_thread']['thread_key']['other_user_id'] for k in j if k['message_thread'].get('thread_type') == 'ONE_TO_ONE']
|
||||||
|
pages_and_users = {}
|
||||||
|
if len(pages_and_user_ids) != 0:
|
||||||
|
pages_and_users = self._fetchInfo(*pages_and_user_ids)
|
||||||
|
|
||||||
|
rtn = {}
|
||||||
|
for i, entry in enumerate(j):
|
||||||
|
entry = entry['message_thread']
|
||||||
|
if entry.get('thread_type') == 'GROUP':
|
||||||
|
_id = entry['thread_key']['thread_fbid']
|
||||||
|
rtn[_id] = graphql_to_group(entry)
|
||||||
|
elif entry.get('thread_type') == 'ONE_TO_ONE':
|
||||||
|
_id = entry['thread_key']['other_user_id']
|
||||||
|
if pages_and_users.get(_id) is None:
|
||||||
|
raise Exception('Could not fetch thread {}'.format(_id))
|
||||||
|
entry.update(pages_and_users[_id])
|
||||||
|
if entry['type'] == ThreadType.USER:
|
||||||
|
rtn[_id] = graphql_to_user(entry)
|
||||||
|
else:
|
||||||
|
rtn[_id] = graphql_to_page(entry)
|
||||||
|
else:
|
||||||
|
raise Exception('{} had an unknown thread type: {}'.format(thread_ids[i], entry))
|
||||||
|
|
||||||
|
return rtn
|
||||||
|
|
||||||
def fetchThreadMessages(self, thread_id=None, limit=20, before=None):
|
def fetchThreadMessages(self, thread_id=None, limit=20, before=None):
|
||||||
"""
|
"""
|
||||||
@@ -624,9 +740,10 @@ class Client(object):
|
|||||||
if k['thread_type'] == 1:
|
if k['thread_type'] == 1:
|
||||||
if k['other_user_fbid'] not in participants:
|
if k['other_user_fbid'] not in participants:
|
||||||
raise Exception('A thread was not in participants: {}'.format(j['payload']))
|
raise Exception('A thread was not in participants: {}'.format(j['payload']))
|
||||||
|
participants[k['other_user_fbid']].message_count = k['message_count']
|
||||||
entries.append(participants[k['other_user_fbid']])
|
entries.append(participants[k['other_user_fbid']])
|
||||||
elif k['thread_type'] == 2:
|
elif k['thread_type'] == 2:
|
||||||
entries.append(Group(k['thread_fbid'], participants=[p.strip('fbid:') for p in k['participants']], photo=k['image_src'], name=k['name']))
|
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']))
|
||||||
else:
|
else:
|
||||||
raise Exception('A thread had an unknown thread type: {}'.format(k))
|
raise Exception('A thread had an unknown thread type: {}'.format(k))
|
||||||
|
|
||||||
@@ -715,6 +832,13 @@ class Client(object):
|
|||||||
except (KeyError, IndexError) as e:
|
except (KeyError, IndexError) as e:
|
||||||
raise Exception('Error when sending message: No message IDs could be found: {}'.format(j))
|
raise Exception('Error when sending message: No message IDs could be found: {}'.format(j))
|
||||||
|
|
||||||
|
# update JS token if receive from response
|
||||||
|
if ('jsmods' in j) and ('require' in j['jsmods']):
|
||||||
|
try:
|
||||||
|
self.payloadDefault['fb_dtsg'] = j['jsmods']['require'][0][3][0]
|
||||||
|
except (KeyError, IndexError) as e:
|
||||||
|
log.warning("Error when update fb_dtsg. Facebook might have changed protocol.")
|
||||||
|
|
||||||
return message_id
|
return message_id
|
||||||
|
|
||||||
def sendMessage(self, message, thread_id=None, thread_type=ThreadType.USER):
|
def sendMessage(self, message, thread_id=None, thread_type=ThreadType.USER):
|
||||||
@@ -827,7 +951,7 @@ class Client(object):
|
|||||||
"""
|
"""
|
||||||
Sends a local image to a thread
|
Sends a local image to a thread
|
||||||
|
|
||||||
:param image_path: URL of an image to upload and send
|
:param image_path: Path of an image to upload and send
|
||||||
:param message: Additional message
|
:param message: Additional message
|
||||||
:param thread_id: User/Group ID to send to. See :ref:`intro_threads`
|
:param thread_id: User/Group ID to send to. See :ref:`intro_threads`
|
||||||
:param thread_type: See :ref:`intro_threads`
|
:param thread_type: See :ref:`intro_threads`
|
||||||
@@ -899,6 +1023,7 @@ class Client(object):
|
|||||||
:type thread_type: models.ThreadType
|
:type thread_type: models.ThreadType
|
||||||
:raises: Exception if request failed
|
:raises: Exception if request failed
|
||||||
"""
|
"""
|
||||||
|
|
||||||
thread_id, thread_type = self._getThread(thread_id, thread_type)
|
thread_id, thread_type = self._getThread(thread_id, thread_type)
|
||||||
|
|
||||||
if thread_type == ThreadType.USER:
|
if thread_type == ThreadType.USER:
|
||||||
@@ -911,6 +1036,8 @@ class Client(object):
|
|||||||
data['log_message_data[name]'] = title
|
data['log_message_data[name]'] = title
|
||||||
data['log_message_type'] = 'log:thread-name'
|
data['log_message_type'] = 'log:thread-name'
|
||||||
|
|
||||||
|
return self._doSendRequest(data)
|
||||||
|
|
||||||
def changeNickname(self, nickname, user_id, thread_id=None, thread_type=ThreadType.USER):
|
def changeNickname(self, nickname, user_id, thread_id=None, thread_type=ThreadType.USER):
|
||||||
"""
|
"""
|
||||||
Changes the nickname of a user in a thread
|
Changes the nickname of a user in a thread
|
||||||
@@ -1163,7 +1290,6 @@ class Client(object):
|
|||||||
thread_id = str(metadata['threadKey']['threadFbId'])
|
thread_id = str(metadata['threadKey']['threadFbId'])
|
||||||
self.onPeopleAdded(mid=mid, added_ids=added_ids, author_id=author_id, thread_id=thread_id,
|
self.onPeopleAdded(mid=mid, added_ids=added_ids, author_id=author_id, thread_id=thread_id,
|
||||||
ts=ts, msg=m)
|
ts=ts, msg=m)
|
||||||
continue
|
|
||||||
|
|
||||||
# Left/removed participants
|
# Left/removed participants
|
||||||
elif 'leftParticipantFbId' in delta:
|
elif 'leftParticipantFbId' in delta:
|
||||||
@@ -1171,15 +1297,13 @@ class Client(object):
|
|||||||
thread_id = str(metadata['threadKey']['threadFbId'])
|
thread_id = str(metadata['threadKey']['threadFbId'])
|
||||||
self.onPersonRemoved(mid=mid, removed_id=removed_id, author_id=author_id, thread_id=thread_id,
|
self.onPersonRemoved(mid=mid, removed_id=removed_id, author_id=author_id, thread_id=thread_id,
|
||||||
ts=ts, msg=m)
|
ts=ts, msg=m)
|
||||||
continue
|
|
||||||
|
|
||||||
# Color change
|
# Color change
|
||||||
elif delta_type == "change_thread_theme":
|
elif delta_type == "change_thread_theme":
|
||||||
new_color = ThreadColor(delta["untypedData"]["theme_color"])
|
new_color = graphql_color_to_enum(delta["untypedData"]["theme_color"])
|
||||||
thread_id, thread_type = getThreadIdAndThreadType(metadata)
|
thread_id, thread_type = getThreadIdAndThreadType(metadata)
|
||||||
self.onColorChange(mid=mid, author_id=author_id, new_color=new_color, thread_id=thread_id,
|
self.onColorChange(mid=mid, author_id=author_id, new_color=new_color, thread_id=thread_id,
|
||||||
thread_type=thread_type, ts=ts, metadata=metadata, msg=m)
|
thread_type=thread_type, ts=ts, metadata=metadata, msg=m)
|
||||||
continue
|
|
||||||
|
|
||||||
# Emoji change
|
# Emoji change
|
||||||
elif delta_type == "change_thread_icon":
|
elif delta_type == "change_thread_icon":
|
||||||
@@ -1187,7 +1311,6 @@ class Client(object):
|
|||||||
thread_id, thread_type = getThreadIdAndThreadType(metadata)
|
thread_id, thread_type = getThreadIdAndThreadType(metadata)
|
||||||
self.onEmojiChange(mid=mid, author_id=author_id, new_emoji=new_emoji, thread_id=thread_id,
|
self.onEmojiChange(mid=mid, author_id=author_id, new_emoji=new_emoji, thread_id=thread_id,
|
||||||
thread_type=thread_type, ts=ts, metadata=metadata, msg=m)
|
thread_type=thread_type, ts=ts, metadata=metadata, msg=m)
|
||||||
continue
|
|
||||||
|
|
||||||
# Thread title change
|
# Thread title change
|
||||||
elif delta.get("class") == "ThreadName":
|
elif delta.get("class") == "ThreadName":
|
||||||
@@ -1195,7 +1318,6 @@ class Client(object):
|
|||||||
thread_id, thread_type = getThreadIdAndThreadType(metadata)
|
thread_id, thread_type = getThreadIdAndThreadType(metadata)
|
||||||
self.onTitleChange(mid=mid, author_id=author_id, new_title=new_title, thread_id=thread_id,
|
self.onTitleChange(mid=mid, author_id=author_id, new_title=new_title, thread_id=thread_id,
|
||||||
thread_type=thread_type, ts=ts, metadata=metadata, msg=m)
|
thread_type=thread_type, ts=ts, metadata=metadata, msg=m)
|
||||||
continue
|
|
||||||
|
|
||||||
# Nickname change
|
# Nickname change
|
||||||
elif delta_type == "change_thread_nickname":
|
elif delta_type == "change_thread_nickname":
|
||||||
@@ -1205,7 +1327,6 @@ class Client(object):
|
|||||||
self.onNicknameChange(mid=mid, author_id=author_id, changed_for=changed_for,
|
self.onNicknameChange(mid=mid, author_id=author_id, changed_for=changed_for,
|
||||||
new_nickname=new_nickname,
|
new_nickname=new_nickname,
|
||||||
thread_id=thread_id, thread_type=thread_type, ts=ts, metadata=metadata, msg=m)
|
thread_id=thread_id, thread_type=thread_type, ts=ts, metadata=metadata, msg=m)
|
||||||
continue
|
|
||||||
|
|
||||||
# Message delivered
|
# Message delivered
|
||||||
elif delta.get("class") == "DeliveryReceipt":
|
elif delta.get("class") == "DeliveryReceipt":
|
||||||
@@ -1215,7 +1336,6 @@ class Client(object):
|
|||||||
thread_id, thread_type = getThreadIdAndThreadType(delta)
|
thread_id, thread_type = getThreadIdAndThreadType(delta)
|
||||||
self.onMessageDelivered(msg_ids=message_ids, delivered_for=delivered_for,
|
self.onMessageDelivered(msg_ids=message_ids, delivered_for=delivered_for,
|
||||||
thread_id=thread_id, thread_type=thread_type, ts=ts, metadata=metadata, msg=m)
|
thread_id=thread_id, thread_type=thread_type, ts=ts, metadata=metadata, msg=m)
|
||||||
continue
|
|
||||||
|
|
||||||
# Message seen
|
# Message seen
|
||||||
elif delta.get("class") == "ReadReceipt":
|
elif delta.get("class") == "ReadReceipt":
|
||||||
@@ -1225,7 +1345,6 @@ class Client(object):
|
|||||||
thread_id, thread_type = getThreadIdAndThreadType(delta)
|
thread_id, thread_type = getThreadIdAndThreadType(delta)
|
||||||
self.onMessageSeen(seen_by=seen_by, thread_id=thread_id, thread_type=thread_type,
|
self.onMessageSeen(seen_by=seen_by, thread_id=thread_id, thread_type=thread_type,
|
||||||
seen_ts=seen_ts, ts=delivered_ts, metadata=metadata, msg=m)
|
seen_ts=seen_ts, ts=delivered_ts, metadata=metadata, msg=m)
|
||||||
continue
|
|
||||||
|
|
||||||
# Messages marked as seen
|
# Messages marked as seen
|
||||||
elif delta.get("class") == "MarkRead":
|
elif delta.get("class") == "MarkRead":
|
||||||
@@ -1237,19 +1356,21 @@ class Client(object):
|
|||||||
threads = [getThreadIdAndThreadType({"threadKey": thr}) for thr in delta.get("threadKeys")]
|
threads = [getThreadIdAndThreadType({"threadKey": thr}) for thr in delta.get("threadKeys")]
|
||||||
|
|
||||||
# thread_id, thread_type = getThreadIdAndThreadType(delta)
|
# thread_id, thread_type = getThreadIdAndThreadType(delta)
|
||||||
self.onMarkedSeen(threads=threads, seen_ts=seen_ts, delivered_ts=delivered_ts, metadata=delta, msg=m)
|
self.onMarkedSeen(threads=threads, seen_ts=seen_ts, ts=delivered_ts, metadata=delta, msg=m)
|
||||||
continue
|
|
||||||
|
|
||||||
# New message
|
# New message
|
||||||
elif delta.get("class") == "NewMessage":
|
elif delta.get("class") == "NewMessage":
|
||||||
message = delta.get('body', '')
|
message = delta.get('body', '')
|
||||||
thread_id, thread_type = getThreadIdAndThreadType(metadata)
|
thread_id, thread_type = getThreadIdAndThreadType(metadata)
|
||||||
self.onMessage(mid=mid, author_id=author_id, message=message,
|
self.onMessage(mid=mid, author_id=author_id, message=message,
|
||||||
thread_id=thread_id, thread_type=thread_type, ts=ts, metadata=m, msg=m)
|
thread_id=thread_id, thread_type=thread_type, ts=ts, metadata=metadata, msg=m)
|
||||||
continue
|
|
||||||
|
# Unknown message type
|
||||||
|
else:
|
||||||
|
self.onUnknownMesssageType(msg=m)
|
||||||
|
|
||||||
# Inbox
|
# Inbox
|
||||||
if mtype == "inbox":
|
elif mtype == "inbox":
|
||||||
self.onInbox(unseen=m["unseen"], unread=m["unread"], recent_unread=m["recent_unread"], msg=m)
|
self.onInbox(unseen=m["unseen"], unread=m["unread"], recent_unread=m["recent_unread"], msg=m)
|
||||||
|
|
||||||
# Typing
|
# Typing
|
||||||
@@ -1277,6 +1398,14 @@ class Client(object):
|
|||||||
elif mtype == "deltaflow":
|
elif mtype == "deltaflow":
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Chat timestamp
|
||||||
|
elif mtype == "chatproxy-presence":
|
||||||
|
buddylist = {}
|
||||||
|
for _id in m.get('buddyList', {}):
|
||||||
|
payload = m['buddyList'][_id]
|
||||||
|
buddylist[_id] = payload.get('lat')
|
||||||
|
self.onChatTimestamp(buddylist=buddylist, msg=m)
|
||||||
|
|
||||||
# Unknown message type
|
# Unknown message type
|
||||||
else:
|
else:
|
||||||
self.onUnknownMesssageType(msg=m)
|
self.onUnknownMesssageType(msg=m)
|
||||||
@@ -1298,9 +1427,6 @@ class Client(object):
|
|||||||
Does one cycle of the listening loop.
|
Does one cycle of the listening loop.
|
||||||
This method is useful if you want to control fbchat from an external event loop
|
This method is useful if you want to control fbchat from an external event loop
|
||||||
|
|
||||||
.. note::
|
|
||||||
markAlive is currently broken, and is ignored
|
|
||||||
|
|
||||||
:param markAlive: Whether this should ping the Facebook server before running
|
:param markAlive: Whether this should ping the Facebook server before running
|
||||||
:type markAlive: bool
|
:type markAlive: bool
|
||||||
:return: Whether the loop should keep running
|
:return: Whether the loop should keep running
|
||||||
@@ -1380,7 +1506,7 @@ class Client(object):
|
|||||||
|
|
||||||
:param exception: The exception that was encountered
|
:param exception: The exception that was encountered
|
||||||
"""
|
"""
|
||||||
raise exception
|
log.exception('Got exception while listening')
|
||||||
|
|
||||||
|
|
||||||
def onMessage(self, mid=None, author_id=None, message=None, thread_id=None, thread_type=ThreadType.USER, ts=None, metadata=None, msg={}):
|
def onMessage(self, mid=None, author_id=None, message=None, thread_id=None, thread_type=ThreadType.USER, ts=None, metadata=None, msg={}):
|
||||||
@@ -1558,7 +1684,7 @@ class Client(object):
|
|||||||
"""
|
"""
|
||||||
log.info('Inbox event: {}, {}, {}'.format(unseen, unread, recent_unread))
|
log.info('Inbox event: {}, {}, {}'.format(unseen, unread, recent_unread))
|
||||||
|
|
||||||
def onQprimer(self, made=None, msg={}):
|
def onQprimer(self, ts=None, msg={}):
|
||||||
"""
|
"""
|
||||||
Called when the client just started listening
|
Called when the client just started listening
|
||||||
|
|
||||||
@@ -1567,6 +1693,14 @@ class Client(object):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def onChatTimestamp(self, buddylist={}, msg={}):
|
||||||
|
"""
|
||||||
|
Called when the client receives chat online presence update
|
||||||
|
|
||||||
|
:param buddylist: A list of dicts with friend id and last seen timestamp
|
||||||
|
:param msg: A full set of the data recieved
|
||||||
|
"""
|
||||||
|
log.debug('Chat Timestamps received: {}'.format(buddylist))
|
||||||
|
|
||||||
def onUnknownMesssageType(self, msg={}):
|
def onUnknownMesssageType(self, msg={}):
|
||||||
"""
|
"""
|
||||||
|
@@ -23,6 +23,44 @@ class ConcatJSONDecoder(json.JSONDecoder):
|
|||||||
return objs
|
return objs
|
||||||
# End shameless copy
|
# End shameless copy
|
||||||
|
|
||||||
|
def graphql_color_to_enum(color):
|
||||||
|
if color is None:
|
||||||
|
return None
|
||||||
|
if len(color) == 0:
|
||||||
|
return ThreadColor.MESSENGER_BLUE
|
||||||
|
try:
|
||||||
|
return ThreadColor('#{}'.format(color[2:].lower()))
|
||||||
|
except ValueError:
|
||||||
|
raise Exception('Could not get ThreadColor from color: {}'.format(color))
|
||||||
|
|
||||||
|
def get_customization_info(thread):
|
||||||
|
if thread is None or thread.get('customization_info') is None:
|
||||||
|
return {}
|
||||||
|
info = thread['customization_info']
|
||||||
|
|
||||||
|
rtn = {
|
||||||
|
'emoji': info.get('emoji'),
|
||||||
|
'color': graphql_color_to_enum(info.get('outgoing_bubble_color'))
|
||||||
|
}
|
||||||
|
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')
|
||||||
|
elif info.get('participant_customizations'):
|
||||||
|
uid = thread.get('thread_key', {}).get('other_user_id') or thread.get('id')
|
||||||
|
pc = info['participant_customizations']
|
||||||
|
if len(pc) > 0:
|
||||||
|
if pc[0].get('participant_id') == uid:
|
||||||
|
rtn['nickname'] = pc[0].get('nickname')
|
||||||
|
else:
|
||||||
|
rtn['own_nickname'] = pc[0].get('nickname')
|
||||||
|
if len(pc) > 1:
|
||||||
|
if pc[1].get('participant_id') == uid:
|
||||||
|
rtn['nickname'] = pc[1].get('nickname')
|
||||||
|
else:
|
||||||
|
rtn['own_nickname'] = pc[1].get('nickname')
|
||||||
|
return rtn
|
||||||
|
|
||||||
def graphql_to_message(message):
|
def graphql_to_message(message):
|
||||||
if message.get('message_sender') is None:
|
if message.get('message_sender') is None:
|
||||||
message['message_sender'] = {}
|
message['message_sender'] = {}
|
||||||
@@ -40,12 +78,14 @@ def graphql_to_message(message):
|
|||||||
text=message.get('message').get('text'),
|
text=message.get('message').get('text'),
|
||||||
mentions=[Mention(m.get('entity', {}).get('id'), offset=m.get('offset'), length=m.get('length')) for m in message.get('message').get('ranges', [])],
|
mentions=[Mention(m.get('entity', {}).get('id'), offset=m.get('offset'), length=m.get('length')) for m in message.get('message').get('ranges', [])],
|
||||||
sticker=message.get('sticker'),
|
sticker=message.get('sticker'),
|
||||||
attachments=message.get('blob_attachments')
|
attachments=message.get('blob_attachments'),
|
||||||
|
extensible_attachment=message.get('extensible_attachment')
|
||||||
)
|
)
|
||||||
|
|
||||||
def graphql_to_user(user):
|
def graphql_to_user(user):
|
||||||
if user.get('profile_picture') is None:
|
if user.get('profile_picture') is None:
|
||||||
user['profile_picture'] = {}
|
user['profile_picture'] = {}
|
||||||
|
c_info = get_customization_info(user)
|
||||||
return User(
|
return User(
|
||||||
user['id'],
|
user['id'],
|
||||||
url=user.get('url'),
|
url=user.get('url'),
|
||||||
@@ -54,18 +94,28 @@ def graphql_to_user(user):
|
|||||||
is_friend=user.get('is_viewer_friend'),
|
is_friend=user.get('is_viewer_friend'),
|
||||||
gender=GENDERS[user.get('gender')],
|
gender=GENDERS[user.get('gender')],
|
||||||
affinity=user.get('affinity'),
|
affinity=user.get('affinity'),
|
||||||
|
nickname=c_info.get('nickname'),
|
||||||
|
color=c_info.get('color'),
|
||||||
|
emoji=c_info.get('emoji'),
|
||||||
|
own_nickname=c_info.get('own_nickname'),
|
||||||
photo=user['profile_picture'].get('uri'),
|
photo=user['profile_picture'].get('uri'),
|
||||||
name=user.get('name')
|
name=user.get('name'),
|
||||||
|
message_count=user.get('messages_count')
|
||||||
)
|
)
|
||||||
|
|
||||||
def graphql_to_group(group):
|
def graphql_to_group(group):
|
||||||
if group.get('image') is None:
|
if group.get('image') is None:
|
||||||
group['image'] = {}
|
group['image'] = {}
|
||||||
|
c_info = get_customization_info(group)
|
||||||
return Group(
|
return Group(
|
||||||
group['thread_key']['thread_fbid'],
|
group['thread_key']['thread_fbid'],
|
||||||
participants=[node['messaging_actor']['id'] for node in group['all_participants']['nodes']],
|
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'),
|
||||||
photo=group['image'].get('uri'),
|
photo=group['image'].get('uri'),
|
||||||
name=group.get('name')
|
name=group.get('name'),
|
||||||
|
message_count=group.get('messages_count')
|
||||||
)
|
)
|
||||||
|
|
||||||
def graphql_to_page(page):
|
def graphql_to_page(page):
|
||||||
@@ -79,7 +129,8 @@ def graphql_to_page(page):
|
|||||||
city=page.get('city').get('name'),
|
city=page.get('city').get('name'),
|
||||||
category=page.get('category_type'),
|
category=page.get('category_type'),
|
||||||
photo=page['profile_picture'].get('uri'),
|
photo=page['profile_picture'].get('uri'),
|
||||||
name=page.get('name')
|
name=page.get('name'),
|
||||||
|
message_count=page.get('messages_count')
|
||||||
)
|
)
|
||||||
|
|
||||||
def graphql_queries_to_json(*queries):
|
def graphql_queries_to_json(*queries):
|
||||||
@@ -160,6 +211,14 @@ class GraphQL(object):
|
|||||||
id
|
id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
customization_info {
|
||||||
|
participant_customizations {
|
||||||
|
participant_id,
|
||||||
|
nickname
|
||||||
|
},
|
||||||
|
outgoing_bubble_color,
|
||||||
|
emoji
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
@@ -13,13 +13,18 @@ class Thread(object):
|
|||||||
photo = str
|
photo = str
|
||||||
#: The name of the thread
|
#: The name of the thread
|
||||||
name = str
|
name = str
|
||||||
|
#: Timestamp of last message
|
||||||
def __init__(self, _type, uid, photo=None, name=None):
|
last_message_timestamp = str
|
||||||
|
#: Number of messages in the thread
|
||||||
|
message_count = int
|
||||||
|
def __init__(self, _type, uid, photo=None, name=None, last_message_timestamp=None, message_count=None):
|
||||||
"""Represents a Facebook thread"""
|
"""Represents a Facebook thread"""
|
||||||
self.uid = str(uid)
|
self.uid = str(uid)
|
||||||
self.type = _type
|
self.type = _type
|
||||||
self.photo = photo
|
self.photo = photo
|
||||||
self.name = name
|
self.name = name
|
||||||
|
self.last_message_timestamp = last_message_timestamp
|
||||||
|
self.message_count = message_count
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return self.__unicode__()
|
return self.__unicode__()
|
||||||
@@ -41,8 +46,16 @@ class User(Thread):
|
|||||||
gender = str
|
gender = str
|
||||||
#: From 0 to 1. How close the client is to the user
|
#: From 0 to 1. How close the client is to the user
|
||||||
affinity = float
|
affinity = float
|
||||||
|
#: The user's nickname
|
||||||
|
nickname = str
|
||||||
|
#: The clients nickname, as seen by the user
|
||||||
|
own_nickname = str
|
||||||
|
#: A :class:`ThreadColor`. The message color
|
||||||
|
color = None
|
||||||
|
#: The default emoji
|
||||||
|
emoji = str
|
||||||
|
|
||||||
def __init__(self, uid, url=None, first_name=None, last_name=None, is_friend=None, gender=None, affinity=None, **kwargs):
|
def __init__(self, uid, url=None, first_name=None, last_name=None, is_friend=None, gender=None, affinity=None, nickname=None, own_nickname=None, color=None, emoji=None, **kwargs):
|
||||||
"""Represents a Facebook user. Inherits `Thread`"""
|
"""Represents a Facebook user. Inherits `Thread`"""
|
||||||
super(User, self).__init__(ThreadType.USER, uid, **kwargs)
|
super(User, self).__init__(ThreadType.USER, uid, **kwargs)
|
||||||
self.url = url
|
self.url = url
|
||||||
@@ -51,16 +64,29 @@ class User(Thread):
|
|||||||
self.is_friend = is_friend
|
self.is_friend = is_friend
|
||||||
self.gender = gender
|
self.gender = gender
|
||||||
self.affinity = affinity
|
self.affinity = affinity
|
||||||
|
self.nickname = nickname
|
||||||
|
self.own_nickname = own_nickname
|
||||||
|
self.color = color
|
||||||
|
self.emoji = emoji
|
||||||
|
|
||||||
|
|
||||||
class Group(Thread):
|
class Group(Thread):
|
||||||
#: List of the group thread's participant user IDs
|
#: Unique list (set) of the group thread's participant user IDs
|
||||||
participants = list
|
participants = set
|
||||||
|
#: Dict, containing user nicknames mapped to their IDs
|
||||||
|
nicknames = dict
|
||||||
|
#: A :class:`ThreadColor`. The groups's message color
|
||||||
|
color = None
|
||||||
|
#: The groups's default emoji
|
||||||
|
emoji = str
|
||||||
|
|
||||||
def __init__(self, uid, participants=[], **kwargs):
|
def __init__(self, uid, participants=set(), nicknames=[], color=None, emoji=None, **kwargs):
|
||||||
"""Represents a Facebook group. Inherits `Thread`"""
|
"""Represents a Facebook group. Inherits `Thread`"""
|
||||||
super(Group, self).__init__(ThreadType.GROUP, uid, **kwargs)
|
super(Group, self).__init__(ThreadType.GROUP, uid, **kwargs)
|
||||||
self.participants = participants
|
self.participants = participants
|
||||||
|
self.nicknames = nicknames
|
||||||
|
self.color = color
|
||||||
|
self.emoji = emoji
|
||||||
|
|
||||||
|
|
||||||
class Page(Thread):
|
class Page(Thread):
|
||||||
@@ -104,8 +130,10 @@ class Message(object):
|
|||||||
sticker = str
|
sticker = str
|
||||||
#: A list of attachments
|
#: A list of attachments
|
||||||
attachments = list
|
attachments = list
|
||||||
|
#: An extensible attachment, e.g. share object
|
||||||
|
extensible_attachment = dict
|
||||||
|
|
||||||
def __init__(self, uid, author=None, timestamp=None, is_read=None, reactions=[], text=None, mentions=[], sticker=None, attachments=[]):
|
def __init__(self, uid, author=None, timestamp=None, is_read=None, reactions=[], text=None, mentions=[], sticker=None, attachments=[], extensible_attachment={}):
|
||||||
"""Represents a Facebook message"""
|
"""Represents a Facebook message"""
|
||||||
self.uid = uid
|
self.uid = uid
|
||||||
self.author = author
|
self.author = author
|
||||||
@@ -116,6 +144,7 @@ class Message(object):
|
|||||||
self.mentions = mentions
|
self.mentions = mentions
|
||||||
self.sticker = sticker
|
self.sticker = sticker
|
||||||
self.attachments = attachments
|
self.attachments = attachments
|
||||||
|
self.extensible_attachment = extensible_attachment
|
||||||
|
|
||||||
|
|
||||||
class Mention(object):
|
class Mention(object):
|
||||||
|
@@ -32,12 +32,6 @@ USER_AGENTS = [
|
|||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6"
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6"
|
||||||
]
|
]
|
||||||
|
|
||||||
TYPES = {
|
|
||||||
'Page': ThreadType.PAGE,
|
|
||||||
'User': ThreadType.USER,
|
|
||||||
'Group': ThreadType.GROUP
|
|
||||||
}
|
|
||||||
|
|
||||||
GENDERS = {
|
GENDERS = {
|
||||||
# For standard requests
|
# For standard requests
|
||||||
0: 'unknown',
|
0: 'unknown',
|
||||||
@@ -54,7 +48,7 @@ GENDERS = {
|
|||||||
11: 'unknown_plural',
|
11: 'unknown_plural',
|
||||||
|
|
||||||
# For graphql requests
|
# For graphql requests
|
||||||
#'': 'unknown',
|
'UNKNOWN': 'unknown',
|
||||||
'FEMALE': 'female_singular',
|
'FEMALE': 'female_singular',
|
||||||
'MALE': 'male_singular',
|
'MALE': 'male_singular',
|
||||||
#'': 'female_singular_guess',
|
#'': 'female_singular_guess',
|
||||||
@@ -108,17 +102,17 @@ def now():
|
|||||||
def strip_to_json(text):
|
def strip_to_json(text):
|
||||||
try:
|
try:
|
||||||
return text[text.index('{'):]
|
return text[text.index('{'):]
|
||||||
except ValueError as e:
|
except ValueError:
|
||||||
return None
|
raise Exception('No JSON object found: {}, {}'.format(repr(text), text.index('{')))
|
||||||
|
|
||||||
def get_decoded(r):
|
def get_decoded_r(r):
|
||||||
if not isinstance(r._content, str):
|
return get_decoded(r._content)
|
||||||
return r._content.decode(facebookEncoding)
|
|
||||||
else:
|
def get_decoded(content):
|
||||||
return r._content
|
return content.decode(facebookEncoding)
|
||||||
|
|
||||||
def get_json(r):
|
def get_json(r):
|
||||||
return json.loads(strip_to_json(get_decoded(r)))
|
return json.loads(strip_to_json(get_decoded_r(r)))
|
||||||
|
|
||||||
def digitToChar(digit):
|
def digitToChar(digit):
|
||||||
if digit < 10:
|
if digit < 10:
|
||||||
@@ -162,16 +156,17 @@ def checkRequest(r, do_json_check=True):
|
|||||||
if not r.ok:
|
if not r.ok:
|
||||||
raise Exception('Error when sending request: Got {} response'.format(r.status_code))
|
raise Exception('Error when sending request: Got {} response'.format(r.status_code))
|
||||||
|
|
||||||
content = get_decoded(r)
|
content = get_decoded_r(r)
|
||||||
|
|
||||||
if content is None or len(content) == 0:
|
if content is None or len(content) == 0:
|
||||||
raise Exception('Error when sending request: Got empty response')
|
raise Exception('Error when sending request: Got empty response')
|
||||||
|
|
||||||
if do_json_check:
|
if do_json_check:
|
||||||
|
content = strip_to_json(content)
|
||||||
try:
|
try:
|
||||||
j = json.loads(strip_to_json(content))
|
j = json.loads(content)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception('Error while parsing JSON: {}'.format(repr(content)))
|
raise Exception('Error while parsing JSON: {}'.format(repr(content)), e)
|
||||||
check_json(j)
|
check_json(j)
|
||||||
return j
|
return j
|
||||||
else:
|
else:
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
requests
|
requests
|
||||||
lxml
|
lxml
|
||||||
beautifulsoup4
|
beautifulsoup4
|
||||||
|
enum34
|
||||||
|
5
setup.py
5
setup.py
@@ -16,7 +16,10 @@ except ImportError:
|
|||||||
with open('README.rst') as f:
|
with open('README.rst') as f:
|
||||||
readme_content = f.read().strip()
|
readme_content = f.read().strip()
|
||||||
|
|
||||||
requirements = [line.rstrip('\n') for line in open('requirements.txt')]
|
try:
|
||||||
|
requirements = [line.rstrip('\n') for line in open(os.path.join('fbchat.egg-info', 'requires.txt'))]
|
||||||
|
except IOError:
|
||||||
|
requirements = [line.rstrip('\n') for line in open('requirements.txt')]
|
||||||
|
|
||||||
version = None
|
version = None
|
||||||
author = None
|
author = None
|
||||||
|
11
tests.py
11
tests.py
@@ -79,7 +79,7 @@ class TestFbchat(unittest.TestCase):
|
|||||||
users = client.fetchAllUsers()
|
users = client.fetchAllUsers()
|
||||||
self.assertGreater(len(users), 0)
|
self.assertGreater(len(users), 0)
|
||||||
|
|
||||||
def test_searchForUsers(self):
|
def test_searchFor(self):
|
||||||
users = client.searchForUsers('Mark Zuckerberg')
|
users = client.searchForUsers('Mark Zuckerberg')
|
||||||
self.assertGreater(len(users), 0)
|
self.assertGreater(len(users), 0)
|
||||||
|
|
||||||
@@ -92,6 +92,10 @@ class TestFbchat(unittest.TestCase):
|
|||||||
self.assertEqual(u.url[:4], 'http')
|
self.assertEqual(u.url[:4], 'http')
|
||||||
self.assertEqual(u.name, 'Mark Zuckerberg')
|
self.assertEqual(u.name, 'Mark Zuckerberg')
|
||||||
|
|
||||||
|
group_name = client.changeThreadTitle('tést_searchFor', thread_id=group_id, thread_type=ThreadType.GROUP)
|
||||||
|
groups = client.searchForGroups('té')
|
||||||
|
self.assertGreater(len(groups), 0)
|
||||||
|
|
||||||
def test_sendEmoji(self):
|
def test_sendEmoji(self):
|
||||||
self.assertIsNotNone(client.sendEmoji(size=EmojiSize.SMALL, thread_id=user_id, thread_type=ThreadType.USER))
|
self.assertIsNotNone(client.sendEmoji(size=EmojiSize.SMALL, thread_id=user_id, thread_type=ThreadType.USER))
|
||||||
self.assertIsNotNone(client.sendEmoji(size=EmojiSize.MEDIUM, thread_id=user_id, thread_type=ThreadType.USER))
|
self.assertIsNotNone(client.sendEmoji(size=EmojiSize.MEDIUM, thread_id=user_id, thread_type=ThreadType.USER))
|
||||||
@@ -140,10 +144,13 @@ class TestFbchat(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertTrue(client.got_qprimer)
|
self.assertTrue(client.got_qprimer)
|
||||||
|
|
||||||
def test_fetchUserInfo(self):
|
def test_fetchInfo(self):
|
||||||
info = client.fetchUserInfo('4')['4']
|
info = client.fetchUserInfo('4')['4']
|
||||||
self.assertEqual(info.name, 'Mark Zuckerberg')
|
self.assertEqual(info.name, 'Mark Zuckerberg')
|
||||||
|
|
||||||
|
info = client.fetchGroupInfo(group_id)[group_id]
|
||||||
|
self.assertEqual(info.type, ThreadType.GROUP)
|
||||||
|
|
||||||
def test_removeAddFromGroup(self):
|
def test_removeAddFromGroup(self):
|
||||||
client.removeUserFromGroup(user_id, thread_id=group_id)
|
client.removeUserFromGroup(user_id, thread_id=group_id)
|
||||||
client.addUsersToGroup(user_id, thread_id=group_id)
|
client.addUsersToGroup(user_id, thread_id=group_id)
|
||||||
|
Reference in New Issue
Block a user