From 375f0e50faad169482095e61dc465916ed2fdb10 Mon Sep 17 00:00:00 2001 From: cheney Date: Fri, 17 Jul 2026 20:55:47 +0800 Subject: [PATCH] save --- config/server-config.json | 12 +--- package.json | 16 ++--- src/cli.js | 9 ++- src/get-cookie.js | 148 +++++++++++++++++++++++++++++++------- src/index.js | 2 +- src/sync-douxiaoyun.js | 41 +++++------ 6 files changed, 158 insertions(+), 70 deletions(-) diff --git a/config/server-config.json b/config/server-config.json index 649eb7b..a48b2fc 100644 --- a/config/server-config.json +++ b/config/server-config.json @@ -1,11 +1,5 @@ { - "apiUrl": "http://dsv6.honor3.com:10101/api/config/update", - "authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiZG91eWluIiwianRpIjoiYzQ2ODc3MzMtY2I3OS00OWNmLTg2ODMtODc1MGJjMzRmYzYzIiwiZXhwIjoxNzg0ODkwMjExLCJpc3MiOiIyMDc4MDY5NzQ5MTQ5MTkyMTkyIiwiYXVkIjoiMjA3ODA2OTc0OTE0OTE5MjE5MyJ9.86oluoimnUJEUBwCfKog4hLNYgrzgdwcqAVphRCbgik", - "userName": "3js", - "id": "2004162624794681344", - "savePath": "/app/collect", - "favSavePath": "/app/favorite", - "upSavePath": "/app/uper", - "imgSavePath": "/app/images", - "status": 1 + "apiBase": "http://dsv6.honor3.com:10101", + "username": "douyin", + "password": "douyin2025" } diff --git a/package.json b/package.json index 77c12c7..c104f77 100644 --- a/package.json +++ b/package.json @@ -2,18 +2,18 @@ "name": "p20260129-douxiaoyun", "version": "1.0.0", "description": "抖小云插件", - "main": "index.js", + "main": "src/index.js", + "type": "module", "scripts": { - "loop": "node cli.js loop", - "unfavorite": "node cli.js unfavorite", - "sync": "node cli.js sync", - "uncollect": "node cli.js uncollect", - "get-cookie": "node cli.js get-cookie" + "cookies": "node src/cli.js cookies", + "sync": "node src/cli.js sync", + "unfavorite": "node src/cli.js unfavorite", + "uncollect": "node src/cli.js uncollect", + "loop": "node src/cli.js loop" }, "author": "", "license": "ISC", "dependencies": { "playwright": "^1.61.1" - }, - "type": "module" + } } diff --git a/src/cli.js b/src/cli.js index c076b1a..8adf30e 100644 --- a/src/cli.js +++ b/src/cli.js @@ -1,6 +1,6 @@ // 抖音小云 CLI 工具 // 用法: -// node cli.js get-cookie 获取抖音 cookies 并同步到 douxiaoyun +// node cli.js cookies 获取抖音 cookies 并同步到 douxiaoyun // node cli.js sync 将本地 cookies 同步到 douxiaoyun // node cli.js unfavorite 取消点赞(单次执行) // node cli.js uncollect 取消收藏(单次执行) @@ -11,7 +11,7 @@ const validCommands = ["cookies", "sync", "unfavorite", "uncollect", "loop"]; if (!cmd || !validCommands.includes(cmd)) { console.log("抖音小云 CLI 工具\n"); - console.log("用法: node cli.js <命令>\n"); + console.log("用法: node src/cli.js <命令>\n"); console.log("可用命令:"); console.log(" cookies 获取抖音 cookies 并同步到 douxiaoyun"); console.log(" sync 将本地 cookies 同步到 douxiaoyun"); @@ -22,7 +22,6 @@ if (!cmd || !validCommands.includes(cmd)) { } async function main() { - console.log("cmd=" + cmd) switch (cmd) { case "cookies": console.log("🔑 获取抖音 cookies..."); @@ -34,10 +33,10 @@ async function main() { console.log("🔄 同步 cookies 到 douxiaoyun..."); const { syncToDouxiaoyun } = await import("./sync-douxiaoyun.js"); const { readFileSync } = await import("fs"); - const { resolve, dirname } = await import("path"); + const { resolve: pathResolve, dirname } = await import("path"); const { fileURLToPath } = await import("url"); const __dir = dirname(fileURLToPath(import.meta.url)); - const config = JSON.parse(readFileSync(resolve(__dir, "src", "config.json"), "utf8")); + const config = JSON.parse(readFileSync(pathResolve(__dir, "..", "config", "config.json"), "utf8")); await syncToDouxiaoyun(config.cookie, config.sec_user_id); break; diff --git a/src/get-cookie.js b/src/get-cookie.js index 71a1ca6..c9b14f1 100644 --- a/src/get-cookie.js +++ b/src/get-cookie.js @@ -10,7 +10,117 @@ import { fileURLToPath } from "url"; import { syncToDouxiaoyun } from "./sync-douxiaoyun.js"; const __dirname = dirname(fileURLToPath(import.meta.url)); -const CONFIG_PATH = resolve(__dirname, "config.json"); +const CONFIG_PATH = resolve(__dirname, "..", "config", "config.json"); + +async function main() { + console.log("🚀 启动浏览器..."); + + const browser = await chromium.launch({ + channel: "chrome", + headless: false, + args: ["--disable-blink-features=AutomationControlled"], + }); + + const context = await browser.newContext({ + viewport: { width: 1366, height: 768 }, + userAgent: + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", + }); + + const page = await context.newPage(); + + let capturedSecUserId = null; + + page.on("request", (request) => { + const url = request.url(); + if (url.includes("follow") || url.includes("aweme")) { + try { + const postData = request.postData(); + if (postData && postData.includes("sec_user_id")) { + const parsed = JSON.parse(postData); + if (parsed.sec_user_id) { + capturedSecUserId = parsed.sec_user_id; + console.log("✅ 从请求中捕获到 sec_user_id:", capturedSecUserId); + } + } + } catch (e) {} + } + }); + + page.on("response", async (response) => { + const url = response.url(); + if (url.includes("user/self") || url.includes("aweme")) { + try { + const body = await response.text(); + const match = body.match(/"sec_uid":"([^"]+)"/); + if (match && !capturedSecUserId) { + capturedSecUserId = match[1]; + console.log("✅ 从响应中捕获到 sec_user_id:", capturedSecUserId); + } + } catch (e) {} + } + }); + + console.log("📱 正在打开抖音登录页..."); + await page.goto("https://www.douyin.com/", { + waitUntil: "domcontentloaded", + timeout: 30000, + }); + + console.log("🔑 请在浏览器中扫码登录抖音..."); + console.log(" (等待登录完成,检测到登录后会自动继续)"); + + await waitForLogin(context); + console.log("✅ 登录成功!"); + + console.log("📋 正在访问个人主页获取 sec_user_id..."); + await page.goto("https://www.douyin.com/user/self?from_tab_name=main", { + waitUntil: "domcontentloaded", + timeout: 30000, + }); + await page.waitForTimeout(3000); + + if (!capturedSecUserId) { + capturedSecUserId = await extractSecUserIdFromPage(page); + } + + if (!capturedSecUserId) { + console.log("🖱️ 尝试触发关注列表请求以获取 sec_user_id..."); + try { + const followBtn = page.locator('[data-e2e="follow-count"]').first(); + if (await followBtn.isVisible({ timeout: 5000 })) { + await followBtn.click(); + await page.waitForTimeout(3000); + } + } catch (e) { + console.log(" 未能找到关注按钮,尝试其他方式..."); + } + if (!capturedSecUserId) { + capturedSecUserId = await extractSecUserIdFromPage(page); + } + } + + const cookies = await context.cookies(); + const cookieString = cookies.map((c) => `${c.name}=${c.value}`).join("; "); + + console.log("\n========================================"); + console.log("📋 获取结果:"); + console.log("========================================"); + console.log("Cookie:", cookieString.substring(0, 200) + "..."); + console.log("sec_user_id:", capturedSecUserId || "❌ 未获取到"); + console.log("========================================\n"); + + await browser.close(); + console.log("👋 浏览器已关闭"); + + saveConfig(cookieString, capturedSecUserId); + + if (capturedSecUserId) { + await syncToDouxiaoyun(cookieString, capturedSecUserId); + } else { + console.log("\n⚠️ 未获取到 sec_user_id,跳过服务器上传"); + } +} // ============================================================ // 保存配置到本地 config.json @@ -32,22 +142,11 @@ function saveConfig(cookieString, secUserId) { writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 4), "utf8"); console.log(`💾 配置已保存到: ${CONFIG_PATH}`); console.log(` updatedAt: ${config.updatedAt}`); - - if (!secUserId) { - console.log("\n⚠️ 未能自动获取 sec_user_id"); - console.log(" 请参考 README 手动获取:"); - console.log(" 1. 按 F12 打开开发者工具 -> Network"); - console.log(" 2. 筛选 'follow'"); - console.log(" 3. 点击关注按钮,在请求 Payload 中找到 sec_user_id"); - console.log(" 4. 手动填入 config.json"); - } } // ============================================================ // 辅助函数 // ============================================================ - -// 等待用户登录成功 async function waitForLogin(context) { return new Promise((resolve) => { const checkInterval = setInterval(async () => { @@ -61,25 +160,19 @@ async function waitForLogin(context) { }); } -// 从页面中提取 sec_user_id async function extractSecUserIdFromPage(page) { - // 方法1: 从页面 URL 提取 const url = page.url(); const urlMatch = url.match(/user\/([^?]+)/); if (urlMatch && urlMatch[1] !== "self") { return urlMatch[1]; } - // 方法2: 从页面 HTML 中的 __INITIAL_STATE__ 提取 try { const html = await page.content(); const match = html.match(/"sec_uid":"([^"]+)"/); if (match) return match[1]; - } catch (e) { - // 忽略 - } + } catch (e) {} - // 方法3: 从 window.__INITIAL_STATE__ 提取 try { const secUid = await page.evaluate(() => { const state = window.__INITIAL_STATE__; @@ -87,16 +180,17 @@ async function extractSecUserIdFromPage(page) { return null; }); if (secUid) return secUid; - } catch (e) { - // 忽略 - } + } catch (e) {} return null; } -main().catch((err) => { - console.error("❌ 脚本执行出错:", err.message); - process.exit(1); -}); - export { main }; + +const isMain = process.argv[1] && process.argv[1].endsWith("get-cookie.js"); +if (isMain) { + main().catch((err) => { + console.error("❌ 脚本执行出错:", err.message); + process.exit(1); + }); +} diff --git a/src/index.js b/src/index.js index b72ff8b..b0e34b9 100644 --- a/src/index.js +++ b/src/index.js @@ -2,7 +2,7 @@ import { fileURLToPath } from "url"; import { dirname, resolve } from "path"; const __dirname = dirname(fileURLToPath(import.meta.url)); -const config = JSON.parse(readFileSync(resolve(__dirname, "config.json"), "utf8")); +const config = JSON.parse(readFileSync(resolve(__dirname, "..", "config", "config.json"), "utf8")); const { cookie, sec_user_id } = config; async function getCollection() { diff --git a/src/sync-douxiaoyun.js b/src/sync-douxiaoyun.js index b982386..86a085f 100644 --- a/src/sync-douxiaoyun.js +++ b/src/sync-douxiaoyun.js @@ -4,17 +4,27 @@ import { fileURLToPath } from "url"; import { dirname, resolve } from "path"; const __dirname = dirname(fileURLToPath(import.meta.url)); -const DOUXIAOYUN_BASE = "http://dsv6.honor3.com:10101"; -const DOUXIAOYUN_USER = "douyin"; -const DOUXIAOYUN_PASS = "douyin2025"; +const SERVER_CONFIG_PATH = resolve(__dirname, "..", "config", "server-config.json"); + +// 读取服务器配置 +function getServerConfig() { + const raw = JSON.parse(readFileSync(SERVER_CONFIG_PATH, "utf8")); + return { + apiBase: raw.apiBase || "http://dsv6.honor3.com:10101", + username: raw.username || "douyin", + password: raw.password || "douyin2025", + }; +} async function syncToDouxiaoyun(cookieString, secUserId) { console.log("\n🔄 正在同步 cookies 到 douxiaoyun 服务..."); + const { apiBase, username, password } = getServerConfig(); + try { // 1. 登录 douxiaoyun,获取 JWT token - console.log(" 1/3 登录 douxiaoyun..."); - const loginResp = await fetch(`${DOUXIAOYUN_BASE}/api/auth/login`, { + console.log(` 1/3 登录 douxiaoyun (${username})...`); + const loginResp = await fetch(`${apiBase}/api/auth/login`, { method: "POST", headers: { "accept": "application/json, text/plain, */*", @@ -22,10 +32,9 @@ async function syncToDouxiaoyun(cookieString, secUserId) { "cache-control": "no-cache", "content-type": "application/json", "pragma": "no-cache", - "Referer": `${DOUXIAOYUN_BASE}/`, - "Referrer-Policy": "no-referrer-when-downgrade", + "Referer": `${apiBase}/`, }, - body: JSON.stringify({ username: DOUXIAOYUN_USER, password: DOUXIAOYUN_PASS }), + body: JSON.stringify({ username, password }), }); if (!loginResp.ok) throw new Error(`登录失败: HTTP ${loginResp.status}`); @@ -39,17 +48,13 @@ async function syncToDouxiaoyun(cookieString, secUserId) { // 2. 查询现有配置 console.log(" 2/3 查询现有配置..."); - const configResp = await fetch(`${DOUXIAOYUN_BASE}/api/config/paged`, { + const configResp = await fetch(`${apiBase}/api/config/paged`, { method: "POST", headers: { "accept": "application/json, text/plain, */*", - "accept-language": "zh-CN,zh;q=0.9", "authorization": `Bearer ${token}`, - "cache-control": "no-cache", "content-type": "application/json", - "pragma": "no-cache", - "Referer": `${DOUXIAOYUN_BASE}/`, - "Referrer-Policy": "no-referrer-when-downgrade", + "Referer": `${apiBase}/`, }, body: JSON.stringify({ pageIndex: 1, pageSize: 10 }), }); @@ -73,17 +78,13 @@ async function syncToDouxiaoyun(cookieString, secUserId) { secUserId: secUserId || existingConfig.secUserId || "", }; - const updateResp = await fetch(`${DOUXIAOYUN_BASE}/api/config/update`, { + const updateResp = await fetch(`${apiBase}/api/config/update`, { method: "POST", headers: { "accept": "application/json, text/plain, */*", - "accept-language": "zh-CN,zh;q=0.9", "authorization": `Bearer ${token}`, - "cache-control": "no-cache", "content-type": "application/json", - "pragma": "no-cache", - "Referer": `${DOUXIAOYUN_BASE}/`, - "Referrer-Policy": "no-referrer-when-downgrade", + "Referer": `${apiBase}/`, }, body: JSON.stringify(updateBody), });