diff --git a/fbchat/client.py b/fbchat/client.py index d16c136..633124c 100644 --- a/fbchat/client.py +++ b/fbchat/client.py @@ -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 -from .event_hook import EventHook - +import sys # Python 3 does not have raw_input, whereas Python 2 has and it's more secure try: @@ -89,51 +89,6 @@ 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) @@ -159,7 +114,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.isLoggedIn(): + if not session_cookies or not self.setSession(session_cookies) or not self.is_logged_in(): self.login(email, password, max_retries) def _console(self, msg): @@ -180,6 +135,11 @@ 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 @@ -211,7 +171,7 @@ class Client(object): payload=self._generatePayload(None) return self._session.post(url, data=payload, timeout=timeout, files=files) - def _postLogin(self): + def _post_login(self): self.payloadDefault = {} self.client_id = hex(int(random()*2147483648))[2:] self.start_time = now() @@ -225,11 +185,7 @@ 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'] - - for i in self.fb_dtsg: - self.ttstamp += str(ord(i)) - self.ttstamp += '2' - + self._setttstamp() # Set default payload self.payloadDefault['__rev'] = int(r.text.split('"revision":',1)[1].split(",",1)[0]) self.payloadDefault['__user'] = self.uid @@ -274,7 +230,7 @@ class Client(object): r = self._cleanGet(SaveDeviceURL) if 'home' in r.url: - self._postLogin() + self._post_login() return True else: return False @@ -330,10 +286,13 @@ class Client(object): r = self._cleanPost(CheckpointURL, data) return r - def isLoggedIn(self): + def is_logged_in(self): # Send a request to the login url, to see if we're directed to the home page. r = self._cleanGet(LoginURL) - return 'home' in r.url + if 'home' in r.url: + return True + else: + return False def getSession(self): """Returns the session cookies""" @@ -341,6 +300,7 @@ 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 """ @@ -348,15 +308,16 @@ class Client(object): # Quick check to see if session_cookies is formatted properly if not session_cookies or 'c_user' not in session_cookies: return False - + # Load cookies into current session self._session.cookies = requests.cookies.merge_cookies(self._session.cookies, session_cookies) - self._postLogin() + self._post_login() return True def login(self, email, password, max_retries=5): - self.onLoggingIn(email) - + # Logging in + log.info("Logging in {}...".format(email)) + if not (email and password): raise Exception("Email and password not set.") @@ -365,11 +326,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 < 5, ''))) + log.warning("Attempt #{} failed{}".format(i,{True:', retrying'}.get(i < max_retries, ''))) time.sleep(1) continue else: - self.onLoggedIn(email) + log.info("Login of {} successful.".format(email)) break else: raise Exception("Login failed. Check email/password.") @@ -399,6 +360,39 @@ 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 """ @@ -416,7 +410,7 @@ class Client(object): for k in payload.keys(): try: - user = User.adaptFromChat(payload[k]) + user = self._adapt_user_in_chat_to_user_model(payload[k]) except KeyError: continue @@ -520,10 +514,13 @@ class Client(object): if image_id: data['image_ids[0]'] = image_id - if like and not type(like) is Sticker: - data["sticker_id"] = Sticker.SMALL.value - else: - data["sticker_id"] = like.value + 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 r = self._post(SendURL, data) @@ -552,6 +549,7 @@ 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 @@ -577,6 +575,7 @@ 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 @@ -589,6 +588,7 @@ 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,6 +625,7 @@ class Client(object): messages.append(Message(**message)) return list(reversed(messages)) + def getThreadList(self, start, length=20): """Get thread list of your facebook account. @@ -667,6 +668,7 @@ class Client(object): return self.threads + def getUnread(self): form = { 'client': 'mercury_sync', @@ -695,6 +697,7 @@ class Client(object): r = self._post(DeliveredURL, data) return r.ok + def markAsRead(self, userID): data = { "watermarkTimestamp": now(), @@ -705,11 +708,13 @@ 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 friendConnect(self, friend_id): + + def friend_connect(self, friend_id): data = { "to_friend": friend_id, "action": "confirm" @@ -719,6 +724,7 @@ class Client(object): return r.ok + def ping(self, sticky): data = { 'channel': self.user_channel, @@ -732,8 +738,11 @@ 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, @@ -751,6 +760,7 @@ 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.""" @@ -768,6 +778,7 @@ 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. @@ -776,141 +787,71 @@ class Client(object): if 'ms' not in content: return log.debug("Received {}".format(content["ms"])) - for m in content["ms"]: - mtype = m.get("type") + for m in content['ms']: try: - # 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 + 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) else: - self.onUnknownMesssageType(msg=m) - + self.on_unknown_type(m) except Exception as e: - log.debug(str(e)) + # ex_type, ex, tb = sys.exc_info() + self.on_message_error(sys.exc_info(), m) - def startListening(self): + + def start_listening(self): """Start listening from an external event loop.""" self.listening = True 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. This method is only useful if you want to control fbchat from an external event loop.""" @@ -926,19 +867,22 @@ class Client(object): except requests.exceptions.Timeout: pass - def stopListening(self): + + def stop_listening(self): """Cleans up the variables from start_listening.""" self.listening = False self.sticky, self.pool = (None, None) + def listen(self, markAlive=True): - self.startListening() - self.onListening() + self.start_listening() + log.info("Listening...") while self.listening: - self.doOneListen(markAlive) + self.do_one_listen(markAlive) + + self.stop_listening() - self.stopListening() def getUserInfo(self, *user_ids): """Get user info from id. Unordered. @@ -956,6 +900,7 @@ 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) @@ -964,7 +909,8 @@ class Client(object): full_data=full_data[0] return full_data - def removeUserFromChat(self, threadID, userID): + + def remove_user_from_chat(self, threadID, userID): """Remove user (userID) from group chat (threadID) :param threadID: group chat id @@ -980,12 +926,13 @@ class Client(object): return r.ok - def addUserToChat(self, threadID, userID): + def add_users_to_chat(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): @@ -1029,3 +976,64 @@ 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)) + diff --git a/fbchat/event_hook.py b/fbchat/event_hook.py deleted file mode 100644 index 00f64c0..0000000 --- a/fbchat/event_hook.py +++ /dev/null @@ -1,57 +0,0 @@ -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() diff --git a/fbchat/models.py b/fbchat/models.py index 6c526a4..73f9126 100644 --- a/fbchat/models.py +++ b/fbchat/models.py @@ -1,8 +1,6 @@ from __future__ import unicode_literals -from enum import Enum import sys - class Base(): def __repr__(self): uni = self.__unicode__() @@ -11,7 +9,6 @@ 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': @@ -25,62 +22,10 @@ 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' diff --git a/fbchat/stickers.py b/fbchat/stickers.py new file mode 100644 index 0000000..6e02adb --- /dev/null +++ b/fbchat/stickers.py @@ -0,0 +1,8 @@ +LIKES={ + 'l': '369239383222810', + 'm': '369239343222814', + 's': '369239263222822' +} +LIKES['large'] = LIKES['l'] +LIKES['medium'] =LIKES['m'] +LIKES['small'] = LIKES['s'] diff --git a/fbchat/utils.py b/fbchat/utils.py index a16ea44..292d422 100644 --- a/fbchat/utils.py +++ b/fbchat/utils.py @@ -2,7 +2,6 @@ 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", @@ -40,7 +39,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) @@ -51,14 +50,15 @@ 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)) +