Files
mcn-short-video/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-aicreate.mjs
T

183 lines
9.6 KiB
JavaScript
Raw Normal View History

/* cdp-verify-aicreate.mjs — 验证 AI创作 弹窗 prompt 注入(09-01 改动闭环)
* 覆盖点:①#/rewrites 页 AI创作 按钮(noPersonaNote P0 bug 修复后不再报错)
* ②账号详情视频列表 AI创作 按钮(人设+视频选题注入)
* 方法:CDP 9223 → 复用现有 8900 标签页 → hook window.confirmRun 捕获 opts(不真实执行任务)
* 运行:node cdp-verify-aicreate.mjs
*/
const CDP = 'http://127.0.0.1:9223';
async function getWs(targetId) {
const tabs = await (await fetch(CDP + '/json')).json();
const t = tabs.find((x) => x.id === targetId);
if (!t) throw new Error('target not found: ' + targetId);
return t.webSocketDebuggerUrl;
}
function cdp(ws, method, params = {}) {
return new Promise((resolve, reject) => {
const id = ++cdp._id;
const w = new WebSocket(ws);
const timer = setTimeout(() => { try { w.close(); } catch (e) {} reject(new Error('timeout: ' + method)); }, 30000);
w.onopen = () => w.send(JSON.stringify({ id, method, params }));
w.onmessage = (ev) => {
const m = JSON.parse(ev.data);
if (m.id !== id) return;
clearTimeout(timer);
try { w.close(); } catch (e) {}
m.error ? reject(new Error(method + ': ' + JSON.stringify(m.error))) : resolve(m.result);
};
w.onerror = () => { clearTimeout(timer); reject(new Error('ws error: ' + method)); };
});
}
cdp._id = 0;
async function evalJs(ws, expr) {
const r = await cdp(ws, 'Runtime.evaluate', { expression: expr, awaitPromise: true, returnByValue: true });
if (r.exceptionDetails) throw new Error('eval err: ' + JSON.stringify(r.exceptionDetails).slice(0, 300));
return r.result && r.result.value;
}
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function waitFor(ws, expr, timeout = 40000, label = '') {
const t0 = Date.now();
while (Date.now() - t0 < timeout) {
try { if (await evalJs(ws, expr)) return true; } catch (e) {}
await sleep(800);
}
throw new Error('waitFor timeout: ' + label + ' -> ' + expr);
}
async function main() {
const tabs = await (await fetch(CDP + '/json')).json();
const wb = tabs.find((t) => t.url.includes('localhost:8900') || t.url.includes('127.0.0.1:8900'));
if (!wb) throw new Error('未找到 8900 工作台标签页,请先在内置浏览器打开工作台');
const ws = await getWs(wb.id);
console.log('▶ 复用标签页:', wb.url);
// ---------- ① #/rewrites 页 ----------
console.log('\n【① #/rewrites 页 AI创作 按钮】');
await evalJs(ws, `location.hash = '#/rewrites'`);
await cdp(ws, 'Page.reload', { ignoreCache: true });
await waitFor(ws, `!!document.querySelector('[data-rewrite]')`, 40000, 'rewrites 列表渲染');
await sleep(500);
// hook confirmRun 捕获 opts
await evalJs(ws, `window.__cap = null; window.__origConfirmRun = window.confirmRun;
window.confirmRun = (o) => { window.__cap = o; }; true`);
// 找一条「有人设账号」的已解析视频按钮(俊希 3 / 旧梦留声机 1629 优先),并判断其解析内容是否含「# 选题」段
const parsedInfo = await evalJs(ws, `(async () => {
const prefer = [3, 1629];
const btns = [...document.querySelectorAll('[data-rewrite]')];
let fallback = null;
for (const b of btns) {
const r = JSON.parse(b.dataset.rewrite);
if (!r.parsed) continue;
let hasTopic = false;
try { const vd = await api('/api/dsh/video-detail?videoId=' + encodeURIComponent(r.id)); hasTopic = !!extractVideoTopic((vd.parse && vd.parse.content) || ''); } catch (e) {}
const info = { idx: btns.indexOf(b), name: r.name, title: r.title, accId: r.accountId, parsed: r.parsed, hasTopic };
if (!fallback) fallback = info;
if (prefer.includes(r.accountId)) return info;
}
return fallback;
})()`);
console.log('已解析视频:', JSON.stringify(parsedInfo));
if (!parsedInfo) throw new Error('无已解析视频按钮可测');
await evalJs(ws, `document.querySelectorAll('[data-rewrite]')[${parsedInfo.idx}].click(); true`);
await waitFor(ws, `!!window.__cap`, 30000, 'confirmRun 捕获(已解析)');
const cap1 = await evalJs(ws, `JSON.stringify(window.__cap)`);
const c1 = JSON.parse(cap1);
console.log(' title:', c1.title, '| label:', c1.label);
console.log(' target:', c1.target);
const p1 = c1.prompt || '';
console.log(' prompt 含【账号设定(人设卡)】:', p1.includes('【账号设定(人设卡)】'));
console.log(' prompt 含【视频选题】:', p1.includes('【视频选题】'));
// 有选题段 → 直接用选题;无选题段(分镜头表等)→ 带解析内容 + 提炼指令(09-01 用户要求)
if (parsedInfo.hasTopic) {
console.log(' prompt 含 # 选题:', p1.includes('# 选题'), '(视频实际含选题段: true)');
} else {
console.log(' prompt 含【视频解析内容】:', p1.includes('【视频解析内容(供提炼选题)】'), '(视频实际含选题段: false)');
console.log(' prompt 含提炼指令:', p1.includes('提炼出该视频的选题'));
}
console.log(' prompt 含 noPersonaNote 泄漏:', p1.includes('undefined'));
const ok1 = c1.title === 'AI创作'
&& p1.includes('【账号设定(人设卡)】')
&& (parsedInfo.hasTopic ? (p1.includes('【视频选题】') && p1.includes('# 选题')) : (p1.includes('【视频解析内容(供提炼选题)】') && p1.includes('提炼出该视频的选题')))
&& !p1.includes('undefined');
console.log(' ✅ 已解析分支断言:', ok1 ? 'PASS' : 'FAIL');
// 未解析视频按钮(parsed=false)
await evalJs(ws, `window.__cap = null; true`);
const unparsedInfo = await evalJs(ws, `(() => {
const btns = [...document.querySelectorAll('[data-rewrite]')];
for (const b of btns) { const r = JSON.parse(b.dataset.rewrite); if (!r.parsed) return { idx: btns.indexOf(b), name: r.name, title: r.title, url: r.url }; }
return null;
})()`);
console.log('未解析视频:', JSON.stringify(unparsedInfo));
if (unparsedInfo) {
await evalJs(ws, `document.querySelectorAll('[data-rewrite]')[${unparsedInfo.idx}].click(); true`);
await waitFor(ws, `!!window.__cap`, 30000, 'confirmRun 捕获(未解析)');
const p2 = JSON.parse(await evalJs(ws, `JSON.stringify(window.__cap)`)).prompt || '';
const ok2 = p2.includes('请先解析这个抖音视频') && !p2.includes('undefined');
console.log(' 未解析分支(先解析再创作+无 undefined 泄漏):', ok2 ? 'PASS' : 'FAIL');
} else {
console.log(' 无未解析视频可测(跳过)');
}
await evalJs(ws, `window.confirmRun = window.__origConfirmRun; true`);
// ---------- ② 账号详情视频列表 ----------
console.log('\n【② 账号详情视频列表 AI创作 按钮】');
const accId = parsedInfo.accId || 1629;
await evalJs(ws, `location.hash = '#/account/${accId}'`);
await cdp(ws, 'Page.reload', { ignoreCache: true });
await waitFor(ws, `!!document.querySelector('[data-rewrite]')`, 40000, '账号详情视频列表渲染');
await sleep(500);
await evalJs(ws, `window.__cap = null; window.__origConfirmRun = window.confirmRun;
window.confirmRun = (o) => { window.__cap = o; }; true`);
// 账号详情已解析视频(详情页视频都属于当前账号,取第一条 parsed 即可;同时判定其解析内容是否含选题段)
const dt = await evalJs(ws, `(async () => {
const btns = [...document.querySelectorAll('[data-rewrite]')];
for (const b of btns) {
const r = JSON.parse(b.dataset.rewrite);
if (!r.parsed || !r.id) continue;
let hasTopic = false;
try { const vd = await api('/api/dsh/video-detail?videoId=' + encodeURIComponent(r.id)); hasTopic = !!extractVideoTopic((vd.parse && vd.parse.content) || ''); } catch (e) {}
return { idx: btns.indexOf(b), name: r.name, title: r.title, id: r.id, parsed: r.parsed, hasTopic };
}
return null;
})()`);
console.log('账号详情已解析视频:', JSON.stringify(dt));
if (dt) {
await evalJs(ws, `document.querySelectorAll('[data-rewrite]')[${dt.idx}].click(); true`);
await waitFor(ws, `!!window.__cap`, 40000, '账号详情 confirmRun 捕获');
const c3 = JSON.parse(await evalJs(ws, `JSON.stringify(window.__cap)`));
const p3 = c3.prompt || '';
console.log(' title:', c3.title, '| label:', c3.label);
console.log(' prompt 含【账号设定(人设卡)】:', p3.includes('【账号设定(人设卡)】'));
console.log(' prompt 含【视频选题】:', p3.includes('【视频选题】'));
if (dt.hasTopic) {
console.log(' prompt 含 # 选题:', p3.includes('# 选题'), '(视频实际含选题段: true)');
} else {
console.log(' prompt 含【视频解析内容】:', p3.includes('【视频解析内容(供提炼选题)】'), '(视频实际含选题段: false)');
console.log(' prompt 含提炼指令:', p3.includes('提炼出该视频的选题'));
}
console.log(' prompt 含 undefined 泄漏:', p3.includes('undefined'));
console.log(' prompt 长度:', p3.length);
const ok3 = c3.title === 'AI创作'
&& p3.includes('【账号设定(人设卡)】')
&& p3.includes('【视频选题】')
&& (dt.hasTopic ? p3.includes('# 选题') : (p3.includes('【视频解析内容(供提炼选题)】') && p3.includes('提炼出该视频的选题')))
&& !p3.includes('undefined');
console.log(' ✅ 账号详情分支断言:', ok3 ? 'PASS' : 'FAIL');
} else {
console.log(' 账号详情无可测已解析视频(跳过)');
}
await evalJs(ws, `window.confirmRun = window.__origConfirmRun; true`);
console.log('\n==== 验证完成 ====');
}
main().catch((e) => { console.error('❌', e.message); process.exit(1); });