koishi-plugin-cat-raising 1.0.0 → 1.1.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.
- package/lib/index.d.ts +8 -1
- package/lib/index.js +85 -58
- package/package.json +1 -1
package/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { Context, Schema } from 'koishi';
|
|
2
2
|
export declare const name = "cat-raising";
|
|
3
|
+
/** B站 access_key 配置项 */
|
|
4
|
+
export interface BiliAccessKeyConfig {
|
|
5
|
+
/** Bilibili access_key */
|
|
6
|
+
key: string;
|
|
7
|
+
/** 对此 access_key 的备注,例如所属账号 */
|
|
8
|
+
remark?: string;
|
|
9
|
+
}
|
|
3
10
|
/** 监听群组的配置 */
|
|
4
11
|
export interface MonitorGroupConfig {
|
|
5
12
|
/** 要监听的 QQ 群号 */
|
|
@@ -18,7 +25,7 @@ export interface Config {
|
|
|
18
25
|
/** 用于防复读的历史记录大小 */
|
|
19
26
|
historySize: number;
|
|
20
27
|
/** 用于发送B站弹幕的 access_key 列表 */
|
|
21
|
-
biliAccessKeys:
|
|
28
|
+
biliAccessKeys: BiliAccessKeyConfig[];
|
|
22
29
|
}
|
|
23
30
|
export declare const Config: Schema<Config>;
|
|
24
31
|
/**
|
package/lib/index.js
CHANGED
|
@@ -47,7 +47,10 @@ var Config = import_koishi.Schema.object({
|
|
|
47
47
|
sendHelperMessages: import_koishi.Schema.boolean().description("是否在此群内发送“看到啦”之类的辅助/警告消息").default(true)
|
|
48
48
|
})).description("监听的群组列表及其配置").required(),
|
|
49
49
|
historySize: import_koishi.Schema.number().description("用于防复读的历史记录大小,防止短期内对同一活动重复转发").default(30).min(5).max(100),
|
|
50
|
-
biliAccessKeys: import_koishi.Schema.array(import_koishi.Schema.
|
|
50
|
+
biliAccessKeys: import_koishi.Schema.array(import_koishi.Schema.object({
|
|
51
|
+
key: import_koishi.Schema.string().description("Bilibili access_key").required(),
|
|
52
|
+
remark: import_koishi.Schema.string().description("对此 access_key 的备注,例如所属账号")
|
|
53
|
+
})).description("用于发送B站弹幕的 access_key 列表。插件会为列表中的每个 key 发送弹幕。如果留空,则不执行发送弹幕功能。").default([])
|
|
51
54
|
});
|
|
52
55
|
function preprocessChineseNumerals(text) {
|
|
53
56
|
const numMap = { "零": 0, "一": 1, "二": 2, "两": 2, "三": 3, "四": 4, "五": 5, "六": 6, "七": 7, "八": 8, "九": 9 };
|
|
@@ -146,9 +149,10 @@ function parseEventFromText(text) {
|
|
|
146
149
|
return allRewards.length > 0 ? { dateTime: globalDateTime || "时间未知", rewards: allRewards } : null;
|
|
147
150
|
}
|
|
148
151
|
__name(parseEventFromText, "parseEventFromText");
|
|
149
|
-
var HARD_REJECTION_KEYWORDS = ["发言榜单"];
|
|
152
|
+
var HARD_REJECTION_KEYWORDS = ["发言榜单", "投稿数:"];
|
|
150
153
|
var REJECTION_KEYWORDS = ["签到", "打卡"];
|
|
151
154
|
var OVERRIDE_KEYWORDS = ["神金", "发"];
|
|
155
|
+
var CHECK_IN_REJECTION_REGEX = /\b\d{2,3}\s*\+/;
|
|
152
156
|
var TRIGGER_REGEX = /神金|发|掉落|猫猫钻|w|\b\d{3,5}\b|一千|一百|十|九|八|七|六|五|四|三|两|二|一/i;
|
|
153
157
|
var BILI_APPKEY = "4409e2ce8ffd12b8";
|
|
154
158
|
var BILI_APPSECRET = "59b43e04ad6965f34319062b478f83dd";
|
|
@@ -159,48 +163,64 @@ function signBilibiliParams(params, appSecret) {
|
|
|
159
163
|
return sign;
|
|
160
164
|
}
|
|
161
165
|
__name(signBilibiliParams, "signBilibiliParams");
|
|
162
|
-
async function sendBilibiliDanmaku(ctx,
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
const accessKey = config.biliAccessKeys[Math.floor(Math.random() * config.biliAccessKeys.length)];
|
|
166
|
+
async function sendBilibiliDanmaku(ctx, keyConfig, roomId, message) {
|
|
167
|
+
const MAX_RETRIES = 4;
|
|
168
|
+
const RETRY_DELAY_MS = 3e3;
|
|
169
|
+
const FREQUENCY_LIMIT_KEYWORD = "频率过快";
|
|
168
170
|
const url = "https://api.live.bilibili.com/xlive/app-room/v1/dM/sendmsg";
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
171
|
+
const logIdentifier = keyConfig.remark || keyConfig.key.slice(0, 8);
|
|
172
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
173
|
+
if (attempt > 0) {
|
|
174
|
+
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
|
|
175
|
+
}
|
|
176
|
+
const ts = Math.floor(Date.now() / 1e3);
|
|
177
|
+
const baseParams = {
|
|
178
|
+
access_key: keyConfig.key,
|
|
179
|
+
actionKey: "appkey",
|
|
180
|
+
appkey: BILI_APPKEY,
|
|
181
|
+
cid: roomId,
|
|
182
|
+
msg: message,
|
|
183
|
+
rnd: ts,
|
|
184
|
+
color: "16777215",
|
|
185
|
+
// 白色
|
|
186
|
+
fontsize: "25",
|
|
187
|
+
mode: "1",
|
|
188
|
+
// 滚动弹幕
|
|
189
|
+
ts
|
|
190
|
+
};
|
|
191
|
+
const sign = signBilibiliParams(baseParams, BILI_APPSECRET);
|
|
192
|
+
const params = { ...baseParams, sign };
|
|
193
|
+
const formData = new import_url.URLSearchParams();
|
|
194
|
+
for (const key in params) {
|
|
195
|
+
formData.append(key, params[key]);
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
const response = await ctx.http.post(url, formData, {
|
|
199
|
+
headers: {
|
|
200
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
201
|
+
"User-Agent": "Mozilla/5.0 BiliDroid/6.73.1 (bbcallen@gmail.com) os/android model/Mi 10 Pro mobi_app/android build/6731100 channel/xiaomi innerVer/6731110 osVer/12 network/2"
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
if (response.code === 0) {
|
|
205
|
+
const successMessage = attempt > 0 ? `[弹幕] [${logIdentifier}] 成功向直播间 ${roomId} 发送弹幕 (重试 ${attempt} 次后)` : `[弹幕] [${logIdentifier}] 成功向直播间 ${roomId} 发送弹幕: "${message}"`;
|
|
206
|
+
ctx.logger.info(successMessage);
|
|
207
|
+
return;
|
|
195
208
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
209
|
+
if (response.message?.includes(FREQUENCY_LIMIT_KEYWORD)) {
|
|
210
|
+
if (attempt < MAX_RETRIES) {
|
|
211
|
+
ctx.logger.warn(`[弹幕] [${logIdentifier}] 发送频率过快 (尝试 ${attempt + 1}/${MAX_RETRIES + 1})。准备重试...`);
|
|
212
|
+
continue;
|
|
213
|
+
} else {
|
|
214
|
+
ctx.logger.warn(`[弹幕] [${logIdentifier}] 发送频率过快,已达最大重试次数 (${MAX_RETRIES}),放弃发送。`);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
ctx.logger.warn(`[弹幕] [${logIdentifier}] 发送失败,直播间 ${roomId}。原因: ${response.message || "未知错误"}`);
|
|
219
|
+
return;
|
|
220
|
+
} catch (error) {
|
|
221
|
+
ctx.logger.error(`[弹幕] [${logIdentifier}] 发送请求时发生网络错误 (尝试 ${attempt + 1}),直播间 ${roomId}:`, error);
|
|
222
|
+
return;
|
|
201
223
|
}
|
|
202
|
-
} catch (error) {
|
|
203
|
-
ctx.logger.error(`[弹幕] 发送请求时发生网络错误,直播间 ${roomId}:`, error);
|
|
204
224
|
}
|
|
205
225
|
}
|
|
206
226
|
__name(sendBilibiliDanmaku, "sendBilibiliDanmaku");
|
|
@@ -228,6 +248,10 @@ function apply(ctx, config) {
|
|
|
228
248
|
const strippedContent = session.stripped.content;
|
|
229
249
|
if (!strippedContent.trim()) return;
|
|
230
250
|
if (HARD_REJECTION_KEYWORDS.some((keyword) => strippedContent.includes(keyword))) return;
|
|
251
|
+
if (CHECK_IN_REJECTION_REGEX.test(strippedContent)) {
|
|
252
|
+
ctx.logger.info(`[忽略] 消息包含签到模式 (如 110+),判定为非奖励信息。内容: "${strippedContent.replace(/\n/g, " ")}"`);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
231
255
|
if (!TRIGGER_REGEX.test(strippedContent)) return;
|
|
232
256
|
const roomIds = extractAllRoomIds(session.content);
|
|
233
257
|
if (roomIds.length !== 1) {
|
|
@@ -243,25 +267,21 @@ function apply(ctx, config) {
|
|
|
243
267
|
const hasStrongContext = /神金|发|w/i.test(preprocessedMessage);
|
|
244
268
|
const hasTime = parsedEvent.dateTime !== "时间未知";
|
|
245
269
|
if (!hasStrongContext && !hasTime) return;
|
|
246
|
-
const { dateTime } = parsedEvent;
|
|
247
|
-
if (forwardedHistory.some((entry) => entry.roomId === roomId && entry.dateTime === dateTime)) {
|
|
248
|
-
ctx.logger.info(`[防复读] 检测到重复活动: 房间=${roomId}, 时间=${dateTime}`);
|
|
249
|
-
if (groupConfig.sendHelperMessages) {
|
|
250
|
-
try {
|
|
251
|
-
const [warningId] = await session.send(`看到啦看到啦,不要发那么多次嘛~`);
|
|
252
|
-
if (warningId) warningMessageMap.set(session.messageId, warningId);
|
|
253
|
-
} catch (e) {
|
|
254
|
-
ctx.logger.warn("[消息] 发送重复警告失败:", e);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
270
|
const biliInfo = await fetchBilibiliInfo(ctx, roomId);
|
|
260
271
|
if (!biliInfo) return;
|
|
261
272
|
let helperMessageId;
|
|
262
273
|
if (groupConfig.sendHelperMessages) {
|
|
263
|
-
|
|
274
|
+
try {
|
|
275
|
+
[helperMessageId] = await session.send(`直播间: ${roomId}
|
|
264
276
|
投稿数: ${biliInfo.videoCount}`);
|
|
277
|
+
} catch (e) {
|
|
278
|
+
ctx.logger.warn("[消息] 发送辅助信息失败:", e);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
const { dateTime } = parsedEvent;
|
|
282
|
+
if (forwardedHistory.some((entry) => entry.roomId === roomId && entry.dateTime === dateTime)) {
|
|
283
|
+
ctx.logger.info(`[防复读] 检测到重复活动,已发送辅助信息,跳过转发: 房间=${roomId}, 时间=${dateTime}`);
|
|
284
|
+
return;
|
|
265
285
|
}
|
|
266
286
|
try {
|
|
267
287
|
const forwardMessage = `${session.content}
|
|
@@ -273,12 +293,18 @@ function apply(ctx, config) {
|
|
|
273
293
|
originalMessageId: session.messageId,
|
|
274
294
|
forwardedMessageId,
|
|
275
295
|
helperMessageId,
|
|
276
|
-
//
|
|
296
|
+
// 存储辅助消息ID用于撤回联动
|
|
277
297
|
roomId,
|
|
278
298
|
dateTime
|
|
279
299
|
});
|
|
280
300
|
if (forwardedHistory.length > config.historySize) forwardedHistory.shift();
|
|
281
|
-
|
|
301
|
+
if (config.biliAccessKeys && config.biliAccessKeys.length > 0) {
|
|
302
|
+
ctx.logger.info(`[弹幕] 准备为 ${config.biliAccessKeys.length} 个账号发送弹幕到直播间 ${roomId}...`);
|
|
303
|
+
const danmakuPromises = config.biliAccessKeys.map(
|
|
304
|
+
(keyConfig) => sendBilibiliDanmaku(ctx, keyConfig, roomId, "喵喵喵")
|
|
305
|
+
);
|
|
306
|
+
Promise.allSettled(danmakuPromises);
|
|
307
|
+
}
|
|
282
308
|
} catch (error) {
|
|
283
309
|
session.send("🐱 - 转发失败,请检查目标QQ/群号配置是否正确");
|
|
284
310
|
ctx.logger.error("[转发] 失败:", error);
|
|
@@ -299,7 +325,8 @@ function apply(ctx, config) {
|
|
|
299
325
|
}
|
|
300
326
|
}
|
|
301
327
|
try {
|
|
302
|
-
|
|
328
|
+
const targetChannel = config.isGroup ? config.targetQQ : `private:${config.targetQQ}`;
|
|
329
|
+
await session.bot.deleteMessage(targetChannel, entry.forwardedMessageId);
|
|
303
330
|
} catch (e) {
|
|
304
331
|
ctx.logger.warn(`[撤回] 转发消息 (ID: ${entry.forwardedMessageId}) 失败:`, e);
|
|
305
332
|
} finally {
|