add event hooks, change method names

This commit is contained in:
Dainius
2017-05-06 00:16:20 +03:00
parent dc95f367b8
commit 1700f95810
5 changed files with 281 additions and 212 deletions

View File

@@ -21,9 +21,9 @@ 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 .stickers import *
import time 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 # Python 3 does not have raw_input, whereas Python 2 has and it's more secure
try: try:
@@ -75,7 +75,6 @@ class Client(object):
import fbchat import fbchat
chat = fbchat.Client(email, password) chat = fbchat.Client(email, password)
""" """
if do_login and not (email and password): if do_login and not (email and password):
raise Exception("Email and password not found.") raise Exception("Email and password not found.")
@@ -103,6 +102,37 @@ class Client(object):
11: 'unknown_plural', 11: 'unknown_plural',
} }
# Setup event hooks
self.onLoggingIn = EventHook()
self.onLoggedIn = EventHook()
self.onMessage = EventHook(mid=str, author_id=str, message=str, recipient_id=int, msg_type=MsgType, ts=str, metadata=dict)
self.onColorChange = EventHook(mid=str, author_id=str, new_color=str, changed_for=str, msg_type=MsgType, ts=str)
self.onTitleChange = EventHook(mid=str, author_id=str, new_title=str, changed_for=str, thread_id=str, ts=str)
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)
# Setup event handlers
self.onLoggingIn += lambda: log.info("Logging in...")
self.onLoggedIn += lambda: log.info("Login successful.")
self.onListening += lambda: log.info("Listening...")
self.onMessage += lambda mid, author_id, message, recipient_id, msg_type, ts, metadata:\
log.info("Message from %s: %s" % (author_id, message))
self.onColorChange += lambda mid, author_id, new_color, changed_for, msg_type, ts:\
log.info("%s changed color to %s" % (author_id, new_color))
self.onTitleChange += lambda mid, author_id, new_title, changed_for, thread_id, ts:\
log.info("%s changed title of %s to %s" % (author_id, 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))
if not user_agent: if not user_agent:
user_agent = choice(USER_AGENTS) user_agent = choice(USER_AGENTS)
@@ -151,11 +181,6 @@ class Client(object):
DeprecationWarning) DeprecationWarning)
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
@@ -187,7 +212,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()
@@ -201,7 +226,11 @@ 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
@@ -246,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
@@ -328,14 +357,13 @@ class Client(object):
return False return False
# Load cookies into current session # Load cookies into current session
self._session.cookies = requests.cookies.merge_cookies(self._session.cookies, j) self._session.cookies = requests.cookies.merge_cookies(self._session.cookies, j)
self._post_login() self._postLogin()
return True return True
except Exception as e: except Exception as e:
raise Exception('Invalid json in {}, or bad merging of cookies'.format(sessionfile)) raise Exception('Invalid json in {}, or bad merging of cookies'.format(sessionfile))
def login(self, email, password, max_retries=5): def login(self, email, password, max_retries=5):
# Logging in self.onLoggingIn()
log.info("Logging in...")
self.email = email self.email = email
self.password = password self.password = password
@@ -346,7 +374,7 @@ class Client(object):
time.sleep(1) time.sleep(1)
continue continue
else: else:
log.info("Login successful.") self.onLoggedIn()
break break
else: else:
raise Exception("Login failed. Check email/password.") raise Exception("Login failed. Check email/password.")
@@ -376,39 +404,6 @@ class Client(object):
self.def_is_user = is_user self.def_is_user = is_user
self.is_def_recipient_set = True 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): def getAllUsers(self):
""" Gets all users from chat with info included """ """ Gets all users from chat with info included """
@@ -426,7 +421,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
@@ -434,7 +429,6 @@ class Client(object):
return users return users
def getUsers(self, name): def getUsers(self, name):
"""Find and get user by his/her name """Find and get user by his/her name
@@ -529,13 +523,10 @@ class Client(object):
if image_id: if image_id:
data['image_ids[0]'] = image_id data['image_ids[0]'] = image_id
if like: if like and not type(like) is Sticker:
try: data["sticker_id"] = Sticker.SMALL.value
sticker = LIKES[like.lower()] else:
except KeyError: data["sticker_id"] = like.value
# if user doesn't enter l or m or s, then use the large one
sticker = LIKES['l']
data["sticker_id"] = sticker
r = self._post(SendURL, data) r = self._post(SendURL, data)
@@ -556,7 +547,6 @@ class Client(object):
log.debug("With data {}".format(data)) log.debug("With data {}".format(data))
return True return True
def sendRemoteImage(self, recipient_id=None, message=None, is_user=True, image=''): def sendRemoteImage(self, recipient_id=None, message=None, is_user=True, image=''):
"""Send an image from a URL """Send an image from a URL
@@ -582,7 +572,6 @@ class Client(object):
image_id = self.uploadImage({'file': (image, open(image, 'rb'), mimetype)}) image_id = self.uploadImage({'file': (image, open(image, 'rb'), mimetype)})
return self.send(recipient_id, message, is_user, None, image_id) return self.send(recipient_id, message, is_user, None, image_id)
def uploadImage(self, image): def uploadImage(self, image):
"""Upload an image and get the image_id for sending in a message """Upload an image and get the image_id for sending in a message
@@ -595,7 +584,6 @@ class Client(object):
# Strip the start and parse out the returned image_id # Strip the start and parse out the returned image_id
return json.loads(r._content[9:])['payload']['metadata'][0]['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): def getThreadInfo(self, userID, last_n=20, start=None, is_user=True):
"""Get the info of one Thread """Get the info of one Thread
@@ -632,7 +620,6 @@ class Client(object):
messages.append(Message(**message)) messages.append(Message(**message))
return list(reversed(messages)) return list(reversed(messages))
def getThreadList(self, start, length=20): def getThreadList(self, start, length=20):
"""Get thread list of your facebook account. """Get thread list of your facebook account.
@@ -675,7 +662,6 @@ class Client(object):
return self.threads return self.threads
def getUnread(self): def getUnread(self):
form = { form = {
'client': 'mercury_sync', 'client': 'mercury_sync',
@@ -704,7 +690,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(),
@@ -715,13 +700,11 @@ 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
def friendConnect(self, friend_id):
def friend_connect(self, friend_id):
data = { data = {
"to_friend": friend_id, "to_friend": friend_id,
"action": "confirm" "action": "confirm"
@@ -731,7 +714,6 @@ class Client(object):
return r.ok return r.ok
def ping(self, sticky): def ping(self, sticky):
data = { data = {
'channel': self.user_channel, 'channel': self.user_channel,
@@ -745,11 +727,8 @@ 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.
"""
data = { data = {
"msgs_recv": 0, "msgs_recv": 0,
@@ -767,7 +746,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."""
@@ -785,7 +763,6 @@ 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 contains multiple messages in the content. May contains multiple messages in the content.
@@ -794,71 +771,120 @@ class Client(object):
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 getRecipientIdAndMsgType(msg_metadata):
fbid = m['message']['sender_fbid'] """Returns a tuple consisting of recipient id and message type"""
name = m['message']['sender_name'] recipient = None
self.on_message(mid, fbid, name, message, m) message_type = None
elif m['type'] in ['typ']: if 'threadFbId' in msg_metadata['threadKey']:
self.on_typing(m.get("from")) recipient = msg_metadata['threadKey']['threadFbId']
elif m['type'] in ['m_read_receipt']: message_type = MsgType.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']: recipient = msg_metadata['threadKey']['otherUserFbId']
viewer = m.get('realtime_viewer_fbid') message_type = MsgType.USER
unseen = m.get('unseen') return recipient, message_type
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']: # Added participants
self.on_qprimer(m.get('made')) if 'addedParticipants' in delta:
elif m['type'] in ['delta']: added_ids = [x['userFbId'] for x in delta['addedParticipants']]
if 'leftParticipantFbId' in m['delta']: author_id = metadata['actorFbId']
user_id = m['delta']['leftParticipantFbId'] thread_id = metadata['threadKey']['threadFbId']
actor_id = m['delta']['messageMetadata']['actorFbId'] ts = metadata["timestamp"]
thread_id = m['delta']['messageMetadata']['threadKey']['threadFbId'] self.onPeopleAdded(added_ids=added_ids, author_id=author_id, thread_id=thread_id, ts=ts)
self.on_person_removed(user_id, actor_id, thread_id)
elif 'addedParticipants' in m['delta']: # Left/removed participants
user_ids = [x['userFbId'] for x in m['delta']['addedParticipants']] elif 'leftParticipantFbId' in delta:
actor_id = m['delta']['messageMetadata']['actorFbId'] removed_id = delta['leftParticipantFbId']
thread_id = m['delta']['messageMetadata']['threadKey']['threadFbId'] author_id = metadata['actorFbId']
self.on_people_added(user_ids, actor_id, thread_id) thread_id = metadata['threadKey']['threadFbId']
elif 'messageMetadata' in m['delta']: ts = metadata["timestamp"]
recipient_id = 0 self.onPersonRemoved(removed_id=removed_id, author_id=author_id, thread_id=thread_id, ts=ts)
thread_type = None
if 'threadKey' in m['delta']['messageMetadata']: # Seen
if 'threadFbId' in m['delta']['messageMetadata']['threadKey']: elif delta.get("class") == "ReadReceipt":
recipient_id = m['delta']['messageMetadata']['threadKey']['threadFbId'] seen_by = delta["actorFbId"] or delta["threadKey"]["otherUserFbId"]
thread_type = 'group' thread_id = delta["threadKey"].get("threadFbId")
elif 'otherUserFbId' in m['delta']['messageMetadata']['threadKey']: timestamp = delta["actionTimestampMs"]
recipient_id = m['delta']['messageMetadata']['threadKey']['otherUserFbId'] self.onSeen(seen_by=seen_by, thread_id=thread_id, timestamp=timestamp)
thread_type = 'user'
mid = m['delta']['messageMetadata']['messageId'] # Color change
message = m['delta'].get('body','') elif delta_type == "change_thread_theme":
fbid = m['delta']['messageMetadata']['actorFbId'] mid = metadata["messageId"]
self.on_message_new(mid, fbid, message, m, recipient_id, thread_type) author_id = metadata["actorFbId"]
elif m['type'] in ['jewel_requests_add']: new_color = delta["untypedData"]["theme_color"]
from_id = m['from'] changed_for, msg_type = getRecipientIdAndMsgType(metadata)
self.on_friend_request(from_id) ts = metadata["timestamp"]
self.onColorChange(mid=mid, author_id=author_id, new_color=new_color, changed_for=changed_for,
msg_type=msg_type, ts=ts)
# Title change
elif delta_type == "change_thread_nickname":
mid = metadata["messageId"]
author_id = metadata["actorFbId"]
changed_for = delta["untypedData"]["participant_id"]
new_title = delta["untypedData"]["nickanme"]
thread_id = metadata["threadKey"].get("threadFbId")
ts = metadata["timestamp"]
self.onTitleChange(mid=mid, author_id=author_id, changed_for=changed_for, new_title=new_title,
thread_id=thread_id, ts=ts)
# New message
elif "messageMetadata" in delta:
mid = metadata['messageId']
message = delta.get('body', '')
author_id = metadata['actorFbId']
recipient_id, msg_type = getRecipientIdAndMsgType(metadata)
ts = metadata["timestamp"]
self.onMessage(mid=mid, author_id=author_id, message=message,
recipient_id=recipient_id, msg_type=msg_type, ts=ts, metadata=m)
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
else: else:
log.debug("Unknown type {}".format(m)) log.debug("Unknown type %s" % 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)
def startListening(self):
def start_listening(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()
def doOneListen(self, markAlive=True):
def do_one_listen(self, markAlive=True):
"""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."""
@@ -874,22 +900,19 @@ class Client(object):
except requests.exceptions.Timeout: except requests.exceptions.Timeout:
pass pass
def stopListening(self):
def stop_listening(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):
"""Get user info from id. Unordered. """Get user info from id. Unordered.
@@ -907,7 +930,6 @@ class Client(object):
user_ids = [fbidStrip(uid) for uid in user_ids] user_ids = [fbidStrip(uid) for uid in user_ids]
data = {"ids[{}]".format(i):uid for i,uid in enumerate(user_ids)} data = {"ids[{}]".format(i):uid for i,uid in enumerate(user_ids)}
r = self._post(UserInfoURL, data) r = self._post(UserInfoURL, data)
info = get_json(r.text) info = get_json(r.text)
@@ -916,8 +938,7 @@ class Client(object):
full_data=full_data[0] full_data=full_data[0]
return full_data return full_data
def removeUserFromChat(self, threadID, userID):
def remove_user_from_chat(self, threadID, userID):
"""Remove user (userID) from group chat (threadID) """Remove user (userID) from group chat (threadID)
:param threadID: group chat id :param threadID: group chat id
@@ -933,13 +954,12 @@ class Client(object):
return r.ok return r.ok
def add_users_to_chat(self, threadID, userID): def addUserToChat(self, threadID, userID):
"""Add user (userID) to group chat (threadID) """Add user (userID) to group chat (threadID)
:param threadID: group chat id :param threadID: group chat id
:param userID: user id to add to chat :param userID: user id to add to chat
""" """
return self.send(threadID, is_user=False, add_user_ids=[userID]) return self.send(threadID, is_user=False, add_user_ids=[userID])
def changeThreadTitle(self, threadID, newTitle): def changeThreadTitle(self, threadID, newTitle):
@@ -983,58 +1003,3 @@ class Client(object):
r = self._post(SendURL, data) r = self._post(SendURL, data)
return r.ok 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

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 __future__ import unicode_literals
from enum import Enum
import sys import sys
class Base(): class Base():
def __repr__(self): def __repr__(self):
uni = self.__unicode__() uni = self.__unicode__()
@@ -9,6 +11,7 @@ class Base():
def __unicode__(self): def __unicode__(self):
return u'<%s %s (%s)>' % (self.type.upper(), self.name, self.url) return u'<%s %s (%s)>' % (self.type.upper(), self.name, self.url)
class User(Base): class User(Base):
def __init__(self, data): def __init__(self, data):
if data['type'] != 'user': if data['type'] != 'user':
@@ -22,10 +25,62 @@ class User(Base):
self.data = data 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(): 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)
class MsgType(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 import json
from time import time from time import time
from random import random from random import random
USER_AGENTS = [ 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_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", "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",
@@ -22,7 +23,7 @@ def digit_to_char(digit):
return str(digit) return str(digit)
return chr(ord('a') + digit - 10) return chr(ord('a') + digit - 10)
def str_base(number,base): def str_base(number, base):
if number < 0: if number < 0:
return '-' + str_base(-number, base) return '-' + str_base(-number, base)
(d, m) = divmod(number, base) (d, m) = divmod(number, base)
@@ -33,15 +34,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))