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 +2 -0
- package/lib/index.js +104 -46
- package/package.json +1 -1
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
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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
|
-
}
|
|
403
|
-
|
|
404
|
-
|
|
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,
|