Compare commits

...

12 Commits

Author SHA1 Message Date
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
Mads Marquart
4a8ef00442 Fixed a few bugs, updated to v. 1.0.3 2017-06-26 11:37:54 +02:00
Mads Marquart
add06ffa7a I was having trouble with PyPI ;) 2017-06-22 23:35:28 +02:00
Mads Marquart
fbb8d8e24a Update README.rst 2017-06-22 22:54:12 +02:00
Mads Marquart
cd0e001219 Update README.rst 2017-06-22 22:50:10 +02:00
10 changed files with 264 additions and 63 deletions

View File

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

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

@@ -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.6'
__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,6 +3,7 @@
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
@@ -109,7 +110,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,10 +125,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]
""" """
@@ -420,7 +426,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 +463,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 +515,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 +572,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):
""" """
@@ -626,7 +739,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))
@@ -1175,7 +1288,7 @@ class Client(object):
# 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)
@@ -1237,7 +1350,7 @@ 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 continue
# New message # New message
@@ -1298,9 +1411,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 +1490,7 @@ class Client(object):
:param exception: The exception that was encountered :param exception: The exception that was encountered
""" """
raise exception traceback.print_exc()
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 +1668,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

View File

@@ -23,6 +23,39 @@ 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
try:
return ThreadColor('#{}'.format(color[2:].lower()))
except KeyError:
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'] = {}
@@ -46,6 +79,7 @@ def graphql_to_message(message):
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 +88,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 +99,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 +202,14 @@ class GraphQL(object):
id id
} }
} }
},
customization_info {
participant_customizations {
participant_id,
nickname
},
outgoing_bubble_color,
emoji
} }
} }
""" """

View File

@@ -41,8 +41,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 +59,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):

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

View File

@@ -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 FileNotFoundError:
requirements = [line.rstrip('\n') for line in open('requirements.txt')]
version = None version = None
author = None author = None

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)