diff --git a/docs/install.md b/docs/install.md index 63b80d5..175b23c 100644 --- a/docs/install.md +++ b/docs/install.md @@ -103,8 +103,15 @@ sau bilibili --help sau douyin login --account sau douyin check --account sau douyin upload-video --account --file videos/demo.mp4 --title "示例标题" --desc "示例简介" -sau douyin upload-note --account --images videos/1.png videos/2.png --title "图文标题" --note "图文正文" -``` +sau douyin upload-note --account --images videos/demo1.png videos/demo2.png --title "图文标题" --note "图文正文" +``` + +抖音卡login手动获取cookie: + +- 目标服务器使用vnc +- 浏览器登录抖音创作者中心https://creator.douyin.com/ +- 执行`bash export_douyin_cookie.sh --account ` +- 检查cookie可用性,执行`sau douyin check --account ` ### 8. 快手主线示例 @@ -112,7 +119,7 @@ sau douyin upload-note --account --images videos/1.png videos/2.p sau kuaishou login --account sau kuaishou check --account sau kuaishou upload-video --account --file videos/demo.mp4 --title "示例标题" --desc "示例简介" -sau kuaishou upload-note --account --images videos/1.png videos/2.png videos/3.png --title "图文标题" --note "图文正文" +sau kuaishou upload-note --account --images videos/demo1.png videos/demo2.png videos/demo.png --title "图文标题" --note "图文正文" ``` ### 9. 小红书主线示例 @@ -121,7 +128,7 @@ sau kuaishou upload-note --account --images videos/1.png videos/2 sau xiaohongshu login --account sau xiaohongshu check --account sau xiaohongshu upload-video --account --file videos/demo.mp4 --title "示例标题" --desc "示例简介" -sau xiaohongshu upload-note --account --images videos/1.png videos/2.png videos/3.png --title "图文标题" --note "图文正文" +sau xiaohongshu upload-note --account --images videos/demo1.png videos/demo2.png videos/demo.png --title "图文标题" --note "图文正文" ``` ### 10. Bilibili 主线示例 diff --git a/export_douyin_cookie.sh b/export_douyin_cookie.sh new file mode 100644 index 0000000..57afd9b --- /dev/null +++ b/export_douyin_cookie.sh @@ -0,0 +1,230 @@ +#!/bin/bash +# export_douyin_cookie.sh +# 导出已登录 Chrome 的抖音 cookie 到 douyin_{8位随机字符}.json +# 使用 curl 直接调用 Chrome DevTools HTTP API + +set -e + +# 解析参数 +USERNAME="" +while [ $# -gt 0 ]; do + case "$1" in + --account) + USERNAME="$2" + shift 2 + ;; + *) + echo "用法: $0 --account <用户名>" + exit 1 + ;; + esac +done + +if [ -z "$USERNAME" ]; then + echo "未指定用户名,将使用随机文件名" +fi + +DEBUG_PORT=9222 + +echo "==========================================" +echo " 抖音 Cookie 导出工具" +echo "==========================================" +echo "" + +# 依赖: curl, python3, websocket-client + +echo "检查 Chrome remote debugging..." +RESPONSE=$(curl -s "http://localhost:${DEBUG_PORT}/json" 2>/dev/null) || true + +if ! echo "$RESPONSE" | grep -q "webSocketDebuggerUrl"; then + echo "Chrome remote debugging 未运行" + echo "请先启动 Chrome: chromium --remote-debugging-port=9222 ..." + exit 1 +fi + +echo "Chrome remote debugging 已运行" +echo "" +echo "获取抖音 Cookie..." + +USERNAME="$USERNAME" python3 << 'PYEOF' +import json +import os +import uuid +import sys + +DEBUG_PORT = 9222 +if USERNAME: + COOKIE_FILE = f"cookies/douyin_{USERNAME}.json" +else: + COOKIE_FILE = f"cookies/douyin_{uuid.uuid4().hex[:8]}.json" + +def get_douyin_cookies(): + import urllib.request + import websocket + + # 获取页面列表 + url = f"http://localhost:{DEBUG_PORT}/json" + with urllib.request.urlopen(url, timeout=10) as response: + pages = json.loads(response.read().decode()) + + # 查找抖音创作者页面 + douyin_page = None + for page in pages: + page_url = page.get("url", "") + if "creator.douyin.com" in page_url: + douyin_page = page + break + + if not douyin_page: + print("未找到抖音创作者平台页面") + print("请先在 Chrome 中打开并登录 https://creator.douyin.com") + return False + + page_url = douyin_page["url"] + ws_url = douyin_page.get("webSocketDebuggerUrl", "") + + print(f"找到页面: {page_url}") + + # 连接到目标页面的 WebSocket + print(f"连接页面 WebSocket...") + ws = websocket.create_connection(ws_url, timeout=30) + + # 获取所有 Cookie + ws.send(json.dumps({"id": 1, "method": "Network.getAllCookies"})) + response = json.loads(ws.recv()) + + cookies = response.get("result", {}).get("cookies", []) + + # 过滤抖音相关的 cookie + douyin_cookies = [ + c for c in cookies + if "douyin.com" in c.get("domain", "") or ".douyin.com" in c.get("domain", "") + ] + + print(f"获取到 {len(douyin_cookies)} 个抖音 Cookie") + + if len(douyin_cookies) == 0: + print("未获取到任何抖音 Cookie,可能未登录") + return False + + # 获取 localStorage (origins) + print(f"获取 localStorage...") + local_storage_by_origin = {} + + # 获取页面 frame 树 + ws.send(json.dumps({"id": 2, "method": "Page.getResourceTree"})) + resp = json.loads(ws.recv()) + frames = resp.get("result", {}).get("frameTree", {}).get("childFrames", []) + all_frames = [resp.get("result", {}).get("frameTree", {})] + frames + + for frame in all_frames: + frame_url = frame.get("url", "") + frame_id = frame.get("id", "") + if "douyin.com" in frame_url or "bytedance.com" in frame_url: + # 执行 JavaScript 获取该 frame 的 localStorage + script = """ + (function() { + var result = []; + for (var i = 0; i < localStorage.length; i++) { + var key = localStorage.key(i); + result.push([key, localStorage.getItem(key)]); + } + return result; + })() + """ + ws.send(json.dumps({ + "id": 3, + "method": "Runtime.evaluate", + "params": {"expression": script, "contextId": frame.get("id")} + })) + resp = json.loads(ws.recv()) + result_eval = resp.get("result", {}) + if result_eval.get("result", {}).get("type") == "array": + items = result_eval.get("result", {}).get("value", []) + if items: + origin = frame_url.rsplit("/", 2)[0] + "//" + frame_url.split("/")[2] + local_storage_by_origin[origin] = items + + # 获取主文档的 localStorage + script_main = """ + (function() { + var result = []; + try { + for (var i = 0; i < localStorage.length; i++) { + var key = localStorage.key(i); + result.push([key, localStorage.getItem(key)]); + } + } catch(e) {} + return result; + })() + """ + ws.send(json.dumps({ + "id": 4, + "method": "Runtime.evaluate", + "params": {"expression": script_main} + })) + resp = json.loads(ws.recv()) + result_eval = resp.get("result", {}) + if result_eval.get("result", {}).get("type") == "array": + items = result_eval.get("result", {}).get("value", []) + if items: + origin = page_url.rsplit("/", 2)[0] + "//" + page_url.split("/")[2] + local_storage_by_origin[origin] = items + + ws.close() + + # 构建 JSON + result = { + "cookies": [], + "origins": [] + } + + for c in douyin_cookies: + result["cookies"].append({ + "name": c.get("name", ""), + "value": c.get("value", ""), + "domain": c.get("domain", ""), + "path": c.get("path", "/"), + "expires": c.get("expires", -1), + "httpOnly": c.get("httpOnly", False), + "secure": c.get("secure", True), + "sameSite": c.get("sameSite", "Lax") + }) + + # 添加 origins + for origin, items in local_storage_by_origin.items(): + origin_entry = { + "origin": origin, + "localStorage": [{"name": name, "value": value} for name, value in items] + } + result["origins"].append(origin_entry) + + # 保存 + os.makedirs("cookies", exist_ok=True) + with open(COOKIE_FILE, "w", encoding="utf-8") as f: + json.dump(result, f, indent=2, ensure_ascii=False) + + print(f"Cookie 已保存到: {COOKIE_FILE}") + print("") + + # 打印关键 Cookie + key_cookies = ["sessionid", "uid_tt", "ssid", "ttwid"] + print("关键 Cookie:") + for c in douyin_cookies: + if c.get("name") in key_cookies: + val = c.get("value", "") + if len(val) > 30: + val = val[:30] + "..." + print(f" {c.get('name')}: {val}") + + return True + +if __name__ == "__main__": + if not get_douyin_cookies(): + sys.exit(1) +PYEOF + +echo "" +echo "==========================================" +echo " 完成!文件已导出为$COOKIE_FILE" +echo "==========================================" \ No newline at end of file