Merge pull request #134 from Dainius14/master

NOT BACKWARDS COMPATIBLE: Added event hooks, changed method names
This commit is contained in:
Mads T Marquart
2017-05-08 20:27:26 +02:00
committed by GitHub
5 changed files with 322 additions and 226 deletions

View File

@@ -21,9 +21,9 @@ from bs4 import BeautifulSoup as bs
from mimetypes import guess_type
from .utils import *
from .models import *
from .stickers import *
import time
import sys
from .event_hook import EventHook
# Python 3 does not have raw_input, whereas Python 2 has and it's more secure
try:
@@ -89,6 +89,51 @@ class Client(object):
self.listening = False
self.threads = []
# 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)
if not user_agent:
user_agent = choice(USER_AGENTS)
@@ -114,7 +159,7 @@ class Client(object):
log.addHandler(handler)
# 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)
def _console(self, msg):
@@ -135,11 +180,6 @@ class Client(object):
DeprecationWarning)
log.debug(msg)
def _setttstamp(self):
for i in self.fb_dtsg:
self.ttstamp += str(ord(i))
self.ttstamp += '2'
def _generatePayload(self, query):
"""Adds the following defaults to the payload:
__rev, __user, __a, ttstamp, fb_dtsg, __req
@@ -171,7 +211,7 @@ class Client(object):
payload=self._generatePayload(None)
return self._session.post(url, data=payload, timeout=timeout, files=files)
def _post_login(self):
def _postLogin(self):
self.payloadDefault = {}
self.client_id = hex(int(random()*2147483648))[2:]
self.start_time = now()
@@ -185,7 +225,11 @@ class Client(object):
log.debug(r.url)
self.fb_dtsg = soup.find("input", {'name':'fb_dtsg'})['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
self.payloadDefault['__rev'] = int(r.text.split('"revision":',1)[1].split(",",1)[0])
self.payloadDefault['__user'] = self.uid
@@ -230,7 +274,7 @@ class Client(object):
r = self._cleanGet(SaveDeviceURL)
if 'home' in r.url:
self._post_login()
self._postLogin()
return True
else:
return False
@@ -286,13 +330,10 @@ class Client(object):
r = self._cleanPost(CheckpointURL, data)
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.
r = self._cleanGet(LoginURL)
if 'home' in r.url:
return True
else:
return False
return 'home' in r.url
def getSession(self):
"""Returns the session cookies"""
@@ -300,7 +341,6 @@ class Client(object):
def setSession(self, session_cookies):
"""Loads session cookies
:param session_cookies: dictionary containing session cookies
Return false if session_cookies does not contain proper cookies
"""
@@ -311,12 +351,11 @@ class Client(object):
# Load cookies into current session
self._session.cookies = requests.cookies.merge_cookies(self._session.cookies, session_cookies)
self._post_login()
self._postLogin()
return True
def login(self, email, password, max_retries=5):
# Logging in
log.info("Logging in {}...".format(email))
self.onLoggingIn(email)
if not (email and password):
raise Exception("Email and password not set.")
@@ -326,11 +365,11 @@ class Client(object):
for i in range(1, max_retries+1):
if not self._login():
log.warning("Attempt #{} failed{}".format(i,{True:', retrying'}.get(i < max_retries, '')))
log.warning("Attempt #{} failed{}".format(i, {True: ', retrying'}.get(i < 5, '')))
time.sleep(1)
continue
else:
log.info("Login of {} successful.".format(email))
self.onLoggedIn(email)
break
else:
raise Exception("Login failed. Check email/password.")
@@ -360,39 +399,6 @@ class Client(object):
self.def_is_user = is_user
self.is_def_recipient_set = True
def _adapt_user_in_chat_to_user_model(self, 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,
}
def getAllUsers(self):
""" Gets all users from chat with info included """
@@ -410,7 +416,7 @@ class Client(object):
for k in payload.keys():
try:
user = self._adapt_user_in_chat_to_user_model(payload[k])
user = User.adaptFromChat(payload[k])
except KeyError:
continue
@@ -514,13 +520,10 @@ class Client(object):
if image_id:
data['image_ids[0]'] = image_id
if like:
try:
sticker = LIKES[like.lower()]
except KeyError:
# if user doesn't enter l or m or s, then use the large one
sticker = LIKES['l']
data["sticker_id"] = sticker
if like and not type(like) is Sticker:
data["sticker_id"] = Sticker.SMALL.value
else:
data["sticker_id"] = like.value
r = self._post(SendURL, data)
@@ -549,7 +552,6 @@ class Client(object):
log.debug("With data {}".format(data))
return message_ids
def sendRemoteImage(self, recipient_id=None, message=None, is_user=True, image=''):
"""Send an image from a URL
@@ -575,7 +577,6 @@ class Client(object):
image_id = self.uploadImage({'file': (image, open(image, 'rb'), mimetype)})
return self.send(recipient_id, message, is_user, None, image_id)
def uploadImage(self, image):
"""Upload an image and get the image_id for sending in a message
@@ -588,7 +589,6 @@ class Client(object):
# Strip the start and parse out the returned image_id
return json.loads(r._content[9:])['payload']['metadata'][0]['image_id']
def getThreadInfo(self, userID, last_n=20, start=None, is_user=True):
"""Get the info of one Thread
@@ -625,7 +625,6 @@ class Client(object):
messages.append(Message(**message))
return list(reversed(messages))
def getThreadList(self, start, length=20):
"""Get thread list of your facebook account.
@@ -668,7 +667,6 @@ class Client(object):
return self.threads
def getUnread(self):
form = {
'client': 'mercury_sync',
@@ -697,7 +695,6 @@ class Client(object):
r = self._post(DeliveredURL, data)
return r.ok
def markAsRead(self, userID):
data = {
"watermarkTimestamp": now(),
@@ -708,13 +705,11 @@ class Client(object):
r = self._post(ReadStatusURL, data)
return r.ok
def markAsSeen(self):
r = self._post(MarkSeenURL, {"seen_timestamp": 0})
return r.ok
def friend_connect(self, friend_id):
def friendConnect(self, friend_id):
data = {
"to_friend": friend_id,
"action": "confirm"
@@ -724,7 +719,6 @@ class Client(object):
return r.ok
def ping(self, sticky):
data = {
'channel': self.user_channel,
@@ -738,11 +732,8 @@ class Client(object):
r = self._get(PingURL, data)
return r.ok
def _getSticky(self):
"""Call pull api to get sticky and pool parameter,
newer api needs these parameter to work.
"""
"""Call pull api to get sticky and pool parameter, newer api needs these parameter to work."""
data = {
"msgs_recv": 0,
@@ -760,7 +751,6 @@ class Client(object):
pool = j['lb_info']['pool']
return sticky, pool
def _pullMessage(self, sticky, pool):
"""Call pull api with seq value to get message data."""
@@ -778,7 +768,6 @@ class Client(object):
self.seq = j.get('seq', '0')
return j
def _parseMessage(self, content):
"""Get message and author name from content.
May contains multiple messages in the content.
@@ -787,71 +776,141 @@ class Client(object):
if 'ms' not in content: return
log.debug("Received {}".format(content["ms"]))
for m in content['ms']:
for m in content["ms"]:
mtype = m.get("type")
try:
if m['type'] in ['m_messaging', 'messaging']:
if m['event'] in ['deliver']:
mid = m['message']['mid']
message = m['message']['body']
fbid = m['message']['sender_fbid']
name = m['message']['sender_name']
self.on_message(mid, fbid, name, message, m)
elif m['type'] in ['typ']:
self.on_typing(m.get("from"))
elif m['type'] in ['m_read_receipt']:
self.on_read(m.get('realtime_viewer_fbid'), m.get('reader'), m.get('time'))
elif m['type'] in ['inbox']:
viewer = m.get('realtime_viewer_fbid')
unseen = m.get('unseen')
unread = m.get('unread')
other_unseen = m.get('other_unseen')
other_unread = m.get('other_unread')
timestamp = m.get('seen_timestamp')
self.on_inbox(viewer, unseen, unread, other_unseen, other_unread, timestamp)
elif m['type'] in ['qprimer']:
self.on_qprimer(m.get('made'))
elif m['type'] in ['delta']:
if 'leftParticipantFbId' in m['delta']:
user_id = m['delta']['leftParticipantFbId']
actor_id = m['delta']['messageMetadata']['actorFbId']
thread_id = m['delta']['messageMetadata']['threadKey']['threadFbId']
self.on_person_removed(user_id, actor_id, thread_id)
elif 'addedParticipants' in m['delta']:
user_ids = [x['userFbId'] for x in m['delta']['addedParticipants']]
actor_id = m['delta']['messageMetadata']['actorFbId']
thread_id = m['delta']['messageMetadata']['threadKey']['threadFbId']
self.on_people_added(user_ids, actor_id, thread_id)
elif 'messageMetadata' in m['delta']:
recipient_id = 0
thread_type = None
if 'threadKey' in m['delta']['messageMetadata']:
if 'threadFbId' in m['delta']['messageMetadata']['threadKey']:
recipient_id = m['delta']['messageMetadata']['threadKey']['threadFbId']
thread_type = 'group'
elif 'otherUserFbId' in m['delta']['messageMetadata']['threadKey']:
recipient_id = m['delta']['messageMetadata']['threadKey']['otherUserFbId']
thread_type = 'user'
mid = m['delta']['messageMetadata']['messageId']
message = m['delta'].get('body','')
fbid = m['delta']['messageMetadata']['actorFbId']
self.on_message_new(mid, fbid, message, m, recipient_id, thread_type)
elif m['type'] in ['jewel_requests_add']:
from_id = m['from']
self.on_friend_request(from_id)
# Things that directly change chat
if mtype == "delta":
def getThreadIdAndThreadType(msg_metadata):
"""Returns a tuple consisting of thread id and thread type"""
id_thread = None
type_thread = None
if 'threadFbId' in msg_metadata['threadKey']:
id_thread = str(msg_metadata['threadKey']['threadFbId'])
type_thread = ThreadType.GROUP
elif 'otherUserFbId' in msg_metadata['threadKey']:
id_thread = str(msg_metadata['threadKey']['otherUserFbId'])
type_thread = ThreadType.USER
return id_thread, type_thread
delta = m["delta"]
delta_type = delta.get("type")
metadata = delta.get("messageMetadata")
if metadata is not None:
mid = metadata["messageId"]
author_id = str(metadata['actorFbId'])
ts = int(metadata["timestamp"])
# Added participants
if 'addedParticipants' in delta:
added_ids = [str(x['userFbId']) for x in delta['addedParticipants']]
thread_id = str(metadata['threadKey']['threadFbId'])
self.onPeopleAdded(mid=mid, added_ids=added_ids, author_id=author_id, thread_id=thread_id, ts=ts)
continue
# Left/removed participants
elif 'leftParticipantFbId' in delta:
removed_id = str(delta['leftParticipantFbId'])
thread_id = str(metadata['threadKey']['threadFbId'])
self.onPersonRemoved(mid=mid, removed_id=removed_id, author_id=author_id, thread_id=thread_id, ts=ts)
continue
# Color change
elif delta_type == "change_thread_theme":
new_color = delta["untypedData"]["theme_color"]
thread_id, thread_type = getThreadIdAndThreadType(metadata)
self.onColorChange(mid=mid, author_id=author_id, new_color=new_color, thread_id=thread_id,
thread_type=thread_type, ts=ts, metadata=metadata)
continue
# 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:
self.on_unknown_type(m)
self.onUnknownMesssageType(msg=m)
except Exception as e:
# ex_type, ex, tb = sys.exc_info()
self.on_message_error(sys.exc_info(), m)
log.debug(str(e))
def start_listening(self):
def startListening(self):
"""Start listening from an external event loop."""
self.listening = True
self.sticky, self.pool = self._getSticky()
def do_one_listen(self, markAlive=True):
def doOneListen(self, markAlive=True):
"""Does one cycle of the listening loop.
This method is only useful if you want to control fbchat from an
external event loop."""
@@ -867,22 +926,19 @@ class Client(object):
except requests.exceptions.Timeout:
pass
def stop_listening(self):
def stopListening(self):
"""Cleans up the variables from start_listening."""
self.listening = False
self.sticky, self.pool = (None, None)
def listen(self, markAlive=True):
self.start_listening()
self.startListening()
self.onListening()
log.info("Listening...")
while self.listening:
self.do_one_listen(markAlive)
self.stop_listening()
self.doOneListen(markAlive)
self.stopListening()
def getUserInfo(self, *user_ids):
"""Get user info from id. Unordered.
@@ -900,7 +956,6 @@ class Client(object):
user_ids = [fbidStrip(uid) for uid in user_ids]
data = {"ids[{}]".format(i):uid for i,uid in enumerate(user_ids)}
r = self._post(UserInfoURL, data)
info = get_json(r.text)
@@ -909,8 +964,7 @@ class Client(object):
full_data=full_data[0]
return full_data
def remove_user_from_chat(self, threadID, userID):
def removeUserFromChat(self, threadID, userID):
"""Remove user (userID) from group chat (threadID)
:param threadID: group chat id
@@ -926,13 +980,12 @@ class Client(object):
return r.ok
def add_users_to_chat(self, threadID, userID):
def addUserToChat(self, threadID, userID):
"""Add user (userID) to group chat (threadID)
:param threadID: group chat id
:param userID: user id to add to chat
"""
return self.send(threadID, is_user=False, add_user_ids=[userID])
def changeThreadTitle(self, threadID, newTitle):
@@ -976,64 +1029,3 @@ class Client(object):
r = self._post(SendURL, data)
return r.ok
def on_message_new(self, mid, author_id, message, metadata, recipient_id, thread_type):
"""subclass Client and override this method to add custom behavior on event
This version of on_message recieves recipient_id and thread_type.
For backwards compatability, this data is sent directly to the old on_message.
"""
self.on_message(mid, author_id, None, 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"""
self.markAsDelivered(author_id, mid)
self.markAsRead(author_id)
log.info("%s said: %s" % (author_name, message))
def on_friend_request(self, from_id):
"""subclass Client and override this method to add custom behavior on event"""
log.info("Friend request from %s." % from_id)
def on_typing(self, author_id):
"""subclass Client and override this method to add custom behavior on event"""
pass
def on_read(self, author, reader, time):
"""subclass Client and override this method to add custom behavior on event"""
pass
def on_people_added(self, user_ids, actor_id, thread_id):
"""subclass Client and override this method to add custom behavior on event"""
log.info("User(s) {} was added to {} by {}".format(repr(user_ids), thread_id, actor_id))
def on_person_removed(self, user_id, actor_id, thread_id):
"""subclass Client and override this method to add custom behavior on event"""
log.info("User {} was removed from {} by {}".format(user_id, thread_id, actor_id))
def on_inbox(self, viewer, unseen, unread, other_unseen, other_unread, timestamp):
"""subclass Client and override this method to add custom behavior on event"""
pass
def on_message_error(self, exception, message):
"""subclass Client and override this method to add custom behavior on event"""
log.warning("Exception:\n{}".format(exception))
def on_qprimer(self, timestamp):
pass
def on_unknown_type(self, m):
"""subclass Client and override this method to add custom behavior on event"""
log.debug("Unknown type {}".format(m))

57
fbchat/event_hook.py Normal file
View 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()

View File

@@ -1,6 +1,8 @@
from __future__ import unicode_literals
from enum import Enum
import sys
class Base():
def __repr__(self):
uni = self.__unicode__()
@@ -9,6 +11,7 @@ class Base():
def __unicode__(self):
return u'<%s %s (%s)>' % (self.type.upper(), self.name, self.url)
class User(Base):
def __init__(self, data):
if data['type'] != 'user':
@@ -22,10 +25,62 @@ class User(Base):
self.data = data
@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):
self.__dict__.update(entries)
class Message():
def __init__(self, **entries):
self.__dict__.update(entries)
class ThreadType(Enum):
USER = 1
GROUP = 2
class TypingStatus(Enum):
Deleted = 0
Typing = 1
class Sticker(Enum):
LARGE = '369239383222810'
MEDIUM = '369239343222814'
SMALL = '369239263222822'

View File

@@ -1,8 +0,0 @@
LIKES={
'l': '369239383222810',
'm': '369239343222814',
's': '369239263222822'
}
LIKES['large'] = LIKES['l']
LIKES['medium'] =LIKES['m']
LIKES['small'] = LIKES['s']

View File

@@ -2,6 +2,7 @@ import re
import json
from time import time
from random import random
USER_AGENTS = [
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/601.1.10 (KHTML, like Gecko) Version/8.0.5 Safari/601.1.10",
@@ -39,7 +40,7 @@ def digit_to_char(digit):
return str(digit)
return chr(ord('a') + digit - 10)
def str_base(number,base):
def str_base(number, base):
if number < 0:
return '-' + str_base(-number, base)
(d, m) = divmod(number, base)
@@ -50,15 +51,14 @@ def str_base(number,base):
def generateMessageID(client_id=None):
k = now()
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():
return hex(int(random() * 2147483648))
def generateOfflineThreadingID() :
def generateOfflineThreadingID():
ret = now()
value = int(random() * 4294967295);
value = int(random() * 4294967295)
string = ("0000000000000000000000" + bin(value))[-22:]
msgs = bin(ret) + string
return str(int(msgs,2))
return str(int(msgs, 2))