From 41f10079361fae5d521eb82f16dce2f1c9179916 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 8 Jan 2020 10:33:25 +0100 Subject: [PATCH] Make Session public --- fbchat/__init__.py | 3 ++- fbchat/_client.py | 6 ++--- fbchat/_session.py | 58 +++++++++++++++++++++++++++++++++++++++------- 3 files changed, 55 insertions(+), 12 deletions(-) diff --git a/fbchat/__init__.py b/fbchat/__init__.py index 1586476..4f88896 100644 --- a/fbchat/__init__.py +++ b/fbchat/__init__.py @@ -13,6 +13,7 @@ _logging.getLogger(__name__).addHandler(_logging.NullHandler()) from . import _core, _util from ._core import Image from ._exception import FBchatException, FBchatFacebookError +from ._session import Session from ._thread import ThreadType, ThreadLocation, ThreadColor, Thread from ._user import TypingStatus, User, ActiveStatus from ._group import Group @@ -44,4 +45,4 @@ __license__ = "BSD 3-Clause" __author__ = "Taehoon Kim; Moreels Pieter-Jan; Mads Marquart" __email__ = "carpedm20@gmail.com" -__all__ = ("Client",) +__all__ = ("Session", "Client") diff --git a/fbchat/_client.py b/fbchat/_client.py index 7dbc843..5d9f974 100644 --- a/fbchat/_client.py +++ b/fbchat/_client.py @@ -38,9 +38,9 @@ ACONTEXT = { class Client: """A client for the Facebook Chat (Messenger). - This is the main class, which contains all the methods you use to interact with - Facebook. You can extend this class, and overwrite the ``on`` methods, to provide - custom event handling (mainly useful while listening). + This contains all the methods you use to interact with Facebook. You can extend this + class, and overwrite the ``on`` methods, to provide custom event handling (mainly + useful while listening). """ @property diff --git a/fbchat/_session.py b/fbchat/_session.py index 13acbf6..aa86a7c 100644 --- a/fbchat/_session.py +++ b/fbchat/_session.py @@ -100,7 +100,10 @@ def _2fa_helper(session, code, r): @attrs_default class Session: - """Stores and manages state required for most Facebook requests.""" + """Stores and manages state required for most Facebook requests. + + This is the main class, which is used to login to Facebook. + """ user_id = attr.ib() _fb_dtsg = attr.ib() @@ -110,7 +113,7 @@ class Session: _client_id = attr.ib(factory=client_id_factory) _logout_h = attr.ib(None) - def get_params(self): + def _get_params(self): self._counter += 1 # TODO: Make this operation atomic / thread-safe return { "__a": 1, @@ -121,6 +124,16 @@ class Session: @classmethod def login(cls, email, password, on_2fa_callback): + """Login the user, using ``email`` and ``password``. + + Args: + email: Facebook ``email`` or ``id`` or ``phone number`` + password: Facebook account password + on_2fa_callback: Function that will be called, in case a 2FA code is needed + + Raises: + FBchatException: On failed login + """ session = session_factory() soup = find_input_fields(session.get("https://m.facebook.com/").text) @@ -145,7 +158,7 @@ class Session: r = session.get("https://m.facebook.com/login/save-device/cancel/") if is_home(r.url): - return cls.from_session(session=session) + return cls._from_session(session=session) else: raise _exception.FBchatException( "Login failed. Check email/password. " @@ -153,12 +166,24 @@ class Session: ) def is_logged_in(self): + """Send a request to Facebook to check the login status. + + Returns: + bool: Whether the user is still logged in + """ # Send a request to the login url, to see if we're directed to the home page url = "https://m.facebook.com/login.php?login_attempt=1" r = self._session.get(url, allow_redirects=False) return "Location" in r.headers and is_home(r.headers["Location"]) def logout(self): + """Safely log out the user. + + The session object must not be used after this action has been performed! + + Raises: + FBchatException: On failed logout + """ logout_h = self._logout_h if not logout_h: url = _util.prefix_url("/bluebar/modern_settings_menu/") @@ -166,10 +191,14 @@ class Session: logout_h = re.search(r'name=\\"h\\" value=\\"(.*?)\\"', h_r.text).group(1) url = _util.prefix_url("/logout.php") - return self._session.get(url, params={"ref": "mb", "h": logout_h}).ok + r = self._session.get(url, params={"ref": "mb", "h": logout_h}) + if not r.ok: + raise exception.FBchatException( + "Failed logging out: {}".format(r.status_code) + ) @classmethod - def from_session(cls, session): + def _from_session(cls, session): # TODO: Automatically set user_id when the cookie changes in the session user_id = get_user_id(session) @@ -198,22 +227,35 @@ class Session: ) def get_cookies(self): + """Retrieve session cookies, that can later be used in `from_cookies`. + + Returns: + dict: A dictionary containing session cookies + """ return self._session.cookies.get_dict() @classmethod def from_cookies(cls, cookies): + """Load a session from session cookies. + + Args: + cookies (dict): A dictionary containing session cookies + + Raises: + FBchatException: If given invalid cookies + """ session = session_factory() session.cookies = requests.cookies.merge_cookies(session.cookies, cookies) - return cls.from_session(session=session) + return cls._from_session(session=session) def _get(self, url, params, error_retries=3): - params.update(self.get_params()) + params.update(self._get_params()) r = self._session.get(_util.prefix_url(url), params=params) content = _util.check_request(r) return _util.to_json(content) def _post(self, url, data, files=None, as_graphql=False): - data.update(self.get_params()) + data.update(self._get_params()) r = self._session.post(_util.prefix_url(url), data=data, files=files) content = _util.check_request(r) if as_graphql: