diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 40e7f5b..453d42c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -21,8 +21,8 @@ Traceback (most recent call last): File "[site-packages]/fbchat/client.py", line 78, in __init__ self.login(email, password, max_tries) File "[site-packages]/fbchat/client.py", line 407, in login - raise FBchatUserError('Login failed. Check email/password. (Failed on URL: {})'.format(login_url)) -fbchat.models.FBchatUserError: Login failed. Check email/password. (Failed on URL: https://m.facebook.com/login.php?login_attempt=1) + raise FBchatException('Login failed. Check email/password. (Failed on URL: {})'.format(login_url)) +fbchat.FBchatException: Login failed. Check email/password. (Failed on URL: https://m.facebook.com/login.php?login_attempt=1) ``` ## Environment information diff --git a/docs/api.rst b/docs/api.rst index 3d9a920..83a9466 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -38,7 +38,6 @@ Exceptions .. autoexception:: FBchatException() .. autoexception:: FBchatFacebookError() -.. autoexception:: FBchatUserError() Attachments ----------- diff --git a/fbchat/__init__.py b/fbchat/__init__.py index c8c5831..36d1659 100644 --- a/fbchat/__init__.py +++ b/fbchat/__init__.py @@ -11,10 +11,10 @@ _logging.getLogger(__name__).addHandler(_logging.NullHandler()) # The order of these is somewhat significant, e.g. User has to be imported after Thread! from . import _core, _util -from ._exception import FBchatException, FBchatFacebookError, FBchatUserError +from ._exception import FBchatException, FBchatFacebookError from ._thread import ThreadType, ThreadLocation, ThreadColor, Thread from ._user import TypingStatus, User, ActiveStatus -from ._group import Group, Room +from ._group import Group from ._page import Page from ._message import EmojiSize, MessageReaction, Mention, Message from ._attachment import Attachment, UnsentMessage, ShareAttachment diff --git a/fbchat/_client.py b/fbchat/_client.py index bf6308e..b0f2915 100644 --- a/fbchat/_client.py +++ b/fbchat/_client.py @@ -7,7 +7,7 @@ from collections import OrderedDict from ._core import log from . import _util, _graphql, _state -from ._exception import FBchatException, FBchatFacebookError, FBchatUserError +from ._exception import FBchatException, FBchatFacebookError from ._thread import ThreadType, ThreadLocation, ThreadColor from ._user import TypingStatus, User, ActiveStatus from ._group import Group @@ -43,25 +43,6 @@ class Client: to provide custom event handling (mainly useful while listening). """ - listening = False - """Whether the client is listening. - - Used when creating an external event loop to determine when to stop listening. - """ - - @property - def ssl_verify(self): - """Verify SSL certificate. - - Set to False to allow debugging with a proxy. - """ - # TODO: Deprecate this - return self._state._session.verify - - @ssl_verify.setter - def ssl_verify(self, value): - self._state._session.verify = value - @property def uid(self): """The ID of the client. @@ -70,16 +51,12 @@ class Client: """ return self._uid - def __init__( - self, email, password, user_agent=None, max_tries=5, session_cookies=None - ): + def __init__(self, email, password, session_cookies=None): """Initialize and log in the client. Args: email: Facebook ``email``, ``id`` or ``phone number`` password: Facebook account password - user_agent: Custom user agent to use when sending requests. If `None`, user agent will be chosen from a premade list - max_tries (int): Maximum number of times to try logging in session_cookies (dict): Cookies from a previous session (Will default to login if these are invalid) Raises: @@ -87,8 +64,6 @@ class Client: """ self._sticky, self._pool = (None, None) self._seq = "0" - self._default_thread_id = None - self._default_thread_type = None self._pull_channel = 0 self._markAlive = True self._buddylist = dict() @@ -96,10 +71,10 @@ class Client: # 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, user_agent=user_agent) + or not self.setSession(session_cookies) or not self.isLoggedIn() ): - self.login(email, password, max_tries, user_agent=user_agent) + self.login(email, password) """ INTERNAL REQUEST METHODS @@ -160,7 +135,7 @@ class Client: """ return self._state.get_cookies() - def setSession(self, session_cookies, user_agent=None): + def setSession(self, session_cookies): """Load session cookies. Args: @@ -171,16 +146,14 @@ class Client: """ try: # Load cookies into current session - self._state = _state.State.from_cookies( - session_cookies, user_agent=user_agent - ) + self._state = _state.State.from_cookies(session_cookies) self._uid = self._state.user_id except Exception as e: log.exception("Failed loading session") return False return True - def login(self, email, password, max_tries=5, user_agent=None): + def login(self, email, password): """Login the user, using ``email`` and ``password``. If the user is already logged in, this will do a re-login. @@ -188,36 +161,20 @@ class Client: Args: email: Facebook ``email`` or ``id`` or ``phone number`` password: Facebook account password - max_tries (int): Maximum number of times to try logging in Raises: FBchatException: On failed login """ self.onLoggingIn(email=email) - if max_tries < 1: - raise FBchatUserError("Cannot login: max_tries should be at least one") - if not (email and password): - raise FBchatUserError("Email and password not set") + raise ValueError("Email and password not set") - for i in range(1, max_tries + 1): - try: - self._state = _state.State.login( - email, - password, - on_2fa_callback=self.on2FACode, - user_agent=user_agent, - ) - self._uid = self._state.user_id - except Exception: - if i >= max_tries: - raise - log.exception("Attempt #{} failed, retrying".format(i)) - time.sleep(1) - else: - self.onLoggedIn(email=email) - break + self._state = _state.State.login( + email, password, on_2fa_callback=self.on2FACode + ) + self._uid = self._state.user_id + self.onLoggedIn(email=email) def logout(self): """Safely log out the client. @@ -235,45 +192,6 @@ class Client: END LOGIN METHODS """ - """ - DEFAULT THREAD METHODS - """ - - def _getThread(self, given_thread_id=None, given_thread_type=None): - """Check if thread ID is given and if default is set, and return correct values. - - Returns: - tuple: Thread ID and thread type - - Raises: - ValueError: If thread ID is not given and there is no default - """ - if given_thread_id is None: - if self._default_thread_id is not None: - return self._default_thread_id, self._default_thread_type - else: - raise ValueError("Thread ID is not set") - else: - return given_thread_id, given_thread_type - - def setDefaultThread(self, thread_id, thread_type): - """Set default thread to send messages to. - - Args: - thread_id: User/Group ID to default to. See :ref:`intro_threads` - thread_type (ThreadType): See :ref:`intro_threads` - """ - self._default_thread_id = thread_id - self._default_thread_type = thread_type - - def resetDefaultThread(self): - """Reset default thread.""" - self.setDefaultThread(None, None) - - """ - END DEFAULT THREAD METHODS - """ - """ FETCH METHODS """ @@ -494,8 +412,6 @@ class Client: Raises: FBchatException: If request failed """ - thread_id, thread_type = self._getThread(thread_id, None) - data = { "query": query, "snippetOffset": offset, @@ -624,7 +540,7 @@ class Client: if thread.type == ThreadType.USER: users[id_] = thread else: - raise FBchatUserError("Thread {} was not a user".format(thread)) + raise ValueError("Thread {} was not a user".format(thread)) return users @@ -649,7 +565,7 @@ class Client: if thread.type == ThreadType.PAGE: pages[id_] = thread else: - raise FBchatUserError("Thread {} was not a page".format(thread)) + raise ValueError("Thread {} was not a page".format(thread)) return pages @@ -671,7 +587,7 @@ class Client: if thread.type == ThreadType.GROUP: groups[id_] = thread else: - raise FBchatUserError("Thread {} was not a group".format(thread)) + raise ValueError("Thread {} was not a group".format(thread)) return groups @@ -756,8 +672,6 @@ class Client: Raises: FBchatException: If request failed """ - thread_id, thread_type = self._getThread(thread_id, None) - params = { "id": thread_id, "message_limit": limit, @@ -789,12 +703,11 @@ class Client: return messages def fetchThreadList( - self, offset=None, limit=20, thread_location=ThreadLocation.INBOX, before=None + self, limit=20, thread_location=ThreadLocation.INBOX, before=None ): """Fetch the client's thread list. Args: - offset: Deprecated. Do not use! limit (int): Max. number of threads to retrieve. Capped at 20 thread_location (ThreadLocation): INBOX, PENDING, ARCHIVED or OTHER before (datetime.datetime): The point from which to retrieve threads @@ -805,20 +718,13 @@ class Client: Raises: FBchatException: If request failed """ - if offset is not None: - log.warning( - "Using `offset` in `fetchThreadList` is no longer supported, " - "since Facebook migrated to the use of GraphQL in this request. " - "Use `before` instead." - ) - if limit > 20 or limit < 1: - raise FBchatUserError("`limit` should be between 1 and 20") + raise ValueError("`limit` should be between 1 and 20") if thread_location in ThreadLocation: loc_str = thread_location.value else: - raise FBchatUserError('"thread_location" must be a value of ThreadLocation') + raise TypeError('"thread_location" must be a value of ThreadLocation') params = { "limit": limit, @@ -891,6 +797,7 @@ class Client: image_id = str(image_id) data = {"photo_id": str(image_id)} j = self._post("/mercury/attachments/photo/", data) + _util.handle_payload_error(j) url = _util.get_jsmods_require(j, 3) if url is None: @@ -910,7 +817,6 @@ class Client: Raises: FBchatException: If request failed """ - thread_id, thread_type = self._getThread(thread_id, None) message_info = self._forcedFetch(thread_id, mid).get("message") return Message._from_graphql(message_info) @@ -995,7 +901,6 @@ class Client: Returns: typing.Iterable: :class:`ImageAttachment` or :class:`VideoAttachment` """ - thread_id, thread_type = self._getThread(thread_id, None) data = {"id": thread_id, "first": 48} thread_id = str(thread_id) j, = self.graphql_requests(_graphql.from_query_id("515216185516880", data)) @@ -1057,32 +962,11 @@ class Client: Raises: FBchatException: If request failed """ - thread_id, thread_type = self._getThread(thread_id, thread_type) thread = thread_type._to_class()(thread_id) data = thread._to_send_data() data.update(message._to_send_data()) return self._doSendRequest(data) - def sendMessage(self, message, thread_id=None, thread_type=ThreadType.USER): - """Deprecated. Use :func:`fbchat.Client.send` instead.""" - return self.send( - Message(text=message), thread_id=thread_id, thread_type=thread_type - ) - - def sendEmoji( - self, - emoji=None, - size=EmojiSize.SMALL, - thread_id=None, - thread_type=ThreadType.USER, - ): - """Deprecated. Use :func:`fbchat.Client.send` instead.""" - return self.send( - Message(text=emoji, emoji_size=size), - thread_id=thread_id, - thread_type=thread_type, - ) - def wave(self, wave_first=True, thread_id=None, thread_type=None): """Wave hello to a thread. @@ -1097,7 +981,6 @@ class Client: Raises: FBchatException: If request failed """ - thread_id, thread_type = self._getThread(thread_id, thread_type) thread = thread_type._to_class()(thread_id) data = thread._to_send_data() data["action_type"] = "ma-type:user-generated-message" @@ -1131,7 +1014,7 @@ class Client: ) elif isinstance(quick_reply, QuickReplyLocation): if not isinstance(payload, LocationAttachment): - raise ValueError( + raise TypeError( "Payload must be an instance of `fbchat.LocationAttachment`" ) return self.sendLocation( @@ -1162,7 +1045,6 @@ class Client: def _sendLocation( self, location, current=True, message=None, thread_id=None, thread_type=None ): - thread_id, thread_type = self._getThread(thread_id, thread_type) thread = thread_type._to_class()(thread_id) data = thread._to_send_data() if message is not None: @@ -1231,7 +1113,6 @@ class Client: `files` should be a list of tuples, with a file's ID and mimetype. """ - thread_id, thread_type = self._getThread(thread_id, thread_type) thread = thread_type._to_class()(thread_id) data = thread._to_send_data() data.update(self._oldMessage(message)._to_send_data()) @@ -1337,48 +1218,6 @@ class Client: files=files, message=message, thread_id=thread_id, thread_type=thread_type ) - def sendImage( - self, - image_id, - message=None, - thread_id=None, - thread_type=ThreadType.USER, - is_gif=False, - ): - """Deprecated.""" - if is_gif: - mimetype = "image/gif" - else: - mimetype = "image/png" - return self._sendFiles( - files=[(image_id, mimetype)], - message=message, - thread_id=thread_id, - thread_type=thread_type, - ) - - def sendRemoteImage( - self, image_url, message=None, thread_id=None, thread_type=ThreadType.USER - ): - """Deprecated. Use :func:`fbchat.Client.sendRemoteFiles` instead.""" - return self.sendRemoteFiles( - file_urls=[image_url], - message=message, - thread_id=thread_id, - thread_type=thread_type, - ) - - def sendLocalImage( - self, image_path, message=None, thread_id=None, thread_type=ThreadType.USER - ): - """Deprecated. Use :func:`fbchat.Client.sendLocalFiles` instead.""" - return self.sendLocalFiles( - file_paths=[image_path], - message=message, - thread_id=thread_id, - thread_type=thread_type, - ) - def forwardAttachment(self, attachment_id, thread_id=None): """Forward an attachment. @@ -1389,7 +1228,6 @@ class Client: Raises: FBchatException: If request failed """ - thread_id, thread_type = self._getThread(thread_id, None) data = { "attachment_id": attachment_id, "recipient_map[{}]".format(_util.generateOfflineThreadingID()): thread_id, @@ -1417,7 +1255,7 @@ class Client: data = self._oldMessage(message)._to_send_data() if len(user_ids) < 2: - raise FBchatUserError("Error when creating group: Not enough participants") + raise ValueError("Error when creating group: Not enough participants") for i, user_id in enumerate(user_ids + [self._uid]): data["specific_to_list[{}]".format(i)] = "fbid:{}".format(user_id) @@ -1439,7 +1277,6 @@ class Client: Raises: FBchatException: If request failed """ - thread_id, thread_type = self._getThread(thread_id, None) data = Group(thread_id)._to_send_data() data["action_type"] = "ma-type:log-message" @@ -1449,7 +1286,7 @@ class Client: for i, user_id in enumerate(user_ids): if user_id == self._uid: - raise FBchatUserError( + raise ValueError( "Error when adding users: Cannot add self to group thread" ) else: @@ -1469,14 +1306,10 @@ class Client: Raises: FBchatException: If request failed """ - thread_id, thread_type = self._getThread(thread_id, None) - data = {"uid": user_id, "tid": thread_id} j = self._payload_post("/chat/remove_participants/", data) def _adminStatus(self, admin_ids, admin, thread_id=None): - thread_id, thread_type = self._getThread(thread_id, None) - data = {"add": admin, "thread_fbid": thread_id} admin_ids = _util.require_list(admin_ids) @@ -1520,14 +1353,10 @@ class Client: Raises: FBchatException: If request failed """ - thread_id, thread_type = self._getThread(thread_id, None) - data = {"set_mode": int(require_admin_approval), "thread_fbid": thread_id} j = self._payload_post("/messaging/set_approval_mode/?dpr=1", data) def _usersApproval(self, user_ids, approve, thread_id=None): - thread_id, thread_type = self._getThread(thread_id, None) - user_ids = _util.require_list(user_ids) data = { @@ -1576,8 +1405,6 @@ class Client: Raises: FBchatException: If request failed """ - thread_id, thread_type = self._getThread(thread_id, None) - data = {"thread_image_id": image_id, "thread_id": thread_id} j = self._payload_post("/messaging/set_thread_image/?dpr=1", data) @@ -1625,8 +1452,6 @@ class Client: Raises: FBchatException: If request failed """ - thread_id, thread_type = self._getThread(thread_id, thread_type) - if thread_type == ThreadType.USER: # The thread is a user, so we change the user's nickname return self.changeNickname( @@ -1650,8 +1475,6 @@ class Client: Raises: FBchatException: If request failed """ - thread_id, thread_type = self._getThread(thread_id, thread_type) - data = { "nickname": nickname, "participant_id": user_id, @@ -1671,8 +1494,6 @@ class Client: Raises: FBchatException: If request failed """ - thread_id, thread_type = self._getThread(thread_id, None) - data = { "color_choice": color.value if color != ThreadColor.MESSENGER_BLUE else "", "thread_or_other_fbid": thread_id, @@ -1695,8 +1516,6 @@ class Client: Raises: FBchatException: If request failed """ - thread_id, thread_type = self._getThread(thread_id, None) - data = {"emoji_choice": emoji, "thread_or_other_fbid": thread_id} j = self._payload_post( "/messaging/save_thread_emoji/?source=thread_settings&dpr=1", data @@ -1733,8 +1552,6 @@ class Client: Raises: FBchatException: If request failed """ - thread_id, thread_type = self._getThread(thread_id, None) - data = { "event_type": "EVENT", "event_time": _util.datetime_to_seconds(plan.time), @@ -1801,11 +1618,6 @@ class Client: } j = self._payload_post("/ajax/eventreminder/rsvp", data) - def eventReminder(self, thread_id, time, title, location="", location_id=""): - """Deprecated. Use :func:`fbchat.Client.createPlan` instead.""" - plan = Plan(time=time, title=title, location=location, location_id=location_id) - self.createPlan(plan=plan, thread_id=thread_id) - def createPoll(self, poll, thread_id=None): """Create poll in a group thread. @@ -1816,8 +1628,6 @@ class Client: Raises: FBchatException: If request failed """ - thread_id, thread_type = self._getThread(thread_id, None) - # We're using ordered dictionaries, because the Facebook endpoint that parses # the POST parameters is badly implemented, and deals with ordering the options # wrongly. If you can find a way to fix this for the endpoint, or if you find @@ -1874,8 +1684,6 @@ class Client: Raises: FBchatException: If request failed """ - thread_id, thread_type = self._getThread(thread_id, thread_type) - data = { "typ": status.value, "thread": thread_id, @@ -2087,7 +1895,6 @@ class Client: Raises: FBchatException: If request failed """ - thread_id, thread_type = self._getThread(thread_id, None) j = self._payload_post("/ajax/mercury/mark_spam.php?dpr=1", {"id": thread_id}) return True @@ -2117,7 +1924,6 @@ class Client: mute_time (datetime.timedelta): Time to mute, use ``None`` to mute forever thread_id: User/Group ID to mute. See :ref:`intro_threads` """ - thread_id, thread_type = self._getThread(thread_id, None) if mute_time is None: mute_settings = -1 else: @@ -2140,7 +1946,6 @@ class Client: mute: Boolean. True to mute, False to unmute thread_id: User/Group ID to mute. See :ref:`intro_threads` """ - thread_id, thread_type = self._getThread(thread_id, None) data = {"reactions_mute_mode": int(mute), "thread_fbid": thread_id} j = self._payload_post( "/ajax/mercury/change_reactions_mute_thread/?dpr=1", data @@ -2161,7 +1966,6 @@ class Client: mute: Boolean. True to mute, False to unmute thread_id: User/Group ID to mute. See :ref:`intro_threads` """ - thread_id, thread_type = self._getThread(thread_id, None) data = {"mentions_mute_mode": int(mute), "thread_fbid": thread_id} j = self._payload_post("/ajax/mercury/change_mentions_mute_thread/?dpr=1", data) @@ -2194,6 +1998,7 @@ class Client: "https://{}-edge-chat.facebook.com/active_ping".format(self._pull_channel), data, ) + _util.handle_payload_error(j) def _pullMessage(self): """Call pull api to fetch message data.""" @@ -2205,9 +2010,11 @@ class Client: "clientid": self._state._client_id, "state": "active" if self._markAlive else "offline", } - return self._get( + j = self._get( "https://{}-edge-chat.facebook.com/pull".format(self._pull_channel), data ) + _util.handle_payload_error(j) + return j def _parseDelta(self, m): def getThreadIdAndThreadType(msg_metadata): @@ -2728,7 +2535,6 @@ class Client: self.onMessage( mid=message.uid, author_id=message.author, - message=message.text, message_object=message, thread_id=thread_id, thread_type=thread_type, @@ -2743,7 +2549,6 @@ class Client: self.onMessage( mid=mid, author_id=author_id, - message=delta.get("body", ""), message_object=Message._from_pull( delta, mid=mid, @@ -2869,29 +2674,7 @@ class Client: except Exception as e: self.onMessageError(exception=e, msg=m) - def startListening(self): - """Start listening from an external event loop. - - Raises: - FBchatException: If request failed - """ - self.listening = True - - def doOneListen(self, markAlive=None): - """Do one cycle of the listening loop. - - This method is useful if you want to control the client from an external event - loop. - - Warning: - ``markAlive`` parameter is deprecated, use :func:`Client.setActiveStatus` - or ``markAlive`` parameter in :func:`Client.listen` instead. - - Returns: - bool: Whether the loop should keep running - """ - if markAlive is not None: - self._markAlive = markAlive + def _doOneListen(self): try: if self._markAlive: self._ping() @@ -2910,7 +2693,6 @@ class Client: if e.request_status_code in [502, 503]: # Bump pull channel, while contraining withing 0-4 self._pull_channel = (self._pull_channel + 1) % 5 - self.startListening() else: raise e except Exception as e: @@ -2918,11 +2700,6 @@ class Client: return True - def stopListening(self): - """Clean up the variables from `Client.startListening`.""" - self.listening = False - self._sticky, self._pool = (None, None) - def listen(self, markAlive=None): """Initialize and runs the listening loop continually. @@ -2932,13 +2709,12 @@ class Client: if markAlive is not None: self.setActiveStatus(markAlive) - self.startListening() self.onListening() - while self.listening and self.doOneListen(): + while self._doOneListen(): pass - self.stopListening() + self._sticky, self._pool = (None, None) def setActiveStatus(self, markAlive): """Change active status while listening. @@ -2996,7 +2772,6 @@ class Client: self, mid=None, author_id=None, - message=None, message_object=None, thread_id=None, thread_type=ThreadType.USER, @@ -3009,7 +2784,6 @@ class Client: Args: mid: The message ID author_id: The ID of the author - message: (deprecated. Use ``message_object.text`` instead) message_object (Message): The message (As a `Message` object) thread_id: Thread ID that the message was sent to. See :ref:`intro_threads` thread_type (ThreadType): Type of thread that the message was sent to. See :ref:`intro_threads` diff --git a/fbchat/_exception.py b/fbchat/_exception.py index c28d68b..b84c5b9 100644 --- a/fbchat/_exception.py +++ b/fbchat/_exception.py @@ -50,7 +50,3 @@ class FBchatPleaseRefresh(FBchatFacebookError): fb_error_code = "1357004" fb_error_message = "Please try closing and re-opening your browser window." - - -class FBchatUserError(FBchatException): - """Thrown by ``fbchat`` when wrong values are entered.""" diff --git a/fbchat/_group.py b/fbchat/_group.py index c0eeddd..d8eea84 100644 --- a/fbchat/_group.py +++ b/fbchat/_group.py @@ -103,16 +103,3 @@ class Group(Thread): def _to_send_data(self): return {"thread_fbid": self.uid} - - -@attr.s(cmp=False, init=False) -class Room(Group): - """Deprecated. Use `Group` instead.""" - - # True is room is not discoverable - privacy_mode = attr.ib(None) - - def __init__(self, uid, privacy_mode=None, **kwargs): - super(Room, self).__init__(uid, **kwargs) - self.type = ThreadType.ROOM - self.privacy_mode = privacy_mode diff --git a/fbchat/_state.py b/fbchat/_state.py index e11e2f6..5215229 100644 --- a/fbchat/_state.py +++ b/fbchat/_state.py @@ -23,11 +23,11 @@ def find_input_fields(html): return bs4.BeautifulSoup(html, "html.parser", parse_only=bs4.SoupStrainer("input")) -def session_factory(user_agent=None): +def session_factory(): session = requests.session() session.headers["Referer"] = "https://www.facebook.com" # TODO: Deprecate setting the user agent manually - session.headers["User-Agent"] = user_agent or random.choice(_util.USER_AGENTS) + session.headers["User-Agent"] = random.choice(_util.USER_AGENTS) return session @@ -120,8 +120,8 @@ class State: } @classmethod - def login(cls, email, password, on_2fa_callback, user_agent=None): - session = session_factory(user_agent=user_agent) + def login(cls, email, password, on_2fa_callback): + session = session_factory() soup = find_input_fields(session.get("https://m.facebook.com/").text) data = dict( @@ -147,7 +147,7 @@ class State: if is_home(r.url): return cls.from_session(session=session) else: - raise _exception.FBchatUserError( + raise _exception.FBchatException( "Login failed. Check email/password. " "(Failed on url: {})".format(r.url) ) @@ -201,63 +201,29 @@ class State: return self._session.cookies.get_dict() @classmethod - def from_cookies(cls, cookies, user_agent=None): - session = session_factory(user_agent=user_agent) + def from_cookies(cls, cookies): + session = session_factory() session.cookies = requests.cookies.merge_cookies(session.cookies, cookies) return cls.from_session(session=session) - def _do_refresh(self): - # TODO: Raise the error instead, and make the user do the refresh manually - # It may be a bad idea to do this in an exception handler, if you have a better method, please suggest it! - log.warning("Refreshing state and resending request") - new = State.from_session(session=self._session) - self.user_id = new.user_id - self._fb_dtsg = new._fb_dtsg - self._revision = new._revision - self._counter = new._counter - self._logout_h = new._logout_h or self._logout_h - def _get(self, url, params, error_retries=3): params.update(self.get_params()) r = self._session.get(_util.prefix_url(url), params=params) content = _util.check_request(r) - j = _util.to_json(content) - try: - _util.handle_payload_error(j) - except _exception.FBchatPleaseRefresh: - if error_retries > 0: - self._do_refresh() - return self._get(url, params, error_retries=error_retries - 1) - raise - return j + return _util.to_json(content) - def _post(self, url, data, files=None, as_graphql=False, error_retries=3): + def _post(self, url, data, files=None, as_graphql=False): data.update(self.get_params()) r = self._session.post(_util.prefix_url(url), data=data, files=files) content = _util.check_request(r) - try: - if as_graphql: - return _graphql.response_to_json(content) - else: - j = _util.to_json(content) - # TODO: Remove this, and move it to _payload_post instead - # We can't yet, since errors raised in here need to be caught below - _util.handle_payload_error(j) - return j - except _exception.FBchatPleaseRefresh: - if error_retries > 0: - self._do_refresh() - return self._post( - url, - data, - files=files, - as_graphql=as_graphql, - error_retries=error_retries - 1, - ) - raise + if as_graphql: + return _graphql.response_to_json(content) + else: + return _util.to_json(content) def _payload_post(self, url, data, files=None): j = self._post(url, data, files=files) + _util.handle_payload_error(j) try: return j["payload"] except (KeyError, TypeError): diff --git a/fbchat/_thread.py b/fbchat/_thread.py index 6cc444c..0659257 100644 --- a/fbchat/_thread.py +++ b/fbchat/_thread.py @@ -10,7 +10,6 @@ class ThreadType(Enum): USER = 1 GROUP = 2 - ROOM = 2 PAGE = 3 def _to_class(self): @@ -20,7 +19,6 @@ class ThreadType(Enum): return { ThreadType.USER: _user.User, ThreadType.GROUP: _group.Group, - ThreadType.ROOM: _group.Room, ThreadType.PAGE: _page.Page, }[self] diff --git a/tests/conftest.py b/tests/conftest.py index 53c4773..f2a8a34 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -45,9 +45,11 @@ def client2(pytestconfig): @pytest.fixture(scope="module") def client(client1, thread): - client1.setDefaultThread(thread["id"], thread["type"]) + # TODO: These are commented out since `setDefaultThread` is removed - But now any + # test that use this fixture is likely broken! + # client1.setDefaultThread(thread["id"], thread["type"]) yield client1 - client1.resetDefaultThread() + # client1.resetDefaultThread() @pytest.fixture(scope="session", params=["client1", "client2"]) diff --git a/tests/test_base.py b/tests/test_base.py index 25aa94b..6cdf43a 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -3,7 +3,7 @@ import py_compile from glob import glob from os import path, environ -from fbchat import FBchatUserError, Message, Client +from fbchat import FBchatException, Message, Client @pytest.mark.offline @@ -24,7 +24,7 @@ def test_login(client1): assert not client1.isLoggedIn() - with pytest.raises(FBchatUserError): + with pytest.raises(FBchatException): client1.login("", "", max_tries=1) client1.login(email, password) @@ -38,13 +38,3 @@ def test_sessions(client1): Client("no email needed", "no password needed", session_cookies=session) client1.setSession(session) assert client1.isLoggedIn() - - -@pytest.mark.tryfirst -def test_default_thread(client1, thread): - client1.setDefaultThread(thread["id"], thread["type"]) - assert client1.send(Message(text="Sent to the specified thread")) - - client1.resetDefaultThread() - with pytest.raises(ValueError): - client1.send(Message(text="Should not be sent")) diff --git a/tests/utils.py b/tests/utils.py index 440001c..1e44c92 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -39,22 +39,20 @@ TEXT_LIST = [ class ClientThread(threading.Thread): + # TODO: Refactor this to work with the new listening setup def __init__(self, client, *args, **kwargs): self.client = client self.should_stop = threading.Event() super(ClientThread, self).__init__(*args, **kwargs) def start(self): - self.client.startListening() - self.client.doOneListen() # QPrimer, Facebook now knows we're about to start pulling + self.client._doOneListen() # QPrimer, Facebook now knows we're about to start pulling super(ClientThread, self).start() def run(self): - while not self.should_stop.is_set() and self.client.doOneListen(): + while not self.should_stop.is_set() and self.client._doOneListen(): pass - self.client.stopListening() - class CaughtValue(threading.Event): def set(self, res): @@ -93,7 +91,6 @@ def load_client(n, cache): client = Client( load_variable("client{}_email".format(n), cache), load_variable("client{}_password".format(n), cache), - user_agent="Mozilla/5.0 (Windows NT 6.3; WOW64; ; NCT50_AAP285C84A1328) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36", session_cookies=cache.get("client{}_session".format(n), None), max_tries=1, )