first commit
This commit is contained in:
41 files changed
+4964
No files matched your search
@@ -0,0 +1,173 @@
|
||||
/* 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;
|
||||
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',()=>{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,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) {
|
||||
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('标题校验失败');
|
||||
const aids=new Set(Object.values(result.images));
|
||||
for(const aid of aids) if(!snapshot.images.some(x=>x.id==='attachimg_'+aid))throw new Error('配图附件缺失:'+aid);
|
||||
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(!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);
|
||||
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';
|
||||
}
|
||||
if(job.state==='user_submitting') {
|
||||
// Result selectors intentionally await user-provided HTML; URL alone is not proof.
|
||||
if(!editorPage(job) || (local?.clickedAt && Date.now()-local.clickedAt>45000)) {
|
||||
await rpc('event',{id:job.id,state:'submit_unknown',error:'等待结果页 HTML 适配;候选页面:'+location.href});
|
||||
await rpc('stop');
|
||||
status('已记录候选结果页;尚不能自动核对。流程已暂停,请补充结果页 HTML。');
|
||||
}
|
||||
}else if(job.state==='submit_unknown')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) {status('请安装并重新加载带联动接口的换号插件');return;}
|
||||
await rpc('switch-begin');
|
||||
if(halted||!run?.running)return;
|
||||
button.click();status('正在换号;等待插件完成并核对新账号');
|
||||
}
|
||||
async function tick() {
|
||||
if(tickBusy)return;tickBusy=true;
|
||||
try {
|
||||
run=await rpc('heartbeat',{account:account(),url:location.href,extension:extension()});
|
||||
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;
|
||||
}
|
||||
const next=await rpc('next',{account:account(),extension:extension()});run=next.run;
|
||||
if(next.job)active=next.job;
|
||||
if(next.action==='prepare'||(next.action==='resume'&&next.job.state==='preparing'))void 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==='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();
|
||||
})();
|
||||
Reference in new issue
Block a user