AI summary
An Xposed module for Telegram that automates daily check-ins with bots. It learns check-in commands by observing manual button taps and automatically retries on network failure. Version 1.6.0 introduces a major internal refactor to resolve account crossover bugs, fix duplicate scheduling, and improve logging.
Generated by AI. May contain inaccuracies.
Screenshots
About this app
✨ 特性
自动学习:手动点一次机器人的签到按钮,模块自动记住「机器人 + 签到指令」,无需任何配置
每日一签:每天只签一次,签到请求发出成功即判定完成,当天绝不再发
已签提示:打开 Telegram 发现今天已全部签过 → 提示「今天已经签到过了 ✅」
断网补签:失败/断网自动重试,网络恢复立即补签,每天每目标最多 5 次
多目标:支持多个签到机器人同时托管,每个目标独立记录,互不干扰
纯本地:无服务器、无遥测,数据仅存于本机 SharedPreferences
🧠 工作原理(为什么不用猜指令)
不同机器人的签到指令五花八门(📅 签到、/qd、/checkin…),模块 不猜不匹配文本,而是:
观察真实网络请求:按钮点击最终都会变成一条真实消息请求(TL_messages_sendMessage),模块在 ConnectionsManager.sendRequest 处 Hook;
自动识别:当 未登记 的机器人(user.bot == true)收到一条 ≤20 字符的短指令,判定为"又一个签到按钮",自动加入目标;
之后每天向每个目标发送其各自的签到指令。
⚠️ 当前状态说明
JMB 界面版(推荐):以 LSPilot 插件形态发布,在 Telegram 内发 /jmb 管理,安装方式与源码见 wlmosv-png/TGAutoSign
APK 模块版:实验性,跨进程通信方案仍在迭代,暂不推荐日常使用
📲 安装
需要 LSPosed / Vector(API ≥ 102)环境,且设备已 Root
安装 APK(本模块无界面,安装后不需要打开)
LSPosed 管理器 → 模块 → 启用 TGAutoSign
作用域勾选 Telegram(org.telegram.messenger)
完全关闭 Telegram 后重新打开
看到 Toast 「TGAutoSign 注入成功: org.telegram.messenger」 → 注入完成 ✅
仅支持官方 Telegram。第三方客户端(Nekogram 等)以及 iOS 版不受影响。
🚀 使用(三步上手)
重启 Telegram(模块加载,此时还不认识任何目标)
打开签到机器人的聊天页,手动点一次签到按钮
屏幕弹出 ✅ 已添加新签到目标: xxx → 学习成功
部分按钮网络层也会自动识别,弹出 ✅ 已自动添加新签到目标
把所有要签到的机器人各点一次,之后全自动
What's new
- 更新日志 1.6.0 (123) — 2026-09-26 累积更新:自 1.5.8 以来的全部改动合并发布,含一次账号隔离体系重构。
- 架构更新 · Architecture 自 v1.0 以来最大的一次内部重构。无用户可见行为变化,但它是本次大量 bug 得以根治的前提。 The largest internal refactor since v1.0. No user-visible behaviour change, but it is the precondition that made this release's bug fixes possible.
- 账号隔离从「约定」变成「结构」 重构前,代码里有 75 处读「当前账号」、67 处读无参账号前缀 —— 全部读宿主的静态字段。 任何一处出现在异步回调或延迟任务里,就会串号。本次引入不可变账号快照(Ctx): 发起任务时锁定账号,执行时只认快照,从结构上杜绝串号,而不是靠每一处记得判断。
- Account isolation moved from convention to structure. Before the refactor the codebase read the "current account" in 75 places and the no-arg account prefix in 67 — all reading a host static field. Any one of them sitting inside an asynchronous callback or delayed task caused crossover. An immutable account snapshot (Ctx) is now used instead: the account is pinned when work is started and only the snapshot is honoured when it runs, preventing crossover structurally rather than relying on every call site remembering to check.
- 核心文件拆出 6 个职责单一的类 Keys(存储键唯一真相源)、AccountManager(账号解析与快照)、 PrefsStore(持久化与落盘策略)、SignStateStore(签到状态读写与不变式)、 SettingsRefs(设置面板控件容器)、SignLogic(无 Android 依赖的纯逻辑层)。 主文件仍承载业务编排,但每类问题都有了明确归属。
- Six single-responsibility classes extracted from the core file. Keys (single source of truth for storage keys), AccountManager (account resolution and snapshot), PrefsStore (persistence and flush policy), SignStateStore (sign-in state I/O and invariants), SettingsRefs (settings panel control container) and SignLogic (pure logic with no Android dependency). The main file still orchestrates the business, but every class of problem now has a clear home.
- 纯逻辑可单测 时间窗、退避、回复判定、签到闸门等逻辑已与 Android API 解耦,可在桌面直接跑断言 —— 本版单测从 115 条扩到 174 条,新增用例逐条对应本次修掉的 bug。
- Pure logic is now unit-testable. Time windows, backoff, reply verdicts and the sign gate are decoupled from Android APIs and can be asserted directly on a desktop — assertions grew from 115 to 174 in this release, each new case mapping to a bug fixed here.
- 存储键名收敛到唯一真相源 键生成函数原来散落在核心文件的 4 处,改一个键名要 grep 全文且极易漏。 (历史事故:日志名从 run.log 改为 run-YYYYMMDD.log 时漏改 4 处,静默失效。)
- Storage keys consolidated into a single source of truth. Key-building helpers were scattered across four places in the core file; renaming one key meant grepping the whole file and was easy to miss. A past incident: the log file name change from run.log to run-YYYYMMDD.log missed four call sites and failed silently.
- 落盘策略显式化 原来 244 处直接访问存储,落盘方式(apply / commit)没有任何规则。 现在状态变更一律 commit,其余 apply,并明确哪些变更必须落盘。
- Flush policy made explicit. There were 244 direct storage accesses with no rule for apply versus commit. State changes now always commit, everything else applies, and which changes must persist is explicit.
- 修复 · Fixed 账号串号:六条路径全部改为锁定账号(重要) 病根只有一个 —— 在异步回调或延迟任务里读「当前账号」。回调执行时用户可能已经切到别的账号, 于是拿新账号去发旧账号的目标。本次把这条根因的落点全部扫清,覆盖六条路径: 定时任务(延迟 1~5 分钟)、回调签到等面板(异步最长 8 秒)、面板事件补签(延迟 700 毫秒)、 网络层学习、收藏夹告警、结果归因与汇总通知。全部改为在发起时锁定账号,执行时只用锁定值。 此前用户报告「账号1自动给只有账号2配置的 bot 发了消息」,即由此而来。
- Account crossover — all six paths now pin the account. There was a single root cause: reading the "current account" inside an asynchronous callback or a delayed task. By the time the callback runs the user may have switched accounts, so a new account sent an older account's target. This release clears every instance across six paths: scheduled tasks (1–5 min delay), callback sign-in waiting for a panel (up to 8 s async), panel-triggered make-up sign-in (700 ms delay), network-layer learning, saved-message alerts, and result attribution plus summary notifications. All of them now capture the account when the work is initiated and use only that captured value. This is what produced the report "account 1 automatically messaged a bot that only exists on account 2".
- 越界保护反而读到别人的分区(重要) v1.5.8 加了一道保护:读到超出已登录数的索引就按 0 处理。出发点是好的,但前提是错的 —— 实测部分客户端(如 Nagram XF 登录 4 个账号)selectedAccount 会读到 7、9, 那些是合法索引,数据就存在 acc9_ 里。被改成 0 之后,模块读的是另一个账号的分区: 目标、已签记录、重试退避全部错位,表现为「账号1的配置变成了账号2的」。 现已回退:索引原值使用,只有负值(不可能合法)才跳过本轮。
- Out-of-range guard read another account's partition (important). v1.5.8 added a guard that coerced an index beyond the signed-in count to 0. The intent was reasonable, but the assumption was wrong: on some clients (e.g. Nagram XF with four accounts) selectedAccount reads 7 or 9, and those are legitimate indexes whose data lives under acc9_. Coercing to 0 made the module read another account's partition, misplacing targets, signed state and backoff — reported as "account 1's config turned into account 2's". Reverted: the index is used as-is, and only negative values (which cannot be valid) skip the round.
- 失败目标被反复重签(重要) 机器人连回两条消息是常态,两条消息的判定结果会互相抵消:第一条「正在签到,请稍后…」 被宽松模式当作成功并把重试计数清零,第二条「请先关注」判失败只把计数加到 1 —— 计数永远在 0 和 1 之间震荡,涨不到上限,于是每一轮都被重新选中。实测有目标一天被签了 8 次。 现在加两道熔断:确定性失败词(请先关注 / 活动已结束 / 已过期 等)命中即当日停止; 其它失败用当天独立计数(不受"成功清零"影响)累计 3 次后同样当日停止。跨天自动恢复。
- Failed targets were signed over and over (important). Robots commonly send two replies whose verdicts cancel each other out: the first ("signing in, please wait") is treated as success by loose mode and resets the retry counter, while the second ("please follow first") counts as a failure and bumps it to 1 — so the counter oscillates between 0 and 1, never reaching the cap, and the target is re-selected every round. One target was signed 8 times in a single day. Two circuit breakers are now in place: permanent-failure phrases (follow-first / event ended / expired) stop the target for the day immediately, and other failures accumulate in a day-scoped counter that is immune to the success reset. Both reset automatically the next day.
- 同一账号重复排期发送(重要) 排下一次定时任务时只检查了「内存中是否正在发送」,漏了「已发出但还没等到结论」。 内存状态在进程重启后是空的,于是重启后一遇网络恢复之类的触发,就会把同一个目标再排一次 —— 现象是同一账号同一目标被连发两条。现在两半一起查,并统一到一个判断入口,避免以后再分叉。
- Duplicate scheduling within one account (important). Scheduling the next timed task only checked "is a send in flight in memory", missing the other half: "already sent, still awaiting a verdict". In-memory state is empty after a process restart, so the first trigger — such as network recovery — queued the same target again, sending twice to the same account and target. Both halves are now checked together behind a single entry point so they cannot diverge again.
- 明明签到成功,却被判失败并退回「退避中」(重要) 回调按钮签到分两步。当前置命令已经完成签到、而第二步的按钮因为消息已更新被 Telegram 拒绝 (MESSAGE_ID_INVALID)时,模块把「第二步失败」当成了「签到整体失败」:先撤销已签标记, 再排一次退避重试。用户侧看到的是已经签过了,状态却是「退避中」,10 分钟后又白跑一次。 MESSAGE_ID_INVALID 只是"按钮过期了",不代表签到失败。现已改为:前置命令确认发送成功时, 按钮过期不再撤销已签,只清掉退避状态。
- A successful check-in was revoked and shown as "backoff" (important). Callback sign-in runs in two steps. When the pre-command had already completed the check-in but the second step's button was rejected by Telegram because the message had been updated (MESSAGE_ID_INVALID), the module treated "step two failed" as "the whole check-in failed": it revoked the signed marker and scheduled a backoff retry. Users saw a completed check-in displayed as "backoff", followed by another pointless attempt ten minutes later. MESSAGE_ID_INVALID only means the button is stale, not that the check-in failed. Now, when a pre-command was accepted, a stale button no longer revokes the sign-in — only the backoff state is cleared.
- 「等面板」永远超时走兜底(重要) 回调签到靠"面板刷新事件"驱动。但该事件的唯一入口开头有一句"启动后 30 秒未就绪就直接返回", 于是启动窗口内面板事件被整条丢弃 → 等面板必然超时 → 只能走 8 秒兜底 msg_id, 而兜底用的旧按钮往往已过期 → 报 MESSAGE_ID_INVALID。用户看到的是 机器人明明秒回,模块却一直走兜底。现在未就绪只限制"模块主动发起的动作",不再丢被动事件。
- Panel waiting always timed out into the fallback (important). Callback sign-in is driven by panel-refresh events, but the only entry point for those events began with "return if not ready for the first 30 seconds" — so during that window the events were dropped entirely, waiting always timed out, and the flow fell back to an 8-second stale msg_id that reported MESSAGE_ID_INVALID. Users saw the bot reply instantly while the module kept falling back. Not-ready now only throttles actions the module initiates, never passive events.
- 同类问题全量清查:还有三处会把成功当失败 同一个病根还有三处,全部无条件撤销了已签:① bot 不回结果(BOT_RESPONSE_TIMEOUT)—— 有些机器人本来就不回复签到结论;② 服务器限流(FLOOD_WAIT)—— 限流只是"稍后再试", 不代表没签上;③ 其他请求层错误 —— 请求已经成功发出并标了已签,后续报错不足以否定它。 现在统一用「是否已乐观标记为已签」来区分:已发出且标记成功时,后续第二步失败只清退避状态。
- Same class of bug, full sweep: three more places treated success as failure. Three more instances of the same root cause unconditionally revoked the sign-in: (1) the bot never replying (BOT_RESPONSE_TIMEOUT) — some robots simply never send a verdict; (2) server rate limiting (FLOOD_WAIT) — that only means "try again later", not that the check-in failed; (3) other request-layer errors — the request had already been sent and marked. All now share one rule: if the send succeeded and was marked, a later second-step failure clears only the backoff state.
- 「待确认」池按账号隔离,并自动迁移旧数据 待确认池原来是一个全局键,多个账号的候选目标混在一起,点「加入」还可能加到错的账号。 现按账号分键存储,并提供一次性迁移:旧数据搬到账号 1,若账号 1 已有内容则保留现有、不覆盖, 迁移完成后删除旧键。整个过程幂等,只执行一次。
- The "pending" pool is now per-account, with automatic migration. The pending pool used to be one global key, mixing candidates from every account, so tapping "Add" could file a target under the wrong one. It is now stored per account, with a one-time migration: legacy data moves to account 1, existing account-1 content wins (never overwritten), and the old key is removed afterwards. The whole step is idempotent and runs once.
- 「待确认」是个死状态:看得到、点不动(重要) 这个状态以前只写不读 —— 置位后除了"手动测试"没有任何清除入口,用户永远卡在「待确认」, 而重试计数已被清零 → 每天照发、照超时、照标待确认(死循环)。现在给出三个明确动作 (重试 / 忽略 / 删除),并把用户的选择记下来。
- "Pending" was a dead state: visible but un-actionable (important). The state used to be write-only: once set there was no way to clear it apart from a manual test, so users were stuck on "pending" forever while the retry counter had already been reset — sending, timing out and re-marking every day. Three explicit actions are now offered (retry / ignore / delete) and the user's choice is recorded.
- 回复判定用错账号(重要) 回复判定在异步回调里执行,读的是「当前账号」的目标列表。切过账号之后, 机器人发来的结论会被记到别的账号上。现在按消息所属账号取目标列表。
- Reply verdict could land on the wrong account (important). Reply verdicts ran in an async callback and read the current account's target list. After an account switch, a bot's verdict was recorded against the wrong account. The list is now taken from the account the message belongs to.
- 全账号签到时结果串到别的账号 结果统计内部读「当前账号」,于是账号 A 的成绩会显示成账号 B 的。现在改为显式传入账号, 并按账号独立聚合、逐账号提示与汇总通知。
- A full-account round reported one account's tally as another's. Result tallying read the "current account" internally, so account A's score was shown as account B's. It now takes an explicit account, aggregates per account, and reports and notifies per account.
- 清空配置会残留状态,导致重新添加的 bot 状态复活(重要) 条目 id 按 <会话>_<序号> 生成,清空配置后重新添加同一个 bot 会拿到同一个 id, 残留的冻结 / 暂停 / 已放弃状态被新条目直接继承,表现为"重新添加了但它就是不签"。 现在补齐了 v1.3.0 之后新增的全部状态键前缀,并增加孤儿状态键清理。
- Clearing config left state behind, resurrecting it on re-add (important). Entry ids are generated as <dialog>_<seq>, so re-adding the same bot after clearing config yields the same id and inherits leftover frozen / snoozed / given-up state — it "just won't sign in" after re-adding. All state-key prefixes added since v1.3.0 are now covered, plus orphan-state cleanup.
- 清空配置会误删账号级待确认池 清空配置的保留名单是精确字符串匹配,表达不了 acc<N>_ 这种不定后缀,于是旧的全局池被保留、 新的账号级池反而被删掉 —— 同一份数据换个键名后行为不一致。现在单独判定并保留。
- Clearing config wrongly deleted the per-account pending pool. The keep-list used exact string matching, which cannot express the variable acc<N>_ prefix, so the legacy global pool was kept while the new per-account pool was deleted — the same data behaved differently under a new key name. It is now matched and kept explicitly.
- 按钮反复过期导致无限重试 有些机器人的按钮随消息变化,每次重新拉取面板都会换一套 msg_id,模拟点击永远追不上。 模块会一直"拉新面板 → 点击 → 过期 → 再拉",一天白跑十几次。现在同一目标连续 3 次过期即熔断, 日志直接给出出路(把这条改成「文本指令」目标)。
- Stale buttons caused an endless retry loop. Some robots regenerate their buttons with each message, so every panel refresh yields a new msg_id and a simulated tap can never keep up. The module kept pulling a fresh panel, tapping, failing and pulling again, wasting a dozen attempts a day. Three consecutive stale results now trip a circuit breaker, and the log states the way out: convert that target to a text-command target.
- 不回结果的机器人每天白等超时 查询类、菜单类机器人本来就不回复签到结论,模块仍会为它们等满超时。现在连续 3 次无响应 即停止自动重试并标记「待确认」,交由用户处置。
- Silent robots made the module wait out a timeout every day. Query and menu robots never reply with a check-in verdict, yet the module still waited out the timeout for them. After three consecutive silent results it now stops retrying and marks the target "pending" for the user to decide.
- 设置界面保存时闪退 重构设置界面时抽出了控件容器,其中两个控件的引用只改了读取处、没改定义处,点保存时空指针。 已修正,并对全部控件做了一次读写配对自查。
- Crash when saving in the settings screen. The settings refactor extracted a control container, but two controls had their reads updated while their definitions were not, causing a null-pointer crash on save. Fixed, plus a full read/write pairing audit of every control.
- 打开「运行日志」会卡顿 日志渲染在主线程逐行构建,长日志会明显卡顿。现在限制渲染量并改为异步读取。
- Opening "Logs" stuttered. Log rendering built every line on the main thread, so long logs stuttered. The amount rendered is now bounded and reading is asynchronous.
- 启动日志只落盘第一行 日志为省电做了落盘采样(INFO 连续输出只在首次落盘),而启动那几行几乎同一毫秒写出, 结果只有首行进了文件。现已新增绕过采样的强制落盘,启动信息完整入档。
- Only the first startup line reached the log file. Log writing is sampled to save power (consecutive INFO lines only flush on the first), and the startup lines are emitted within the same millisecond — so only the first reached the file. A sampling-bypassing forced flush now writes them all.
- 冷启动 30 秒内点 bot 按钮毫无反应 未就绪窗口同样挡掉了主动学习。现在按钮学习不受该窗口限制。
- Tapping a bot button within 30 s of a cold start did nothing. The not-ready window also blocked active learning. Button learning is no longer gated by it.
- 跨天窗口下补签时段变成全天 补签时段跨天时区间判断失效,导致全天都算补签时段。
- The make-up window became all day when it crossed midnight. The make-up window's range check failed when it crossed midnight, making the whole day count as make-up time.
- 跨端同步的配置在本机不生效 从其他客户端同步过来的配置没有被应用。
- Configs synced from another client did not apply locally. Configs synced from another client were not applied on this device.
- 连点多个 bot 按钮时只有第一个能被学到 连续点击多个按钮时,只有第一个会被记住。
- Only the first of several bot buttons was learned. Tapping several bot buttons in a row only learned the first one.
- 日志里的链路编号会跨账号重复 链路编号原本是全局随机数,多账号并行时会撞号,同一个编号横跨两个账号, 排查时极易误判成"串号"。现在编号带账号前缀,一眼可辨。
- Chain ids in the log could repeat across accounts. Chain ids were global random numbers and collided when accounts ran in parallel: one id spanning two accounts, easily mistaken for account crossover while troubleshooting. The id now carries an account prefix.
- 热重载后两个实例并行跑 热重载可能留下两个模块实例同时运行。
- Two instances ran in parallel after a hot reload. A hot reload could leave two module instances running side by side.
- 启动日志里的数字标题渲染成错误字形 启动日志中的数字标题会显示成错误的字形。
- Numeric headings rendered as wrong glyphs in the startup log. Numeric headings in the startup log rendered with the wrong glyphs.
- 花体字有豆腐块 部分设备上装饰性文字会显示成方框。
- Decorative text showed tofu boxes. Decorative text rendered as tofu boxes on some devices.
- 「待确认」的「重试」按钮用错账号 待确认列表里的重试按钮读的是当前账号,而不是该目标所属的账号。
- The "pending" retry button used the wrong account. The retry button in the pending list used the current account instead of the target's own.
- 新增 · New 日志与诊断包显示详细版本信息 启动日志现在输出:模块版本名 + 版本码、宿主友好名 + 包名 + 版本名/码、CPU 架构、 Android 版本、注入方式(已知客户端 / 能力探测命中)、模块包名。诊断包同步补全。 排查「装了没生效」「版本对不上」「到底哪个包在跑」不再靠猜。
- Detailed version info in logs and the diagnostics bundle. The startup log now reports the module version name and code, the host's friendly name, package and version name/code, the CPU ABI, the Android version, the injection method (known client or capability probe) and the module package. The diagnostics bundle matches. No more guessing whether the module loaded, which version is installed, or which package is running.
- 宿主友好名支持英文 英文界面下输出 Official / Nagram / ExteraLess,不再在英文环境里出现中文宿主名。
- Host friendly names follow the UI language. English UI now prints Official / Nagram / ExteraLess instead of Chinese host names.
- 工具 · Tooling 仓库里的 build.sh 缺三道门禁 仓库版只有国际化检查一道门禁,纯逻辑单测、更新日志双语、版本五查都在构建工具目录里 —— 任何直接 clone 源码仓的人跑 ./build.sh 都会绕过它们。现已补齐并新增接线自检门禁。
- The in-repo build.sh was missing three gates. The in-repo script only ran the i18n gate; the unit tests, bilingual changelog and version checks lived only in the build kit, so anyone cloning the source and running ./build.sh skipped them. All are now in place, plus a new wiring self-check gate.
- 纯逻辑单测从 115 条扩到 174 条 新增用例逐条对应本次修掉的 bug,改回去会被门禁拦住。
- Unit assertions grew from 115 to 174. Each new case maps to a bug fixed here, so a regression is caught by the gate.
- 接线自检的成员清单里有早已删除的方法 导致该检查长期空转;已修正并挂进构建。
- The wiring checker listed a method that no longer existed. The check had been a no-op for a long time; fixed and wired into the build.
About this version
- Version
- 1.6.0 (123)
- Size
- 457.05 KB
- Requires Android
- 8.0
- Target SDK
- 26
- Architecture
- universal
- Downloads
- 10
- Updated
- Sep 26, 2026
- Package
- io.github.wlmosv_png.tgautosign
Similar apps
Ratings & reviews
- 50
- 40
- 30
- 20
- 10