qq-bot/douyin/index.ts

84 lines
2.6 KiB
TypeScript

import path from 'path';
import { chromium } from 'playwright';
const __dirname = path.dirname(new URL(import.meta.url).pathname);
export async function downloadDouyinMedia(targetUrl: string, target_id?: string) {
const USER_DATA_DIR = path.join(__dirname, 'profiles/site1');
const TARGET_URL = targetUrl;
const HEADLESS = true;
const TIMEOUT = 10000;
const context = await chromium.launchPersistentContext(USER_DATA_DIR, {
headless: HEADLESS,
});
try {
const page = context.pages()[0] || (await context.newPage());
await page.goto(TARGET_URL, { waitUntil: 'load' });
await page.waitForSelector('.xgplayer', { timeout: TIMEOUT }).catch(() => {
});
if (target_id) {
await page.evaluate(`window.f_down_target_id = "${target_id}"; window.f_down_server_port = ${process.env.PORT || 6100}`)
}
await page.evaluate(async () => {
const d = (globalThis as any).document;
const vEle = d.querySelector('.xgplayer video source')
const isVideo = !!vEle && !vEle.src.includes('.mp3');
const urls: string[] = isVideo ? [d.querySelector(".xgplayer video source").src] : [
...new Set(
Array.from(d.querySelectorAll('.xgplayer img'))
// @ts-ignore
.map(img => img?.src)
.filter(Boolean)
),
];
async function urlToBlob(url: string) {
const res = await fetch(url, { credentials: "omit" });
if (!res.ok) throw new Error(`fetch image failed: ${res.status}`);
return await res.blob(); // 保持原 MIME
}
const fd = new FormData();
// 附带标题作为一个普通字段
fd.append("title", d.title || "");
let idx = 1;
for (const src of urls) {
const blob = await urlToBlob(src); // ✅ 直接用字符串
const ext = (blob.type && blob.type.split("/")[1]) || "bin";
// @ts-ignore
fd.append(`file_${idx}`, blob, `media_${idx}.${ext}`);
idx++;
}
// @ts-ignore
const url = new URL(`http://127.0.0.1:${window.f_down_server_port}/upload`);
url.searchParams.set("title", d.title || "");
url.searchParams.set("type", isVideo ? "video" : "image");
// @ts-ignore
url.searchParams.set("target_id", window.f_down_target_id || "");
const r = await fetch(url.toString(), {
method: "POST",
body: fd,
});
// 为了在 evaluate 内确保完成整个请求-响应流程,读取下响应
const json = await r.json().catch(() => ({}));
return json;
});
await page.waitForTimeout(1000)
} finally {
await context.close();
console.log('Done.');
}
}