From b1cf39ac64325bab5cebff37a88e774d2ff5e2b5 Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Wed, 24 Aug 2011 00:21:45 -0400 Subject: [PATCH] major improvements to editing; fixes, cleanup, support for AssertEdit is complete w/ logging in following a failed assertion; bugfixes --- bot/wiki/page.py | 115 ++++++++++++++++++++++++++++++++++++++++++------------- bot/wiki/site.py | 2 +- 2 files changed, 89 insertions(+), 28 deletions(-) diff --git a/bot/wiki/page.py b/bot/wiki/page.py index 812824b..19cfac3 100644 --- a/bot/wiki/page.py +++ b/bot/wiki/page.py @@ -2,7 +2,7 @@ from hashlib import md5 import re -from time import strftime +from time import gmtime, strftime from urllib import quote from wiki.exceptions import * @@ -182,7 +182,7 @@ class Page(object): except KeyError: pass else: - self._starttimestamp = strftime("%Y-%m-%dT%H:%M:%SZ") + self._starttimestamp = strftime("%Y-%m-%dT%H:%M:%SZ", gmtime()) # We've determined the namespace and talkpage status in __init__() # based on the title, but now we can be sure: @@ -225,37 +225,59 @@ class Page(object): 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 a page! + """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(). - If `params` is given, + 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 PermissionsError(e) - self._force_validity() # Weed these out before we get too far + + # Weed out invalid pages before we get too far: + self._force_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 SiteAPIError as error: - if not hasattr(error, code): - raise - result = self._handle_edit_exceptions(error, params, tries) - - # These attributes are now invalidated: - self._content = None - self._basetimestamp = None - - return result + 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 = 0 + 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 EditError(result["edit"]) + self._handle_assert_edit(assertion, params, tries) def _build_edit_params(self, text, summary, minor, bot, force, section, captcha_id, captcha_word): - """Something.""" + """Given some keyword arguments, build an API edit query string.""" hashed = md5(text).hexdigest() # Checksum to ensure text is correct params = {"action": "edit", "title": self._title, "text": text, "token": self._token, "summary": summary, "md5": hashed} @@ -271,40 +293,50 @@ class Page(object): params["notminor"] = "true" if bot: params["bot"] = "true" - if self._exists == 2: # Page does not already exist - params["recreate"] = "true" if not force: params["starttimestamp"] = self._starttimestamp if self._basetimestamp: params["basetimestamp"] = self._basetimestamp - if self._exists == 3: - # Page exists; don't re-create it by accident if it's deleted: - params["nocreate"] = "true" - else: + if self._exists == 2: # Page does not exist; don't edit if it already exists: params["createonly"] = "true" + else: + params["recreate"] = "true" return params - def _handle_edit_exceptions(self, error, params, tries): - """Something.""" + 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 PermissionsError(error.info) elif error.code in ["noedit-anon", "cantcreate-anon", "noimageredirect-anon"]: - if not all(self._site._login_info): # Insufficient login info + if not all(self._site._login_info): + # Insufficient login info: raise PermissionsError(error.info) - if self.tries == 0: # We have login info; try to login: + 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! + 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 LoginError(e) elif error.code in ["editconflict", "pagedeleted", "articleexists"]: + # These attributes are now invalidated: + self._content = None + self._basetimestamp = None + self._exists = 0 raise EditConflictError(error.info) elif error.code in ["emptypage", "emptynewsection"]: @@ -319,7 +351,36 @@ class Page(object): elif error.code == "filtered": raise FilteredError(error.info) - raise EditError(", ".join((error.code, error.info))) + raise EditError(": ".join((error.code, error.info))) + + 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 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 LoginError(e) + + elif assertion == "bot": + e = "AssertEdit: bot assertion failed; we don't have a bot flag!" + raise PermissionsError(e) + + # Unknown assertion, maybe "true", "false", or "exists": + e = "AssertEdit: assertion '{0}' failed.".format(assertion) + raise PermissionsError(e) def title(self, force=False): """Returns the Page's title, or pagename. diff --git a/bot/wiki/site.py b/bot/wiki/site.py index 0103b68..bca2807 100644 --- a/bot/wiki/site.py +++ b/bot/wiki/site.py @@ -162,7 +162,7 @@ class Site(object): try: code = res["error"]["code"] info = res["error"]["info"] - except KeyError: + except (TypeError, KeyError): return res if code == "maxlag":