Compare commits

...

65 Commits

Author SHA1 Message Date
Mads Marquart
023fd58f05 Version up, thanks to @ekohilas 2017-10-03 22:46:04 +02:00
Mads Marquart
ad10a8f07f Merge pull request #213 from ekohilas/requirements_fix
Updates setup.py for better compatibility with setuptools
2017-10-03 22:44:56 +02:00
Mads Marquart
7d6cf039d4 Merge branch 'master' into requirements_fix 2017-10-03 22:44:03 +02:00
ekohilas
f0271e17b0 updated for older setuptools 2017-10-04 01:57:02 +11:00
Mads Marquart
57954816b2 Version up, thanks to @WeiTang114 2017-10-03 08:29:34 +02:00
Mads Marquart
3e4e1f9bb9 Merge pull request #212 from WeiTang114/gif_support_2
Add Gif support to send(Local/Remote)Image
2017-10-03 08:26:10 +02:00
Mads Marquart
7340918209 Merge pull request #211 from WeiTang114/fetch_pending_thread_2
Enable fetching pending/archived threads
2017-10-03 08:25:58 +02:00
Tang
707df4f941 use mimetype to see if it's a GIF
thanks to @madsmtm's good idea
2017-10-03 03:29:15 +08:00
Tang
8eb6b83411 Update for feedback by @madsmtm
1. Add ThreadLocation Enum in models.
2. avoid using build-in name "type" as parameter name
3. replace ValueError with FBchatUserError

thanks to @madsmtm
2017-10-03 03:05:08 +08:00
Tang
e0aedd617b add param is_gif to doc of functions 2017-10-03 01:40:19 +08:00
Tang
ee81620c14 Add send GIF images support
When uploading and sending GIF images, the keys are explicitly changed
to "gif_id" or "gif_ids" rather than "image_id" or "image_ids".
2017-10-03 01:39:20 +08:00
Tang
2d027af71a Enable fetching pending/archived threads
Add "type" parameter to fetchThreadList().
type can be 'inbox', 'pending' or 'archived'

If set to 'pending', it can fetch messages from unknown users.
It is quite useful to build a service accepting requests from anyone.

For example, in doOneListen(), fetch pending messages once for a while
to handle the messages from strangers.
2017-10-03 01:37:25 +08:00
Mads Marquart
9d5f06b810 Fixed pip setup 2017-09-30 19:17:40 +02:00
Mads Marquart
b8fdcda2fb Properly uploading requirements (pip requires changed version number) 2017-09-30 01:15:41 +02:00
Mads Marquart
0dac7b7b81 Version up, thanks to @ekohilas 2017-09-27 21:20:20 +02:00
Mads Marquart
b750e753d6 Merge pull request #206 from ekohilas/master
Fixes 2FA bug and updates pip requirements
2017-09-27 21:19:14 +02:00
ekohilas
ee33e92bed added conditional enum34 2.7 requirement 2017-09-27 19:24:35 +10:00
ekohilas
7413a643f6 fixed 2FA bug 2017-09-27 19:23:58 +10:00
Mads Marquart
cd4a18cb5a Version up 2017-09-25 20:02:35 +02:00
Mads Marquart
c00b3df8b2 Merge pull request #201 from madsmtm/improved-stability
Possibly fixes #175, added custom Exception classes
2017-09-25 20:01:23 +02:00
Mads Marquart
f0c6e8612f Fixed typo and made name more generic 2017-09-21 10:09:48 +02:00
Mads Marquart
1cebbf92e6 Fixed loading sessions 2017-09-20 11:31:44 +02:00
Mads Marquart
a64982583b Fixes 502/503 errors and a the 1357004 error
Thereby also moving ReqUrl to self.req_url
2017-09-19 23:08:48 +02:00
Mads Marquart
cb8b0915de Improved default doOneListen loop 2017-09-19 16:42:03 +02:00
Mads Marquart
1d2576b06d More custom exceptions 2017-09-19 16:36:24 +02:00
Mads Marquart
ead9a3c0e9 Improved error handling, and improved uid-loading
Requests would sometimes throw an error while retrieving the c_user cookie (If there were multiple cookies with this name)
2017-09-19 16:36:08 +02:00
Mads Marquart
59ba418faa Added custom exceptions
Added `FBchatException`, `FBchatFacebookError` and `FBchatUserError`, which can help in differentiating between errors
2017-09-19 16:31:53 +02:00
Mads Marquart
c51a332560 Version up, thanks to @PythonNut 2017-08-27 23:03:50 +02:00
Mads Marquart
a73d2feed6 Merge pull request #193 from PythonNut/master
Fix UNKNOWN gender in graphql requests
2017-08-27 23:02:05 +02:00
PythonNut
6929193e9d Fix UNKNOWN gender in graphql requests 2017-08-13 23:10:11 +00:00
Mads Marquart
fea4ad9e89 Version up, Thanks to ritu99 2017-08-10 15:25:38 +02:00
Mads Marquart
68099049d4 Merge pull request #189 from ritu99/master
Added Message Count to thread information
2017-08-10 15:22:22 +02:00
Ritvik Annam
44cf08bdfd fetchThreadInfo now pulls message_count 2017-08-10 01:15:29 -05:00
Ritvik Annam
9e32cf17a4 fetchThreadList now pulls message_count 2017-08-10 00:53:06 -05:00
Mads Marquart
0661367ebb Properly fixed #182 2017-08-02 23:08:34 +02:00
Mads Marquart
3c07e42ba2 Version up, fixed #182 2017-07-26 23:13:19 +02:00
Mads Marquart
2cd6376818 Merge pull request #178 from Bankde/fix-fail-after-running-for-days
Fix issue when running for long time
2017-07-26 23:03:49 +02:00
Mads Marquart
5e7f7750de Fixed enums in python 2.7, thanks to @liamkirsh 2017-07-12 14:52:15 +02:00
Bankde@hotmail.com
2a223ec6db fix array indexing (I don't know why fb do that) 2017-07-10 10:25:23 +07:00
Mads Marquart
a99108fff6 Version up thanks to @Bankde 2017-07-09 20:55:06 +02:00
Mads Marquart
8de4698cc4 Merge pull request #174 from Bankde/fix-error-in-python2
No FileNotFoundError in py2
2017-07-09 20:53:47 +02:00
Bankde@hotmail.com
637319ec2c add token update 2017-07-10 00:51:51 +07:00
Bankde@hotmail.com
f9398564cd replace FileNotFoundError with IOError so it can work in Py2 2017-07-05 09:18:13 +07:00
Mads Marquart
b57f423eb4 Version up thanks to @aaronlewism 2017-07-01 12:48:02 +02:00
Mads Marquart
3093f1f2b6 Merge pull request #173 from aaronlewism/master
Check for alternate 2Factor page text
2017-07-01 12:46:11 +02:00
Aaron Lewis
961777e0c1 Check for alternate 2Factor page text 2017-06-29 13:21:25 -07:00
Mads Marquart
d7139701f7 Fixed typo, improved formatting. Thanks to @JarbasAI! 2017-06-29 20:04:01 +02:00
Mads Marquart
c6bac17d48 Merge pull request #172 from JarbasAI/patch-1
Add on chat presence event
2017-06-29 19:55:49 +02:00
Mads Marquart
3638fc5356 Made fetchThreadInfo able to fetch own user's info 2017-06-29 19:53:29 +02:00
Jarbas
aca9176f7f Add on chat presence event
Last_seen time stamps were handled in unknown message type, this info is freely available and potentially useful
2017-06-29 17:56:14 +01:00
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
10 changed files with 569 additions and 212 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.25'
__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'

File diff suppressed because it is too large Load Diff

View File

@@ -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 FBchatException('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):
@@ -92,7 +143,11 @@ def graphql_queries_to_json(*queries):
return json.dumps(rtn) return json.dumps(rtn)
def graphql_response_to_json(content): def graphql_response_to_json(content):
j = json.loads(content, cls=ConcatJSONDecoder) content = strip_to_json(content) # Usually only needed in some error cases
try:
j = json.loads(content, cls=ConcatJSONDecoder)
except Exception:
raise FBchatException('Error while parsing JSON: {}'.format(repr(content)))
rtn = [None]*(len(j)) rtn = [None]*(len(j))
for x in j: for x in j:
@@ -125,7 +180,7 @@ class GraphQL(object):
'query_params': params 'query_params': params
} }
else: else:
raise Exception('A query or doc_id must be specified') raise FBchatUserError('A query or doc_id must be specified')
FRAGMENT_USER = """ FRAGMENT_USER = """
@@ -160,6 +215,14 @@ class GraphQL(object):
id id
} }
} }
},
customization_info {
participant_customizations {
participant_id,
nickname
},
outgoing_bubble_color,
emoji
} }
} }
""" """

View File

@@ -4,6 +4,26 @@ from __future__ import unicode_literals
import enum import enum
class FBchatException(Exception):
"""Custom exception thrown by fbchat. All exceptions in the fbchat module inherits this"""
class FBchatFacebookError(FBchatException):
#: The error code that Facebook returned
fb_error_code = str
#: The error message that Facebook returned (In the user's own language)
fb_error_message = str
#: The status code that was sent in the http response (eg. 404) (Usually only set if not successful, aka. not 200)
request_status_code = int
def __init__(self, message, fb_error_code=None, fb_error_message=None, request_status_code=None):
super(FBchatFacebookError, self).__init__(message)
"""Thrown by fbchat when Facebook returns an error"""
self.fb_error_code = str(fb_error_code)
self.fb_error_message = fb_error_message
self.request_status_code = request_status_code
class FBchatUserError(FBchatException):
"""Thrown by fbchat when wrong values are entered"""
class Thread(object): class Thread(object):
#: The unique identifier of the thread. Can be used a `thread_id`. See :ref:`intro_threads` for more info #: The unique identifier of the thread. Can be used a `thread_id`. See :ref:`intro_threads` for more info
uid = str uid = str
@@ -13,13 +33,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 +66,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 +84,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 +150,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 +164,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):
@@ -144,6 +193,13 @@ class ThreadType(Enum):
GROUP = 2 GROUP = 2
PAGE = 3 PAGE = 3
class ThreadLocation(Enum):
"""Used to specify where a thread is located (inbox, pending, archived, other)."""
INBOX = 'inbox'
PENDING = 'pending'
ARCHIVED = 'action:archived'
OTHER = 'other'
class TypingStatus(Enum): class TypingStatus(Enum):
"""Used to specify whether the user is typing or has stopped typing""" """Used to specify whether the user is typing or has stopped typing"""
STOPPED = 0 STOPPED = 0

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',
@@ -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',
@@ -99,6 +93,16 @@ class ReqUrl(object):
TYPING = "https://www.facebook.com/ajax/messaging/typ.php" TYPING = "https://www.facebook.com/ajax/messaging/typ.php"
GRAPHQL = "https://www.facebook.com/api/graphqlbatch/" GRAPHQL = "https://www.facebook.com/api/graphqlbatch/"
pull_channel = 0
def change_pull_channel(self, channel=None):
if channel is None:
self.pull_channel = (self.pull_channel + 1) % 5 # Pull channel will be 0-4
else:
self.pull_channel = channel
self.STICKY = "https://{}-edge-chat.facebook.com/pull".format(self.pull_channel)
self.PING = "https://{}-edge-chat.facebook.com/active_ping".format(self.pull_channel)
facebookEncoding = 'UTF-8' facebookEncoding = 'UTF-8'
@@ -109,7 +113,7 @@ def strip_to_json(text):
try: try:
return text[text.index('{'):] return text[text.index('{'):]
except ValueError: except ValueError:
raise Exception('No JSON object found: {}, {}'.format(repr(text), text.index('{'))) raise FBchatException('No JSON object found: {}, {}'.format(repr(text), text.index('{')))
def get_decoded_r(r): def get_decoded_r(r):
return get_decoded(r._content) return get_decoded(r._content)
@@ -149,30 +153,31 @@ def generateOfflineThreadingID():
return str(int(msgs, 2)) return str(int(msgs, 2))
def check_json(j): def check_json(j):
if 'error' in j and j['error'] is not None: if j.get('error') is None:
if 'errorDescription' in j: return
# 'errorDescription' is in the users own language! if 'errorDescription' in j:
raise Exception('Error #{} when sending request: {}'.format(j['error'], j['errorDescription'])) # 'errorDescription' is in the users own language!
elif 'debug_info' in j['error']: raise FBchatFacebookError('Error #{} when sending request: {}'.format(j['error'], j['errorDescription']), fb_error_code=j['error'], fb_error_message=j['errorDescription'])
raise Exception('Error #{} when sending request: {}'.format(j['error']['code'], repr(j['error']['debug_info']))) elif 'debug_info' in j['error'] and 'code' in j['error']:
else: raise FBchatFacebookError('Error #{} when sending request: {}'.format(j['error']['code'], repr(j['error']['debug_info'])), fb_error_code=j['error']['code'], fb_error_message=j['error']['debug_info'])
raise Exception('Error {} when sending request'.format(j['error'])) else:
raise FBchatFacebookError('Error {} when sending request'.format(j['error']), fb_error_code=j['error'])
def checkRequest(r, do_json_check=True): def check_request(r, as_json=True):
if not r.ok: if not r.ok:
raise Exception('Error when sending request: Got {} response'.format(r.status_code)) raise FBchatFacebookError('Error when sending request: Got {} response'.format(r.status_code), request_status_code=r.status_code)
content = get_decoded_r(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 FBchatFacebookError('Error when sending request: Got empty response')
if do_json_check: if as_json:
content = strip_to_json(content) content = strip_to_json(content)
try: try:
j = json.loads(content) j = json.loads(content)
except Exception as e: except ValueError:
raise Exception('Error while parsing JSON: {}'.format(repr(content)), e) raise FBchatFacebookError('Error while parsing JSON: {}'.format(repr(content)))
check_json(j) check_json(j)
return j return j
else: else:

View File

@@ -1,3 +1,4 @@
requests requests
lxml lxml
beautifulsoup4 beautifulsoup4
enum34; python_version < '3.4'

View File

@@ -4,22 +4,24 @@
""" """
Setup script for fbchat Setup script for fbchat
""" """
import os import os
try: try:
from setuptools import setup from setuptools import setup
except ImportError: except ImportError:
from distutils.core import setup from distutils.core import setup
with open('README.rst') as f: with open('README.rst') as f:
readme_content = f.read().strip() readme_content = f.read().strip()
try: requirements = [
requirements = [line.rstrip('\n') for line in open(os.path.join('fbchat.egg-info', 'requires.txt'))] 'requests',
except FileNotFoundError: 'lxml',
requirements = [line.rstrip('\n') for line in open('requirements.txt')] 'beautifulsoup4'
]
extras_requirements = {
':python_version < "3.4"': ['enum34']
}
version = None version = None
author = None author = None
@@ -75,6 +77,7 @@ setup(
include_package_data=True, include_package_data=True,
packages=['fbchat'], packages=['fbchat'],
install_requires=requirements, install_requires=requirements,
extras_require=extras_requirements,
url=source, url=source,
version=version, version=version,
zip_safe=True, zip_safe=True,

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)