koishi-plugin-bilibili-notify 2.0.0 → 3.0.0-alpha.1

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/biliAPI.d.ts CHANGED
@@ -26,6 +26,21 @@ declare class BiliAPI extends Service {
26
26
  }): Promise<string>;
27
27
  encrypt(text: string): string;
28
28
  decrypt(text: string): string;
29
+ getTheUserWhoIsLiveStreaming(): Promise<{
30
+ count: number;
31
+ group: string;
32
+ items: [
33
+ {
34
+ face: string;
35
+ is_reserve_recall: boolean;
36
+ jump_url: string;
37
+ mid: number;
38
+ room_id: number;
39
+ title: string;
40
+ uname: string;
41
+ }
42
+ ];
43
+ }>;
29
44
  getLiveRoomInfoStreamKey(roomId: string): Promise<any>;
30
45
  getServerUTCTime(): Promise<number>;
31
46
  getTimeNow(): Promise<any>;
@@ -54,6 +69,7 @@ declare class BiliAPI extends Service {
54
69
  createNewClient(): void;
55
70
  getTimeOfUTC8(): number;
56
71
  getCookies(): string;
72
+ getCookiesForHeader(): Promise<string>;
57
73
  getLoginInfoIsLoaded(): boolean;
58
74
  getLoginInfoFromDB(): Promise<{
59
75
  cookies: any;
package/lib/biliAPI.js CHANGED
@@ -34,6 +34,8 @@ const GET_LIVE_ROOM_INFO = 'https://api.live.bilibili.com/room/v1/Room/get_info'
34
34
  const GET_MASTER_INFO = 'https://api.live.bilibili.com/live_user/v1/Master/info';
35
35
  const GET_TIME_NOW = 'https://api.bilibili.com/x/report/click/now';
36
36
  const GET_SERVER_UTC_TIME = 'https://interface.bilibili.com/serverdate.js';
37
+ // 最近更新UP
38
+ const GET_LATEST_UPDATED_UPS = 'https://api.bilibili.com/x/polymer/web-dynamic/v1/portal';
37
39
  // 操作
38
40
  const MODIFY_RELATION = 'https://api.bilibili.com/x/relation/modify';
39
41
  const CREATE_GROUP = 'https://api.bilibili.com/x/relation/tag/create';
@@ -105,6 +107,17 @@ class BiliAPI extends koishi_1.Service {
105
107
  return decrypted.toString();
106
108
  }
107
109
  // BA API
110
+ async getTheUserWhoIsLiveStreaming() {
111
+ try {
112
+ // 获取直播间信息流密钥
113
+ const { data: { live_users } } = await this.client.get(GET_LATEST_UPDATED_UPS);
114
+ // 返回data
115
+ return live_users;
116
+ }
117
+ catch (e) {
118
+ throw new Error('网络异常,本次请求失败!');
119
+ }
120
+ }
108
121
  async getLiveRoomInfoStreamKey(roomId) {
109
122
  try {
110
123
  // 获取直播间信息流密钥
@@ -375,6 +388,21 @@ class BiliAPI extends koishi_1.Service {
375
388
  const cookies = JSON.stringify(this.jar.serializeSync().cookies);
376
389
  return cookies;
377
390
  }
391
+ async getCookiesForHeader() {
392
+ try {
393
+ // 获取cookies对象
394
+ const cookies = this.jar.serializeSync().cookies;
395
+ // 将每个 cookie 对象转换为 "key=value" 形式,并用 "; " 连接起来
396
+ const cookieHeader = cookies
397
+ .map(cookie => `${cookie.key}=${cookie.value}`)
398
+ .join('; ');
399
+ return cookieHeader;
400
+ }
401
+ catch (e) {
402
+ console.error("无效的 JSON 格式:", e);
403
+ return "";
404
+ }
405
+ }
378
406
  getLoginInfoIsLoaded() {
379
407
  return this.loginInfoIsLoaded;
380
408
  }
package/lib/blive.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ import { Awaitable, Context, Schema, Service } from "koishi";
2
+ import { type MsgHandler } from 'blive-message-listener';
3
+ declare module 'koishi' {
4
+ interface Context {
5
+ bl: BLive;
6
+ }
7
+ }
8
+ declare class BLive extends Service {
9
+ static inject: string[];
10
+ private listenerRecord;
11
+ private timerRecord;
12
+ constructor(ctx: Context);
13
+ protected stop(): Awaitable<void>;
14
+ startLiveRoomListener(roomId: string, handler: MsgHandler, pushOnceEveryTens: () => void): Promise<void>;
15
+ closeListener(roomId: string): void;
16
+ }
17
+ declare namespace BLive {
18
+ interface Config {
19
+ danmakuPushTime: number;
20
+ }
21
+ const Config: Schema<Config>;
22
+ }
23
+ export default BLive;
package/lib/blive.js ADDED
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const koishi_1 = require("koishi");
4
+ const blive_message_listener_1 = require("blive-message-listener");
5
+ class BLive extends koishi_1.Service {
6
+ // 必要服务
7
+ static inject = ['ba'];
8
+ // 定义类属性
9
+ listenerRecord = {};
10
+ timerRecord = {};
11
+ constructor(ctx) {
12
+ super(ctx, 'bl');
13
+ }
14
+ // 注册插件dispose逻辑
15
+ stop() {
16
+ // 清除所有监听器
17
+ for (const key of Object.keys(this.listenerRecord)) {
18
+ this.closeListener(key);
19
+ }
20
+ }
21
+ async startLiveRoomListener(roomId, handler, pushOnceEveryTens) {
22
+ // 获取cookieStr
23
+ const cookiesStr = await this.ctx.ba.getCookiesForHeader();
24
+ // 获取自身信息
25
+ const mySelfInfo = await this.ctx.ba.getMyselfInfo();
26
+ // 创建实例并保存到Record中
27
+ this.listenerRecord[roomId] = (0, blive_message_listener_1.startListen)(parseInt(roomId), handler, {
28
+ ws: {
29
+ headers: {
30
+ Cookie: cookiesStr
31
+ },
32
+ uid: mySelfInfo.data.mid
33
+ }
34
+ });
35
+ // 默认30s推送一次弹幕消息到群组并将dispose函数保存到Record中
36
+ this.timerRecord[roomId] = this.ctx.setInterval(pushOnceEveryTens, this.config.danmakuPushTime * 1000 * 60);
37
+ }
38
+ closeListener(roomId) {
39
+ // 判断直播间监听器是否关闭
40
+ if (!this.listenerRecord || !this.listenerRecord[roomId] || !this.listenerRecord[roomId].closed) {
41
+ // 输出logger
42
+ this.logger.info('直播间监听器无需关闭');
43
+ }
44
+ // 判断消息发送定时器是否关闭
45
+ if (!this.timerRecord || !this.timerRecord[roomId]) {
46
+ // 输出logger
47
+ this.logger.info('消息发送定时器无需关闭');
48
+ }
49
+ // 关闭直播间监听器
50
+ this.listenerRecord[roomId].close();
51
+ // 关闭消息发送定时器
52
+ this.timerRecord[roomId]();
53
+ // 判断是否关闭成功
54
+ if (this.listenerRecord[roomId].closed) {
55
+ // 删除直播间监听器
56
+ delete this.listenerRecord[roomId];
57
+ // 删除消息发送定时器
58
+ delete this.timerRecord[roomId];
59
+ // 输出logger
60
+ this.logger.info('直播间监听已关闭');
61
+ // 直接返回
62
+ return;
63
+ }
64
+ // 未关闭成功
65
+ this.logger.warn('直播间监听未成功关闭');
66
+ }
67
+ }
68
+ // eslint-disable-next-line @typescript-eslint/no-namespace
69
+ (function (BLive) {
70
+ BLive.Config = koishi_1.Schema.object({
71
+ danmakuPushTime: koishi_1.Schema.number().required()
72
+ });
73
+ })(BLive || (BLive = {}));
74
+ exports.default = BLive;
@@ -1,6 +1,12 @@
1
1
  import { Bot, Context, FlatPick, Logger, Schema, Session } from "koishi";
2
2
  import { Notifier } from "@koishijs/plugin-notifier";
3
3
  import { LoginBili } from "./database";
4
+ declare enum LiveType {
5
+ NotLiveBroadcast = 0,
6
+ StartBroadcasting = 1,
7
+ LiveBroadcast = 2,
8
+ StopBroadcast = 3
9
+ }
4
10
  type ChannelIdArr = Array<{
5
11
  channelId: string;
6
12
  dynamic: boolean;
@@ -47,7 +53,19 @@ declare class ComRegister {
47
53
  sendMsg(ctx: Context, targets: Target, content: any, live?: boolean): Promise<void>;
48
54
  dynamicDetect(ctx: Context): () => Promise<void>;
49
55
  debug_dynamicDetect(ctx: Context): () => Promise<void>;
50
- liveDetect(ctx: Context, roomId: string, target: Target): () => Promise<void>;
56
+ sendLiveNotifyCard(ctx: Context, info: {
57
+ username: string;
58
+ userface: string;
59
+ target: Target;
60
+ data: any;
61
+ }, liveType: LiveType, liveNotifyMsg?: string): Promise<void>;
62
+ useMasterInfo(ctx: Context, uid: string): Promise<{
63
+ username: string;
64
+ userface: string;
65
+ }>;
66
+ useLiveRoomInfo(ctx: Context, roomId: string): Promise<any>;
67
+ liveDetectWithAPI(ctx: Context): Promise<() => Promise<void>>;
68
+ liveDetectWithListener(ctx: Context, roomId: string, target: Target): void;
51
69
  subShow(): string;
52
70
  checkIfNeedSub(liveSub: boolean, dynamicSub: boolean, session: Session, liveRoomData: any): Promise<Array<boolean>>;
53
71
  updateSubNotifier(ctx: Context): void;
@@ -18,7 +18,7 @@ var LiveType;
18
18
  LiveType[LiveType["StopBroadcast"] = 3] = "StopBroadcast";
19
19
  })(LiveType || (LiveType = {}));
20
20
  class ComRegister {
21
- static inject = ['ba', 'gi', 'database', 'sm'];
21
+ static inject = ['ba', 'gi', 'database', 'bl', 'sm'];
22
22
  qqRelatedBotList = ['qq', 'onebot', 'red', 'satori', 'chronocat'];
23
23
  logger;
24
24
  config;
@@ -430,8 +430,8 @@ class ComRegister {
430
430
  let liveDispose;
431
431
  // 订阅直播
432
432
  if (liveMsg) {
433
- // 开始循环检测
434
- liveDispose = ctx.setInterval(this.liveDetect(ctx, roomId, target), config.liveLoopTime * 1000);
433
+ // 连接到服务器
434
+ this.liveDetectWithListener(ctx, roomId, target);
435
435
  // 发送订阅消息通知
436
436
  await session.send(`订阅${userData.info.uname}直播通知`);
437
437
  }
@@ -1075,137 +1075,148 @@ class ComRegister {
1075
1075
  }
1076
1076
  };
1077
1077
  }
1078
- liveDetect(ctx, roomId, target) {
1079
- let firstSubscription = true;
1080
- let timer = 0;
1081
- let open = false;
1082
- let liveTime;
1083
- let username;
1084
- let userface;
1085
- // 相当于锁的作用,防止上一个循环没处理完
1086
- let flag = true;
1087
- // 定义发送直播通知卡片方法
1088
- const sendLiveNotifyCard = async (data, liveType, liveNotifyMsg) => {
1089
- // 定义变量
1090
- let pic;
1091
- let buffer;
1092
- // 多次尝试生成图片
1093
- const attempts = 3;
1094
- for (let i = 0; i < attempts; i++) {
1095
- try {
1096
- // 获取直播通知卡片
1097
- const { pic: picv, buffer: bufferv } = await ctx.gi.generateLiveImg(data, username, userface, liveType);
1098
- // 赋值
1099
- pic = picv;
1100
- buffer = bufferv;
1101
- // 成功则跳出循环
1102
- break;
1103
- }
1104
- catch (e) {
1105
- if (i === attempts - 1) { // 已尝试三次
1106
- this.logger.error('liveDetect generateLiveImg() 推送卡片生成失败,原因:' + e.message);
1107
- // 发送私聊消息并重启服务
1108
- return await this.sendPrivateMsgAndStopService(ctx);
1109
- }
1110
- }
1078
+ // 定义发送直播通知卡片方法
1079
+ async sendLiveNotifyCard(ctx, info, liveType, liveNotifyMsg) {
1080
+ // 定义变量
1081
+ let pic;
1082
+ let buffer;
1083
+ // 多次尝试生成图片
1084
+ const attempts = 3;
1085
+ for (let i = 0; i < attempts; i++) {
1086
+ try {
1087
+ // 获取直播通知卡片
1088
+ const { pic: picv, buffer: bufferv } = await ctx.gi.generateLiveImg(info.data, info.username, info.userface, liveType);
1089
+ // 赋值
1090
+ pic = picv;
1091
+ buffer = bufferv;
1092
+ // 成功则跳出循环
1093
+ break;
1111
1094
  }
1112
- // 推送直播信息
1113
- // pic 存在,使用的是render模式
1114
- if (pic) {
1115
- // 只有在开播时才艾特全体成员
1116
- if (liveType === LiveType.StartBroadcasting) {
1117
- return await this.sendMsg(ctx, target, pic + (liveNotifyMsg ?? ''), true);
1095
+ catch (e) {
1096
+ if (i === attempts - 1) { // 已尝试三次
1097
+ this.logger.error('liveDetect generateLiveImg() 推送卡片生成失败,原因:' + e.message);
1098
+ // 发送私聊消息并重启服务
1099
+ return await this.sendPrivateMsgAndStopService(ctx);
1118
1100
  }
1119
- // 正常不需要艾特全体成员
1120
- return await this.sendMsg(ctx, target, pic + (liveNotifyMsg ?? ''));
1121
1101
  }
1122
- // pic不存在,说明使用的是page模式
1123
- const msg = (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [koishi_1.h.image(buffer, 'image/png'), liveNotifyMsg || ''] });
1102
+ }
1103
+ // 推送直播信息
1104
+ // pic 存在,使用的是render模式
1105
+ if (pic) {
1124
1106
  // 只有在开播时才艾特全体成员
1125
1107
  if (liveType === LiveType.StartBroadcasting) {
1126
- return await this.sendMsg(ctx, target, msg, true);
1108
+ return await this.sendMsg(ctx, info.target, pic + (liveNotifyMsg ?? ''), true);
1127
1109
  }
1128
1110
  // 正常不需要艾特全体成员
1129
- return await this.sendMsg(ctx, target, msg);
1130
- };
1131
- // 定义获取主播信息方法
1132
- let useMasterInfo;
1133
- if (this.config.changeMasterInfoApi) {
1134
- useMasterInfo = async (uid) => {
1135
- const { data } = await ctx.ba.getUserInfo(uid);
1136
- username = data.name;
1137
- userface = data.face;
1138
- };
1111
+ return await this.sendMsg(ctx, info.target, pic + (liveNotifyMsg ?? ''));
1139
1112
  }
1140
- else {
1141
- useMasterInfo = async (uid) => {
1142
- const { data: { info } } = await ctx.ba.getMasterInfo(uid);
1143
- username = info.uname;
1144
- userface = info.face;
1145
- };
1113
+ // pic不存在,说明使用的是page模式
1114
+ const msg = (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [koishi_1.h.image(buffer, 'image/png'), liveNotifyMsg || ''] });
1115
+ // 只有在开播时才艾特全体成员
1116
+ if (liveType === LiveType.StartBroadcasting) {
1117
+ return await this.sendMsg(ctx, info.target, msg, true);
1118
+ }
1119
+ // 正常不需要艾特全体成员
1120
+ return await this.sendMsg(ctx, info.target, msg);
1121
+ }
1122
+ // 定义获取主播信息方法
1123
+ async useMasterInfo(ctx, uid) {
1124
+ const { data: { info } } = await ctx.ba.getMasterInfo(uid);
1125
+ return { username: info.name, userface: info.face };
1126
+ }
1127
+ async useLiveRoomInfo(ctx, roomId) {
1128
+ // 发送请求获取直播间信息
1129
+ let content;
1130
+ const attempts = 3;
1131
+ for (let i = 0; i < attempts; i++) {
1132
+ try {
1133
+ // 发送请求获取room信息
1134
+ content = await ctx.ba.getLiveRoomInfo(roomId);
1135
+ // 成功则跳出循环
1136
+ break;
1137
+ }
1138
+ catch (e) {
1139
+ this.logger.error('liveDetect getLiveRoomInfo 发生了错误,错误为:' + e.message);
1140
+ if (i === attempts - 1) { // 已尝试三次
1141
+ // 发送私聊消息并重启服务
1142
+ return await this.sendPrivateMsgAndStopService(ctx);
1143
+ }
1144
+ }
1146
1145
  }
1146
+ return content.data;
1147
+ }
1148
+ /* liveDetect(
1149
+ ctx: Context,
1150
+ roomId: string,
1151
+ target: Target
1152
+ ) {
1153
+ let firstSubscription: boolean = true;
1154
+ let timer: number = 0;
1155
+ let open: boolean = false;
1156
+ let liveTime: string;
1157
+ let username: string
1158
+ let userface: string
1159
+ // 相当于锁的作用,防止上一个循环没处理完
1160
+ let flag: boolean = true
1161
+
1147
1162
  return async () => {
1148
1163
  // 如果flag为false则说明前面的代码还未执行完,则直接返回
1149
- if (!flag)
1150
- return;
1151
- flag = false;
1164
+ if (!flag) return
1165
+ flag = false
1152
1166
  // 无论是否执行成功都要释放锁
1153
1167
  try {
1154
1168
  // 发送请求检测直播状态
1155
- let content;
1156
- const attempts = 3;
1169
+ let content: any
1170
+ const attempts = 3
1157
1171
  for (let i = 0; i < attempts; i++) {
1158
1172
  try {
1159
1173
  // 发送请求获取room信息
1160
- content = await ctx.ba.getLiveRoomInfo(roomId);
1174
+ content = await ctx.ba.getLiveRoomInfo(roomId)
1161
1175
  // 成功则跳出循环
1162
- break;
1163
- }
1164
- catch (e) {
1165
- this.logger.error('liveDetect getLiveRoomInfo 发生了错误,错误为:' + e.message);
1176
+ break
1177
+ } catch (e) {
1178
+ this.logger.error('liveDetect getLiveRoomInfo 发生了错误,错误为:' + e.message)
1166
1179
  if (i === attempts - 1) { // 已尝试三次
1167
1180
  // 发送私聊消息并重启服务
1168
- return await this.sendPrivateMsgAndStopService(ctx);
1181
+ return await this.sendPrivateMsgAndStopService(ctx)
1169
1182
  }
1170
1183
  }
1171
1184
  }
1172
- const { data } = content;
1185
+ const { data } = content
1173
1186
  // 判断是否是第一次订阅
1174
1187
  if (firstSubscription) {
1175
- firstSubscription = false;
1188
+ firstSubscription = false
1176
1189
  // 获取主播信息
1177
- const attempts = 3;
1190
+ const attempts = 3
1178
1191
  for (let i = 0; i < attempts; i++) {
1179
1192
  try {
1180
1193
  // 发送请求获取主播信息
1181
- await useMasterInfo(data.uid);
1194
+ await useMasterInfo(data.uid)
1182
1195
  // 成功则跳出循环
1183
- break;
1184
- }
1185
- catch (e) {
1186
- this.logger.error('liveDetect getMasterInfo() 发生了错误,错误为:' + e.message);
1196
+ break
1197
+ } catch (e) {
1198
+ this.logger.error('liveDetect getMasterInfo() 发生了错误,错误为:' + e.message)
1187
1199
  if (i === attempts - 1) { // 已尝试三次
1188
1200
  // 发送私聊消息并重启服务
1189
- return await this.sendPrivateMsgAndStopService(ctx);
1201
+ return await this.sendPrivateMsgAndStopService(ctx)
1190
1202
  }
1191
1203
  }
1192
1204
  }
1193
1205
  // 判断直播状态
1194
1206
  if (data.live_status === 1) { // 当前正在直播
1195
1207
  // 设置开播时间
1196
- liveTime = data.live_time;
1208
+ liveTime = data.live_time
1197
1209
  // 设置直播中消息
1198
1210
  const liveMsg = this.config.customLive ? this.config.customLive
1199
1211
  .replace('-name', username)
1200
1212
  .replace('-time', await ctx.gi.getTimeDifference(liveTime))
1201
- .replace('-link', `https://live.bilibili.com/${data.short_id === 0 ? data.room_id : data.short_id}`) : null;
1213
+ .replace('-link', `https://live.bilibili.com/${data.short_id === 0 ? data.room_id : data.short_id}`) : null
1202
1214
  // 发送直播通知卡片
1203
- if (this.config.restartPush)
1204
- sendLiveNotifyCard(data, LiveType.LiveBroadcast, liveMsg);
1215
+ if (this.config.restartPush) sendLiveNotifyCard(data, LiveType.LiveBroadcast, liveMsg)
1205
1216
  // 改变开播状态
1206
- open = true;
1217
+ open = true
1207
1218
  } // 未开播,直接返回
1208
- return;
1219
+ return
1209
1220
  }
1210
1221
  // 检查直播状态
1211
1222
  switch (data.live_status) {
@@ -1213,67 +1224,65 @@ class ComRegister {
1213
1224
  case 2: { // 状态 0 和 2 说明未开播
1214
1225
  if (open) { // 之前开播,现在下播了
1215
1226
  // 更改直播状态
1216
- open = false;
1227
+ open = false
1217
1228
  // 下播了将定时器清零
1218
- timer = 0;
1229
+ timer = 0
1219
1230
  // 定义下播通知消息
1220
1231
  const liveEndMsg = this.config.customLiveEnd ? this.config.customLiveEnd
1221
1232
  .replace('-name', username)
1222
- .replace('-time', await ctx.gi.getTimeDifference(liveTime)) : null;
1233
+ .replace('-time', await ctx.gi.getTimeDifference(liveTime)) : null
1223
1234
  // 更改直播时长
1224
- data.live_time = liveTime;
1225
- // 发送@全体成员通知
1226
- await sendLiveNotifyCard(data, LiveType.StopBroadcast, liveEndMsg);
1235
+ data.live_time = liveTime
1236
+ // 推送下播通知
1237
+ await sendLiveNotifyCard(data, LiveType.StopBroadcast, liveEndMsg)
1227
1238
  }
1228
1239
  // 未进循环,还未开播,继续循环
1229
- break;
1240
+ break
1230
1241
  }
1231
1242
  case 1: {
1232
1243
  if (!open) { // 之前未开播,现在开播了
1233
1244
  // 更改直播状态
1234
- open = true;
1245
+ open = true
1235
1246
  // 设置开播时间
1236
- liveTime = data.live_time;
1247
+ liveTime = data.live_time
1237
1248
  // 获取主播信息
1238
- const attempts = 3;
1249
+ const attempts = 3
1239
1250
  for (let i = 0; i < attempts; i++) {
1240
1251
  try {
1241
1252
  // 主播信息不会变,开播时刷新一次即可
1242
1253
  // 发送请求获取主播信息
1243
- await useMasterInfo(data.uid);
1254
+ await useMasterInfo(data.uid)
1244
1255
  // 成功则跳出循环
1245
- break;
1246
- }
1247
- catch (e) {
1248
- this.logger.error('liveDetect open getMasterInfo() 发生了错误,错误为:' + e.message);
1256
+ break
1257
+ } catch (e) {
1258
+ this.logger.error('liveDetect open getMasterInfo() 发生了错误,错误为:' + e.message)
1249
1259
  if (i === attempts - 1) { // 已尝试三次
1250
1260
  // 发送私聊消息并重启服务
1251
- return await this.sendPrivateMsgAndStopService(ctx);
1261
+ return await this.sendPrivateMsgAndStopService(ctx)
1252
1262
  }
1253
1263
  }
1254
1264
  }
1255
- // 定义开播通知语
1265
+ // 定义开播通知语
1256
1266
  const liveStartMsg = this.config.customLiveStart ? this.config.customLiveStart
1257
1267
  .replace('-name', username)
1258
1268
  .replace('-time', await ctx.gi.getTimeDifference(liveTime))
1259
- .replace('-link', `https://live.bilibili.com/${data.short_id === 0 ? data.room_id : data.short_id}`) : null;
1269
+ .replace('-link', `https://live.bilibili.com/${data.short_id === 0 ? data.room_id : data.short_id}`) : null
1260
1270
  // 发送消息
1261
- await sendLiveNotifyCard(data, LiveType.StartBroadcasting, liveStartMsg);
1262
- }
1263
- else { // 还在直播
1271
+ await sendLiveNotifyCard(data, LiveType.StartBroadcasting, liveStartMsg)
1272
+ } else { // 还在直播
1264
1273
  if (this.config.pushTime > 0) {
1265
- timer++;
1274
+ timer++
1266
1275
  // 开始记录时间
1267
1276
  if (timer >= (6 * 60 * this.config.pushTime)) { // 到时间推送直播消息
1268
1277
  // 到时间重新计时
1269
- timer = 0;
1278
+ timer = 0
1270
1279
  // 定义直播中通知消息
1271
1280
  const liveMsg = this.config.customLive ? this.config.customLive
1272
1281
  .replace('-name', username)
1273
1282
  .replace('-time', await ctx.gi.getTimeDifference(liveTime))
1274
- .replace('-link', `https://live.bilibili.com/${data.short_id === 0 ? data.room_id : data.short_id}`) : null;
1283
+ .replace('-link', `https://live.bilibili.com/${data.short_id === 0 ? data.room_id : data.short_id}`) : null
1275
1284
  // 发送直播通知卡片
1276
- sendLiveNotifyCard(data, LiveType.LiveBroadcast, liveMsg);
1285
+ sendLiveNotifyCard(data, LiveType.LiveBroadcast, liveMsg)
1277
1286
  }
1278
1287
  }
1279
1288
  // 否则继续循环
@@ -1281,12 +1290,299 @@ class ComRegister {
1281
1290
  }
1282
1291
  }
1283
1292
  }
1293
+ finally {
1294
+ // 执行完方法体不论如何都把flag设置为true
1295
+ flag = true
1296
+ }
1297
+ }
1298
+ } */
1299
+ async liveDetectWithAPI(ctx) {
1300
+ // 定义变量:第一次订阅
1301
+ let firstSubscription = true;
1302
+ // 定义变量:timer计时器
1303
+ let timer = 0;
1304
+ // 相当于锁的作用,防止上一个循环没处理完
1305
+ let flag = true;
1306
+ // 定义订阅对象Record 0未开播 1正在直播 2轮播中
1307
+ const liveRecord = {};
1308
+ // 初始化subRecord
1309
+ this.subManager.forEach(sub => {
1310
+ // 判断是否订阅直播
1311
+ if (sub.live) {
1312
+ // 将该订阅添加到subRecord中
1313
+ liveRecord[sub.uid] = { liveStatus: 0, liveTime: '', target: sub.target };
1314
+ }
1315
+ });
1316
+ // 定义函数: 发送请求获取直播状态
1317
+ const useLiveStatus = async (roomId) => {
1318
+ let content;
1319
+ const attempts = 3;
1320
+ for (let i = 0; i < attempts; i++) {
1321
+ try {
1322
+ // 发送请求获取room信息
1323
+ content = await ctx.ba.getLiveRoomInfo(roomId);
1324
+ // 成功则跳出循环
1325
+ break;
1326
+ }
1327
+ catch (e) {
1328
+ this.logger.error('liveDetect getLiveRoomInfo 发生了错误,错误为:' + e.message);
1329
+ if (i === attempts - 1) { // 已尝试三次
1330
+ // 发送私聊消息并重启服务
1331
+ return await this.sendPrivateMsgAndStopService(ctx);
1332
+ }
1333
+ }
1334
+ }
1335
+ // 返回data
1336
+ return content.data;
1337
+ };
1338
+ return async () => {
1339
+ // 如果flag为false则说明前面的代码还未执行完,则直接返回
1340
+ if (!flag)
1341
+ return;
1342
+ // 将标志位置为false
1343
+ flag = false;
1344
+ try {
1345
+ // 获取正在直播对象
1346
+ const liveUsers = await ctx.ba.getTheUserWhoIsLiveStreaming();
1347
+ // 判断是否是第一次订阅
1348
+ if (firstSubscription) {
1349
+ // 将第一次订阅置为false
1350
+ firstSubscription = false;
1351
+ // 先判断是否有UP主正在直播
1352
+ if (liveUsers.count > 0) {
1353
+ // 遍历liveUsers
1354
+ liveUsers.items.forEach(async (item) => {
1355
+ // 判断是否有订阅对象正在直播
1356
+ if (liveRecord[item.mid]) {
1357
+ // 获取当前用户直播间信息
1358
+ const data = await useLiveStatus(item.room_id.toString());
1359
+ // 设置开播时间
1360
+ liveRecord[item.mid].liveTime = data.live_time;
1361
+ // 设置直播中消息
1362
+ const liveMsg = this.config.customLive ? this.config.customLive
1363
+ .replace('-name', item.uname)
1364
+ .replace('-time', await ctx.gi.getTimeDifference(liveRecord[item.mid].liveTime))
1365
+ .replace('-link', `https://live.bilibili.com/${data.short_id === 0 ? data.room_id : data.short_id}`) : null;
1366
+ // 发送直播通知卡片
1367
+ if (this.config.restartPush)
1368
+ this.sendLiveNotifyCard(ctx, {
1369
+ username: item.uname,
1370
+ userface: item.face,
1371
+ target: liveRecord[item.mid].target,
1372
+ data
1373
+ }, LiveType.LiveBroadcast, liveMsg);
1374
+ // 改变开播状态
1375
+ liveRecord[item.mid].liveStatus = 1;
1376
+ }
1377
+ });
1378
+ }
1379
+ // 没有正在直播的订阅对象,直接返回
1380
+ return;
1381
+ }
1382
+ // 获取当前订阅直播的数量
1383
+ const currentLiveSubs = this.subManager.filter(sub => sub.live);
1384
+ // 获取当前liveRecord里的订阅数量
1385
+ const currentLiveRecordKeys = Object.keys(liveRecord);
1386
+ // 判断是否能匹配双方数量
1387
+ if (currentLiveRecordKeys.length < currentLiveSubs.length) {
1388
+ // 遍历currentLiveSubs
1389
+ for (const sub of currentLiveSubs) {
1390
+ // 判断liveRecord中缺少了哪些订阅
1391
+ if (!liveRecord[sub.uid]) {
1392
+ // 获取当前用户直播间信息
1393
+ const data = await useLiveStatus(sub.roomId.toString());
1394
+ switch (data.live_status) {
1395
+ case 0:
1396
+ case 2: { // 未开播
1397
+ // 添加到liveRecord中
1398
+ liveRecord[sub.uid] = { liveStatus: 0, liveTime: '', target: sub.target };
1399
+ // break
1400
+ break;
1401
+ }
1402
+ case 1: { //正在直播
1403
+ // 添加到liveRecord中
1404
+ liveRecord[sub.uid] = { liveStatus: 1, liveTime: data.live_time, target: sub.target };
1405
+ }
1406
+ }
1407
+ }
1408
+ }
1409
+ }
1410
+ if (currentLiveRecordKeys.length > currentLiveSubs.length) {
1411
+ // 创建Set
1412
+ const setCurrentLiveSubs = new Set(currentLiveSubs.map(sub => sub.uid));
1413
+ // 找出 currentLiveRecordKeys中比currentLiveSubs 多的元素
1414
+ const extraInCurrentLiveSubs = currentLiveRecordKeys.filter(key => !setCurrentLiveSubs.has(key));
1415
+ // 遍历 extraInCurrentLiveSubs
1416
+ for (const subUID of extraInCurrentLiveSubs) {
1417
+ // 删除记录
1418
+ delete liveRecord[subUID];
1419
+ }
1420
+ }
1421
+ // 遍历liveUsers
1422
+ liveUsers.items.forEach(async (item) => {
1423
+ // 判断是否有正在直播的订阅对象
1424
+ if (liveRecord[item.mid]) { // 有正在直播的订阅对象
1425
+ // 获取当前用户直播间信息
1426
+ const data = await useLiveStatus(item.room_id.toString());
1427
+ // 判断开播状态
1428
+ switch (liveRecord[item.mid].liveStatus) {
1429
+ case 0: { // 之前未开播,现在开播了
1430
+ // 设置开播时间
1431
+ liveRecord[item.mid].liveTime = data.live_time;
1432
+ // 定义开播通知语
1433
+ const liveStartMsg = this.config.customLiveStart ? this.config.customLiveStart
1434
+ .replace('-name', item.uname)
1435
+ .replace('-time', await ctx.gi.getTimeDifference(liveRecord[item.mid].liveTime))
1436
+ .replace('-link', `https://live.bilibili.com/${data.short_id === 0 ? data.room_id : data.short_id}`) : null;
1437
+ // 发送直播通知卡片
1438
+ if (this.config.restartPush)
1439
+ this.sendLiveNotifyCard(ctx, {
1440
+ username: item.uname,
1441
+ userface: item.face,
1442
+ target: liveRecord[item.mid].target,
1443
+ data
1444
+ }, LiveType.LiveBroadcast, liveStartMsg);
1445
+ // 改变开播状态
1446
+ liveRecord[item.mid].liveStatus = 1;
1447
+ // 结束
1448
+ break;
1449
+ }
1450
+ case 1: { // 仍在直播
1451
+ if (this.config.pushTime > 0) {
1452
+ timer++;
1453
+ // 开始记录时间
1454
+ if (timer >= (6 * 60 * this.config.pushTime)) { // 到时间推送直播消息
1455
+ // 到时间重新计时
1456
+ timer = 0;
1457
+ // 定义直播中通知消息
1458
+ const liveMsg = this.config.customLive ? this.config.customLive
1459
+ .replace('-name', item.uname)
1460
+ .replace('-time', await ctx.gi.getTimeDifference(liveRecord[item.mid].liveTime))
1461
+ .replace('-link', `https://live.bilibili.com/${data.short_id === 0 ? data.room_id : data.short_id}`) : null;
1462
+ // 发送直播通知卡片
1463
+ this.sendLiveNotifyCard(ctx, {
1464
+ username: item.uname,
1465
+ userface: item.face,
1466
+ target: liveRecord[item.mid].target,
1467
+ data
1468
+ }, LiveType.LiveBroadcast, liveMsg);
1469
+ }
1470
+ }
1471
+ }
1472
+ }
1473
+ }
1474
+ });
1475
+ }
1284
1476
  finally {
1285
1477
  // 执行完方法体不论如何都把flag设置为true
1286
1478
  flag = true;
1287
1479
  }
1288
1480
  };
1289
1481
  }
1482
+ liveDetectWithListener(ctx, roomId, target) {
1483
+ // 定义开播时间
1484
+ let liveTime;
1485
+ // 定义定时推送定时器
1486
+ let pushAtTimeTimer;
1487
+ // 定义弹幕存放数组
1488
+ const currentLiveDanmakuArr = [];
1489
+ const temporaryLiveDanmakuArr = [];
1490
+ // 定义定时推送函数
1491
+ const pushAtTime = async () => {
1492
+ // 获取直播间信息
1493
+ const liveRoomInfo = await this.useLiveRoomInfo(ctx, roomId);
1494
+ // 获取主播信息
1495
+ const masterInfo = await this.useMasterInfo(ctx, liveRoomInfo.uid);
1496
+ // 定义直播中通知消息
1497
+ const liveMsg = this.config.customLive ? this.config.customLive
1498
+ .replace('-name', masterInfo.username)
1499
+ .replace('-time', await ctx.gi.getTimeDifference(liveTime))
1500
+ .replace('-link', `https://live.bilibili.com/${liveRoomInfo.short_id === 0 ? liveRoomInfo.room_id : liveRoomInfo.short_id}`) : null;
1501
+ // 发送直播通知卡片
1502
+ await this.sendLiveNotifyCard(liveRoomInfo, {
1503
+ username: masterInfo.username,
1504
+ userface: masterInfo.userface,
1505
+ target,
1506
+ data: liveRoomInfo
1507
+ }, LiveType.LiveBroadcast, liveMsg);
1508
+ };
1509
+ // 定义10秒推送函数
1510
+ const pushOnceEveryTenS = () => {
1511
+ // 判断数组是否有内容
1512
+ if (temporaryLiveDanmakuArr.length > 0) {
1513
+ // 发送消息
1514
+ this.sendMsg(ctx, target, temporaryLiveDanmakuArr.join('\n'));
1515
+ // 将临时消息数组清空
1516
+ temporaryLiveDanmakuArr.length = 0;
1517
+ }
1518
+ };
1519
+ // 构建消息处理函数
1520
+ const handler = {
1521
+ onOpen: () => {
1522
+ this.logger.info('直播间连接成功');
1523
+ },
1524
+ onClose: () => {
1525
+ this.logger.info('直播间连接已断开');
1526
+ },
1527
+ onIncomeDanmu: ({ body }) => {
1528
+ // 处理消息,只需要UP主名字和消息内容
1529
+ const content = `${body.user.uname}:${body.content}`;
1530
+ // 保存消息到数组
1531
+ currentLiveDanmakuArr.push(content);
1532
+ temporaryLiveDanmakuArr.push(content);
1533
+ },
1534
+ onIncomeSuperChat: (msg) => {
1535
+ console.log(msg.id, msg.body);
1536
+ },
1537
+ onLiveStart: async () => {
1538
+ // 获取直播间信息
1539
+ const liveRoomInfo = await this.useLiveRoomInfo(ctx, roomId);
1540
+ // 获取主播信息
1541
+ const masterInfo = await this.useMasterInfo(ctx, liveRoomInfo.uid);
1542
+ // 定义下播通知消息
1543
+ const liveEndMsg = this.config.customLiveEnd ? this.config.customLiveEnd
1544
+ .replace('-name', masterInfo.username)
1545
+ .replace('-time', await ctx.gi.getTimeDifference(liveTime)) : null;
1546
+ // 更改直播时长
1547
+ liveRoomInfo.live_time = liveTime;
1548
+ // 推送下播通知
1549
+ await this.sendLiveNotifyCard(liveRoomInfo, {
1550
+ username: masterInfo.username,
1551
+ userface: masterInfo.userface,
1552
+ target,
1553
+ data: liveRoomInfo
1554
+ }, LiveType.StopBroadcast, liveEndMsg);
1555
+ // 关闭定时器
1556
+ pushAtTimeTimer();
1557
+ // 定时器变量置空
1558
+ pushAtTimeTimer = null;
1559
+ },
1560
+ onLiveEnd: async () => {
1561
+ // 获取直播间消息
1562
+ const liveRoomInfo = await this.useLiveRoomInfo(ctx, roomId);
1563
+ // 获取主播信息
1564
+ const masterInfo = await this.useMasterInfo(ctx, liveRoomInfo.uid);
1565
+ // 设置开播时间
1566
+ liveTime = liveRoomInfo.live_time;
1567
+ // 开启推送定时器
1568
+ pushAtTimeTimer = ctx.setInterval(pushAtTime, 3600 * 1000);
1569
+ // 定义开播通知语
1570
+ const liveStartMsg = this.config.customLiveStart ? this.config.customLiveStart
1571
+ .replace('-name', masterInfo.username)
1572
+ .replace('-time', await ctx.gi.getTimeDifference(liveTime))
1573
+ .replace('-link', `https://live.bilibili.com/${liveRoomInfo.short_id === 0 ? liveRoomInfo.room_id : liveRoomInfo.short_id}`) : null;
1574
+ // 推送通知卡片
1575
+ await this.sendLiveNotifyCard(ctx, {
1576
+ username: masterInfo.username,
1577
+ userface: masterInfo.userface,
1578
+ target,
1579
+ data: liveRoomInfo
1580
+ }, LiveType.StartBroadcasting, liveStartMsg);
1581
+ }
1582
+ };
1583
+ // 启动直播间弹幕监测
1584
+ ctx.bl.startLiveRoomListener(roomId, handler, pushOnceEveryTenS);
1585
+ }
1290
1586
  subShow() {
1291
1587
  // 在控制台中显示订阅对象
1292
1588
  let table = ``;
@@ -1489,7 +1785,7 @@ class ComRegister {
1489
1785
  // 判断是否订阅直播
1490
1786
  if (sub.live) {
1491
1787
  // 订阅直播
1492
- liveDispose = ctx.setInterval(this.liveDetect(ctx, data.live_room.roomid, sub.target), this.config.liveLoopTime * 1000);
1788
+ this.liveDetectWithListener(ctx, data.live_room.roomid, sub.target);
1493
1789
  }
1494
1790
  }
1495
1791
  // 在B站中订阅该对象
@@ -1637,9 +1933,7 @@ class ComRegister {
1637
1933
  // 直播订阅数+1
1638
1934
  liveSubNum++;
1639
1935
  // 订阅直播,开始循环检测
1640
- const dispose = ctx.setInterval(this.liveDetect(ctx, sub.room_id, target), this.config.liveLoopTime * 1000);
1641
- // 保存销毁函数
1642
- subManagerItem.liveDispose = dispose;
1936
+ this.liveDetectWithListener(ctx, sub.room_id, target);
1643
1937
  }
1644
1938
  }
1645
1939
  // 保存新订阅对象
package/lib/index.d.ts CHANGED
@@ -39,6 +39,7 @@ export interface Config {
39
39
  liveStartAtAll: boolean;
40
40
  restartPush: boolean;
41
41
  pushTime: number;
42
+ danmakuPushTime: number;
42
43
  customLiveStart: string;
43
44
  customLive: string;
44
45
  customLiveEnd: string;
package/lib/index.js CHANGED
@@ -47,6 +47,7 @@ const Database = __importStar(require("./database"));
47
47
  // import Service
48
48
  const generateImg_1 = __importDefault(require("./generateImg"));
49
49
  const biliAPI_1 = __importDefault(require("./biliAPI"));
50
+ const blive_1 = __importDefault(require("./blive"));
50
51
  exports.inject = ['puppeteer', 'database', 'notifier'];
51
52
  exports.name = 'bilibili-notify';
52
53
  let globalConfig;
@@ -138,7 +139,13 @@ exports.Config = koishi_1.Schema.object({
138
139
  .max(12)
139
140
  .step(0.5)
140
141
  .default(1)
141
- .description('设定隔多长时间推送一次直播状态,单位为小时,默认为一小时'),
142
+ .description('设定间隔多长时间推送一次直播状态,单位为小时,默认为一小时'),
143
+ danmakuPushTime: koishi_1.Schema.number()
144
+ .min(0)
145
+ .max(10)
146
+ .step(0.5)
147
+ .default(0.5)
148
+ .description('设定间隔多长时间推送一次弹幕消息,单位为分钟,默认为半分钟'),
142
149
  customLiveStart: koishi_1.Schema.string()
143
150
  .default('-name开播啦 -link')
144
151
  .description('自定义开播提示语,-name代表UP昵称,-link代表直播间链接(如果使用的是QQ官方机器人,请不要使用)。例如-name开播啦,会发送为xxxUP开播啦'),
@@ -322,8 +329,13 @@ class ServerManager extends koishi_1.Service {
322
329
  filter: globalConfig.filter,
323
330
  dynamicDebugMode: globalConfig.dynamicDebugMode
324
331
  });
332
+ // BL = BLive
333
+ const bl = this.ctx.plugin(blive_1.default, {
334
+ danmakuPushTime: globalConfig.danmakuPushTime
335
+ });
325
336
  // 添加服务
326
337
  this.servers.push(ba);
338
+ this.servers.push(bl);
327
339
  this.servers.push(gi);
328
340
  this.servers.push(cr);
329
341
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-bilibili-notify",
3
3
  "description": "Koishi bilibili notify plugin",
4
- "version": "2.0.0",
4
+ "version": "3.0.0-alpha.1",
5
5
  "contributors": [
6
6
  "Akokko <admin@akokko.com>"
7
7
  ],
@@ -29,6 +29,7 @@
29
29
  "dependencies": {
30
30
  "axios": "^1.7.9",
31
31
  "axios-cookiejar-support": "^5.0.5",
32
+ "blive-message-listener": "^0.5.0",
32
33
  "jsdom": "^24.1.3",
33
34
  "luxon": "^3.5.0",
34
35
  "md5": "^2.3.0",
package/readme.md CHANGED
@@ -210,7 +210,9 @@
210
210
  - ver 2.0.0-alpha.21 修复:在某些场景下仍会出现 `2.0.0-alpha.19` 和 `2.0.0-alpha.20` 版本已修复的问题
211
211
  - ver 2.0.0-alpha.22 移除:不需要的服务
212
212
  - ver 2.0.0-alpha.23 优化:将艾特全体成员消息单独发送
213
- - ver 2.0.0 修复:只订阅直播也会将该UP主的动态进行推送、推送过的动态过一段时间又会再次推送
213
+
214
+ - ver 3.0.0-alpha.0 重构:直播 新增:直播弹幕推送到群
215
+ - ver 3.0.0-alpha.1 修复:只订阅直播也会将该UP主的动态进行推送、推送过的动态过一段时间又会再次推送
214
216
 
215
217
  ## 交流群
216
218