245 lines
14 KiB
JavaScript
245 lines
14 KiB
JavaScript
/* 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,signal:aborter.signal});assertLive(epoch);
|
||
if(result?.cancelled)throw new Error('导入已取消');
|
||
await event('formatting');assertLive(epoch);
|
||
window.DWW11Formatter.format(job.source);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({...local,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=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;
|
||
}
|
||
const revision=stopRevision,epoch=run.epoch;
|
||
await rpc('accept',{id:job.id,...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);
|
||
setInterval(tick,2000);tick();
|
||
})();
|