@bililive-tools/bilibili-recorder 1.16.0 → 1.17.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/danma.js CHANGED
@@ -59,6 +59,12 @@ class DanmaClient extends EventEmitter {
59
59
  const info = await getCachedBuvidConf();
60
60
  const buvid3 = info.data.b_3;
61
61
  const handler = {
62
+ onOpen: () => {
63
+ this.emit("open");
64
+ },
65
+ onClose: () => {
66
+ this.emit("close");
67
+ },
62
68
  onIncomeDanmu: (msg) => {
63
69
  let content = msg.body.content;
64
70
  content = content.replace(/(^\s*)|(\s*$)/g, "").replace(/[\r\n]/g, "");
@@ -168,6 +174,7 @@ class DanmaClient extends EventEmitter {
168
174
  },
169
175
  });
170
176
  this.client.live.on("error", (err) => {
177
+ console.error("DanmaClient error", err);
171
178
  this.retryCount -= 1;
172
179
  if (this.retryCount > 0) {
173
180
  setTimeout(() => {
package/lib/index.js CHANGED
@@ -13,6 +13,7 @@ function createRecorder(opts) {
13
13
  ...mitt(),
14
14
  ...opts,
15
15
  cache: null,
16
+ appendTimeline: null,
16
17
  availableStreams: [],
17
18
  availableSources: [],
18
19
  state: "idle",
@@ -87,10 +88,13 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, isManualStart, b
87
88
  liveStartTime: new Date(),
88
89
  recordStartTime: new Date(),
89
90
  };
90
- this.state = "idle";
91
+ this.emit("stateChange", { state: "idle" });
91
92
  }
92
93
  catch (error) {
93
- this.state = "check-error";
94
+ this.emit("stateChange", {
95
+ state: "check-error",
96
+ msg: `检查失败,` + (error instanceof Error ? error.message : String(error)),
97
+ });
94
98
  throw error;
95
99
  }
96
100
  if (this.liveInfo.liveId === banLiveId) {
@@ -127,10 +131,13 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, isManualStart, b
127
131
  catch (err) {
128
132
  if (qualityRetryLeft > 0)
129
133
  await this.cache.set("qualityRetryLeft", qualityRetryLeft - 1);
130
- this.state = "check-error";
134
+ this.emit("stateChange", {
135
+ state: "check-error",
136
+ msg: `检查失败,` + (err instanceof Error ? err.message : String(err)),
137
+ });
131
138
  throw err;
132
139
  }
133
- this.state = "recording";
140
+ this.emit("stateChange", { state: "recording" });
134
141
  const { streamOptions, currentStream: stream, sources: availableSources, streams: availableStreams, } = res;
135
142
  this.availableStreams = availableStreams.map((s) => s.desc);
136
143
  this.availableSources = availableSources.map((s) => s.name);
@@ -193,6 +200,7 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, isManualStart, b
193
200
  headers: {
194
201
  Referer: "https://live.bilibili.com/",
195
202
  },
203
+ proxy: this.proxy,
196
204
  }, onEnd, async () => {
197
205
  const info = await getInfo(this.channelId);
198
206
  return info;
@@ -216,8 +224,8 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, isManualStart, b
216
224
  });
217
225
  };
218
226
  downloader.on("videoFileCreated", handleVideoCreated);
219
- downloader.on("videoFileCompleted", ({ filename }) => {
220
- this.emit("videoFileCompleted", { filename });
227
+ downloader.on("videoFileCompleted", (data) => {
228
+ this.emit("videoFileCompleted", data);
221
229
  });
222
230
  downloader.on("DebugLog", (data) => {
223
231
  this.emit("DebugLog", data);
@@ -233,34 +241,60 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, isManualStart, b
233
241
  uid: Number(this.uid),
234
242
  useServerTimestamp: this.useServerTimestamp,
235
243
  });
236
- if (!this.disableProvideCommentsWhenRecording) {
237
- danmaClient.on("Message", (msg) => {
238
- const extraDataController = downloader.getExtraDataController();
239
- if (!extraDataController)
240
- return;
241
- if (msg.type === "super_chat" && this.saveSCDanma === false)
242
- return;
243
- if ((msg.type === "give_gift" || msg.type === "guard") && this.saveGiftDanma === false)
244
- return;
245
- this.emit("Message", msg);
246
- extraDataController.addMessage(msg);
247
- });
248
- danmaClient.on("onRoomInfoChange", (msg) => {
249
- if (utils.shouldCheckTitleKeywords(isManualStart, this.titleKeywords)) {
250
- const title = msg?.body?.title ?? "";
251
- const hasTitleKeyword = utils.hasBlockedTitleKeywords(title, this.titleKeywords);
252
- if (hasTitleKeyword) {
253
- this.state = "title-blocked";
254
- this.emit("DebugLog", {
255
- type: "common",
256
- text: `检测到标题包含关键词,停止录制:直播间标题 "${title}" 包含关键词 "${this.titleKeywords}"`,
257
- });
258
- // 停止录制
259
- this.recordHandle && this.recordHandle.stop("直播间标题包含关键词");
260
- }
244
+ // 开启了禁止提供弹幕功能,并且也没有设置标题关键词,才完全禁止连接弹幕服务器,否则都连接弹幕服务器,前者不处理弹幕消息,后者根据标题关键词来判断是否停止录制
245
+ const enableDanmaListen = !this.disableProvideCommentsWhenRecording ||
246
+ utils.shouldCheckTitleKeywords(isManualStart, this.titleKeywords);
247
+ danmaClient.on("Message", (msg) => {
248
+ if (this.disableProvideCommentsWhenRecording)
249
+ return;
250
+ const extraDataController = downloader.getExtraDataController();
251
+ if (!extraDataController)
252
+ return;
253
+ if (msg.type === "super_chat" && this.saveSCDanma === false)
254
+ return;
255
+ if ((msg.type === "give_gift" || msg.type === "guard") && this.saveGiftDanma === false)
256
+ return;
257
+ this.emit("Message", msg);
258
+ extraDataController.addMessage(msg);
259
+ });
260
+ danmaClient.on("onRoomInfoChange", (msg) => {
261
+ if (utils.shouldCheckTitleKeywords(isManualStart, this.titleKeywords)) {
262
+ const title = msg?.body?.title ?? "";
263
+ const hasTitleKeyword = utils.hasBlockedTitleKeywords(title, this.titleKeywords);
264
+ if (hasTitleKeyword) {
265
+ this.emit("stateChange", {
266
+ state: "title-blocked",
267
+ msg: `停止录制,直播间标题 "${title}" 包含关键词 "${this.titleKeywords}"`,
268
+ });
269
+ this.emit("DebugLog", {
270
+ type: "common",
271
+ text: `检测到标题包含关键词,停止录制:直播间标题 "${title}" 包含关键词 "${this.titleKeywords}"`,
272
+ });
273
+ // 停止录制
274
+ this.recordHandle && this.recordHandle.stop("直播间标题包含关键词");
261
275
  }
276
+ }
277
+ });
278
+ danmaClient.on("open", () => {
279
+ this.appendTimeline({
280
+ text: "弹幕连接已建立",
281
+ });
282
+ });
283
+ danmaClient.on("close", () => {
284
+ this.appendTimeline({
285
+ text: "弹幕连接已关闭",
262
286
  });
263
- danmaClient.start();
287
+ });
288
+ if (enableDanmaListen) {
289
+ try {
290
+ danmaClient.start();
291
+ }
292
+ catch (err) {
293
+ this.emit("DebugLog", {
294
+ type: "error",
295
+ text: `弹幕连接失败,错误信息: ${String(err)}`,
296
+ });
297
+ }
264
298
  }
265
299
  const downloaderArgs = downloader.getArguments();
266
300
  downloader.run();
@@ -272,7 +306,7 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, isManualStart, b
272
306
  const stop = utils.singleton(async (reason) => {
273
307
  if (!this.recordHandle)
274
308
  return;
275
- this.state = "stopping-record";
309
+ this.emit("stateChange", { state: "stopping-record" });
276
310
  intervalId && clearInterval(intervalId);
277
311
  try {
278
312
  danmaClient.stop();
@@ -289,7 +323,7 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, isManualStart, b
289
323
  this.emit("RecordStop", { recordHandle: this.recordHandle, reason });
290
324
  this.recordHandle = undefined;
291
325
  this.liveInfo = undefined;
292
- this.state = "idle";
326
+ this.emit("stateChange", { state: "idle" });
293
327
  this.cache.set("qualityRetryLeft", this.qualityRetry);
294
328
  });
295
329
  this.recordHandle = {
package/lib/stream.js CHANGED
@@ -75,6 +75,7 @@ export async function getInfo(channelId) {
75
75
  }
76
76
  async function getLiveInfo(roomIdOrShortId, opts) {
77
77
  const res = await getRoomPlayInfo(roomIdOrShortId, opts);
78
+ assert(res.playurl_info, "没有找到流");
78
79
  // https://github.com/FFmpeg/FFmpeg/commit/b76053d8bf322b197a9d07bd27bbdad14fd5bc15
79
80
  let conditons = [
80
81
  {
@@ -208,10 +209,6 @@ async function getLiveInfo(roomIdOrShortId, opts) {
208
209
  }
209
210
  export async function getStream(opts) {
210
211
  const roomId = Number(opts.channelId);
211
- const roomInit = await getRoomInit(roomId);
212
- if (roomInit.live_status !== 1) {
213
- throw new Error("It must be called getStream when living");
214
- }
215
212
  const qn = BiliQualities.includes(opts.quality) ? opts.quality : 10000;
216
213
  let liveInfo = await getLiveInfo(roomId, {
217
214
  qn: qn,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bililive-tools/bilibili-recorder",
3
- "version": "1.16.0",
3
+ "version": "1.17.0",
4
4
  "description": "bililive-tools bilibili recorder implemention",
5
5
  "main": "./lib/index.js",
6
6
  "type": "module",
@@ -34,12 +34,12 @@
34
34
  "author": "renmu123",
35
35
  "license": "LGPL",
36
36
  "dependencies": {
37
- "blive-message-listener": "^0.5.2",
37
+ "blive-message-listener": "^0.5.5",
38
38
  "mitt": "^3.0.1",
39
39
  "tiny-bilibili-ws": "^1.0.2",
40
40
  "lodash-es": "^4.17.21",
41
41
  "axios": "^1.15.0",
42
- "@bililive-tools/manager": "^1.16.0"
42
+ "@bililive-tools/manager": "^1.17.0"
43
43
  },
44
44
  "scripts": {
45
45
  "build": "tsc",
@@ -1 +0,0 @@
1
- export function listenAll(instance: any, roomId: any, handler: any): void;
@@ -1,821 +0,0 @@
1
- // src/parser/HEARTBEAT.ts
2
- var parser = (data) => {
3
- return {
4
- attention: data,
5
- };
6
- };
7
- var HEARTBEAT = {
8
- parser,
9
- eventName: "heartbeat",
10
- handlerName: "onAttentionChange",
11
- };
12
- // src/parser/LIVE.ts
13
- var parser2 = (data) => {
14
- return {
15
- live_platform: data.live_platform,
16
- room_id: data.roomid,
17
- };
18
- };
19
- var LIVE = {
20
- parser: parser2,
21
- eventName: "LIVE",
22
- handlerName: "onLiveStart",
23
- };
24
- // src/parser/PREPARING.ts
25
- var parser3 = (data) => {
26
- return {
27
- room_id: parseInt(data.roomid),
28
- };
29
- };
30
- var PREPARING = {
31
- parser: parser3,
32
- eventName: "PREPARING",
33
- handlerName: "onLiveEnd",
34
- };
35
- // src/parser/ANCHOR_LOT_AWARD.ts
36
- var parser4 = (data, roomId) => {
37
- const rawData = data.data;
38
- return {
39
- id: rawData.id,
40
- award: {
41
- image: rawData.award_image,
42
- name: rawData.award_name,
43
- virtual: rawData.award_type === 1,
44
- },
45
- winner: rawData.award_users.map((user) => ({
46
- uid: user.uid,
47
- uname: user.uname,
48
- face: user.face,
49
- level: user.level,
50
- num: user.num,
51
- })),
52
- };
53
- };
54
- var ANCHOR_LOT_AWARD = {
55
- parser: parser4,
56
- eventName: "ANCHOR_LOT_AWARD",
57
- handlerName: "onAnchorLotteryEnd",
58
- };
59
- // src/parser/ANCHOR_LOT_START.ts
60
- var parser5 = (data, roomId) => {
61
- const rawData = data.data;
62
- return {
63
- id: rawData.id,
64
- start_time: rawData.current_time,
65
- duration: rawData.max_time,
66
- award: {
67
- image: rawData.award_image,
68
- name: rawData.award_name,
69
- num: rawData.award_num,
70
- virtual: rawData.award_type === 1,
71
- price_text: rawData.award_price_text || "",
72
- },
73
- require: {
74
- danmu: rawData.danmu || "",
75
- gift: rawData.gift_id
76
- ? {
77
- id: `${rawData.gift_id}`,
78
- name: rawData.gift_name,
79
- num: rawData.gift_num,
80
- price: rawData.gift_price,
81
- }
82
- : null,
83
- user: rawData.require_type
84
- ? {
85
- type: ["follow", "medal", "guard"][rawData.require_type - 1],
86
- value: rawData.require_value,
87
- text: rawData.require_text,
88
- }
89
- : null,
90
- },
91
- };
92
- };
93
- var ANCHOR_LOT_START = {
94
- parser: parser5,
95
- eventName: "ANCHOR_LOT_START",
96
- handlerName: "onAnchorLotteryStart",
97
- };
98
- // src/utils/color.ts
99
- var intToColorHex = (int) => {
100
- const hex = int.toString(16);
101
- return `#${hex.padStart(6, "0")}`;
102
- };
103
- // src/parser/DANMU_MSG.ts
104
- var parser6 = (data, roomId) => {
105
- const rawData = data.info;
106
- const content = rawData?.[1];
107
- if (!content)
108
- return;
109
- const shouldParseInMessageEmoticon = /\[.*?\]/.test(content);
110
- let inMessageEmoticon;
111
- if (shouldParseInMessageEmoticon) {
112
- const messageExtraInfo = JSON.parse(rawData[0][15].extra);
113
- const emoctionDict = {};
114
- if (messageExtraInfo.emots) {
115
- inMessageEmoticon = Object.keys(messageExtraInfo.emots).reduce((acc, key) => {
116
- const emoticon = messageExtraInfo.emots[key];
117
- acc[key] = {
118
- id: emoticon.emoticon_unique,
119
- emoticon_id: emoticon.emoticon_id,
120
- height: emoticon.height,
121
- width: emoticon.width,
122
- url: emoticon.url,
123
- description: emoticon.descript,
124
- };
125
- return acc;
126
- }, emoctionDict);
127
- }
128
- }
129
- return {
130
- user: {
131
- uid: rawData[2][0],
132
- uname: rawData[2][1],
133
- badge: rawData[3].length
134
- ? {
135
- active: rawData[3][7] !== 12632256,
136
- name: rawData[3][1],
137
- level: rawData[3][0],
138
- color: intToColorHex(rawData[3][4]),
139
- gradient: [
140
- intToColorHex(rawData[3][7]),
141
- intToColorHex(rawData[3][8]),
142
- intToColorHex(rawData[3][9]),
143
- ],
144
- anchor: {
145
- uid: rawData[3][12],
146
- uname: rawData[3][2],
147
- room_id: rawData[3][3],
148
- is_same_room: rawData[3][3] === roomId,
149
- },
150
- }
151
- : void 0,
152
- identity: {
153
- rank: rawData[4][4],
154
- guard_level: rawData[7],
155
- room_admin: rawData[2][2] === 1,
156
- },
157
- },
158
- content,
159
- type: rawData[0][1],
160
- content_color: intToColorHex(rawData[0][3]),
161
- timestamp: rawData[0][4],
162
- lottery: rawData[0][9] !== 0,
163
- emoticon: rawData[0][13]?.emoticon_unique
164
- ? {
165
- id: rawData[0][13].emoticon_unique,
166
- height: rawData[0][13].height,
167
- width: rawData[0][13].width,
168
- url: rawData[0][13].url,
169
- }
170
- : void 0,
171
- in_message_emoticon: inMessageEmoticon,
172
- };
173
- };
174
- var DANMU_MSG = {
175
- parser: parser6,
176
- eventName: "DANMU_MSG",
177
- handlerName: "onIncomeDanmu",
178
- };
179
- // src/parser/GUARD_BUY.ts
180
- var parser7 = (data) => {
181
- const rawData = data.data;
182
- return {
183
- user: {
184
- uid: rawData.uid,
185
- uname: rawData.username,
186
- },
187
- gift_id: rawData.gift_id,
188
- gift_name: rawData.gift_name,
189
- guard_level: rawData.guard_level,
190
- price: rawData.price,
191
- start_time: rawData.start_time,
192
- end_time: rawData.end_time,
193
- };
194
- };
195
- var GUARD_BUY = {
196
- parser: parser7,
197
- eventName: "GUARD_BUY",
198
- handlerName: "onGuardBuy",
199
- };
200
- // src/parser/INTERACT_WORD_ENTRY_EFFECT.ts
201
- var parserNormal = (data, roomId) => {
202
- const rawData = data.data;
203
- let actionType = "unknown";
204
- if (rawData.msg_type === 1) {
205
- actionType = "enter";
206
- }
207
- else if (rawData.msg_type === 2) {
208
- actionType = "follow";
209
- }
210
- else if (rawData.msg_type === 3) {
211
- actionType = "share";
212
- }
213
- return {
214
- user: {
215
- uid: rawData.uid,
216
- uname: rawData.uname,
217
- face: rawData?.face,
218
- badge: rawData.fans_medal?.target_id
219
- ? {
220
- active: !!rawData.fans_medal?.is_lighted,
221
- name: rawData.fans_medal?.medal_name,
222
- level: rawData.fans_medal?.medal_level,
223
- color: intToColorHex(rawData.fans_medal?.medal_color),
224
- gradient: [
225
- intToColorHex(rawData.fans_medal?.medal_color_start),
226
- intToColorHex(rawData.fans_medal?.medal_color_start),
227
- intToColorHex(rawData.fans_medal?.medal_color_end),
228
- ],
229
- anchor: {
230
- uid: rawData.fans_medal?.target_id,
231
- uname: "",
232
- room_id: rawData.fans_medal?.anchor_roomid,
233
- is_same_room: rawData.fans_medal?.anchor_roomid === roomId,
234
- },
235
- }
236
- : void 0,
237
- identity: {
238
- rank: 0,
239
- guard_level: rawData.privilege_type,
240
- room_admin: false,
241
- },
242
- },
243
- action: actionType,
244
- timestamp: Math.ceil(rawData.trigger_time / 1e6),
245
- };
246
- };
247
- var parserGuard = (data, roomId) => {
248
- const rawData = data.data;
249
- const uname = /<%(.*)%>/.exec(rawData.copy_writing)?.[1] || "";
250
- return {
251
- user: {
252
- uid: rawData.uid,
253
- uname,
254
- // 超长会有省略号
255
- identity: {
256
- rank: 0,
257
- guard_level: rawData.privilege_type,
258
- room_admin: false,
259
- },
260
- },
261
- action: "enter",
262
- timestamp: Math.ceil(rawData.trigger_time / 1e6),
263
- };
264
- };
265
- var parserLike = (data, roomId) => {
266
- const rawData = data.data;
267
- return {
268
- user: {
269
- uid: rawData.uid,
270
- uname: rawData.uname,
271
- badge: rawData.fans_medal?.target_id
272
- ? {
273
- active: rawData.fans_medal?.is_lighted,
274
- name: rawData.fans_medal?.medal_name,
275
- level: rawData.fans_medal?.medal_level,
276
- color: intToColorHex(rawData.fans_medal?.medal_color),
277
- gradient: [
278
- intToColorHex(rawData.fans_medal?.medal_color_start),
279
- intToColorHex(rawData.fans_medal?.medal_color_start),
280
- intToColorHex(rawData.fans_medal?.medal_color_end),
281
- ],
282
- anchor: {
283
- uid: rawData.fans_medal?.target_id,
284
- uname: "",
285
- room_id: rawData.fans_medal?.anchor_roomid,
286
- // 返回为 0
287
- is_same_room: rawData.fans_medal?.anchor_roomid === roomId,
288
- },
289
- }
290
- : void 0,
291
- },
292
- action: "like",
293
- timestamp: Date.now(),
294
- };
295
- };
296
- var parser8 = (data, roomId) => {
297
- const msgType = data.cmd;
298
- if (msgType === "ENTRY_EFFECT") {
299
- return parserGuard(data, roomId);
300
- }
301
- if (msgType === "LIKE_INFO_V3_CLICK") {
302
- return parserLike(data, roomId);
303
- }
304
- return parserNormal(data, roomId);
305
- };
306
- var INTERACT_WORD = {
307
- parser: parser8,
308
- eventName: "INTERACT_WORD",
309
- handlerName: "onUserAction",
310
- };
311
- var ENTRY_EFFECT = {
312
- parser: parser8,
313
- eventName: "ENTRY_EFFECT",
314
- handlerName: "onUserAction",
315
- };
316
- var LIKE_INFO_V3_CLICK = {
317
- parser: parser8,
318
- eventName: "LIKE_INFO_V3_CLICK",
319
- handlerName: "onUserAction",
320
- };
321
- // src/parser/LIKE_INFO_V3_UPDATE.ts
322
- var parser9 = (data) => {
323
- const rawData = data.data;
324
- return {
325
- count: rawData.click_count,
326
- };
327
- };
328
- var LIKE_INFO_V3_UPDATE = {
329
- parser: parser9,
330
- eventName: "LIKE_INFO_V3_UPDATE",
331
- handlerName: "onLikedChange",
332
- };
333
- // src/parser/ONLINE_RANK_COUNT.ts
334
- var parser10 = (data) => {
335
- const rawData = data.data;
336
- return {
337
- count: rawData.count,
338
- };
339
- };
340
- var ONLINE_RANK_COUNT = {
341
- parser: parser10,
342
- eventName: "ONLINE_RANK_COUNT",
343
- handlerName: "onRankCountChange",
344
- };
345
- // src/parser/POPULARITY_RED_POCKET_START.ts
346
- var parser11 = (data, roomId) => {
347
- const rawData = data.data;
348
- return {
349
- id: rawData.lot_id,
350
- user: {
351
- uid: rawData.sender_uid,
352
- uname: rawData.sender_name,
353
- face: rawData.sender_face,
354
- },
355
- start_time: rawData.start_time,
356
- end_time: rawData.end_time,
357
- duration: rawData.last_time,
358
- danmu: rawData.danmu,
359
- awards: rawData.awards,
360
- total_price: rawData.total_price,
361
- wait_num: rawData.wait_num,
362
- };
363
- };
364
- var POPULARITY_RED_POCKET_START = {
365
- parser: parser11,
366
- eventName: "POPULARITY_RED_POCKET_START",
367
- handlerName: "onRedPocketStart",
368
- };
369
- // src/parser/POPULARITY_RED_POCKET_WINNER_LIST.ts
370
- var parser12 = (data, roomId) => {
371
- const rawData = data.data;
372
- return {
373
- id: rawData.lot_id,
374
- total_num: rawData.total_num,
375
- winner: rawData.winner_info.map((item) => ({
376
- uid: item[0],
377
- uname: item[1],
378
- award_id: item[3],
379
- ...rawData.awards[item[3]],
380
- })),
381
- awards: rawData.awards,
382
- };
383
- };
384
- var POPULARITY_RED_POCKET_WINNER_LIST = {
385
- parser: parser12,
386
- eventName: "POPULARITY_RED_POCKET_WINNER_LIST",
387
- handlerName: "onRedPocketEnd",
388
- };
389
- // src/parser/ROOM_ADMIN.ts
390
- var parser13 = (data, roomId) => {
391
- const msgType = data.cmd;
392
- const rawData = data;
393
- return {
394
- type: msgType === "room_admin_entrance" ? "set" : "revoke",
395
- uid: rawData.uid,
396
- };
397
- };
398
- var room_admin_entrance = {
399
- parser: parser13,
400
- eventName: "room_admin_entrance",
401
- handlerName: "onRoomAdminSet",
402
- };
403
- var ROOM_ADMIN_REVOKE = {
404
- parser: parser13,
405
- eventName: "ROOM_ADMIN_REVOKE",
406
- handlerName: "onRoomAdminSet",
407
- };
408
- // src/parser/ROOM_CHANGE.ts
409
- var parser14 = (data) => {
410
- const rawData = data.data;
411
- return {
412
- title: rawData.title,
413
- parent_area_id: rawData.parent_area_id,
414
- parent_area_name: rawData.parent_area_name,
415
- area_id: rawData.area_id,
416
- area_name: rawData.area_name,
417
- };
418
- };
419
- var ROOM_CHANGE = {
420
- parser: parser14,
421
- eventName: "ROOM_CHANGE",
422
- handlerName: "onRoomInfoChange",
423
- };
424
- // src/parser/ROOM_SILENT.ts
425
- var parser15 = (data, roomId) => {
426
- const msgType = data.cmd;
427
- const rawData = data.data;
428
- return {
429
- type: msgType === "ROOM_SILENT_OFF" ? "off" : rawData.type,
430
- level: rawData.level,
431
- second: rawData.second,
432
- };
433
- };
434
- var ROOM_SILENT_ON = {
435
- parser: parser15,
436
- eventName: "ROOM_SILENT_ON",
437
- handlerName: "onRoomSilent",
438
- };
439
- var ROOM_SILENT_OFF = {
440
- parser: parser15,
441
- eventName: "ROOM_SILENT_OFF",
442
- handlerName: "onRoomSilent",
443
- };
444
- // src/parser/SEND_GIFT.ts
445
- var parser16 = (data) => {
446
- const rawData = data.data;
447
- return {
448
- user: {
449
- uid: rawData.uid,
450
- uname: rawData.uname,
451
- face: rawData.face,
452
- badge: rawData.medal_info?.medal_level
453
- ? {
454
- active: rawData.medal_info.is_lighted === 1,
455
- name: rawData.medal_info.medal_name,
456
- level: rawData.medal_info.medal_level,
457
- color: intToColorHex(rawData.medal_info.medal_color),
458
- gradient: [
459
- intToColorHex(rawData.medal_info.medal_color_start),
460
- intToColorHex(rawData.medal_info.medal_color_start),
461
- intToColorHex(rawData.medal_info.medal_color_end),
462
- ],
463
- anchor: {
464
- uid: rawData.medal_info.target_id,
465
- uname: rawData.medal_info.anchor_uname,
466
- // maybe ''
467
- room_id: rawData.medal_info.anchor_roomid,
468
- // maybe 0
469
- },
470
- }
471
- : void 0,
472
- identity: {
473
- rank: 0,
474
- guard_level: rawData.guard_level,
475
- room_admin: false,
476
- },
477
- },
478
- gift_id: rawData.giftId,
479
- gift_name: rawData.giftName,
480
- coin_type: rawData.coin_type,
481
- price: rawData.price,
482
- amount: rawData.num,
483
- send_master: rawData.send_master?.uid
484
- ? {
485
- uid: rawData.send_master.uid,
486
- uname: rawData.send_master.uname,
487
- room_id: rawData.send_master.room_id,
488
- }
489
- : void 0,
490
- // 礼物连击:
491
- // data.combo_send 仅首次连击不为空;data.batch_combo_send 速度过快时可能为空;data.batch_combo_id 常驻存在
492
- combo: rawData.batch_combo_id
493
- ? {
494
- batch_id: rawData.batch_combo_id,
495
- combo_num: rawData.super_batch_gift_num,
496
- total_price: rawData.combo_total_coin,
497
- }
498
- : void 0,
499
- };
500
- };
501
- var SEND_GIFT = {
502
- parser: parser16,
503
- eventName: "SEND_GIFT",
504
- handlerName: "onGift",
505
- };
506
- // src/parser/SUPER_CHAT_MESSAGE.ts
507
- var parser17 = (data, roomId) => {
508
- const rawData = data.data;
509
- const { medal_info, user_info } = data.data;
510
- return {
511
- id: rawData.id,
512
- user: {
513
- uid: rawData.uid,
514
- uname: rawData.user_info.uname,
515
- badge: medal_info
516
- ? {
517
- active: medal_info.is_lighted === 1,
518
- name: medal_info.medal_name,
519
- level: medal_info.medal_level,
520
- color: medal_info.medal_color,
521
- anchor: {
522
- uid: medal_info.target_id,
523
- uname: medal_info.anchor_uname,
524
- room_id: medal_info.anchor_roomid,
525
- is_same_room: medal_info.anchor_roomid === roomId,
526
- },
527
- }
528
- : void 0,
529
- identity: {
530
- rank: 0,
531
- guard_level: user_info.guard_level || 0,
532
- room_admin: user_info.manager === 1,
533
- },
534
- },
535
- content: rawData.message,
536
- content_color: rawData.background_price_color,
537
- price: rawData.price,
538
- time: rawData.time,
539
- };
540
- };
541
- var SUPER_CHAT_MESSAGE = {
542
- parser: parser17,
543
- eventName: "SUPER_CHAT_MESSAGE",
544
- handlerName: "onIncomeSuperChat",
545
- };
546
- // src/parser/WARNING_CUT_OFF.ts
547
- var parser18 = (data, roomId) => {
548
- const msgType = data.cmd;
549
- const rawData = data;
550
- return {
551
- type: msgType === "WARNING" ? "warning" : "cut",
552
- msg: rawData.msg,
553
- };
554
- };
555
- var WARNING = {
556
- parser: parser18,
557
- eventName: "WARNING",
558
- handlerName: "onRoomWarn",
559
- };
560
- var CUT_OFF = {
561
- parser: parser18,
562
- eventName: "CUT_OFF",
563
- handlerName: "onRoomWarn",
564
- };
565
- // src/parser/WATCHED_CHANGE.ts
566
- var parser19 = (data) => {
567
- const rawData = data.data;
568
- return {
569
- num: rawData.num,
570
- text_small: rawData.text_small,
571
- };
572
- };
573
- var WATCHED_CHANGE = {
574
- parser: parser19,
575
- eventName: "WATCHED_CHANGE",
576
- handlerName: "onWatchedChange",
577
- };
578
- // src/utils/message.ts
579
- var normalizeDanmu = (msgType, body, rawBody) => {
580
- const timestamp = Date.now();
581
- const randomText = Math.floor(Math.random() * 1e4).toString();
582
- const id = `${timestamp}:${msgType}:${body.user?.uid}:${randomText}`;
583
- return {
584
- id,
585
- timestamp,
586
- type: msgType,
587
- body,
588
- raw: rawBody,
589
- };
590
- };
591
- // src/listener/index.ts
592
- var listenAll = (instance, roomId, handler) => {
593
- if (!handler)
594
- return;
595
- const rawHandler = handler.raw || {};
596
- const rawHandlerNames = new Set(Object.keys(rawHandler));
597
- const isHandleRaw = rawHandlerNames.size > 0;
598
- if (handler.onOpen) {
599
- instance.on("open", () => {
600
- handler.onOpen?.();
601
- });
602
- }
603
- if (handler.onClose) {
604
- instance.on("close", () => {
605
- handler.onClose?.();
606
- });
607
- }
608
- if (handler.onStartListen) {
609
- instance.on("live", () => {
610
- handler.onStartListen?.();
611
- });
612
- }
613
- if (handler[HEARTBEAT.handlerName] || rawHandlerNames.has(HEARTBEAT.eventName)) {
614
- rawHandlerNames.delete(HEARTBEAT.eventName);
615
- instance.on(HEARTBEAT.eventName, (data) => {
616
- isHandleRaw && rawHandler[HEARTBEAT.eventName]?.(data);
617
- const parsedData = HEARTBEAT.parser(data);
618
- handler[HEARTBEAT.handlerName]?.(normalizeDanmu(HEARTBEAT.eventName, parsedData, data));
619
- });
620
- }
621
- if (handler[LIVE.handlerName] || rawHandlerNames.has(LIVE.eventName)) {
622
- rawHandlerNames.delete(LIVE.eventName);
623
- instance.on(LIVE.eventName, (data) => {
624
- isHandleRaw && rawHandler[LIVE.eventName]?.(data.data);
625
- const parsedData = LIVE.parser(data.data);
626
- handler[LIVE.handlerName]?.(normalizeDanmu(LIVE.eventName, parsedData, data.data));
627
- });
628
- }
629
- if (handler[PREPARING.handlerName] || rawHandlerNames.has(PREPARING.eventName)) {
630
- rawHandlerNames.delete(LIVE.eventName);
631
- instance.on(PREPARING.eventName, (data) => {
632
- isHandleRaw && rawHandler[PREPARING.eventName]?.(data.data);
633
- const parsedData = PREPARING.parser(data.data);
634
- handler[PREPARING.handlerName]?.(normalizeDanmu(PREPARING.eventName, parsedData, data.data));
635
- });
636
- }
637
- if (handler[ANCHOR_LOT_AWARD.handlerName] || rawHandlerNames.has(ANCHOR_LOT_AWARD.eventName)) {
638
- rawHandlerNames.delete(ANCHOR_LOT_AWARD.eventName);
639
- instance.on(ANCHOR_LOT_AWARD.eventName, (data) => {
640
- isHandleRaw && rawHandler[ANCHOR_LOT_AWARD.eventName]?.(data.data);
641
- const parsedData = ANCHOR_LOT_AWARD.parser(data.data, roomId);
642
- handler[ANCHOR_LOT_AWARD.handlerName]?.(normalizeDanmu(ANCHOR_LOT_AWARD.eventName, parsedData, data.data));
643
- });
644
- }
645
- if (handler[ANCHOR_LOT_START.handlerName] || rawHandlerNames.has(ANCHOR_LOT_START.eventName)) {
646
- rawHandlerNames.delete(ANCHOR_LOT_START.eventName);
647
- instance.on(ANCHOR_LOT_START.eventName, (data) => {
648
- isHandleRaw && rawHandler[ANCHOR_LOT_START.eventName]?.(data.data);
649
- const parsedData = ANCHOR_LOT_START.parser(data.data, roomId);
650
- handler[ANCHOR_LOT_START.handlerName]?.(normalizeDanmu(ANCHOR_LOT_START.eventName, parsedData, data.data));
651
- });
652
- }
653
- if (handler[DANMU_MSG.handlerName] || rawHandlerNames.has(DANMU_MSG.eventName)) {
654
- rawHandlerNames.delete(DANMU_MSG.eventName);
655
- instance.on(DANMU_MSG.eventName, (data) => {
656
- isHandleRaw && rawHandler[DANMU_MSG.eventName]?.(data.data);
657
- const parsedData = DANMU_MSG.parser(data.data, roomId);
658
- if (parsedData) {
659
- handler[DANMU_MSG.handlerName]?.(normalizeDanmu(DANMU_MSG.eventName, parsedData, data.data));
660
- }
661
- });
662
- }
663
- if (handler[GUARD_BUY.handlerName] || rawHandlerNames.has(GUARD_BUY.eventName)) {
664
- rawHandlerNames.delete(GUARD_BUY.eventName);
665
- instance.on(GUARD_BUY.eventName, (data) => {
666
- isHandleRaw && rawHandler[GUARD_BUY.eventName]?.(data.data);
667
- const parsedData = GUARD_BUY.parser(data.data);
668
- handler[GUARD_BUY.handlerName]?.(normalizeDanmu(GUARD_BUY.eventName, parsedData, data.data));
669
- });
670
- }
671
- if (handler[INTERACT_WORD.handlerName] ||
672
- handler[ENTRY_EFFECT.handlerName] ||
673
- handler[LIKE_INFO_V3_CLICK.handlerName] ||
674
- rawHandlerNames.has(INTERACT_WORD.eventName) ||
675
- rawHandlerNames.has(ENTRY_EFFECT.eventName) ||
676
- rawHandlerNames.has(LIKE_INFO_V3_CLICK.eventName)) {
677
- rawHandlerNames.delete(INTERACT_WORD.eventName);
678
- rawHandlerNames.delete(ENTRY_EFFECT.eventName);
679
- rawHandlerNames.delete(LIKE_INFO_V3_CLICK.eventName);
680
- instance.on(INTERACT_WORD.eventName, (data) => {
681
- isHandleRaw && rawHandler[INTERACT_WORD.eventName]?.(data.data);
682
- const parsedData = INTERACT_WORD.parser(data.data, roomId);
683
- handler[INTERACT_WORD.handlerName]?.(normalizeDanmu(INTERACT_WORD.eventName, parsedData, data.data));
684
- });
685
- instance.on(ENTRY_EFFECT.eventName, (data) => {
686
- isHandleRaw && rawHandler[ENTRY_EFFECT.eventName]?.(data.data);
687
- const parsedData = ENTRY_EFFECT.parser(data.data, roomId);
688
- handler[ENTRY_EFFECT.handlerName]?.(normalizeDanmu(ENTRY_EFFECT.eventName, parsedData, data.data));
689
- });
690
- instance.on(LIKE_INFO_V3_CLICK.eventName, (data) => {
691
- isHandleRaw && rawHandler[LIKE_INFO_V3_CLICK.eventName]?.(data.data);
692
- const parsedData = LIKE_INFO_V3_CLICK.parser(data.data, roomId);
693
- handler[LIKE_INFO_V3_CLICK.handlerName]?.(normalizeDanmu(LIKE_INFO_V3_CLICK.eventName, parsedData, data.data));
694
- });
695
- }
696
- if (handler[LIKE_INFO_V3_UPDATE.handlerName] ||
697
- rawHandlerNames.has(LIKE_INFO_V3_UPDATE.eventName)) {
698
- rawHandlerNames.delete(LIKE_INFO_V3_UPDATE.eventName);
699
- instance.on(LIKE_INFO_V3_UPDATE.eventName, (data) => {
700
- isHandleRaw && rawHandler[LIKE_INFO_V3_UPDATE.eventName]?.(data.data);
701
- const parsedData = LIKE_INFO_V3_UPDATE.parser(data.data);
702
- handler[LIKE_INFO_V3_UPDATE.handlerName]?.(normalizeDanmu(LIKE_INFO_V3_UPDATE.eventName, parsedData, data.data));
703
- });
704
- }
705
- if (handler[ONLINE_RANK_COUNT.handlerName] || rawHandlerNames.has(ONLINE_RANK_COUNT.eventName)) {
706
- rawHandlerNames.delete(ONLINE_RANK_COUNT.eventName);
707
- instance.on(ONLINE_RANK_COUNT.eventName, (data) => {
708
- isHandleRaw && rawHandler[ONLINE_RANK_COUNT.eventName]?.(data.data);
709
- const parsedData = ONLINE_RANK_COUNT.parser(data.data);
710
- handler[ONLINE_RANK_COUNT.handlerName]?.(normalizeDanmu(ONLINE_RANK_COUNT.eventName, parsedData, data.data));
711
- });
712
- }
713
- if (handler[POPULARITY_RED_POCKET_START.handlerName] ||
714
- rawHandlerNames.has(POPULARITY_RED_POCKET_START.eventName)) {
715
- rawHandlerNames.delete(POPULARITY_RED_POCKET_START.eventName);
716
- instance.on(POPULARITY_RED_POCKET_START.eventName, (data) => {
717
- isHandleRaw && rawHandler[POPULARITY_RED_POCKET_START.eventName]?.(data.data);
718
- const parsedData = POPULARITY_RED_POCKET_START.parser(data.data, roomId);
719
- handler[POPULARITY_RED_POCKET_START.handlerName]?.(normalizeDanmu(POPULARITY_RED_POCKET_START.eventName, parsedData, data.data));
720
- });
721
- }
722
- if (handler[POPULARITY_RED_POCKET_WINNER_LIST.handlerName] ||
723
- rawHandlerNames.has(POPULARITY_RED_POCKET_WINNER_LIST.eventName)) {
724
- rawHandlerNames.delete(POPULARITY_RED_POCKET_WINNER_LIST.eventName);
725
- instance.on(POPULARITY_RED_POCKET_WINNER_LIST.eventName, (data) => {
726
- isHandleRaw && rawHandler[POPULARITY_RED_POCKET_WINNER_LIST.eventName]?.(data.data);
727
- const parsedData = POPULARITY_RED_POCKET_WINNER_LIST.parser(data.data, roomId);
728
- handler[POPULARITY_RED_POCKET_WINNER_LIST.handlerName]?.(normalizeDanmu(POPULARITY_RED_POCKET_WINNER_LIST.eventName, parsedData, data.data));
729
- });
730
- }
731
- if (handler[room_admin_entrance.handlerName] ||
732
- handler[ROOM_ADMIN_REVOKE.handlerName] ||
733
- rawHandlerNames.has(room_admin_entrance.eventName) ||
734
- rawHandlerNames.has(ROOM_SILENT_OFF.eventName)) {
735
- rawHandlerNames.delete(room_admin_entrance.eventName);
736
- rawHandlerNames.delete(ROOM_ADMIN_REVOKE.eventName);
737
- instance.on(room_admin_entrance.eventName, (data) => {
738
- isHandleRaw && rawHandler[room_admin_entrance.eventName]?.(data.data);
739
- const parsedData = room_admin_entrance.parser(data.data, roomId);
740
- handler[room_admin_entrance.handlerName]?.(normalizeDanmu(room_admin_entrance.eventName, parsedData, data.data));
741
- });
742
- instance.on(ROOM_ADMIN_REVOKE.eventName, (data) => {
743
- isHandleRaw && rawHandler[ROOM_ADMIN_REVOKE.eventName]?.(data.data);
744
- const parsedData = ROOM_ADMIN_REVOKE.parser(data.data, roomId);
745
- handler[ROOM_ADMIN_REVOKE.handlerName]?.(normalizeDanmu(ROOM_ADMIN_REVOKE.eventName, parsedData, data.data));
746
- });
747
- }
748
- if (handler[ROOM_CHANGE.handlerName] || rawHandlerNames.has(ROOM_CHANGE.eventName)) {
749
- rawHandlerNames.delete(ROOM_CHANGE.eventName);
750
- instance.on(ROOM_CHANGE.eventName, (data) => {
751
- isHandleRaw && rawHandler[ROOM_CHANGE.eventName]?.(data.data);
752
- const parsedData = ROOM_CHANGE.parser(data.data);
753
- handler[ROOM_CHANGE.handlerName]?.(normalizeDanmu(ROOM_CHANGE.eventName, parsedData, data.data));
754
- });
755
- }
756
- if (handler[ROOM_SILENT_ON.handlerName] ||
757
- handler[ROOM_SILENT_OFF.handlerName] ||
758
- rawHandlerNames.has(ROOM_SILENT_ON.eventName) ||
759
- rawHandlerNames.has(ROOM_SILENT_OFF.eventName)) {
760
- rawHandlerNames.delete(ROOM_SILENT_ON.eventName);
761
- rawHandlerNames.delete(ROOM_SILENT_OFF.eventName);
762
- instance.on(ROOM_SILENT_ON.eventName, (data) => {
763
- isHandleRaw && rawHandler[ROOM_SILENT_ON.eventName]?.(data.data);
764
- const parsedData = ROOM_SILENT_ON.parser(data.data, roomId);
765
- handler[ROOM_SILENT_ON.handlerName]?.(normalizeDanmu(ROOM_SILENT_ON.eventName, parsedData, data.data));
766
- });
767
- instance.on(ROOM_SILENT_OFF.eventName, (data) => {
768
- isHandleRaw && rawHandler[ROOM_SILENT_OFF.eventName]?.(data.data);
769
- const parsedData = ROOM_SILENT_OFF.parser(data.data, roomId);
770
- handler[ROOM_SILENT_OFF.handlerName]?.(normalizeDanmu(ROOM_SILENT_OFF.eventName, parsedData, data.data));
771
- });
772
- }
773
- if (handler[SEND_GIFT.handlerName] || rawHandlerNames.has(SEND_GIFT.eventName)) {
774
- rawHandlerNames.delete(SEND_GIFT.eventName);
775
- instance.on(SEND_GIFT.eventName, (data) => {
776
- isHandleRaw && rawHandler[SEND_GIFT.eventName]?.(data.data);
777
- const parsedData = SEND_GIFT.parser(data.data);
778
- handler[SEND_GIFT.handlerName]?.(normalizeDanmu(SEND_GIFT.eventName, parsedData, data.data));
779
- });
780
- }
781
- if (handler[SUPER_CHAT_MESSAGE.handlerName] ||
782
- rawHandlerNames.has(SUPER_CHAT_MESSAGE.eventName)) {
783
- rawHandlerNames.delete(SUPER_CHAT_MESSAGE.eventName);
784
- instance.on(SUPER_CHAT_MESSAGE.eventName, (data) => {
785
- isHandleRaw && rawHandler[SUPER_CHAT_MESSAGE.eventName]?.(data.data);
786
- const parsedData = SUPER_CHAT_MESSAGE.parser(data.data, roomId);
787
- handler[SUPER_CHAT_MESSAGE.handlerName]?.(normalizeDanmu(SUPER_CHAT_MESSAGE.eventName, parsedData, data.data));
788
- });
789
- }
790
- if (handler[WARNING.handlerName] ||
791
- handler[CUT_OFF.handlerName] ||
792
- rawHandlerNames.has(WARNING.eventName) ||
793
- rawHandlerNames.has(CUT_OFF.eventName)) {
794
- rawHandlerNames.delete(WARNING.eventName);
795
- rawHandlerNames.delete(CUT_OFF.eventName);
796
- instance.on(WARNING.eventName, (data) => {
797
- isHandleRaw && rawHandler[WARNING.eventName]?.(data.data);
798
- const parsedData = WARNING.parser(data.data, roomId);
799
- handler[WARNING.handlerName]?.(normalizeDanmu(WARNING.eventName, parsedData, data.data));
800
- });
801
- instance.on(CUT_OFF.eventName, (data) => {
802
- isHandleRaw && rawHandler[CUT_OFF.eventName]?.(data.data);
803
- const parsedData = CUT_OFF.parser(data.data, roomId);
804
- handler[CUT_OFF.handlerName]?.(normalizeDanmu(CUT_OFF.eventName, parsedData, data.data));
805
- });
806
- }
807
- if (handler[WATCHED_CHANGE.handlerName] || rawHandlerNames.has(WATCHED_CHANGE.eventName)) {
808
- rawHandlerNames.delete(WATCHED_CHANGE.eventName);
809
- instance.on(WATCHED_CHANGE.eventName, (data) => {
810
- isHandleRaw && rawHandler[WATCHED_CHANGE.eventName]?.(data.data);
811
- const parsedData = WATCHED_CHANGE.parser(data.data);
812
- handler[WATCHED_CHANGE.handlerName]?.(normalizeDanmu(WATCHED_CHANGE.eventName, parsedData, data.data));
813
- });
814
- }
815
- for (const eventName of rawHandlerNames) {
816
- instance.on(eventName, (data) => {
817
- rawHandler[eventName]?.(data.data);
818
- });
819
- }
820
- };
821
- export { listenAll };
@@ -1,14 +0,0 @@
1
- export var GuardLevel: any;
2
- export function startListen(roomId: any, handler: any, options: any): {
3
- live: KeepLiveTCP<object>;
4
- roomId: number;
5
- online: number;
6
- closed: boolean;
7
- close: () => boolean;
8
- getAttention: () => Promise<number>;
9
- getOnline: () => Promise<number>;
10
- reconnect: () => void;
11
- heartbeat: () => void;
12
- send: (op: any, data: any) => void;
13
- };
14
- import { KeepLiveTCP } from "tiny-bilibili-ws";
@@ -1,30 +0,0 @@
1
- import { listenAll } from "./chunk-5NTNTWA4.js";
2
- // src/index.ts
3
- import { KeepLiveTCP } from "tiny-bilibili-ws";
4
- // src/types/const.ts
5
- var GuardLevel = /* @__PURE__ */ ((GuardLevel2) => {
6
- GuardLevel2[GuardLevel2["None"] = 0] = "None";
7
- GuardLevel2[GuardLevel2["Zongdu"] = 1] = "Zongdu";
8
- GuardLevel2[GuardLevel2["Tidu"] = 2] = "Tidu";
9
- GuardLevel2[GuardLevel2["Jianzhang"] = 3] = "Jianzhang";
10
- return GuardLevel2;
11
- })(GuardLevel || {});
12
- // src/index.ts
13
- var startListen = (roomId, handler, options) => {
14
- const live = new KeepLiveTCP(roomId, options?.ws);
15
- listenAll(live, roomId, handler);
16
- const listenerInstance = {
17
- live,
18
- roomId: live.roomId,
19
- online: live.online,
20
- closed: live.closed,
21
- close: () => live.close(),
22
- getAttention: () => live.getOnline(),
23
- getOnline: () => live.getOnline(),
24
- reconnect: () => live.reconnect(),
25
- heartbeat: () => live.heartbeat(),
26
- send: (op, data) => live.send(op, data)
27
- };
28
- return listenerInstance;
29
- };
30
- export { GuardLevel, startListen };