| @@ -1,6 +1,8 @@ | |||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
| from hashlib import md5 | |||||
| import re | import re | ||||
| from time import strftime | |||||
| from urllib import quote | from urllib import quote | ||||
| from wiki.exceptions import * | from wiki.exceptions import * | ||||
| @@ -25,7 +27,9 @@ class Page(object): | |||||
| is_redirect -- returns True if the page is a redirect, else False | is_redirect -- returns True if the page is a redirect, else False | ||||
| toggle_talk -- returns a content page's talk page, or vice versa | toggle_talk -- returns a content page's talk page, or vice versa | ||||
| get -- returns page content | 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): | def __init__(self, site, title, follow_redirects=False): | ||||
| @@ -54,6 +58,11 @@ class Page(object): | |||||
| self._content = None | self._content = None | ||||
| self._creator = 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 | # Try to determine the page's namespace using our site's namespace | ||||
| # converter: | # converter: | ||||
| prefix = self._title.split(":", 1)[0] | prefix = self._title.split(":", 1)[0] | ||||
| @@ -124,16 +133,16 @@ class Page(object): | |||||
| """Loads various data from the API in a single query. | """Loads various data from the API in a single query. | ||||
| Loads self._title, ._exists, ._is_redirect, ._pageid, ._fullurl, | 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. | Assuming the API is sound, this should not raise any exceptions. | ||||
| """ | """ | ||||
| if result is None: | 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"} | "titles": self._title, "inprop": "protection|url"} | ||||
| result = self._site._api_query(params) | result = self._site._api_query(params) | ||||
| @@ -168,6 +177,13 @@ class Page(object): | |||||
| self._fullurl = res["fullurl"] | self._fullurl = res["fullurl"] | ||||
| self._protection = res["protection"] | 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__() | # We've determined the namespace and talkpage status in __init__() | ||||
| # based on the title, but now we can be sure: | # based on the title, but now we can be sure: | ||||
| self._namespace = res["ns"] | self._namespace = res["ns"] | ||||
| @@ -192,13 +208,13 @@ class Page(object): | |||||
| """ | """ | ||||
| if result is None: | if result is None: | ||||
| params = {"action": "query", "prop": "revisions", "rvlimit": 1, | params = {"action": "query", "prop": "revisions", "rvlimit": 1, | ||||
| "rvprop": "content", "titles": self._title} | |||||
| "rvprop": "content|timestamp", "titles": self._title} | |||||
| result = self._site._api_query(params) | result = self._site._api_query(params) | ||||
| res = result["query"]["pages"].values()[0] | res = result["query"]["pages"].values()[0] | ||||
| try: | try: | ||||
| content = res["revisions"][0]["*"] | |||||
| self._content = content | |||||
| self._content = res["revisions"][0]["*"] | |||||
| self._basetimestamp = res["revisions"][0]["timestamp"] | |||||
| except KeyError: | except KeyError: | ||||
| # This can only happen if the page was deleted since we last called | # This can only happen if the page was deleted since we last called | ||||
| # self._load_attributes(). In that case, some of our attributes are | # self._load_attributes(). In that case, some of our attributes are | ||||
| @@ -206,6 +222,25 @@ class Page(object): | |||||
| self._load_attributes() | self._load_attributes() | ||||
| self._force_existence() | 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): | def title(self, force=False): | ||||
| """Returns the Page's title, or pagename. | """Returns the Page's title, or pagename. | ||||
| @@ -394,9 +429,9 @@ class Page(object): | |||||
| if force or self._exists == 0: | if force or self._exists == 0: | ||||
| # Kill two birds with one stone by doing an API query for both our | # Kill two birds with one stone by doing an API query for both our | ||||
| # attributes and our page content: | # 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", | "prop": "info|revisions", "inprop": "protection|url", | ||||
| "titles": self._title} | |||||
| "intoken": "edit", "rvprop": "content|timestamp"} | |||||
| result = self._site._api_query(params) | result = self._site._api_query(params) | ||||
| self._load_attributes(result=result) | self._load_attributes(result=result) | ||||
| self._force_existence() | self._force_existence() | ||||
| @@ -438,3 +473,45 @@ class Page(object): | |||||
| except IndexError: | except IndexError: | ||||
| e = "The page does not appear to have a redirect target." | e = "The page does not appear to have a redirect target." | ||||
| raise RedirectError(e) | 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 | |||||