init
This commit is contained in:
35
mcp/didatodolist-mcp/scripts/dev_check_tasks.py
Normal file
35
mcp/didatodolist-mcp/scripts/dev_check_tasks.py
Normal file
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
开发辅助脚本:快速拉取任务并筛选今日未完成任务,打印前若干条。
|
||||
|
||||
用法:
|
||||
python scripts/dev_check_tasks.py
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from tools.official_api import init_api
|
||||
from tools.adapter import adapter
|
||||
|
||||
|
||||
def main() -> int:
|
||||
load_dotenv()
|
||||
try:
|
||||
init_api(config_path="oauth_config.json")
|
||||
except Exception:
|
||||
# 允许仅使用 .env 的 token 覆盖
|
||||
pass
|
||||
|
||||
tasks = adapter.list_tasks()
|
||||
print(f"All tasks: {len(tasks)}")
|
||||
# 过滤:未完成
|
||||
inc = [t for t in tasks if not t.get('isCompleted')]
|
||||
print(f"Incomplete: {len(inc)}")
|
||||
for t in inc[:10]:
|
||||
print(f"- {t.get('title')} | project={t.get('projectName')} | due={t.get('dueDate')}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
69
mcp/didatodolist-mcp/scripts/generate_auth_url.py
Normal file
69
mcp/didatodolist-mcp/scripts/generate_auth_url.py
Normal file
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
滴答清单OAuth授权URL生成器
|
||||
|
||||
使用: python scripts/generate_auth_url.py
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlencode
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="滴答清单OAuth授权URL生成器")
|
||||
parser.add_argument("--config", default="oauth_config.json", help="配置文件路径")
|
||||
parser.add_argument("--port", type=int, default=38000, help="回调端口")
|
||||
parser.add_argument("--scope", default="tasks:read tasks:write", help="权限范围")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# 加载配置
|
||||
config_path = Path(args.config)
|
||||
if not config_path.exists():
|
||||
print("❌ 配置文件不存在,请先创建 oauth_config.json")
|
||||
print("\n参考 oauth_config.json.example 创建配置文件")
|
||||
return
|
||||
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
|
||||
client_id = config.get("client_id")
|
||||
if not client_id:
|
||||
print("❌ 配置文件缺少 client_id")
|
||||
return
|
||||
|
||||
# 生成授权URL
|
||||
redirect_uri = f"http://localhost:{args.port}/callback"
|
||||
params = {
|
||||
"client_id": client_id,
|
||||
"redirect_uri": redirect_uri,
|
||||
"scope": args.scope,
|
||||
"state": "auth",
|
||||
"response_type": "code"
|
||||
}
|
||||
|
||||
auth_url = f"https://dida365.com/oauth/authorize?{urlencode(params)}"
|
||||
|
||||
# 显示信息
|
||||
print("\n" + "="*70)
|
||||
print("滴答清单OAuth授权URL")
|
||||
print("="*70)
|
||||
print(f"\nClient ID: {client_id}")
|
||||
print(f"Redirect URI: {redirect_uri}")
|
||||
print(f"Scope: {args.scope}")
|
||||
print("\n" + "="*70)
|
||||
print("授权URL:")
|
||||
print("="*70)
|
||||
print(f"\n{auth_url}\n")
|
||||
print("="*70)
|
||||
print("使用步骤:")
|
||||
print("="*70)
|
||||
print("1. 复制上面的URL到浏览器")
|
||||
print("2. 登录滴答清单账号并授权")
|
||||
print(f"3. 确保本地服务器运行在端口 {args.port}\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
146
mcp/didatodolist-mcp/scripts/oauth_authenticate.py
Normal file
146
mcp/didatodolist-mcp/scripts/oauth_authenticate.py
Normal file
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
滴答清单 OAuth 完整认证流程(.env-only)
|
||||
|
||||
功能:
|
||||
1. 启动本地回调服务器
|
||||
2. 生成并显示授权URL
|
||||
3. 接收授权码
|
||||
4. 交换访问令牌
|
||||
5. 测试API调用
|
||||
6. 将令牌写入 .env(DIDA_ACCESS_TOKEN / DIDA_REFRESH_TOKEN)
|
||||
|
||||
使用:
|
||||
python scripts/oauth_authenticate.py
|
||||
python scripts/oauth_authenticate.py --port 38000
|
||||
"""
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到路径
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
import os
|
||||
import json
|
||||
import dotenv
|
||||
from utils.oauth_auth import DidaOAuthClient
|
||||
|
||||
|
||||
def write_env_tokens(env_path: Path, access_token: str, refresh_token: str | None):
|
||||
"""将令牌写入 .env(若存在则更新相关行)。"""
|
||||
lines = []
|
||||
if env_path.exists():
|
||||
with open(env_path, "r", encoding="utf-8") as f:
|
||||
lines = f.read().splitlines()
|
||||
|
||||
def upsert(key: str, value: str | None):
|
||||
nonlocal lines
|
||||
# 删除旧行
|
||||
lines = [ln for ln in lines if not ln.startswith(f"{key}=")]
|
||||
if value is not None:
|
||||
lines.append(f"{key}={value}")
|
||||
|
||||
upsert("DIDA_ACCESS_TOKEN", access_token)
|
||||
upsert("DIDA_REFRESH_TOKEN", refresh_token or "")
|
||||
|
||||
content = "\n".join(lines) + ("\n" if not content_endswith_newline(lines) else "")
|
||||
with open(env_path, "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
|
||||
|
||||
def content_endswith_newline(lines: list[str]) -> bool:
|
||||
if not lines:
|
||||
return False
|
||||
return lines[-1].endswith("\n")
|
||||
|
||||
|
||||
def main():
|
||||
# 加载 .env,确保 DIDA_CLIENT_ID/SECRET 可被读取
|
||||
dotenv.load_dotenv()
|
||||
parser = argparse.ArgumentParser(description="滴答清单OAuth认证(.env-only)")
|
||||
|
||||
parser.add_argument(
|
||||
"--port",
|
||||
type=int,
|
||||
default=38000,
|
||||
help="回调服务器端口"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
print("\n" + "="*70)
|
||||
print("滴答清单OAuth 2.0 认证")
|
||||
print("="*70 + "\n")
|
||||
|
||||
# 仅使用 .env 环境变量
|
||||
client_id = os.environ.get("DIDA_CLIENT_ID")
|
||||
client_secret = os.environ.get("DIDA_CLIENT_SECRET")
|
||||
redirect_uri = os.environ.get("DIDA_REDIRECT_URI", f"http://localhost:{args.port}/callback")
|
||||
if not client_id or not client_secret:
|
||||
print("❌ 未检测到环境变量 DIDA_CLIENT_ID / DIDA_CLIENT_SECRET")
|
||||
print("请在 .env 中设置以下变量后重试:\n")
|
||||
example = {
|
||||
"DIDA_CLIENT_ID": "YOUR_CLIENT_ID",
|
||||
"DIDA_CLIENT_SECRET": "YOUR_CLIENT_SECRET",
|
||||
"DIDA_REDIRECT_URI": f"http://localhost:{args.port}/callback"
|
||||
}
|
||||
print(json.dumps(example, indent=2))
|
||||
sys.exit(1)
|
||||
|
||||
if not client_id or not client_secret:
|
||||
print("❌ 配置文件缺少 client_id 或 client_secret")
|
||||
sys.exit(1)
|
||||
|
||||
# 创建OAuth客户端
|
||||
oauth_client = DidaOAuthClient(
|
||||
client_id=client_id,
|
||||
client_secret=client_secret,
|
||||
redirect_uri=redirect_uri
|
||||
)
|
||||
|
||||
# 执行授权流程
|
||||
print("步骤1: 启动本地回调服务器")
|
||||
print(f"端口: {args.port}\n")
|
||||
|
||||
success = oauth_client.authorize(auto_open_browser=False)
|
||||
|
||||
if success:
|
||||
# 写入 .env(不会加入版本控制)
|
||||
env_path = Path(".env")
|
||||
write_env_tokens(env_path, oauth_client.access_token, oauth_client.refresh_token)
|
||||
print(f"\n已写入 .env: DIDA_ACCESS_TOKEN / DIDA_REFRESH_TOKEN")
|
||||
|
||||
print("\n" + "="*70)
|
||||
print("✅ OAuth认证成功!")
|
||||
print("="*70)
|
||||
print(f"Access Token: {oauth_client.access_token[:30]}...")
|
||||
|
||||
# 测试API
|
||||
print("\n正在测试API...")
|
||||
try:
|
||||
import requests
|
||||
headers = oauth_client.get_headers()
|
||||
response = requests.get(
|
||||
"https://api.dida365.com/open/v1/project",
|
||||
headers=headers,
|
||||
timeout=10
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
projects = response.json()
|
||||
print(f"✅ API测试成功! 找到 {len(projects)} 个项目\n")
|
||||
else:
|
||||
print(f"⚠️ API测试失败: HTTP {response.status_code}\n")
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ API测试失败: {str(e)}\n")
|
||||
|
||||
else:
|
||||
print("\n❌ OAuth认证失败")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
117
mcp/didatodolist-mcp/scripts/sanity_openapi_demo.py
Normal file
117
mcp/didatodolist-mcp/scripts/sanity_openapi_demo.py
Normal file
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
最小验证脚本:基于官方 /open/v1 端点,跑通创建→更新→完成→删除 闭环。
|
||||
|
||||
前置:已完成 OAuth 认证,存在 oauth_config.json。
|
||||
|
||||
步骤:
|
||||
1) 创建临时项目
|
||||
2) 在项目下创建任务(含 desc、reminders、items、timeZone/日期)
|
||||
3) 更新任务(修改 desc/priority/reminders)
|
||||
4) 完成任务
|
||||
5) 删除任务
|
||||
6) 删除临时项目
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from tools.official_api import init_api, APIError
|
||||
from tools.adapter import adapter
|
||||
|
||||
|
||||
def ts() -> str:
|
||||
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
print(f"[{ts()}] 初始化 OAuth 客户端…")
|
||||
try:
|
||||
init_api(config_path="oauth_config.json")
|
||||
except Exception as e:
|
||||
print("初始化失败:请先运行 `python scripts/oauth_authenticate.py --port 38000` 完成认证。")
|
||||
print(f"详情: {e}")
|
||||
return 1
|
||||
|
||||
# 1) 创建临时项目
|
||||
demo_name = f"MCP Demo {int(time.time())}"
|
||||
print(f"[{ts()}] 创建演示项目: {demo_name}")
|
||||
project = adapter.create_project(name=demo_name, color="#F18181")
|
||||
project_id = project.get("id")
|
||||
if not project_id:
|
||||
print("创建项目失败:未返回 id")
|
||||
return 1
|
||||
print(f"[{ts()}] 项目ID: {project_id}")
|
||||
|
||||
try:
|
||||
# 2) 创建任务
|
||||
start_local = (datetime.now() + timedelta(minutes=5)).strftime("%Y-%m-%d %H:%M:%S")
|
||||
due_local = (datetime.now() + timedelta(hours=1)).strftime("%Y-%m-%d %H:%M:%S")
|
||||
print(f"[{ts()}] 创建任务…")
|
||||
task = adapter.create_task({
|
||||
"title": "Demo Task",
|
||||
"projectId": project_id,
|
||||
"content": "Demo content",
|
||||
"desc": "Checklist description",
|
||||
"isAllDay": False,
|
||||
"startDate": start_local,
|
||||
"dueDate": due_local,
|
||||
"timeZone": "Asia/Shanghai",
|
||||
"reminders": ["TRIGGER:PT0S"],
|
||||
"repeatFlag": "RRULE:FREQ=DAILY;INTERVAL=1",
|
||||
"priority": 1,
|
||||
"sortOrder": 12345,
|
||||
"items": [
|
||||
{
|
||||
"title": "Subtask A",
|
||||
"isAllDay": False,
|
||||
"startDate": start_local,
|
||||
"sortOrder": 1
|
||||
}
|
||||
]
|
||||
})
|
||||
task_id = task.get("id")
|
||||
if not task_id:
|
||||
print("创建任务失败:未返回 id")
|
||||
return 1
|
||||
print(f"[{ts()}] 任务ID: {task_id}")
|
||||
|
||||
# 3) 更新任务
|
||||
print(f"[{ts()}] 更新任务…")
|
||||
task = adapter.update_task(task_id, {
|
||||
"projectId": project_id,
|
||||
"desc": "Checklist description (updated)",
|
||||
"priority": 3,
|
||||
"reminders": ["TRIGGER:P0DT9H0M0S"],
|
||||
})
|
||||
print(f"[{ts()}] 更新后 priority={task.get('priority')} reminders={task.get('reminders')}")
|
||||
|
||||
# 4) 完成任务
|
||||
print(f"[{ts()}] 完成任务…")
|
||||
adapter.complete_task(project_id, task_id)
|
||||
tasks_after = adapter.list_tasks(project_id=project_id, completed=True)
|
||||
done = next((t for t in tasks_after if t.get('id') == task_id), None)
|
||||
print(f"[{ts()}] 完成校验 isCompleted={done.get('isCompleted') if done else None}")
|
||||
|
||||
# 5) 删除任务
|
||||
print(f"[{ts()}] 删除任务…")
|
||||
adapter.delete_task(project_id, task_id)
|
||||
print(f"[{ts()}] 任务已删除")
|
||||
finally:
|
||||
# 6) 清理项目
|
||||
print(f"[{ts()}] 删除项目…")
|
||||
try:
|
||||
adapter.delete_project(project_id)
|
||||
print(f"[{ts()}] 项目已删除")
|
||||
except APIError as e:
|
||||
print(f"删除项目失败(可忽略):{e}")
|
||||
|
||||
print(f"[{ts()}] 演示完毕 ✅")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user