/* 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(); })();