koishi-plugin-cat-raising 1.3.8 → 1.3.10

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/lib/index.d.ts CHANGED
@@ -28,6 +28,8 @@ export interface Config {
28
28
  biliAccessKeys: BiliAccessKeyConfig[];
29
29
  /** 硬拒绝关键词列表 */
30
30
  hardRejectionKeywords: string[];
31
+ /** 仅弹幕模式关键词(包含任一关键词则只发弹幕不转发) */
32
+ danmakuOnlyKeywords: string[];
31
33
  /** 发送弹幕的延迟下限(秒) */
32
34
  danmakuDelayMinSeconds?: number;
33
35
  /** 发送弹幕的延迟上限(秒) */
package/lib/index.js CHANGED
@@ -37,6 +37,7 @@ __export(src_exports, {
37
37
  module.exports = __toCommonJS(src_exports);
38
38
  var import_koishi = require("koishi");
39
39
  var crypto = __toESM(require("crypto"));
40
+ var net = __toESM(require("net"));
40
41
  var import_url = require("url");
41
42
  var name = "cat-raising";
42
43
  var Config = import_koishi.Schema.intersect([
@@ -60,7 +61,8 @@ var Config = import_koishi.Schema.intersect([
60
61
  danmakuDelayMaxSeconds: import_koishi.Schema.number().description("弹幕延迟上限(秒)").role("slider", { min: 0, max: 60, step: 1 }).default(10)
61
62
  }).description("弹幕设置"),
62
63
  import_koishi.Schema.object({
63
- hardRejectionKeywords: import_koishi.Schema.array(import_koishi.Schema.string()).description("命中则忽略的关键词(硬拒绝)").default(["发言榜单", "投稿数:", "预告"])
64
+ hardRejectionKeywords: import_koishi.Schema.array(import_koishi.Schema.string()).description("命中则忽略的关键词(硬拒绝)").default(["发言榜单", "投稿数:", "预告"]),
65
+ danmakuOnlyKeywords: import_koishi.Schema.array(import_koishi.Schema.string()).description("仅弹幕模式关键词(包含任一关键词则只发弹幕不转发)").default(["闪击"])
64
66
  }).description("过滤规则")
65
67
  ]);
66
68
  function preprocessChineseNumerals(text) {
@@ -317,6 +319,19 @@ function apply(ctx, config) {
317
319
  const warningMessageMap = /* @__PURE__ */ new Map();
318
320
  const pendingDanmakuTimersByMessage = /* @__PURE__ */ new Map();
319
321
  const pendingDanmakuTimersByRoom = /* @__PURE__ */ new Map();
322
+ const pendingDanmakuRoomMap = /* @__PURE__ */ new Map();
323
+ ctx.command("cat", "🐱喵喵喵");
324
+ ctx.command("cat.status", "查看插件状态").action(async () => {
325
+ let latencyMsg = "";
326
+ try {
327
+ const ms = await tcpPing("api.live.bilibili.com", 80);
328
+ latencyMsg = `B站API延迟: ${ms}ms`;
329
+ } catch (e) {
330
+ latencyMsg = `B站API延迟: 失败 (${e.message || "Unknown"})`;
331
+ }
332
+ return `🐱喵喵喵
333
+ ${latencyMsg}`;
334
+ });
320
335
  ctx.on("message", async (session) => {
321
336
  const groupConfig = config.monitorGroups.find((g) => g.groupId === session.channelId);
322
337
  if (!groupConfig) return;
@@ -344,8 +359,10 @@ function apply(ctx, config) {
344
359
  if (!hasStrongContext && !hasTime) return;
345
360
  const biliInfo = await fetchBilibiliInfo(ctx, roomId);
346
361
  if (!biliInfo) return;
362
+ const danmakuOnlyKeywords = config.danmakuOnlyKeywords || ["闪击"];
363
+ const isDanmakuOnly = danmakuOnlyKeywords.some((keyword) => preprocessedMessage.includes(keyword));
347
364
  let helperMessageId;
348
- if (groupConfig.sendHelperMessages) {
365
+ if (!isDanmakuOnly && groupConfig.sendHelperMessages) {
349
366
  try {
350
367
  const displayAnchor = biliInfo.anchorName;
351
368
  [helperMessageId] = await session.send(`直播间: ${roomId}
@@ -356,66 +373,87 @@ function apply(ctx, config) {
356
373
  }
357
374
  }
358
375
  const { dateTime } = parsedEvent;
359
- if (forwardedHistory.some((entry) => entry.roomId === roomId && entry.dateTime === dateTime)) {
376
+ if (!isDanmakuOnly && forwardedHistory.some((entry) => entry.roomId === roomId && entry.dateTime === dateTime)) {
360
377
  ctx.logger.info(`[防复读] 检测到重复活动,已发送辅助信息,跳过转发: 房间=${roomId}, 时间=${dateTime}`);
361
378
  return;
362
379
  }
363
- try {
364
- const displayAnchor = biliInfo.anchorName;
365
- const forwardMessage = `${session.content}
380
+ if (!isDanmakuOnly) {
381
+ try {
382
+ const displayAnchor = biliInfo.anchorName;
383
+ const forwardMessage = `${session.content}
366
384
 
367
385
  ---
368
386
  主播: ${displayAnchor}
369
387
  投稿数: ${biliInfo.videoCount}`;
370
- const [forwardedMessageId] = config.isGroup ? await session.bot.sendMessage(config.targetQQ, forwardMessage) : await session.bot.sendPrivateMessage(config.targetQQ, forwardMessage);
371
- forwardedHistory.push({
372
- originalMessageId: session.messageId,
373
- forwardedMessageId,
374
- helperMessageId,
375
- // 存储辅助消息ID用于撤回联动
376
- roomId,
377
- dateTime
378
- });
379
- if (forwardedHistory.length > config.historySize) forwardedHistory.shift();
380
- if (config.biliAccessKeys && config.biliAccessKeys.length > 0) {
381
- const min = Math.max(0, config.danmakuDelayMinSeconds ?? 8);
382
- const max = Math.max(min, config.danmakuDelayMaxSeconds ?? 10);
383
- const delayMs = (Math.floor(Math.random() * (max - min + 1)) + min) * 1e3;
384
- ctx.logger.info(`[弹幕] ${config.biliAccessKeys.length} 个账号将于 ${Math.round(delayMs / 1e3)} 秒后发送弹幕到直播间 ${roomId}。`);
385
- const timer = setTimeout(() => {
386
- const list1 = pendingDanmakuTimersByMessage.get(session.messageId);
387
- if (list1) pendingDanmakuTimersByMessage.set(session.messageId, list1.filter((t) => t !== timer));
388
- const list2 = pendingDanmakuTimersByRoom.get(roomId);
389
- if (list2) pendingDanmakuTimersByRoom.set(roomId, list2.filter((t) => t !== timer));
390
- const danmakuPromises = config.biliAccessKeys.map(
391
- (keyConfig) => sendBilibiliDanmaku(ctx, keyConfig, roomId, "喵喵喵")
392
- );
393
- Promise.allSettled(danmakuPromises);
394
- }, delayMs);
395
- const byMsg = pendingDanmakuTimersByMessage.get(session.messageId) || [];
396
- byMsg.push(timer);
397
- pendingDanmakuTimersByMessage.set(session.messageId, byMsg);
398
- const byRoom = pendingDanmakuTimersByRoom.get(roomId) || [];
399
- byRoom.push(timer);
400
- pendingDanmakuTimersByRoom.set(roomId, byRoom);
388
+ const [forwardedMessageId] = config.isGroup ? await session.bot.sendMessage(config.targetQQ, forwardMessage) : await session.bot.sendPrivateMessage(config.targetQQ, forwardMessage);
389
+ forwardedHistory.push({
390
+ originalMessageId: session.messageId,
391
+ forwardedMessageId,
392
+ helperMessageId,
393
+ // 存储辅助消息ID用于撤回联动
394
+ roomId,
395
+ dateTime
396
+ });
397
+ if (forwardedHistory.length > config.historySize) forwardedHistory.shift();
398
+ } catch (error) {
399
+ session.send("🐱 - 转发失败,请检查目标QQ/群号配置是否正确");
400
+ ctx.logger.error("[转发] 失败:", error);
401
401
  }
402
- } catch (error) {
403
- session.send("🐱 - 转发失败,请检查目标QQ/群号配置是否正确");
404
- ctx.logger.error("[转发] 失败:", error);
402
+ }
403
+ if (config.biliAccessKeys && config.biliAccessKeys.length > 0) {
404
+ const min = Math.max(0, config.danmakuDelayMinSeconds ?? 8);
405
+ const max = Math.max(min, config.danmakuDelayMaxSeconds ?? 10);
406
+ const delayMs = (Math.floor(Math.random() * (max - min + 1)) + min) * 1e3;
407
+ ctx.logger.info(`[弹幕] ${config.biliAccessKeys.length} 个账号将于 ${Math.round(delayMs / 1e3)} 秒后发送弹幕到直播间 ${roomId}。`);
408
+ const timer = setTimeout(() => {
409
+ const list1 = pendingDanmakuTimersByMessage.get(session.messageId);
410
+ if (list1) {
411
+ const newList = list1.filter((t) => t !== timer);
412
+ if (newList.length === 0) {
413
+ pendingDanmakuTimersByMessage.delete(session.messageId);
414
+ pendingDanmakuRoomMap.delete(session.messageId);
415
+ } else {
416
+ pendingDanmakuTimersByMessage.set(session.messageId, newList);
417
+ }
418
+ }
419
+ const list2 = pendingDanmakuTimersByRoom.get(roomId);
420
+ if (list2) {
421
+ const newList = list2.filter((t) => t !== timer);
422
+ if (newList.length === 0) {
423
+ pendingDanmakuTimersByRoom.delete(roomId);
424
+ } else {
425
+ pendingDanmakuTimersByRoom.set(roomId, newList);
426
+ }
427
+ }
428
+ const danmakuPromises = config.biliAccessKeys.map(
429
+ (keyConfig) => sendBilibiliDanmaku(ctx, keyConfig, roomId, "喵喵喵")
430
+ );
431
+ Promise.allSettled(danmakuPromises);
432
+ }, delayMs);
433
+ const byMsg = pendingDanmakuTimersByMessage.get(session.messageId) || [];
434
+ byMsg.push(timer);
435
+ pendingDanmakuTimersByMessage.set(session.messageId, byMsg);
436
+ pendingDanmakuRoomMap.set(session.messageId, roomId);
437
+ const byRoom = pendingDanmakuTimersByRoom.get(roomId) || [];
438
+ byRoom.push(timer);
439
+ pendingDanmakuTimersByRoom.set(roomId, byRoom);
405
440
  }
406
441
  });
407
442
  ctx.on("message-deleted", async (session) => {
408
443
  const isMonitored = config.monitorGroups.some((g) => g.groupId === session.channelId);
409
444
  if (!isMonitored) return;
410
445
  const originalMessageId = session.messageId;
446
+ const timersByMsg = pendingDanmakuTimersByMessage.get(originalMessageId);
447
+ if (timersByMsg) {
448
+ for (const t of timersByMsg) clearTimeout(t);
449
+ pendingDanmakuTimersByMessage.delete(originalMessageId);
450
+ const roomId = pendingDanmakuRoomMap.get(originalMessageId);
451
+ pendingDanmakuRoomMap.delete(originalMessageId);
452
+ ctx.logger.info(`[撤回] 已取消源消息 ${originalMessageId}${roomId ? ` (直播间: ${roomId})` : ""} 的延迟弹幕。`);
453
+ }
411
454
  const entryIndex = forwardedHistory.findIndex((entry) => entry.originalMessageId === originalMessageId);
412
455
  if (entryIndex !== -1) {
413
456
  const entry = forwardedHistory[entryIndex];
414
- const timersByMsg = pendingDanmakuTimersByMessage.get(originalMessageId);
415
- if (timersByMsg) {
416
- for (const t of timersByMsg) clearTimeout(t);
417
- pendingDanmakuTimersByMessage.delete(originalMessageId);
418
- }
419
457
  const timersByRoom = pendingDanmakuTimersByRoom.get(entry.roomId);
420
458
  if (timersByRoom) {
421
459
  for (const t of timersByRoom) clearTimeout(t);
@@ -435,7 +473,7 @@ function apply(ctx, config) {
435
473
  ctx.logger.warn(`[撤回] 转发消息 (ID: ${entry.forwardedMessageId}) 失败:`, e);
436
474
  } finally {
437
475
  forwardedHistory.splice(entryIndex, 1);
438
- ctx.logger.info(`[撤回] 已联动撤回与源消息 ${originalMessageId} 相关的转发。`);
476
+ ctx.logger.info(`[撤回] 已联动撤回与源消息 ${originalMessageId} (直播间: ${entry.roomId}) 相关的转发。`);
439
477
  }
440
478
  }
441
479
  if (warningMessageMap.has(originalMessageId)) {
@@ -456,6 +494,26 @@ function sleep(ms) {
456
494
  return new Promise((resolve) => setTimeout(resolve, ms));
457
495
  }
458
496
  __name(sleep, "sleep");
497
+ function tcpPing(host, port = 80, timeout = 2e3) {
498
+ return new Promise((resolve, reject) => {
499
+ const start = Date.now();
500
+ const socket = new net.Socket();
501
+ socket.setTimeout(timeout, () => {
502
+ socket.destroy();
503
+ reject(new Error("Timeout"));
504
+ });
505
+ socket.connect(port, host, () => {
506
+ const ms = Date.now() - start;
507
+ socket.destroy();
508
+ resolve(ms);
509
+ });
510
+ socket.on("error", (err) => {
511
+ socket.destroy();
512
+ reject(err);
513
+ });
514
+ });
515
+ }
516
+ __name(tcpPing, "tcpPing");
459
517
  // Annotate the CommonJS export names for ESM import in node:
460
518
  0 && (module.exports = {
461
519
  Config,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-cat-raising",
3
3
  "description": "",
4
- "version": "1.3.8",
4
+ "version": "1.3.10",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [