diff --git a/fbchat/_client.py b/fbchat/_client.py index bfee672..afa3815 100644 --- a/fbchat/_client.py +++ b/fbchat/_client.py @@ -106,39 +106,42 @@ class Client(object): query.update(self._state.get_params()) return query - def _fix_fb_errors(self, error_code): - """ - This fixes "Please try closing and re-opening your browser window" errors (1357004) - This error usually happens after 1-2 days of inactivity - It may be a bad idea to do this in an exception handler, if you have a better method, please suggest it! - """ - if error_code == "1357004": - log.warning("Got error #1357004. Refreshing state and resending request") - self._state = State.from_session(session=self._state._session) - return True - return False + 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") + self._state = State.from_session(session=self._state._session) def _get(self, url, query=None, error_retries=3): payload = self._generatePayload(query) r = self._state._session.get(prefix_url(url), params=payload) + content = check_request(r) + j = to_json(content) try: - return check_request(r) - except FBchatFacebookError as e: - if error_retries > 0 and self._fix_fb_errors(e.fb_error_code): + handle_payload_error(j) + except FBchatPleaseRefresh: + if error_retries > 0: + self._do_refresh() return self._get(url, query=query, error_retries=error_retries - 1) - raise e + raise + return j def _post(self, url, query=None, files=None, as_graphql=False, error_retries=3): payload = self._generatePayload(query) r = self._state._session.post(prefix_url(url), data=payload, files=files) + content = check_request(r) try: if as_graphql: - content = check_request(r, as_json=False) return graphql_response_to_json(content) else: - return check_request(r) - except FBchatFacebookError as e: - if error_retries > 0 and self._fix_fb_errors(e.fb_error_code): + j = 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 + handle_payload_error(j) + return j + except FBchatPleaseRefresh: + if error_retries > 0: + self._do_refresh() return self._post( url, query=query, @@ -146,7 +149,7 @@ class Client(object): as_graphql=as_graphql, error_retries=error_retries - 1, ) - raise e + raise def _payload_post(self, url, data, files=None): j = self._post(url, data, files=files) @@ -1443,6 +1446,11 @@ class Client(object): "recipient_map[{}]".format(generateOfflineThreadingID()): thread_id, } j = self._payload_post("/mercury/attachments/forward/", data) + if not j.get("success"): + raise FBchatFacebookError( + "Failed forwarding attachment: {}".format(j["error"]), + fb_error_message=j["error"], + ) def createGroup(self, message, user_ids): """ @@ -1731,6 +1739,7 @@ class Client(object): } data = {"doc_id": 1491398900900362, "variables": json.dumps({"data": data})} j = self._payload_post("/webgraphql/mutation", data) + handle_graphql_errors(j) def createPlan(self, plan, thread_id=None): """ @@ -1753,6 +1762,11 @@ class Client(object): "acontext": ACONTEXT, } j = self._payload_post("/ajax/eventreminder/create", data) + if "error" in j: + raise FBchatFacebookError( + "Failed creating plan: {}".format(j["error"]), + fb_error_message=j["error"], + ) def editPlan(self, plan, new_plan): """ @@ -1828,6 +1842,11 @@ class Client(object): data["option_is_selected_array[{}]".format(i)] = str(int(option.vote)) j = self._payload_post("/messaging/group_polling/create_poll/?dpr=1", data) + if j.get("status") != "success": + raise FBchatFacebookError( + "Failed creating poll: {}".format(j.get("errorTitle")), + fb_error_message=j.get("errorMessage"), + ) def updatePollVote(self, poll_id, option_ids=[], new_options=[]): """ @@ -1850,6 +1869,11 @@ class Client(object): data["new_options[{}]".format(i)] = option_text j = self._payload_post("/messaging/group_polling/update_vote/?dpr=1", data) + if j.get("status") != "success": + raise FBchatFacebookError( + "Failed updating poll vote: {}".format(j.get("errorTitle")), + fb_error_message=j.get("errorMessage"), + ) def setTypingStatus(self, status, thread_id=None, thread_type=None): """ diff --git a/fbchat/_exception.py b/fbchat/_exception.py index c1dd0d6..8b28faf 100644 --- a/fbchat/_exception.py +++ b/fbchat/_exception.py @@ -28,5 +28,30 @@ class FBchatFacebookError(FBchatException): self.request_status_code = request_status_code +class FBchatInvalidParameters(FBchatFacebookError): + """Raised by Facebook if: + + - Some function supplied invalid parameters. + - Some content is not found. + - Some content is no longer available. + """ + + +class FBchatNotLoggedIn(FBchatFacebookError): + """Raised by Facebook if the client has been logged out.""" + + fb_error_code = "1357001" + + +class FBchatPleaseRefresh(FBchatFacebookError): + """Raised by Facebook if the client has been inactive for too long. + + This error usually happens after 1-2 days of inactivity. + """ + + 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/_graphql.py b/fbchat/_graphql.py index 508d914..50eaea4 100644 --- a/fbchat/_graphql.py +++ b/fbchat/_graphql.py @@ -49,9 +49,9 @@ def graphql_response_to_json(content): if "error_results" in x: del rtn[-1] continue - _util.check_json(x) + _util.handle_payload_error(x) [(key, value)] = x.items() - _util.check_json(value) + _util.handle_graphql_errors(value) if "response" in value: rtn[int(key[1:])] = value["response"] else: diff --git a/fbchat/_util.py b/fbchat/_util.py index 823e313..c9729e9 100644 --- a/fbchat/_util.py +++ b/fbchat/_util.py @@ -11,7 +11,13 @@ from os.path import basename import warnings import logging import requests -from ._exception import FBchatException, FBchatFacebookError +from ._exception import ( + FBchatException, + FBchatFacebookError, + FBchatInvalidParameters, + FBchatNotLoggedIn, + FBchatPleaseRefresh, +) try: from urllib.parse import urlencode, parse_qs, urlparse @@ -107,51 +113,62 @@ def generateOfflineThreadingID(): return str(int(msgs, 2)) -def check_json(j): - if hasattr(j.get("payload"), "get") and j["payload"].get("error"): +def handle_payload_error(j): + if "error" not in j: + return + error = j["error"] + if j["error"] == 1357001: + error_cls = FBchatNotLoggedIn + elif j["error"] == 1357004: + error_cls = FBchatPleaseRefresh + elif j["error"] in (1357031, 1545010, 1545003): + error_cls = FBchatInvalidParameters + else: + error_cls = FBchatFacebookError + # TODO: Use j["errorSummary"] + # "errorDescription" is in the users own language! + raise error_cls( + "Error #{} when sending request: {}".format(error, j["errorDescription"]), + fb_error_code=error, + fb_error_message=j["errorDescription"], + ) + + +def handle_graphql_errors(j): + errors = [] + if "error" in j: + errors = [j["error"]] + if "errors" in j: + errors = j["errors"] + if errors: + error = errors[0] # TODO: Handle multiple errors + # TODO: Use `summary`, `severity` and `description` raise FBchatFacebookError( - "Error when sending request: {}".format(j["payload"]["error"]), - fb_error_code=None, - fb_error_message=j["payload"]["error"], + "GraphQL error #{}: {} / {!r}".format( + error.get("code"), error.get("message"), error.get("debug_info") + ), + fb_error_code=error.get("code"), + fb_error_message=error.get("message"), ) - elif j.get("error"): - if "errorDescription" in j: - # 'errorDescription' is in the users own language! - raise FBchatFacebookError( - "Error #{} when sending request: {}".format( - j["error"], j["errorDescription"] - ), - fb_error_code=j["error"], - fb_error_message=j["errorDescription"], - ) - elif "debug_info" in j["error"] and "code" in j["error"]: - raise FBchatFacebookError( - "Error #{} when sending request: {}".format( - j["error"]["code"], repr(j["error"]["debug_info"]) - ), - fb_error_code=j["error"]["code"], - fb_error_message=j["error"]["debug_info"], - ) - else: - raise FBchatFacebookError( - "Error {} when sending request".format(j["error"]), - fb_error_code=j["error"], - ) -def check_request(r, as_json=True): +def check_request(r): check_http_code(r.status_code) content = get_decoded_r(r) check_content(content) - return to_json(content) if as_json else content + return content def check_http_code(code): - if 400 <= code < 600: + msg = "Error when sending request: Got {} response.".format(code) + if code == 404: raise FBchatFacebookError( - "Error when sending request: Got {} response".format(code), + msg + " This is either because you specified an invalid URL, or because" + " you provided an invalid id (Facebook usually requires integer ids).", request_status_code=code, ) + if 400 <= code < 600: + raise FBchatFacebookError(msg, request_status_code=code) def check_content(content, as_json=True): @@ -162,7 +179,6 @@ def check_content(content, as_json=True): def to_json(content): content = strip_json_cruft(content) j = parse_json(content) - check_json(j) log.debug(j) return j