Files
mcn-short-video/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-aicreate.mjs
T
maogeigei 9589680dc3 feat(工作台): AI创作 分镜头表老视频先提炼选题再创作 + 修复 #/rewrites noPersonaNote 未定义 P0
- buildTopicBlock(): 有#选题段直接用;无(分镜头表等)带解析内容+提炼指令;空只带标题
- 账号详情视频列表 + #/rewrites 两入口同步改造,desc 注明分镜头表先提炼
- 修复 renderRewrites 分支引用未定义 noPersonaNote 导致弹窗打不开(ReferenceError)
- 验证脚本 cdp-verify-aicreate.mjs(hook confirmRun 捕获 prompt,按 hasTopic 动态断言)
2026-09-01 12:03:47 +08:00

184 lines
9.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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); });