feat: 按会话控制发送后的已读上报并完善新版兼容 by qingbo1011 · Pull Request #9 · starccy/feishu-unreadme · GitHub
Skip to content

feat: 按会话控制发送后的已读上报并完善新版兼容 - #9

Merged
starccy merged 5 commits into
starccy:mainfrom
qingbo1011:fix/read-receipt-reporting
Aug 12, 2026
Merged

feat: 按会话控制发送后的已读上报并完善新版兼容#9
starccy merged 5 commits into
starccy:mainfrom
qingbo1011:fix/read-receipt-reporting

Conversation

@qingbo1011

Copy link
Copy Markdown
Contributor

背景

当前实现会在 updateMessagesMeRead 调用前清空 messageIds,从而阻止普通浏览触发已读回执。但在新版客户端中,已读请求除了消息 ID,还包含以下位置字段:

  • maxPosition / maxPositionBadgeCount
  • threadMaxPosition / threadMaxPositionBadgeCount
  • foldIds

实测中,即使 messageIds 已被清空,只要这些会话或话题位置仍然上报,对方仍会在打开会话后立即看到已读。

另一方面,完全屏蔽已读也会产生一个明显的交互问题:用户已经回复消息,对方却仍然看到未读。#8 已提出在 MessageService::sendMessage:onSendMessageSuccess: 处放行一次已读上报的思路。本 PR 在该思路上继续完善,增加会话隔离、短时有效期和新版字段的完整处理。

主要改动

1. 完整拦截普通浏览产生的已读请求

未获得发送许可时,不再只清空 messageIds,而是将本次上报中的所有已读信息恢复为无操作状态:

  • 清空 messageIdsfoldIds
  • 将会话位置恢复为 maxPosition=-1maxPositionBadgeCount=0
  • 清空话题 ID、话题位置和话题角标位置

客户端内部仍可以处理原始消息列表,但传给 native 层的请求不再携带可推进服务端已读状态的信息。

2. 发送成功后按会话短时放行一次

MessageService::sendMessage:onSendMessageSuccess: 锚点设置一次性许可:

  • 许可绑定当前 chatId,其他会话无法消费
  • 许可有效期为 1.5 秒,避免很久之后的浏览请求意外放行
  • 只允许下一次同会话已读上报,消费后立即删除
  • 发送失败不会创建许可
  • 其他会话的已读请求会继续被阻止,也不会消耗当前许可

这样可以实现:普通查看保持未读;成功发送普通消息或引用回复后,当前会话此前的消息可以正常变为已读。

3. 支持多补丁锚点并在不兼容时快速失败

  • 将补丁定义整理为带名称的 Patch 结构
  • 支持在多个文件中匹配同一补丁锚点
  • 同一文件内按 offset 倒序插入,避免多个补丁产生位置偏移
  • 任一必要锚点缺失时停止修改,避免对不兼容版本生成半成品 ASAR

4. 提高 ASAR 重打包可靠性

  • 为每个文件重新生成 Electron ASAR 使用的 SHA-256 integrity 元数据
  • 使用 4 MiB block 生成分块哈希
  • 先写入 messenger.asar.tmp 并验证可以重新打开
  • 验证通过后再备份原文件并原子替换,降低中途失败损坏客户端的风险
  • 新增 --repatch,可以直接基于已有的原始 .bak 重新生成新版补丁

5. 文档与测试

  • README 增加新的行为说明、--repatch 用法和限制条件
  • 增加补丁发现、多锚点插入、许可创建/消费/过期/会话隔离测试
  • 增加 ASAR integrity、分块哈希及解包回环测试

行为预期

场景 预期行为
打开会话或滚动浏览 不向对方显示已读
成功发送普通消息 当前会话下一次已读上报被放行
引用消息并成功回复 与普通发送相同,当前会话放行一次
发送失败 不放行
发送后其他会话先产生已读请求 其他会话继续被拦截,许可不被消费
许可超过 1.5 秒 自动失效

验证情况

  • python3 -m unittest discover -s tests -v:8/8 通过
  • python3 -m py_compile main.py asar.py tests/test_patch.py tests/test_asar.py
  • git diff --check
  • 使用真实 messenger.asar 原始备份完成离线解包、修改、重打包和再次解包
  • 对全部 7 个实际修改的 bundle 执行 node --check
  • macOS Lark 7.53.16(Lark Framework 131.0.6778.268)双账号手工验证通过:仅打开会话不再立即上报已读,发送后当前会话可以恢复合理的已读状态
  • 已验证已有 .bak 时可通过 --repatch 完成重新生成

整个验证过程没有通过自动化工具操作或发送飞书消息,账号侧行为由人工双账号测试确认。

已知限制

  • 同一会话在 1.5 秒许可窗口内恰好收到新消息时,新消息可能进入本次已读上报
  • 表情回应和接收文件仍可能通过其他前端入口触发已读
  • 在未应用补丁的手机、网页或其他客户端打开消息,仍会正常上报已读
  • 飞书升级后仍可能更换 bundle 或锚点;必要锚点缺失时脚本会拒绝修改,需要重新适配

#8 的关系

感谢 #8 提供“在 onSendMessageSuccess 后放行一次已读上报”的实现方向和真机验证。本 PR 延续这一交互目标,并主要补充以下差异:

  • 从全局计数器改为会话 ID 绑定的一次性、限时许可
  • 完整处理新版客户端的会话位置、话题位置和折叠消息字段
  • 增加 ASAR integrity、临时文件验证和原子替换
  • 增加 --repatch 及相应测试和文档

qingbo1011 and others added 2 commits August 11, 2026 23:21
Comment thread README.md Outdated
Comment on lines +101 to +112

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感谢您的提交,整体没问题。就是 验证情况 这部分可以不用放在 README 里,在 PR 描述里就够了,毕竟使用者不会关心这些细节。然后麻烦您可以将适用的飞书版本号描述提到 README 的最前面吗

@qingbo1011 qingbo1011 Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感谢您的提交,整体没问题。就是 验证情况 这部分可以不用放在 README 里,在 PR 描述里就够了,毕竟使用者不会关心这些细节。然后麻烦您可以将适用的飞书版本号描述提到 README 的最前面吗

@starccy 感谢review。我已删除README中的“验证情况”,并将适用版本说明移到开头,标明macOS飞书 7.53.16(已双账号手工验证通过)。麻烦您再看一下。

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK 哦

@starccy
starccy merged commit 85e5fe8 into starccy:main Aug 12, 2026
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