Files

263 lines
16 KiB
JavaScript
Raw Permalink 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.
/* Page-side orchestrator. This file never clicks or submits the publication form. */
(() => {
'use strict';
if (window.DWW11NewsRunner) return;
window.DWW11NewsRunner = true;
const SLOT='auto-news-current';
const owner=sessionStorage.getItem('auto-news-owner') || crypto.randomUUID();
sessionStorage.setItem('auto-news-owner',owner);
let run=null, working=false, halted=true, aborter=null, active=null, tickBusy=false;
let skipBusy=false, preparation=null, stopRevision=0 /*, resultSeenAt=0 */;
const pending=new Map();
function rpc(route,body={}) {
return new Promise((resolve,reject)=>{
const id=crypto.randomUUID();
const timer=setTimeout(()=>{pending.delete(id);reject(new Error('本地连接无响应'));},65000);
pending.set(id,{resolve,reject,timer});
document.dispatchEvent(new CustomEvent('dww11:local-request',{detail:JSON.stringify({id,route,body:{owner,...body}})}));
});
}
document.addEventListener('dww11:local-response',event=>{
let data;try{data=JSON.parse(event.detail);}catch{return;}
const waiter=pending.get(data.id);if(!waiter)return;
clearTimeout(waiter.timer);pending.delete(data.id);
data.error?waiter.reject(new Error(data.error)):waiter.resolve(data.value);
});
const status=message=>document.dispatchEvent(new CustomEvent('dww11:connection-status',{detail:message}));
document.addEventListener('dww11:auto-stop',()=>{stopRevision++;halted=true;aborter?.abort(new Error('流程已终止'));});
function account() {
const element=document.querySelector('#mumucms_username');
if(!element)return null;
const name=[...element.childNodes].filter(n=>n.nodeType===Node.TEXT_NODE).map(n=>n.textContent).join('').trim();
const links=[element,...element.querySelectorAll('a')].filter(e=>e.href);
let uid=null;
for(const link of links) {
const href=link.href;
uid=new URL(href,location.href).searchParams.get('uid') || href.match(/space-uid-(\d+)/)?.[1];
if(uid)break;
}
return uid&&name?{uid,name}:null;
}
function extension() {
try{return JSON.parse(document.querySelector('#dww11-extension-panel')?.getAttribute('data-auto-news-state') || '{}');}catch{return {};}
}
function assertLive(epoch) {
if(halted||!run?.running||run.epoch!==epoch)throw new Error('流程已暂停或终止');
if(active?.account?.uid!==account()?.uid)throw new Error('当前账号与任务不一致');
}
function editorSnapshot() {
const body=document.querySelector('#e_iframe')?.contentDocument?.body;
if(!body)throw new Error('找不到可视化编辑器');
return {title:document.querySelector('#subject')?.value || '',
images:[...body.querySelectorAll('img')].map(x=>({id:x.id,aid:x.getAttribute('aid'),src:x.getAttribute('src')})),
// Snapshot only final submitted content; never includes hidden tokens or the full form.
html:body.innerHTML.slice(0,500000), text:body.innerText.slice(0,100000)};
}
function saveLocal(value) {sessionStorage.setItem(SLOT,JSON.stringify(value));}
function loadLocal(){try{return JSON.parse(sessionStorage.getItem(SLOT)||'null');}catch{return null;}}
async function event(state,extra={}) {return rpc('event',{id:active.id,state,epoch:run.epoch,...extra});}
async function fail(error) {
if(skipBusy)return;
status(error.message);
if(active)try{await event('needs_attention',{error:error.message});}catch{}
halted=true;aborter?.abort();document.dispatchEvent(new CustomEvent('dww11:auto-stop'));
}
function editorPage(job) {
const u=new URL(location.href);
return u.searchParams.get('mod')==='post'&&u.searchParams.get('action')==='newthread'&&u.searchParams.get('fid')===String(job.fid);
}
async function prepare(job) {
working=true;active=job;aborter=new AbortController();const epoch=run.epoch;
try {
assertLive(epoch);
if(!editorPage(job)) {
saveLocal({id:job.id});
location.assign(`https://www.dww11.com/forum.php?mod=post&action=newthread&fid=${job.fid}`);return;
}
if(!window.DWW11Importer||!window.DWW11Formatter)throw new Error('导入或排版模块尚未就绪,请检查安装');
status('正在检查文章包…');
const pack=await rpc('package',{id:job.id});assertLive(epoch);
const prepared=await window.DWW11Importer.stagePackage(pack);assertLive(epoch);
if(prepared.warnings?.length)throw new Error('文章需要人工处理:'+prepared.warnings.join(';'));
await event('importing');assertLive(epoch);status('上传图片并填写文章…');
const result=await window.DWW11Importer.importPrepared({automatic:true,verifyImages:false,signal:aborter.signal});assertLive(epoch);
if(result?.cancelled)throw new Error('导入已取消');
await event('formatting');assertLive(epoch);
window.DWW11Formatter.format(job.source,{verifyImages:false});assertLive(epoch);
const snapshot=editorSnapshot();
if(snapshot.title!==job.title)throw new Error('标题校验失败');
// 按用户要求暂停图片检查;保留原调用以便恢复。
// await window.DWW11Importer.verifyEditorImages(
// document.querySelector('#e_iframe').contentDocument.body, result.attachmentOrder, aborter.signal);
assertLive(epoch);
await event('awaiting_user_review',{snapshot});assertLive(epoch);
active={...job,state:'awaiting_user_review'};saveLocal({id:job.id});
status('已完成排版,请核对内容并亲手点击“发表帖子”。');
}catch(error){await fail(error);}finally{working=false;}
}
// Record only trusted user activation. Never replay a click, call submit(), or send a post request.
document.addEventListener('click',event=>{
if(skipBusy&&event.target.closest?.('#postsubmit')) {
event.preventDefault();event.stopImmediatePropagation();return;
}
if(!event.isTrusted||!event.target.closest?.('#postsubmit')||active?.state!=='awaiting_user_review')return;
try {
const snapshot=editorSnapshot();
const local={id:active.id,snapshot,clickedAt:Date.now(),observed:false};saveLocal(local);
rpc('event',{id:active.id,state:'user_submitting',epoch:run?.epoch,snapshot})
.then(()=>{const latest=loadLocal();if(latest?.clickedAt===local.clickedAt)saveLocal({...latest,observed:true});})
.catch(error=>status('提交记录待恢复:'+error.message));
active.state='user_submitting';status('已观察到人工点击,等待结果页核对。');
}catch(error){status('提交快照记录失败:'+error.message);}
},true);
document.addEventListener('submit',event=>{
if(skipBusy&&event.target.id==='postform') {event.preventDefault();event.stopImmediatePropagation();}
},true);
document.addEventListener('dww11:skip-article',async()=>{
if(skipBusy)return;
if(!active||!run||run.owner!==owner) {status('当前标签没有可跳过的文章');return;}
if(!['preparing','importing','formatting','awaiting_user_review','needs_attention'].includes(active.state)) {
status('文章已提交或结果待核对,请先人工核对');return;
}
skipBusy=true;halted=true;
document.dispatchEvent(new CustomEvent('dww11:skip-busy',{detail:true}));
const jobId=active.id,epoch=run.epoch;
aborter?.abort(new Error('正在跳过当前文章'));
status('正在跳过当前文章…');
try {
// Let cancelled imports finish their rollback before leaving the editor.
await preparation;
const result=await rpc('skip',{id:jobId,epoch});
sessionStorage.removeItem(SLOT);active=null;run=result.run;
status('已跳过,正在打开下一篇文章的发帖页…');
// Always load a fresh editor, including when the fid and URL are unchanged.
location.assign(`https://www.dww11.com/forum.php?mod=post&action=newthread&fid=${result.fid}`);
}catch(error){
try{await rpc('stop');}catch{}
status('跳过未完成,流程已暂停:'+error.message);
skipBusy=false;
document.dispatchEvent(new CustomEvent('dww11:skip-busy',{detail:false}));
}
});
async function observeResult(job) {
const local=loadLocal();
if(local?.id===job.id&&local.snapshot&&job.state==='awaiting_user_review') {
await rpc('event',{id:job.id,state:'user_submitting',epoch:run.epoch,snapshot:local.snapshot});
job.state='user_submitting';job.snapshot=local.snapshot;
}
if(!['user_submitting','submit_unknown'].includes(job.state))return;
const snapshot=job.snapshot||local?.snapshot||{};
const result=(local?.id===job.id&&local.promptResult)||window.DWW11Results?.inspect(document,location.href,snapshot);
if(result?.kind==='rejected'&&job.state==='user_submitting') {
await rpc('event',{id:job.id,state:'rejected',error:result.error});
status('网站明确拒绝本次提交,已暂停;核对并处理稿件后仍需亲手发表');return;
}
// 暂停详情页标题、作者、版块和图文核对;保留原代码。
// if(result?.kind==='accepted') {
// if(result.author_uid!==job.account.uid||result.fid!==job.fid||result.title!==window.DWW11Results.clean(snapshot.title)) {
// if(job.state==='user_submitting')await rpc('event',{id:job.id,state:'submit_unknown',error:'结果页标题、首帖作者或版块与提交任务不符'});
// await rpc('stop');status('结果不匹配,已暂停,请人工核对');return;
// }
// resultSeenAt ||= Date.now();
// if(result.evidence.integrity_issues.some(x=>x.includes('尚未加载'))&&Date.now()-resultSeenAt<15000) {
// status('已看到审核中详情,等待正文图片加载后核对');return;
// }
if(result?.kind==='accepted_prompt') {
const revision=stopRevision,epoch=run.epoch;
await rpc('accept',{id:job.id,attempt:job.attempt,...result});
sessionStorage.removeItem(SLOT);active=null;
// Re-read persistent control after accounting; Stop must suppress navigation and subsequent jobs.
run=await rpc('heartbeat',{account:account(),url:location.href,extension:extension()});
halted=!run.running;
if(!halted&&revision===stopRevision&&run.epoch===epoch)location.assign('https://www.dww11.com/');
else status('受理结果已保存;流程已暂停,请核对后明确继续');
return;
}
const clickedAt=local?.clickedAt||Number(job.submitted_at||0)*1000;
if(clickedAt&&Date.now()-clickedAt<45000) {
status(result?.prompt||result?.error||'等待网站提交结果,不会重复发表');return;
}
if(job.state==='user_submitting')await rpc('event',{id:job.id,state:'submit_unknown',error:result?.error||'未观察到网站新主题审核提示,请人工核对是否已受理'});
if(run.running)await rpc('stop');
status('未观察到网站审核提示,已暂停;请人工核对,不会重发');
}
async function switchAccount() {
if(working||halted)return;
if(location.origin!=='https://www.dww11.com'||location.pathname!=='/') {
location.assign('https://www.dww11.com/');return;
}
const ext=extension(),button=document.querySelector('#dww11-extension-next');
if(!ext.ready||!ext.idle||!button||button.disabled) {await rpc('stop');status('换号插件未就绪,流程已暂停,请检查插件后继续');return;}
const revision=stopRevision,epoch=run.epoch;
await rpc('switch-begin',{epoch});
if(halted||!run?.running||revision!==stopRevision)return;
button.click();status('正在换号;等待插件完成并核对新账号');
}
async function tick() {
if(tickBusy||skipBusy)return;tickBusy=true;
try {
run=await rpc('heartbeat',{account:account(),url:location.href,extension:extension()});
if(skipBusy)return;
halted=!run.running;
if(!run.running) {aborter?.abort();document.dispatchEvent(new CustomEvent('dww11:auto-stop'));}
if(working)return;
// Passive result recording is permitted after Stop, but no new business actions.
if(run.active) {
const local=loadLocal();
if(local?.id===run.active&&local.snapshot&&!run.running)status('流程已暂停;提交结果仍需核对');
}
if(!run.running) {
if(run.active && run.owner===owner) {const saved=await rpc('current');if(saved.job) {active=saved.job;await observeResult(saved.job);}}
status(run.reason || '流程已暂停');return;
}
if(run.active&&run.owner===owner) {
const saved=await rpc('current');
if(saved.job&&['user_submitting','submit_unknown'].includes(saved.job.state)) {active=saved.job;await observeResult(saved.job);return;}
}
const revision=stopRevision,epoch=run.epoch;
const next=await rpc('next',{account:account(),extension:extension(),url:location.href});run=next.run;
if(revision!==stopRevision||run.epoch!==epoch)return;
if(skipBusy)return;
if(next.job)active=next.job;
if(next.action==='prepare'||(next.action==='resume'&&next.job.state==='preparing'))preparation=prepare(next.job);
else if(next.action==='resume') {
await observeResult(next.job);
if(next.job.state==='awaiting_user_review') {
if(!editorPage(next.job)||!document.querySelector('#subject')?.value) await fail(new Error('待审稿页面已丢失,请人工核对旧稿,不自动重新导入'));
else status('等待你亲手发表;程序不再修改编辑器');
}
else if(['importing','formatting'].includes(next.job.state))await fail(new Error('导入阶段发生中断,请检查编辑器和已上传图片后处理任务'));
else if(next.job.state==='needs_attention')status(next.job.error);
}else if(next.action==='home') {
if(!halted&&run.running)location.assign('https://www.dww11.com/');
}else if(next.action==='switch')await switchAccount();
else if(next.action==='switch_wait')status('等待换号结果;若插件已暂停,请人工处理后继续');
else if(next.action==='login')status('未能读取当前账号 UID,请登录并确认账号元素');
else if(next.action==='other_tab')status('另一个标签持有任务;如需接管请先暂停');
else if(next.action==='interval')status('等待设定的处理间隔');
else status(run.reason || '等待任务');
}catch(error){status(error.message);halted=true;aborter?.abort();document.dispatchEvent(new CustomEvent('dww11:auto-stop'));}
finally{tickBusy=false;}
}
const takeover=document.createElement('button');takeover.type='button';takeover.textContent='接管已暂停任务';
takeover.style.cssText='position:fixed;left:16px;top:16px;z-index:2147483646;padding:8px;background:#fff;border:1px solid #aab;border-radius:6px';
takeover.onclick=async()=>{try{await rpc('takeover');status('已接管;核对当前页面后在工作台继续');}catch(error){status(error.message);}};
document.body.append(takeover);
// 提示可能短暂出现后跳转;及时保存到本次任务,避免两秒轮询漏掉。
function capturePrompt() {
const local=loadLocal();
if(!local?.id||!local.snapshot||!local.clickedAt||local.promptResult)return false;
if(active&&(active.id!==local.id||!['user_submitting','submit_unknown'].includes(active.state)))return false;
const result=window.DWW11Results?.inspect(document,location.href,local.snapshot);
if(result?.kind!=='accepted_prompt')return false;
saveLocal({...local,promptResult:result});
return true;
}
if(typeof MutationObserver!=='undefined')new MutationObserver(()=>{
if(capturePrompt())void tick();
}).observe(document.body,{childList:true,subtree:true,characterData:true});
// 跳转后的提示页可能已经渲染完成,首次轮询前也捕获一次。
capturePrompt();
setInterval(tick,2000);tick();
})();