Compare commits

...

15 Commits

Author SHA1 Message Date
Mads Marquart
0d5e4f6d3f Version up thanks to @enwar3 2017-06-29 16:03:55 +02:00
Mads Marquart
92a5ffdef8 Merge pull request #170 from OMGWINNING/master
Add extensible_attachment field to Message for fb share objects
2017-06-29 16:02:27 +02:00
Joe Lau
b3359fccdb Add last_message_timestamp to Thread objects 2017-06-28 18:08:45 -07:00
Joe Lau
d8f7366d1f Add extensible_attachment field to Message for fb share objects 2017-06-28 13:19:17 -07:00
Mads Marquart
ff94dc20af Minor cleanup 2017-06-28 16:06:13 +02:00
Mads Marquart
a8df0a548f Minor fixes 2017-06-28 14:42:11 +02:00
Mads Marquart
13d0dc4ba4 Fixed ChangeThreadTitle and ThreadColor.MESSENGER_BLUE 2017-06-28 14:30:29 +02:00
Mads Marquart
64125a1aca Updated to 1.0.6, thanks to @enwar3 2017-06-28 10:24:44 +02:00
Mads Marquart
4feae03092 Merge pull request #169 from OMGWINNING/master
Handle empty participant_customizations field
2017-06-28 10:23:33 +02:00
Joe Lau
5f993c2bf8 Use .get() instead 2017-06-27 16:16:51 -07:00
Joe Lau
35bbcbffba Add __init__.py 2017-06-26 17:54:25 -07:00
Joe Lau
5faca54d67 Handle empty participant_customizations field 2017-06-26 14:16:57 -07:00
Mads Marquart
82496b8e04 Minor fixes 2017-06-26 17:02:32 +02:00
Mads Marquart
2d74ec7823 Made getAllUsers more stable 2017-06-26 15:42:26 +02:00
Mads Marquart
1d42c4d3a6 Updated to 1.0.4, added fetchThread&GroupInfo and improved models 2017-06-26 15:41:58 +02:00
8 changed files with 262 additions and 59 deletions

View File

@@ -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:

View File

@@ -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`

View File

@@ -17,7 +17,7 @@ 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.3' __version__ = '1.0.10'
__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'

View File

@@ -3,7 +3,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import requests import requests
import urllib import urllib
import traceback
from uuid import uuid1 from uuid import uuid1
from random import choice from random import choice
from datetime import datetime from datetime import datetime
@@ -72,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
@@ -110,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
@@ -125,10 +127,15 @@ 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 tuple(j)
return j[0]
else: def graphql_request(self, query):
return tuple(j) """
Shorthand for `graphql_requests(query)[0]`
:raises: Exception if request failed
"""
return self.graphql_requests(query)[0]
""" """
@@ -422,6 +429,9 @@ class Client(object):
for key in j['payload']: for key in j['payload']:
k = j['payload'][key] k = j['payload'][key]
if k['type'] in ['user', 'friend']: 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')])) 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
@@ -455,9 +465,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
@@ -510,35 +517,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
@@ -546,19 +574,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):
""" """
@@ -628,7 +741,7 @@ class Client(object):
raise Exception('A thread was not in participants: {}'.format(j['payload'])) raise Exception('A thread was not in participants: {}'.format(j['payload']))
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']))
else: else:
raise Exception('A thread had an unknown thread type: {}'.format(k)) raise Exception('A thread had an unknown thread type: {}'.format(k))
@@ -901,6 +1014,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:
@@ -913,6 +1027,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
@@ -1165,7 +1281,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:
@@ -1173,15 +1288,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":
@@ -1189,7 +1302,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":
@@ -1197,7 +1309,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":
@@ -1207,7 +1318,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":
@@ -1217,7 +1327,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":
@@ -1227,7 +1336,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":
@@ -1239,19 +1347,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
@@ -1379,7 +1489,7 @@ class Client(object):
:param exception: The exception that was encountered :param exception: The exception that was encountered
""" """
traceback.print_exc() 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={}):

View File

@@ -23,6 +23,41 @@ 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['participant_customizations']:
rtn['nicknames'][k['participant_id']] = k.get('nickname')
elif info.get('participant_customizations'):
_id = thread.get('thread_key', {}).get('other_user_id') or thread.get('id')
if info['participant_customizations'][0]['participant_id'] == _id:
rtn['nickname'] = info['participant_customizations'][0]
rtn['own_nickname'] = info['participant_customizations'][1]
elif info['participant_customizations'][1]['participant_id'] == _id:
rtn['nickname'] = info['participant_customizations'][1]
rtn['own_nickname'] = info['participant_customizations'][0]
else:
raise Exception('No participant matching the user {} found: {}'.format(_id, info['participant_customizations']))
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 +75,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,6 +91,10 @@ 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')
) )
@@ -61,9 +102,13 @@ def graphql_to_user(user):
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')
) )
@@ -160,6 +205,14 @@ class GraphQL(object):
id id
} }
} }
},
customization_info {
participant_customizations {
participant_id,
nickname
},
outgoing_bubble_color,
emoji
} }
} }
""" """

View File

@@ -13,13 +13,16 @@ 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
last_message_timestamp = str
def __init__(self, _type, uid, photo=None, name=None): def __init__(self, _type, uid, photo=None, name=None, last_message_timestamp=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
def __repr__(self): def __repr__(self):
return self.__unicode__() return self.__unicode__()
@@ -41,8 +44,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 +62,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 +128,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 +142,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):

View File

@@ -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',

View File

@@ -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('')
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)