From ff9f6323eb34ac9ce9fe02317d2af8944038b9ad Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Sun, 21 Aug 2011 15:32:39 -0400 Subject: [PATCH] I can has page edit support? --- bot/wiki/page.py | 101 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 89 insertions(+), 12 deletions(-) diff --git a/bot/wiki/page.py b/bot/wiki/page.py index 14139fe..62cba0d 100644 --- a/bot/wiki/page.py +++ b/bot/wiki/page.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- +from hashlib import md5 import re +from time import strftime from urllib import quote from wiki.exceptions import * @@ -25,7 +27,9 @@ class Page(object): is_redirect -- returns True if the page is a redirect, else False toggle_talk -- returns a content page's talk page, or vice versa get -- returns page content - get_redirect_target -- if the page is a redirect, returns its destination + get_redirect_target -- if the page is a redirect, returns its destination + edit -- replaces the page's content or creates a new page + add_section -- add a new section at the bottom of the page """ def __init__(self, site, title, follow_redirects=False): @@ -54,6 +58,11 @@ class Page(object): self._content = None self._creator = None + # Attributes used for editing/deleting/protecting/etc: + self._token = None + self._basetimestamp = None + self._starttimestamp = None + # Try to determine the page's namespace using our site's namespace # converter: prefix = self._title.split(":", 1)[0] @@ -124,16 +133,16 @@ class Page(object): """Loads various data from the API in a single query. Loads self._title, ._exists, ._is_redirect, ._pageid, ._fullurl, - ._protection, ._namespace, ._is_talkpage, ._creator, and ._lastrevid - using the API. It will do a query of its own unless `result` is - provided, in which case we'll pretend `result` is what the query - returned. + ._protection, ._namespace, ._is_talkpage, ._creator, ._lastrevid, + ._token, and ._starttimestamp using the API. It will do a query of + its own unless `result` is provided, in which case we'll pretend + `result` is what the query returned. Assuming the API is sound, this should not raise any exceptions. """ if result is None: - params = {"action": "query", "rvprop": "user", "rvdir": "newer", - "prop": "info|revisions", "rvlimit": 1, + params = {"action": "query", "rvprop": "user", "intoken": "edit", + "prop": "info|revisions", "rvlimit": 1, "rvdir": "newer", "titles": self._title, "inprop": "protection|url"} result = self._site._api_query(params) @@ -168,6 +177,13 @@ class Page(object): self._fullurl = res["fullurl"] self._protection = res["protection"] + try: + self._token = res["edittoken"] + except KeyError: + pass + else: + self._starttimestamp = strftime("%Y-%m-%dT%H:%M:%SZ") + # We've determined the namespace and talkpage status in __init__() # based on the title, but now we can be sure: self._namespace = res["ns"] @@ -192,13 +208,13 @@ class Page(object): """ if result is None: params = {"action": "query", "prop": "revisions", "rvlimit": 1, - "rvprop": "content", "titles": self._title} + "rvprop": "content|timestamp", "titles": self._title} result = self._site._api_query(params) res = result["query"]["pages"].values()[0] try: - content = res["revisions"][0]["*"] - self._content = content + self._content = res["revisions"][0]["*"] + self._basetimestamp = res["revisions"][0]["timestamp"] except KeyError: # This can only happen if the page was deleted since we last called # self._load_attributes(). In that case, some of our attributes are @@ -206,6 +222,25 @@ class Page(object): self._load_attributes() self._force_existence() + def _get_token(self): + """Tries to get an edit token for the page. + + This is actually the same as the delete and protect tokens, so we'll + use it for everything. Raises PermissionError if we're not allowed to + edit the page, otherwise sets self._token and self._starttimestamp. + """ + params = {"action": "query", "prop": "info", "intoken": "edit", + "titles": self._title} + result = self._site._api_query(params) + + try: + self._token = result["query"]["pages"].values()[0]["edittoken"] + except KeyError: + e = "You don't have permission to edit this page." + raise PermissionsError(e) + else: + self._starttimestamp = strftime("%Y-%m-%dT%H:%M:%SZ") + def title(self, force=False): """Returns the Page's title, or pagename. @@ -394,9 +429,9 @@ class Page(object): if force or self._exists == 0: # Kill two birds with one stone by doing an API query for both our # attributes and our page content: - params = {"action": "query", "rvprop": "content", "rvlimit": 1, + params = {"action": "query", "rvlimit": 1, "titles": self._title, "prop": "info|revisions", "inprop": "protection|url", - "titles": self._title} + "intoken": "edit", "rvprop": "content|timestamp"} result = self._site._api_query(params) self._load_attributes(result=result) self._force_existence() @@ -438,3 +473,45 @@ class Page(object): except IndexError: e = "The page does not appear to have a redirect target." raise RedirectError(e) + + def edit(self, text, summary, minor=False, bot=True, force=False): + """Replaces the page's content or creates a new page. + + `text` is the new page content, with `summary` as the edit summary. + If `minor` is True, the edit will be marked as minor. If `bot` is true, + the edit will be marked as a bot edit, but only if we actually have a + bot flag. + + Use `force` to ignore edit conflicts and page deletions/recreations + that occured between getting our edit token and editing our page. Be + careful with this! + """ + if not self._token: + self._get_token() + + hashed = md5(text).hexdigest() + + params = {"action": "edit", "title": self._title, "text": text, + "token": self._token, "summary": summary, "md5": hashed} + + if minor: + params["minor"] = "true" + else: + params["notminor"] = "true" + if bot: + params["bot"] = "true" + + if not force: + params["starttimestamp"] = self._starttimestamp + if self._basetimestamp: + params["basetimestamp"] = self._basetimestamp + else: + params["recreate"] = "true" + + result = self._site._api_query(params) + print result + + def add_section(self, text, title, minor=False, bot=True): + """ + """ + pass \ No newline at end of file