File size: 6,547 Bytes
845178e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 | #!/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()
|