Merge pull request #141 from Dainius14/dev
reintroduce things skipped on conflict
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,5 +1,7 @@
|
|||||||
*py[co]
|
*py[co]
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
|
||||||
# Test scripts
|
# Test scripts
|
||||||
*.sh
|
*.sh
|
||||||
|
|
||||||
|
379
fbchat/client.py
379
fbchat/client.py
@@ -20,6 +20,7 @@ from bs4 import BeautifulSoup as bs
|
|||||||
from mimetypes import guess_type
|
from mimetypes import guess_type
|
||||||
from .utils import *
|
from .utils import *
|
||||||
from .models import *
|
from .models import *
|
||||||
|
from .event_hook import *
|
||||||
import time
|
import time
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@@ -68,7 +69,8 @@ class Client(object):
|
|||||||
documentation for the API.
|
documentation for the API.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, email, password, debug=False, info_log=False, user_agent=None, max_retries=5, session_cookies=None, logging_level=logging.INFO):
|
def __init__(self, email, password, debug=False, info_log=False, user_agent=None, max_retries=5,
|
||||||
|
session_cookies=None, logging_level=logging.INFO):
|
||||||
"""A client for the Facebook Chat (Messenger).
|
"""A client for the Facebook Chat (Messenger).
|
||||||
|
|
||||||
:param email: Facebook `email` or `id` or `phone number`
|
:param email: Facebook `email` or `id` or `phone number`
|
||||||
@@ -92,6 +94,8 @@ class Client(object):
|
|||||||
self.default_thread_type = None
|
self.default_thread_type = None
|
||||||
self.threads = []
|
self.threads = []
|
||||||
|
|
||||||
|
self._setupEventHooks()
|
||||||
|
|
||||||
if not user_agent:
|
if not user_agent:
|
||||||
user_agent = choice(USER_AGENTS)
|
user_agent = choice(USER_AGENTS)
|
||||||
|
|
||||||
@@ -114,9 +118,55 @@ class Client(object):
|
|||||||
handler.setLevel(logging_level)
|
handler.setLevel(logging_level)
|
||||||
|
|
||||||
# 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.is_logged_in():
|
if not session_cookies or not self.setSession(session_cookies) or not self.isLoggedIn():
|
||||||
self.login(email, password, max_retries)
|
self.login(email, password, max_retries)
|
||||||
|
|
||||||
|
def _setupEventHooks(self):
|
||||||
|
# Setup event hooks
|
||||||
|
self.onLoggingIn = EventHook(email=str)
|
||||||
|
self.onLoggedIn = EventHook(email=str)
|
||||||
|
self.onListening = EventHook()
|
||||||
|
|
||||||
|
self.onMessage = EventHook(mid=str, author_id=str, message=str, thread_id=int, thread_type=ThreadType, ts=str, metadata=dict)
|
||||||
|
self.onColorChange = EventHook(mid=str, author_id=str, new_color=str, thread_id=str, thread_type=ThreadType, ts=str, metadata=dict)
|
||||||
|
self.onEmojiChange = EventHook(mid=str, author_id=str, new_emoji=str, thread_id=str, thread_type=ThreadType, ts=str, metadata=dict)
|
||||||
|
self.onTitleChange = EventHook(mid=str, author_id=str, new_title=str, thread_id=str, thread_type=ThreadType, ts=str, metadata=dict)
|
||||||
|
self.onNicknameChange = EventHook(mid=str, author_id=str, changed_for=str, new_title=str, thread_id=str, thread_type=ThreadType, ts=str, metadata=dict)
|
||||||
|
# self.onTyping = EventHook(author_id=int, typing_status=TypingStatus)
|
||||||
|
# self.onSeen = EventHook(seen_by=str, thread_id=str, timestamp=str)
|
||||||
|
|
||||||
|
self.onInbox = EventHook(unseen=int, unread=int, recent_unread=int)
|
||||||
|
self.onPeopleAdded = EventHook(added_ids=list, author_id=str, thread_id=str)
|
||||||
|
self.onPersonRemoved = EventHook(removed_id=str, author_id=str, thread_id=str)
|
||||||
|
self.onFriendRequest = EventHook(from_id=str)
|
||||||
|
|
||||||
|
self.onUnknownMesssageType = EventHook(msg=dict)
|
||||||
|
|
||||||
|
# Setup event handlers
|
||||||
|
self.onLoggingIn += lambda email: log.info("Logging in %s..." % email)
|
||||||
|
self.onLoggedIn += lambda email: log.info("Login of %s successful." % email)
|
||||||
|
self.onListening += lambda: log.info("Listening...")
|
||||||
|
|
||||||
|
self.onMessage += lambda mid, author_id, message, thread_id, thread_type, ts, metadata:\
|
||||||
|
log.info("Message from %s in %s (%s): %s" % (author_id, thread_id, thread_type.name, message))
|
||||||
|
|
||||||
|
self.onColorChange += lambda mid, author_id, new_color, thread_id, thread_type, ts, metadata:\
|
||||||
|
log.info("Color change from %s in %s (%s): %s" % (author_id, thread_id, thread_type.name, new_color))
|
||||||
|
self.onEmojiChange += lambda mid, author_id, new_emoji, thread_id, thread_type, ts, metadata:\
|
||||||
|
log.info("Emoji change from %s in %s (%s): %s" % (author_id, thread_id, thread_type.name, new_emoji))
|
||||||
|
self.onTitleChange += lambda mid, author_id, new_title, thread_id, thread_type, ts, metadata:\
|
||||||
|
log.info("Title change from %s in %s (%s): %s" % (author_id, thread_id, thread_type.name, new_title))
|
||||||
|
self.onNicknameChange += lambda mid, author_id, new_title, changed_for, thread_id, thread_type, ts, metadata:\
|
||||||
|
log.info("Nickname change from %s in %s (%s) for %s: %s" % (author_id, thread_id, thread_type.name, changed_for, new_title))
|
||||||
|
|
||||||
|
self.onPeopleAdded += lambda added_ids, author_id, thread_id:\
|
||||||
|
log.info("%s added: %s" % (author_id, [x for x in added_ids]))
|
||||||
|
self.onPersonRemoved += lambda removed_id, author_id, thread_id:\
|
||||||
|
log.info("%s removed: %s" % (author_id, removed_id))
|
||||||
|
|
||||||
|
self.onUnknownMesssageType += lambda msg:\
|
||||||
|
log.info("Unknown message type received: %s" % msg)
|
||||||
|
|
||||||
@deprecated(deprecated_in='0.6.0', details='Use log.<level> instead')
|
@deprecated(deprecated_in='0.6.0', details='Use log.<level> instead')
|
||||||
def _console(self, msg):
|
def _console(self, msg):
|
||||||
"""Assumes an INFO level and log it.
|
"""Assumes an INFO level and log it.
|
||||||
@@ -133,11 +183,6 @@ class Client(object):
|
|||||||
"""
|
"""
|
||||||
log.debug(msg)
|
log.debug(msg)
|
||||||
|
|
||||||
def _setttstamp(self):
|
|
||||||
for i in self.fb_dtsg:
|
|
||||||
self.ttstamp += str(ord(i))
|
|
||||||
self.ttstamp += '2'
|
|
||||||
|
|
||||||
def _generatePayload(self, query):
|
def _generatePayload(self, query):
|
||||||
"""Adds the following defaults to the payload:
|
"""Adds the following defaults to the payload:
|
||||||
__rev, __user, __a, ttstamp, fb_dtsg, __req
|
__rev, __user, __a, ttstamp, fb_dtsg, __req
|
||||||
@@ -169,7 +214,7 @@ class Client(object):
|
|||||||
payload=self._generatePayload(None)
|
payload=self._generatePayload(None)
|
||||||
return self._session.post(url, data=payload, timeout=timeout, files=files)
|
return self._session.post(url, data=payload, timeout=timeout, files=files)
|
||||||
|
|
||||||
def _post_login(self):
|
def _postLogin(self):
|
||||||
self.payloadDefault = {}
|
self.payloadDefault = {}
|
||||||
self.client_id = hex(int(random()*2147483648))[2:]
|
self.client_id = hex(int(random()*2147483648))[2:]
|
||||||
self.start_time = now()
|
self.start_time = now()
|
||||||
@@ -183,7 +228,9 @@ class Client(object):
|
|||||||
log.debug(r.url)
|
log.debug(r.url)
|
||||||
self.fb_dtsg = soup.find("input", {'name':'fb_dtsg'})['value']
|
self.fb_dtsg = soup.find("input", {'name':'fb_dtsg'})['value']
|
||||||
self.fb_h = soup.find("input", {'name':'h'})['value']
|
self.fb_h = soup.find("input", {'name':'h'})['value']
|
||||||
self._setttstamp()
|
for i in self.fb_dtsg:
|
||||||
|
self.ttstamp += str(ord(i))
|
||||||
|
self.ttstamp += '2'
|
||||||
# Set default payload
|
# Set default payload
|
||||||
self.payloadDefault['__rev'] = int(r.text.split('"revision":',1)[1].split(",",1)[0])
|
self.payloadDefault['__rev'] = int(r.text.split('"revision":',1)[1].split(",",1)[0])
|
||||||
self.payloadDefault['__user'] = self.uid
|
self.payloadDefault['__user'] = self.uid
|
||||||
@@ -228,7 +275,7 @@ class Client(object):
|
|||||||
r = self._cleanGet(SaveDeviceURL)
|
r = self._cleanGet(SaveDeviceURL)
|
||||||
|
|
||||||
if 'home' in r.url:
|
if 'home' in r.url:
|
||||||
self._post_login()
|
self._postLogin()
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
@@ -284,13 +331,10 @@ class Client(object):
|
|||||||
r = self._cleanPost(CheckpointURL, data)
|
r = self._cleanPost(CheckpointURL, data)
|
||||||
return r
|
return r
|
||||||
|
|
||||||
def is_logged_in(self):
|
def isLoggedIn(self):
|
||||||
# Send a request to the login url, to see if we're directed to the home page.
|
# Send a request to the login url, to see if we're directed to the home page.
|
||||||
r = self._cleanGet(LoginURL)
|
r = self._cleanGet(LoginURL)
|
||||||
if 'home' in r.url:
|
return 'home' in r.url
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def getSession(self):
|
def getSession(self):
|
||||||
"""Returns the session cookies"""
|
"""Returns the session cookies"""
|
||||||
@@ -309,12 +353,11 @@ class Client(object):
|
|||||||
|
|
||||||
# Load cookies into current session
|
# Load cookies into current session
|
||||||
self._session.cookies = requests.cookies.merge_cookies(self._session.cookies, session_cookies)
|
self._session.cookies = requests.cookies.merge_cookies(self._session.cookies, session_cookies)
|
||||||
self._post_login()
|
self._postLogin()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def login(self, email, password, max_retries=5):
|
def login(self, email, password, max_retries=5):
|
||||||
# Logging in
|
self.onLoggingIn(email=email)
|
||||||
log.info("Logging in {}...".format(email))
|
|
||||||
|
|
||||||
if not (email and password):
|
if not (email and password):
|
||||||
raise Exception("Email and password not set.")
|
raise Exception("Email and password not set.")
|
||||||
@@ -328,7 +371,7 @@ class Client(object):
|
|||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
log.info("Login of {} successful.".format(email))
|
self.onLoggedIn(email=email)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
raise Exception("Login failed. Check email/password.")
|
raise Exception("Login failed. Check email/password.")
|
||||||
@@ -350,9 +393,10 @@ class Client(object):
|
|||||||
|
|
||||||
@deprecated(deprecated_in='0.10.2', details='Use setDefaultThread instead')
|
@deprecated(deprecated_in='0.10.2', details='Use setDefaultThread instead')
|
||||||
def setDefaultRecipient(self, recipient_id, is_user=True):
|
def setDefaultRecipient(self, recipient_id, is_user=True):
|
||||||
self.setDefaultThread(recipient_id, thread_type=isUserToThreadType(is_user))
|
self.setDefaultThread(str(recipient_id), thread_type=isUserToThreadType(is_user))
|
||||||
|
|
||||||
def setDefaultThread(self, thread_id=None, thread_type=ThreadType.USER):
|
def setDefaultThread(self, thread_id, thread_type):
|
||||||
|
# type: (str, ThreadType) -> None
|
||||||
"""Sets default thread to send messages and images to.
|
"""Sets default thread to send messages and images to.
|
||||||
|
|
||||||
:param thread_id: user/group ID to default to
|
:param thread_id: user/group ID to default to
|
||||||
@@ -361,38 +405,11 @@ class Client(object):
|
|||||||
self.default_thread_id = thread_id
|
self.default_thread_id = thread_id
|
||||||
self.default_thread_type = thread_type
|
self.default_thread_type = thread_type
|
||||||
|
|
||||||
def _adapt_user_in_chat_to_user_model(self, user_in_chat):
|
def resetDefaultThread(self):
|
||||||
""" Adapts user info from chat to User model acceptable initial dict
|
# type: () -> None
|
||||||
|
"""Resets default thread."""
|
||||||
:param user_in_chat: user info from chat
|
self.default_thread_id = None
|
||||||
|
self.default_thread_type = None
|
||||||
'dir': None,
|
|
||||||
'mThumbSrcSmall': None,
|
|
||||||
'is_friend': False,
|
|
||||||
'is_nonfriend_messenger_contact': True,
|
|
||||||
'alternateName': '',
|
|
||||||
'i18nGender': 16777216,
|
|
||||||
'vanity': '',
|
|
||||||
'type': 'friend',
|
|
||||||
'searchTokens': ['Voznesenskij', 'Sergej'],
|
|
||||||
'thumbSrc': 'https://fb-s-b-a.akamaihd.net/h-ak-xfa1/v/t1.0-1/c9.0.32.32/p32x32/10354686_10150004552801856_220367501106153455_n.jpg?oh=71a87d76d4e4d17615a20c43fb8dbb47&oe=59118CE4&__gda__=1493753268_ae75cef40e9785398e744259ccffd7ff',
|
|
||||||
'mThumbSrcLarge': None,
|
|
||||||
'firstName': 'Sergej',
|
|
||||||
'name': 'Sergej Voznesenskij',
|
|
||||||
'uri': 'https://www.facebook.com/profile.php?id=100014812758264',
|
|
||||||
'id': '100014812758264',
|
|
||||||
'gender': 2
|
|
||||||
"""
|
|
||||||
|
|
||||||
return {
|
|
||||||
'type': 'user',
|
|
||||||
'uid': user_in_chat['id'],
|
|
||||||
'photo': user_in_chat['thumbSrc'],
|
|
||||||
'path': user_in_chat['uri'],
|
|
||||||
'text': user_in_chat['name'],
|
|
||||||
'score': '',
|
|
||||||
'data': user_in_chat,
|
|
||||||
}
|
|
||||||
|
|
||||||
def getAllUsers(self):
|
def getAllUsers(self):
|
||||||
""" Gets all users from chat with info included """
|
""" Gets all users from chat with info included """
|
||||||
@@ -411,7 +428,7 @@ class Client(object):
|
|||||||
|
|
||||||
for k in payload.keys():
|
for k in payload.keys():
|
||||||
try:
|
try:
|
||||||
user = self._adapt_user_in_chat_to_user_model(payload[k])
|
user = User.adaptFromChat(payload[k])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -448,6 +465,7 @@ class Client(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def _send(self, thread_id=None, message=None, thread_type=ThreadType.USER, emoji_size=None, image_id=None, add_user_ids=None, new_title=None):
|
def _send(self, thread_id=None, message=None, thread_type=ThreadType.USER, emoji_size=None, image_id=None, add_user_ids=None, new_title=None):
|
||||||
|
# type: (str, str, ThreadType, EmojiSize, str, list, str) -> list
|
||||||
"""Send a message with given thread id
|
"""Send a message with given thread id
|
||||||
|
|
||||||
:param thread_id: the user id or thread id that you want to send a message to
|
:param thread_id: the user id or thread id that you want to send a message to
|
||||||
@@ -538,7 +556,7 @@ class Client(object):
|
|||||||
|
|
||||||
if not r.ok:
|
if not r.ok:
|
||||||
log.warning('Error when sending message: Got {} response'.format(r.status_code))
|
log.warning('Error when sending message: Got {} response'.format(r.status_code))
|
||||||
return False
|
return None
|
||||||
|
|
||||||
response_content = {}
|
response_content = {}
|
||||||
if isinstance(r.content, str) is False:
|
if isinstance(r.content, str) is False:
|
||||||
@@ -547,7 +565,7 @@ class Client(object):
|
|||||||
if 'error' in j:
|
if 'error' in j:
|
||||||
# 'errorDescription' is in the users own language!
|
# 'errorDescription' is in the users own language!
|
||||||
log.warning('Error #{} when sending message: {}'.format(j['error'], j['errorDescription']))
|
log.warning('Error #{} when sending message: {}'.format(j['error'], j['errorDescription']))
|
||||||
return False
|
return None
|
||||||
|
|
||||||
message_ids = []
|
message_ids = []
|
||||||
try:
|
try:
|
||||||
@@ -555,7 +573,7 @@ class Client(object):
|
|||||||
message_ids[0] # Try accessing element
|
message_ids[0] # Try accessing element
|
||||||
except (KeyError, IndexError) as e:
|
except (KeyError, IndexError) as e:
|
||||||
log.warning('Error when sending message: No message ids could be found')
|
log.warning('Error when sending message: No message ids could be found')
|
||||||
return False
|
return None
|
||||||
|
|
||||||
log.info('Message sent.')
|
log.info('Message sent.')
|
||||||
log.debug("Sending {}".format(r))
|
log.debug("Sending {}".format(r))
|
||||||
@@ -567,8 +585,10 @@ class Client(object):
|
|||||||
return self._send(thread_id=recipient_id, message=message, thread_type=isUserToThreadType(is_user), emoji_size=LIKES[like], image_id=image_id, add_user_ids=add_user_ids)
|
return self._send(thread_id=recipient_id, message=message, thread_type=isUserToThreadType(is_user), emoji_size=LIKES[like], image_id=image_id, add_user_ids=add_user_ids)
|
||||||
|
|
||||||
def sendMessage(self, message, thread_id=None, thread_type=ThreadType.USER):
|
def sendMessage(self, message, thread_id=None, thread_type=ThreadType.USER):
|
||||||
|
# type: (str, str, ThreadType) -> list
|
||||||
"""
|
"""
|
||||||
Sends a message to given (or default, if not) thread with an additional image.
|
Sends a message to given (or default, if not) thread with an additional image.
|
||||||
|
|
||||||
:param message: message to send
|
:param message: message to send
|
||||||
:param thread_id: user/group chat ID
|
:param thread_id: user/group chat ID
|
||||||
:param thread_type: specify whether thread_id is user or group chat
|
:param thread_type: specify whether thread_id is user or group chat
|
||||||
@@ -576,20 +596,25 @@ class Client(object):
|
|||||||
"""
|
"""
|
||||||
return self._send(thread_id=thread_id, message=message, thread_type=thread_type)
|
return self._send(thread_id=thread_id, message=message, thread_type=thread_type)
|
||||||
|
|
||||||
def sendEmoji(self, thread_id, size=Size.MEDIUM, emoji=None, thread_type=ThreadType.USER):
|
def sendEmoji(self, emoji=None, size=EmojiSize.SMALL, thread_id=None, thread_type=ThreadType.USER):
|
||||||
|
# type: (str, EmojiSize, str, ThreadType) -> list
|
||||||
"""
|
"""
|
||||||
Sends an emoji to given (or default, if not) thread.
|
Sends an emoji to given (or default, if not) thread.
|
||||||
|
|
||||||
|
:param emoji: WIP
|
||||||
:param size: size of emoji to send
|
:param size: size of emoji to send
|
||||||
:param thread_id: user/group chat ID
|
:param thread_id: user/group chat ID
|
||||||
:param emoji: WIP
|
|
||||||
:param thread_type: specify whether thread_id is user or group chat
|
:param thread_type: specify whether thread_id is user or group chat
|
||||||
:return: a list of message ids of the sent message(s)
|
:return: a list of message ids of the sent message(s)
|
||||||
"""
|
"""
|
||||||
return self._send(thread_id=thread_id, thread_type=thread_type, emoji_size=size)
|
return self._send(thread_id=thread_id, thread_type=thread_type, emoji_size=size)
|
||||||
|
|
||||||
def sendRemoteImage(self, image_url, message=None, thread_id=None, thread_type=ThreadType.USER, recipient_id=None, is_user=None, image=None):
|
def sendRemoteImage(self, image_url, message=None, thread_id=None, thread_type=ThreadType.USER,
|
||||||
|
recipient_id=None, is_user=None, image=None):
|
||||||
|
# type: (str, str, str, ThreadType) -> list
|
||||||
"""
|
"""
|
||||||
Sends an image from given URL to given (or default, if not) thread.
|
Sends an image from given URL to given (or default, if not) thread.
|
||||||
|
|
||||||
:param image_url: URL of an image to upload and send
|
:param image_url: URL of an image to upload and send
|
||||||
:param message: additional message
|
:param message: additional message
|
||||||
:param thread_id: user/group chat ID
|
:param thread_id: user/group chat ID
|
||||||
@@ -597,7 +622,6 @@ class Client(object):
|
|||||||
:return: a list of message ids of the sent message(s)
|
:return: a list of message ids of the sent message(s)
|
||||||
"""
|
"""
|
||||||
if recipient_id is not None:
|
if recipient_id is not None:
|
||||||
deprecation('sendRemoteImage(recipient_id)', deprecated_in='0.10.2', details='Use sendRemoteImage(thread_id) instead')
|
|
||||||
thread_id = recipient_id
|
thread_id = recipient_id
|
||||||
if is_user is not None:
|
if is_user is not None:
|
||||||
deprecation('sendRemoteImage(is_user)', deprecated_in='0.10.2', details='Use sendRemoteImage(thread_type) instead')
|
deprecation('sendRemoteImage(is_user)', deprecated_in='0.10.2', details='Use sendRemoteImage(thread_type) instead')
|
||||||
@@ -611,9 +635,12 @@ class Client(object):
|
|||||||
return self._send(thread_id=thread_id, message=message, thread_type=thread_type, image_id=image_id)
|
return self._send(thread_id=thread_id, message=message, thread_type=thread_type, image_id=image_id)
|
||||||
|
|
||||||
# Doesn't upload properly
|
# Doesn't upload properly
|
||||||
def sendLocalImage(self, image_path, message=None, thread_id=None, thread_type=ThreadType.USER, recipient_id=None, is_user=None, image=None):
|
def sendLocalImage(self, image_path, message=None, thread_id=None, thread_type=ThreadType.USER,
|
||||||
|
recipient_id=None, is_user=None, image=None):
|
||||||
|
# type: (str, str, str, ThreadType) -> list
|
||||||
"""
|
"""
|
||||||
Sends an image from given URL to given (or default, if not) thread.
|
Sends an image from given URL to given (or default, if not) thread.
|
||||||
|
|
||||||
:param image_path: path of an image to upload and send
|
:param image_path: path of an image to upload and send
|
||||||
:param message: additional message
|
:param message: additional message
|
||||||
:param thread_id: user/group chat ID
|
:param thread_id: user/group chat ID
|
||||||
@@ -634,15 +661,18 @@ class Client(object):
|
|||||||
return self._send(thread_id=thread_id, message=message, thread_type=thread_type, image_id=image_id)
|
return self._send(thread_id=thread_id, message=message, thread_type=thread_type, image_id=image_id)
|
||||||
|
|
||||||
def addUsersToChat(self, user_ids, thread_id=None):
|
def addUsersToChat(self, user_ids, thread_id=None):
|
||||||
|
# type: (list, str) -> list
|
||||||
"""
|
"""
|
||||||
Adds users to given (or default, if not) thread.
|
Adds users to given (or default, if not) thread.
|
||||||
|
|
||||||
:param user_ids: list of user ids to add
|
:param user_ids: list of user ids to add
|
||||||
:param thread_id: group chat ID
|
:param thread_id: group chat ID
|
||||||
:return: a list of message ids of the sent message(s)
|
:return: a list of message ids of the sent message(s)
|
||||||
"""
|
"""
|
||||||
return self._send(thread_id=thread_id, thread_type=ThreadType.GROUP, add_user_ids=users)
|
return self._send(thread_id=thread_id, thread_type=ThreadType.GROUP, add_user_ids=user_ids)
|
||||||
|
|
||||||
def removeUserFromChat(self, user_id, thread_id=None):
|
def removeUserFromChat(self, user_id, thread_id=None):
|
||||||
|
# type: (str, str) -> bool
|
||||||
"""
|
"""
|
||||||
Adds users to given (or default, if not) thread.
|
Adds users to given (or default, if not) thread.
|
||||||
:param user_id: user ID to remove
|
:param user_id: user ID to remove
|
||||||
@@ -705,6 +735,7 @@ class Client(object):
|
|||||||
return json.loads(response_content[9:])['payload']['metadata'][0]['image_id']
|
return json.loads(response_content[9:])['payload']['metadata'][0]['image_id']
|
||||||
|
|
||||||
def getThreadInfo(self, last_n=20, thread_id=None, thread_type=ThreadType.USER):
|
def getThreadInfo(self, last_n=20, thread_id=None, thread_type=ThreadType.USER):
|
||||||
|
# type: (int, str, ThreadType) -> list
|
||||||
"""Get the info of one Thread
|
"""Get the info of one Thread
|
||||||
|
|
||||||
:param last_n: number of retrieved messages from start (default 20)
|
:param last_n: number of retrieved messages from start (default 20)
|
||||||
@@ -745,6 +776,7 @@ class Client(object):
|
|||||||
|
|
||||||
|
|
||||||
def getThreadList(self, start, length=20):
|
def getThreadList(self, start, length=20):
|
||||||
|
# type: (int, int) -> list
|
||||||
"""Get thread list of your facebook account.
|
"""Get thread list of your facebook account.
|
||||||
|
|
||||||
:param start: the start index of a thread
|
:param start: the start index of a thread
|
||||||
@@ -786,7 +818,6 @@ class Client(object):
|
|||||||
|
|
||||||
return self.threads
|
return self.threads
|
||||||
|
|
||||||
|
|
||||||
def getUnread(self):
|
def getUnread(self):
|
||||||
form = {
|
form = {
|
||||||
'client': 'mercury_sync',
|
'client': 'mercury_sync',
|
||||||
@@ -815,7 +846,6 @@ class Client(object):
|
|||||||
r = self._post(DeliveredURL, data)
|
r = self._post(DeliveredURL, data)
|
||||||
return r.ok
|
return r.ok
|
||||||
|
|
||||||
|
|
||||||
def markAsRead(self, userID):
|
def markAsRead(self, userID):
|
||||||
data = {
|
data = {
|
||||||
"watermarkTimestamp": now(),
|
"watermarkTimestamp": now(),
|
||||||
@@ -826,23 +856,24 @@ class Client(object):
|
|||||||
r = self._post(ReadStatusURL, data)
|
r = self._post(ReadStatusURL, data)
|
||||||
return r.ok
|
return r.ok
|
||||||
|
|
||||||
|
|
||||||
def markAsSeen(self):
|
def markAsSeen(self):
|
||||||
r = self._post(MarkSeenURL, {"seen_timestamp": 0})
|
r = self._post(MarkSeenURL, {"seen_timestamp": 0})
|
||||||
return r.ok
|
return r.ok
|
||||||
|
|
||||||
|
@deprecated(deprecated_in='0.10.2', details='Use friendConnect() instead')
|
||||||
def friend_connect(self, friend_id):
|
def friend_connect(self, friend_id):
|
||||||
|
return self.friendConnect(friend_id)
|
||||||
|
|
||||||
|
def friendConnect(self, friend_id):
|
||||||
|
# type: (str) -> bool
|
||||||
data = {
|
data = {
|
||||||
"to_friend": friend_id,
|
"to_friend": friend_id,
|
||||||
"action": "confirm"
|
"action": "confirm"
|
||||||
}
|
}
|
||||||
|
|
||||||
r = self._post(ConnectURL, data)
|
r = self._post(ConnectURL, data)
|
||||||
|
|
||||||
return r.ok
|
return r.ok
|
||||||
|
|
||||||
|
|
||||||
def ping(self, sticky):
|
def ping(self, sticky):
|
||||||
data = {
|
data = {
|
||||||
'channel': self.user_channel,
|
'channel': self.user_channel,
|
||||||
@@ -856,7 +887,6 @@ class Client(object):
|
|||||||
r = self._get(PingURL, data)
|
r = self._get(PingURL, data)
|
||||||
return r.ok
|
return r.ok
|
||||||
|
|
||||||
|
|
||||||
def _getSticky(self):
|
def _getSticky(self):
|
||||||
"""Call pull api to get sticky and pool parameter,
|
"""Call pull api to get sticky and pool parameter,
|
||||||
newer api needs these parameter to work.
|
newer api needs these parameter to work.
|
||||||
@@ -878,7 +908,6 @@ class Client(object):
|
|||||||
pool = j['lb_info']['pool']
|
pool = j['lb_info']['pool']
|
||||||
return sticky, pool
|
return sticky, pool
|
||||||
|
|
||||||
|
|
||||||
def _pullMessage(self, sticky, pool):
|
def _pullMessage(self, sticky, pool):
|
||||||
"""Call pull api with seq value to get message data."""
|
"""Call pull api with seq value to get message data."""
|
||||||
|
|
||||||
@@ -896,80 +925,161 @@ class Client(object):
|
|||||||
self.seq = j.get('seq', '0')
|
self.seq = j.get('seq', '0')
|
||||||
return j
|
return j
|
||||||
|
|
||||||
|
|
||||||
def _parseMessage(self, content):
|
def _parseMessage(self, content):
|
||||||
"""Get message and author name from content.
|
"""Get message and author name from content. May contain multiple messages in the content."""
|
||||||
May contains multiple messages in the content.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if 'ms' not in content: return
|
if 'ms' not in content: return
|
||||||
|
|
||||||
log.debug("Received {}".format(content["ms"]))
|
log.debug("Received {}".format(content["ms"]))
|
||||||
for m in content['ms']:
|
for m in content["ms"]:
|
||||||
|
mtype = m.get("type")
|
||||||
try:
|
try:
|
||||||
if m['type'] in ['m_messaging', 'messaging']:
|
# Things that directly change chat
|
||||||
if m['event'] in ['deliver']:
|
if mtype == "delta":
|
||||||
mid = m['message']['mid']
|
|
||||||
message = m['message']['body']
|
def getThreadIdAndThreadType(msg_metadata):
|
||||||
fbid = m['message']['sender_fbid']
|
"""Returns a tuple consisting of thread id and thread type"""
|
||||||
name = m['message']['sender_name']
|
id_thread = None
|
||||||
self.on_message(mid, fbid, name, message, m)
|
type_thread = None
|
||||||
elif m['type'] in ['typ']:
|
if 'threadFbId' in msg_metadata['threadKey']:
|
||||||
self.on_typing(m.get("from"))
|
id_thread = str(msg_metadata['threadKey']['threadFbId'])
|
||||||
elif m['type'] in ['m_read_receipt']:
|
type_thread = ThreadType.GROUP
|
||||||
self.on_read(m.get('realtime_viewer_fbid'), m.get('reader'), m.get('time'))
|
elif 'otherUserFbId' in msg_metadata['threadKey']:
|
||||||
elif m['type'] in ['inbox']:
|
id_thread = str(msg_metadata['threadKey']['otherUserFbId'])
|
||||||
viewer = m.get('realtime_viewer_fbid')
|
type_thread = ThreadType.USER
|
||||||
unseen = m.get('unseen')
|
return id_thread, type_thread
|
||||||
unread = m.get('unread')
|
|
||||||
other_unseen = m.get('other_unseen')
|
delta = m["delta"]
|
||||||
other_unread = m.get('other_unread')
|
delta_type = delta.get("type")
|
||||||
timestamp = m.get('seen_timestamp')
|
metadata = delta.get("messageMetadata")
|
||||||
self.on_inbox(viewer, unseen, unread, other_unseen, other_unread, timestamp)
|
|
||||||
elif m['type'] in ['qprimer']:
|
if metadata is not None:
|
||||||
self.on_qprimer(m.get('made'))
|
mid = metadata["messageId"]
|
||||||
elif m['type'] in ['delta']:
|
author_id = str(metadata['actorFbId'])
|
||||||
if 'leftParticipantFbId' in m['delta']:
|
ts = int(metadata["timestamp"])
|
||||||
user_id = m['delta']['leftParticipantFbId']
|
|
||||||
actor_id = m['delta']['messageMetadata']['actorFbId']
|
# Added participants
|
||||||
thread_id = m['delta']['messageMetadata']['threadKey']['threadFbId']
|
if 'addedParticipants' in delta:
|
||||||
self.on_person_removed(user_id, actor_id, thread_id)
|
added_ids = [str(x['userFbId']) for x in delta['addedParticipants']]
|
||||||
elif 'addedParticipants' in m['delta']:
|
thread_id = str(metadata['threadKey']['threadFbId'])
|
||||||
user_ids = [x['userFbId'] for x in m['delta']['addedParticipants']]
|
self.onPeopleAdded(mid=mid, added_ids=added_ids, author_id=author_id, thread_id=thread_id,
|
||||||
actor_id = m['delta']['messageMetadata']['actorFbId']
|
ts=ts)
|
||||||
thread_id = m['delta']['messageMetadata']['threadKey']['threadFbId']
|
continue
|
||||||
self.on_people_added(user_ids, actor_id, thread_id)
|
|
||||||
elif 'messageMetadata' in m['delta']:
|
# Left/removed participants
|
||||||
recipient_id = 0
|
elif 'leftParticipantFbId' in delta:
|
||||||
thread_type = None
|
removed_id = str(delta['leftParticipantFbId'])
|
||||||
if 'threadKey' in m['delta']['messageMetadata']:
|
thread_id = str(metadata['threadKey']['threadFbId'])
|
||||||
if 'threadFbId' in m['delta']['messageMetadata']['threadKey']:
|
self.onPersonRemoved(mid=mid, removed_id=removed_id, author_id=author_id, thread_id=thread_id,
|
||||||
recipient_id = m['delta']['messageMetadata']['threadKey']['threadFbId']
|
ts=ts)
|
||||||
thread_type = 'group'
|
continue
|
||||||
elif 'otherUserFbId' in m['delta']['messageMetadata']['threadKey']:
|
|
||||||
recipient_id = m['delta']['messageMetadata']['threadKey']['otherUserFbId']
|
# Color change
|
||||||
thread_type = 'user'
|
elif delta_type == "change_thread_theme":
|
||||||
mid = m['delta']['messageMetadata']['messageId']
|
new_color = delta["untypedData"]["theme_color"]
|
||||||
message = m['delta'].get('body','')
|
thread_id, thread_type = getThreadIdAndThreadType(metadata)
|
||||||
fbid = m['delta']['messageMetadata']['actorFbId']
|
self.onColorChange(mid=mid, author_id=author_id, new_color=new_color, thread_id=thread_id,
|
||||||
self.on_message_new(mid, fbid, message, m, recipient_id, thread_type)
|
thread_type=thread_type, ts=ts, metadata=metadata)
|
||||||
elif m['type'] in ['jewel_requests_add']:
|
continue
|
||||||
from_id = m['from']
|
|
||||||
self.on_friend_request(from_id)
|
# Emoji change
|
||||||
|
elif delta_type == "change_thread_icon":
|
||||||
|
new_emoji = delta["untypedData"]["thread_icon"]
|
||||||
|
thread_id, thread_type = getThreadIdAndThreadType(metadata)
|
||||||
|
self.onEmojiChange(mid=mid, author_id=author_id, new_emoji=new_emoji, thread_id=thread_id,
|
||||||
|
thread_type=thread_type, ts=ts, metadata=metadata)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Thread title change
|
||||||
|
elif delta.get("class") == "ThreadName":
|
||||||
|
new_title = delta["name"]
|
||||||
|
thread_id, thread_type = getThreadIdAndThreadType(metadata)
|
||||||
|
self.onTitleChange(mid=mid, author_id=author_id, new_title=new_title, thread_id=thread_id,
|
||||||
|
thread_type=thread_type, ts=ts, metadata=metadata)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Nickname change
|
||||||
|
elif delta_type == "change_thread_nickname":
|
||||||
|
changed_for = str(delta["untypedData"]["participant_id"])
|
||||||
|
new_title = delta["untypedData"]["nickname"]
|
||||||
|
thread_id, thread_type = getThreadIdAndThreadType(metadata)
|
||||||
|
self.onNicknameChange(mid=mid, author_id=author_id, changed_for=changed_for,
|
||||||
|
new_title=new_title,
|
||||||
|
thread_id=thread_id, thread_type=thread_type, ts=ts, metadata=metadata)
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
|
# TODO properly implement these as they differ on different scenarios
|
||||||
|
# Seen
|
||||||
|
# elif delta.get("class") == "ReadReceipt":
|
||||||
|
# seen_by = delta["actorFbId"] or delta["threadKey"]["otherUserFbId"]
|
||||||
|
# thread_id = delta["threadKey"].get("threadFbId")
|
||||||
|
# self.onSeen(seen_by=seen_by, thread_id=thread_id, ts=ts)
|
||||||
|
#
|
||||||
|
# # Message delivered
|
||||||
|
# elif delta.get("class") == 'DeliveryReceipt':
|
||||||
|
# time_delivered = delta['deliveredWatermarkTimestampMs']
|
||||||
|
# self.onDelivered()
|
||||||
|
|
||||||
|
# New message
|
||||||
|
elif delta.get("class") == "NewMessage":
|
||||||
|
message = delta.get('body', '')
|
||||||
|
thread_id, thread_type = getThreadIdAndThreadType(metadata)
|
||||||
|
self.onMessage(mid=mid, author_id=author_id, message=message,
|
||||||
|
thread_id=thread_id, thread_type=thread_type, ts=ts, metadata=m)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Inbox
|
||||||
|
if mtype == "inbox":
|
||||||
|
self.onInbox(unseen=m["unseen"], unread=m["unread"], recent_unread=m["recent_unread"])
|
||||||
|
|
||||||
|
# Typing
|
||||||
|
# elif mtype == "typ":
|
||||||
|
# author_id = str(m.get("from"))
|
||||||
|
# typing_status = TypingStatus(m.get("st"))
|
||||||
|
# self.onTyping(author_id=author_id, typing_status=typing_status)
|
||||||
|
|
||||||
|
# Seen
|
||||||
|
# elif mtype == "m_read_receipt":
|
||||||
|
#
|
||||||
|
# self.onSeen(m.get('realtime_viewer_fbid'), m.get('reader'), m.get('time'))
|
||||||
|
|
||||||
|
# elif mtype in ['jewel_requests_add']:
|
||||||
|
# from_id = m['from']
|
||||||
|
# self.on_friend_request(from_id)
|
||||||
|
|
||||||
|
# Happens on every login
|
||||||
|
elif mtype == "qprimer":
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Is sent before any other message
|
||||||
|
elif mtype == "deltaflow":
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Unknown message type
|
||||||
else:
|
else:
|
||||||
self.on_unknown_type(m)
|
self.onUnknownMesssageType(msg=m)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# ex_type, ex, tb = sys.exc_info()
|
log.debug(str(e))
|
||||||
self.on_message_error(sys.exc_info(), m)
|
|
||||||
|
|
||||||
|
|
||||||
|
@deprecated(deprecated_in='0.10.2', details='Use startListening() instead')
|
||||||
def start_listening(self):
|
def start_listening(self):
|
||||||
|
return self.startListening()
|
||||||
|
|
||||||
|
def startListening(self):
|
||||||
"""Start listening from an external event loop."""
|
"""Start listening from an external event loop."""
|
||||||
self.listening = True
|
self.listening = True
|
||||||
self.sticky, self.pool = self._getSticky()
|
self.sticky, self.pool = self._getSticky()
|
||||||
|
|
||||||
|
|
||||||
|
@deprecated(deprecated_in='0.10.2', details='Use doOneListen() instead')
|
||||||
def do_one_listen(self, markAlive=True):
|
def do_one_listen(self, markAlive=True):
|
||||||
|
return self.doOneListen(markAlive)
|
||||||
|
|
||||||
|
def doOneListen(self, markAlive=True):
|
||||||
|
# type: (bool) -> None
|
||||||
"""Does one cycle of the listening loop.
|
"""Does one cycle of the listening loop.
|
||||||
This method is only useful if you want to control fbchat from an
|
This method is only useful if you want to control fbchat from an
|
||||||
external event loop."""
|
external event loop."""
|
||||||
@@ -986,20 +1096,24 @@ class Client(object):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@deprecated(deprecated_in='0.10.2', details='Use stopListening() instead')
|
||||||
def stop_listening(self):
|
def stop_listening(self):
|
||||||
|
return self.stopListening()
|
||||||
|
|
||||||
|
def stopListening(self):
|
||||||
"""Cleans up the variables from start_listening."""
|
"""Cleans up the variables from start_listening."""
|
||||||
self.listening = False
|
self.listening = False
|
||||||
self.sticky, self.pool = (None, None)
|
self.sticky, self.pool = (None, None)
|
||||||
|
|
||||||
|
|
||||||
def listen(self, markAlive=True):
|
def listen(self, markAlive=True):
|
||||||
self.start_listening()
|
self.startListening()
|
||||||
|
self.onListening()
|
||||||
|
|
||||||
log.info("Listening...")
|
|
||||||
while self.listening:
|
while self.listening:
|
||||||
self.do_one_listen(markAlive)
|
self.doOneListen(markAlive)
|
||||||
|
|
||||||
self.stop_listening()
|
self.stopListening()
|
||||||
|
|
||||||
|
|
||||||
def getUserInfo(self, *user_ids):
|
def getUserInfo(self, *user_ids):
|
||||||
@@ -1037,7 +1151,6 @@ class Client(object):
|
|||||||
"""
|
"""
|
||||||
self.on_message(mid, author_id, None, message, metadata)
|
self.on_message(mid, author_id, None, message, metadata)
|
||||||
|
|
||||||
@deprecated(deprecated_in='0.7.0', details='Use on_message_new() instead')
|
|
||||||
def on_message(self, mid, author_id, author_name, message, metadata):
|
def on_message(self, mid, author_id, author_name, message, metadata):
|
||||||
"""subclass Client and override this method to add custom behavior on event"""
|
"""subclass Client and override this method to add custom behavior on event"""
|
||||||
self.markAsDelivered(author_id, mid)
|
self.markAsDelivered(author_id, mid)
|
||||||
|
57
fbchat/event_hook.py
Normal file
57
fbchat/event_hook.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import inspect
|
||||||
|
|
||||||
|
|
||||||
|
class EventHook(object):
|
||||||
|
"""
|
||||||
|
A simple implementation of the Observer-Pattern.
|
||||||
|
The user can specify an event signature upon inizializazion,
|
||||||
|
defined by kwargs in the form of argumentname=class (e.g. id=int).
|
||||||
|
The arguments' types are not checked in this implementation though.
|
||||||
|
Callables with a fitting signature can be added with += or removed with -=.
|
||||||
|
All listeners can be notified by calling the EventHook class with fitting
|
||||||
|
arguments.
|
||||||
|
|
||||||
|
Thanks http://stackoverflow.com/a/35957226/5556222
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, **signature):
|
||||||
|
self._signature = signature
|
||||||
|
self._argnames = set(signature.keys())
|
||||||
|
self._handlers = []
|
||||||
|
|
||||||
|
def _kwargs_str(self):
|
||||||
|
return ", ".join(k+"="+v.__name__ for k, v in self._signature.items())
|
||||||
|
|
||||||
|
def __iadd__(self, handler):
|
||||||
|
params = inspect.signature(handler).parameters
|
||||||
|
valid = True
|
||||||
|
argnames = set(n for n in params.keys())
|
||||||
|
if argnames != self._argnames:
|
||||||
|
valid = False
|
||||||
|
for p in params.values():
|
||||||
|
if p.kind == p.VAR_KEYWORD:
|
||||||
|
valid = True
|
||||||
|
break
|
||||||
|
if p.kind not in (p.POSITIONAL_OR_KEYWORD, p.KEYWORD_ONLY):
|
||||||
|
valid = False
|
||||||
|
break
|
||||||
|
if not valid:
|
||||||
|
raise ValueError("Listener must have these arguments: (%s)"
|
||||||
|
% self._kwargs_str())
|
||||||
|
self._handlers.append(handler)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __isub__(self, handler):
|
||||||
|
self._handlers.remove(handler)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
if args or set(kwargs.keys()) != self._argnames:
|
||||||
|
raise ValueError("This EventHook must be called with these " +
|
||||||
|
"keyword arguments: (%s)" % self._kwargs_str() +
|
||||||
|
", but was called with: (%s)" %self._signature)
|
||||||
|
for handler in self._handlers[:]:
|
||||||
|
handler(**kwargs)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "EventHook(%s)" % self._kwargs_str()
|
@@ -2,15 +2,7 @@ from __future__ import unicode_literals
|
|||||||
import sys
|
import sys
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
class Base():
|
class User:
|
||||||
def __repr__(self):
|
|
||||||
uni = self.__unicode__()
|
|
||||||
return uni.encode('utf-8') if sys.version_info < (3, 0) else uni
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return u'<%s %s (%s)>' % (self.type.upper(), self.name, self.url)
|
|
||||||
|
|
||||||
class User(Base):
|
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
if data['type'] != 'user':
|
if data['type'] != 'user':
|
||||||
raise Exception("[!] %s <%s> is not a user" % (data['text'], data['path']))
|
raise Exception("[!] %s <%s> is not a user" % (data['text'], data['path']))
|
||||||
@@ -22,11 +14,53 @@ class User(Base):
|
|||||||
self.score = data['score']
|
self.score = data['score']
|
||||||
self.data = data
|
self.data = data
|
||||||
|
|
||||||
class Thread():
|
def __repr__(self):
|
||||||
|
uni = self.__unicode__()
|
||||||
|
return uni.encode('utf-8') if sys.version_info < (3, 0) else uni
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return u'<%s %s (%s)>' % (self.type.upper(), self.name, self.url)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def adaptFromChat(user_in_chat):
|
||||||
|
""" Adapts user info from chat to User model acceptable initial dict
|
||||||
|
|
||||||
|
:param user_in_chat: user info from chat
|
||||||
|
|
||||||
|
'dir': None,
|
||||||
|
'mThumbSrcSmall': None,
|
||||||
|
'is_friend': False,
|
||||||
|
'is_nonfriend_messenger_contact': True,
|
||||||
|
'alternateName': '',
|
||||||
|
'i18nGender': 16777216,
|
||||||
|
'vanity': '',
|
||||||
|
'type': 'friend',
|
||||||
|
'searchTokens': ['Voznesenskij', 'Sergej'],
|
||||||
|
'thumbSrc': 'https://fb-s-b-a.akamaihd.net/h-ak-xfa1/v/t1.0-1/c9.0.32.32/p32x32/10354686_10150004552801856_220367501106153455_n.jpg?oh=71a87d76d4e4d17615a20c43fb8dbb47&oe=59118CE4&__gda__=1493753268_ae75cef40e9785398e744259ccffd7ff',
|
||||||
|
'mThumbSrcLarge': None,
|
||||||
|
'firstName': 'Sergej',
|
||||||
|
'name': 'Sergej Voznesenskij',
|
||||||
|
'uri': 'https://www.facebook.com/profile.php?id=100014812758264',
|
||||||
|
'id': '100014812758264',
|
||||||
|
'gender': 2
|
||||||
|
"""
|
||||||
|
|
||||||
|
return {
|
||||||
|
'type': 'user',
|
||||||
|
'uid': user_in_chat['id'],
|
||||||
|
'photo': user_in_chat['thumbSrc'],
|
||||||
|
'path': user_in_chat['uri'],
|
||||||
|
'text': user_in_chat['name'],
|
||||||
|
'score': '',
|
||||||
|
'data': user_in_chat,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Thread:
|
||||||
def __init__(self, **entries):
|
def __init__(self, **entries):
|
||||||
self.__dict__.update(entries)
|
self.__dict__.update(entries)
|
||||||
|
|
||||||
class Message():
|
class Message:
|
||||||
def __init__(self, **entries):
|
def __init__(self, **entries):
|
||||||
self.__dict__.update(entries)
|
self.__dict__.update(entries)
|
||||||
|
|
||||||
@@ -40,22 +74,15 @@ class TypingStatus(Enum):
|
|||||||
|
|
||||||
|
|
||||||
# WIP
|
# WIP
|
||||||
class StickerSize(Enum):
|
class EmojiSize(Enum):
|
||||||
LARGE = '369239383222810'
|
LARGE = '369239383222810'
|
||||||
MEDIUM = '369239343222814'
|
MEDIUM = '369239343222814'
|
||||||
SMALL = '369239263222822'
|
SMALL = '369239263222822'
|
||||||
|
|
||||||
#class Size(Enum):
|
|
||||||
# LARGE = 'large'
|
|
||||||
# MEDIUM = 'medium'
|
|
||||||
# SMALL = 'small'
|
|
||||||
|
|
||||||
Size = StickerSize
|
|
||||||
|
|
||||||
LIKES = {
|
LIKES = {
|
||||||
'l': Size.LARGE,
|
'l': EmojiSize.LARGE,
|
||||||
'm': Size.MEDIUM,
|
'm': EmojiSize.MEDIUM,
|
||||||
's': Size.SMALL
|
's': EmojiSize.SMALL
|
||||||
}
|
}
|
||||||
LIKES['large'] = LIKES['l']
|
LIKES['large'] = LIKES['l']
|
||||||
LIKES['medium'] =LIKES['m']
|
LIKES['medium'] =LIKES['m']
|
||||||
|
@@ -26,6 +26,7 @@ GENDERS = {
|
|||||||
11: 'unknown_plural',
|
11: 'unknown_plural',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def now():
|
def now():
|
||||||
return int(time()*1000)
|
return int(time()*1000)
|
||||||
|
|
||||||
@@ -51,14 +52,14 @@ def str_base(number,base):
|
|||||||
def generateMessageID(client_id=None):
|
def generateMessageID(client_id=None):
|
||||||
k = now()
|
k = now()
|
||||||
l = int(random() * 4294967295)
|
l = int(random() * 4294967295)
|
||||||
return ("<%s:%s-%s@mail.projektitan.com>" % (k, l, client_id));
|
return "<%s:%s-%s@mail.projektitan.com>" % (k, l, client_id)
|
||||||
|
|
||||||
def getSignatureID():
|
def getSignatureID():
|
||||||
return hex(int(random() * 2147483648))
|
return hex(int(random() * 2147483648))
|
||||||
|
|
||||||
def generateOfflineThreadingID():
|
def generateOfflineThreadingID():
|
||||||
ret = now()
|
ret = now()
|
||||||
value = int(random() * 4294967295);
|
value = int(random() * 4294967295)
|
||||||
string = ("0000000000000000000000" + bin(value))[-22:]
|
string = ("0000000000000000000000" + bin(value))[-22:]
|
||||||
msgs = bin(ret) + string
|
msgs = bin(ret) + string
|
||||||
return str(int(msgs, 2))
|
return str(int(msgs, 2))
|
||||||
|
6
test_data.json
Normal file
6
test_data.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"email": "",
|
||||||
|
"password": "",
|
||||||
|
"user_thread_id": "",
|
||||||
|
"group_thread_id": ""
|
||||||
|
}
|
11
tests.py
11
tests.py
@@ -18,14 +18,7 @@ logging.basicConfig(level=logging.INFO)
|
|||||||
Tests for fbchat
|
Tests for fbchat
|
||||||
~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
To use these tests, make a json file called test_data.json, put this example in it, and fill in the gaps:
|
To use these tests, fill in test_data.json or type this information manually in the terminal prompts.
|
||||||
{
|
|
||||||
"email": "example@email.com",
|
|
||||||
"password": "example_password",
|
|
||||||
"group_thread_id": 0,
|
|
||||||
"user_thread_id": 0
|
|
||||||
}
|
|
||||||
or type this information manually in the terminal prompts.
|
|
||||||
|
|
||||||
- email: Your (or a test user's) email / phone number
|
- email: Your (or a test user's) email / phone number
|
||||||
- password: Your (or a test user's) password
|
- password: Your (or a test user's) password
|
||||||
@@ -169,7 +162,7 @@ if __name__ == 'tests':
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(path.join(path.dirname(__file__), 'test_data.js'), 'r') as f:
|
with open(path.join(path.dirname(__file__), 'test_data.json'), 'r') as f:
|
||||||
json = json.load(f)
|
json = json.load(f)
|
||||||
email = json['email']
|
email = json['email']
|
||||||
password = json['password']
|
password = json['password']
|
||||||
|
Reference in New Issue
Block a user