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.'); } }