diff --git a/notifier/deletions.py b/notifier/deletions.py index 912094c..7d00d2a 100644 --- a/notifier/deletions.py +++ b/notifier/deletions.py @@ -170,6 +170,41 @@ def rename_invalid_user_config_pages( continue +def rename_misnamed_user_config_pages( + local_config: LocalConfig, connection: Connection +) -> None: + """Renames user configs to fix an error in the page creation link. + + The link syntax breaks if it contains a space, but spaces are permitted in usernames. Therefore if a user has a space in their username, their user config create link will be broken and will have a nonsensical name. + + Users are allowed to name their pages whatever they like but this cleans up that one case specifically. + """ + logger.info("Finding user configs from users with spaces in their name") + misnamed_configs = [ + (slug, config) + for slug, config in fetch_user_configs(local_config, connection) + if " " in config["username"] + and config["title"] == config["username"].split(" ")[0] + ] + logger.debug( + "Found misnamed configs to rename %s", {"count": len(misnamed_configs)} + ) + for slug, config in misnamed_configs: + try: + connection.rename_page( + local_config["config_wiki"], + slug, + f"{local_config['user_config_category']}:{config['user_id']}", + ) + except Exception as error: + logger.error( + "Couldn't rename config page %s", + {"slug": slug}, + exc_info=error, + ) + continue + + def delete_prepared_invalid_user_pages( local_config: LocalConfig, wikidot: Wikidot ) -> None: diff --git a/notifier/wikidot.py b/notifier/wikidot.py index 18c5e5f..90e7b11 100644 --- a/notifier/wikidot.py +++ b/notifier/wikidot.py @@ -1,3 +1,5 @@ +from contextlib import contextmanager +from functools import lru_cache import logging import re import time @@ -63,6 +65,10 @@ class OngoingConnectionError(Exception): by trying it multiple times.""" +class LockNotEstablished(Exception): + """The requested edit lock could not be established.""" + + class Wikidot: """Connection to Wikidot facilitating communications with it.""" @@ -398,6 +404,7 @@ def get_contacts(self) -> EmailAddresses: addresses[username.strip()] = address return addresses + @lru_cache(maxsize=1) def get_page_id(self, wiki_id: str, slug: str) -> int: """Get a page's ID from its source.""" try: @@ -418,8 +425,80 @@ def get_page_id(self, wiki_id: str, slug: str) -> int: cast(Match[str], re.search(r"pageId = ([0-9]+);", page)).group(1) ) + @contextmanager + def edit_lock(self, wiki_id: str, slug: str) -> Iterator[Tuple[str, str]]: + """Establishes an edit lock to commit an edit to a page. + + Exposes the lock id and secret to be used in an edit request. + + Connection needs to be logged in. + """ + logger.debug( + "Establishing edit lock %s", {"wiki_id": wiki_id, "slug": slug} + ) + page_id = self.get_page_id(wiki_id, slug) + response = self.module( + wiki_id, + "edit/PageEditModule", + page_id=str(page_id), + wiki_page=slug, + mode="page", + ) + lock_id = str(response.get("lock_id")) + lock_secret = str(response.get("lock_secret")) + if response.get("locked", False) or not lock_id or not lock_secret: + logger.debug( + "Failed to acquire edit lock %s", + {"wiki_id": wiki_id, "slug": slug}, + ) + raise LockNotEstablished + try: + yield lock_id, lock_secret + except: + # If the edit fails, release the lock + logger.debug( + "Releasing edit lock %s", {"wiki_id": wiki_id, "slug": slug} + ) + self.module( + wiki_id, + "Empty", + action="WikiPageAction", + event="removePageEditLock", + page_id="1453558361", + wiki_page=slug, + lock_id=lock_id, + lock_secret=lock_secret, + leave_draft="false", + ) + raise + + def edit_page_title(self, wiki_id: str, slug: str, title: str) -> None: + """Changes the title of a page. + + Connection needs to be logged in. + """ + page_id = self.get_page_id(wiki_id, slug) + with + logger.debug( + "Renaming page %s", + { + "with id": page_id, + "from slug": slug, + "to slug": to_slug, + "in wiki": wiki_id, + }, + ) + self.module( + wiki_id, + "Empty", + action="WikiPageAction", + event="renamePage", + page_id=str(page_id), + new_name=to_slug, + ) + def rename_page(self, wiki_id: str, from_slug: str, to_slug: str) -> None: - """Renames a page. + """Renames (i.e. moves) a page. Renames can take a while (30+ seconds) to take effect, so if renaming for safe deletion, probably don't bother deleting until @@ -449,6 +528,8 @@ def rename_page(self, wiki_id: str, from_slug: str, to_slug: str) -> None: def delete_page(self, wiki_id: str, slug: str) -> None: """Deletes a page. + Very rarely, a delete semi-fails and leaves the page in a corrupted state where it can no longer be interacted with. To avoid this affecting anything, rename the page to something random first. + Connection needs to be logged in. """ if not slug.startswith("deleted:"):