koishi-plugin-kkk 3.10.1 → 3.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +272 -0
  2. package/assets/web/assets/index-BRROK0vh.js +1 -1
  3. package/lib/build-metadata.json +5 -5
  4. package/lib/compat/imageMarkdown.js +128 -56
  5. package/lib/compat/imageSize.js +112 -0
  6. package/lib/index.js +175 -24
  7. package/lib/karin/apps/tools.js +111 -1
  8. package/lib/karin/module/utils/Base.js +97 -5
  9. package/lib/karin/module/utils/CardImageCache.js +134 -0
  10. package/lib/karin/module/utils/CommentPics.js +76 -0
  11. package/lib/karin/module/utils/ImageSlice.js +35 -10
  12. package/lib/karin/module/utils/ParseSteps.js +44 -5
  13. package/lib/karin/module/utils/QqPanel.js +341 -49
  14. package/lib/karin/module/utils/Render/index.js +13 -25
  15. package/lib/karin/platform/bilibili/bilibili.js +128 -72
  16. package/lib/karin/platform/bilibili/push.js +86 -22
  17. package/lib/karin/platform/douyin/douyin.js +71 -28
  18. package/lib/karin/platform/douyin/push.js +10 -8
  19. package/lib/karin/platform/kuaishou/kuaishou.js +65 -1
  20. package/lib/karin/platform/xiaohongshu/xiaohongshu.js +102 -45
  21. package/lib/qqFields.json +10 -0
  22. package/package.json +1 -1
  23. package/scripts/build.mjs +43 -13
  24. package/scripts/patch-webui.mjs +12 -0
  25. package/scripts/probe-extract-buttons.cjs +326 -0
  26. package/scripts/probe-group-file-name.cjs +259 -0
  27. package/scripts/probe-md-image-size.cjs +247 -0
  28. package/scripts/smoke-commands.cjs +85 -31
  29. package/scripts/smoke-koishi-commands.cjs +253 -135
  30. package/src/compat/imageMarkdown.ts +128 -49
  31. package/src/compat/imageSize.ts +144 -0
  32. package/src/index.ts +189 -22
  33. package/src/karin/apps/tools.ts +124 -1
  34. package/src/karin/module/utils/Base.ts +99 -4
  35. package/src/karin/module/utils/CardImageCache.ts +163 -0
  36. package/src/karin/module/utils/CommentPics.ts +85 -0
  37. package/src/karin/module/utils/ImageSlice.ts +34 -10
  38. package/src/karin/module/utils/ParseSteps.ts +37 -3
  39. package/src/karin/module/utils/QqPanel.ts +342 -48
  40. package/src/karin/module/utils/Render/index.ts +13 -21
  41. package/src/karin/platform/bilibili/bilibili.ts +124 -82
  42. package/src/karin/platform/bilibili/push.ts +84 -23
  43. package/src/karin/platform/douyin/douyin.ts +74 -34
  44. package/src/karin/platform/douyin/push.ts +11 -10
  45. package/src/karin/platform/kuaishou/kuaishou.ts +64 -2
  46. package/src/karin/platform/xiaohongshu/xiaohongshu.ts +103 -42
  47. package/src/qqFields.json +10 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,277 @@
1
1
  # 更新日志
2
2
 
3
+ ## 3.11.0
4
+
5
+ ### 修「发送视频」失败:QQ 群文件名不能直接用作品标题
6
+
7
+ 用户贴的日志:
8
+
9
+ ```
10
+ [视频发送] 平台=qq 体积=40.6MB QQ阈值=30MB 走群文件=true
11
+ 被动消息: 视频大小: 40.6MB 正在通过e.bot.uploadFile回复...
12
+ 视频文件上传错误,Error
13
+ [ErrorHandler] 原始错误: 解析过程中有 1 个步骤失败(发送视频):
14
+ Error
15
+ at _QQMessageEncoder.send (…/@satorijs/core/lib/index.cjs:756:13)
16
+ ```
17
+
18
+ 查了原始日志(`data/logs/2026-10-01-*.log`),事实是:**40.62MB、走群文件、1.8 秒就失败**,
19
+ 而且**今天同一个快手视频连失败三次**;历史上走群文件成功的三次是 B站 / 抖音 / onebot。
20
+
21
+ 根因:**群文件名**。
22
+
23
+ - 快手的 `originTitle` 就是**作品标题原文**(`kuaishou.ts` 里 `originTitle: \`${work.photo.caption}.mp4\``),
24
+ 这条视频的标题是一段**带换行、224 个字符**的多行文案
25
+ (`《打电话赢 1 万!…爽到爆》\n\n三大现金挑战…`);
26
+ - 群文件那条分支以前把 `originTitle` **原样**拼上 `.mp4` 当 `file_name` 发给 QQ 的
27
+ `upload_prepare` —— 名字里带 `\n`,QQ 直接判参数错误,**视频一个字节都没发出去**;
28
+ - 标题本身已经带 `.mp4` 时还会拼成 `xxx.mp4.mp4`;
29
+ - 同一条链路在 B站 上是好的,因为 `bilibili.ts` 早就做过
30
+ `substring(0, 50).replace(/[\\/:*?"<>|\r\n\s]/g, ' ')` —— 快手 / 抖音 / 番剧那几处漏了。
31
+ 磁盘上的文件名有 `processFilename` 兜着,所以这个问题**只在「发到 QQ」时**暴露,本地看不出异常。
32
+
33
+ 改法:
34
+
35
+ - 新增 `Base.groupFileName(file)`:去掉调用方自带的 `.mp4` → 把
36
+ `\ / : * ? " < > |`、换行、制表符、控制字符换成空格 → 去掉开头的点 → 截到 60 字符 →
37
+ 标题为空时退回 `filepath` 的文件名,都没有才用 `kkk_<时间戳>.mp4`。
38
+ 一处收口,四个平台(含定时推送里 `useGroupFile: true` 那条)一起好。
39
+ - 群文件发送**失败换名字重试一次**:先用清洗后的标题名,被 QQ 打回就换
40
+ `kkk_<时间戳>.mp4` 再来一遍。群文件名是这条链路上唯一「因文件而异」的入参,
41
+ 换个名字往往就通了,代价只有一次调用。
42
+ - **错误信息不再只说「Error」**:适配器抛的是 `@satorijs/core` 的 `AggregateError`,
43
+ 它 `super('')` —— message 是空的,真正的原因在 `errors: Error[]` 里
44
+ (`QQ 消息发送失败 [400xxxxx] xxx`)。以前的日志把它整个丢掉,只剩
45
+ `视频文件上传错误,Error` 和一句「解析过程中有 1 个步骤失败(发送视频):」。
46
+ 新增 `ParseSteps.describeError`,把 `errors[]` / `cause` 逐层剥开,
47
+ 日志里、错误卡片上、`throwIfFailed` 合成的错误里都带上了真正的原因。
48
+ - 顺带修 `临时预览地址:http://localhost:undefined/kkk/ssr/video/…`:
49
+ `process.env.HTTP_PORT` 是上游 karin 的环境变量,Koishi 侧没有 ——
50
+ 改成读 `ctx.config.port`,拿不到再退回环境变量 / 7777。
51
+ - 顺带修 `scripts/build.mjs` 里唯一一处**同步起子进程**
52
+ (`execFileSync('git', ['rev-parse', 'HEAD'])` 取 commit hash):这台开发机上同步
53
+ `spawn` 会被直接掐掉,表现是**构建一行输出都没有就退出**。改成直接读 `.git/HEAD`
54
+ (含 `packed-refs` / worktree 两种形态),`GITHUB_SHA` 仍作兜底。
55
+ - 新增 `scripts/probe-group-file-name.cjs`(38 项,全程离线):用**这条视频的真实标题**
56
+ 断言文件名的合法性;端到端真调 `uploadFile`,断言交给适配器的名字里没有换行;
57
+ 断言失败后会换临时名重试、且只重试一次;断言 `describeError` 能挖出 `AggregateError`
58
+ 里的错误码;外加源码守卫。
59
+
60
+ ### 「是否收集评论区的图片」不再是摆设;评论区没图就不挂按钮
61
+
62
+ 用户两句话,一个主题:**按钮和开关都得名副其实**。
63
+
64
+ 1. 「评论区没有图片就不要显示按钮了」—— 一条评论都没贴图时,「提取评论区图片」
65
+ 点下去什么也发不出来(缓存里没有那张图),是个**点了没反应的摆设**。
66
+ 2. 「是否收集评论区的图片……这个开不开都没有用啊」—— 那个开关**确实一直是个摆设**:
67
+ B站视频那条链路上的实现被写死成 `if (false && …)` 整段短路了,点开点关都一样。
68
+
69
+ 现在:
70
+
71
+ - 新增 `QqPanel.hasCardImage(key, 'cover' | 'comment')`:**缓存里没有那张图就不挂对应按钮**。
72
+ 「提取评论区图片」要的是**评论里用户自己贴的图**(`commentPics`),不是渲染出来的那张评论长图
73
+ —— 长图卡片本身已经发过一遍了,再挂个按钮去重发它没有意义。
74
+ 四个平台(B站 / 抖音 / 快手 / 小红书)统一走这个口径,原生按钮和 markdown 文字链两条路都走。
75
+ 抖音以前连 `commentPics` 都不记,按钮只能退回去重发长图 —— 现在补上了。
76
+ - 「是否收集评论区的图片」**真的接上了**:打开时解析完直接把评论区里用户贴的图**发一条**
77
+ (优先合并成一条 markdown:官方 QQ bot 上合并转发经常发不出去,会退化成一张图一条消息;
78
+ 不支持 markdown 的适配器才退回 `makeForward`),
79
+ **并且不再挂「提取评论区图片」按钮** —— 图已经在群里了,再挂一个只是重复发一遍。
80
+ 关着(默认)就照旧:不直接发,按钮挂上。
81
+ - 新增 `src/karin/module/utils/CommentPics.ts`:把「评论区图片直接发」收成**一个**函数,
82
+ 返回「**到底发出去了没有**」,调用方拿这个结果决定按钮挂不挂。
83
+ 三个调用点(B站视频 / B站动态 / 抖音)原本各抄一份,而这次的规则要横跨「发送」和「挂按钮」两处,
84
+ 抄三份必然会漏改一处,出现「发了图还挂按钮」或「没发图也没按钮」这种半截状态。
85
+ - 发失败(没图 / 图全部落地失败 / 适配器报错)**不算发过**,按钮照挂 —— 别让用户没路走。
86
+ - 图**直接发过之后仍然记进缓存**:按钮不挂只是「别再点重发」,手敲 `kkk评论` 还要读这份缓存。
87
+ - 面板上那个开关的说明也跟着改了(`scripts/patch-webui.mjs` 里加了一条描述改写):
88
+ 「打开后:…卡片下面就不再挂『提取评论区图片』按钮了;关闭(默认):…想单独存图再点一下。」
89
+ 免得用户再对着文案猜它到底管什么。
90
+ - `scripts/probe-extract-buttons.cjs` 重写:覆盖「只有封面 / 有评论图 / 只有评论长图 / 没有作品 id」
91
+ 四种缓存状态下四个平台按钮的挂与不挂、开关打开后的按钮抑制,
92
+ 以及 `sendCommentPicsDirectly` 的四条出口(没图 / 落地失败 / 正常两张 / 发送抛错),
93
+ 外加一条**源码守卫**(产物里不许再出现 `if (false && Config.…)` 这种短路,且开关真的被读到)。
94
+ 全程离线(探针自己起 `127.0.0.1` 服务供图),28 项全过。
95
+
96
+ ### 所有指令收进 `kkk` 分组(控制台里不再铺一地)
97
+
98
+ - 以前 40 条指令全是**顶层**的(`解析`、`kkk帮助`、`b站登录`、`设置抖音推送`、
99
+ `抖音全部强制推送`…),控制台的「指令」页从头翻到尾。
100
+ - 现在统一挂在 **`kkk` 分组**下:`kkk.解析`、`kkk.帮助`、`kkk.版本`、`kkk.B站登录`、
101
+ `kkk.设置抖音推送`、`kkk.下载进度`…… 共 34 条子指令 + 1 个分组节点。
102
+ - Koishi 的指令树是**靠名字里的 `.` 分词**的(`ctx.command('kkk.帮助')` 会顺手建出父指令 `kkk`),
103
+ 而控制台只把**名字里带 `.`** 的子指令收进 `children` ——
104
+ 所以「只把 `parent` 指过去、名字还叫 `kkk帮助`」是没用的,名字必须真的写成 `kkk.xxx`。
105
+ - 显示名会**去掉前导的 `kkk`**(`kkk帮助` → `帮助`、`kkkB站登录` → `B站登录`),
106
+ 免得控制台里出现 `kkk/kkk帮助` 这种套娃。
107
+ - **老名字一个都没少,而且不能少**:用户习惯、QQ 按钮里的指令文本(`解析 … --qn=16`)、
108
+ 面板上的 📊 按钮(`下载进度`)用的都是裸名字,所以全部原样挂成**别名**。
109
+ `解析` / `kkk解析` / `弹幕解析` 这类本来就指向同一个 handler 的名字会**合并成一条指令**
110
+ (`解析` 与 `kkk解析` 现在是同一条),控制台里不再重复出现。
111
+ - 四种写法都能用:`解析 <链接>`、`/解析 <链接>`、`kkk.解析 <链接>`、`kkk 解析 <链接>`。
112
+ 后两种是分组带来的新写法,`action` 里会把 `kkk` + 分隔符(空格 / 点 / 全角空格)
113
+ 还原成 karin 注册表认的那个名字再往下传(移植过来的业务代码按
114
+ `e.msg.replace(/^#kkk帮助/, '')` 这种裸名字写的,不能改一行)。
115
+ - 只敲 `kkk` 会列出所有子指令(父指令上挂了个 action),子指令照常优先。
116
+ - **顺手修**:面板上那个 📊「查询下载进度」按钮走的是**回调按钮**(`interaction/button`),
117
+ 回调文本进的是文本兜底(`runTextCommand`),而 `下载进度` 从来就不是 karin 注册表里的指令 ——
118
+ 也就是说**点了没反应**。现在文本兜底里单独认一下 `#?(kkk)?下载进度`。
119
+ - `alias()` 撞名会抛异常、把整个 `apply` 拖死(apply 一挂,指令和控制台路由全都上不去)。
120
+ 正常升级路径上旧指令会先被 dispose 掉、撞不上,但热重载时序偶尔会让上一个实例的指令还在表里,
121
+ 所以挂别名包了一层 try/catch:撞名只警告(重启即恢复),绝不让 apply 抛出去。
122
+
123
+ ### md 图片的尺寸改成「真量」;量不到就不发 markdown(修「图片被强制拉伸」)
124
+
125
+ 用户先后反馈过两件事,根因是同一个:**尺寸是猜出来的**。
126
+
127
+ 1. 「发送的所有 md 图片都要 `#宽px #高px`,不然根本看不见」——
128
+ QQ 的 markdown 图片必须写死尺寸,`![](url)` 在手机端是**一片空白**。
129
+ 2. 「自适应发送的图片长宽高有些被强制拉伸了」——
130
+ 读不出尺寸时以前会**猜**一个 `宽度 × 宽度×1.3` 的框填进去,
131
+ 而 QQ 是**按声明的框渲染**的:框的比例和原图对不上就被硬拉变形。
132
+
133
+ 现在统一成一句话:**尺寸必须来自真实测量,量不到就返回 `null`、退回普通图片段发**
134
+ (普通图片会被客户端压得糊一点,但比例是对的、也看得见,比一张拉变形的图强)。
135
+
136
+ - 新增 `src/compat/imageSize.ts`:一个**无依赖**的叶子模块,从二进制头读 PNG / JPEG / GIF / WebP
137
+ 的真实像素尺寸(含 WebP 的 `VP8X` / `VP8L` / `VP8 ` 三种头)。
138
+ 以前「读尺寸」在插件里有**四个消费方各写各的**,能力还不一样 —— 同一张 WebP
139
+ 在一条链路上能读出来、在另一条上读不出来,读不出来的那条只能猜。
140
+ 现在四条链路共用它,格式覆盖一次就全都覆盖。
141
+ - `Render.getImageMetadata` 从「只认 PNG / JPEG」换成它;
142
+ `compat/imageMarkdown.ts` 里那份重复的读取实现删掉;
143
+ `QqPanel` 的面板卡片尺寸也走它。
144
+ - **删掉所有「读不出就猜」的兜底**:
145
+ - `QqPanel.toMarkdownImage` 原来有**四处**「读不出尺寸就按 `maxWidth × maxWidth×1.3` 估一个」
146
+ (这就是拉伸的来源)。现在:读不出 → 先补一次 ffprobe → 还是不行就 `null`;
147
+ 宿主的 `assets` 服务没给出公网地址、图片下载失败,同样 `null`。
148
+ - `QqPanel.panelCardInfo` 新增:面板卡片的 `#宽px #高px` 只用真实值,
149
+ 读不出就**不发这张卡**(不再 `|| 1440` / `|| 1080` 兜一个)。
150
+ - 发送出口的兜底(`compat/imageMarkdown`)也跟着改:
151
+ - `ensureMarkdownImageSize` 保留为**估算版**,只在「连图都读不到」时当最后一道兜底;
152
+ - 新增 `sizeMarkdownImages`:**先把图下下来量真实尺寸**,量到就用真实比例,
153
+ 量不到才退到估算。走的是它,不是估算那版。
154
+ - 这层兜底要**放在「有没有图片段」的判断之前**:长图切片那种消息整条只有
155
+ markdown、一个图片段都没有,放后面会整条漏掉。
156
+ markdown 段的正文在 `children[].attrs.content`(`segment.markdown` 走 satori 的 `h()`),
157
+ 不在 `attrs.content` 上,两种形状都认(改写函数因此变成 async)。
158
+ - `scripts/probe-md-image-size.cjs`:探针重写成**离线可重复**的
159
+ (自己起一个 `127.0.0.1` 的服务提供确定尺寸的图),
160
+ 覆盖「800×600 要缩成 420×315 而不是 420×546」「读不出尺寸 / 404 / base64 / data:
161
+ 一律返回 null」「量不到才用估算」等 39 项。
162
+
163
+ ### 封面解析也要去重
164
+
165
+ - 出面板(B站 / 抖音那条「封面 + 画质表格」)原来是在作品级去重**之前**就 return 的,
166
+ 于是短时间内重复发同一条链接会一遍遍重新出面板;
167
+ 反倒**点清晰度按钮是有去重的**(按钮命令带 `--qn` / `--q`,绕过面板走到作品级去重)。
168
+ - 现在首发链接也按「平台 + 会话 + 用户 + 作品 id」挡一次,**画质不在键里**
169
+ (选了画质走下面那道作品级去重,两者互不干扰)。
170
+ - 面板按钮点出来的(带 `--p` / `--qn` / `--panel` / `--bgp`)一律放行,
171
+ 否则「重发面板」「上一页 / 下一页」会被它自己上一次的点击挡住。
172
+
173
+ ### 卡片下面加「提取封面图 / 提取评论区图片」
174
+
175
+ - 卡片在 QQ 上是一整条 markdown 图片,几张图叠在一起,想单独存封面或只看评论区长图很不方便。
176
+ 现在卡片下面多一行两个按钮,点一下把那张图**单独发一遍**(md 格式,不重新渲染卡片)。
177
+ - 按钮挂在**评论区那条长图**下面(详情卡片下面也有):QQ 上的解析基本都是
178
+ 「发链接 → 出面板 → 点清晰度」,这种走法详情卡片是不发的(面板已经发过那张卡),
179
+ 评论区才是每次都能看到的那条。
180
+ - **按钮和图片在同一条消息里**:不再单独发一条按钮消息,而是直接拼在图片下面一起发
181
+ (详情卡片、评论区卡片都是 `[...图片, ...按钮]` 一次 reply;
182
+ 评论长图要走切片,切片发送加了「附加元素」参数,按钮只挂在**最后一组**切片后面)。
183
+ - **按钮不在画质面板里**:点清晰度后面板会被撤回(`replyReplacing`),挂在面板上的按钮
184
+ 跟着一起没了,点了没反应。面板只在**内部**把封面记进缓存,不显示按钮。
185
+ - **改成 QQ 原生回调按钮**(`keyboard`,`action.type = 1`),不再是 markdown 里的
186
+ `<qqbot-cmd-input>` —— 那个在 QQ 上渲染成**蓝色文字链**,不是按钮。
187
+ 原生按钮点一下直接把指令回调给机器人(走 `interaction/button` 事件,插件里已有监听),
188
+ **不用再往输入框塞文本、不用再点一次发送**。
189
+ 适配器不支持原生按钮时(官方 `@koishijs/plugin-adapter-qq` 没实现 keyboard)自动退回原来的文字链。
190
+ - **两张卡各带各的按钮**,不再堆在最后一条下面:
191
+ 封面 / 详情卡片 → 「提取封面图」;评论区 → 「提取评论区图片」。
192
+ 动态卡片同样按内容给(有封面才给「提取封面图」),动态评论区给「提取评论区图片」。
193
+ - **面板那张封面卡上也带「提取封面图」**:发链接后第一条是面板,封面是**单独一条消息**
194
+ 发的、选清晰度时只撤下面那张表格、卡片留在群里,所以按钮挂在这里点得到
195
+ (挂在画质表格上不行 —— 一选清晰度整条就被撤了)。面板上**不给**「提取评论区图片」,
196
+ 那会儿评论图还没渲染。
197
+ - **修:点「提取评论区图片」却变成重新解析一遍视频**。
198
+ 按钮回调的文本是 `kkk评论 bilibili:BV1JSan6GEFW`,而 B站链接指令的正则是靠 BV 号认链接的
199
+ —— **它也能匹配这串字**。`runTextCommand` 按注册顺序取第一条命中,而各平台链接指令
200
+ 的优先级是 800、提取指令原来没设(0),于是被抢走、当成「解析这条链接」跑了一遍。
201
+ 现在提取指令优先级提到 **900**(正则锚在 `^#?`,抢在最前面也不会误伤别的消息)。
202
+ - **「提取评论区图片」改发评论里用户贴的图**。
203
+ 之前发的是插件**渲染出来的那张评论长图**,而那张卡片本身已经发过了 —— 等于原样重发。
204
+ 现在缓存里分开记两类图:渲染卡片(`comment`)和评论里用户自己贴的图(`commentPics`,
205
+ 取自 `bilibiliComments` 的 `image_urls`),按钮优先发后者,
206
+ 点击时过一遍 `processImageUrls` 落地成能发的地址;一条评论都没贴图时才退回渲染卡片。
207
+ 视频评论 / 动态评论两处都记了,与「评论区图片收集」那个开关无关(关着也能提取)。
208
+
209
+ ### QQ 适配器设置:图片超过指定高度就自动切片
210
+
211
+ - 新增 **`sliceImageAutoHeight`**(「图片发送」组,默认 **6000** 像素,填 `0` = 关)。
212
+ - 原来的「超大图片自动切片」是**先整张发一次、被 QQ 拒收了才补切**:QQ 收得下的超高图
213
+ 就这么整张出去了。而太高的图 QQ 会整体缩小显示,卡片下面挂了提取按钮时,
214
+ 缩图留下的空白会被按钮撑开 —— 一大片空白很难看。
215
+ - 设了阈值就**直接切开**不再试发,每片都在正常尺寸内,没有这块空白。
216
+
217
+ - **按钮带作品参数**:指令形如 `kkk封面 bilibili:BV1JSan6GEFW`。
218
+ 之前按「会话最近一次解析」取图,群里**又有人发了别的链接**之后缓存就被覆盖,
219
+ 回头点老卡片下面的按钮会发出来**另一个作品**的图。现在缓存按「平台 + 作品 id」存,
220
+ 按钮把键当参数带上,点哪张卡就取哪个作品(手敲不带参数时才回退到本会话最近一条)。
221
+ - **动态也有这两个按钮**:B站动态卡片(图文 / 纯文 / 转发 / 视频 / 专栏)和动态评论区
222
+ 下面都挂了;动态的作品键是**里面的 BV**(视频动态)或动态 id,
223
+ 封面取视频封面 / 第一张图 / 被转发的那条;没有封面的(纯文字)只给「提取评论区图片」。
224
+ - **只有 QQ / QQ 频道有按钮**;其它平台显示一句提示,照着「引用这条消息发送 `kkk封面 …` /
225
+ `kkk评论 …`」操作即可。
226
+
227
+ ### 小红书 / 快手也补上了这两个提取按钮
228
+
229
+ - **小红书**:上一版只做了 B站 / 抖音,小红书整条链路一次都没调用过按钮,
230
+ 所以发小红书链接时卡片下面什么都没有。现在笔记信息卡(封面 + 字体正文 + 数据的详情卡)
231
+ 下面挂「提取封面图」,评论卡片下面挂「提取评论区图片」。
232
+ 封面取 `image_list[0]`,视频笔记的 `image_list` 常为空数组,按
233
+ `video.image.first_frame` → `video.cover` 兜底(和详情卡片显示的是同一个值)。
234
+ - **快手**:这条链路**只有评论区一张卡** —— 作品信息本身就画在评论卡顶部,没有单独的封面卡,
235
+ 所以封面与评论区**两个按钮都挂在评论卡下面**。封面取 `photo.coverUrls[0].url`。
236
+ - **「提取评论区图片」发的是评论里用户贴的图**,两个平台各自的取法:
237
+ 小红书取每条评论的 `pictures`(根评论 + 子评论都收,按出现顺序去重 ——
238
+ 卡片里每条只渲染了第一张,按钮要发的是全部);
239
+ 快手取 `rootComments[i].attachments[].content.smallUrl`(多 CDN 取第一条)。
240
+ - 两处都按「平台 + 作品 id」记缓存,按钮回调带着 `xiaohongshu:<note_id>` / `kuaishou:<photoId>`,
241
+ 和 B站 / 抖音同一套口径(15 分钟过期、过期提示重发链接)。
242
+
243
+ ### 推送消息下面的「解析」按钮
244
+
245
+ - 定时推送出来的动态 / 作品卡片自带链接,但没开自动解析时没人会去解析它。
246
+ 现在卡片下面多一个「解析」按钮,点一下即按这条链接解析。
247
+ - 以前这里用的是旧式回调按钮 `segment.button`,且只认 `adapter.name === 'QQ Official Bot'`,
248
+ 很多部署上根本不出按钮。现在和「提取图」按钮同一套写法:QQ / QQ 频道给 markdown 按钮,
249
+ 其它平台给文字提示。B站、抖音推送都改了。
250
+ - **解析的是动态里面的作品,不是动态本身**:推送卡片已经把这条动态整个渲染出来了,
251
+ 再点「解析」去解析同一条动态等于把刚才那张卡片原样再发一遍,没有意义。现在按钮取的是
252
+ **动态里带的那个作品** —— 视频动态取里面的 BV;转发动态取**被转发的**那一条(转发本身只是个壳);
253
+ 图文 / 纯文字动态翻正文富文本和网页卡片里的跳转链接,取第一条**能解析的作品链接**
254
+ (话题页、`t.bilibili.com` 动态页、个人主页、专栏、直播间这类会被跳过)。
255
+ 一条动态里什么作品都没带的(纯文字、纯图文、转发了条纯文字)→ **不显示按钮**。
256
+ - 按钮前面加了换行:原来按钮元素是拼在卡片内容末尾的,不加换行会粘在图片那一行后面。
257
+ - **只有 QQ / QQ 频道有按钮**;其它平台显示一句提示,照着「引用这条消息发送 `kkk封面` /
258
+ `kkk评论`」操作即可。两个平台都能用的指令:`kkk封面`、`kkk评论`。
259
+ - 点按钮**不重新解析、不重新渲染**:图源取自上次解析(按会话缓存 15 分钟,
260
+ 换了作品就整条作废;评论长图是临时文件,会被定时清理,过期就提示先发条链接)。
261
+
262
+ ## 3.10.2
263
+
264
+ > 修小红书图文笔记的图集一次解析发两遍的问题。
265
+
266
+ ### 修小红书图集发两遍
267
+
268
+ - 图文笔记的图集有**两个发送点**:一个不看「发送内容」开关、直接发一条 markdown 全套图,
269
+ 另一个按 `sendContent` 里的「图片」发(含实况图处理)。两个都命中时就是**两套图集**。
270
+ - 删掉前面那个不看开关的,图集统一由「图片」那处发送;关掉「图片」现在是真的不发了
271
+ (以前关掉照样会发一份)。
272
+ - 顺带给图集发送加了失败兜底:发不出去只记一步失败,后面的视频和错误卡片照常,
273
+ 不再把整条解析掀翻。
274
+
3
275
  ## 3.10.1
4
276
 
5
277
  > B站的复制跳转修一下:3.10.0 改成用用户发的那条链接后,B站 App 复制了反而不跳。