From 0234327bf91598438d9e2dd5edb8bcad63c8161b Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 6 Feb 2017 10:48:01 +0100 Subject: [PATCH 1/5] Added functions to save and load session cookies to and from file This uses some fairly unknown functions from the "requests" library --- fbchat/client.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/fbchat/client.py b/fbchat/client.py index aabf36b..6e275e6 100644 --- a/fbchat/client.py +++ b/fbchat/client.py @@ -189,6 +189,36 @@ class Client(object): payload=self._generatePayload(None) return self._session.post(url, data=payload, timeout=timeout, files=files) + 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 + f.write(json.dumps(self._session.cookies.get_dict(), ensure_ascii=False)) + + 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: + j = json.load(f) + if not j or 'c_user' not in j: + return False + # Load cookies into current session + self._session.cookies = requests.cookies.merge_cookies(self._session.cookies, j) + return True + except Exception as e: + raise Exception('Invalid json in {}, or bad merging of cookies'.format(sessionfile)) + def login(self): if not (self.email and self.password): From 0708d981d598518bdbc57cc0723dc74d00efe446 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 6 Feb 2017 10:48:39 +0100 Subject: [PATCH 2/5] Fixed a spelling mistake --- fbchat/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fbchat/client.py b/fbchat/client.py index 6e275e6..751cea4 100644 --- a/fbchat/client.py +++ b/fbchat/client.py @@ -145,7 +145,7 @@ class Client(object): >>> from fbchat.client import Client, log >>> log.setLevel(logging.DEBUG) - You can do the same thing by addint the 'debug' argument: + You can do the same thing by adding the 'debug' argument: >>> from fbchat import Client >>> client = Client("...", "...", debug=True) From f7fd5b4be520360ad7bbc1e4fdfb282560d4bc3c Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 6 Feb 2017 15:23:21 +0100 Subject: [PATCH 3/5] Changed login function, so that loading a session from file is possible --- fbchat/client.py | 145 +++++++++++++++++++++++++---------------------- 1 file changed, 77 insertions(+), 68 deletions(-) diff --git a/fbchat/client.py b/fbchat/client.py index 751cea4..5a9eb58 100644 --- a/fbchat/client.py +++ b/fbchat/client.py @@ -58,7 +58,7 @@ class Client(object): """ - def __init__(self, email, password, debug=True, user_agent=None , max_retries=5): + 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` @@ -69,11 +69,9 @@ class Client(object): """ - if not (email and password): + if do_login and not (email and password): raise Exception("id and password or config is needed") - self.email = email - self.password = password self.debug = debug self._session = requests.session() self.req_counter = 1 @@ -118,21 +116,9 @@ class Client(object): handler.setLevel(logging_level) log.addHandler(handler) log.setLevel(logging.DEBUG) - - # Logging in - log.info("Logging in...") - - 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) - continue - else: - log.info("Login successful") - break - else: - raise Exception("login failed. Check id/password") - + + if do_login: + self.login(email, password, max_retries) self.threads = [] @@ -189,6 +175,62 @@ class Client(object): payload=self._generatePayload(None) return self._session.post(url, data=payload, timeout=timeout, files=files) + def _post_login(self): + self.payloadDefault = {} + self.client_id = hex(int(random()*2147483648))[2:] + self.start_time = now() + self.uid = int(self._session.cookies['c_user']) + self.user_channel = "p_" + str(self.uid) + self.ttstamp = '' + + r = self._get(BaseURL) + soup = bs(r.text, "lxml") + log.debug(r.text) + 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() + # Set default payload + self.payloadDefault['__rev'] = int(r.text.split('"revision":',1)[1].split(",",1)[0]) + self.payloadDefault['__user'] = self.uid + self.payloadDefault['__a'] = '1' + self.payloadDefault['ttstamp'] = self.ttstamp + self.payloadDefault['fb_dtsg'] = self.fb_dtsg + + self.form = { + 'channel' : self.user_channel, + 'partition' : '-2', + 'clientid' : self.client_id, + 'viewer_uid' : self.uid, + 'uid' : self.uid, + 'state' : 'active', + 'format' : 'json', + 'idle' : 0, + 'cap' : '8' + } + + self.prev = now() + self.tmp_prev = now() + self.last_sync = now() + + def _login(self): + if not (self.email and self.password): + raise Exception("id and password or config is needed") + + soup = bs(self._get(MobileURL).text, "lxml") + data = dict((elem['name'], elem['value']) for elem in soup.findAll("input") if elem.has_attr('value') and elem.has_attr('name')) + data['email'] = self.email + data['pass'] = self.password + data['login'] = 'Log In' + + r = self._cleanPost(LoginURL, data) + + if 'home' in r.url: + self._post_login() + return True + else: + return False + def saveSession(self, sessionfile): """Dumps the session cookies to (sessionfile). WILL OVERWRITE ANY EXISTING FILE @@ -215,61 +257,28 @@ class Client(object): return False # Load cookies into current session self._session.cookies = requests.cookies.merge_cookies(self._session.cookies, j) + self._post_login() return True except Exception as e: raise Exception('Invalid json in {}, or bad merging of cookies'.format(sessionfile)) + def login(self, email, password, max_retries=5): + # Logging in + log.info("Logging in...") + + self.email = email + self.password = password - def login(self): - if not (self.email and self.password): - raise Exception("id and password or config is needed") - - soup = bs(self._get(MobileURL).text, "lxml") - data = dict((elem['name'], elem['value']) for elem in soup.findAll("input") if elem.has_attr('value') and elem.has_attr('name')) - data['email'] = self.email - data['pass'] = self.password - data['login'] = 'Log In' - - r = self._cleanPost(LoginURL, data) - - if 'home' in r.url: - self.client_id = hex(int(random()*2147483648))[2:] - self.start_time = now() - self.uid = int(self._session.cookies['c_user']) - self.user_channel = "p_" + str(self.uid) - self.ttstamp = '' - - r = self._get(BaseURL) - soup = bs(r.text, "lxml") - self.fb_dtsg = soup.find("input", {'name':'fb_dtsg'})['value'] - self.fb_h = soup.find("input", {'name':'h'})['value'] - self._setttstamp() - # Set default payload - self.payloadDefault['__rev'] = int(r.text.split('"revision":',1)[1].split(",",1)[0]) - self.payloadDefault['__user'] = self.uid - self.payloadDefault['__a'] = '1' - self.payloadDefault['ttstamp'] = self.ttstamp - self.payloadDefault['fb_dtsg'] = self.fb_dtsg - - self.form = { - 'channel' : self.user_channel, - 'partition' : '-2', - 'clientid' : self.client_id, - 'viewer_uid' : self.uid, - 'uid' : self.uid, - 'state' : 'active', - 'format' : 'json', - 'idle' : 0, - 'cap' : '8' - } - - self.prev = now() - self.tmp_prev = now() - self.last_sync = now() - - return True + 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) + continue + else: + log.info("Login successful") + break else: - return False + raise Exception("login failed. Check id/password") def logout(self, timeout=30): data = {} From d712497a8a6e329ad5a83884f9f104cbccba946a Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 6 Feb 2017 15:32:22 +0100 Subject: [PATCH 4/5] Updated README --- README.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.rst b/README.rst index bf978c9..73cc53a 100644 --- a/README.rst +++ b/README.rst @@ -94,6 +94,22 @@ Example Echobot bot.listen() +Saving session +========================== + +.. code-block:: python + + client.saveSession(sessionfile) + + +Saving session +========================== + +.. code-block:: python + + client = fbchat.Client(None, None, do_login=False) + client.loadSession(sessionfile) + Authors ======= From 907027b4234d2db12795d783b8820192eb8d598f Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 6 Feb 2017 15:33:35 +0100 Subject: [PATCH 5/5] Fixed minor mistake in README --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 73cc53a..816e6a0 100644 --- a/README.rst +++ b/README.rst @@ -102,7 +102,7 @@ Saving session client.saveSession(sessionfile) -Saving session +Loading session ========================== .. code-block:: python