From 3c79443a8d8546b1af2c2f3f9c815251d0330966 Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Sat, 28 Sep 2024 17:11:36 +0900 Subject: [PATCH 1/2] add page edit func --- pyproject.toml | 2 +- src/wikidot/common/exceptions.py | 7 ++ src/wikidot/module/page.py | 115 +++++++++++++++++++++++++++---- src/wikidot/module/site.py | 33 +++++++++ 4 files changed, 141 insertions(+), 16 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7a50c8c..f983e3b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "wikidot" -version = "3.1.0dev2" +version = "3.1.0dev3" authors = [{ name = "ukwhatn", email = "ukwhatn@gmail.com" }] description = "Wikidot Utility Library" readme = "README.md" diff --git a/src/wikidot/common/exceptions.py b/src/wikidot/common/exceptions.py index 8c2517e..4d8dae1 100644 --- a/src/wikidot/common/exceptions.py +++ b/src/wikidot/common/exceptions.py @@ -89,6 +89,13 @@ def __init__(self, message): super().__init__(message) +class TargetExistsException(WikidotException): + """対象が既に存在しているときの例外""" + + def __init__(self, message): + super().__init__(message) + + class TargetErrorException(WikidotException): """メソッドの対象としたオブジェクトに操作が適用できないときの例外""" diff --git a/src/wikidot/module/page.py b/src/wikidot/module/page.py index c027ed4..bbd9f7f 100644 --- a/src/wikidot/module/page.py +++ b/src/wikidot/module/page.py @@ -99,8 +99,8 @@ def _parse(site: "Site", html_body: BeautifulSoup): # レーティング方式を判定 is_5star_rating = ( - page_element.select_one("span.rating span.page-rate-list-pages-start") - is not None + page_element.select_one("span.rating span.page-rate-list-pages-start") + is not None ) # 各値を取得 @@ -179,17 +179,17 @@ def search_pages(site: "Site", query: SearchPagesQuery = SearchPagesQuery()): query_dict = query.as_dict() query_dict["moduleName"] = "list/ListPagesModule" query_dict["module_body"] = ( - '[[div class="page"]]\n' - + "".join( - [ - f'[[span class="set {key}"]]' - f'[[span class="name"]] {key} [[/span]]' - f'[[span class="value"]] %%{key}%% [[/span]]' - f"[[/span]]" - for key in DEFAULT_MODULE_BODY - ] - ) - + "\n[[/div]]" + '[[div class="page"]]\n' + + "".join( + [ + f'[[span class="set {key}"]]' + f'[[span class="name"]] {key} [[/span]]' + f'[[span class="value"]] %%{key}%% [[/span]]' + f"[[/span]]" + for key in DEFAULT_MODULE_BODY + ] + ) + + "\n[[/div]]" ) try: @@ -320,7 +320,7 @@ def _acquire_page_revisions(site: "Site", pages: list["Page"]): revs = [] body_html = BeautifulSoup(body, "lxml") for rev_element in body_html.select( - "table.page-history > tr[id^=revision-row-]" + "table.page-history > tr[id^=revision-row-]" ): rev_id = int(rev_element["id"].removeprefix("revision-row-")) @@ -545,7 +545,7 @@ def revisions(self) -> PageRevisionCollection["PageRevision"]: @revisions.setter def revisions( - self, value: list["PageRevision"] | PageRevisionCollection["PageRevision"] + self, value: list["PageRevision"] | PageRevisionCollection["PageRevision"] ): self._revisions = value @@ -629,3 +629,88 @@ def delete_meta(self, name: str): } ] ) + + @staticmethod + def create_or_edit( + site: "Site", + fullname: str, + page_id: int | None = None, + title: str = "", + source: str = "", + comment: str = "", + force_edit: bool = False, + raise_on_exists: bool = False, + ): + site.client.login_check() + + # ページロックを取得しにいく + page_lock_request_body = { + "mode": "page", + "wiki_page": fullname, + "moduleName": "edit/PageEditModule" + } + if force_edit: + page_lock_request_body["force_lock"] = "yes" + + page_lock_response = site.amc_request([page_lock_request_body])[0] + page_lock_response_data = page_lock_response.json() + + if "locked" in page_lock_response_data or "other_locks" in page_lock_response_data: + raise exceptions.TargetErrorException( + f"Page {fullname} is locked or other locks exist", + ) + + # ページが存在するか(page_revision_idがあるか)確認 + is_exist = "page_revision_id" in page_lock_response_data + + if raise_on_exists and is_exist: + raise exceptions.TargetExistsException(f"Page {fullname} already exists") + + if is_exist and page_id is None: + raise ValueError("page_id must be specified when editing existing page") + + # lock_idとlock_secret、page_revision_id(あれば)を取得 + lock_id = page_lock_response_data["lock_id"] + lock_secret = page_lock_response_data["lock_secret"] + page_revision_id = page_lock_response_data.get("page_revision_id") + + # ページの作成または編集 + edit_request_body = { + "action": "WikiPageAction", + "event": "savePage", + "moduleName": "Empty", + "mode": "page", + "lock_id": lock_id, + "lock_secret": lock_secret, + "revision_id": page_revision_id if page_revision_id is not None else "", + "wiki_page": fullname, + "page_id": page_id if page_id is not None else "", + "title": title, + "source": source, + "comments": comment + } + response = site.amc_request([edit_request_body])[0] + + return response.json() + + def edit( + self, + title: str = None, + source: str = None, + comment: str = None, + force_edit: bool = False, + ): + # Noneならそのままにする + title = title or self.title + source = source or self.source.wiki_text + comment = comment or "" + + return Page.create_or_edit( + self.site, + self.fullname, + self.id, + title, + source, + comment, + force_edit, + ) diff --git a/src/wikidot/module/site.py b/src/wikidot/module/site.py index 0b6515d..834a3b9 100644 --- a/src/wikidot/module/site.py +++ b/src/wikidot/module/site.py @@ -68,6 +68,39 @@ def get(self, fullname: str, raise_when_not_found: bool = True) -> Optional["Pag return None return res[0] + def create( + self, + fullname: str, + title: str = "", + source: str = "", + comment: str = "", + force_edit: bool = False, + ) -> None: + """ページを作成する + + Parameters + ---------- + fullname: str + ページのフルネーム + title: str + ページのタイトル + source: str + ページのソース + comment: str + コメント + force_edit: bool + ページが存在する場合に上書きするかどうか + """ + Page.create_or_edit( + site=self.site, + fullname=fullname, + title=title, + source=source, + comment=comment, + force_edit=force_edit, + raise_on_exists=True + ) + @dataclass class Site: From c88274507cb642e3c919238f35173f8b75bd5be4 Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Sat, 28 Sep 2024 17:12:03 +0900 Subject: [PATCH 2/2] release: 3.1.0dev3