buddy2api / extract_token_remote.py
li2895's picture
Upload 6 files
845178e verified
Raw
History Blame Contribute Delete
6.55 kB
#!/usr/bin/env python3
"""
从远程 WorkBuddy 实例提取 Token。
支持两种模式:
1. 本地模式:自动检测本机 WorkBuddy
2. 远程模式:连接远程 Mac 的 CDP 端口
用法:
# 本机自动检测
python extract_token_remote.py
# 远程 Mac(需要先在目标机器启动 WorkBuddy 调试模式)
python extract_token_remote.py --host 192.168.1.100 --port 9222 --save
前置条件(远程 Mac):
/Applications/WorkBuddy.app/Contents/MacOS/Electron --remote-debugging-port=9222
注意:可能需要配置防火墙允许 9222 端口入站。
"""
import argparse
import asyncio
import json
import sys
import time
from pathlib import Path
if sys.platform == "win32":
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
try:
import httpx
except ImportError:
sys.exit("请先安装依赖: pip install httpx")
try:
import websockets
except ImportError:
sys.exit("请先安装依赖: pip install websockets")
async def extract(cdp_host: str = "127.0.0.1", cdp_port: int = 9222) -> dict | None:
base = f"http://{cdp_host}:{cdp_port}"
print(f"正在连接 CDP ({base})...")
async with httpx.AsyncClient() as client:
try:
resp = await client.get(f"{base}/json", timeout=10)
except httpx.ConnectError as e:
print(f"❌ 无法连接 CDP: {e}")
print(f"\n请确保远程 Mac 已启动 WorkBuddy 调试模式:")
print(f" /Applications/WorkBuddy.app/Contents/MacOS/Electron --remote-debugging-port={cdp_port}")
return None
targets = resp.json()
ws_url = None
for t in targets:
if t.get("type") == "page" and "workbench" in t.get("url", ""):
ws_url = t.get("webSocketDebuggerUrl")
if ws_url:
if cdp_host != "127.0.0.1" and cdp_host != "localhost":
ws_url = ws_url.replace("127.0.0.1", cdp_host)
print(f"✅ 找到 Workbench 页面")
break
if not ws_url:
for t in targets:
if t.get("type") == "page":
ws_url = t.get("webSocketDebuggerUrl")
if ws_url:
if cdp_host != "127.0.0.1" and cdp_host != "localhost":
ws_url = ws_url.replace("127.0.0.1", cdp_host)
print(f"⚠️ 未找到 Workbench,找到通用页面")
break
if not ws_url:
print("❌ 未找到可用的 CDP 页面目标")
return None
print(f"正在通过 WebSocket 提取 Token...")
async with websockets.connect(ws_url, ping_timeout=30) as ws:
cmd = {
"id": 1,
"method": "Runtime.evaluate",
"params": {
"expression": """
(async () => {
try {
const s = await window.vscode.ipcRenderer.invoke(
'vscode:genie:auth:getSession'
);
return JSON.stringify(s);
} catch(e) {
return JSON.stringify({error: e.message});
}
})()
""",
"awaitPromise": True,
"returnByValue": True,
},
}
await ws.send(json.dumps(cmd))
result = json.loads(await asyncio.wait_for(ws.recv(), timeout=30))
value = result.get("result", {}).get("result", {}).get("value", "")
if not value:
print("❌ CDP 返回为空")
return None
session = json.loads(value)
if session.get("error"):
print(f"❌ CDP 错误: {session['error']}")
return None
return session
def main():
parser = argparse.ArgumentParser(
description="从 WorkBuddy 提取 Token(支持远程)",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
示例:
# 本机自动检测
python extract_token_remote.py
# 远程 Mac
python extract_token_remote.py --host 192.168.1.100 --save
# 自定义端口
python extract_token_remote.py --host 192.168.1.100 --port 9223 --save
"""
)
parser.add_argument(
"--host", "-H",
default="127.0.0.1",
help="CDP 主机地址 (默认: 127.0.0.1)"
)
parser.add_argument(
"--port", "-p",
type=int,
default=9222,
help="CDP 端口 (默认: 9222)"
)
parser.add_argument(
"--save", "-s",
action="store_true",
help="保存到 data/token.json"
)
parser.add_argument(
"--output", "-o",
help="保存到指定文件 (默认: data/token.json)"
)
args = parser.parse_args()
is_remote = args.host not in ("127.0.0.1", "localhost")
if is_remote:
print(f"\n🔗 远程模式")
print(f" 目标: {args.host}:{args.port}")
print(f" 注意: 确保目标 Mac 已启动 WorkBuddy 调试模式\n")
else:
print(f"\n🔗 本地模式\n")
session = asyncio.run(extract(args.host, args.port))
if not session:
sys.exit(1)
auth = session.get("auth", session)
access_token = auth.get("accessToken", "")
refresh_token = auth.get("refreshToken", "")
if not access_token:
print("❌ 未获取到 accessToken")
sys.exit(1)
print(f"\n✅ Token 提取成功!")
print(f" accessToken: {access_token[:40]}... (len={len(access_token)})")
print(f" refreshToken: {refresh_token[:40] if refresh_token else 'N/A'}... "
f"(len={len(refresh_token) if refresh_token else 0})")
if args.save or args.output:
import time as time_module
out_path = Path(args.output) if args.output else Path(__file__).parent / "data" / "token.json"
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(json.dumps({
"access_token": access_token,
"refresh_token": refresh_token,
"saved_at": time_module.strftime("%Y-%m-%d %H:%M:%S"),
"source_host": args.host if is_remote else "localhost",
"source_port": args.port,
}, indent=2))
print(f"\n💾 已保存到: {out_path}")
if is_remote:
print(f"\n📋 接下来在本地 Mac 上:")
print(f" 1. 将生成的 data/token.json 复制到本项目")
print(f" 2. 启动代理: python server.py")
if __name__ == "__main__":
main()