dww11速发 v0.2.0
By Amethyst Eralbrunia Sys & ChatGPT
dww11速发 v0.2.0
By Longtail Amethyst Eralbrunia & ChatGPT
下载与文章
原创正文![]() |
diff --git a/README.md b/README.md index 3148ec8..bdad4c2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ 将新闻下载、文章打包、浏览器导入、排版与换号联动整合到本地控制台。 -**当前版本:0.2.0。已接通下载 → 入队 → 导入 → 排版 → 人工发表 → 审核中详情核对 → 受理记账 → 回首页 → 下一篇或换号。** 结果页需同时核对真实主题 ID、首帖作者、版块、最终提交标题和标题区域的审核中标志。图文检查有差异则保留已受理去重、暂停后续处理,不自动编辑或重发。尚无公开成功页样本,不能凭审核中标志消失认定已公开;完整真实发布与换号链路仍需小批验收。 +**当前版本:0.2.1。下载 → 入队 → 导入 → 排版 → 人工发表 → 网站审核提示 → 受理记账 → 回首页 → 下一篇或换号。** 暂时只以网站提示“新主题需要审核,您的帖子通过审核后才能显示”判定发表成功,状态仍记为已受理、审核中。原详情页标题、作者、版块与图文核对代码已注释保留,当前不执行。 ## 启动 @@ -35,9 +35,9 @@ uv run --no-project --python 3.12 --with-requirements requirements.txt python ap 3. 填写目标版块 `fid`、每账号最少/最多篇数和处理间隔。先核对历史发帖记录,再勾选确认并保存。版块和篇数不会替用户选择。 4. 登录论坛,建议只保留一个执行标签。点击控制台“开始 / 继续”。执行器识别账号后领取一篇任务,进入指定新帖页、导入、排版,停止在人工审核阶段。 5. 在论坛检查账号、版块、标题、图片位置、正文和来源,并亲手点击“发表帖子”。程序不调用发帖提交函数,不发送发帖请求,也不自动保存草稿。 -6. 人工点击时保存最终提交快照。快速提示页仅作为候选证据,等待详情页核对;审核中计入受理一次,公开数量另记。真实主题 ID 来自页面链接中的 `tid`,不将 `/thread/…html` 的整串数字当作 TID;首帖 PID 单独保存。 -7. 图文无差异且流程仍在运行时,先持久记账,再回首页;在设定间隔后处理下一篇。达到账号目标且仍有待处理文章时调用换号插件,确认新 UID 和插件空闲后创建下一批次。队列为空直接结束。 -8. 等待结果超过 45 秒仍无足够证据时暂停;结果不明可在原执行标签打开正确详情页进行被动核对。图片加载最多观察 15 秒,附件区图片、占位图、正文文字或图片顺序差异都会暂停后续处理。 +6. 人工点击时保存最终提交快照。观察网站提示“新主题需要审核,您的帖子通过审核后才能显示”即登记受理一次;暂不要求详情页标题、作者、版块、主题 ID 或图文匹配,不猜测帖子 ID。 +7. 先持久记账与去重,再回首页;在设定间隔后处理下一篇。达到账号目标且仍有待处理文章时调用换号插件,确认新 UID 和插件空闲后创建下一批次。队列为空直接结束;暂停或终止后仅记账,不自动继续。 +8. 等待超过 45 秒仍未观察到审核提示则暂停,不重复发表。仅有详情页“审核中”标志不再触发自动记账;旧的待核对任务需人工处理,不能凭此次规则改动推定成功。 9. 已有现场结果可在暂停后使用队列中的“补核已受理结果”导入观察 JSON,并人工确认文章关联。支持本项目 `local-records/publish-result-*.json` 结构,字段含 title、uid、fid、threadId、postId、url、status;补核须与已有人工提交快照匹配,不增加当前批次计数。历史补核不会自动检查发布后的图文完整性。 ## 终止、恢复与冲突 @@ -118,3 +118,7 @@ node test/browser.test.cjs 更新油猴中的 `browser/auto-news.user.js` 至 0.2.0,连接脚本沿用现有 0.1.1。更新本地代码后重启服务、重新配对并刷新论坛;不会自动继续任务。新增真实 TID 与 PID 分离、链接别名防重、被动结果恢复、图文差异暂停、历史结果补核,以及记账后回首页的持久步骤。旧受理记录如只有路径数字会保留原值并标记主题身份待核对,不猜测删除 URL 后缀。 换号过程中插件暂停、超过 120 秒未确认、或执行后超过 10 秒插件空闲但 UID 未变化,流程会暂停;请在插件内核对并恢复,不自动重复抽号。图片 HTTP 500 的服务端根因仍未解决。 + +## 0.2.1 临时审核提示规则 + +更新油猴中的 `browser/auto-news.user.js` 至 0.2.1,并重启本地服务、重新配对后刷新论坛页面。连接脚本无需更新。结果适配器和执行器中的旧详情页核对代码以行注释保留;后端旧核对接口继续保留供人工历史补核使用。自动受理使用当前任务的人工提交批次校验,审核提示可不含帖子链接,不创建虚构帖子 ID。 diff --git a/browser/auto-news.user.js b/browser/auto-news.user.js index d22b3c5..bdf58f3 100644 --- a/browser/auto-news.user.js +++ b/browser/auto-news.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name 新闻工作台 · 导入排版与流程 // @namespace local.dww11.auto-news -// @version 0.2.0 +// @version 0.2.1 // @match https://www.dww11.com/* // @match https://dww11.com/* // @match https://house.dww11.com/* @@ -741,41 +741,52 @@ window.DWW11Formatter = Object.freeze({ function parameter(href,key,base){try{return new URL(href,base).searchParams.get(key);}catch{return null;}} function aid(image){return String(image.getAttribute('aid')||image.id||'').match(/(?:attachimg_|aimg_)?(\d+)$/)?.[1]||null;} function inspect(doc,url,snapshot={}) { - const titleNode=doc.querySelector('#thread_subject'); - if(!titleNode) { - const prompt=doc.querySelector('#messagetext, .messagetext'); - const text=clean(prompt?.textContent); - if(/^(?:抱歉[,,]\s*)?(?:您填写的)?(?:帖子)?标题.{0,40}(?:超过|超出).{0,40}(?:字符|长度|限制)/.test(text))return {kind:'rejected',error:text}; - return {kind:'waiting',prompt:text,candidate:prompt?.querySelector('a[href]')?.href||''}; - } - const region=titleNode.closest('td')||titleNode.closest('h1')?.parentElement; - if(!region)return {kind:'incomplete',error:'缺少标题状态区域'}; - const ids=new Set([...region.querySelectorAll('a[href]')].filter(a=>parameter(a.href,'mod',url)==='viewthread').map(a=>parameter(a.href,'tid',url)).filter(x=>/^\d+$/.test(x))); - if(ids.size!==1)return {kind:'incomplete',error:'详情页真实主题编号缺失或冲突'}; - const thread_id=[...ids][0],body=doc.querySelector('[id^="postmessage_"]'); - const pid=body?.id.match(/^postmessage_(\d+)$/)?.[1]; - const post=pid&&doc.querySelector('#post_'+pid); - if(!post||clean(post.querySelector('#postnum'+pid)?.textContent)!=='楼主')return {kind:'incomplete',error:'无法确认当前正文属于首帖'}; - const authors=post?[...post.querySelectorAll('.plc .pti .authi a[href], .pls .authi a[href]')]:[]; - const uids=new Set(authors.map(a=>parameter(a.href,'uid',url)).filter(x=>/^\d+$/.test(x))); - const fids=new Set([...doc.querySelectorAll('#pt a[href], .mumucms_pt a[href]')].filter(a=>parameter(a.href,'mod',url)==='forumdisplay').map(a=>parameter(a.href,'fid',url)).filter(x=>/^\d+$/.test(x))); - if(!body||!pid||uids.size!==1||fids.size!==1)return {kind:'incomplete',error:'首帖作者、正文或版块证据不足'}; - const moderation=[...region.querySelectorAll('span.xg1')].some(n=>/\(\s*审核中\s*\)|(\s*审核中\s*)/.test(n.textContent)); - // A public-page sample is not available yet. Absence of moderation is not proof of publication. - if(!moderation)return {kind:'incomplete',error:'尚无可验证的公开或审核中状态'}; - const allImages=[...body.querySelectorAll('img')]; - const images=allImages.filter(img=>aid(img)).map(img=>({aid:aid(img),src:img.getAttribute('src'),loaded:img.complete&&img.naturalWidth>0&&!/\/none\.gif(?:[?#]|$)/.test(img.src||img.getAttribute('src')||''),file:img.getAttribute('file')||img.getAttribute('zoomfile')})); - const expected=(snapshot.images||[]).map(img=>String(img.aid||img.id||'').match(/(?:attachimg_|aimg_)?(\d+)$/)?.[1]).filter(Boolean); - const issues=[]; - if(allImages.some(img=>!aid(img)))issues.push('正文存在无法关联附件的图片'); - if(expected.length!==(snapshot.images||[]).length)issues.push('提交快照存在无法识别附件编号的图片'); - if(JSON.stringify(expected)!==JSON.stringify(images.map(img=>img.aid)))issues.push('首帖正文图片引用或顺序与提交快照不一致'); - if(images.some(img=>!img.loaded))issues.push('结果页正文图片尚未加载或加载失败'); - if(snapshot.text&&clean(body.innerText||body.textContent)!==clean(snapshot.text))issues.push('结果正文与最终提交快照存在文字差异'); - const attachments=post?[...post.querySelectorAll('.pattl img, .attm img')].map(img=>({aid:aid(img),src:img.getAttribute('src')})):[]; - if(attachments.length)issues.push('结果页存在附件区图片,请核对正文位置'); - return {kind:'accepted',thread_id,first_post_pid:pid,post_url:url,title:clean(titleNode.textContent),author_uid:[...uids][0],fid:Number([...fids][0]),state:'accepted_pending_moderation', - evidence:{adapter:1,thread_id,first_post_pid:pid,title:clean(titleNode.textContent),author_uid:[...uids][0],fid:Number([...fids][0]),moderation:true,images,attachments,integrity_issues:issues}}; + const prompt=doc.querySelector('#messagetext, .messagetext'); + const text=clean(prompt?.textContent); + const message='新主题需要审核,您的帖子通过审核后才能显示'; + if(text.includes(message))return { + kind:'accepted_prompt',state:'accepted_pending_moderation', + evidence:{adapter:'moderation_prompt',message,observed_text:text,observed_url:url} + }; + if(/^(?:抱歉[,,]\s*)?(?:您填写的)?(?:帖子)?标题.{0,40}(?:超过|超出).{0,40}(?:字符|长度|限制)/.test(text))return {kind:'rejected',error:text}; + return {kind:'waiting',prompt:text}; + + // 暂停详情页核对:按用户要求注释保留,待恢复时启用。 + // const titleNode=doc.querySelector('#thread_subject'); + // if(!titleNode) { + // const prompt=doc.querySelector('#messagetext, .messagetext'); + // const text=clean(prompt?.textContent); + // if(/^(?:抱歉[,,]\s*)?(?:您填写的)?(?:帖子)?标题.{0,40}(?:超过|超出).{0,40}(?:字符|长度|限制)/.test(text))return {kind:'rejected',error:text}; + // return {kind:'waiting',prompt:text,candidate:prompt?.querySelector('a[href]')?.href||''}; + // } + // const region=titleNode.closest('td')||titleNode.closest('h1')?.parentElement; + // if(!region)return {kind:'incomplete',error:'缺少标题状态区域'}; + // const ids=new Set([...region.querySelectorAll('a[href]')].filter(a=>parameter(a.href,'mod',url)==='viewthread').map(a=>parameter(a.href,'tid',url)).filter(x=>/^\d+$/.test(x))); + // if(ids.size!==1)return {kind:'incomplete',error:'详情页真实主题编号缺失或冲突'}; + // const thread_id=[...ids][0],body=doc.querySelector('[id^="postmessage_"]'); + // const pid=body?.id.match(/^postmessage_(\d+)$/)?.[1]; + // const post=pid&&doc.querySelector('#post_'+pid); + // if(!post||clean(post.querySelector('#postnum'+pid)?.textContent)!=='楼主')return {kind:'incomplete',error:'无法确认当前正文属于首帖'}; + // const authors=post?[...post.querySelectorAll('.plc .pti .authi a[href], .pls .authi a[href]')]:[]; + // const uids=new Set(authors.map(a=>parameter(a.href,'uid',url)).filter(x=>/^\d+$/.test(x))); + // const fids=new Set([...doc.querySelectorAll('#pt a[href], .mumucms_pt a[href]')].filter(a=>parameter(a.href,'mod',url)==='forumdisplay').map(a=>parameter(a.href,'fid',url)).filter(x=>/^\d+$/.test(x))); + // if(!body||!pid||uids.size!==1||fids.size!==1)return {kind:'incomplete',error:'首帖作者、正文或版块证据不足'}; + // const moderation=[...region.querySelectorAll('span.xg1')].some(n=>/\(\s*审核中\s*\)|(\s*审核中\s*)/.test(n.textContent)); + // // A public-page sample is not available yet. Absence of moderation is not proof of publication. + // if(!moderation)return {kind:'incomplete',error:'尚无可验证的公开或审核中状态'}; + // const allImages=[...body.querySelectorAll('img')]; + // const images=allImages.filter(img=>aid(img)).map(img=>({aid:aid(img),src:img.getAttribute('src'),loaded:img.complete&&img.naturalWidth>0&&!/\/none\.gif(?:[?#]|$)/.test(img.src||img.getAttribute('src')||''),file:img.getAttribute('file')||img.getAttribute('zoomfile')})); + // const expected=(snapshot.images||[]).map(img=>String(img.aid||img.id||'').match(/(?:attachimg_|aimg_)?(\d+)$/)?.[1]).filter(Boolean); + // const issues=[]; + // if(allImages.some(img=>!aid(img)))issues.push('正文存在无法关联附件的图片'); + // if(expected.length!==(snapshot.images||[]).length)issues.push('提交快照存在无法识别附件编号的图片'); + // if(JSON.stringify(expected)!==JSON.stringify(images.map(img=>img.aid)))issues.push('首帖正文图片引用或顺序与提交快照不一致'); + // if(images.some(img=>!img.loaded))issues.push('结果页正文图片尚未加载或加载失败'); + // if(snapshot.text&&clean(body.innerText||body.textContent)!==clean(snapshot.text))issues.push('结果正文与最终提交快照存在文字差异'); + // const attachments=post?[...post.querySelectorAll('.pattl img, .attm img')].map(img=>({aid:aid(img),src:img.getAttribute('src')})):[]; + // if(attachments.length)issues.push('结果页存在附件区图片,请核对正文位置'); + // return {kind:'accepted',thread_id,first_post_pid:pid,post_url:url,title:clean(titleNode.textContent),author_uid:[...uids][0],fid:Number([...fids][0]),state:'accepted_pending_moderation', + // evidence:{adapter:1,thread_id,first_post_pid:pid,title:clean(titleNode.textContent),author_uid:[...uids][0],fid:Number([...fids][0]),moderation:true,images,attachments,integrity_issues:issues}}; } return {inspect,clean}; }); @@ -789,7 +800,7 @@ window.DWW11Formatter = Object.freeze({ 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; + let skipBusy=false, preparation=null, stopRevision=0 /*, resultSeenAt=0 */; const pending=new Map(); function rpc(route,body={}) { return new Promise((resolve,reject)=>{ @@ -886,7 +897,7 @@ window.DWW11Formatter = Object.freeze({ 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});}) + .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);} @@ -928,22 +939,24 @@ window.DWW11Formatter = Object.freeze({ } if(!['user_submitting','submit_unknown'].includes(job.state))return; const snapshot=job.snapshot||local?.snapshot||{}; - const result=window.DWW11Results?.inspect(document,location.href,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') { + // 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,...result}); + 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()}); @@ -956,9 +969,9 @@ window.DWW11Formatter = Object.freeze({ 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(job.state==='user_submitting')await rpc('event',{id:job.id,state:'submit_unknown',error:result?.error||'未观察到网站新主题审核提示,请人工核对是否已受理'}); if(run.running)await rpc('stop'); - status('发布结果待核对;打开对应详情页可继续核对,不会重发'); + status('未观察到网站审核提示,已暂停;请人工核对,不会重发'); } async function switchAccount() { if(working||halted)return; @@ -1022,5 +1035,20 @@ window.DWW11Formatter = Object.freeze({ 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(); })(); diff --git a/browser/results.js b/browser/results.js index ce7c7bd..60bab8b 100644 --- a/browser/results.js +++ b/browser/results.js @@ -9,41 +9,52 @@ function parameter(href,key,base){try{return new URL(href,base).searchParams.get(key);}catch{return null;}} function aid(image){return String(image.getAttribute('aid')||image.id||'').match(/(?:attachimg_|aimg_)?(\d+)$/)?.[1]||null;} function inspect(doc,url,snapshot={}) { - const titleNode=doc.querySelector('#thread_subject'); - if(!titleNode) { - const prompt=doc.querySelector('#messagetext, .messagetext'); - const text=clean(prompt?.textContent); - if(/^(?:抱歉[,,]\s*)?(?:您填写的)?(?:帖子)?标题.{0,40}(?:超过|超出).{0,40}(?:字符|长度|限制)/.test(text))return {kind:'rejected',error:text}; - return {kind:'waiting',prompt:text,candidate:prompt?.querySelector('a[href]')?.href||''}; - } - const region=titleNode.closest('td')||titleNode.closest('h1')?.parentElement; - if(!region)return {kind:'incomplete',error:'缺少标题状态区域'}; - const ids=new Set([...region.querySelectorAll('a[href]')].filter(a=>parameter(a.href,'mod',url)==='viewthread').map(a=>parameter(a.href,'tid',url)).filter(x=>/^\d+$/.test(x))); - if(ids.size!==1)return {kind:'incomplete',error:'详情页真实主题编号缺失或冲突'}; - const thread_id=[...ids][0],body=doc.querySelector('[id^="postmessage_"]'); - const pid=body?.id.match(/^postmessage_(\d+)$/)?.[1]; - const post=pid&&doc.querySelector('#post_'+pid); - if(!post||clean(post.querySelector('#postnum'+pid)?.textContent)!=='楼主')return {kind:'incomplete',error:'无法确认当前正文属于首帖'}; - const authors=post?[...post.querySelectorAll('.plc .pti .authi a[href], .pls .authi a[href]')]:[]; - const uids=new Set(authors.map(a=>parameter(a.href,'uid',url)).filter(x=>/^\d+$/.test(x))); - const fids=new Set([...doc.querySelectorAll('#pt a[href], .mumucms_pt a[href]')].filter(a=>parameter(a.href,'mod',url)==='forumdisplay').map(a=>parameter(a.href,'fid',url)).filter(x=>/^\d+$/.test(x))); - if(!body||!pid||uids.size!==1||fids.size!==1)return {kind:'incomplete',error:'首帖作者、正文或版块证据不足'}; - const moderation=[...region.querySelectorAll('span.xg1')].some(n=>/\(\s*审核中\s*\)|(\s*审核中\s*)/.test(n.textContent)); - // A public-page sample is not available yet. Absence of moderation is not proof of publication. - if(!moderation)return {kind:'incomplete',error:'尚无可验证的公开或审核中状态'}; - const allImages=[...body.querySelectorAll('img')]; - const images=allImages.filter(img=>aid(img)).map(img=>({aid:aid(img),src:img.getAttribute('src'),loaded:img.complete&&img.naturalWidth>0&&!/\/none\.gif(?:[?#]|$)/.test(img.src||img.getAttribute('src')||''),file:img.getAttribute('file')||img.getAttribute('zoomfile')})); - const expected=(snapshot.images||[]).map(img=>String(img.aid||img.id||'').match(/(?:attachimg_|aimg_)?(\d+)$/)?.[1]).filter(Boolean); - const issues=[]; - if(allImages.some(img=>!aid(img)))issues.push('正文存在无法关联附件的图片'); - if(expected.length!==(snapshot.images||[]).length)issues.push('提交快照存在无法识别附件编号的图片'); - if(JSON.stringify(expected)!==JSON.stringify(images.map(img=>img.aid)))issues.push('首帖正文图片引用或顺序与提交快照不一致'); - if(images.some(img=>!img.loaded))issues.push('结果页正文图片尚未加载或加载失败'); - if(snapshot.text&&clean(body.innerText||body.textContent)!==clean(snapshot.text))issues.push('结果正文与最终提交快照存在文字差异'); - const attachments=post?[...post.querySelectorAll('.pattl img, .attm img')].map(img=>({aid:aid(img),src:img.getAttribute('src')})):[]; - if(attachments.length)issues.push('结果页存在附件区图片,请核对正文位置'); - return {kind:'accepted',thread_id,first_post_pid:pid,post_url:url,title:clean(titleNode.textContent),author_uid:[...uids][0],fid:Number([...fids][0]),state:'accepted_pending_moderation', - evidence:{adapter:1,thread_id,first_post_pid:pid,title:clean(titleNode.textContent),author_uid:[...uids][0],fid:Number([...fids][0]),moderation:true,images,attachments,integrity_issues:issues}}; + const prompt=doc.querySelector('#messagetext, .messagetext'); + const text=clean(prompt?.textContent); + const message='新主题需要审核,您的帖子通过审核后才能显示'; + if(text.includes(message))return { + kind:'accepted_prompt',state:'accepted_pending_moderation', + evidence:{adapter:'moderation_prompt',message,observed_text:text,observed_url:url} + }; + if(/^(?:抱歉[,,]\s*)?(?:您填写的)?(?:帖子)?标题.{0,40}(?:超过|超出).{0,40}(?:字符|长度|限制)/.test(text))return {kind:'rejected',error:text}; + return {kind:'waiting',prompt:text}; + + // 暂停详情页核对:按用户要求注释保留,待恢复时启用。 + // const titleNode=doc.querySelector('#thread_subject'); + // if(!titleNode) { + // const prompt=doc.querySelector('#messagetext, .messagetext'); + // const text=clean(prompt?.textContent); + // if(/^(?:抱歉[,,]\s*)?(?:您填写的)?(?:帖子)?标题.{0,40}(?:超过|超出).{0,40}(?:字符|长度|限制)/.test(text))return {kind:'rejected',error:text}; + // return {kind:'waiting',prompt:text,candidate:prompt?.querySelector('a[href]')?.href||''}; + // } + // const region=titleNode.closest('td')||titleNode.closest('h1')?.parentElement; + // if(!region)return {kind:'incomplete',error:'缺少标题状态区域'}; + // const ids=new Set([...region.querySelectorAll('a[href]')].filter(a=>parameter(a.href,'mod',url)==='viewthread').map(a=>parameter(a.href,'tid',url)).filter(x=>/^\d+$/.test(x))); + // if(ids.size!==1)return {kind:'incomplete',error:'详情页真实主题编号缺失或冲突'}; + // const thread_id=[...ids][0],body=doc.querySelector('[id^="postmessage_"]'); + // const pid=body?.id.match(/^postmessage_(\d+)$/)?.[1]; + // const post=pid&&doc.querySelector('#post_'+pid); + // if(!post||clean(post.querySelector('#postnum'+pid)?.textContent)!=='楼主')return {kind:'incomplete',error:'无法确认当前正文属于首帖'}; + // const authors=post?[...post.querySelectorAll('.plc .pti .authi a[href], .pls .authi a[href]')]:[]; + // const uids=new Set(authors.map(a=>parameter(a.href,'uid',url)).filter(x=>/^\d+$/.test(x))); + // const fids=new Set([...doc.querySelectorAll('#pt a[href], .mumucms_pt a[href]')].filter(a=>parameter(a.href,'mod',url)==='forumdisplay').map(a=>parameter(a.href,'fid',url)).filter(x=>/^\d+$/.test(x))); + // if(!body||!pid||uids.size!==1||fids.size!==1)return {kind:'incomplete',error:'首帖作者、正文或版块证据不足'}; + // const moderation=[...region.querySelectorAll('span.xg1')].some(n=>/\(\s*审核中\s*\)|(\s*审核中\s*)/.test(n.textContent)); + // // A public-page sample is not available yet. Absence of moderation is not proof of publication. + // if(!moderation)return {kind:'incomplete',error:'尚无可验证的公开或审核中状态'}; + // const allImages=[...body.querySelectorAll('img')]; + // const images=allImages.filter(img=>aid(img)).map(img=>({aid:aid(img),src:img.getAttribute('src'),loaded:img.complete&&img.naturalWidth>0&&!/\/none\.gif(?:[?#]|$)/.test(img.src||img.getAttribute('src')||''),file:img.getAttribute('file')||img.getAttribute('zoomfile')})); + // const expected=(snapshot.images||[]).map(img=>String(img.aid||img.id||'').match(/(?:attachimg_|aimg_)?(\d+)$/)?.[1]).filter(Boolean); + // const issues=[]; + // if(allImages.some(img=>!aid(img)))issues.push('正文存在无法关联附件的图片'); + // if(expected.length!==(snapshot.images||[]).length)issues.push('提交快照存在无法识别附件编号的图片'); + // if(JSON.stringify(expected)!==JSON.stringify(images.map(img=>img.aid)))issues.push('首帖正文图片引用或顺序与提交快照不一致'); + // if(images.some(img=>!img.loaded))issues.push('结果页正文图片尚未加载或加载失败'); + // if(snapshot.text&&clean(body.innerText||body.textContent)!==clean(snapshot.text))issues.push('结果正文与最终提交快照存在文字差异'); + // const attachments=post?[...post.querySelectorAll('.pattl img, .attm img')].map(img=>({aid:aid(img),src:img.getAttribute('src')})):[]; + // if(attachments.length)issues.push('结果页存在附件区图片,请核对正文位置'); + // return {kind:'accepted',thread_id,first_post_pid:pid,post_url:url,title:clean(titleNode.textContent),author_uid:[...uids][0],fid:Number([...fids][0]),state:'accepted_pending_moderation', + // evidence:{adapter:1,thread_id,first_post_pid:pid,title:clean(titleNode.textContent),author_uid:[...uids][0],fid:Number([...fids][0]),moderation:true,images,attachments,integrity_issues:issues}}; } return {inspect,clean}; }); diff --git a/browser/runner.js b/browser/runner.js index 6a7a98b..f4238e8 100644 --- a/browser/runner.js +++ b/browser/runner.js @@ -7,7 +7,7 @@ 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; + let skipBusy=false, preparation=null, stopRevision=0 /*, resultSeenAt=0 */; const pending=new Map(); function rpc(route,body={}) { return new Promise((resolve,reject)=>{ @@ -104,7 +104,7 @@ 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});}) + .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);} @@ -146,22 +146,24 @@ } if(!['user_submitting','submit_unknown'].includes(job.state))return; const snapshot=job.snapshot||local?.snapshot||{}; - const result=window.DWW11Results?.inspect(document,location.href,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') { + // 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,...result}); + 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()}); @@ -174,9 +176,9 @@ 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(job.state==='user_submitting')await rpc('event',{id:job.id,state:'submit_unknown',error:result?.error||'未观察到网站新主题审核提示,请人工核对是否已受理'}); if(run.running)await rpc('stop'); - status('发布结果待核对;打开对应详情页可继续核对,不会重发'); + status('未观察到网站审核提示,已暂停;请人工核对,不会重发'); } async function switchAccount() { if(working||halted)return; @@ -240,5 +242,20 @@ 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(); })(); diff --git a/build_browser.py b/build_browser.py index d4ef2fe..18e3251 100644 --- a/build_browser.py +++ b/build_browser.py @@ -4,7 +4,7 @@ ROOT = Path(__file__).resolve().parent header = '''// ==UserScript== // @name 新闻工作台 · 导入排版与流程 // @namespace local.dww11.auto-news -// @version 0.2.0 +// @version 0.2.1 // @match https://www.dww11.com/* // @match https://dww11.com/* // @match https://house.dww11.com/* diff --git a/static/index.html b/static/index.html index 2201e66..f1eb14d 100644 --- a/static/index.html +++ b/static/index.html @@ -4,10 +4,10 @@
By Amethyst Eralbrunia Sys & ChatGPT
By Longtail Amethyst Eralbrunia & ChatGPT
原创正文![]() |