koishi-plugin-kkk 3.3.0-beta.1 → 3.3.0-beta.3

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.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,12 @@
1
1
  # 更新日志
2
2
 
3
- ## 3.3.0-beta.1
3
+ ## 3.3.0-beta.3
4
+
5
+ ### 修了两个「设置根本没生效」的问题
6
+
7
+ - **「强制在线播放的适配器」这类设置以前是静默失效的**:运行时配置是一份**手写白名单**,新加的开关(强制在线播放的适配器、面板「在线看」列、按需切片、解析去重…)忘了往里补一行,运行时读到的就是 undefined —— 表现就是 B站私聊明明配了强制在线播放,**还是去下载并发视频文件**。现在改成「字段表里的默认值打底 + 归一化覆盖」,以后再加开关不会漏。
8
+ - **不支持的适配器不再尝试合并转发**:判断「这个适配器支不支持聊天记录」用的是**黑名单**(只排除 QQ 官方),于是任何自定义适配器都被当成支持 —— B站私聊适配器(platform 就是 bilibili)也被算进去了,整条转发发出去「没有拿到消息 ID」,连退回来的逐条发送也失败,用户什么都没收到。现在改成**白名单**(OneBot 系平台名,或适配器自带 sendGroupForwardMsg/sendPrivateForwardMsg),并会在日志里写明「适配器 xxx 不支持合并转发,本次逐条发送」。
9
+ ## 3.3.0-beta.2
4
10
 
5
11
  > **预览版**:给愿意先试的人用。npm 上默认安装的(latest)还是 3.2.2,想装这个用 npm i koishi-plugin-kkk@beta(或者面板里的「检查更新」换成 beta 通道)。
6
12
 
@@ -11,6 +17,7 @@
11
17
 
12
18
  ### 顺手修掉的几个问题
13
19
 
20
+ - **预览版不会再被提示「有新版本」了**:更新检测以前一律拿 npm 上的 latest 比,本地跑预览版时会说「有新版本 3.2.2」—— 那其实是**降级**。现在本地版本带 -(beta)时改看 beta 通道,而且只在远端确实更新时才提示。
14
21
  - **修了快手/小红书一解析就报错的 bug**:这两个平台的处理里调用了在线播放器的 `applyForceOnlinePlayer`,却**从来没 import 过它** —— 一发快手链接就是 `ReferenceError: applyForceOnlinePlayer is not defined`(错误卡片倒是发得挺快)。现在补上 import,并加了 `scripts/smoke-imports.cjs` 把这类「用了没 import」钉死(自检 + 全仓库扫描,实测能抓到这次这个坑)。
15
22
  - **错误提示的分段加了空行**:markdown 里单个换行不算换行,两段会被拼成一整行、QQ 那边字号被撑得很难看;现在 ID 一行、说明一行,中间空一行。
16
23
  - **卡片解析不再去搜抖音**:抖音那边没有「卡片消息」这种东西,以前那条「当前接口库没有抖音搜索能力,跳过」的日志就是多搜了一个平台留下的 —— 现在只搜 B站(入口本来就只放 B站卡片进来),少一次风控风险、也少几秒等待。
@@ -28,6 +28,8 @@ const node_url_1 = require("node:url");
28
28
  const node_fs_1 = __importDefault(require("node:fs"));
29
29
  const node_path_1 = __importDefault(require("node:path"));
30
30
  const koishi_1 = require("koishi");
31
+ // 版本比较复用注入器那边的实现(它处理了 -beta.1 这类预发布号的先后)
32
+ const semver_1 = require("../karin/module/utils/semver");
31
33
  const logger_1 = require("./logger");
32
34
  Object.defineProperty(exports, "logger", { enumerable: true, get: function () { return logger_1.logger; } });
33
35
  const forward_collect_1 = require("./forward-collect");
@@ -954,11 +956,21 @@ const checkPkgUpdate = async (name, _options) => {
954
956
  if (!response.ok)
955
957
  return { status: 'error', error: new Error('HTTP ' + response.status) };
956
958
  const meta = await response.json();
957
- const remote = meta?.['dist-tags']?.latest;
959
+ const tags = meta?.['dist-tags'] ?? {};
960
+ /**
961
+ * 预览版(3.3.0-beta.1 这种)**不能拿 latest 比**:本地跑 beta 时 latest 还在 3.2.2,
962
+ * 按老逻辑会报「有新版本 3.2.2」—— 那其实是**降级**。
963
+ * 所以带 - 的本地版本改看 beta 通道;beta 没有(说明预览已经并进正式版)才回退到 latest,
964
+ * 并且只在远端确实比本地新时才说「有更新」。
965
+ */
966
+ const isPreview = local.includes('-');
967
+ const remote = (isPreview ? (tags.beta || tags.latest) : tags.latest) || '';
958
968
  if (!remote)
959
969
  return { status: 'error', error: new Error('响应缺少 dist-tags.latest') };
960
970
  if (remote === local)
961
971
  return { status: 'no', local };
972
+ if (isPreview && !(0, semver_1.isSemverGreater)(remote, local))
973
+ return { status: 'no', local };
962
974
  return { status: 'yes', local, remote };
963
975
  }
964
976
  catch (error) {
package/lib/index.js CHANGED
@@ -892,7 +892,16 @@ async function apply(ctx, rawConfig) {
892
892
  if (raw[key] !== undefined)
893
893
  legacy[key] = raw[key];
894
894
  }
895
- const groupQq = { ...(qq ?? {}) };
895
+ /**
896
+ * **每个 qq 字段都要有值**:不能指望 Koishi 的 schema 默认值一定被填上 ——
897
+ * 线上实测踩过(用户报「B站私聊还是发视频、没给在线播放链接」):
898
+ * 配置里没写过的字段,运行时读到的是 `undefined`,
899
+ * 于是「强制在线播放的适配器」(默认 bilibili)整个失效、面板列按钮的开关也失效。
900
+ *
901
+ * 所以这里用 `readQqOptions` 先铺一层**字段表里的默认值**(qqFields.json 是唯一出处),
902
+ * 再用用户显式配的值覆盖 —— 显式配置永远优先,缺的字段一律有默认。
903
+ */
904
+ const groupQq = { ...(0, qqOptions_1.readQqOptions)(raw), ...(qq ?? {}) };
896
905
  const groupNative = { ...(advanced ?? {}) };
897
906
  for (const key of qqOptions_1.QQ_KEYS)
898
907
  if (legacy[key] !== undefined)
@@ -952,7 +961,18 @@ async function apply(ctx, rawConfig) {
952
961
  const dataRoot = node_path_1.default.isAbsolute(config.dataPath) ? config.dataPath : node_path_1.default.resolve(ctx.baseDir ?? process.cwd(), config.dataPath);
953
962
  (0, runtime_1.bindRuntime)({
954
963
  ctx,
964
+ /**
965
+ * ⚠️ 这份 config **不是**原封不动转发,而是运行时真正读的那一份(`tryGetRuntime().config`)。
966
+ *
967
+ * 以前它是一个**手写白名单** —— 于是每加一个新开关(例如「强制在线播放的适配器」
968
+ * `forceOnlinePlayer`),忘了往这里补一行,运行时就读到 undefined,功能整个静默失效:
969
+ * 线上真实故障就是「B站私聊该给在线播放链接,结果还是去发视频文件」。
970
+ *
971
+ * 现在改成 `...readQqOptions(config)` 打底(qqFields.json 里的字段一个不落,缺的用默认值),
972
+ * 下面那些需要**归一化**的字段(端口取整数、分钟数夹范围、布尔用 !== false 这种)再覆盖上去。
973
+ */
955
974
  config: {
975
+ ...(0, qqOptions_1.readQqOptions)(config),
956
976
  masters: config.masters ?? [],
957
977
  debug: config.debug,
958
978
  dataPath: config.dataPath,
@@ -41,9 +41,31 @@ function parseForwardPeer(e) {
41
41
  function platformOf(e) {
42
42
  return String(e?.bot?.bot?.platform ?? e?.bot?.platform ?? '');
43
43
  }
44
- /** 这台部署的适配器支不支持合并转发 */
44
+ /**
45
+ * OneBot 系适配器(NapCat / Lagrange / go-cqhttp / Chronocat…):它们真的有「聊天记录」这个概念。
46
+ *
47
+ * 注意这份名单是**白名单**:compat 那边的 isForwardSupported 是黑名单(只排除 QQ 官方),
48
+ * 于是**任何自定义适配器都会被当成支持** —— 线上真实故障就是这样:
49
+ * B站私聊适配器(koishi-plugin-adapter-bilibili-dm,platform 就是 bilibili)被认成支持合并转发,
50
+ * 结果整条转发发出去「没有拿到消息 ID」(适配器根本没有聊天记录这种东西),
51
+ * 连退回来的逐条发送也失败了 —— 用户什么都没收到。
52
+ */
53
+ const ONEBOT_LIKE = /onebot|napcat|lagrange|go-?cqhttp|chronocat|mirai/i;
54
+ /** 这台部署的适配器支不支持合并转发(**只有明确支持才算支持**) */
45
55
  function canForwardParseResult(e) {
46
- return (0, node_karin_1.isForwardSupported)({ platform: platformOf(e) });
56
+ const raw = e?.bot?.bot ?? e?.bot;
57
+ /**
58
+ * ① 适配器自己带合并转发 API(OneBot 系的 sendGroupForwardMsg / sendPrivateForwardMsg)
59
+ * —— 这条最可靠,不依赖平台名怎么写。
60
+ */
61
+ const internal = raw?.internal ?? e?.bot?.internal;
62
+ if (internal && (typeof internal.sendGroupForwardMsg === 'function' || typeof internal.sendPrivateForwardMsg === 'function'))
63
+ return true;
64
+ /** ② 其余只认 OneBot 系的平台名 */
65
+ const platform = platformOf(e);
66
+ if (!platform)
67
+ return false;
68
+ return ONEBOT_LIKE.test(platform);
47
69
  }
48
70
  /** 平台名 → 配置段名(合并转发的平台开关就写在各自的平台段里) */
49
71
  const PLATFORM_SECTIONS = {
@@ -311,8 +333,13 @@ function withParseForward(handler, platform) {
311
333
  + '.forward 都没打开),按逐条发送处理');
312
334
  return await handler(e, next);
313
335
  }
314
- if (!parseForwardPeer(e) || !canForwardParseResult(e))
336
+ if (!parseForwardPeer(e))
315
337
  return await handler(e, next);
338
+ if (!canForwardParseResult(e)) {
339
+ // 说明白为什么没合并:以前这里静默退化,用户只看到「转发了但没收到」
340
+ node_karin_1.logger.mark('[合并转发] 适配器 ' + (platformOf(e) || '未知') + ' 不支持合并转发(不是 OneBot 系、也没有 forward API),本次逐条发送');
341
+ return await handler(e, next);
342
+ }
316
343
  return await (0, node_karin_1.runWithForwardBag)(parseForwardPeer(e), async () => {
317
344
  try {
318
345
  return await handler(e, next);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koishi-plugin-kkk",
3
- "version": "3.3.0-beta.1",
3
+ "version": "3.3.0-beta.3",
4
4
  "description": "抖音 / B站 / 快手 / 小红书 视频解析与动态推送,带 QQ 交互面板与弹幕烧录(karin-plugin-kkk 的 Koishi 移植版)",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
@@ -14,6 +14,9 @@ import path from 'node:path'
14
14
  import { h } from 'koishi'
15
15
  import type { Bot, Context, Session } from 'koishi'
16
16
 
17
+ // 版本比较复用注入器那边的实现(它处理了 -beta.1 这类预发布号的先后)
18
+ import { isSemverGreater } from '../karin/module/utils/semver'
19
+
17
20
  import { logger } from './logger'
18
21
  import { COLLECTED_MESSAGE_ID, collectForward } from './forward-collect'
19
22
  import { UnconfirmedSendError, classifySendFailure, describeSendFailure, isPassiveLimitFailure } from './sendError'
@@ -963,9 +966,18 @@ export const checkPkgUpdate = async (name: string, _options?: { compare?: string
963
966
  const response = await fetch('https://registry.npmmirror.com/' + name, { signal: AbortSignal.timeout(10000) })
964
967
  if (!response.ok) return { status: 'error' as const, error: new Error('HTTP ' + response.status) }
965
968
  const meta: any = await response.json()
966
- const remote = meta?.['dist-tags']?.latest
969
+ const tags: Record<string, string> = meta?.['dist-tags'] ?? {}
970
+ /**
971
+ * 预览版(3.3.0-beta.1 这种)**不能拿 latest 比**:本地跑 beta 时 latest 还在 3.2.2,
972
+ * 按老逻辑会报「有新版本 3.2.2」—— 那其实是**降级**。
973
+ * 所以带 - 的本地版本改看 beta 通道;beta 没有(说明预览已经并进正式版)才回退到 latest,
974
+ * 并且只在远端确实比本地新时才说「有更新」。
975
+ */
976
+ const isPreview = local.includes('-')
977
+ const remote = (isPreview ? (tags.beta || tags.latest) : tags.latest) || ''
967
978
  if (!remote) return { status: 'error' as const, error: new Error('响应缺少 dist-tags.latest') }
968
979
  if (remote === local) return { status: 'no' as const, local }
980
+ if (isPreview && !isSemverGreater(remote, local)) return { status: 'no' as const, local }
969
981
  return { status: 'yes' as const, local, remote }
970
982
  } catch (error) {
971
983
  return { status: 'error' as const, error: error as Error }
package/src/index.ts CHANGED
@@ -885,7 +885,16 @@ export async function apply (ctx: Context, rawConfig: Config) {
885
885
  for (const key of [...NATIVE_KEYS, ...QQ_KEYS]) {
886
886
  if (raw[key] !== undefined) legacy[key] = raw[key]
887
887
  }
888
- const groupQq: any = { ...(qq ?? {}) }
888
+ /**
889
+ * **每个 qq 字段都要有值**:不能指望 Koishi 的 schema 默认值一定被填上 ——
890
+ * 线上实测踩过(用户报「B站私聊还是发视频、没给在线播放链接」):
891
+ * 配置里没写过的字段,运行时读到的是 `undefined`,
892
+ * 于是「强制在线播放的适配器」(默认 bilibili)整个失效、面板列按钮的开关也失效。
893
+ *
894
+ * 所以这里用 `readQqOptions` 先铺一层**字段表里的默认值**(qqFields.json 是唯一出处),
895
+ * 再用用户显式配的值覆盖 —— 显式配置永远优先,缺的字段一律有默认。
896
+ */
897
+ const groupQq: any = { ...readQqOptions(raw as any), ...(qq ?? {}) }
889
898
  const groupNative: any = { ...(advanced ?? {}) }
890
899
  for (const key of QQ_KEYS) if (legacy[key] !== undefined) groupQq[key] = legacy[key]
891
900
  for (const key of NATIVE_KEYS) if (legacy[key] !== undefined) groupNative[key] = legacy[key]
@@ -940,7 +949,18 @@ export async function apply (ctx: Context, rawConfig: Config) {
940
949
 
941
950
  bindRuntime({
942
951
  ctx,
952
+ /**
953
+ * ⚠️ 这份 config **不是**原封不动转发,而是运行时真正读的那一份(`tryGetRuntime().config`)。
954
+ *
955
+ * 以前它是一个**手写白名单** —— 于是每加一个新开关(例如「强制在线播放的适配器」
956
+ * `forceOnlinePlayer`),忘了往这里补一行,运行时就读到 undefined,功能整个静默失效:
957
+ * 线上真实故障就是「B站私聊该给在线播放链接,结果还是去发视频文件」。
958
+ *
959
+ * 现在改成 `...readQqOptions(config)` 打底(qqFields.json 里的字段一个不落,缺的用默认值),
960
+ * 下面那些需要**归一化**的字段(端口取整数、分钟数夹范围、布尔用 !== false 这种)再覆盖上去。
961
+ */
943
962
  config: {
963
+ ...readQqOptions(config),
944
964
  masters: config.masters ?? [],
945
965
  debug: config.debug,
946
966
  dataPath: config.dataPath,
@@ -35,9 +35,30 @@ function platformOf (e: any): string {
35
35
  return String(e?.bot?.bot?.platform ?? e?.bot?.platform ?? '')
36
36
  }
37
37
 
38
- /** 这台部署的适配器支不支持合并转发 */
38
+ /**
39
+ * OneBot 系适配器(NapCat / Lagrange / go-cqhttp / Chronocat…):它们真的有「聊天记录」这个概念。
40
+ *
41
+ * 注意这份名单是**白名单**:compat 那边的 isForwardSupported 是黑名单(只排除 QQ 官方),
42
+ * 于是**任何自定义适配器都会被当成支持** —— 线上真实故障就是这样:
43
+ * B站私聊适配器(koishi-plugin-adapter-bilibili-dm,platform 就是 bilibili)被认成支持合并转发,
44
+ * 结果整条转发发出去「没有拿到消息 ID」(适配器根本没有聊天记录这种东西),
45
+ * 连退回来的逐条发送也失败了 —— 用户什么都没收到。
46
+ */
47
+ const ONEBOT_LIKE = /onebot|napcat|lagrange|go-?cqhttp|chronocat|mirai/i
48
+
49
+ /** 这台部署的适配器支不支持合并转发(**只有明确支持才算支持**) */
39
50
  export function canForwardParseResult (e: any): boolean {
40
- return isForwardSupported({ platform: platformOf(e) })
51
+ const raw: any = e?.bot?.bot ?? e?.bot
52
+ /**
53
+ * ① 适配器自己带合并转发 API(OneBot 系的 sendGroupForwardMsg / sendPrivateForwardMsg)
54
+ * —— 这条最可靠,不依赖平台名怎么写。
55
+ */
56
+ const internal: any = raw?.internal ?? e?.bot?.internal
57
+ if (internal && (typeof internal.sendGroupForwardMsg === 'function' || typeof internal.sendPrivateForwardMsg === 'function')) return true
58
+ /** ② 其余只认 OneBot 系的平台名 */
59
+ const platform = platformOf(e)
60
+ if (!platform) return false
61
+ return ONEBOT_LIKE.test(platform)
41
62
  }
42
63
 
43
64
  /** 平台名 → 配置段名(合并转发的平台开关就写在各自的平台段里) */
@@ -302,7 +323,12 @@ export function withParseForward<F extends (e: any, next?: any) => any> (handler
302
323
  + '.forward 都没打开),按逐条发送处理')
303
324
  return await handler(e, next)
304
325
  }
305
- if (!parseForwardPeer(e) || !canForwardParseResult(e)) return await handler(e, next)
326
+ if (!parseForwardPeer(e)) return await handler(e, next)
327
+ if (!canForwardParseResult(e)) {
328
+ // 说明白为什么没合并:以前这里静默退化,用户只看到「转发了但没收到」
329
+ logger.mark('[合并转发] 适配器 ' + (platformOf(e) || '未知') + ' 不支持合并转发(不是 OneBot 系、也没有 forward API),本次逐条发送')
330
+ return await handler(e, next)
331
+ }
306
332
  return await runWithForwardBag(parseForwardPeer(e), async () => {
307
333
  try {
308
334
  return await handler(e, next)