Skip to content

feat(userscript): CLOSE_INVALID_DELAY via closePlan pattern (v22.4) - #21

Open
danny0119 wants to merge 2 commits into
OLmatter:mainfrom
danny0119:pr-d-rush-mode-v2
Open

feat(userscript): CLOSE_INVALID_DELAY via closePlan pattern (v22.4)#21
danny0119 wants to merge 2 commits into
OLmatter:mainfrom
danny0119:pr-d-rush-mode-v2

Conversation

@danny0119

@danny0119 danny0119 commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

CLOSE_INVALID_DELAY — closePlan pattern (v22.4)

Based on review feedback: uses schedule/plan/consume pattern instead of per-tick recalculation.


Design

preview result settles (busy/sold_out)
  → generate closePlan ONCE: { startedAt, delayMs, reason }
  → delayMs = CLOSE_INVALID_DELAY + random(-300, +300)

checkPayDialog()  — only consumes closePlan
  → if closePlan ready → return 'close'
  → else → return 'keep'

exitTask()       — also consumes closePlan
  → if closePlan ready AND dialog gone → exitTask

Key difference from previous approach:

  • Random delay generated once at plan creation, never per-tick
  • checkPayDialog no longer calculates elapsed/delay inline
  • exitTask no longer has its own hardcoded 2000ms check

Changes

Location Change
DEF CLOSE_INVALID_DELAY: 1500
Config panel Delay input after auto-close checkbox
closePlan variable {startedAt, delayMs, reason}, generated once when preview returns busy/sold_out
checkPayDialog 2-line plan consumption (was 14-line inline calc)
exitTask Uses closePlan + !isPayDialog() guard
scripts/userscripts/ copy Precise edits from main copy only — no historical drift

Verification

node --check glm-coding-helper.user.js                    OK
node --check scripts/userscripts/glm-coding-helper.user.js OK
git diff --check                                             OK
git diff vs olmatter/main: 2 files, +48/-27, clean

@danny0119
danny0119 force-pushed the pr-d-rush-mode-v2 branch from 4e26e2d to f6afbc3 Compare June 16, 2026 06:03
@OLmatter

Copy link
Copy Markdown
Owner

感谢提交,这个 PR 想解决的问题我理解,也认可方向:

  • CLOSE_INVALID_DELAY 这个需求本身是合理的;
  • 把“无效弹窗关闭延时”的计时起点从 taskClickTime 挪到 preview 结果返回之后,这个思路也是对的;
  • busy 分支不应该永远瞬关,这个判断也成立。

但我这边现在最大的顾虑,不是功能点本身,而是 改动边界和插入方式


1. 当前 PR 的顾虑:不是纯补丁,混入了很多无关历史改动

你在描述里说这是基于 v22.2 做的一处功能增量,但从实际 diff 看:

  • 根目录 glm-coding-helper.user.js 这份主文件,确实比较像围绕 CLOSE_INVALID_DELAY 做的小改动;
  • scripts/userscripts/glm-coding-helper.user.js 这份副本,已经明显混进了大量和本 PR 无关的结构性变化。

这些无关变化包括但不限于:

  • captcha 会话封装
  • rush 相关状态整理
  • 验证码点击延时策略
  • click schedule / plan 拆分
  • 一些旧状态机收口
  • 其它这两天主线才刚整理过的 userscript 结构

也就是说,这个 PR 现在不是“只加 CLOSE_INVALID_DELAY”,而是“功能补丁 + 一批历史状态一起带进来”。

这对我们来说风险很大,因为:

  • 后面很难审计哪些改动是为了 CLOSE_INVALID_DELAY
  • 很难判断 scripts/userscripts/ 这个副本到底是跟主文件同步,还是又重新漂移了
  • 仓库很容易重新回到“补丁泥球”状态

所以我们现在不太敢直接合,并不是觉得这个功能不该做,而是这个 PR 目前不是干净增量


2. CLOSE_INVALID_DELAY 应该加在哪里,方向其实是有讲究的

这个我想说清楚,因为我们最近在验证码随机延迟上已经踩过很多坑。

关键经验:不能随便往热路径里插延时 / 插随机

最近我们给验证码点字加随机延迟时,一开始也失败了很多次。

问题不是“随机延迟这个需求本身不对”,而是:

  • 一开始插错了位置;
  • 甚至连 220ms 这种看似简单的点击间隔,都不能随便在热路径里直接改;
  • 后来我们是把验证码链路一点点拆开,拆出明确的 schedule / plan / config 层之后,才终于摸清楚真正安全的插入点

这个经验同样适用于你这次的 CLOSE_INVALID_DELAY


3. 这个功能正确的插入点,不应该是“每次 checkPayDialog 都重新算一遍”

你现在的写法里,busy / sold_out 关闭延时是放在 checkPayDialog() 里动态计算的,而且每次 tick 都重新抽一次 jitter。

这就是我们比较担心的点。

因为这样一来:

  • 同一个弹窗在每次轮询时,关闭阈值都在漂移;
  • 不是“这一轮决定等多久”,而是“每检查一次就重新决定一次等多久”;
  • 这会让关闭行为变得不可预测。

这和我们后来在验证码点击延时上总结出的结论是一致的:

真正安全的随机化 / 延时化,必须在一轮流程开始时就确定计划,然后整轮复用。

不能边跑边重新算。


4. 我们认为更合适的改法

如果只看功能目标,CLOSE_INVALID_DELAY 最好这么做:

A. 计时起点

这个你方向是对的:

  • 不要再从 taskClickTime
  • 要从 preview 结果落地之后开始算
  • 也就是你现在引入的 previewFinishedAt

这部分我认同。

B. 延时计划

真正应该加的不是“一个高频 if 分支里的随机 delay”,而是:

  • 一轮 preview 结果出现后
  • 生成一次固定的 close plan
  • 例如:
    • invalidClosePlannedAt
    • invalidCloseDelayMs
    • invalidCloseReason (busy / sold_out)

也就是:

  • preview 结束时,抽一次随机值
  • 后面整轮都用这个值
  • 不要每次 checkPayDialog() 都重新抽

这和我们后来给验证码随机延迟找到的正确插入点,本质是一样的:

  • 先生成 plan
  • 再执行 plan
  • 不要在执行热路径里边跑边改 plan

C. checkPayDialog() 的职责

checkPayDialog() 更适合只做:

  • 看当前有没有有效 close plan
  • 看当前是否到达 plan 的触发时刻
  • 到了就 close
  • 没到就 keep

而不是在这里每次重新发明一次延时判断。

D. exitTask() 的职责

exitTask() 最好只做兜底收尾,不要让它再变成另一套“基于时间判断的关闭/退出主逻辑”。

否则就会变成:

  • checkPayDialog() 一套关闭时序
  • exitTask() 又一套退出时序

这种双状态机后面非常容易再次打架。


5. 我们为什么会特别在意“插入点”

因为这不是抽象担心,而是我们刚踩过坑:

  • 最近验证码随机延迟这块,一开始我们也是觉得“就加一点随机 delay,很简单”;
  • 结果一加就坏;
  • 后来才发现问题根本不是参数,而是插入层级错了
  • 直到把 captcha 链路拆出 config / schedule / plan / runner 这些明确层次之后,才终于能安全加进去。

所以这次我们对 CLOSE_INVALID_DELAY 的判断也是一样:

  • 功能不是不能做;
  • 但要加在“单轮关闭计划”这一层;
  • 不能直接把随机判断散在 checkPayDialog() 高频分支里;
  • 更不能顺手把一堆与本 PR 无关的历史 userscript 结构一起带进来。

6. 我们当前的结论

我们现在的顾虑主要有两类:

提交边界问题

  • scripts/userscripts/glm-coding-helper.user.js 明显带入了很多和本 PR 无关的历史改动;
  • 这让整个 PR 变成了“脏合风险”很高的提交。

实现层级问题

  • previewFinishedAt 这个方向是对的;
  • CLOSE_INVALID_DELAY 最好做成“一轮固定 close plan”,而不是每次 tick 动态重算 delay;
  • 正确插入点应该是:preview 结果落地后生成计划,checkPayDialog() 只消费计划

7. 建议怎么改

如果你愿意继续改,我们更希望看到这样的版本:

  1. 只保留与 CLOSE_INVALID_DELAY 直接相关的最小改动。
  2. scripts/userscripts/ 副本不要混入这两天主线其它 userscript 结构整理内容。
  3. previewFinishedAt 保留。
  4. 新增“一轮固定的 invalid dialog close plan”,例如:
    • startedAt
    • delayMs
    • reason
  5. busy / sold_out 都统一消费这份 plan。
  6. 不要在 checkPayDialog() 里每次轮询都重新随机一次。

如果按这个方向重新整理,我们会更容易接受这个功能。

- closePlan: single random delay generated once when preview settles
  (busy/sold_out), never recalculated per-tick
- checkPayDialog: only consumes closePlan, no inline random or delay calc
- exitTask: uses closePlan instead of hardcoded 2000ms
- Config panel: delay input (default 1500ms, +-300ms jitter on plan creation)
- scripts/userscripts: precise patch from main copy, no historical cruft

This follows the schedule/plan/consume pattern:
  generate closePlan once → checkPayDialog/exitTask only check if ready
@danny0119
danny0119 force-pushed the pr-d-rush-mode-v2 branch from f6afbc3 to 83e5756 Compare June 16, 2026 07:32
@danny0119 danny0119 changed the title feat(userscript): CLOSE_INVALID_DELAY v22.4 — configurable close delay with jitter feat(userscript): CLOSE_INVALID_DELAY via closePlan pattern (v22.4) Jun 16, 2026
@OLmatter

Copy link
Copy Markdown
Owner

感谢继续整理,这一版比上一版干净很多,方向也基本对了。

现在我这边看下来,还差两处收尾,改完我会更倾向直接合:

  1. sold_out 的“DOM 里其实有价格就别关”保护分支被删掉了。

旧逻辑里,PS.result === 'sold_out' 时,会先 readDialogPrices()。如果弹窗 DOM 里其实已经有有效价格,就 keep,不关闭。

这个保护不是多余逻辑,而是为了防一种前后端不一致的情况:接口层记录成 sold_out,但前端经过覆盖后,弹窗里其实已经出现了可支付内容。

你这版改完以后,sold_out 只要 closePlan 到时,就会直接 close。这样会有回归风险:本来能继续支付的弹窗,也可能被脚本按 sold_out 关掉。

我建议这里把原来这层保护补回去,至少在 sold_out 分支下,close 前先看一眼 readDialogPrices(),有价格就继续保留弹窗。

  1. closePlan 还没有跟任务生命周期完全绑定,退出或切换时需要统一清理。

当前 closePlan = null 只出现在:

  • checkPayDialog() 真正执行 close 时
  • sold_out 且 !isPayDialog() 的 exit 分支里

但普通的任务退出或切下一个任务时,exitTask() 自己没有统一清掉 closePlan。

这会留下一个风险:上一轮任务生成过 closePlan,但因为别的路径跳出了,没有把它清掉。下一轮任务如果也遇到 busy 或 sold_out,就可能复用上一轮残留的 plan,导致关闭时机串任务。

所以我建议在任务结束或切换的统一收尾点,把 closePlan = null 明确加上,让它真正跟随单轮任务生命周期,而不是只在部分分支里被清掉。

次要建议:

delayMs 这行最好也顺手做一下下限保护,比如 Math.max(0, ...)。不然如果用户把 CLOSE_INVALID_DELAY 设得比较小,理论上可能抽出负值,变成立即关闭。

总的来说,这版已经比上一版接近很多了,不是思路问题,主要就是这两处逻辑收尾。把这两点补上之后,我这边会更愿意直接合。

…ath.max

- sold_out branch: checks readDialogPrices() before closing (prevents
  closing dialogs that have valid DOM prices despite API sold_out)
- exitTask(): adds closePlan = null at unified task cleanup point
- delayMs: Math.max(0, ...) prevents negative values from small config
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants