Compare commits
52 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
57954816b2 | ||
|
3e4e1f9bb9 | ||
|
7340918209 | ||
|
707df4f941 | ||
|
8eb6b83411 | ||
|
e0aedd617b | ||
|
ee81620c14 | ||
|
2d027af71a | ||
|
9d5f06b810 | ||
|
b8fdcda2fb | ||
|
0dac7b7b81 | ||
|
b750e753d6 | ||
|
ee33e92bed | ||
|
7413a643f6 | ||
|
cd4a18cb5a | ||
|
c00b3df8b2 | ||
|
f0c6e8612f | ||
|
1cebbf92e6 | ||
|
a64982583b | ||
|
cb8b0915de | ||
|
1d2576b06d | ||
|
ead9a3c0e9 | ||
|
59ba418faa | ||
|
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 |
@@ -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.7'
|
__version__ = '1.0.24'
|
||||||
__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'
|
||||||
|
398
fbchat/client.py
398
fbchat/client.py
File diff suppressed because it is too large
Load Diff
@@ -30,8 +30,8 @@ def graphql_color_to_enum(color):
|
|||||||
return ThreadColor.MESSENGER_BLUE
|
return ThreadColor.MESSENGER_BLUE
|
||||||
try:
|
try:
|
||||||
return ThreadColor('#{}'.format(color[2:].lower()))
|
return ThreadColor('#{}'.format(color[2:].lower()))
|
||||||
except KeyError, ValueError:
|
except ValueError:
|
||||||
raise Exception('Could not get ThreadColor from color: {}'.format(color))
|
raise FBchatException('Could not get ThreadColor from color: {}'.format(color))
|
||||||
|
|
||||||
def get_customization_info(thread):
|
def get_customization_info(thread):
|
||||||
if thread is None or thread.get('customization_info') is None:
|
if thread is None or thread.get('customization_info') is None:
|
||||||
@@ -44,18 +44,21 @@ def get_customization_info(thread):
|
|||||||
}
|
}
|
||||||
if thread.get('thread_type') == 'GROUP' or thread.get('is_group_thread') or thread.get('thread_key', {}).get('thread_fbid'):
|
if thread.get('thread_type') == 'GROUP' or thread.get('is_group_thread') or thread.get('thread_key', {}).get('thread_fbid'):
|
||||||
rtn['nicknames'] = {}
|
rtn['nicknames'] = {}
|
||||||
for k in info['participant_customizations']:
|
for k in info.get('participant_customizations', []):
|
||||||
rtn['nicknames'][k['participant_id']] = k.get('nickname')
|
rtn['nicknames'][k['participant_id']] = k.get('nickname')
|
||||||
elif info.get('participant_customizations'):
|
elif info.get('participant_customizations'):
|
||||||
_id = thread.get('thread_key', {}).get('other_user_id') or thread.get('id')
|
uid = thread.get('thread_key', {}).get('other_user_id') or thread.get('id')
|
||||||
if info['participant_customizations'][0]['participant_id'] == _id:
|
pc = info['participant_customizations']
|
||||||
rtn['nickname'] = info['participant_customizations'][0]
|
if len(pc) > 0:
|
||||||
rtn['own_nickname'] = info['participant_customizations'][1]
|
if pc[0].get('participant_id') == uid:
|
||||||
elif info['participant_customizations'][1]['participant_id'] == _id:
|
rtn['nickname'] = pc[0].get('nickname')
|
||||||
rtn['nickname'] = info['participant_customizations'][1]
|
else:
|
||||||
rtn['own_nickname'] = info['participant_customizations'][0]
|
rtn['own_nickname'] = pc[0].get('nickname')
|
||||||
else:
|
if len(pc) > 1:
|
||||||
raise Exception('No participant matching the user {} found: {}'.format(_id, info['participant_customizations']))
|
if pc[1].get('participant_id') == uid:
|
||||||
|
rtn['nickname'] = pc[1].get('nickname')
|
||||||
|
else:
|
||||||
|
rtn['own_nickname'] = pc[1].get('nickname')
|
||||||
return rtn
|
return rtn
|
||||||
|
|
||||||
def graphql_to_message(message):
|
def graphql_to_message(message):
|
||||||
@@ -75,7 +78,8 @@ 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):
|
||||||
@@ -95,7 +99,8 @@ def graphql_to_user(user):
|
|||||||
emoji=c_info.get('emoji'),
|
emoji=c_info.get('emoji'),
|
||||||
own_nickname=c_info.get('own_nickname'),
|
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):
|
||||||
@@ -109,7 +114,8 @@ def graphql_to_group(group):
|
|||||||
color=c_info.get('color'),
|
color=c_info.get('color'),
|
||||||
emoji=c_info.get('emoji'),
|
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):
|
||||||
@@ -123,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):
|
||||||
@@ -136,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:
|
||||||
@@ -169,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 = """
|
||||||
|
@@ -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__()
|
||||||
@@ -125,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
|
||||||
@@ -137,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):
|
||||||
@@ -165,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
|
||||||
|
@@ -48,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',
|
||||||
@@ -93,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'
|
||||||
|
|
||||||
@@ -103,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)
|
||||||
@@ -143,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:
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
requests
|
requests
|
||||||
lxml
|
lxml
|
||||||
beautifulsoup4
|
beautifulsoup4
|
||||||
|
enum34; python_version == '2.7'
|
||||||
|
10
setup.py
10
setup.py
@@ -16,10 +16,12 @@ except ImportError:
|
|||||||
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',
|
||||||
|
"enum34; python_version == '2.7'"
|
||||||
|
]
|
||||||
|
|
||||||
version = None
|
version = None
|
||||||
author = None
|
author = None
|
||||||
|
Reference in New Issue
Block a user