在 js 端屏蔽飞书消息的已读回执。即,自己在看到其他人发送的(私聊或群聊)普通消息后, 对方的视角也会保持在自己未读的状态。
已验证版本:macOS 飞书 7.53.16、7.73.9。
先确保飞书进程已经关闭,避免脚本执行时因文件占用导致报错。
终端下执行:
python main.py <飞书安装根目录>脚本会解压 webcontent/messenger.asar 文件并修改里面的 js 文件。
修改成功后请重启飞书客户端。若发现飞书功能异常,请将修改后的 messenger.asar 删除,
并将 messenger.asar.bak 文件重命名回 messenger.asar。
若已经应用过旧补丁,需要基于现有原始备份重新生成补丁,可执行:
python main.py <飞书安装根目录> --repatch原有实现会在 updateMessagesMeRead 调用前清空 messageIds,从而阻止普通浏览触发
已读回执。但在新版客户端中,已读请求除了消息 ID,还包含以下位置字段:
maxPosition/maxPositionBadgeCountthreadMaxPosition/threadMaxPositionBadgeCountfoldIds
实测中,即使 messageIds 已被清空,只要这些会话或话题位置仍然上报,对方仍会在
打开会话后立即看到已读。
另一方面,完全屏蔽已读也会产生一个明显的交互问题:用户已经回复消息,对方却仍然 看到未读。因此,本项目会在发送成功后为当前会话短时放行一次已读上报。
未获得发送许可时,不再只清空 messageIds,而是将本次上报中的所有已读信息恢复为
无操作状态:
- 清空
messageIds和foldIds - 将会话位置恢复为
maxPosition=-1、maxPositionBadgeCount=0 - 清空话题 ID、话题位置和话题角标位置
客户端内部仍可以处理原始消息列表,但传给 native 层的请求不再携带可推进服务端 已读状态的信息。
在 MessageService::sendMessage:onSendMessageSuccess: 锚点设置一次性许可:
- 许可绑定当前
chatId,其他会话无法消费 - 许可有效期为 1.5 秒,避免很久之后的浏览请求意外放行
- 只允许下一次同会话已读上报,消费后立即删除
- 发送失败不会创建许可
- 其他会话的已读请求会继续被阻止,也不会消耗当前许可
这样可以实现:普通查看保持未读;成功发送普通消息或引用回复后,当前会话此前的消息 可以正常变为已读。
- 补丁使用带名称的
Patch结构定义 - 支持在多个文件中匹配同一补丁锚点
- 同一文件内按 offset 倒序插入,避免多个补丁产生位置偏移
- 任一必要锚点缺失时停止修改,避免对不兼容版本生成半成品 ASAR
- 为每个文件重新生成 Electron ASAR 使用的 SHA-256 integrity 元数据
- 使用 4 MiB block 生成分块哈希
- 先写入
messenger.asar.tmp并验证可以重新打开 - 验证通过后再备份原文件并原子替换,降低中途失败损坏客户端的风险
- 支持通过
--repatch基于已有的原始.bak重新生成补丁
| 场景 | 预期行为 |
|---|---|
| 打开会话或滚动浏览 | 不向对方显示已读 |
| 成功发送普通消息 | 当前会话下一次已读上报被放行 |
| 引用消息并成功回复 | 与普通发送相同,当前会话放行一次 |
| 发送失败 | 不放行 |
| 发送后其他会话先产生已读请求 | 其他会话继续被拦截,许可不被消费 |
| 许可超过 1.5 秒 | 自动失效 |
- 同一会话在 1.5 秒许可窗口内恰好收到新消息时,新消息可能进入本次已读上报
- 表情回应和接收文件仍可能通过其他前端入口触发已读
- 在未应用补丁的手机、网页或其他客户端打开消息,仍会正常上报已读
- 飞书升级后可能更换 bundle 或锚点;必要锚点缺失时脚本会拒绝修改,需要重新适配