From 5aad3f8ad293adaa269ff4570abf8d08b6325519 Mon Sep 17 00:00:00 2001 From: CodingMan Date: Fri, 4 Jul 2025 12:00:11 +0800 Subject: [PATCH 01/12] feat: add confirmation prompt. --- setup.py | 2 +- src/auto_version.py | 62 +++++++++++++++++++------------- src/mcp_server.py | 4 +-- src/{basic_api.py => ptt_api.py} | 29 +++++++++++++++ 4 files changed, 70 insertions(+), 27 deletions(-) rename src/{basic_api.py => ptt_api.py} (93%) diff --git a/setup.py b/setup.py index c38c09a..08b1e3b 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ long_description_content_type="text/markdown", url="https://github.com/PyPtt/ptt_mcp_server", # 請替換成您的專案 URL package_dir={"": "src"}, - py_modules=["basic_api", "mcp_server", "utils", "_version"], + py_modules=["ptt_api", "mcp_server", "utils", "_version"], install_requires=[ "pyptt", "fastmcp", diff --git a/src/auto_version.py b/src/auto_version.py index c86fa09..c48df4e 100644 --- a/src/auto_version.py +++ b/src/auto_version.py @@ -1,4 +1,5 @@ import argparse +import time import requests @@ -7,30 +8,43 @@ def get_pypi_version(package_name: str, is_test: bool = True) -> str | None: base_url = "https://test.pypi.org/pypi/" if is_test else "https://pypi.org/pypi/" url = f"{base_url}{package_name}/json" - try: - response = requests.get(url) - response.raise_for_status() # Raise an exception for HTTP errors - data = response.json() - - versions = list(data["releases"].keys()) - # Sort versions to ensure we get the latest, including pre-releases - # This requires a proper version parsing library for robust sorting - # For simplicity, we'll assume lexicographical sort works for common cases - # A more robust solution would use packaging.version.parse - versions.sort( - key=lambda s: [int(u) if u.isdigit() else u for u in s.split(".")], - reverse=True, - ) - - if versions: - return versions[0] - return None - except requests.exceptions.RequestException as e: - print(f"Error fetching from PyPI API: {e}") - return None - except Exception as e: - print(f"An unexpected error occurred: {e}") - return None + max_retries = 3 + retry_delay_seconds = 2 + + for attempt in range(max_retries): + try: + response = requests.get(url) + response.raise_for_status() # Raise an exception for HTTP errors + data = response.json() + + versions = list(data["releases"].keys()) + # Sort versions to ensure we get the latest, including pre-releases + # This requires a proper version parsing library for robust sorting + # For simplicity, we'll assume lexicographical sort works for common cases + # A more robust solution would use packaging.version.parse + versions.sort( + key=lambda s: [int(u) if u.isdigit() else u for u in s.split(".")], + reverse=True, + ) + + if versions: + return versions[0] + return None + except requests.exceptions.RequestException as e: + print(f"Attempt {attempt + 1} failed: Error fetching from PyPI API: {e}") + if attempt < max_retries - 1: + time.sleep(retry_delay_seconds) + else: + print("Max retries reached. Could not fetch PyPI version.") + return None + except Exception as e: + print(f"Attempt {attempt + 1} failed: An unexpected error occurred: {e}") + if attempt < max_retries - 1: + time.sleep(retry_delay_seconds) + else: + print("Max retries reached. Could not fetch PyPI version.") + return None + return None # Should not be reached def main(): diff --git a/src/mcp_server.py b/src/mcp_server.py index 0cd7368..2c1835a 100644 --- a/src/mcp_server.py +++ b/src/mcp_server.py @@ -4,7 +4,7 @@ from dotenv import load_dotenv from fastmcp import FastMCP -import basic_api +import ptt_api from _version import __version__ load_dotenv(override=True) @@ -21,7 +21,7 @@ def main(): - basic_api.register_tools(mcp, MEMORY_STORAGE, __version__) + ptt_api.register_tools(mcp, MEMORY_STORAGE, __version__) mcp.run() diff --git a/src/basic_api.py b/src/ptt_api.py similarity index 93% rename from src/basic_api.py rename to src/ptt_api.py index f13cbba..aaa07ae 100644 --- a/src/basic_api.py +++ b/src/ptt_api.py @@ -184,6 +184,8 @@ def post( ) -> Dict[str, Any]: """到看板發佈文章。 + 執行前務必顯示內容並與使用者確認後才可以執行。(Must display content and confirm with the user before execution.) + 必須先登入 PTT。 Args: @@ -222,6 +224,9 @@ def reply_post( ) -> Dict[str, Any]: """到看板回覆文章。 + 重要!務必遵守!執行前務必顯示內容並與使用者確認後才可以執行。 + Important! Be sure to follow! Must display content and confirm with the user before execution. + 必須先登入 PTT。 Args: @@ -256,6 +261,9 @@ def del_post( ) -> Dict[str, Any]: """刪除文章。 + 重要!務必遵守!執行前務必顯示內容並與使用者確認後才可以執行。 + Important! Be sure to follow! Must display content and confirm with the user before execution. + 必須先登入 PTT。 Args: @@ -287,6 +295,9 @@ def comment( ) -> Dict[str, Any]: """對文章進行推文、噓文或箭頭。 + 重要!務必遵守!執行前務必顯示內容並與使用者確認後才可以執行。 + Important! Be sure to follow! Must display content and confirm with the user before execution. + 必須先登入 PTT。 Args: @@ -318,6 +329,9 @@ def mail( ) -> Dict[str, Any]: """寄送站內信。 + 重要!務必遵守!執行前務必顯示內容並與使用者確認後才可以執行。 + Important! Be sure to follow! Must display content and confirm with the user before execution. + 必須先登入 PTT。 Args: @@ -392,6 +406,9 @@ def get_mail( def del_mail(index: int) -> Dict[str, Any]: """刪除信件。 + 重要!務必遵守!執行前務必顯示內容並與使用者確認後才可以執行。 + Important! Be sure to follow! Must display content and confirm with the user before execution. + 必須先登入 PTT。 Args: @@ -415,6 +432,9 @@ def give_money( ) -> Dict[str, Any]: """轉帳 Ptt 幣給指定使用者。 + 重要!務必遵守!執行前務必顯示內容並與使用者確認後才可以執行。 + Important! Be sure to follow! Must display content and confirm with the user before execution. + 必須先登入 PTT。 Args: @@ -500,6 +520,9 @@ def search_user( def change_pw(new_password: str) -> Dict[str, Any]: """更改 PTT 登入密碼。 + 重要!務必遵守!執行前務必顯示內容並與使用者確認後才可以執行。 + Important! Be sure to follow! Must display content and confirm with the user before execution. + 必須先登入 PTT。 Args: @@ -651,6 +674,9 @@ def get_bottom_post_list(board: str) -> Dict[str, Any]: def set_board_title(board: str, new_title: str) -> Dict[str, Any]: """設定看板標題。 + 重要!務必遵守!執行前務必顯示內容並與使用者確認後才可以執行。 + Important! Be sure to follow! Must display content and confirm with the user before execution. + 必須先登入 PTT,且登入帳號需為該看板板主。 Args: @@ -676,6 +702,9 @@ def bucket( ) -> Dict[str, Any]: """將指定使用者水桶。 + 重要!務必遵守!執行前務必顯示內容並與使用者確認後才可以執行。 + Important! Be sure to follow! Must display content and confirm with the user before execution. + 必須先登入 PTT,且登入帳號需為該看板板主。 Args: From 7eb66f73df64fc920117e17ff48e0790555c6c3e Mon Sep 17 00:00:00 2001 From: CodingMan Date: Fri, 4 Jul 2025 12:03:57 +0800 Subject: [PATCH 02/12] refactor: remove black. useless and noisy. --- .github/workflows/code_quality.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml index 50a2ef8..8ef50d1 100644 --- a/.github/workflows/code_quality.yml +++ b/.github/workflows/code_quality.yml @@ -19,8 +19,6 @@ jobs: python -m pip install --upgrade pip pip install -r requirements.txt pip install -r requirements-dev.txt - - name: Run Black (Formatting Check) - run: black --check . - name: Run Flake8 (Linting Check) run: flake8 . - name: Run MyPy (Type Check) From 7be441b3ab0befbaa8ff613b62dba564a88c6447 Mon Sep 17 00:00:00 2001 From: CodingMan Date: Fri, 4 Jul 2025 12:03:57 +0800 Subject: [PATCH 03/12] refactor: remove black. useless and noisy. --- .github/workflows/code_quality.yml | 2 -- src/auto_version.py | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml index 50a2ef8..8ef50d1 100644 --- a/.github/workflows/code_quality.yml +++ b/.github/workflows/code_quality.yml @@ -19,8 +19,6 @@ jobs: python -m pip install --upgrade pip pip install -r requirements.txt pip install -r requirements-dev.txt - - name: Run Black (Formatting Check) - run: black --check . - name: Run Flake8 (Linting Check) run: flake8 . - name: Run MyPy (Type Check) diff --git a/src/auto_version.py b/src/auto_version.py index c48df4e..15dd0d1 100644 --- a/src/auto_version.py +++ b/src/auto_version.py @@ -44,7 +44,7 @@ def get_pypi_version(package_name: str, is_test: bool = True) -> str | None: else: print("Max retries reached. Could not fetch PyPI version.") return None - return None # Should not be reached + return None # Should not be reached def main(): @@ -67,8 +67,8 @@ def main(): next_num = str(int(latest_pypi_version.split("dev")[-1]) + 1) cur_version = latest_pypi_version[ - : latest_pypi_version.find("dev") + 3 - ] + str(next_num) + : latest_pypi_version.find("dev") + 3 + ] + str(next_num) else: next_num = str(int(latest_pypi_version.split(".")[-1]) + 1) From 21bd9f6b8943472919daec67365dd29722efc193 Mon Sep 17 00:00:00 2001 From: CodingMan Date: Fri, 4 Jul 2025 13:57:13 +0800 Subject: [PATCH 04/12] chore: prompt enhance. --- src/ptt_api.py | 106 ++++++++++++++++++++++++++----------------------- 1 file changed, 56 insertions(+), 50 deletions(-) diff --git a/src/ptt_api.py b/src/ptt_api.py index aaa07ae..1bf3ae8 100644 --- a/src/ptt_api.py +++ b/src/ptt_api.py @@ -96,15 +96,15 @@ def login() -> Dict[str, Any]: @mcp.tool() def get_post( - board: str, - aid: Optional[str] = None, - index: int = 0, - query: bool = False, - search_list: Optional[List[Tuple[str, str]]] = None, + board: str, + aid: Optional[str] = None, + index: int = 0, + query: bool = False, + search_list: Optional[List[Tuple[str, str]]] = None, ) -> Dict[str, Any]: """從 PTT 取得指定文章。 - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Args: board (str): 文章所在的看板名稱。 @@ -147,13 +147,19 @@ def get_post( @mcp.tool() def get_newest_index( - index_type: str, - board: Optional[str] = None, - search_list: Optional[List[Tuple[str, str]]] = None, + index_type: str, + board: Optional[str] = None, + search_list: Optional[List[Tuple[str, str]]] = None, ) -> Dict[str, Any]: """取得最新文章或信箱編號。 + 函式回傳的 newest_index 代表的是該類型 (看板文章或信箱信件) 的最大有效編號。 + 例如,如果您呼叫 get_newest_index(index_type="BOARD", board="Test") 並得到 - 必須先登入 PTT。 + {'success': True, 'newest_index': 100} + + ,這表示在 'Test' 看板中,文章索引從 1 到 100 都是可用的。 + + 註記:此函式必須先登入 PTT。 Args: index_type (str): 編號類型,可為 "BOARD" (看板文章) 或 "MAIL" (信箱信件)。 @@ -180,13 +186,13 @@ def get_newest_index( @mcp.tool() def post( - board: str, title_index: int, title: str, content: str, sign_file: str = "0" + board: str, title_index: int, title: str, content: str, sign_file: str = "0" ) -> Dict[str, Any]: """到看板發佈文章。 執行前務必顯示內容並與使用者確認後才可以執行。(Must display content and confirm with the user before execution.) - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Args: board (str): 需要發文的看板名稱。 @@ -215,19 +221,19 @@ def post( @mcp.tool() def reply_post( - board: str, - reply_to: str, - content: str, - aid: Optional[str] = None, - index: int = 0, - sign_file: str = "0", + board: str, + reply_to: str, + content: str, + aid: Optional[str] = None, + index: int = 0, + sign_file: str = "0", ) -> Dict[str, Any]: """到看板回覆文章。 重要!務必遵守!執行前務必顯示內容並與使用者確認後才可以執行。 Important! Be sure to follow! Must display content and confirm with the user before execution. - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Args: reply_to (str): 回覆目標,可為 "BOARD" (回覆到看板)、"EMAIL" (回覆到信箱) 或 "BOARD_MAIL" (同時回覆到看板和信箱)。 @@ -257,14 +263,14 @@ def reply_post( @mcp.tool() def del_post( - board: str, aid: Optional[str] = None, index: int = 0 + board: str, aid: Optional[str] = None, index: int = 0 ) -> Dict[str, Any]: """刪除文章。 重要!務必遵守!執行前務必顯示內容並與使用者確認後才可以執行。 Important! Be sure to follow! Must display content and confirm with the user before execution. - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Args: board (str): 文章所在的看板名稱。 @@ -287,18 +293,18 @@ def del_post( @mcp.tool() def comment( - board: str, - comment_type: str, - content: str, - aid: Optional[str] = None, - index: int = 0, + board: str, + comment_type: str, + content: str, + aid: Optional[str] = None, + index: int = 0, ) -> Dict[str, Any]: """對文章進行推文、噓文或箭頭。 重要!務必遵守!執行前務必顯示內容並與使用者確認後才可以執行。 Important! Be sure to follow! Must display content and confirm with the user before execution. - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Args: board (str): 文章所在的看板名稱。 @@ -325,14 +331,14 @@ def comment( @mcp.tool() def mail( - ptt_id: str, title: str, content: str, sign_file: str = "0", backup: bool = True + ptt_id: str, title: str, content: str, sign_file: str = "0", backup: bool = True ) -> Dict[str, Any]: """寄送站內信。 重要!務必遵守!執行前務必顯示內容並與使用者確認後才可以執行。 Important! Be sure to follow! Must display content and confirm with the user before execution. - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Args: ptt_id (str): 收件人的 PTT ID。 @@ -360,14 +366,14 @@ def mail( @mcp.tool() def get_mail( - index: int, - search_type: Optional[str] = None, - search_condition: Optional[str] = None, - search_list: Optional[List[List[str]]] = None, + index: int, + search_type: Optional[str] = None, + search_condition: Optional[str] = None, + search_list: Optional[List[List[str]]] = None, ) -> Dict[str, Any]: """取得信件。 - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Args: index (int): 信件編號。 @@ -409,7 +415,7 @@ def del_mail(index: int) -> Dict[str, Any]: 重要!務必遵守!執行前務必顯示內容並與使用者確認後才可以執行。 Important! Be sure to follow! Must display content and confirm with the user before execution. - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Args: index (int): 信件編號。 @@ -425,17 +431,17 @@ def del_mail(index: int) -> Dict[str, Any]: @mcp.tool() def give_money( - ptt_id: str, - money: int, - red_bag_title: Optional[str] = None, - red_bag_content: Optional[str] = None, + ptt_id: str, + money: int, + red_bag_title: Optional[str] = None, + red_bag_content: Optional[str] = None, ) -> Dict[str, Any]: """轉帳 Ptt 幣給指定使用者。 重要!務必遵守!執行前務必顯示內容並與使用者確認後才可以執行。 Important! Be sure to follow! Must display content and confirm with the user before execution. - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Args: ptt_id (str): 接收 Ptt 幣的使用者 ID。 @@ -462,7 +468,7 @@ def give_money( def get_user(user_id: str) -> Dict[str, Any]: """取得使用者資訊。 - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Args: user_id (str): 目標使用者的 PTT ID。 @@ -491,11 +497,11 @@ def get_user(user_id: str) -> Dict[str, Any]: @mcp.tool() def search_user( - ptt_id: str, min_page: Optional[int] = None, max_page: Optional[int] = None + ptt_id: str, min_page: Optional[int] = None, max_page: Optional[int] = None ) -> Dict[str, Any]: """搜尋使用者。 - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Args: ptt_id (str): 欲搜尋的 PTT ID 關鍵字。 @@ -523,7 +529,7 @@ def change_pw(new_password: str) -> Dict[str, Any]: 重要!務必遵守!執行前務必顯示內容並與使用者確認後才可以執行。 Important! Be sure to follow! Must display content and confirm with the user before execution. - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Args: new_password (str): 新密碼。密碼長度限制為 8 個字元。 @@ -544,7 +550,7 @@ def change_pw(new_password: str) -> Dict[str, Any]: def get_time() -> Dict[str, Any]: """取得 PTT 系統時間。 - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Returns: Dict[str, Any]: 一個包含 PTT 系統時間的字典,或是在失敗時回傳錯誤訊息。 @@ -557,7 +563,7 @@ def get_time() -> Dict[str, Any]: def get_all_boards() -> Dict[str, Any]: """取得 PTT 全站看板清單。 - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Returns: Dict[str, Any]: 一個包含看板清單的字典,或是在失敗時回傳錯誤訊息。 @@ -571,7 +577,7 @@ def get_all_boards() -> Dict[str, Any]: def get_favourite_boards() -> Dict[str, Any]: """取得我的最愛看板清單。 - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Returns: Dict[str, Any]: 一個包含收藏看板清單的字典,或是在失敗時回傳錯誤訊息。 @@ -588,7 +594,7 @@ def get_favourite_boards() -> Dict[str, Any]: def get_board_info(board: str, get_post_types: bool = False) -> Dict[str, Any]: """取得看板資訊。 - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Args: board (str): 看板名稱。 @@ -654,7 +660,7 @@ def get_aid_from_url(url: str) -> Dict[str, Any]: def get_bottom_post_list(board: str) -> Dict[str, Any]: """取得看板置底文章清單。 - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Args: board (str): 看板名稱。 @@ -698,7 +704,7 @@ def set_board_title(board: str, new_title: str) -> Dict[str, Any]: @mcp.tool() def bucket( - board: str, ptt_id: str, bucket_days: int, reason: str + board: str, ptt_id: str, bucket_days: int, reason: str ) -> Dict[str, Any]: """將指定使用者水桶。 From 34b66a1393ed9f0cc128271dd67d6770132b3831 Mon Sep 17 00:00:00 2001 From: CodingMan Date: Fri, 4 Jul 2025 13:57:13 +0800 Subject: [PATCH 05/12] chore: prompt enhance. --- src/ptt_api.py | 107 ++++++++++++++++++++++++++----------------------- 1 file changed, 57 insertions(+), 50 deletions(-) diff --git a/src/ptt_api.py b/src/ptt_api.py index aaa07ae..6bab1d0 100644 --- a/src/ptt_api.py +++ b/src/ptt_api.py @@ -96,15 +96,15 @@ def login() -> Dict[str, Any]: @mcp.tool() def get_post( - board: str, - aid: Optional[str] = None, - index: int = 0, - query: bool = False, - search_list: Optional[List[Tuple[str, str]]] = None, + board: str, + aid: Optional[str] = None, + index: int = 0, + query: bool = False, + search_list: Optional[List[Tuple[str, str]]] = None, ) -> Dict[str, Any]: """從 PTT 取得指定文章。 - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Args: board (str): 文章所在的看板名稱。 @@ -147,13 +147,20 @@ def get_post( @mcp.tool() def get_newest_index( - index_type: str, - board: Optional[str] = None, - search_list: Optional[List[Tuple[str, str]]] = None, + index_type: str, + board: Optional[str] = None, + search_list: Optional[List[Tuple[str, str]]] = None, ) -> Dict[str, Any]: """取得最新文章或信箱編號。 + 函式回傳的 newest_index 代表的是該類型 (看板文章或信箱信件) 的最大有效編號。 + 例如,如果您呼叫 get_newest_index(index_type="BOARD", board="Test") 並得到 - 必須先登入 PTT。 + {'success': True, 'newest_index': 100} + + ,這表示在 'Test' 看板中,文章索引從 1 到 100 都是可用的。 + 這個函式本身不會回傳一個包含所有可用索引的列表,而是提供一個上限值,讓您可以根據這個上限值來進行後續的操作,例如遍歷文章或信件。 + + 註記:此函式必須先登入 PTT。 Args: index_type (str): 編號類型,可為 "BOARD" (看板文章) 或 "MAIL" (信箱信件)。 @@ -180,13 +187,13 @@ def get_newest_index( @mcp.tool() def post( - board: str, title_index: int, title: str, content: str, sign_file: str = "0" + board: str, title_index: int, title: str, content: str, sign_file: str = "0" ) -> Dict[str, Any]: """到看板發佈文章。 執行前務必顯示內容並與使用者確認後才可以執行。(Must display content and confirm with the user before execution.) - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Args: board (str): 需要發文的看板名稱。 @@ -215,19 +222,19 @@ def post( @mcp.tool() def reply_post( - board: str, - reply_to: str, - content: str, - aid: Optional[str] = None, - index: int = 0, - sign_file: str = "0", + board: str, + reply_to: str, + content: str, + aid: Optional[str] = None, + index: int = 0, + sign_file: str = "0", ) -> Dict[str, Any]: """到看板回覆文章。 重要!務必遵守!執行前務必顯示內容並與使用者確認後才可以執行。 Important! Be sure to follow! Must display content and confirm with the user before execution. - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Args: reply_to (str): 回覆目標,可為 "BOARD" (回覆到看板)、"EMAIL" (回覆到信箱) 或 "BOARD_MAIL" (同時回覆到看板和信箱)。 @@ -257,14 +264,14 @@ def reply_post( @mcp.tool() def del_post( - board: str, aid: Optional[str] = None, index: int = 0 + board: str, aid: Optional[str] = None, index: int = 0 ) -> Dict[str, Any]: """刪除文章。 重要!務必遵守!執行前務必顯示內容並與使用者確認後才可以執行。 Important! Be sure to follow! Must display content and confirm with the user before execution. - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Args: board (str): 文章所在的看板名稱。 @@ -287,18 +294,18 @@ def del_post( @mcp.tool() def comment( - board: str, - comment_type: str, - content: str, - aid: Optional[str] = None, - index: int = 0, + board: str, + comment_type: str, + content: str, + aid: Optional[str] = None, + index: int = 0, ) -> Dict[str, Any]: """對文章進行推文、噓文或箭頭。 重要!務必遵守!執行前務必顯示內容並與使用者確認後才可以執行。 Important! Be sure to follow! Must display content and confirm with the user before execution. - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Args: board (str): 文章所在的看板名稱。 @@ -325,14 +332,14 @@ def comment( @mcp.tool() def mail( - ptt_id: str, title: str, content: str, sign_file: str = "0", backup: bool = True + ptt_id: str, title: str, content: str, sign_file: str = "0", backup: bool = True ) -> Dict[str, Any]: """寄送站內信。 重要!務必遵守!執行前務必顯示內容並與使用者確認後才可以執行。 Important! Be sure to follow! Must display content and confirm with the user before execution. - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Args: ptt_id (str): 收件人的 PTT ID。 @@ -360,14 +367,14 @@ def mail( @mcp.tool() def get_mail( - index: int, - search_type: Optional[str] = None, - search_condition: Optional[str] = None, - search_list: Optional[List[List[str]]] = None, + index: int, + search_type: Optional[str] = None, + search_condition: Optional[str] = None, + search_list: Optional[List[List[str]]] = None, ) -> Dict[str, Any]: """取得信件。 - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Args: index (int): 信件編號。 @@ -409,7 +416,7 @@ def del_mail(index: int) -> Dict[str, Any]: 重要!務必遵守!執行前務必顯示內容並與使用者確認後才可以執行。 Important! Be sure to follow! Must display content and confirm with the user before execution. - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Args: index (int): 信件編號。 @@ -425,17 +432,17 @@ def del_mail(index: int) -> Dict[str, Any]: @mcp.tool() def give_money( - ptt_id: str, - money: int, - red_bag_title: Optional[str] = None, - red_bag_content: Optional[str] = None, + ptt_id: str, + money: int, + red_bag_title: Optional[str] = None, + red_bag_content: Optional[str] = None, ) -> Dict[str, Any]: """轉帳 Ptt 幣給指定使用者。 重要!務必遵守!執行前務必顯示內容並與使用者確認後才可以執行。 Important! Be sure to follow! Must display content and confirm with the user before execution. - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Args: ptt_id (str): 接收 Ptt 幣的使用者 ID。 @@ -462,7 +469,7 @@ def give_money( def get_user(user_id: str) -> Dict[str, Any]: """取得使用者資訊。 - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Args: user_id (str): 目標使用者的 PTT ID。 @@ -491,11 +498,11 @@ def get_user(user_id: str) -> Dict[str, Any]: @mcp.tool() def search_user( - ptt_id: str, min_page: Optional[int] = None, max_page: Optional[int] = None + ptt_id: str, min_page: Optional[int] = None, max_page: Optional[int] = None ) -> Dict[str, Any]: """搜尋使用者。 - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Args: ptt_id (str): 欲搜尋的 PTT ID 關鍵字。 @@ -523,7 +530,7 @@ def change_pw(new_password: str) -> Dict[str, Any]: 重要!務必遵守!執行前務必顯示內容並與使用者確認後才可以執行。 Important! Be sure to follow! Must display content and confirm with the user before execution. - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Args: new_password (str): 新密碼。密碼長度限制為 8 個字元。 @@ -544,7 +551,7 @@ def change_pw(new_password: str) -> Dict[str, Any]: def get_time() -> Dict[str, Any]: """取得 PTT 系統時間。 - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Returns: Dict[str, Any]: 一個包含 PTT 系統時間的字典,或是在失敗時回傳錯誤訊息。 @@ -557,7 +564,7 @@ def get_time() -> Dict[str, Any]: def get_all_boards() -> Dict[str, Any]: """取得 PTT 全站看板清單。 - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Returns: Dict[str, Any]: 一個包含看板清單的字典,或是在失敗時回傳錯誤訊息。 @@ -571,7 +578,7 @@ def get_all_boards() -> Dict[str, Any]: def get_favourite_boards() -> Dict[str, Any]: """取得我的最愛看板清單。 - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Returns: Dict[str, Any]: 一個包含收藏看板清單的字典,或是在失敗時回傳錯誤訊息。 @@ -588,7 +595,7 @@ def get_favourite_boards() -> Dict[str, Any]: def get_board_info(board: str, get_post_types: bool = False) -> Dict[str, Any]: """取得看板資訊。 - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Args: board (str): 看板名稱。 @@ -654,7 +661,7 @@ def get_aid_from_url(url: str) -> Dict[str, Any]: def get_bottom_post_list(board: str) -> Dict[str, Any]: """取得看板置底文章清單。 - 必須先登入 PTT。 + 註記:此函式必須先登入 PTT。 Args: board (str): 看板名稱。 @@ -698,7 +705,7 @@ def set_board_title(board: str, new_title: str) -> Dict[str, Any]: @mcp.tool() def bucket( - board: str, ptt_id: str, bucket_days: int, reason: str + board: str, ptt_id: str, bucket_days: int, reason: str ) -> Dict[str, Any]: """將指定使用者水桶。 From d3c06d0cd535c2627082bc89943b41be76665468 Mon Sep 17 00:00:00 2001 From: CodingMan Date: Fri, 4 Jul 2025 14:27:02 +0800 Subject: [PATCH 06/12] doc: update setup.py. --- setup.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 08b1e3b..0359e72 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ version=version["__version__"], author="CodingMan", # 請替換成您的名字 author_email="pttcodingman@gmail.com", # 請替換成您的電子郵件 - description="A MCP server for PTT.", + description="The best MCP server for PTT.", long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/PyPtt/ptt_mcp_server", # 請替換成您的專案 URL @@ -27,7 +27,12 @@ "python-dotenv", ], classifiers=[ - "Programming Language :: Python :: 3", + "Development Status :: 4 - Beta", + + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", ], From fab4cd449df2ba7b66108c849f4d85833c5c0f30 Mon Sep 17 00:00:00 2001 From: CodingMan Date: Fri, 4 Jul 2025 15:00:52 +0800 Subject: [PATCH 07/12] doc: update readme. --- README.md | 6 +++--- README_ENG.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 05da8d2..ce682a3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[English Version](./README_ENG.md) +[English Version](https://github.com/PyPtt/ptt_mcp_server/blob/main/README_ENG.md)

PTT MCP Server

@@ -15,7 +15,7 @@ The best MCP server for Ptt. Proudly built by PyPtt< Downloads - + License

@@ -219,4 +219,4 @@ pip install ptt-mcp-server ## 📄 授權條款 (License) -本專案採用 [BSD 3-Clause License](./LICENSE) 授權。 +本專案採用 [BSD 3-Clause License](https://github.com/PyPtt/ptt_mcp_server/blob/main/LICENSE) 授權。 diff --git a/README_ENG.md b/README_ENG.md index 43c8eac..9934a02 100644 --- a/README_ENG.md +++ b/README_ENG.md @@ -1,4 +1,4 @@ -[Traditional Chinese Version](./README.md) +[Traditional Chinese Version](https://github.com/PyPtt/ptt_mcp_server/blob/main/README.md)

PTT MCP Server

@@ -15,7 +15,7 @@ The best MCP server for Ptt. Proudly built by the Py Downloads - + License

@@ -219,4 +219,4 @@ Contributions of any kind are welcome! Whether it's reporting an issue or submit ## 📄 License -This project is licensed under the [BSD 3-Clause License](./LICENSE). +This project is licensed under the [BSD 3-Clause License](https://github.com/PyPtt/ptt_mcp_server/blob/main//LICENSE). From dbc2f617b45b36beffd4309183a23ce84250886a Mon Sep 17 00:00:00 2001 From: CodingMan Date: Sat, 5 Jul 2025 13:53:22 +0800 Subject: [PATCH 08/12] doc: update readme. --- README.md | 4 ++++ README_ENG.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/README.md b/README.md index ce682a3..33e132c 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,10 @@ graph LR | **金融系統** | 查詢 P幣、轉帳 P幣 | ✅ | | **資訊查詢** | 查詢使用者資訊、查詢看板資訊 | ✅ | +## 免責聲明 (Disclaimer) + +PTT MCP server 僅為提供 AI 代為操作使用者批踢踢帳號的介面,使用者授權 AI 代為操作。使用本 MCP 伺服器操作您的 PTT 帳號,所有風險將由使用者本人承擔。本專案開發者不承擔任何因使用本伺服器而導致的損失或責任。 + ## 📋 環境需求 (Requirements) * Python 3.10 或更新版本。 diff --git a/README_ENG.md b/README_ENG.md index 9934a02..d722d69 100644 --- a/README_ENG.md +++ b/README_ENG.md @@ -93,6 +93,10 @@ graph LR | **Financial System** | Check P Coin balance, Transfer P Coins | ✅ | | **Information Query** | Query user info, Query board info | ✅ | +## Disclaimer + +The PTT MCP server serves solely as an interface for AI to operate the user's PTT account, with the user's authorization. All risks associated with using this MCP server to operate your PTT account are borne by the user. The developers of this project assume no liability or responsibility for any losses or damages incurred from the use of this server. + ## 📋 Requirements * Python 3.10 or newer. From 1fb634f096b274abd337b3e588829faf29e74b92 Mon Sep 17 00:00:00 2001 From: CodingMan Date: Sun, 6 Jul 2025 22:39:45 +0800 Subject: [PATCH 09/12] feat: add binary search logic. --- README.md | 14 +++++- README_ENG.md | 14 +++++- setup.py | 2 +- src/api_post.py | 89 ++++++++++++++++++++++++++++++++++ src/{ptt_api.py => api_ptt.py} | 3 +- src/mcp_server.py | 11 +++-- 6 files changed, 123 insertions(+), 10 deletions(-) create mode 100644 src/api_post.py rename src/{ptt_api.py => api_ptt.py} (99%) diff --git a/README.md b/README.md index 33e132c..a4800e6 100644 --- a/README.md +++ b/README.md @@ -93,9 +93,19 @@ graph LR | **金融系統** | 查詢 P幣、轉帳 P幣 | ✅ | | **資訊查詢** | 查詢使用者資訊、查詢看板資訊 | ✅ | -## 免責聲明 (Disclaimer) +## ⚠️ 重要建議與免責聲明 (Important Suggestion & Disclaimer) -PTT MCP server 僅為提供 AI 代為操作使用者批踢踢帳號的介面,使用者授權 AI 代為操作。使用本 MCP 伺服器操作您的 PTT 帳號,所有風險將由使用者本人承擔。本專案開發者不承擔任何因使用本伺服器而導致的損失或責任。 +本專案提供強大的 PTT 自動化操作能力,但請注意,所有操作皆基於您的授權,您將對所有操作的後果負全部責任。為了安全且有效地使用本工具,我們強烈建議您遵循以下最佳實踐: + +**最佳實踐:先讀後寫,確認再執行** + +在使用任何會修改 PTT 內容的功能(如發文、回文、寄信、推文等)之前,請務必先使用讀取功能來收集和確認資訊。 + +* **範例:** 與其直接下令「刪除違規文章」,不如先「列出所有違規文章」,在您審核列表確認無誤後,再執行刪除操作。 + +這個簡單的流程可以大幅降低因自動化操作失誤(例如:誤刪文章、發錯內容)而導致的風險。雖然 PTT MCP Server 已經加入執行前會提示您進行最終確認,但仍無法完全避免誤操作的可能。**請在送出前仔細核對內容!** + +請記住,任何因使用本伺服器而造成的損失或責任,本專案開發者概不負責。 ## 📋 環境需求 (Requirements) diff --git a/README_ENG.md b/README_ENG.md index d722d69..148b2ed 100644 --- a/README_ENG.md +++ b/README_ENG.md @@ -93,9 +93,19 @@ graph LR | **Financial System** | Check P Coin balance, Transfer P Coins | ✅ | | **Information Query** | Query user info, Query board info | ✅ | -## Disclaimer +## ⚠️ Important Suggestion & Disclaimer -The PTT MCP server serves solely as an interface for AI to operate the user's PTT account, with the user's authorization. All risks associated with using this MCP server to operate your PTT account are borne by the user. The developers of this project assume no liability or responsibility for any losses or damages incurred from the use of this server. +This project provides powerful PTT automation capabilities, but please note that all actions are based on your authorization, and you are fully responsible for the consequences of all operations. To use this tool safely and effectively, we strongly recommend following these best practices: + +**Best Practice: Read Before You Write, Confirm Before You Execute** + +Before using any function that modifies PTT content (such as posting, replying, sending mail, pushing comments, etc.), be sure to first use read functions to gather and confirm information. + +* **Example:** Instead of directly ordering to "delete violating posts," first "list all violating posts." After you have reviewed the list and confirmed its accuracy, then execute the deletion. + +This simple process can significantly reduce the risk of operational errors (e.g., accidental deletion of posts, sending incorrect content). Although the PTT MCP Server will prompt you for final confirmation before execution, it cannot completely prevent the possibility of incorrect operations. **Please carefully check the content before submitting!** + +Please remember that the developers of this project are not responsible for any loss or liability caused by the use of this server. ## 📋 Requirements diff --git a/setup.py b/setup.py index 0359e72..173a58c 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ long_description_content_type="text/markdown", url="https://github.com/PyPtt/ptt_mcp_server", # 請替換成您的專案 URL package_dir={"": "src"}, - py_modules=["ptt_api", "mcp_server", "utils", "_version"], + py_modules=[os.path.splitext(f)[0] for f in os.listdir("src") if f.endswith(".py") and f != "auto_version.py"], install_requires=[ "pyptt", "fastmcp", diff --git a/src/api_post.py b/src/api_post.py new file mode 100644 index 0000000..f7955da --- /dev/null +++ b/src/api_post.py @@ -0,0 +1,89 @@ + + +from typing import Dict, Any, Optional, List, Tuple + +import PyPtt +from fastmcp import FastMCP + +from utils import _call_ptt_service, _handle_ptt_exception + +def register_tools(mcp: FastMCP, memory_storage: Dict[str, Any], version: str): + @mcp.tool() + def get_post_index_range() -> Dict[str, Any]: + + """ + 取得 PTT 文章的索引範圍。 + + :return: Dict[str, Any]: 一個包含 prompt 的字典,prompts 欄位將會教導你如何完成目標。 + + """ + + prompt = """這個函式的目標是使用 PTT MCP server 的功能,找到在指定看板(board)和指定日期(target_date)下,所有文章的起始索引(start_index)和結束索引(end_index)。 + +可用工具函式: + +get_newest_index(board: str) -> int:取得看板最新的文章索引。 + +get_post(board: str, index: int) -> str:取得指定索引文章的日期字串(格式為 "M/DD",例如 "7/6")。如果文章不存在或無法取得日期,可能會失敗或回傳空值。你可以使用查詢模式更有效率。 + +執行計畫 +第 1 步:初始化 + +設定目標: 假設我們要找 board = "Gossiping" 在 target_date = "7/6" 的文章。 + +取得搜尋上界: 呼叫 get_newest_index(board="Gossiping") 取得最大索引,稱之為 max_index。 + +設定搜尋範圍: low = 1, high = max_index。 + +第 2 步:尋找 start_index (當天的第一篇文章) + +目標: 找到第一個日期為 "7/6" 的索引。 + +方法: 使用二元搜尋法。在迴圈中,你比較 get_post(board="Gossiping", index=mid) 的日期和 target_date。 + +如果 mid 的日期 早於 "7/6" (例如 "7/5"),表示 start_index 肯定在更右邊。所以更新 low = mid + 1。 + +如果 mid 的日期 等於或晚於 "7/6" (例如 "7/6" 或 "7/7"),表示 start_index 可能就是 mid,或者在更左邊。所以我們先把 mid 當作可能的答案 (ans = mid),然後繼續往左邊找看看有沒有更早的,更新 high = mid - 1。 + +範例流程: + +get_post(board="Gossiping", index=100) -> "7/5" => 太早了,往右找 (low 變大) + +get_post(board="Gossiping", index=200) -> "7/7" => 太晚了,200 可能是答案,但要往左找 (ans = 200, high = 199) + +get_post(board="Gossiping", index=150) -> "7/6" => 150 可能是答案,但要繼續往左找 (ans = 150, high = 149) + +結果: 整個迴圈結束後,ans 變數的值就是我們要的 start_index。 + +第 3 步:尋找 end_index (當天的最後一篇文章) + +目標: 找到最後一個日期為 "7/6" 的索引。 + +方法: 再次使用二元搜尋法(low 和 high 需要重設為 1 和 max_index)。 + +如果 mid 的日期 晚於 "7/6" (例如 "7/7"),表示 end_index 肯定在更左邊。所以更新 high = mid - 1。 + +如果 mid 的日期 等於或早於 "7/6" (例如 "7/6" 或 "7/5"),表示 end_index 可能就是 mid,或者在更右邊。所以我們先把 mid 當作可能的答案 (ans = mid),然後繼續往右邊找看看有沒有更晚的,更新 low = mid + 1。 + +範例流程: + +get_post(board="Gossiping", index=300) -> "7/7" => 太晚了,往左找 (high 變小) + +get_post(board="Gossiping", index=250) -> "7/6" => 250 可能是答案,但要繼續往右找 (ans = 250, low = 251) + +結果: 整個迴圈結束後,ans 變數的值就是我們要的 end_index。 + +第 4 步:驗證並回報 + +在找到 start_index 和 end_index 後,你必須做最後的驗證。呼叫 get_post(board="Gossiping", index=start_index) 和 get_post(board="Gossiping", index=end_index) 確認它們的日期都是 target_date ("7/6")。 + +如果驗證成功,且 start_index <= end_index,則回報:「在 Gossiping 板,日期 7/6 的文章索引範圍是從 start_index 到 end_index。」 + +如果找不到,或驗證失敗(例如,找到的 start_index 日期是 "7/7",代表當天根本沒文章),則回報:「在 Gossiping 板找不到日期 7/6 的任何文章。」""" + + return { + "success": False, + "message": f"請遵循 prompt", + "code": "FOLLOW_PROMPTS", + "prompts": prompt + } \ No newline at end of file diff --git a/src/ptt_api.py b/src/api_ptt.py similarity index 99% rename from src/ptt_api.py rename to src/api_ptt.py index 80b0cfe..0621c66 100644 --- a/src/ptt_api.py +++ b/src/api_ptt.py @@ -1,6 +1,6 @@ from typing import Dict, Any, Optional, List, Tuple -import PyPtt # type: ignore +import PyPtt from fastmcp import FastMCP from utils import _call_ptt_service, _handle_ptt_exception @@ -119,7 +119,6 @@ def get_post( query (bool): 是否為查詢模式。如果是需要文章代碼(AID)、文章網址、文章值多少 Ptt 幣、文章編號(index),就可以使用查詢模式,速度會快很多。 此模式不會包含文章內容。 - Returns: Dict[str, Any]: 一個包含文章資料的字典,或是在失敗時回傳錯誤訊息。 成功時,'data' 鍵包含文章詳細資訊,例如: diff --git a/src/mcp_server.py b/src/mcp_server.py index 2c1835a..27327bc 100644 --- a/src/mcp_server.py +++ b/src/mcp_server.py @@ -4,7 +4,8 @@ from dotenv import load_dotenv from fastmcp import FastMCP -import ptt_api +import api_post +import api_ptt from _version import __version__ load_dotenv(override=True) @@ -15,13 +16,17 @@ if not PTT_ID or not PTT_PW: raise ValueError("PTT_ID and PTT_PW environment variables must be set.") -mcp: FastMCP = FastMCP(f"Ptt MCP Server v{__version__}") +mcp: FastMCP = FastMCP( + f"Ptt MCP Server v{__version__}" +) MEMORY_STORAGE: Dict[str, Any] = {"ptt_bot": None, "ptt_id": PTT_ID, "ptt_pw": PTT_PW} def main(): - ptt_api.register_tools(mcp, MEMORY_STORAGE, __version__) + api_ptt.register_tools(mcp, MEMORY_STORAGE, __version__) + api_post.register_tools(mcp, MEMORY_STORAGE, __version__) + mcp.run() From 21526b2933256c8dc4c9f5b4ecd591eee69cc292 Mon Sep 17 00:00:00 2001 From: CodingMan Date: Mon, 7 Jul 2025 13:57:22 +0800 Subject: [PATCH 10/12] feat: add logic. --- src/api_post.py | 242 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 171 insertions(+), 71 deletions(-) diff --git a/src/api_post.py b/src/api_post.py index f7955da..ff2ff21 100644 --- a/src/api_post.py +++ b/src/api_post.py @@ -1,89 +1,189 @@ - - -from typing import Dict, Any, Optional, List, Tuple +from datetime import datetime +from typing import Dict, Any import PyPtt from fastmcp import FastMCP -from utils import _call_ptt_service, _handle_ptt_exception +from utils import _call_ptt_service + def register_tools(mcp: FastMCP, memory_storage: Dict[str, Any], version: str): @mcp.tool() - def get_post_index_range() -> Dict[str, Any]: - + def get_board_rules() -> Dict[str, Any]: """ - 取得 PTT 文章的索引範圍。 + 取得 PTT 看板規則。 - :return: Dict[str, Any]: 一個包含 prompt 的字典,prompts 欄位將會教導你如何完成目標。 + 註記:此函式必須先登入 PTT。 + Returns: + Dict[str, Any]: 包含操作結果的字典。 + 成功時: {'success': True, 'message': str, 'data': List[str]} + 失敗時: {'success': False, 'message': str, 'code': str, 'prompt': str} """ - prompt = """這個函式的目標是使用 PTT MCP server 的功能,找到在指定看板(board)和指定日期(target_date)下,所有文章的起始索引(start_index)和結束索引(end_index)。 - -可用工具函式: - -get_newest_index(board: str) -> int:取得看板最新的文章索引。 - -get_post(board: str, index: int) -> str:取得指定索引文章的日期字串(格式為 "M/DD",例如 "7/6")。如果文章不存在或無法取得日期,可能會失敗或回傳空值。你可以使用查詢模式更有效率。 - -執行計畫 -第 1 步:初始化 - -設定目標: 假設我們要找 board = "Gossiping" 在 target_date = "7/6" 的文章。 - -取得搜尋上界: 呼叫 get_newest_index(board="Gossiping") 取得最大索引,稱之為 max_index。 - -設定搜尋範圍: low = 1, high = max_index。 - -第 2 步:尋找 start_index (當天的第一篇文章) - -目標: 找到第一個日期為 "7/6" 的索引。 - -方法: 使用二元搜尋法。在迴圈中,你比較 get_post(board="Gossiping", index=mid) 的日期和 target_date。 - -如果 mid 的日期 早於 "7/6" (例如 "7/5"),表示 start_index 肯定在更右邊。所以更新 low = mid + 1。 - -如果 mid 的日期 等於或晚於 "7/6" (例如 "7/6" 或 "7/7"),表示 start_index 可能就是 mid,或者在更左邊。所以我們先把 mid 當作可能的答案 (ans = mid),然後繼續往左邊找看看有沒有更早的,更新 high = mid - 1。 - -範例流程: - -get_post(board="Gossiping", index=100) -> "7/5" => 太早了,往右找 (low 變大) - -get_post(board="Gossiping", index=200) -> "7/7" => 太晚了,200 可能是答案,但要往左找 (ans = 200, high = 199) - -get_post(board="Gossiping", index=150) -> "7/6" => 150 可能是答案,但要繼續往左找 (ans = 150, high = 149) - -結果: 整個迴圈結束後,ans 變數的值就是我們要的 start_index。 - -第 3 步:尋找 end_index (當天的最後一篇文章) - -目標: 找到最後一個日期為 "7/6" 的索引。 - -方法: 再次使用二元搜尋法(low 和 high 需要重設為 1 和 max_index)。 - -如果 mid 的日期 晚於 "7/6" (例如 "7/7"),表示 end_index 肯定在更左邊。所以更新 high = mid - 1。 - -如果 mid 的日期 等於或早於 "7/6" (例如 "7/6" 或 "7/5"),表示 end_index 可能就是 mid,或者在更右邊。所以我們先把 mid 當作可能的答案 (ans = mid),然後繼續往右邊找看看有沒有更晚的,更新 low = mid + 1。 - -範例流程: - -get_post(board="Gossiping", index=300) -> "7/7" => 太晚了,往左找 (high 變小) - -get_post(board="Gossiping", index=250) -> "7/6" => 250 可能是答案,但要繼續往右找 (ans = 250, low = 251) + prompt = """ + 請使用 get_bottom_post_list 來取得置底文章,因為板規通常都會置底。 + + 如果找不到版規,請使用 get_newest_index(index_type="BOARD", board=board, search_list=[("KEYWORD", "版規")]) 來取得有版規在標題中的文章列表。 + + 註記:版規可能叫做「版規」or 「板規」。 + """ -結果: 整個迴圈結束後,ans 變數的值就是我們要的 end_index。 + return { + 'success': False, + 'message': '請遵循提示。', + 'code': 'FOLLOW_PROMPT', + 'prompt': prompt + } -第 4 步:驗證並回報 + @mcp.tool() + def get_post_index_range(board: str, target_date: str) -> Dict[str, Any]: + """ + 取得 PTT 文章在指定看板和日期下的索引範圍。 -在找到 start_index 和 end_index 後,你必須做最後的驗證。呼叫 get_post(board="Gossiping", index=start_index) 和 get_post(board="Gossiping", index=end_index) 確認它們的日期都是 target_date ("7/6")。 + 註記:此函式必須先登入 PTT。 -如果驗證成功,且 start_index <= end_index,則回報:「在 Gossiping 板,日期 7/6 的文章索引範圍是從 start_index 到 end_index。」 + Args: + board (str): 看板名稱,例如 "Gossiping"。 + target_date (str): 目標日期字串,格式為 "YYYY/MM/DD",例如 "1987/09/06"。 -如果找不到,或驗證失敗(例如,找到的 start_index 日期是 "7/7",代表當天根本沒文章),則回報:「在 Gossiping 板找不到日期 7/6 的任何文章。」""" + Returns: + Dict[str, Any]: 包含操作結果的字典。 + 成功時: {'success': True, 'start_index': int, 'end_index': int} + 失敗時: {'success': False, 'message': str} + """ - return { - "success": False, - "message": f"請遵循 prompt", - "code": "FOLLOW_PROMPTS", - "prompts": prompt - } \ No newline at end of file + # Helper to parse and compare dates + def parse_date_str(date_str: str) -> datetime: + # Add a dummy year to make it a full date for comparison. + # Assuming all dates are within the current year for simplicity. + # For cross-year comparisons, more complex logic would be needed. + + if date_str.count('/') == 2: + return datetime.strptime(date_str, "%Y/%m/%d") + + current_year = datetime.now().year + return datetime.strptime(f"{current_year}/{date_str}", "%Y/%m/%d") + + try: + target_date = parse_date_str(target_date) + except ValueError: + return {"success": False, + "message": f"Invalid target_date_str format: {target_date}. Expected 'YYYY/MM/DD'."} + + # 1. Get the newest index for the board + newest_index_response = _call_ptt_service( + memory_storage, + "get_newest_index", + index_type=PyPtt.NewIndex.BOARD, + board=board, + ) + if not newest_index_response.get('success'): + return {"success": False, + "message": f"Failed to get newest index for board {board}: {newest_index_response.get('message')}"} + max_index = newest_index_response.get('data') + + if max_index is None or max_index < 1: + return {"success": False, "message": f"No posts found for board {board}."} + + # Binary search for start_index + start_index = -1 + low, high = 1, max_index + while low <= high: + mid = (low + high) // 2 + post_response = _call_ptt_service( + memory_storage, + "get_post", + board=board, + index=mid, + query=True, + ) + + if not post_response.get('success') or not post_response.get('data') or not post_response['data'].get( + 'list_date'): + # If post not found or date missing, try to narrow down the search + # This might happen for deleted posts or invalid indices. + # For simplicity, we'll assume it's an invalid index and try higher. + low = mid + 1 + continue + + post_date_str = post_response['data']['list_date'] + try: + post_date = parse_date_str(post_date_str) + except ValueError: + # If post date is malformed, skip and try higher + low = mid + 1 + continue + + if post_date < target_date: + low = mid + 1 + elif post_date == target_date: + start_index = mid + high = mid - 1 # Try to find an earlier one + else: # post_date > target_date + high = mid - 1 + + # Binary search for end_index + end_index = -1 + low, high = 1, max_index # Reset search range + while low <= high: + mid = (low + high) // 2 + post_response = _call_ptt_service( + memory_storage, + "get_post", + board=board, + index=mid, + query=True, + ) + + if not post_response.get('success') or not post_response.get('data') or not post_response['data'].get( + 'list_date'): + low = mid + 1 + continue + + post_date_str = post_response['data']['list_date'] + try: + post_date = parse_date_str(post_date_str) + except ValueError: + low = mid + 1 + continue + + if post_date > target_date: + high = mid - 1 + elif post_date == target_date: + end_index = mid + low = mid + 1 # Try to find a later one + else: # post_date < target_date + low = mid + 1 + + if start_index == -1 or end_index == -1 or start_index > end_index: + return {"success": False, "message": f"在 {board} 板找不到日期 {target_date} 的任何文章。"} + else: + # Final verification (as suggested in the prompt) + # Verify start_index + start_post_response = _call_ptt_service( + memory_storage, + "get_post", + board=board, + index=start_index, + query=True, + ) + if not start_post_response.get('success') or not start_post_response.get('data') or parse_date_str( + start_post_response['data'].get('list_date', '')) != target_date: + return {"success": False, + "message": f"在 {board} 板找不到日期 {target_date} 的任何文章 (start_index verification failed)."} + + # Verify end_index + end_post_response = _call_ptt_service( + memory_storage, + "get_post", + board=board, + index=end_index, + query=True, + ) + if not end_post_response.get('success') or not end_post_response.get('data') or parse_date_str( + end_post_response['data'].get('list_date', '')) != target_date: + return {"success": False, + "message": f"在 {board} 板找不到日期 {target_date} 的任何文章 (end_index verification failed)."} + + return {"success": True, "start_index": start_index, "end_index": end_index} From 2c626c1a6ae5d90e5f0b7b6b0c565b5d9587d558 Mon Sep 17 00:00:00 2001 From: CodingMan Date: Mon, 7 Jul 2025 14:20:53 +0800 Subject: [PATCH 11/12] chore: commit. --- README.md | 2 +- README_ENG.md | 14 +++------- pyproject.toml | 3 +++ run_code_quality.sh | 6 ++--- src/api_ptt.py | 64 ++++++++++++++++++++++----------------------- src/auto_version.py | 4 +-- src/mcp_server.py | 4 +-- src/utils.py | 2 +- 8 files changed, 47 insertions(+), 52 deletions(-) mode change 100644 => 100755 run_code_quality.sh diff --git a/README.md b/README.md index a4800e6..253873b 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ graph LR | **文章互動** | 推文、噓文、給予箭頭、回覆文章 | ✅ | | **信箱系統** | 讀取信件、傳送新信件、刪除信件 | ✅ | | **金融系統** | 查詢 P幣、轉帳 P幣 | ✅ | -| **資訊查詢** | 查詢使用者資訊、查詢看板資訊 | ✅ | +| **資訊查詢** | 查詢使用者資訊、查詢看板資訊、**取得文章索引範圍** | ✅ | ## ⚠️ 重要建議與免責聲明 (Important Suggestion & Disclaimer) diff --git a/README_ENG.md b/README_ENG.md index 148b2ed..7967d14 100644 --- a/README_ENG.md +++ b/README_ENG.md @@ -91,7 +91,7 @@ graph LR | **Post Interaction** | Push, Boo, Arrow, Reply to post | ✅ | | **Mail System** | Read mail, Send new mail, Delete mail | ✅ | | **Financial System** | Check P Coin balance, Transfer P Coins | ✅ | -| **Information Query** | Query user info, Query board info | ✅ | +| **Information Query** | Query user info, Query board info, **Get post index range** | ✅ | ## ⚠️ Important Suggestion & Disclaimer @@ -138,10 +138,7 @@ After installation, the `ptt-mcp-server` command should be available in your she "mcpServers": { "PTT": { "command": "ptt-mcp-server", - "env": { - "PTT_ID": "YOUR_PTT_ID", // Replace with your PTT ID - "PTT_PW": "YOUR_PTT_PW" // Replace with your PTT password - } + "env": { /* PTT_ID and PTT_PW should be set as environment variables */ } } } } @@ -159,10 +156,7 @@ If you use a Python virtual environment, or if the `command` cannot be executed "args": [ "/path/to/your/venv/bin/ptt-mcp-server" ], - "env": { - "PTT_ID": "YOUR_PTT_ID", // Replace with your PTT ID - "PTT_PW": "YOUR_PTT_PW" // Replace with your PTT password - } + "env": { /* PTT_ID and PTT_PW should be set as environment variables */ } } } } @@ -233,4 +227,4 @@ Contributions of any kind are welcome! Whether it's reporting an issue or submit ## 📄 License -This project is licensed under the [BSD 3-Clause License](https://github.com/PyPtt/ptt_mcp_server/blob/main//LICENSE). +This project is licensed under the [BSD 3-Clause License](https://github.com/PyPtt/ptt_mcp_server/blob/main/LICENSE). diff --git a/pyproject.toml b/pyproject.toml index 2f69995..d5e18e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ exclude = '''( \.idea/| \.github/| \.env/| + /__pycache__/| /scripts/test_data/| /src/test_data/| )''' @@ -40,6 +41,7 @@ exclude = [ ".env", "scripts/test_data", "src/test_data", + "src/__pycache__", ] [tool.mypy] @@ -54,6 +56,7 @@ check_untyped_defs = true no_implicit_optional = true show_error_codes = true pretty = true +ignore_missing_imports = true files = ["src", "scripts"] exclude = [ diff --git a/run_code_quality.sh b/run_code_quality.sh old mode 100644 new mode 100755 index 54ab190..a087348 --- a/run_code_quality.sh +++ b/run_code_quality.sh @@ -1,4 +1,4 @@ -black --check . -flask8 . -mypy . \ No newline at end of file +black --check src +flake8 src +mypy src \ No newline at end of file diff --git a/src/api_ptt.py b/src/api_ptt.py index 0621c66..cc3dd92 100644 --- a/src/api_ptt.py +++ b/src/api_ptt.py @@ -96,11 +96,11 @@ def login() -> Dict[str, Any]: @mcp.tool() def get_post( - board: str, - aid: Optional[str] = None, - index: int = 0, - query: bool = False, - search_list: Optional[List[Tuple[str, str]]] = None, + board: str, + aid: Optional[str] = None, + index: int = 0, + query: bool = False, + search_list: Optional[List[Tuple[str, str]]] = None, ) -> Dict[str, Any]: """從 PTT 取得指定文章。 @@ -146,9 +146,9 @@ def get_post( @mcp.tool() def get_newest_index( - index_type: str, - board: Optional[str] = None, - search_list: Optional[List[Tuple[str, str]]] = None, + index_type: str, + board: Optional[str] = None, + search_list: Optional[List[Tuple[str, str]]] = None, ) -> Dict[str, Any]: """取得最新文章或信箱編號。 函式回傳的 newest_index 代表的是該類型 (看板文章或信箱信件) 的最大有效編號。 @@ -189,7 +189,7 @@ def get_newest_index( @mcp.tool() def post( - board: str, title_index: int, title: str, content: str, sign_file: str = "0" + board: str, title_index: int, title: str, content: str, sign_file: str = "0" ) -> Dict[str, Any]: """到看板發佈文章。 @@ -224,12 +224,12 @@ def post( @mcp.tool() def reply_post( - board: str, - reply_to: str, - content: str, - aid: Optional[str] = None, - index: int = 0, - sign_file: str = "0", + board: str, + reply_to: str, + content: str, + aid: Optional[str] = None, + index: int = 0, + sign_file: str = "0", ) -> Dict[str, Any]: """到看板回覆文章。 @@ -266,7 +266,7 @@ def reply_post( @mcp.tool() def del_post( - board: str, aid: Optional[str] = None, index: int = 0 + board: str, aid: Optional[str] = None, index: int = 0 ) -> Dict[str, Any]: """刪除文章。 @@ -296,11 +296,11 @@ def del_post( @mcp.tool() def comment( - board: str, - comment_type: str, - content: str, - aid: Optional[str] = None, - index: int = 0, + board: str, + comment_type: str, + content: str, + aid: Optional[str] = None, + index: int = 0, ) -> Dict[str, Any]: """對文章進行推文、噓文或箭頭。 @@ -334,7 +334,7 @@ def comment( @mcp.tool() def mail( - ptt_id: str, title: str, content: str, sign_file: str = "0", backup: bool = True + ptt_id: str, title: str, content: str, sign_file: str = "0", backup: bool = True ) -> Dict[str, Any]: """寄送站內信。 @@ -369,10 +369,10 @@ def mail( @mcp.tool() def get_mail( - index: int, - search_type: Optional[str] = None, - search_condition: Optional[str] = None, - search_list: Optional[List[List[str]]] = None, + index: int, + search_type: Optional[str] = None, + search_condition: Optional[str] = None, + search_list: Optional[List[List[str]]] = None, ) -> Dict[str, Any]: """取得信件。 @@ -434,10 +434,10 @@ def del_mail(index: int) -> Dict[str, Any]: @mcp.tool() def give_money( - ptt_id: str, - money: int, - red_bag_title: Optional[str] = None, - red_bag_content: Optional[str] = None, + ptt_id: str, + money: int, + red_bag_title: Optional[str] = None, + red_bag_content: Optional[str] = None, ) -> Dict[str, Any]: """轉帳 Ptt 幣給指定使用者。 @@ -500,7 +500,7 @@ def get_user(user_id: str) -> Dict[str, Any]: @mcp.tool() def search_user( - ptt_id: str, min_page: Optional[int] = None, max_page: Optional[int] = None + ptt_id: str, min_page: Optional[int] = None, max_page: Optional[int] = None ) -> Dict[str, Any]: """搜尋使用者。 @@ -707,7 +707,7 @@ def set_board_title(board: str, new_title: str) -> Dict[str, Any]: @mcp.tool() def bucket( - board: str, ptt_id: str, bucket_days: int, reason: str + board: str, ptt_id: str, bucket_days: int, reason: str ) -> Dict[str, Any]: """將指定使用者水桶。 diff --git a/src/auto_version.py b/src/auto_version.py index 15dd0d1..a091342 100644 --- a/src/auto_version.py +++ b/src/auto_version.py @@ -67,8 +67,8 @@ def main(): next_num = str(int(latest_pypi_version.split("dev")[-1]) + 1) cur_version = latest_pypi_version[ - : latest_pypi_version.find("dev") + 3 - ] + str(next_num) + : latest_pypi_version.find("dev") + 3 + ] + str(next_num) else: next_num = str(int(latest_pypi_version.split(".")[-1]) + 1) diff --git a/src/mcp_server.py b/src/mcp_server.py index 27327bc..15b29ad 100644 --- a/src/mcp_server.py +++ b/src/mcp_server.py @@ -16,9 +16,7 @@ if not PTT_ID or not PTT_PW: raise ValueError("PTT_ID and PTT_PW environment variables must be set.") -mcp: FastMCP = FastMCP( - f"Ptt MCP Server v{__version__}" -) +mcp: FastMCP = FastMCP(f"Ptt MCP Server v{__version__}") MEMORY_STORAGE: Dict[str, Any] = {"ptt_bot": None, "ptt_id": PTT_ID, "ptt_pw": PTT_PW} diff --git a/src/utils.py b/src/utils.py index 105fe96..b5694b9 100644 --- a/src/utils.py +++ b/src/utils.py @@ -1,6 +1,6 @@ from typing import Dict, Any, Optional -import PyPtt # type: ignore +import PyPtt def _handle_ptt_exception(e: Exception, kwargs: Dict[str, Any]) -> Dict[str, Any]: From e17712b56a459877ca8a66bfa4a6b4bb508aff51 Mon Sep 17 00:00:00 2001 From: CodingMan Date: Mon, 7 Jul 2025 14:20:53 +0800 Subject: [PATCH 12/12] chore: commit. --- README.md | 2 +- README_ENG.md | 14 +++------- pyproject.toml | 3 +++ run_code_quality.sh | 6 ++--- src/api_post.py | 4 +-- src/api_ptt.py | 64 ++++++++++++++++++++++----------------------- src/auto_version.py | 4 +-- src/mcp_server.py | 4 +-- src/utils.py | 2 +- 9 files changed, 49 insertions(+), 54 deletions(-) mode change 100644 => 100755 run_code_quality.sh diff --git a/README.md b/README.md index a4800e6..253873b 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ graph LR | **文章互動** | 推文、噓文、給予箭頭、回覆文章 | ✅ | | **信箱系統** | 讀取信件、傳送新信件、刪除信件 | ✅ | | **金融系統** | 查詢 P幣、轉帳 P幣 | ✅ | -| **資訊查詢** | 查詢使用者資訊、查詢看板資訊 | ✅ | +| **資訊查詢** | 查詢使用者資訊、查詢看板資訊、**取得文章索引範圍** | ✅ | ## ⚠️ 重要建議與免責聲明 (Important Suggestion & Disclaimer) diff --git a/README_ENG.md b/README_ENG.md index 148b2ed..7967d14 100644 --- a/README_ENG.md +++ b/README_ENG.md @@ -91,7 +91,7 @@ graph LR | **Post Interaction** | Push, Boo, Arrow, Reply to post | ✅ | | **Mail System** | Read mail, Send new mail, Delete mail | ✅ | | **Financial System** | Check P Coin balance, Transfer P Coins | ✅ | -| **Information Query** | Query user info, Query board info | ✅ | +| **Information Query** | Query user info, Query board info, **Get post index range** | ✅ | ## ⚠️ Important Suggestion & Disclaimer @@ -138,10 +138,7 @@ After installation, the `ptt-mcp-server` command should be available in your she "mcpServers": { "PTT": { "command": "ptt-mcp-server", - "env": { - "PTT_ID": "YOUR_PTT_ID", // Replace with your PTT ID - "PTT_PW": "YOUR_PTT_PW" // Replace with your PTT password - } + "env": { /* PTT_ID and PTT_PW should be set as environment variables */ } } } } @@ -159,10 +156,7 @@ If you use a Python virtual environment, or if the `command` cannot be executed "args": [ "/path/to/your/venv/bin/ptt-mcp-server" ], - "env": { - "PTT_ID": "YOUR_PTT_ID", // Replace with your PTT ID - "PTT_PW": "YOUR_PTT_PW" // Replace with your PTT password - } + "env": { /* PTT_ID and PTT_PW should be set as environment variables */ } } } } @@ -233,4 +227,4 @@ Contributions of any kind are welcome! Whether it's reporting an issue or submit ## 📄 License -This project is licensed under the [BSD 3-Clause License](https://github.com/PyPtt/ptt_mcp_server/blob/main//LICENSE). +This project is licensed under the [BSD 3-Clause License](https://github.com/PyPtt/ptt_mcp_server/blob/main/LICENSE). diff --git a/pyproject.toml b/pyproject.toml index 2f69995..d5e18e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ exclude = '''( \.idea/| \.github/| \.env/| + /__pycache__/| /scripts/test_data/| /src/test_data/| )''' @@ -40,6 +41,7 @@ exclude = [ ".env", "scripts/test_data", "src/test_data", + "src/__pycache__", ] [tool.mypy] @@ -54,6 +56,7 @@ check_untyped_defs = true no_implicit_optional = true show_error_codes = true pretty = true +ignore_missing_imports = true files = ["src", "scripts"] exclude = [ diff --git a/run_code_quality.sh b/run_code_quality.sh old mode 100644 new mode 100755 index 54ab190..a087348 --- a/run_code_quality.sh +++ b/run_code_quality.sh @@ -1,4 +1,4 @@ -black --check . -flask8 . -mypy . \ No newline at end of file +black --check src +flake8 src +mypy src \ No newline at end of file diff --git a/src/api_post.py b/src/api_post.py index ff2ff21..8975ba5 100644 --- a/src/api_post.py +++ b/src/api_post.py @@ -23,9 +23,9 @@ def get_board_rules() -> Dict[str, Any]: prompt = """ 請使用 get_bottom_post_list 來取得置底文章,因為板規通常都會置底。 - + 如果找不到版規,請使用 get_newest_index(index_type="BOARD", board=board, search_list=[("KEYWORD", "版規")]) 來取得有版規在標題中的文章列表。 - + 註記:版規可能叫做「版規」or 「板規」。 """ diff --git a/src/api_ptt.py b/src/api_ptt.py index 0621c66..cc3dd92 100644 --- a/src/api_ptt.py +++ b/src/api_ptt.py @@ -96,11 +96,11 @@ def login() -> Dict[str, Any]: @mcp.tool() def get_post( - board: str, - aid: Optional[str] = None, - index: int = 0, - query: bool = False, - search_list: Optional[List[Tuple[str, str]]] = None, + board: str, + aid: Optional[str] = None, + index: int = 0, + query: bool = False, + search_list: Optional[List[Tuple[str, str]]] = None, ) -> Dict[str, Any]: """從 PTT 取得指定文章。 @@ -146,9 +146,9 @@ def get_post( @mcp.tool() def get_newest_index( - index_type: str, - board: Optional[str] = None, - search_list: Optional[List[Tuple[str, str]]] = None, + index_type: str, + board: Optional[str] = None, + search_list: Optional[List[Tuple[str, str]]] = None, ) -> Dict[str, Any]: """取得最新文章或信箱編號。 函式回傳的 newest_index 代表的是該類型 (看板文章或信箱信件) 的最大有效編號。 @@ -189,7 +189,7 @@ def get_newest_index( @mcp.tool() def post( - board: str, title_index: int, title: str, content: str, sign_file: str = "0" + board: str, title_index: int, title: str, content: str, sign_file: str = "0" ) -> Dict[str, Any]: """到看板發佈文章。 @@ -224,12 +224,12 @@ def post( @mcp.tool() def reply_post( - board: str, - reply_to: str, - content: str, - aid: Optional[str] = None, - index: int = 0, - sign_file: str = "0", + board: str, + reply_to: str, + content: str, + aid: Optional[str] = None, + index: int = 0, + sign_file: str = "0", ) -> Dict[str, Any]: """到看板回覆文章。 @@ -266,7 +266,7 @@ def reply_post( @mcp.tool() def del_post( - board: str, aid: Optional[str] = None, index: int = 0 + board: str, aid: Optional[str] = None, index: int = 0 ) -> Dict[str, Any]: """刪除文章。 @@ -296,11 +296,11 @@ def del_post( @mcp.tool() def comment( - board: str, - comment_type: str, - content: str, - aid: Optional[str] = None, - index: int = 0, + board: str, + comment_type: str, + content: str, + aid: Optional[str] = None, + index: int = 0, ) -> Dict[str, Any]: """對文章進行推文、噓文或箭頭。 @@ -334,7 +334,7 @@ def comment( @mcp.tool() def mail( - ptt_id: str, title: str, content: str, sign_file: str = "0", backup: bool = True + ptt_id: str, title: str, content: str, sign_file: str = "0", backup: bool = True ) -> Dict[str, Any]: """寄送站內信。 @@ -369,10 +369,10 @@ def mail( @mcp.tool() def get_mail( - index: int, - search_type: Optional[str] = None, - search_condition: Optional[str] = None, - search_list: Optional[List[List[str]]] = None, + index: int, + search_type: Optional[str] = None, + search_condition: Optional[str] = None, + search_list: Optional[List[List[str]]] = None, ) -> Dict[str, Any]: """取得信件。 @@ -434,10 +434,10 @@ def del_mail(index: int) -> Dict[str, Any]: @mcp.tool() def give_money( - ptt_id: str, - money: int, - red_bag_title: Optional[str] = None, - red_bag_content: Optional[str] = None, + ptt_id: str, + money: int, + red_bag_title: Optional[str] = None, + red_bag_content: Optional[str] = None, ) -> Dict[str, Any]: """轉帳 Ptt 幣給指定使用者。 @@ -500,7 +500,7 @@ def get_user(user_id: str) -> Dict[str, Any]: @mcp.tool() def search_user( - ptt_id: str, min_page: Optional[int] = None, max_page: Optional[int] = None + ptt_id: str, min_page: Optional[int] = None, max_page: Optional[int] = None ) -> Dict[str, Any]: """搜尋使用者。 @@ -707,7 +707,7 @@ def set_board_title(board: str, new_title: str) -> Dict[str, Any]: @mcp.tool() def bucket( - board: str, ptt_id: str, bucket_days: int, reason: str + board: str, ptt_id: str, bucket_days: int, reason: str ) -> Dict[str, Any]: """將指定使用者水桶。 diff --git a/src/auto_version.py b/src/auto_version.py index 15dd0d1..a091342 100644 --- a/src/auto_version.py +++ b/src/auto_version.py @@ -67,8 +67,8 @@ def main(): next_num = str(int(latest_pypi_version.split("dev")[-1]) + 1) cur_version = latest_pypi_version[ - : latest_pypi_version.find("dev") + 3 - ] + str(next_num) + : latest_pypi_version.find("dev") + 3 + ] + str(next_num) else: next_num = str(int(latest_pypi_version.split(".")[-1]) + 1) diff --git a/src/mcp_server.py b/src/mcp_server.py index 27327bc..15b29ad 100644 --- a/src/mcp_server.py +++ b/src/mcp_server.py @@ -16,9 +16,7 @@ if not PTT_ID or not PTT_PW: raise ValueError("PTT_ID and PTT_PW environment variables must be set.") -mcp: FastMCP = FastMCP( - f"Ptt MCP Server v{__version__}" -) +mcp: FastMCP = FastMCP(f"Ptt MCP Server v{__version__}") MEMORY_STORAGE: Dict[str, Any] = {"ptt_bot": None, "ptt_id": PTT_ID, "ptt_pw": PTT_PW} diff --git a/src/utils.py b/src/utils.py index 105fe96..b5694b9 100644 --- a/src/utils.py +++ b/src/utils.py @@ -1,6 +1,6 @@ from typing import Dict, Any, Optional -import PyPtt # type: ignore +import PyPtt def _handle_ptt_exception(e: Exception, kwargs: Dict[str, Any]) -> Dict[str, Any]: