diff --git a/fbchat/_client.py b/fbchat/_client.py index daff692..1f10a5c 100644 --- a/fbchat/_client.py +++ b/fbchat/_client.py @@ -429,7 +429,7 @@ class Client: image_id = str(image_id) data = {"photo_id": str(image_id)} j = self.session._post("/mercury/attachments/photo/", data) - _util.handle_payload_error(j) + _exception.handle_payload_error(j) url = _util.get_jsmods_require(j, 3) if url is None: @@ -625,7 +625,7 @@ class Client: "https://{}-edge-chat.facebook.com/active_ping".format(self._pull_channel), data, ) - _util.handle_payload_error(j) + _exception.handle_payload_error(j) def _pull_message(self): """Call pull api to fetch message data.""" @@ -640,7 +640,7 @@ class Client: j = self.session._get( "https://{}-edge-chat.facebook.com/pull".format(self._pull_channel), data ) - _util.handle_payload_error(j) + _exception.handle_payload_error(j) return j def _parse_delta(self, delta): diff --git a/fbchat/_exception.py b/fbchat/_exception.py index 8dad169..aa37028 100644 --- a/fbchat/_exception.py +++ b/fbchat/_exception.py @@ -54,6 +54,14 @@ class ExternalError(FacebookError): return self.message +@attrs_exception +class GraphQLError(ExternalError): + """Raised by Facebook if there was an error in the GraphQL query.""" + + # TODO: Add `summary`, `severity` and `description` + # TODO: Handle multiple errors + + @attrs_exception class InvalidParameters(ExternalError): """Raised by Facebook if: @@ -79,4 +87,51 @@ class PleaseRefresh(ExternalError): """ code = attr.ib(1357004) - message = attr.ib("Please try closing and re-opening your browser window.") + + +def handle_payload_error(j): + if "error" not in j: + return + code = j["error"] + if code == 1357001: + error_cls = NotLoggedIn + elif code == 1357004: + error_cls = PleaseRefresh + elif code in (1357031, 1545010, 1545003): + error_cls = InvalidParameters + else: + error_cls = ExternalError + # TODO: Use j["errorSummary"] + # "errorDescription" is in the users own language! + raise error_cls( + "Error sending request: {}".format(j["errorDescription"]), code=code + ) + + +def handle_graphql_errors(j): + errors = [] + if j.get("error"): + 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 GraphQLError( + "GraphQL error: {} / {!r}".format( + error.get("message"), error.get("debug_info") + ), + code=error.get("code"), + ) + + +def handle_http_error(code): + msg = "Error when sending request: Got {} response.".format(code) + if code == 404: + raise HTTPError( + msg + " This is either because you specified an invalid URL, or because" + " you provided an invalid id (Facebook usually require integer ids)", + status_code=code, + ) + if 400 <= code < 600: + raise HTTPError(msg, status_code=code) diff --git a/fbchat/_graphql.py b/fbchat/_graphql.py index 918fdf1..bf940a8 100644 --- a/fbchat/_graphql.py +++ b/fbchat/_graphql.py @@ -46,9 +46,9 @@ def response_to_json(content): if "error_results" in x: del rtn[-1] continue - _util.handle_payload_error(x) + _exception.handle_payload_error(x) [(key, value)] = x.items() - _util.handle_graphql_errors(value) + _exception.handle_graphql_errors(value) if "response" in value: rtn[int(key[1:])] = value["response"] else: diff --git a/fbchat/_message.py b/fbchat/_message.py index 590dd03..32e6e3c 100644 --- a/fbchat/_message.py +++ b/fbchat/_message.py @@ -2,7 +2,16 @@ import attr import enum from string import Formatter from ._core import log, attrs_default -from . import _util, _session, _attachment, _location, _file, _quick_reply, _sticker +from . import ( + _exception, + _util, + _session, + _attachment, + _location, + _file, + _quick_reply, + _sticker, +) from typing import Optional @@ -112,7 +121,7 @@ class Message: "variables": _util.json_minimal({"data": data}), } j = self.session._payload_post("/webgraphql/mutation", data) - _util.handle_graphql_errors(j) + _exception.handle_graphql_errors(j) def fetch(self) -> "MessageData": """Fetch fresh `MessageData` object.""" diff --git a/fbchat/_session.py b/fbchat/_session.py index d28268a..d515623 100644 --- a/fbchat/_session.py +++ b/fbchat/_session.py @@ -293,7 +293,7 @@ class Session: def _payload_post(self, url, data, files=None): j = self._post(url, data, files=files) - _util.handle_payload_error(j) + _exception.handle_payload_error(j) try: return j["payload"] except (KeyError, TypeError) as e: diff --git a/fbchat/_util.py b/fbchat/_util.py index 4831fa7..3ff0520 100644 --- a/fbchat/_util.py +++ b/fbchat/_util.py @@ -5,13 +5,7 @@ import random import urllib.parse from ._core import log -from ._exception import ( - FBchatException, - FBchatFacebookError, - FBchatInvalidParameters, - FBchatNotLoggedIn, - FBchatPleaseRefresh, -) +from . import _exception from typing import Iterable, Optional @@ -57,8 +51,8 @@ def strip_json_cruft(text): """Removes `for(;;);` (and other cruft) that preceeds JSON responses.""" try: return text[text.index("{") :] - except ValueError: - raise FBchatException("No JSON object found: {!r}".format(text)) + except ValueError as e: + raise _exception.ParseError("No JSON object found", data=text) from e def get_decoded_r(r): @@ -72,8 +66,8 @@ def get_decoded(content): def parse_json(content): try: return json.loads(content) - except ValueError: - raise FBchatFacebookError("Error while parsing JSON: {!r}".format(content)) + except ValueError as e: + raise _exception.ParseError("Error while parsing JSON", data=content) from e def digit_to_char(digit): @@ -109,67 +103,16 @@ def generate_offline_threading_id(): return str(int(msgs, 2)) -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 j.get("error"): - 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( - "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"), - ) - - def check_request(r): - check_http_code(r.status_code) + _exception.handle_http_error(r.status_code) content = get_decoded_r(r) check_content(content) return content -def check_http_code(code): - msg = "Error when sending request: Got {} response.".format(code) - if code == 404: - raise FBchatFacebookError( - 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): if content is None or len(content) == 0: - raise FBchatFacebookError("Error when sending request: Got empty response") + raise _exception.HTTPError("Error when sending request: Got empty response") def to_json(content):