 zyandClaude Opus 5
|
f5af1dba72
|
fix(docker): 镜像内置 tini 作为 PID 1,回收浏览器子进程 (#793)
此前只在 docker-compose.yml 里设了 init: true,直接用 docker run
或自写编排的用户不受保护,Chrome 退出后其子进程会堆积成僵尸进程。
改为镜像自带 tini,与启动方式无关。
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
v2.4.2
|
2026-08-03 01:06:15 +08:00 |
|
 zyandClaude Opus 5
|
2a57ab412f
|
fix(publish): 正文输入框改为轮询定位,不再 panic (#792)
getContentElement 原先用 Race + MustDo:兜底分支返回的普通 error 会让
Race 立刻终止,等于只做一次快照,失败还以 panic 形式抛出。改为在窗口内
轮询多个候选选择器,全部落空返回可读错误。
- findTextboxByPlaceholder 去掉 MustElements,错误正常返回
- 图片上传改为按 accept 挑选输入框
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
v2.4.1
|
2026-08-02 22:43:14 +08:00 |
|
 zyandClaude Opus 5
|
347428476d
|
fix(cookies): 保存时创建缺失的父目录 (#791)
COOKIES_PATH 指向尚不存在的目录时,保存会直接失败。
Thanks to @wzy31124513 for reporting (#422).
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
2026-08-02 16:09:10 +08:00 |
|
 zyandClaude Opus 5
|
d7f176a66f
|
feat(profile): 主页支持按 tab 读取笔记 / 收藏 / 点赞 (#790)
get_my_profile 与 user_profile 新增 tab 参数:note(默认) / fav / liked,
也接受中文「笔记 / 收藏 / 点赞」。
各 tab 的内容存在各自的下标里,改为只取当前 tab 的那一份——原先把所有
下标展平合并,多 tab 场景下会把不同 tab 的内容混在一起。
tab 与请求不符时报错:参数不被接受时页面会落在默认 tab 且不报错,
不拦住会把默认 tab 的内容当成结果返回。
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
v2.4.0
|
2026-08-02 16:00:14 +08:00 |
|
 zyandClaude Opus 5
|
c17940fda6
|
feat(notification): 支持通知的读取、回复与点赞 (#789)
* refactor(humanize): 修正 Click 内部一处延迟的位置
BeforeClick 的语义与实际调用位置不符:调用方先 Delay 再 Click,
而移动发生在 Click 内部。改为在 Click 内部处理。
单测钉住新参数的下限。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(notification): 支持通知的读取、回复与点赞
新增四个能力,MCP 工具与 HTTP 接口同步提供:
- get_unread_count GET /api/v1/notifications/unread
- list_notifications GET/POST /api/v1/notifications/list
- reply_notification POST /api/v1/notifications/reply
- like_notification POST /api/v1/notifications/like
列表返回评论者、内容,以及关联笔记的 feed_id / xsec_token,可接
get_feed_detail。状态非正常的条目会被过滤,数量计入 filtered;
关联对象不是笔记时不给出 feed_id。
页面取数由 Go 结构体解析,并补充报文样例单测:字段名对不上时
json.Unmarshal 不报错只给零值,这类回归编译和现有用例都拦不住。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
v2.3.0
|
2026-08-02 15:21:24 +08:00 |
|
 zyandClaude Opus 5
|
a7e5b883ef
|
chore: 精简注释,并移除无意义的 account 日志字段 (#787)
* chore(humanize): 精简点击落点校验的注释
注释和用例描述里写了过多与实现无关的背景,收敛为只说明函数做什么。
行为无改动。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore(humanize): 精简注释,只保留与实现相关的说明
注释和用例描述里写了过多与实现无关的背景,包括参数换算方式、取值范围的
文字复述,以及与其他实现方式的对比。收敛为只说明函数做什么。
行为无改动,常量与逻辑均未变动。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore(api): 移除无意义的 account 日志字段
每个 handler 都写同一个硬编码字面量,日志里那一栏恒为同一个值、不携带
任何信息,一并去掉日志格式里的对应占位。
/health 的 account 字段改为项目地址。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore(humanize): 去掉两处冗余注释
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore(humanize): 精简 ClickAt 注释
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore(humanize): 移除包内注释
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
2026-08-02 15:19:10 +08:00 |
|
 zyandClaude Fable 5
|
3aacfb3583
|
chore(search): 搜索导航后补上与其他路径一致的停顿 (#786)
搜索是唯一一条导航后没有调用 humanize.Delay 的路径,与收藏、评论、
详情等保持一致。
已本地走查:不带筛选与带筛选(最新 + 图文)两条路径均正常,筛选结果
类型正确。
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
v2.2.7
|
2026-08-02 05:09:59 +08:00 |
|
 zyandClaude Fable 5
|
80484fecda
|
chore(ci): 移除自动代码审查工作流 (#785)
两个工作流的 permissions 均为只读(pull-requests: read、issues: read),
发表评论需要 write 权限,因此运行始终无法产出任何评论。历史上 11 次
"成功"运行未在任何 PR 留下记录,属于绿灯但无实际产出。
项目 PR 均经本地走查后合入,不依赖该流程。
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
2026-08-02 00:00:34 +08:00 |
|
 zyandClaude Fable 5
|
fd0e3a98c8
|
refactor(interact): 发布与导航的点击统一走交互原语 (#784)
publish.go 与 navigate.go 中部分点击未走项目统一的交互封装,与文件内
其余调用点写法不一致。统一替换为 humanize.Click。
已本地走查:完整发布一篇图文笔记,覆盖 Tab 切换、标题输入框、原创声明、
发布按钮及个人主页导航,均正常。可见范围、定时发布、商品绑定三条分支
需特定参数触发,本次未覆盖。
同时忽略 docker compose 的本地覆盖文件,并修正 README 中查看日志的命令
(原文误用镜像名,docker logs 接收容器名)。
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
2026-08-01 23:57:35 +08:00 |
|
tan jun
|
8f2d48968f
|
docs: Update WeChat QR code image in README
|
2026-07-31 16:13:14 +08:00 |
|
 zyandClaude Fable 5
|
a5bb5b872b
|
fix(interact): 交互写操作确认后补足停留,避免请求未完成 (#778)
点赞、收藏及其取消操作在本地状态确认后立即返回,调用方随即关闭页面
与浏览器,此时请求可能尚未完成,导致改动偶发不生效——接口返回成功但
状态并未变更。
在状态确认后增加一段停留再返回。点赞与收藏共用同一处切换逻辑,一并
覆盖。
已本地验证:收藏与取消收藏均正确生效。
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
v2.2.6
|
2026-07-29 00:18:47 +08:00 |
|
 zyandClaude Opus 5
|
085da32eb9
|
fix(search): 搜索与列表只返回笔记 (#776)
站点的列表数据里混着直播卡片(live_v2)和搜索热词(hot_query),它们没有
noteCard,取出来标题为空、也没有可用的 id,对调用方是纯噪音——表现出来
就是「搜索返回的 displayTitle 为空」。
真机走查:搜「露营」+ 最新 + 图文,23 条里 5 条不是笔记(live_v2 三条、
hot_query 两条)。过滤后 19 条,modelType 全为 note。
判据用 modelType 而非 noteCard.type:换成视频筛选走查,20 条笔记的
modelType 同样是 note,差异体现在 noteCard.type(normal / video),
按 modelType 过滤不会误伤视频笔记。
剩下的 3 条空标题不动:它们 modelType 为 note、有真实 id 和作者,是平台
允许的无标题笔记,不属于要滤掉的噪音。
搜索与首页列表两处解析逻辑相同,共用同一个过滤函数。
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
v2.2.5
|
2026-07-28 10:00:56 +08:00 |
|
 zyandClaude Opus 5
|
0033dc7d1e
|
fix(login): 取二维码不再堆积浏览器实例 (#775)
取一次二维码就要留一个浏览器活着等扫码,否则检测不到登录、也存不了
cookie,所以正常路径下不能提前关——这部分是有意的。问题在于没有任何
东西拦着重复调用:每调一次 get_login_qrcode 就多一个浏览器活到 4 分钟
超时为止。实测连调三次,浏览器相关进程 8 → 16 → 24 线性累积。
约束改为同一时刻只保留一个待扫码会话,开新的就取消旧的。会话登记用序号
而不是函数值,因为 Go 的函数不可比较,收尾时得认出自己是否仍是当前会话
——若不加判断,旧会话的收尾会抹掉后来者的登记,后来者就再也关不掉了。
顺带把会话的开始与结束都打进日志。此前用户重复取二维码时,扫的可能是已
被取代的那一张,cookie 自然存不下,而过程中没有任何提示可循。
实测同一场景下修复后稳定维持 8 个进程,不再累积。
Closes #470
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
v2.2.4
|
2026-07-28 08:43:23 +08:00 |
|
 zyandClaude Opus 5
|
082f683e4a
|
docs(readme): 英文版 README 与中文版同步 (#774)
补齐英文版落后的内容:顶部快速开始/相关资源/疑难杂症三块、
运营知识与风险说明遗漏条目、XHS_PROXY 代理配置、Open Code CLI
与 OpenClaw(MCPorter) 客户端、products 参数、OpenClaw 风险提示、
微信/飞书群表格、贡献者表格(22→27)以及许可证章节。
搜索筛选和可见范围的取值改回中文字面量并附英文注解——这些值会
原样传给页面,译成英文会导致调用失败。
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
2026-07-28 02:21:54 +08:00 |
|
 zyandClaude Opus 5
|
e7098e57b4
|
docs: 添加 Apache-2.0 许可证 (#773)
仓库此前没有 LICENSE 文件。这不是「随便用」,法律上默认保留全部权利——
严格讲任何人都未获使用授权,与项目实际在做的事(接受 PR、发布 Release
和镜像、扩大使用面)相互矛盾,也确实挡住了需要走合规审查的公司用户。
选 Apache-2.0 而非 MIT,主要是它明确写了专利授权(第 3 条),企业法务
审查时不必留空白;其次是免责条款里「即使已被告知存在此类损害的可能性」
一句,对本项目的使用场景更贴合。
LICENSE 为规范全文,仅填入版权行。README 补一节说明,并写明贡献默认
按同一许可证授权。
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
2026-07-28 02:13:17 +08:00 |
|
 zyandClaude Fable 5
|
f44b4ed052
|
feat(api): 对齐 HTTP API 与 MCP 工具能力 (#772)
- HTTP 新增 POST /api/v1/feeds/like 与 /api/v1/feeds/favorite,
参数与 MCP like_feed / favorite_feed 一致(unlike/unfavorite 可选)
- MCP 新增 get_my_profile 工具,对齐 HTTP GET /api/v1/user/me
- docs/API.md 补齐发布接口缺失字段(schedule_at/is_original/products)、
新端点文档与错误码
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
v2.2.3
|
2026-07-28 02:07:14 +08:00 |
|
 zyandClaude Opus 5
|
9315948f74
|
chore(mcp): 升级 go-sdk 到 v1.4.0 并开启 Stateless (#771)
* chore(mcp): 升级 go-sdk 到 v1.4.0 并开启 Stateless
- go-sdk v0.7.0 -> v1.4.0,Go 源码零改动(本项目用到的 API 无破坏性变更)
- StreamableHTTPOptions 开启 Stateless:客户端可跳过 initialize 握手,
单次 POST 直接调用工具,简化各类 MCP 客户端接入
- 保留 SDK v1.4.0 默认开启的 DNS rebinding 防护,不做旁路
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test(mcp): 固定无握手单次 POST 的契约
开启 Stateless 后,客户端可以不走 initialize 握手、一发请求就调用工具。
这是对外的行为承诺,但它由一行配置支撑:删掉 Stateless、或 SDK 改了语义,
编译和现有单测都不会报错,承诺会静默消失。
只用 tools/list 断言:它不碰浏览器,测试保持 hermetic,而它恰好就是契约
丢失时的失败点——有状态模式下无 session 调用会被回以「握手前不允许调用」。
已验证把 Stateless 改回 false 时该测试确实失败。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(mcp): 收敛 Stateless 相关注释
三处问题:
一是把未经验证的推断写成了肯定句。原注释断言「将来要加进度推送或
elicitation,必须同时去掉 Stateless 和 JSONResponse」——后半句是从通知
路由规则推出来的,没有实测。留在代码里会被当成既有结论,删掉。
二是复述 SDK 文档。Stateless 是什么、有什么代价,上游文档里都有;注释
该写的是为什么选它、什么时候不能用。
三是位置和体量与既有风格不符:旁边都是行尾短注释,不该在函数调用上方
压一个四行块。改为贴在字段上。
测试的函数头注释同步收敛,去掉「只有这里能拦住」这种绝对表述。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
v2.2.2
|
2026-07-28 01:13:46 +08:00 |
|
zy
|
af95a532e2
|
fix(comment): 评论区较短时也展开楼中楼 (#770)
判定加载完成排在展开按钮之前,评论区较短时 end-container 第一轮就在 DOM 里,直接返回导致展开从未执行。改成到底后先展开一轮,点开了就不算加载完;一个都没点开才收工,避免被阈值跳过的按钮卡死循环。
Closes #769
v2.2.1
|
2026-07-28 00:40:01 +08:00 |
|
zy
|
71dd525b57
|
chore(test): 删掉无效与冗余的测试 (#768)
删掉断言随机数是随机的、打真实外网默认跳过的、测标准库序列化的、以及测 set/get 的四项测试,另把两个测试改成中性命名。断言逻辑未改动。
v2.2.0
|
2026-07-28 00:01:25 +08:00 |
|
zy
|
b033557e7f
|
chore(test): 精简测试注释 (#767)
删掉复述断言的注释和描述第三方库默认行为的段落,保留预期值的推导和测试环境搭建的说明。断言未改动,无逻辑变更。
|
2026-07-27 23:58:16 +08:00 |
|
zy
|
1411cfa3bf
|
chore: 精简全仓注释 (#766)
删掉复述代码的注释和把推导过程当注释写的长段落,保留代码本身说明不了的信息:平台侧的长度与时间限制、DOM 结构、选择器与降级顺序的取舍。无逻辑变更。
|
2026-07-27 23:56:35 +08:00 |
|
zy
|
f4518665bb
|
chore(comment): 精简查找评论相关的注释 (#765)
去掉复述代码和解释推导过程的注释,只留代码本身说明不了的信息:DOM 结构、选择器为什么这么选、两种按钮文案。纯注释改动,无逻辑变更。
|
2026-07-27 23:52:01 +08:00 |
|
zy
|
aa93e0add0
|
fix(comment): 支持回复楼中楼,并给评论查找加上边界 (#764)
二级评论默认折叠在「展开 N 条回复」后面,不点开就不在 DOM 里。
findCommentElement 此前只滚不展开,于是 comment_id 完全正确也只会得到
「未找到评论」。查找循环补上展开:每轮先查、再展开视野里的楼层、
展开后立刻复查,眼前展开完了才继续下滚。
顺带修掉 userID 匹配会选错元素的问题:候选集合去掉 .parent-comment,
只认 .comment-item。前者把整个楼层连同楼中楼一起包着,按 userID 找时
先命中外层楼层并返回,回复按钮就取到了楼主那条。
另外给查找加上收敛条件,避免评论区规模很大时单次请求无限拖下去;
展开按钮按文案和尺寸核对后再点,查找与批量加载两处共用同一判定。
Closes #707
Closes #598
Closes #503
|
2026-07-27 23:47:35 +08:00 |
|
zy
|
d7e8265497
|
fix(search): 点完筛选后等结果真的换一批再读 (#763)
多个筛选项一起用时只有一部分生效。查下来点击本身没问题——探针显示面板里
每一项都正确变成了选中态——问题在读取时机。
站点应用筛选时是先把 __INITIAL_STATE__.search.feeds 清空、再灌入新数据。
实测点完之后 +0s 读到 total=0,+1s 才变成筛选后的 22 条。而原先的等待是
page.MustWait(`() => window.__INITIAL_STATE__ !== undefined`)
这个条件从首屏起就为真,立即返回,等于没等。于是读到的要么是空(报
ErrNoFeeds),要么还是筛选前那一批——表现出来就是「筛选没生效」。
改成记下筛选前的结果 id 列表,点完之后轮询到列表非空且与之前不同为止。
超时不报错,只打日志:筛选已经点上了,宁可返回可能偏旧的数据,也不要让
整个搜索失败。
真机验证:搜「RAG 检索增强」+ 排序「最多点赞」+ 类型「图文」+ 时间「一周内」
→ 20 条全为图文,点赞降序 57/46/41/32/20/18。改之前同样的三项组合会返回
7 个视频 + 13 个图文(即筛选前的旧数据)。
v2.1.1
|
2026-07-27 11:46:31 +08:00 |
|
zy
|
54dd76744c
|
refactor(search): 筛选表按标签组织,去掉全部下标 (#762)
上一版修掉了选项的下标,组还留着 :nth-of-type。两处定位方式不一致,而且
组序号同样依赖 DOM 顺序。这次一并去掉。
- filterOptionsMap 的 19 个条目换成 filterGroups 的 5 行:每行是「面板标签 +
从入参取值的方法 + 合法取值」。原来每个条目都带一个 FiltersIndex,而它就是
map 的 key,是冗余。
- 组改为按面板上的标签文本定位(div.filters 下的直接子 span),选项仍按文本
定位。至此定位不依赖任何顺序。
- 删除 internalFilterOption、findInternalOption、convertToInternalFilters,
换成 collectFilters 一个函数。search.go 净减 72 行。
取值校验移到导航之前。原先放在导航之后,写错一个值要先向平台发一次真实请求
才能报错;现在直接返回,并在错误里列出该组的可选值。
作用域仍限定在 div.filter-panel 内、且只认 div.tags:页面别处存在同文本的
可见元素(顶部频道栏的「图文」「视频」、标签「综合」),放宽会点错地方。
真机验证:
- 搜「AI 编程工具」+ 排序「最多评论」→ 评论数降序 1374/1106/715/498
- 非法取值「最多赞」→ 1.4s 内报错,无平台请求,错误里带可选值列表
|
2026-07-27 11:32:03 +08:00 |
|
zy
|
37719beaa6
|
feat(humanize): 点击前校验落点,够不到就报错而不是静默落空 (#761)
点击走的是「算元素坐标 → 发鼠标事件到那个坐标」,由浏览器决定那一点上
是谁。好处是够不到的元素本来就点不到;代价是够不到的时候我们收不到任何
反馈——调用方以为点了,其实什么也没发生。
补两条检查,都便宜且与页面的瞬时状态无关:
- 落点必须在视口内。实测 left:-9999px 与 top:5000px 的元素都能拿到象限、
也能算出落点,点下去却什么都没命中,且不报错。
- 元素的 visibility 不能是 hidden。这类元素有布局盒子、坐标也在视口里,
但不参与命中测试,点下去会落到它下面的元素上,同样静默落空。
display:none 与零尺寸不用另外查,Shape() 那一步就拿不到可点区域。
刻意不查的三件事,都是查了反而更糟:
- 不用 document.elementFromPoint 做命中校验。悬停浮层的状态在程序化移动
指针时并不稳定,实测会把当时可见可点的选项判成不可点——拦错了比不拦更糟。
- 不查 opacity。opacity:0 的元素点在那个坐标上照样命中,拦了与页面实际
行为不符。
- 不查 pointer-events。设了 none 的元素点下去本来就穿透到下层,同上。
Click / ClickNoWait / Hover 三个入口走完整检查,ClickAt 只查视口(落点是
调用方给的,没有元素可查)。
新增 TestClickGuards 覆盖七种情形,正反两面都断言:该拦的拦下,opacity:0
与 pointer-events:none 必须放行。真机复核搜索筛选(悬停浮层路径)不受影响。
|
2026-07-27 11:15:25 +08:00 |
|
zy
|
2bb8df18f8
|
fix(search): 筛选项按文本匹配,修正选错项的问题 (#760)
* fix(search): 筛选项按文本匹配,修正选错项的问题
search_feeds 带 filter 时会点错选项:要「最多点赞」实际点到「最新」,
要「图文」点到「视频」。closes #661 #657 #640
原因是筛选面板里每个选项会渲染成两个 div.tags,而首项是否重复各组
并不一致——排序依据、位置距离的首项重复,笔记类型、发布时间、搜索
范围的首项不重复。实测「排序依据」组 5 个选项对应 10 个 div.tags,
nth-child 1/2 都是「综合」,3/4 是「最新」,5/6 才是「最多点赞」。
原来的 div.tags:nth-child(N) 把逻辑序号当成节点序号,从第二项起就
错位,且各组偏移量不同,任何下标算术都补不回来。
改成组用 :nth-of-type 定位(组之间没有重复,序号稳定)、组内按文本
匹配。两份重复项的位置尺寸完全相同,取第一个点下去落在同一处。
internalFilterOption 的 TagsIndex 字段随之删除——它的唯一用途就是
拼那个出错的选择器,留着会误导后来的人。校验也从「标签序号在范围内」
改成「组内存在该文本」。
真机验证:搜「咖啡」+ 排序「最多点赞」,返回结果按点赞降序
(299680 / 242417 / 21308 / 9546 ...);修复前同一查询返回的是
2 / 17 / 0 / 2,即按「最新」排的。
思路取自 @drstrangerujn 的 #671,实现上改用 humanize.ClickNoWait:
筛选面板是 hover 浮层,rod 的 Click 会先 Hover + WaitInteractable,
在浮层上会误判被遮挡而死等到超时。
* refactor(search): 悬停走 humanize,删掉已成冗余的二次校验
悬停:filterButton.MustHover() 是 rod 原生实现(WaitInteractable +
一次 MoveTo),指针会直接跳到按钮上。新增 humanize.Hover,沿曲线移动
并在元素框内取带抖动的落点,与 Click/ClickNoWait 一致。至此
search.go 里不再有裸的 rod 交互调用。
校验:上一个提交删掉 TagsIndex 之后,validateInternalFilterOption 的
三项检查(组号范围、组存在、文本在组内)与 findInternalOption 完全
重合,而后者返回的选项本身就取自 filterOptionsMap,按构造必然合法,
这个校验永远不会失败。删掉函数与调用它的循环,convertToInternalFilters
成为唯一校验入口并在注释里写明。单测改为断言转换结果的组号与文本。
真机验证:搜「手冲咖啡」+ 排序「最多点赞」,面板正常展开、筛选生效,
结果按点赞降序(15441 / 8560 / 8443 / 6495 ...)。
|
2026-07-27 08:46:24 +08:00 |
|
zy
|
a007e5ab00
|
fix: 收敛输入与点击的交互原语,修评论加载上限与滚动兜底 (#759)
* feat(humanize): 点击补上按下停留与落点抖动
rod 的 Mouse.Click 是 Down 紧接着 Up、中间无间隔,且对同一元素返回的可点
位置是常量,于是每次点击的落点完全一致。
- 新增 Action ClickHold(median ~85ms),Click/ClickNoWait/ClickAt 改走
pressAndRelease:Down → 采样停留 → Up
- Click/ClickNoWait 的落点在元素框内做小幅抖动,幅度取
±min(边长 15%, 8px)——按比例是为了小元素不偏出去,封顶 8px 是因为宽
元素里真正可点的常常只是中间一小块
- ClickAt 只补停留、不抖动:落点是调用方算出来的
单测覆盖抖动幅度的上下界、抖动后仍落在框内、多次抖动确实分散;集成测试
TestClickPressAndScatter 在本地页面上校验停留时长与落点分散。
* fix(publish): 输入统一走 humanize.Type,浮层关闭改逐级降级
输入:publish 链路还剩 5 处 elem.Input(标签的 # 触发词、联想失败时的空格
兜底、定时发布时间、商品关键词)。elem.Input 在 CDP 插入之外还会多派发一轮
input/change,与 humanize.Type 的逐字符事件序列对不上,统一换掉。后两处需要
ctx,顺带把 ctx 穿到 setSchedulePublish/setDateTime/bindProducts/
searchAndSelectProduct;setDateTime 里的局部变量 input 与 rod/lib/input 包
重名,改叫 elem。
浮层:removePopCover 原本直接摘节点,改名 dismissPopCover 并改成逐级降级
Esc → 点空白 → 摘节点,每步后检查浮层是否已消失。前两步走正常交互,都无效
才落到最后一步——它只要节点还在就必定生效,不该拿发布失败去赌。MustRemove
改用 Remove,失败记日志而不是 panic。
mustClickPublishTab 的失败信息区分两种情况:找不到 TAB,和找到了但浮层一直
关不掉。原来都报「没有找到发布 TAB」,会把人往错方向带。
* fix(feed): 评论加载上限的零值按未设置处理,滚动兜底改用滚轮
上限:HTTP 详情接口传 load_all_comments=true 而不带 comment_config 时,会
滚满 defaultMaxAttempts(500) 轮。成因是两层叠加——HTTP 侧要的是嵌套的
comment_config,按内部字段名传扁平的 max_comment_items 会被 ShouldBindJSON
静默丢掉得到 MaxCommentItems=0;而 0 以前表示「无上限」。MCP 侧 limit 默认
20(README 也是这么写的),两个接口不一致。
改成零值一律按「未设置」处理、回落默认值。normalize 放在 action 层的
GetFeedDetailWithConfig,不是放在某个 handler,这样 MCP、HTTP 和以后新增的
调用方都覆盖得到。行为变化:HTTP 侧不带 config 时从「加载全部」变成「最多
20 条」,与 MCP 和 README 的既有约定对齐;要更多就显式传
comment_config.max_comment_items。
滚动:humanScroll 的兜底分支原本调 window.scrollTo。详情页评论在容器内滚动、
window 的 scrollTop 恒为 0(见 getScrollTop),实测滚 window 推不动评论容器,
读回来的位移也不是它的,等于空转。改成加大幅度再走一次滚轮,容器可滚时滚
容器、不可滚时沿滚动链冒泡到 window。这个兜底只在常规幅度没推动时才进。
v2.1.0
|
2026-07-27 00:49:18 +08:00 |
|
 zyandClaude Opus 5
|
59762c8e46
|
fix: 不再发布无法启动的平台二进制 (#758)
darwin/amd64 与 linux/arm64 没有可用的内置浏览器,二进制下载下来一启动
就会因为拿不到浏览器而退出。发一个必定失败的文件比不发更糟。
- tag-release.yml 停止构建与上传这两个平台
- README / README_EN 的下载列表删掉对应条目,并注明支持范围
test.yml 的五平台交叉编译保留:那是代码健康检查,与是否发布无关。
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
v2.0.1
|
2026-07-26 22:14:51 +08:00 |
|
 zyandClaude Opus 5
|
bd729d313b
|
ci: 停掉每次推 main 的自动发布,交叉编译降级为 PR 检查 (#757)
release.yml 每次推 main 都会打一个日期 tag 并建一个 GitHub Release。
它想填的是"两次发版之间让用户拿到修复"的空档,但本项目发版就是推个
tag 等几分钟,没有这个空档,机制本身没有存在必要。
而且它的清理步骤从未生效:grep 把 '^v' 锚在第一列,但 gh release list
第一列是标题("Release v2026..."),永远匹配不到,加上 continue-on-error
失败也不报警。本该只留 10 个,实际堆了 108 个 release 和 tag。
它还把自动构建标成 prerelease: false,下次推 main 就会顶掉 v2.0.0
成为 GitHub 上的 Latest。
删掉整个 release.yml。发版不受影响:tag-release.yml 与 docker-release.yml
只认语义化 tag,独立运行,且自带全平台二进制产物。
原先只有它在每次推送时做交叉编译,删掉后平台相关的编译错误会拖到发版
才暴露,所以把这项检查搬进 test.yml,只编译不留产物。
存量 108 个日期 release 和 tag 保持不动:不再新增后它们就是静止的历史
档案,删除不可逆且可能打断别人收藏的下载链接。
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
2026-07-26 21:55:33 +08:00 |
|
 zyandClaude Opus 5
|
35bafecc07
|
docs: 补一条发版规范 (#756)
区分两种 tag:语义化 vX.Y.Z 才是发版,日期 tag 是每次推 main 自动生成的。
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
2026-07-26 21:37:05 +08:00 |
|
 zyandClaude Opus 5
|
b8412a20b4
|
feat: 会话文件持久化 seed + 修正 cookies 路径优先级 (#755)
* fix: cookies 路径优先级,显式配置不再被残留文件盖掉
原优先级是 /tmp/cookies.json(存在即用)> COOKIES_PATH > 当前目录。
只要 /tmp 下躺着一个旧文件,就会无条件盖掉用户显式设置的 COOKIES_PATH——
指向账号 B 的配置被账号 A 的残留文件顶掉,且不报错。
改成:COOKIES_PATH > 当前目录 > /tmp(仅为老用户兜底)。
显式指定的配置永远最优先,与浏览器二进制的取值规则保持一致。
测试用 TMPDIR + t.Chdir 双重重定向,不碰真实 /tmp。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat: 会话文件持久化 seed,同一账号每次启动画像一致
未设 XHS_FP_SEED 时,每次启动都是一套新的随机画像——等于在不同机器上
反复登录同一个账号。首次登录生成一个 seed 存进会话文件,之后一直复用。
文件格式升为 v2(外层对象 + cookies 数组),读取时按有无 seed 字段
判断新旧:老的裸数组照常解析,不需要用户做任何事。cookies 用
json.RawMessage 原样嵌入,不经过结构体往返,避免字段走样。
seed 取值优先级:环境变量 > 会话文件 > 新生成并写回。环境变量必须
排第一,否则已经用它钉死画像的部署会被文件里的值顶掉。
注:升级后文件变为 v2,旧版本读不了(表现为未登录,重新扫码即可恢复)。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test: 断言只比语义,不比排版
原来有三处在断言字节相等或文件里的具体排版(`"version": 2` 的空格)。
这些锁的是"文件怎么写"而不是"系统保证什么"——把 Marshal 换成
MarshalIndent 时,行为没变,测试却红了,这就是在测实现细节。
统一改成解析后比内容:cookie 字段和取值一个都不能变,排版随意。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
v2.0.0
v2026.07.26.1327-b8412a2
|
2026-07-26 21:26:56 +08:00 |
|
 zyandClaude Opus 5
|
058e7b33c0
|
fix: 收口三处裸鼠标调用,指针不再瞬移 (#754)
humanize 包开头写着"业务代码不直接调 rod 的 Mouse.MoveTo",但实际还有
三处在裸调:曲线移动函数 moveMouseCurved 没有导出,点坐标(而非点元素)
的场景用不上 humanize.Click,只能退回瞬移。
补两个原语把这条不变量落实:
- humanize.MoveTo:沿曲线移到坐标,不点击
- humanize.ClickAt:沿曲线移到坐标再点击
改造三处调用:
- publish.go 新版发布按钮:MoveTo + Click → ClickAt
- publish.go clickEmptyPosition:MustMoveTo + MustClick → ClickAt,
顺带去掉 Must(兜底操作点不动不该 panic,改为记日志)
- feed_detail.go 移到评论滚动容器:MoveTo → humanize.MoveTo
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
v2026.07.26.1049-058e7b3
|
2026-07-26 18:48:38 +08:00 |
|
 zyandClaude Opus 5
|
4ceb2c329f
|
fix: 只用内置浏览器,拿不到就不启动 (#753)
之前未指定 --bin 时不会给 go-rod 传二进制路径,rod 会自行下载并启动一个
默认 Chromium。它与内置浏览器版本不同、编解码器不全,也不认识我们传的
那些启动参数——未知参数被静默忽略,日志照样打印 fingerprint enabled,
属于无声降级,出问题时完全看不出来。
浏览器来源收敛到唯一一处:内置浏览器,准备不出来就不启动。
- NewBrowser 直接调 EnsureBrowser 并显式传路径给 go-rod,失败即 panic
- 删掉 --bin 参数与 WithBinPath:当前平台没有预编译浏览器就是不支持,
比让用户自己塞一个来路不明的二进制安全
- 不再读 ROD_BROWSER_BIN:那是 go-rod 的环境变量,可能因无关原因被设过
- Dockerfile 把浏览器预置到 Go 端会查的缓存路径,运行时零下载、零参数;
XDG_CACHE_HOME 相应移出挂载卷,否则预置内容会被挂载盖掉
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
v2026.07.26.1021-4ceb2c3
|
2026-07-26 18:21:43 +08:00 |
|
 zyandClaude Opus 5
|
d680e834a9
|
feat: 笔记详情返回视频信息 (#752)
视频笔记的 __INITIAL_STATE__ 里有完整的 video 数据,但 FeedDetail
没有对应字段,unmarshal 时被整个丢掉,导致 type=video 的笔记只返回
一张封面图、没有任何视频地址。
补上 FeedDetail.Video,按页面结构原样映射,不替调用方挑档位:
- Stream 用 map[编码名][]VideoStream,不写死 h264/h265/av1/h266,
避免平台新增编码时再次被静默丢弃
- 三个不同精度的时长(capa 秒 / media.video 秒 / stream 毫秒)都保留
- 字幕只存在于 mediaV2 这个字符串副本里,自定义 UnmarshalJSON 单独解出
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
v2026.07.26.1004-d680e83
|
2026-07-26 18:04:02 +08:00 |
|
 zyandClaude Opus 5
|
9b2a86125d
|
fix: 评论查找先查后判、滚轮按格发送、去掉多余的指针瞬移 (#751)
* fix(comment): findCommentElement 先查找再判断到底
原循环把"是否到达评论底部"放在查找之前,页面一开始就在底部时会直接
退出、一次都不查。评论数少、整个列表一屏装得下的帖子,「THE END」从
开始就可见,回复因此必然失败——实测在已加载完评论的页面上复现:目标
元素就在 DOM 里,仍报未找到。
调整为每轮先查已渲染的评论,再判断是否到底,最后滚动加载更多。查找逻辑
抽成 lookupComment,滚动改用 humanScroll,去掉写死的滚动幅度与固定等待。
实测:33 条父评论的帖子,2 秒内定位到最后一条(修复前立即失败)。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* refactor(feed): 滚轮按格发送,点击前去掉多余的滚动与指针瞬移
smartScroll 原先一次调用把整段位移交给 rod,rod 内部会在同一时刻连发
多个幅度完全相同的滚轮事件。改为按滚轮格逐格发送,每格幅度小幅浮动、
格间留间隔。
moveToCommentScroller 原先每次都移到容器几何中心,一次加载会重复落到
同一点。改为落点在中心附近随机偏移,且指针已在容器内时不再移动。
clickElementWithHumanBehavior 原先先用 JS scrollIntoView(smooth) 再把
指针瞬移到元素中心,导致随后 humanize.Click 的移动因起止点重合而失效,
且 smooth 动画未结束就读取坐标存在竞态。改为用 CDP 滚动定位,落点交给
humanize.Click 自己处理。
实测:滚轮事件幅度各不相同、无同刻连发;展开回复的点击正常生效。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
v2026.07.26.0833-9b2a861
|
2026-07-26 16:33:29 +08:00 |
|
zy
|
e9e5781ad5
|
Revert "test: 固定 shouldSkipButton 的阈值边界语义 (#749)" (#750)
This reverts commit c473315afe.
v2026.07.26.0816-e9e5781
|
2026-07-26 16:15:44 +08:00 |
|
 zyandClaude Opus 5
|
c473315afe
|
test: 固定 shouldSkipButton 的阈值边界语义 (#749)
threshold 是"允许展开的上限":回复数等于阈值仍展开,严格超过才跳过;
threshold <= 0 表示不限制。补 12 个 case 覆盖边界、空格变体与不匹配文案。
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
v2026.07.26.0813-c473315
|
2026-07-26 16:12:49 +08:00 |
|
 zyandClaude Opus 5
|
98d65a473a
|
fix: 补 reply 工具 panic 保护、评论总数改读结构化数据、修正 feeds 列表文档 (#748)
* fix(mcp): reply_comment_in_feed 补上 panic 保护
13 个工具里只有它没包 withPanicRecovery,而其调用链有 MustEval 与
MustScrollIntoView,page 超时或取消时 panic 会逃逸到 SDK。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(feed): 评论总数改读 __INITIAL_STATE__
getTotalCommentCount 原先取 .comments-container .total 的文案再正则
提取「共 N 条评论」,实测取不到,恒返回 0(该值只用于一行 Debug 日志,
所以一直没被发现)。
改为读 noteDetailMap 里的 interactInfo.commentCount,不依赖评论区文案。
实测两条笔记分别返回 7 与 23,与页面数据一致。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: 修正 feeds 列表接口的 interactInfo 说明
推荐流实际只下发 liked 与 likedCount,文档样例却写了 commentCount、
collectedCount、sharedCount 并逐条解释,使用者照着写会取到空字符串。
样例改为真实形态,并补充说明:需要评论数请用搜索或详情接口(这两个
接口确实下发,实测确认,对应章节的样例保持不变)。likedCount 补注
是格式化字符串。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
v2026.07.26.0810-98d65a4
|
2026-07-26 16:09:49 +08:00 |
|
 zyandClaude Opus 5
|
efe6797039
|
chore: 收敛两处注释表述 (#747)
注释只描述代码行为,去掉与实现无关的补充说明。
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
v2026.07.26.0755-efe6797
|
2026-07-26 15:55:23 +08:00 |
|
 zyandClaude Opus 5
|
4082976615
|
fix: 输入改用 CDP 插入,滚动位移改读评论容器 (#746)
* fix(humanize): 输入改用 CDP 插入文本
Type 改为开头聚焦一次,之后每字符走 page.InsertText。保留原有的
WaitEnabled / WaitWritable 前置检查,ctx 取消语义不变。
补充集成测试:本地页面验证 <input> 与 contenteditable 两种形态下
每字符产生一次 input 事件、文本完整正确。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(feed): 滚动位移改读评论容器 scrollTop
评论区在 .note-scroller 容器内滚动,window 的 scrollTop 恒为 0,而
getScrollTop 一直读 window,导致位移读数恒为 0:
- humanScroll 中 scrolled 恒 false,每轮都走 window.scrollTo 兜底
(该页 window 不可滚,兜底实际无效)
- performScroll 每轮判定停滞,lastScrollTop 停在 0,largeMode 常开
并反复触发大冲刺
- 滚动 Debug 日志因 scrolled 恒 false 从不打印,问题被掩盖
改为读实际滚动的容器,容器不可滚时退回 window。选择器提取为
commentScrollerSelectors,与 moveToCommentScroller 共用一份定义。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
v2026.07.26.0753-4082976
|
2026-07-26 15:53:20 +08:00 |
|
 zyandClaude Fable 5
|
cbcaec5ba6
|
fix(feed): 评论加载滚动改用 go-rod 真实滚轮,替代 JS 合成事件 (#745)
- smartScroll: dispatchEvent(WheelEvent) → page.Mouse.Scroll(CDP 真实滚轮)
- 主滚动 performScroll 与 findCommentElement 的 window.scrollBy → 统一走 smartScroll
- 保留 scrollIntoView(元素定位)与滚动卡住时的 scrollTo 兜底
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
v1.2.9
v2026.07.26.0642-cbcaec5
|
2026-07-26 14:41:54 +08:00 |
|
 zyandClaude Fable 5
|
a32150a83f
|
feat: tag 驱动发布流程,版本号以 git tag 为唯一来源 (#744)
- 推送语义化版本 tag (vX.Y.Z) 自动触发: GitHub Release + Docker Hub + 阿里云镜像
- 合并 aliyun-docker-release.yml 到 docker-release.yml,一次构建双推送
- 二进制和镜像通过 ldflags 嵌入版本号,/health 接口返回 version
- tag-release.yml 构建补齐 CGO_ENABLED=0,与 release.yml 一致
- changelog 取上一个语义化版本 tag,排除自动日期 tag
- 保留 workflow_dispatch 手动触发作为兜底
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
v2026.07.26.0402-a32150a
v1.2.8
|
2026-07-26 12:02:02 +08:00 |
|
 zyandClaude Fable 5
|
85aed8b483
|
fix: 登录状态接口返回真实账号信息 (#741)
- CheckLoginStatus 已登录时从 explore 页 __INITIAL_STATE__ 读取真实昵称与 user_id,替换写死的 configs.Username 占位符
- 复用登录检查已加载的页面,零额外导航;读取失败仅记 warn 不影响状态返回
- 响应新增 user_id 字段(个人主页 URL 中的稳定唯一标识),便于确认当前登录账号
- 删除不再使用的 configs/username.go
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
v2026.07.23.0205-85aed8b
|
2026-07-23 10:05:23 +08:00 |
|
 zyandClaude Fable 5
|
a49e7c5129
|
fix: Star History 图表改为 star-history 数据分支发布 (#740)
main 有 require-PR 保护规则, bot 无法直接 push;
图表由 workflow force-push 到独立数据分支(只含图表文件),
README 引用其 raw URL, main 从此无需 bot 提交。
内置 GITHUB_TOKEN 经 GraphQL 回退已验证可用, 无需 PAT secret。
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
2026-07-21 21:47:17 +08:00 |
|
 zyandClaude Fable 5
|
9198f21389
|
fix: README Star History 图表改为自托管生成 (#738)
star-history.com 因 GitHub 2026-06-30 限制 stargazers API 而失效
(官方 issue star-history/star-history#539), 全生态托管图表均不可用。
改为自研 xpzouying/star-history Action 每周在 CI 内用仓库自身
GITHUB_TOKEN 生成静态 SVG (亮/暗双版), README 用 picture 标签自适应主题。
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
2026-07-21 20:57:17 +08:00 |
|
 zyandClaude Fable 5
|
a7d1f2f7f4
|
feat: 内置浏览器自动下载 + 交互拟人化 + 写操作诚实校验 (#737)
内置浏览器:首次运行自动从自建 CDN 下载并校验 SHA256,免手动装 Chrome;
Docker 构建时烤入镜像;支持 Windows 运行画像;每账号可固定 XHS_FP_SEED。
交互拟人化:评论/回复、点赞/收藏、发布图文与视频、feed 浏览、搜索筛选的
点击/输入/停顿改为更接近真人的时序与轨迹(log-normal 延迟、逐字输入、
贝塞尔鼠标),统一走 humanize 包,预留 Provider 注入点。
诚实校验:写操作提交后回读真实结果(评论是否渲染、点赞状态是否翻转、
发布是否跳转发布成功页),消除“点了没报错就算成功”的假成功;
check_login_status 加超时保护;原创声明失败不再静默照发。
分发:内置浏览器走自建 CDN(cdn.one-world.ai)。
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
v2026.07.20.0701-a7d1f2f
|
2026-07-20 15:01:23 +08:00 |
|
tan jun
|
492cf008a1
|
docs: Update WeChat QR code image in README (#736)
|
2026-07-20 11:52:23 +08:00 |
|
tan jun
|
5c5197d686
|
docs: Update WeChat QR code image in README
|
2026-06-29 10:07:48 +08:00 |
|
tan jun
|
aa39271e8d
|
docs: Update WeChat QR code image in README
|
2026-06-23 10:03:10 +08:00 |
|