diff --git a/fbchat/client.py b/fbchat/client.py index aed9a56..0e4c00e 100644 --- a/fbchat/client.py +++ b/fbchat/client.py @@ -12,11 +12,10 @@ """ import requests -import json import logging from uuid import uuid1 import warnings -from random import random, choice +from random import choice from datetime import datetime from bs4 import BeautifulSoup as bs from mimetypes import guess_type @@ -52,15 +51,15 @@ facebookEncoding = 'UTF-8' # Log settings log = logging.getLogger("client") + class Client(object): """A client for the Facebook Chat (Messenger). See http://github.com/carpedm20/fbchat for complete documentation for the API. - """ - def __init__(self, email, password, debug=True, user_agent=None , max_retries=5, do_login=True): + def __init__(self, email, password, debug=True, user_agent=None, max_retries=5, do_login=True): """A client for the Facebook Chat (Messenger). :param email: Facebook `email` or `id` or `phone number` @@ -68,7 +67,6 @@ class Client(object): import fbchat chat = fbchat.Client(email, password) - """ if do_login and not (email and password): @@ -119,7 +117,7 @@ class Client(object): handler.setLevel(logging_level) log.addHandler(handler) log.setLevel(logging.DEBUG) - + if do_login: self.login(email, password, max_retries) @@ -131,13 +129,12 @@ class Client(object): This method shouldn't be used anymore. Use the log itself: >>> import logging - >>> from fbchat.client import Client, log + >>> from fbchat.client import log >>> log.setLevel(logging.DEBUG) You can do the same thing by adding the 'debug' argument: >>> from fbchat import Client >>> client = Client("...", "...", debug=True) - """ warnings.warn( "Client._console shouldn't be used. Use 'log.'", @@ -150,10 +147,9 @@ class Client(object): self.ttstamp += '2' 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 - ''' + """ payload = self.payloadDefault.copy() if query: payload.update(query) @@ -230,7 +226,7 @@ class Client(object): data['login'] = 'Log In' r = self._cleanPost(LoginURL, data) - + # Usually, 'Checkpoint' will refer to 2FA if 'checkpoint' in r.url and 'Enter Security Code to Continue' in r.text: r = self._2FA(r) @@ -244,11 +240,11 @@ class Client(object): return True else: return False - + def _2FA(self,r): soup = bs(r.text, "lxml") data = dict() - s = raw_input('Please enter your 2FA code --> ') + s = input('Please enter your 2FA code --> ') data['approvals_code'] = s data['fb_dtsg'] = soup.find("input", {'name':'fb_dtsg'})['value'] data['nh'] = soup.find("input", {'name':'nh'})['value'] @@ -259,48 +255,48 @@ class Client(object): if 'home' in r.url: return r - + del(data['approvals_code']) del(data['submit[Submit Code]']) del(data['codes_submitted']) - + data['name_action_selected'] = 'save_device' data['submit[Continue]'] = 'Continue' - log.info('Saving browser') #At this stage, we have dtsg, nh, name_action_selected, submit[Continue] + log.info('Saving browser') # At this stage, we have dtsg, nh, name_action_selected, submit[Continue] r = self._cleanPost(CheckpointURL, data) if 'home' in r.url: return r - + del(data['name_action_selected']) - log.info('Starting Facebook checkup flow') #At this stage, we have dtsg, nh, submit[Continue] + log.info('Starting Facebook checkup flow') # At this stage, we have dtsg, nh, submit[Continue] r = self._cleanPost(CheckpointURL, data) if 'home' in r.url: return r - + del(data['submit[Continue]']) data['submit[This was me]'] = 'This Was Me' - log.info('Verifying login attempt') #At this stage, we have dtsg, nh, submit[This was me] + log.info('Verifying login attempt') # At this stage, we have dtsg, nh, submit[This was me] r = self._cleanPost(CheckpointURL, data) if 'home' in r.url: return r - + del(data['submit[This was me]']) data['submit[Continue]'] = 'Continue' data['name_action_selected'] = 'save_device' - log.info('Saving device again') #At this stage, we have dtsg, nh, submit[Continue], name_action_selected + log.info('Saving device again') # At this stage, we have dtsg, nh, submit[Continue], name_action_selected r = self._cleanPost(CheckpointURL, data) return r - + def saveSession(self, sessionfile): """Dumps the session cookies to (sessionfile). WILL OVERWRITE ANY EXISTING FILE - + :param sessionfile: location of saved session file """ - + log.info('Saving session') with open(sessionfile, 'w') as f: # Grab cookies from current session, and save them as JSON @@ -308,10 +304,10 @@ class Client(object): def loadSession(self, sessionfile): """Loads session cookies from (sessionfile) - + :param sessionfile: location of saved session file """ - + log.info('Loading session') with open(sessionfile, 'r') as f: try: @@ -328,11 +324,11 @@ class Client(object): def login(self, email, password, max_retries=5): # Logging in log.info("Logging in...") - + self.email = email self.password = password - for i in range(1,max_retries+1): + for i in range(1, max_retries+1): if not self._login(): log.warning("Attempt #{} failed{}".format(i,{True:', retrying'}.get(i<5,''))) time.sleep(1) @@ -356,16 +352,12 @@ class Client(object): self.seq = "0" return r - def listen(self): - pass def _adapt_user_in_chat_to_user_model(self, user_in_chat): + """ Adapts user info from chat to User model acceptable initial dict - ''' 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, @@ -382,9 +374,7 @@ class Client(object): 'uri': 'https://www.facebook.com/profile.php?id=100014812758264', 'id': '100014812758264', 'gender': 2 - - - ''' + """ return { 'type': 'user', @@ -457,7 +447,7 @@ class Client(object): :param add_user_ids: a list of user ids to add to a chat """ - messageAndOTID=generateOfflineThreadingID() + messageAndOTID = generateOfflineThreadingID() timestamp = now() date = datetime.now() data = { @@ -488,12 +478,12 @@ class Client(object): 'manual_retry_cnt' : '0', 'signatureID' : getSignatureID() } - + if message_type.lower() == 'group': - data["thread_fbid"] = recipient_id + data["thread_fbid"] = recipient_id else: data["other_user_fbid"] = recipient_id - + if add_user_ids: data['action_type'] = 'ma-type:log-message' # It's possible to add multiple users @@ -503,7 +493,7 @@ class Client(object): else: data['action_type'] = 'ma-type:user-generated-message' data['body'] = message - data['has_attachment'] = image_id != None + data['has_attachment'] = image_id is not None data['specific_to_list[0]'] = 'fbid:' + str(recipient_id) data['specific_to_list[1]'] = 'fbid:' + str(self.uid) @@ -519,10 +509,10 @@ class Client(object): data["sticker_id"] = sticker r = self._post(SendURL, data) - + if not r.ok: return False - + if isinstance(r._content, str) is False: r._content = r._content.decode(facebookEncoding) j = get_json(r._content) @@ -570,7 +560,7 @@ class Client(object): r._content = r._content.decode(facebookEncoding) # 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, thread_type='user'): """Get the info of one Thread @@ -579,7 +569,7 @@ class Client(object): :param start: (optional) the start index of a thread (Deprecated) :param thread_type: (optional) change from 'user' for group threads """ - + assert last_n > 0, 'length must be positive integer, got %d' % last_n assert start is None, '`start` is deprecated, always 0 offset querry is returned' data = {} @@ -613,11 +603,11 @@ class Client(object): """Get thread list of your facebook account. :param start: the start index of a thread - :param end: (optional) the last index of a thread + :param length: (optional) the length of a thread """ - + assert length < 21, '`length` is deprecated, max. last 20 threads are returned' - + timestamp = now() date = datetime.now() data = { @@ -659,7 +649,7 @@ class Client(object): 'client': 'mercury_sync', 'folders[0]': 'inbox', 'last_action_timestamp': now() - 60*1000 - #'last_action_timestamp': 0 + # 'last_action_timestamp': 0 } r = self._post(ThreadSyncURL, form) @@ -722,10 +712,9 @@ class Client(object): 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. - ''' + """ data = { "msgs_recv": 0, @@ -745,9 +734,7 @@ class Client(object): 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.""" data = { "msgs_recv": 0, @@ -765,10 +752,9 @@ class Client(object): 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. - ''' + """ if 'ms' not in content: return @@ -878,14 +864,14 @@ class Client(object): def fbidStrip(_fbid): # Stripping of `fbid:` from author_id - if type(_fbid) == int: + if type(_fbid) == int: return _fbid - + if type(_fbid) == str and 'fbid:' in _fbid: return int(_fbid[5:]) - + 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) @@ -898,39 +884,39 @@ class Client(object): def remove_user_from_chat(self, threadID, userID): """Remove user (userID) from group chat (threadID) - + :param threadID: group chat id :param userID: user id to remove from chat """ - + data = { "uid" : userID, "tid" : threadID } - + r = self._post(RemoveUserURL, data) - self._console(r) - self._console(data) + log.info(r) + log.info(data) return r.ok 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, message_type='group', add_user_ids=[userID]) def changeThreadTitle(self, threadID, newTitle): """Change title of a group conversation - + :param threadID: group chat id :param newTitle: new group chat title """ - + messageAndOTID = generateOfflineThreadingID() timestamp = now() date = datetime.now() @@ -964,76 +950,61 @@ class Client(object): r = self._post(SendURL, data) - self._console(r) - self._console(data) + log.info(r) + log.info(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. - ''' + """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 - ''' + """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) + """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 - ''' + """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 - ''' + """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 - ''' + """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 - ''' + """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 - ''' + """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 - ''' + """subclass Client and override this method to add custom behavior on event""" log.warning("Exception:\n{}".format(exception))