Просмотр исходного кода

Update AssertEdit behavior now that it's been merged into MW core.

Note: don't deploy until January 14.
tags/v0.2
Ben Kurtovic 11 лет назад
Родитель
Сommit
08af882841
3 измененных файлов: 47 добавлений и 100 удалений
  1. +14
    -13
      earwigbot/exceptions.py
  2. +7
    -79
      earwigbot/wiki/page.py
  3. +26
    -8
      earwigbot/wiki/site.py

+ 14
- 13
earwigbot/exceptions.py Просмотреть файл

@@ -36,13 +36,13 @@ This module contains all exceptions used by EarwigBot::
| +-- SQLError
+-- NoServiceError
+-- LoginError
+-- PermissionsError
+-- NamespaceNotFoundError
+-- PageNotFoundError
+-- InvalidPageError
+-- RedirectError
+-- UserNotFoundError
+-- EditError
| +-- PermissionsError
| +-- EditConflictError
| +-- NoContentError
| +-- ContentTooBigError
@@ -120,6 +120,19 @@ class LoginError(WikiToolsetError):
Raised by :py:meth:`Site._login <earwigbot.wiki.site.Site._login>`.
"""

class PermissionsError(WikiToolsetError):
"""A permissions error ocurred.

We tried to do something we don't have permission to, like trying to delete
a page as a non-admin, or trying to edit a page without login information
and AssertEdit enabled. This will also be raised if we have been blocked
from editing.

Raised by :py:meth:`Page.edit <earwigbot.wiki.page.Page.edit>`,
:py:meth:`Page.add_section <earwigbot.wiki.page.Page.add_section>`, and
other API methods depending on settings.
"""

class NamespaceNotFoundError(WikiToolsetError):
"""A requested namespace name or namespace ID does not exist.

@@ -164,18 +177,6 @@ class EditError(WikiToolsetError):
:py:meth:`Page.add_section <earwigbot.wiki.page.Page.add_section>`.
"""

class PermissionsError(EditError):
"""A permissions error ocurred while editing.

We tried to do something we don't have permission to, like trying to delete
a page as a non-admin, or trying to edit a page without login information
and AssertEdit enabled. This will also be raised if we have been blocked
from editing.

Raised by :py:meth:`Page.edit <earwigbot.wiki.page.Page.edit>` and
:py:meth:`Page.add_section <earwigbot.wiki.page.Page.add_section>`.
"""

class EditConflictError(EditError):
"""We gotten an edit conflict or a (rarer) delete/recreate conflict.



+ 7
- 79
earwigbot/wiki/page.py Просмотреть файл

@@ -308,6 +308,7 @@ class Page(CopyvioMixIn):
section, captcha_id, captcha_word)
else: # Make sure we have the right token:
params["token"] = self._token
self._token = None # Token now invalid

# Try the API query, catching most errors with our handler:
try:
@@ -324,13 +325,8 @@ class Page(CopyvioMixIn):
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)
# Otherwise, there was some kind of problem. Throw an exception:
raise exceptions.EditError(result["edit"])

def _build_edit_params(self, text, summary, minor, bot, force, section,
captcha_id, captcha_word):
@@ -371,95 +367,27 @@ class Page(CopyvioMixIn):
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"]:
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 ["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 == "blocked":
if tries > 0 or not all(self.site._login_info):
raise exceptions.PermissionsError(error.info)
else:
# Perhaps we are blocked from being logged-out? Try to log in:
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)

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_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)

@property
def site(self):
"""The page's corresponding Site object."""


+ 26
- 8
earwigbot/wiki/site.py Просмотреть файл

@@ -209,11 +209,13 @@ class Site(object):
args.append(key + "=" + val)
return "&".join(args)

def _api_query(self, params, tries=0, wait=5, ignore_maxlag=False):
def _api_query(self, params, tries=0, wait=5, ignore_maxlag=False,
ae_retry=True):
"""Do an API query with *params* as a dict of parameters.

See the documentation for :py:meth:`api_query` for full implementation
details.
details. *tries*, *wait*, and *ignore_maxlag* are for maxlag;
*ae_retry* is for AssertEdit.
"""
since_last_query = time() - self._last_query_time # Throttling support
if since_last_query < self._wait_between_queries:
@@ -247,7 +249,7 @@ class Site(object):
gzipper = GzipFile(fileobj=stream)
result = gzipper.read()

return self._handle_api_query_result(result, params, tries, wait)
return self._handle_api_result(result, params, tries, wait, ae_retry)

def _build_api_query(self, params, ignore_maxlag):
"""Given API query params, return the URL to query and POST data."""
@@ -257,7 +259,8 @@ class Site(object):

url = ''.join((self.url, self._script_path, "/api.php"))
params["format"] = "json" # This is the only format we understand
if self._assert_edit: # If requested, ensure that we're logged in
if self._assert_edit and params.get("action") != "login":
# If requested, ensure that we're logged in
params["assert"] = self._assert_edit
if self._maxlag and not ignore_maxlag:
# If requested, don't overload the servers:
@@ -266,7 +269,7 @@ class Site(object):
data = self._urlencode_utf8(params)
return url, data

def _handle_api_query_result(self, result, params, tries, wait):
def _handle_api_result(self, result, params, tries, wait, ae_retry):
"""Given the result of an API query, attempt to return useful data."""
try:
res = loads(result) # Try to parse as a JSON object
@@ -277,8 +280,8 @@ class Site(object):
try:
code = res["error"]["code"]
info = res["error"]["info"]
except (TypeError, KeyError): # Having these keys indicates a problem
return res # All is well; return the decoded JSON
except (TypeError, KeyError): # If there's no error code/info, return
return res

if code == "maxlag": # We've been throttled by the server
if tries >= self._max_retries:
@@ -288,7 +291,22 @@ class Site(object):
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=tries, wait=wait*2)
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):
# 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
tparams = {"action": "tokens", "type": params["action"]}
params["token"] = self._api_query(tparams, ae_retry=False)
return self._api_query(params, tries, wait, ae_retry=False)
if not all(self._login_info):
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 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))


Загрузка…
Отмена
Сохранить