Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/code_quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
22 changes: 18 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[English Version](./README_ENG.md)
[English Version](https://github.com/PyPtt/ptt_mcp_server/blob/main/README_ENG.md)

<h1 align="center">PTT MCP Server</h1>

Expand All @@ -15,7 +15,7 @@ The best MCP server for Ptt. Proudly built by <a href="https://pyptt.cc/">PyPtt<
<a href="https://img.shields.io/pypi/dm/ptt-mcp-server">
<img src="https://img.shields.io/pypi/dm/ptt-mcp-server" alt="Downloads">
</a>
<a href="./LICENSE">
<a href="https://github.com/PyPtt/ptt_mcp_server/blob/main/LICENSE">
<img src="https://img.shields.io/badge/license-BSD_3--Clause-blue.svg" alt="License">
</a>
</p>
Expand Down Expand Up @@ -91,7 +91,21 @@ graph LR
| **文章互動** | 推文、噓文、給予箭頭、回覆文章 | ✅ |
| **信箱系統** | 讀取信件、傳送新信件、刪除信件 | ✅ |
| **金融系統** | 查詢 P幣、轉帳 P幣 | ✅ |
| **資訊查詢** | 查詢使用者資訊、查詢看板資訊 | ✅ |
| **資訊查詢** | 查詢使用者資訊、查詢看板資訊、**取得文章索引範圍** | ✅ |

## ⚠️ 重要建議與免責聲明 (Important Suggestion & Disclaimer)

本專案提供強大的 PTT 自動化操作能力,但請注意,所有操作皆基於您的授權,您將對所有操作的後果負全部責任。為了安全且有效地使用本工具,我們強烈建議您遵循以下最佳實踐:

**最佳實踐:先讀後寫,確認再執行**

在使用任何會修改 PTT 內容的功能(如發文、回文、寄信、推文等)之前,請務必先使用讀取功能來收集和確認資訊。

* **範例:** 與其直接下令「刪除違規文章」,不如先「列出所有違規文章」,在您審核列表確認無誤後,再執行刪除操作。

這個簡單的流程可以大幅降低因自動化操作失誤(例如:誤刪文章、發錯內容)而導致的風險。雖然 PTT MCP Server 已經加入執行前會提示您進行最終確認,但仍無法完全避免誤操作的可能。**請在送出前仔細核對內容!**

請記住,任何因使用本伺服器而造成的損失或責任,本專案開發者概不負責。

## 📋 環境需求 (Requirements)

Expand Down Expand Up @@ -219,4 +233,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) 授權。
32 changes: 20 additions & 12 deletions README_ENG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[Traditional Chinese Version](./README.md)
[Traditional Chinese Version](https://github.com/PyPtt/ptt_mcp_server/blob/main/README.md)

<h1 align="center">PTT MCP Server</h1>

Expand All @@ -15,7 +15,7 @@ The best MCP server for Ptt. Proudly built by the <a href="https://pyptt.cc/">Py
<a href="https://img.shields.io/pypi/dm/ptt-mcp-server">
<img src="https://img.shields.io/pypi/dm/ptt-mcp-server" alt="Downloads">
</a>
<a href="./LICENSE">
<a href="https://github.com/PyPtt/ptt_mcp_server/blob/main/LICENSE">
<img src="https://img.shields.io/badge/license-BSD_3--Clause-blue.svg" alt="License">
</a>
</p>
Expand Down Expand Up @@ -91,7 +91,21 @@ 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

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

Expand Down Expand Up @@ -124,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 */ }
}
}
}
Expand All @@ -145,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 */ }
}
}
}
Expand Down Expand Up @@ -219,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](./LICENSE).
This project is licensed under the [BSD 3-Clause License](https://github.com/PyPtt/ptt_mcp_server/blob/main/LICENSE).
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ exclude = '''(
\.idea/|
\.github/|
\.env/|
/__pycache__/|
/scripts/test_data/|
/src/test_data/|
)'''
Expand All @@ -40,6 +41,7 @@ exclude = [
".env",
"scripts/test_data",
"src/test_data",
"src/__pycache__",
]

[tool.mypy]
Expand All @@ -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 = [
Expand Down
6 changes: 3 additions & 3 deletions run_code_quality.sh
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

black --check .
flask8 .
mypy .
black --check src
flake8 src
mypy src
11 changes: 8 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,24 @@
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
package_dir={"": "src"},
py_modules=["basic_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",
"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",
],
Expand Down
189 changes: 189 additions & 0 deletions src/api_post.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
from datetime import datetime
from typing import Dict, Any

import PyPtt
from fastmcp import FastMCP

from utils import _call_ptt_service


def register_tools(mcp: FastMCP, memory_storage: Dict[str, Any], version: str):
@mcp.tool()
def get_board_rules() -> Dict[str, Any]:
"""
取得 PTT 看板規則。

註記:此函式必須先登入 PTT。

Returns:
Dict[str, Any]: 包含操作結果的字典。
成功時: {'success': True, 'message': str, 'data': List[str]}
失敗時: {'success': False, 'message': str, 'code': str, 'prompt': str}
"""

prompt = """
請使用 get_bottom_post_list 來取得置底文章,因為板規通常都會置底。

如果找不到版規,請使用 get_newest_index(index_type="BOARD", board=board, search_list=[("KEYWORD", "版規")]) 來取得有版規在標題中的文章列表。

註記:版規可能叫做「版規」or 「板規」。
"""

return {
'success': False,
'message': '請遵循提示。',
'code': 'FOLLOW_PROMPT',
'prompt': prompt
}

@mcp.tool()
def get_post_index_range(board: str, target_date_str: str) -> Dict[str, Any]:
"""
取得 PTT 文章在指定看板和日期下的索引範圍。

註記:此函式必須先登入 PTT。

Args:
board (str): 看板名稱,例如 "Gossiping"。
target_date_str (str): 目標日期字串,格式為 "YYYY/MM/DD",例如 "1987/09/06"。

Returns:
Dict[str, Any]: 包含操作結果的字典。
成功時: {'success': True, 'start_index': int, 'end_index': int}
失敗時: {'success': False, 'message': str}
"""

# 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_str)
except ValueError:
return {"success": False,
"message": f"Invalid target_date_str format: {target_date_str}. 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}
Loading