koishi-plugin-bilirice 0.0.4 → 0.0.5

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
@@ -9,12 +9,14 @@ interface BiliriceConfig {
9
9
  };
10
10
  timeout?: number;
11
11
  bilibiliCookie?: string;
12
+ debugMode?: boolean;
12
13
  }
13
14
  export declare const Config: Schema<Schemastery.ObjectS<{
14
15
  anchors: Schema<[string?, string?, ...any[]][], [string?, string?, ...any[]][]>;
15
16
  pollInterval: Schema<number, number>;
16
17
  timeout: Schema<number, number>;
17
18
  bilibiliCookie: Schema<string, string>;
19
+ debugMode: Schema<boolean, boolean>;
18
20
  templates: Schema<Schemastery.ObjectS<{
19
21
  liveStart: Schema<string, string>;
20
22
  liveEnd: Schema<string, string>;
@@ -27,6 +29,7 @@ export declare const Config: Schema<Schemastery.ObjectS<{
27
29
  pollInterval: Schema<number, number>;
28
30
  timeout: Schema<number, number>;
29
31
  bilibiliCookie: Schema<string, string>;
32
+ debugMode: Schema<boolean, boolean>;
30
33
  templates: Schema<Schemastery.ObjectS<{
31
34
  liveStart: Schema<string, string>;
32
35
  liveEnd: Schema<string, string>;
package/lib/index.js CHANGED
@@ -45,20 +45,18 @@ var Config = import_koishi.Schema.object({
45
45
  import_koishi.Schema.string().description("通知群号(OneBot格式加group_前缀,如group_123456789)")
46
46
  ])).required().description("监听的主播列表 → 点击「添加项」可配置多个主播").role("table"),
47
47
  pollInterval: import_koishi.Schema.number().default(10).description("轮询间隔(秒),建议≥5秒(避免触发B站API风控)").role("slider", { min: 5, max: 30, step: 1 }),
48
- // 控制台显示滑块,更易用
49
48
  timeout: import_koishi.Schema.number().default(1e4).description("API请求超时时间(毫秒),建议5000-15000").role("number", { min: 5e3, max: 3e4 }),
50
- // 核心:Cookie字段适配控制台(密码类型,输入隐藏 + 详细说明)
51
49
  bilibiliCookie: import_koishi.Schema.string().description(`【必填】B站登录Cookie(解决-352错误)
52
50
  获取方式:
53
51
  1. 登录B站直播页 https://live.bilibili.com/
54
52
  2. F12 → 网络 → 搜索 getInfoByRoom
55
53
  3. 复制请求头中的「Cookie」完整值
56
54
  (包含SESSDATA/bili_jct/DedeUserID等核心字段)`).required().role("password"),
57
- // 控制台显示为密码输入框,输入内容隐藏
58
- // 通知模板:控制台支持多行文本编辑
55
+ // 新增:调试模式开关(控制台显示为开关按钮)
56
+ debugMode: import_koishi.Schema.boolean().default(false).description("调试模式:开启后打印API请求/响应、状态变化等详细日志,方便排查问题").role("switch"),
57
+ // 控制台显示为开关,一键开启/关闭
59
58
  templates: import_koishi.Schema.object({
60
59
  liveStart: import_koishi.Schema.string().default("【{uname} 开播啦】\n标题:{title}\n分区:{area}\n链接:{url}\n开播时间:{startTime}\n封面:{cover}").description("开播通知模板,支持占位符:{uname}/{title}/{area}/{url}/{startTime}/{cover}").role("textarea"),
61
- // 控制台显示多行文本框
62
60
  liveEnd: import_koishi.Schema.string().default("【{uname} 下播啦】\n直播时长:{liveTime}\n最高在线:{peakOnline}\n观看人数:{watchCount}\n弹幕数:{dmCount}\n人均弹幕:{avgDmPerUser}\n下播时间:{endTime}").description("下播通知模板,额外支持:{liveTime}/{peakOnline}/{watchCount}/{dmCount}/{avgDmPerUser}/{endTime}").role("textarea")
63
61
  }).description("自定义通知模板(保留占位符即可自动替换数据)")
64
62
  });
@@ -71,9 +69,20 @@ function apply(ctx, config) {
71
69
  liveEnd: config.templates?.liveEnd || "【{uname} 下播啦】\n直播时长:{liveTime}\n最高在线:{peakOnline}\n观看人数:{watchCount}\n弹幕数:{dmCount}\n人均弹幕:{avgDmPerUser}\n下播时间:{endTime}"
72
70
  },
73
71
  anchors: config.anchors || [],
74
- bilibiliCookie: config.bilibiliCookie || ""
72
+ bilibiliCookie: config.bilibiliCookie || "",
73
+ debugMode: config.debugMode || false
74
+ // 读取调试模式配置
75
75
  };
76
76
  const anchorStateCache = /* @__PURE__ */ new Map();
77
+ const debugLog = /* @__PURE__ */ __name((message, data) => {
78
+ if (finalConfig.debugMode) {
79
+ if (data) {
80
+ ctx.logger.debug(`[bilirice调试] ${message}:`, data);
81
+ } else {
82
+ ctx.logger.debug(`[bilirice调试] ${message}`);
83
+ }
84
+ }
85
+ }, "debugLog");
77
86
  const api = import_axios.default.create({
78
87
  baseURL: "https://api.live.bilibili.com",
79
88
  timeout: finalConfig.timeout,
@@ -90,40 +99,64 @@ function apply(ctx, config) {
90
99
  "Sec-Fetch-Mode": "cors",
91
100
  "Sec-Fetch-Site": "same-site",
92
101
  "Cookie": finalConfig.bilibiliCookie
93
- // 读取控制台配置的Cookie
94
102
  },
95
103
  transformResponse: [(data) => data]
96
104
  });
105
+ api.interceptors.request.use((config2) => {
106
+ debugLog(`API请求:${config2.method?.toUpperCase()} ${config2.url}`, {
107
+ params: config2.params,
108
+ headers: {
109
+ // 隐藏Cookie敏感信息,只打印关键头
110
+ "User-Agent": config2.headers["User-Agent"],
111
+ "Referer": config2.headers["Referer"],
112
+ "Cookie": "*** 已隐藏(调试模式可手动查看) ***"
113
+ }
114
+ });
115
+ return config2;
116
+ });
97
117
  api.interceptors.response.use(
98
118
  (response) => {
99
119
  try {
100
120
  response.data = JSON.parse(response.data);
121
+ debugLog(`API响应:${response.config.url}`, response.data);
101
122
  return response;
102
123
  } catch (err) {
124
+ debugLog(`API响应解析失败`, err);
103
125
  throw new Error(`API响应解析失败: ${err.message}`);
104
126
  }
105
127
  },
106
128
  (error) => {
129
+ debugLog(`API请求失败`, {
130
+ url: error.config?.url,
131
+ code: error.code,
132
+ response: error.response?.data
133
+ });
107
134
  throw new Error(`API请求失败: ${error.message || error.code}`);
108
135
  }
109
136
  );
110
137
  async function getRoomIdByMid(mid) {
138
+ debugLog(`开始获取主播${mid}的直播间ID`);
111
139
  const res = await api.get("/room/v1/Room/getRoomInfoOld", {
112
140
  params: { mid: Number(mid) }
113
141
  });
114
142
  if (res.data.code !== 0) throw new Error(`获取直播间ID失败: ${res.data.code} ${res.data.message || ""}`);
115
- return res.data.data.roomid.toString();
143
+ const roomId = res.data.data.roomid.toString();
144
+ debugLog(`主播${mid}的直播间ID:${roomId}`);
145
+ return roomId;
116
146
  }
117
147
  __name(getRoomIdByMid, "getRoomIdByMid");
118
148
  async function getRoomBaseInfo(roomId) {
149
+ debugLog(`开始获取直播间${roomId}基础信息`);
119
150
  const res = await api.get("/room/v1/Room/get_info", {
120
151
  params: { room_id: Number(roomId) }
121
152
  });
122
153
  if (res.data.code !== 0) throw new Error(`获取直播间基础信息失败: ${res.data.code} ${res.data.message || ""}`);
154
+ debugLog(`直播间${roomId}基础信息`, res.data.data);
123
155
  return res.data.data;
124
156
  }
125
157
  __name(getRoomBaseInfo, "getRoomBaseInfo");
126
158
  async function getRoomStat(roomId) {
159
+ debugLog(`开始获取直播间${roomId}统计数据`);
127
160
  const res = await api.get("/xlive/web-room/v1/index/getInfoByRoom", {
128
161
  params: {
129
162
  room_id: Number(roomId),
@@ -135,7 +168,7 @@ function apply(ctx, config) {
135
168
  if (res.data.code !== 0) throw new Error(`获取直播间统计失败: ${res.data.code} ${res.data.message || ""}`);
136
169
  const data = res.data.data;
137
170
  if (!data || !data.room_info) throw new Error("获取直播间统计失败: 返回数据结构异常");
138
- return {
171
+ const stat = {
139
172
  online: data.room_info.online || 0,
140
173
  watch_num: data.room_info.watch_num || 0,
141
174
  interact_num: data.room_info.interact_num || 0,
@@ -144,18 +177,22 @@ function apply(ctx, config) {
144
177
  fans_count: data.anchor_info?.base_info?.fans_count || 0,
145
178
  live_time: data.room_info.live_time || 0
146
179
  };
180
+ debugLog(`直播间${roomId}统计数据`, stat);
181
+ return stat;
147
182
  }
148
183
  __name(getRoomStat, "getRoomStat");
149
184
  async function getAnchorInfo(roomId) {
185
+ debugLog(`开始获取直播间${roomId}主播信息`);
150
186
  const res = await api.get("/live_user/v1/UserInfo/get_anchor_in_room", {
151
187
  params: { roomid: Number(roomId) }
152
188
  });
153
189
  if (res.data.code !== 0) throw new Error(`获取主播信息失败: ${res.data.code} ${res.data.message || ""}`);
190
+ debugLog(`直播间${roomId}主播信息`, res.data.data);
154
191
  return res.data.data;
155
192
  }
156
193
  __name(getAnchorInfo, "getAnchorInfo");
157
194
  function formatTime(timestamp) {
158
- return new Date(timestamp * 1e3).toLocaleString("zh-CN", {
195
+ const formatted = new Date(timestamp * 1e3).toLocaleString("zh-CN", {
159
196
  year: "numeric",
160
197
  month: "2-digit",
161
198
  day: "2-digit",
@@ -163,19 +200,24 @@ function apply(ctx, config) {
163
200
  minute: "2-digit",
164
201
  second: "2-digit"
165
202
  });
203
+ debugLog(`时间戳${timestamp}格式化结果:${formatted}`);
204
+ return formatted;
166
205
  }
167
206
  __name(formatTime, "formatTime");
168
207
  function formatSeconds(seconds) {
169
208
  const h = Math.floor(seconds / 3600);
170
209
  const m = Math.floor(seconds % 3600 / 60);
171
210
  const s = seconds % 60;
172
- return [h, m, s].map((v) => v.toString().padStart(2, "0")).join(":");
211
+ const formatted = [h, m, s].map((v) => v.toString().padStart(2, "0")).join(":");
212
+ debugLog(`秒数${seconds}格式化结果:${formatted}`);
213
+ return formatted;
173
214
  }
174
215
  __name(formatSeconds, "formatSeconds");
175
216
  function calcLiveStat(startTime, endTime, lastStat, currentStat) {
217
+ debugLog(`计算下播统计数据`, { startTime, endTime, lastStat, currentStat });
176
218
  let fansChange = 0;
177
219
  if (lastStat && currentStat) fansChange = currentStat.fans_count - lastStat.fans_count;
178
- return {
220
+ const stat = {
179
221
  liveTime: endTime - startTime,
180
222
  peakOnline: currentStat.online,
181
223
  watchCount: currentStat.watch_num,
@@ -186,20 +228,31 @@ function apply(ctx, config) {
186
228
  fansChange,
187
229
  endTime
188
230
  };
231
+ debugLog(`下播统计数据计算结果`, stat);
232
+ return stat;
189
233
  }
190
234
  __name(calcLiveStat, "calcLiveStat");
191
235
  function renderTemplate(template, data) {
192
- return template.replace(/\{(\w+)\}/g, (_, key) => data[key] ?? `{${key}}`);
236
+ debugLog(`渲染模板`, { template, data });
237
+ const result = template.replace(/\{(\w+)\}/g, (_, key) => data[key] ?? `{${key}}`);
238
+ debugLog(`模板渲染结果`, result);
239
+ return result;
193
240
  }
194
241
  __name(renderTemplate, "renderTemplate");
195
242
  async function sendGroupMessage(groupId, message) {
243
+ debugLog(`准备发送群消息`, { groupId, message });
196
244
  const bot = ctx.bots.values().next().value;
197
245
  if (!bot) throw new Error("未找到可用的Bot实例");
198
246
  await bot.sendMessage(groupId, message);
247
+ debugLog(`群消息发送成功:${groupId}`);
199
248
  }
200
249
  __name(sendGroupMessage, "sendGroupMessage");
201
250
  async function initAnchorState(mid) {
202
- if (anchorStateCache.has(mid)) return;
251
+ if (anchorStateCache.has(mid)) {
252
+ debugLog(`主播${mid}状态已缓存,跳过初始化`);
253
+ return;
254
+ }
255
+ debugLog(`开始初始化主播${mid}状态`);
203
256
  const roomId = await getRoomIdByMid(mid);
204
257
  const roomInfo = await getRoomBaseInfo(roomId);
205
258
  const isOnline = roomInfo.live_status === 1;
@@ -211,11 +264,14 @@ function apply(ctx, config) {
211
264
  liveStartTime: isOnline ? roomInfo.live_time : 0,
212
265
  lastStat
213
266
  });
267
+ debugLog(`主播${mid}状态初始化完成`, anchorStateCache.get(mid));
214
268
  }
215
269
  __name(initAnchorState, "initAnchorState");
216
270
  async function checkAnchorState(mid, groupIds) {
271
+ debugLog(`开始检查主播${mid}状态`, { groupIds });
217
272
  const state = anchorStateCache.get(mid);
218
273
  if (!state) {
274
+ debugLog(`主播${mid}状态未初始化,先执行初始化`);
219
275
  await initAnchorState(mid);
220
276
  return;
221
277
  }
@@ -224,6 +280,7 @@ function apply(ctx, config) {
224
280
  const anchorInfo = await getAnchorInfo(state.roomId);
225
281
  const now = Math.floor(Date.now() / 1e3);
226
282
  if (!state.isOnline && currentOnline) {
283
+ debugLog(`主播${mid}状态变化:离线 → 在线`);
227
284
  const currentStat = await getRoomStat(state.roomId);
228
285
  state.isOnline = true;
229
286
  state.liveStartTime = roomInfo.live_time || now;
@@ -240,6 +297,7 @@ function apply(ctx, config) {
240
297
  for (const gid of groupIds) await sendGroupMessage(gid, message);
241
298
  ctx.logger.info(`主播${mid}开播,已发送通知到指定群聊`);
242
299
  } else if (state.isOnline && !currentOnline && state.liveStartTime > 0) {
300
+ debugLog(`主播${mid}状态变化:在线 → 离线`);
243
301
  const currentStat = await getRoomStat(state.roomId);
244
302
  const liveStat = calcLiveStat(state.liveStartTime, now, state.lastStat, currentStat);
245
303
  state.isOnline = false;
@@ -260,12 +318,18 @@ function apply(ctx, config) {
260
318
  for (const gid of groupIds) await sendGroupMessage(gid, message);
261
319
  ctx.logger.info(`主播${mid}下播,已发送统计通知到指定群聊`);
262
320
  } else if (state.isOnline && currentOnline) {
321
+ debugLog(`主播${mid}持续开播,更新统计数据`);
263
322
  const currentStat = await getRoomStat(state.roomId);
264
323
  state.lastStat = currentStat;
324
+ } else {
325
+ debugLog(`主播${mid}状态无变化:离线`);
265
326
  }
266
327
  }
267
328
  __name(checkAnchorState, "checkAnchorState");
268
329
  ctx.on("ready", async () => {
330
+ if (finalConfig.debugMode) {
331
+ ctx.logger.info("bilirice插件调试模式已开启,将打印详细日志!");
332
+ }
269
333
  if (!finalConfig.anchors || finalConfig.anchors.length === 0) {
270
334
  ctx.logger.error("bilirice插件未配置任何主播,无法启动!");
271
335
  return;
@@ -275,12 +339,19 @@ function apply(ctx, config) {
275
339
  return;
276
340
  }
277
341
  for (const [mid, _] of finalConfig.anchors) {
278
- await initAnchorState(mid).catch((err) => ctx.logger.error(`初始化主播${mid}失败:`, err.message));
342
+ await initAnchorState(mid).catch((err) => {
343
+ ctx.logger.error(`初始化主播${mid}失败:`, err.message);
344
+ debugLog(`初始化主播${mid}失败详情`, err);
345
+ });
279
346
  }
280
347
  ctx.setInterval(async () => {
348
+ debugLog(`开始定时轮询,共${finalConfig.anchors.length}个主播`);
281
349
  for (const [mid, groupStr] of finalConfig.anchors) {
282
350
  const groupIds = groupStr.split(",").filter((g) => g.trim());
283
- await checkAnchorState(mid, groupIds).catch((err) => ctx.logger.error(`检查主播${mid}失败:`, err.message));
351
+ await checkAnchorState(mid, groupIds).catch((err) => {
352
+ ctx.logger.error(`检查主播${mid}失败:`, err.message);
353
+ debugLog(`检查主播${mid}失败详情`, err);
354
+ });
284
355
  }
285
356
  }, finalConfig.pollInterval * 1e3);
286
357
  ctx.logger.info(`bilirice插件已启动,监听主播数:${finalConfig.anchors.length},轮询间隔:${finalConfig.pollInterval}秒`);
@@ -289,11 +360,13 @@ function apply(ctx, config) {
289
360
  if (!mid) return "请输入主播UID";
290
361
  if (!finalConfig.bilibiliCookie) return "插件未配置B站Cookie,无法执行操作!";
291
362
  try {
363
+ debugLog(`手动指令触发:检查主播${mid}状态`, { session });
292
364
  const groupIds = session.guildId ? [session.guildId] : [];
293
365
  await initAnchorState(mid);
294
366
  await checkAnchorState(mid, groupIds);
295
367
  return `已检查主播${mid}的直播间状态(含完整统计数据)`;
296
368
  } catch (err) {
369
+ debugLog(`手动指令执行失败`, err);
297
370
  return `检查失败:${err.message}`;
298
371
  }
299
372
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-bilirice",
3
3
  "description": "这是一个bilibili直播间事件监控插件",
4
- "version": "0.0.4",
4
+ "version": "0.0.5",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [