save
This commit is contained in:
parent
a15b98cc3e
commit
375f0e50fa
@ -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"
|
||||
}
|
||||
|
||||
16
package.json
16
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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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),
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user