This commit is contained in:
fengchuanhn@gmail.com
2026-05-21 00:19:11 +08:00
parent 1325ff92e5
commit 7f57dfa9ed
31 changed files with 10437 additions and 12 deletions

View File

@@ -0,0 +1,197 @@
"use strict";
/**
* 抖音创作者中心发布(对齐 Electron publishToDouyinPuppeteer 版)
*/
const fs = require("fs");
const { delay } = require("./publish_browser.js");
async function clickIfVisible(page, selector) {
const el = await page.$(selector);
if (!el) return false;
try {
await el.click();
return true;
} catch {
return false;
}
}
async function typeIntoFirst(page, selectors, text) {
for (const sel of selectors) {
const el = await page.$(sel);
if (!el) continue;
try {
await el.click({ clickCount: 3 });
await page.keyboard.down("Control");
await page.keyboard.press("a");
await page.keyboard.up("Control");
await page.keyboard.press("Backspace");
await el.type(text, { delay: 30 });
return true;
} catch {
/* try next */
}
}
return false;
}
async function publishToDouyin(page, params) {
const { videoPath, coverPath, title, tags = [], autoPublish } = params;
if (!fs.existsSync(videoPath)) throw new Error("视频文件不存在");
if (!fs.existsSync(coverPath)) throw new Error("封面文件不存在");
globalThis.__native?.emitProgress?.("正在打开抖音上传页…");
const uploadUrl = "https://creator.douyin.com/creator-micro/content/upload";
await page.goto(uploadUrl, { waitUntil: "domcontentloaded" });
await delay(3000);
let currentUrl = page.url();
if (currentUrl.includes("login") || currentUrl.includes("passport")) {
throw new Error("未登录抖音,请先在步骤 07 点击「登录平台」");
}
let avatar = await page.$("#header-avatar");
if (!avatar) {
await delay(5000);
avatar = await page.$("#header-avatar");
}
if (!avatar) {
throw new Error("抖音登录已过期,请重新登录");
}
if (!page.url().includes("content/upload")) {
await page.goto(uploadUrl, { waitUntil: "domcontentloaded" });
await delay(3000);
}
globalThis.__native?.emitProgress?.("正在上传视频…");
await page.waitForSelector('input[type="file"]', { timeout: 60000 });
const fileInputs = await page.$$('input[type="file"]');
if (!fileInputs.length) throw new Error("未找到视频上传控件");
await fileInputs[0].uploadFile(videoPath);
await delay(5000);
globalThis.__native?.emitProgress?.("等待视频处理…");
const start = Date.now();
let ready = false;
while (Date.now() - start < 300000 && !ready) {
const titleBox = await page.$('textarea[placeholder*="标题"], input[placeholder*="标题"]');
if (titleBox) {
try {
const disabled = await page.evaluate((el) => el.disabled, titleBox);
if (!disabled) ready = true;
} catch {
ready = true;
}
}
if (!ready) await delay(2000);
}
globalThis.__native?.emitProgress?.("正在上传封面…");
try {
await clickIfVisible(page, 'button');
const buttons = await page.$$("button");
for (const btn of buttons) {
const text = await page.evaluate((el) => el.textContent || "", btn);
if (String(text).includes("选择封面")) {
await btn.click();
await delay(2000);
break;
}
}
const imgInput = await page.$('input[type="file"][accept*="image"]');
if (imgInput) {
await imgInput.uploadFile(coverPath);
await delay(3000);
}
const doneBtns = await page.$$("button");
for (const btn of doneBtns) {
const text = await page.evaluate((el) => el.textContent || "", btn);
if (String(text).trim() === "完成") {
await btn.click();
await delay(2000);
break;
}
}
} catch (e) {
console.warn("封面上传跳过:", e.message);
}
globalThis.__native?.emitProgress?.("正在填写标题与话题…");
const titleText = String(title || "").slice(0, 30);
await typeIntoFirst(
page,
[
'input[placeholder*="填写作品标题"]',
'input[placeholder*="标题"]',
'textarea[placeholder*="标题"]',
],
titleText,
);
const tagList = Array.isArray(tags) ? tags : [];
if (tagList.length) {
const descOk = await typeIntoFirst(
page,
[
'textarea[placeholder*="描述"]',
'textarea[placeholder*="简介"]',
'motion[contenteditable="true"]',
'div[contenteditable="true"]',
],
"",
);
if (descOk) {
for (const tag of tagList) {
const t = String(tag).trim();
if (!t) continue;
const clean = t.startsWith("#") ? t : `#${t}`;
await page.keyboard.type(clean, { delay: 40 });
await page.keyboard.press("Space");
await delay(400);
}
}
}
if (!autoPublish) {
return {
success: false,
status: "manual_pending",
message: "内容已填写,请在浏览器中检查并手动点击发布",
videoUrl: page.url(),
keepPageOpen: true,
};
}
globalThis.__native?.emitProgress?.("正在点击发布…");
const publishBtns = await page.$$("button");
let clicked = false;
for (const btn of publishBtns) {
const text = (await page.evaluate((el) => el.textContent || "", btn)).trim();
if (text === "发布" || text === "确定发布") {
await btn.click();
clicked = true;
break;
}
}
if (!clicked) {
return {
success: false,
status: "manual_pending",
message: "未找到发布按钮,请手动发布",
videoUrl: page.url(),
};
}
await delay(5000);
return {
success: true,
status: "success",
message: "已提交发布",
videoUrl: page.url(),
};
}
module.exports = { publishToDouyin };