#!/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()