Merge branch 'master' into graphql-cleanup

This commit is contained in:
Mads Marquart
2019-07-02 19:17:53 +02:00
committed by GitHub
4 changed files with 121 additions and 56 deletions

View File

@@ -106,39 +106,42 @@ class Client(object):
query.update(self._state.get_params()) query.update(self._state.get_params())
return query return query
def _fix_fb_errors(self, error_code): def _do_refresh(self):
""" # TODO: Raise the error instead, and make the user do the refresh manually
This fixes "Please try closing and re-opening your browser window" errors (1357004) # It may be a bad idea to do this in an exception handler, if you have a better method, please suggest it!
This error usually happens after 1-2 days of inactivity log.warning("Refreshing state and resending request")
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) self._state = State.from_session(session=self._state._session)
return True
return False
def _get(self, url, query=None, error_retries=3): def _get(self, url, query=None, error_retries=3):
payload = self._generatePayload(query) payload = self._generatePayload(query)
r = self._state._session.get(prefix_url(url), params=payload) r = self._state._session.get(prefix_url(url), params=payload)
content = check_request(r)
j = to_json(content)
try: try:
return check_request(r) handle_payload_error(j)
except FBchatFacebookError as e: except FBchatPleaseRefresh:
if error_retries > 0 and self._fix_fb_errors(e.fb_error_code): if error_retries > 0:
self._do_refresh()
return self._get(url, query=query, error_retries=error_retries - 1) 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): def _post(self, url, query=None, files=None, as_graphql=False, error_retries=3):
payload = self._generatePayload(query) payload = self._generatePayload(query)
r = self._state._session.post(prefix_url(url), data=payload, files=files) r = self._state._session.post(prefix_url(url), data=payload, files=files)
content = check_request(r)
try: try:
if as_graphql: if as_graphql:
content = check_request(r, as_json=False)
return _graphql.response_to_json(content) return _graphql.response_to_json(content)
else: else:
return check_request(r) j = to_json(content)
except FBchatFacebookError as e: # TODO: Remove this, and move it to _payload_post instead
if error_retries > 0 and self._fix_fb_errors(e.fb_error_code): # 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( return self._post(
url, url,
query=query, query=query,
@@ -146,7 +149,7 @@ class Client(object):
as_graphql=as_graphql, as_graphql=as_graphql,
error_retries=error_retries - 1, error_retries=error_retries - 1,
) )
raise e raise
def _payload_post(self, url, data, files=None): def _payload_post(self, url, data, files=None):
j = self._post(url, data, files=files) j = self._post(url, data, files=files)
@@ -1443,6 +1446,11 @@ class Client(object):
"recipient_map[{}]".format(generateOfflineThreadingID()): thread_id, "recipient_map[{}]".format(generateOfflineThreadingID()): thread_id,
} }
j = self._payload_post("/mercury/attachments/forward/", data) 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): def createGroup(self, message, user_ids):
""" """
@@ -1731,6 +1739,7 @@ class Client(object):
} }
data = {"doc_id": 1491398900900362, "variables": json.dumps({"data": data})} data = {"doc_id": 1491398900900362, "variables": json.dumps({"data": data})}
j = self._payload_post("/webgraphql/mutation", data) j = self._payload_post("/webgraphql/mutation", data)
handle_graphql_errors(j)
def createPlan(self, plan, thread_id=None): def createPlan(self, plan, thread_id=None):
""" """
@@ -1753,6 +1762,11 @@ class Client(object):
"acontext": ACONTEXT, "acontext": ACONTEXT,
} }
j = self._payload_post("/ajax/eventreminder/create", data) 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): def editPlan(self, plan, new_plan):
""" """
@@ -1828,6 +1842,11 @@ class Client(object):
data["option_is_selected_array[{}]".format(i)] = str(int(option.vote)) data["option_is_selected_array[{}]".format(i)] = str(int(option.vote))
j = self._payload_post("/messaging/group_polling/create_poll/?dpr=1", data) 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=[]): def updatePollVote(self, poll_id, option_ids=[], new_options=[]):
""" """
@@ -1850,6 +1869,11 @@ class Client(object):
data["new_options[{}]".format(i)] = option_text data["new_options[{}]".format(i)] = option_text
j = self._payload_post("/messaging/group_polling/update_vote/?dpr=1", data) 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): def setTypingStatus(self, status, thread_id=None, thread_type=None):
""" """

View File

@@ -28,5 +28,30 @@ class FBchatFacebookError(FBchatException):
self.request_status_code = request_status_code 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): class FBchatUserError(FBchatException):
"""Thrown by fbchat when wrong values are entered""" """Thrown by fbchat when wrong values are entered"""

View File

@@ -49,9 +49,9 @@ def response_to_json(content):
if "error_results" in x: if "error_results" in x:
del rtn[-1] del rtn[-1]
continue continue
_util.check_json(x) _util.handle_payload_error(x)
[(key, value)] = x.items() [(key, value)] = x.items()
_util.check_json(value) _util.handle_graphql_errors(value)
if "response" in value: if "response" in value:
rtn[int(key[1:])] = value["response"] rtn[int(key[1:])] = value["response"]
else: else:

View File

@@ -11,7 +11,13 @@ from os.path import basename
import warnings import warnings
import logging import logging
import requests import requests
from ._exception import FBchatException, FBchatFacebookError from ._exception import (
FBchatException,
FBchatFacebookError,
FBchatInvalidParameters,
FBchatNotLoggedIn,
FBchatPleaseRefresh,
)
try: try:
from urllib.parse import urlencode, parse_qs, urlparse from urllib.parse import urlencode, parse_qs, urlparse
@@ -107,51 +113,62 @@ def generateOfflineThreadingID():
return str(int(msgs, 2)) return str(int(msgs, 2))
def check_json(j): def handle_payload_error(j):
if hasattr(j.get("payload"), "get") and j["payload"].get("error"): if "error" not in j:
raise FBchatFacebookError( return
"Error when sending request: {}".format(j["payload"]["error"]), error = j["error"]
fb_error_code=None, if j["error"] == 1357001:
fb_error_message=j["payload"]["error"], error_cls = FBchatNotLoggedIn
) elif j["error"] == 1357004:
elif j.get("error"): error_cls = FBchatPleaseRefresh
if "errorDescription" in j: elif j["error"] in (1357031, 1545010, 1545003):
# 'errorDescription' is in the users own language! error_cls = FBchatInvalidParameters
raise FBchatFacebookError( else:
"Error #{} when sending request: {}".format( error_cls = FBchatFacebookError
j["error"], j["errorDescription"] # TODO: Use j["errorSummary"]
), # "errorDescription" is in the users own language!
fb_error_code=j["error"], raise error_cls(
"Error #{} when sending request: {}".format(error, j["errorDescription"]),
fb_error_code=error,
fb_error_message=j["errorDescription"], fb_error_message=j["errorDescription"],
) )
elif "debug_info" in j["error"] and "code" in j["error"]:
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( raise FBchatFacebookError(
"Error #{} when sending request: {}".format( "GraphQL error #{}: {} / {!r}".format(
j["error"]["code"], repr(j["error"]["debug_info"]) error.get("code"), error.get("message"), error.get("debug_info")
), ),
fb_error_code=j["error"]["code"], fb_error_code=error.get("code"),
fb_error_message=j["error"]["debug_info"], fb_error_message=error.get("message"),
)
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) check_http_code(r.status_code)
content = get_decoded_r(r) content = get_decoded_r(r)
check_content(content) check_content(content)
return to_json(content) if as_json else content return content
def check_http_code(code): def check_http_code(code):
if 400 <= code < 600: msg = "Error when sending request: Got {} response.".format(code)
if code == 404:
raise FBchatFacebookError( 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, request_status_code=code,
) )
if 400 <= code < 600:
raise FBchatFacebookError(msg, request_status_code=code)
def check_content(content, as_json=True): def check_content(content, as_json=True):
@@ -162,7 +179,6 @@ def check_content(content, as_json=True):
def to_json(content): def to_json(content):
content = strip_json_cruft(content) content = strip_json_cruft(content)
j = parse_json(content) j = parse_json(content)
check_json(j)
log.debug(j) log.debug(j)
return j return j