你好 👋

落日余晖,记录折腾与思考。

飞书多开指南:一份 .app 改出完全隔离的第二实例

目标:一台 macOS 上同时运行两个相互隔离的飞书(主实例 Lark.app + 第二实例 Lark-org2.app),登录不同账号;数据目录、TCC/通知身份互不影响;第二实例不与主实例抢全局快捷键、不会被官方升级覆盖、菜单栏图标可区分。 本文自包含:不依赖任何现成脚本,按步骤逐条执行即可完成。实测版本:143.0.7499.203(macOS,Apple Silicon)。 这是飞书折腾系列第三篇:撤回机制与加密的逆向原理见《飞书撤回消息逆向分析》,防撤回 asar 补丁见《飞书防撤回补丁复现指南》。 0. 前置条件 项 要求 系统 macOS(Apple Silicon;Intel 需把下文 clang 的 -arch 换成 x86_64 自测) 源应用 /Applications/Lark.app 已安装(是否打了别的补丁无所谓,副本会原样带上) 工具 python3 + Pillow(pip3 install pillow)、clang(Xcode CLT)、codesign/install_name_tool/otool(随 CLT) 磁盘 预留一份飞书体积(~1.5GB) 约定:下文 $VERDIR 一律指当前飞书的版本目录,先取好: VERDIR="$(ls "/Applications/Lark.app/Contents/Frameworks/Lark Framework.framework/Versions" | grep -E '^[0-9]' | sort -V | tail -1)" FW="/Applications/Lark-org2.app/Contents/Frameworks/Lark Framework.framework/Versions/$VERDIR" DATA="$HOME/Library/Application Support/LarkShell-org2" 1. 步骤一:复制 .app 并改身份 cp -R /Applications/Lark.app /Applications/Lark-org2.app /usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier com.electron.lark.org2" /Applications/Lark-org2.app/Contents/Info.plist /usr/libexec/PlistBuddy -c "Set :CFBundleName Feishu2" /Applications/Lark-org2.app/Contents/Info.plist /usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName 飞书2" /Applications/Lark-org2.app/Contents/Info.plist 2>/dev/null || true 改 CFBundleIdentifier 后,macOS 视其为独立 app:TCC(屏幕录制/麦克风等)、通知、 Dock 固定各自一套,与主实例互不干扰。 ...

September 7, 2026 · 7 min · 1339 words

飞书防撤回补丁复现指南:手工 patch asar 的 19 处注入

目标:一名工程师不依赖任何现成脚本,仅凭本文在任何一台 macOS 机器上,对飞书(Lark.app)手工完成防撤回补丁的注入、验证与排障。 撤回机制与加密等深层逆向原理见上一篇《飞书撤回消息逆向分析》;本文只讲怎么做。 实测版本:143.0.7499.203(macOS,Electron/Chromium 143,补丁 v31c)。 131 及更早版本的锚点差异较大(布局都不同),但方法论完全一致,见文末「升级适配」。 0. 前置条件 项 要求 系统 macOS(Apple Silicon / Intel 均可),已安装飞书 /Applications/Lark.app 工具 node、npx、python3 npm 包 @electron/asar(npx 自动拉取)、esbuild(语法校验用,npx 自动拉取) 权限 能读写 /Applications/Lark.app/Contents/...(默认可写) 关键事实(先记住,贯穿全文) 撤回推送会把消息 content 一并抹空(本地 DB 和前端都拿不到原文)——所以必须「渲染时存档、撤回时还原」,只改渲染布尔位只会得到空气泡。 普通消息撤回的原生样式(居中灰字「你撤回了一条消息」)来自 featureTypeParser 的 RECALLED 分支;行级闸门 return 的提示条组件只服务话题根消息(threadMessageType===1)。 原生「(已编辑)」标记不是 richText 数据元素,是富文本引擎在渲染尾部按 message.editVersion 标志挂的 suffix 槽位——所以撤回标记要改引擎门槛,而不是往数据里塞元素。 飞书主进程名是 Feishu(pgrep -x Lark 匹配不到);重启 = pkill -x Feishu + open -a Lark.app。 聊天窗口与设置窗口共用 profile_main 分区的 localStorage(file:// origin)——这是开关跨窗口生效的基础。 Info.plist 里的 AsarIntegrity(SHA256)校验实测不拦截修改后的 asar(改包后正常启动,校验逻辑只记录)。 开关语义:开(默认,frk_hide 不存在)= 显示原文 + 末位「(已撤回)」;关(frk_hide=1)= 完全恢复原生行为(parser 保留 isRecalled → 原生 RECALLED 居中灰字提示条,与未打补丁一模一样)。 1. 总流程 备份 asar(3 个) ──► 解包 ──► 定位锚点(特征串 grep) ──► 六类补丁逐文件修改 ──► 语法校验(node --check + esbuild 双检) ──► 打包 ──► 安装 ──► 重启飞书 ──► 验证(包内 grep 标记 → 功能测试 → 必要时读 leveldb 诊断) 涉及的三个 asar(相对 webcontent/ 目录;143 起 messenger-next.asar 已取消并入 messenger.asar): ...

September 7, 2026 · 11 min · 2154 words

飞书撤回消息逆向分析:原文是怎么被抹掉的

分析对象:飞书 macOS 客户端 /Applications/Lark.app(v131.0.6778.268 起分析,143.0.7499.203 复验,Electron/Chromium 内核)。 方法:静态逆向(strings / 反汇编 / asar 解包)+ 真实撤回的落盘观察 + 前端 asar 补丁注入的实测迭代。 本文讲原理与逆向过程;拿着结论去动手改客户端,见下一篇《飞书防撤回补丁复现指南》。 TL;DR:撤回是怎么实现的 撤回 = 服务端下发一条带撤回标记的消息资源更新,客户端把本地 SQLite 行打上 is_recalled=1 等标记列,前端按标记切换渲染成「撤回了一条消息」提示。而原始 content 会被一并抹掉(实测:绕过渲染门之后,被撤回的消息渲染成空气泡;DB 加载与撤回推送两路拿到的 content 均为空)。所以单纯改前端的布尔分支只能得到空气泡——防撤回必须「渲染即存档、撤回时还原」。 完整链路: 发起方:前端菜单 → JSB 命令 RECALL_GROUP_MESSAGE / RECALL_MESSAGE → Rust 核心 liblark.dylib(lark-message 模块)→ 长连接(pipe/websocket protobuf,非 REST)→ 服务端。日志串:recall_message callback for id: / recall message success: id=。 接收方:服务端推送更新后的 Message protobuf(字段含 is_recalled / recaller_identity / recall_type / recall_user_id)→ lark-message/src/logic/dependencies/processor.rs 校验(chat recall message validate failed, recaller not found 等)→ 更新本地库 → 通知前端。 本地库:SQLCipher 加密的 SQLite,messages 表同一行内:content BLOB(原始内容)+ is_recalled BOOLEAN + recaller_id BIGINT + recaller_identity INTEGER + recall_type INTEGER + recall_user_id BIGINT。 前端渲染(messenger asar,已提取实证代码): let{isRecalled:r,isDeleted:a,recallerIdentity:n}=t; return r ? <E recallerIdentity={n}/> // "X 撤回了一条消息" 提示条 : a ? <S.A/> : <N {...e}/> // 正常消息 以及 function L(e){return !e.isRecalled && !!e.content.filePath} —— 撤回消息对象上 content 依然存在,只是被布尔位挡住(但值已被掏空)。 通知/红点同步收尾:do not push notice to oneself when is recalled(自己撤回的不推通知)、thread 根消息标记 root_message_is_recalled、回复计数修正 update reply count for msg has recalled。 一、客户端架构 层 位置 作用 Electron 主进程 Contents/MacOS/Feishu 壳、窗口、升级 渲染进程 Lark Helper (Renderer) 加载 webcontent/*.asar(messenger.asar = 聊天 UI) aha-service 后台进程 Lark Helper --type=backend(加载 liblark) Rust 核心:同步、存储、JSB 命令 核心库 Libraries/liblark.dylib(84MB) 全部业务逻辑,Rust 编写,字符串含 lark-message/src/*.rs 等源码路径 DB 层 liblark 内静态链接 SQLCipher 4 本地库全加密 前后端通信:JS Bridge 命令表(全大写,如 GET_CHAT_MESSAGES、RECALL_GROUP_MESSAGE、HANDLE_PIPE_PACKET、HANDLE_WEBSOCKET_PACKET);服务端长连接推送走 pipe/websocket 包,protobuf 编码。 ...

September 7, 2026 · 3 min · 569 words

备案之路

备案之路 域名备案 经过比价发现cn域名后缀的域名价格最低(包含续费). 但是要icp备案. 第一次申请,由于公网ip是基于阿里云的vps服务器. 所以理所应当的在阿里云申请了. 网页申请还是很快的 公安备案 第一次 根据ai的建议走的的是户籍所在地备案. 2026-08-17申请 2026-08-27电话通知 由于 本人在北京,无法当面核验和实地检查,审核不通过. 第二次 调整到了北京 .2026-08-18申请 2026-08-31 审核不通过,地址填报有误,已电话告知. 第三次 2026-08-31申请 2026-09-03 审核通过. 北京效率还是高啊

September 7, 2026 · 1 min · 24 words

In time

好吧. 这个是怎么导致人一出生就携带时间倒计时的? 怎么流通100w就整个世界都坏了? timekeeper 就这么几个人吗?

August 22, 2026 · 1 min · 5 words

博客进化记:从手动部署到 AI 代工的全自动流水线

缘起 上一篇记录了博客系统的搭建:iStoreOS 小主机上,Hugo + Gitea + 在线编辑器的骨架立起来了。但那时的它还只是一个「能用的毛坯」:发布靠手动构建再拷贝到云服务器,编辑器只能写新文章,改已发布的内容要 SSH 上去改文件,图片更是没有像样的通道。 这篇记录的是接下来两天发生的事:它被升级成了一套全自动出版系统——而且这一次,大部分活不是我干的,是 AI 干的。 现在的流水线长什么样 写作(三选一) ① 在线编辑器 edit.blog.is-i7.cn (公网,Basic Auth) ② Gitea 网页 :3000 ③ 直接改 pi 上的源码后 git push ↓ push Gitea 钩子 deploy-blog ↓ hugo 构建(约 210ms) ↓ rsync --delete 走 WireGuard 隧道 → 云服务器 预览站(:8888) 和 is-i7.cn 同步更新 从点「发布」到文章上线,全程不到 10 秒。构建在树莓派级别的小主机上完成,云端只做静态托管——3GB 内存的小盒子跑得毫不费力。 编辑器的进化 最初的编辑器只有标题、标签、正文三个框,发布按标题生成文件名——意味着改一个已发布的标题就会创建一篇新文章,而旧文永远无法修改。 升级后的编辑器: 文章侧栏:列出全部已发布文章,点开即载入编辑器,发布按钮自动变为「更新原文」——保留原文件路径、保留原始发布日期、自动携带版本号 图片按钮:选图自动上传图床,光标处插入直链,编辑器预览里也能直接看到 断线续编:刷新页面会恢复「正在编辑哪篇」的状态,不会误当新文章发出去 这些功能上线前经过了一轮「编辑器链路测试」——你没看错,博客里那些名字朴素的测试文章(《编辑器测试》《编辑器链路测试》《测试在线编辑文稿》)就是那个阶段诞生的。它们是 AI 用真实浏览器一轮轮点出来的:新建、发布、从列表加载、修改、再发布、验证线上更新、删除。测试全绿之后,它们作为脚手架被撤下了——但 git 历史里永远留着,算是这段调试期的化石。 ...

August 21, 2026 · 1 min · 194 words

结婚一周年纪念日

August 19, 2026 · 0 min · 0 words

绕过米家云:用 python-miio 本地直连小米设备,秒级拿到传感器数据

折腾 Home Assistant + 米家设备时,最让人头疼的就是"数据延迟"。官方集成走米家云,温湿度 2-3 分钟才更新一次,偶尔云端数据还会"抽风"报错。本文记录我用 python-miio 绕过米家云、局域网直连除湿机,把数据采集频率做到秒级、并接入 HA 的完整实战。 一、痛点:为什么需要本地直连 先看一张真实对比(同一台除湿机,同一时刻): 数据来源 当前值 状态 米家云(官方集成) 30 °F ❌ 云端数据坏了,实际应是 84 °F 本地直连(python-miio) 29.5 °C ✅ 准确 官方 HA 米家集成的工作方式是 MQTT 订阅推送: 设备属性变化 → 设备上报米家云 → 米家云 MQTT 推送 → HA 理论上 HA 能"第一时间"收到,但实际依赖两点: 设备主动上报(米家 WiFi 设备为省电,通常 2-3 分钟才上报一次传感器数据) 推送可靠性(官方仓库 Issue 里提到 WiFi 设备推送丢失率接近 30%) 结果就是:延迟大、还不一定准。 而本地直连的路径是: 你的脚本 → 局域网直连设备(miio 协议)→ 秒级返回 不经过米家云,不依赖设备上报节奏,你想读就读,又快又准。 二、原理:米家设备的本地通信 小米的 WiFi 设备(除湿机、空气净化器、扫地机等)在局域网内会开放一个 miio 协议(UDP 54321 端口)。只要你有设备的 token(一个 32 位十六进制密钥),就能在局域网内直接和设备通信。 ...

August 12, 2026 · 3 min · 584 words

iStoreOS 博客系统搭建全记录

title: “iStoreOS 博客系统搭建全记录” date: 2026-08-05T22:00:00+08:00 tags: [“iStoreOS”, “博客”, “折腾”] 缘起 家里有一台联想小主机(Intel Celeron N3450 / 3GB 内存 / 500GB 机械硬盘),刷了 iStoreOS(基于 OpenWrt 的软路由系统),通过 WiFi 接入家庭网络,平时跑着 Jellyfin 看片、还有个 Telegram 下载机器人。 这次的目标很简单:在这台小主机上搭一套自己的博客系统,能网页写作、自动发布。 第一步:摸清家底 先 SSH 上去看服务器到底装了什么、状态如何。 踩的坑:连不上 Windows 的 OpenSSH 客户端能连,但密码 zzzz. 怎么都认证失败。折腾了半天(试了 SSH_ASKPASS、ssh2 库、plink),最后发现——密码其实是 zzzz,那个句号是中文句末标点,不是密码的一部分。 摸到的家底 项 内容 系统 iStoreOS 24.10.4(OpenWrt 衍生) CPU Intel Celeron N3450 @ 1.10GHz,4 核 内存 3.2GB,空闲 2.7GB 磁盘 东芝 500GB 机械盘,只用了 2.3GB,空闲 99% 在跑的服务 Jellyfin(媒体)、tg-downloader(TG 机器人)、Samba、NFS、Docker 一切健康,空间充裕,可以开搞。 ...

August 5, 2026 · 2 min · 395 words
晋ICP备2026011119号 · 公安备案图标京公网安备11010602203188号