def _handle_edit_errors(self, error, params, tries): """If our edit fails due to some error, try to handle it. We'll either raise an appropriate exception (for example, if the page is protected), or we'll try to fix it (for example, if we can't edit due to being logged out, we'll try to log in). """ if error.code in [ "noedit", "cantcreate", "protectedtitle", "noimageredirect" ]: raise exceptions.PermissionsError(error.info) elif error.code in [ "noedit-anon", "cantcreate-anon", "noimageredirect-anon" ]: if not all(self.site._login_info): # Insufficient login info: raise exceptions.PermissionsError(error.info) if tries == 0: # We have login info; try to login: self.site._login(self.site._login_info) self._token = None # Need a new token; old one is invalid now return self._edit(params=params, tries=1) else: # We already tried to log in and failed! e = "Although we should be logged in, we are not. This may be a cookie problem or an odd bug." raise exceptions.LoginError(e) elif error.code in ["editconflict", "pagedeleted", "articleexists"]: # These attributes are now invalidated: self._content = None self._basetimestamp = None self._exists = self.PAGE_UNKNOWN raise exceptions.EditConflictError(error.info) elif error.code in ["emptypage", "emptynewsection"]: raise exceptions.NoContentError(error.info) elif error.code == "contenttoobig": raise exceptions.ContentTooBigError(error.info) elif error.code == "spamdetected": raise exceptions.SpamDetectedError(error.info) elif error.code == "filtered": raise exceptions.FilteredError(error.info) raise exceptions.EditError(": ".join((error.code, error.info)))
def _edit(self, params=None, text=None, summary=None, minor=None, bot=None, force=None, section=None, captcha_id=None, captcha_word=None, tries=0): """Edit the page! If *params* is given, we'll use it as our API query parameters. Otherwise, we'll build params using the given kwargs via _build_edit_params(). We'll then try to do the API query, and catch any errors the API raises in _handle_edit_errors(). We'll then throw these back as subclasses of EditError. """ # Try to get our edit token, and die if we can't: if not self._token: self._load_attributes() if not self._token: e = "You don't have permission to edit this page." raise exceptions.PermissionsError(e) # Weed out invalid pages before we get too far: self._assert_validity() # Build our API query string: if not params: params = self._build_edit_params(text, summary, minor, bot, force, section, captcha_id, captcha_word) else: # Make sure we have the right token: params["token"] = self._token # Try the API query, catching most errors with our handler: try: result = self.site.api_query(**params) except exceptions.APIError as error: if not hasattr(error, "code"): raise # We can only handle errors with a code attribute result = self._handle_edit_errors(error, params, tries) # If everything was successful, reset invalidated attributes: if result["edit"]["result"] == "Success": self._content = None self._basetimestamp = None self._exists = self.PAGE_UNKNOWN return # If we're here, then the edit failed. If it's because of AssertEdit, # handle that. Otherwise, die - something odd is going on: try: assertion = result["edit"]["assert"] except KeyError: raise exceptions.EditError(result["edit"]) self._handle_assert_edit(assertion, params, tries)
def _handle_edit_errors(self, error, params, retry=True): """If our edit fails due to some error, try to handle it. We'll either raise an appropriate exception (for example, if the page is protected), or we'll try to fix it (for example, if the token is invalid, we'll try to get a new one). """ perms = ["noedit", "noedit-anon", "cantcreate", "cantcreate-anon", "protectedtitle", "noimageredirect", "noimageredirect-anon", "blocked"] if error.code in perms: raise exceptions.PermissionsError(error.info) elif error.code in ["editconflict", "pagedeleted", "articleexists"]: # These attributes are now invalidated: self._content = None self._basetimestamp = None self._exists = self.PAGE_UNKNOWN raise exceptions.EditConflictError(error.info) elif error.code == "badtoken" and retry: params["token"] = self.site.get_token(force=True) try: return self.site.api_query(**params) except exceptions.APIError as err: if not hasattr(err, "code"): raise # We can only handle errors with a code attribute return self._handle_edit_errors(err, params, retry=False) elif error.code in ["emptypage", "emptynewsection"]: raise exceptions.NoContentError(error.info) elif error.code == "contenttoobig": raise exceptions.ContentTooBigError(error.info) elif error.code == "spamdetected": raise exceptions.SpamDetectedError(error.info) elif error.code == "filtered": raise exceptions.FilteredError(error.info) raise exceptions.EditError(": ".join((error.code, error.info)))
def _handle_api_result(self, response, params, tries, wait, ae_retry): """Given an API query response, attempt to return useful data.""" try: res = response.json() except ValueError: e = "API query failed: JSON could not be decoded." raise exceptions.APIError(e) if "warnings" in res: for name, value in res["warnings"].items(): try: warning = value["warnings"] except KeyError: try: warning = value["*"] except KeyError: warning = value self._logger.warning("API warning: %s: %s", name, warning) if self._should_save_cookiejar(): self._save_cookiejar() try: code = res["error"]["code"] info = res["error"]["info"] except (TypeError, KeyError): # If there's no error code/info, return if "query" in res and "tokens" in res["query"]: for name, token in res["query"]["tokens"].iteritems(): self._tokens[name.split("token")[0]] = token return res if code == "maxlag": # We've been throttled by the server if tries >= self._max_retries: e = "Maximum number of retries reached ({0})." raise exceptions.APIError(e.format(self._max_retries)) tries += 1 msg = 'Server says "{0}"; retrying in {1} seconds ({2}/{3})' self._logger.info(msg.format(info, wait, tries, self._max_retries)) sleep(wait) return self._api_query(params, tries, wait * 2, ae_retry=ae_retry) elif code in ["assertuserfailed", "assertbotfailed"]: # AssertEdit if ae_retry and all(self._login_info) and not self._oauth: # Try to log in if we got logged out: self._login(self._login_info) if "token" in params: # Fetch a new one; this is invalid now params["token"] = self.get_token(params["action"]) return self._api_query(params, tries, wait, ae_retry=False) if not all(self._login_info) and not self._oauth: e = "Assertion failed, and no login info was provided." elif code == "assertbotfailed": e = "Bot assertion failed: we don't have a bot flag!" else: e = "User assertion failed due to an unknown issue. Cookie or OAuth problem?" raise exceptions.PermissionsError("AssertEdit: " + e) else: # Some unknown error occurred e = 'API query failed: got error "{0}"; server says: "{1}".' error = exceptions.APIError(e.format(code, info)) error.code, error.info = code, info raise error
def _handle_assert_edit(self, assertion, params, tries): """If we can't edit due to a failed AssertEdit assertion, handle that. If the assertion was 'user' and we have valid login information, try to log in. Otherwise, raise PermissionsError with details. """ if assertion == "user": if not all(self.site._login_info): # Insufficient login info: e = "AssertEdit: user assertion failed, and no login info was provided." raise exceptions.PermissionsError(e) if tries == 0: # We have login info; try to login: self.site._login(self.site._login_info) self._token = None # Need a new token; old one is invalid now return self._edit(params=params, tries=1) else: # We already tried to log in and failed! e = "Although we should be logged in, we are not. This may be a cookie problem or an odd bug." raise exceptions.LoginError(e) elif assertion == "bot": if not all(self.site._login_info): # Insufficient login info: e = "AssertEdit: bot assertion failed, and no login info was provided." raise exceptions.PermissionsError(e) if tries == 0: # Try to log in if we got logged out: self.site._login(self.site._login_info) self._token = None # Need a new token; old one is invalid now return self._edit(params=params, tries=1) else: # We already tried to log in, so we don't have a bot flag: e = "AssertEdit: bot assertion failed: we don't have a bot flag!" raise exceptions.PermissionsError(e) # Unknown assertion, maybe "true", "false", or "exists": e = "AssertEdit: assertion '{0}' failed.".format(assertion) raise exceptions.PermissionsError(e)