@bililive-tools/bilibili-recorder 1.2.0 → 1.4.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/README.md +1 -0
- package/lib/bilibili_api.d.ts +3 -2
- package/lib/bilibili_api.js +12 -0
- package/lib/danma.d.ts +1 -1
- package/lib/danma.js +63 -5
- package/lib/index.js +66 -9
- package/lib/stream.d.ts +1 -0
- package/lib/stream.js +16 -3
- package/lib/utils.d.ts +1 -0
- package/lib/utils.js +8 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -51,6 +51,7 @@ interface Options {
|
|
|
51
51
|
useM3U8Proxy?: boolean; // 是否使用m3u8代理,由于hls及fmp4存在一个小时超时时间,需自行实现代理避免
|
|
52
52
|
m3u8ProxyUrl?: string; // 代理链接,文档待补充
|
|
53
53
|
videoFormat?: "auto"; // 视频格式: "auto", "ts", "mkv" ,auto模式下, 分段使用 "ts",不分段使用 "mp4"
|
|
54
|
+
onlyAudio?: boolean; // 只录制音频,默认为否
|
|
54
55
|
}
|
|
55
56
|
```
|
|
56
57
|
|
package/lib/bilibili_api.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type LiveStatus = 0 | 1 | 2;
|
|
1
|
+
export type LiveStatus = 0 | 1 | 2;
|
|
2
2
|
export declare function getRoomInit(roomIdOrShortId: number): Promise<{
|
|
3
3
|
room_id: number;
|
|
4
4
|
short_id: number;
|
|
@@ -71,6 +71,7 @@ export declare function getPlayURL(roomId: number, opts?: {
|
|
|
71
71
|
export declare function getRoomPlayInfo(roomIdOrShortId: number, opts?: {
|
|
72
72
|
qn?: number;
|
|
73
73
|
cookie?: string;
|
|
74
|
+
onlyAudio?: boolean;
|
|
74
75
|
}): Promise<{
|
|
75
76
|
uid: number;
|
|
76
77
|
room_id: number;
|
|
@@ -85,6 +86,7 @@ export declare function getRoomPlayInfo(roomIdOrShortId: number, opts?: {
|
|
|
85
86
|
};
|
|
86
87
|
};
|
|
87
88
|
}>;
|
|
89
|
+
export declare function getBuvidConf(): Promise<any>;
|
|
88
90
|
export interface ProtocolInfo {
|
|
89
91
|
protocol_name: "http_stream" | "http_hls";
|
|
90
92
|
format: FormatInfo[];
|
|
@@ -110,4 +112,3 @@ export interface SourceProfile {
|
|
|
110
112
|
extra: string;
|
|
111
113
|
stream_ttl: number;
|
|
112
114
|
}
|
|
113
|
-
export {};
|
package/lib/bilibili_api.js
CHANGED
|
@@ -61,6 +61,7 @@ export async function getRoomPlayInfo(roomIdOrShortId, opts = {}) {
|
|
|
61
61
|
codec: "0,1",
|
|
62
62
|
// 0 flv, 1 ts, 2 fmp4
|
|
63
63
|
format: "0,1,2",
|
|
64
|
+
only_audio: opts.onlyAudio ? "1" : "0",
|
|
64
65
|
},
|
|
65
66
|
headers: {
|
|
66
67
|
Cookie: opts.cookie,
|
|
@@ -69,3 +70,14 @@ export async function getRoomPlayInfo(roomIdOrShortId, opts = {}) {
|
|
|
69
70
|
assert(res.data.code === 0, `Unexpected resp, code ${res.data.code}, msg ${res.data.message}`);
|
|
70
71
|
return res.data.data;
|
|
71
72
|
}
|
|
73
|
+
export async function getBuvidConf() {
|
|
74
|
+
const res = await fetch("https://api.bilibili.com/x/frontend/finger/spi", {
|
|
75
|
+
headers: {
|
|
76
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
if (!res.ok)
|
|
80
|
+
throw new Error(`Failed to get buvid conf: ${res.statusText}`);
|
|
81
|
+
const data = await res.json();
|
|
82
|
+
return data;
|
|
83
|
+
}
|
package/lib/danma.d.ts
CHANGED
package/lib/danma.js
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
2
|
import { startListen } from "./blive-message-listener/index.js";
|
|
3
|
+
import { getBuvidConf } from "./bilibili_api.js";
|
|
4
|
+
// 全局缓存,一天过期时间 (24 * 60 * 60 * 1000 ms)
|
|
5
|
+
const CACHE_DURATION = 24 * 60 * 60 * 1000;
|
|
6
|
+
let buvidCache = null;
|
|
7
|
+
// 获取带缓存的 buvid 配置
|
|
8
|
+
async function getCachedBuvidConf() {
|
|
9
|
+
const now = Date.now();
|
|
10
|
+
// 检查缓存是否有效
|
|
11
|
+
if (buvidCache && now - buvidCache.timestamp < CACHE_DURATION) {
|
|
12
|
+
return buvidCache.data;
|
|
13
|
+
}
|
|
14
|
+
// 缓存失效或不存在,重新获取(带重试)
|
|
15
|
+
const info = await getBuvidConfWithRetry();
|
|
16
|
+
buvidCache = {
|
|
17
|
+
data: info,
|
|
18
|
+
timestamp: now,
|
|
19
|
+
};
|
|
20
|
+
return info;
|
|
21
|
+
}
|
|
22
|
+
// 带重试功能的 getBuvidConf
|
|
23
|
+
async function getBuvidConfWithRetry(maxRetries = 3, retryDelay = 1000) {
|
|
24
|
+
let lastError;
|
|
25
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
26
|
+
try {
|
|
27
|
+
const result = await getBuvidConf();
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
lastError = error;
|
|
32
|
+
// 如果是最后一次尝试,直接抛出错误
|
|
33
|
+
if (attempt === maxRetries) {
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
// 等待指定时间后重试,使用指数退避策略
|
|
37
|
+
const delay = retryDelay * Math.pow(2, attempt - 1);
|
|
38
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// 这里不应该到达,但为了类型安全
|
|
42
|
+
throw lastError;
|
|
43
|
+
}
|
|
3
44
|
class DanmaClient extends EventEmitter {
|
|
4
45
|
client = null;
|
|
5
46
|
roomId;
|
|
@@ -12,7 +53,9 @@ class DanmaClient extends EventEmitter {
|
|
|
12
53
|
this.auth = auth;
|
|
13
54
|
this.uid = uid;
|
|
14
55
|
}
|
|
15
|
-
start() {
|
|
56
|
+
async start() {
|
|
57
|
+
const info = await getCachedBuvidConf();
|
|
58
|
+
const buvid3 = info.data.b_3;
|
|
16
59
|
const handler = {
|
|
17
60
|
onIncomeDanmu: (msg) => {
|
|
18
61
|
let content = msg.body.content;
|
|
@@ -21,7 +64,7 @@ class DanmaClient extends EventEmitter {
|
|
|
21
64
|
return;
|
|
22
65
|
const comment = {
|
|
23
66
|
type: "comment",
|
|
24
|
-
timestamp: msg.timestamp,
|
|
67
|
+
timestamp: msg.body.timestamp,
|
|
25
68
|
text: content,
|
|
26
69
|
color: msg.body.content_color,
|
|
27
70
|
mode: msg.body.type,
|
|
@@ -41,7 +84,7 @@ class DanmaClient extends EventEmitter {
|
|
|
41
84
|
const content = msg.body.content.replaceAll(/[\r\n]/g, "");
|
|
42
85
|
const comment = {
|
|
43
86
|
type: "super_chat",
|
|
44
|
-
timestamp: msg.
|
|
87
|
+
timestamp: msg.raw.send_time,
|
|
45
88
|
text: content,
|
|
46
89
|
price: msg.body.price,
|
|
47
90
|
sender: {
|
|
@@ -79,7 +122,7 @@ class DanmaClient extends EventEmitter {
|
|
|
79
122
|
onGift: (msg) => {
|
|
80
123
|
const gift = {
|
|
81
124
|
type: "give_gift",
|
|
82
|
-
timestamp: msg.
|
|
125
|
+
timestamp: msg.raw.send_time,
|
|
83
126
|
name: msg.body.gift_name,
|
|
84
127
|
count: msg.body.amount,
|
|
85
128
|
price: msg.body.coin_type === "silver" ? 0 : msg.body.price / 1000,
|
|
@@ -98,11 +141,26 @@ class DanmaClient extends EventEmitter {
|
|
|
98
141
|
};
|
|
99
142
|
this.emit("Message", gift);
|
|
100
143
|
},
|
|
144
|
+
onRoomInfoChange: (msg) => {
|
|
145
|
+
this.emit("RoomInfoChange", msg);
|
|
146
|
+
},
|
|
101
147
|
};
|
|
148
|
+
let lastAuth = "";
|
|
149
|
+
if (this.auth?.includes("buvid3")) {
|
|
150
|
+
lastAuth = this.auth;
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
if (this.auth) {
|
|
154
|
+
lastAuth = `${this.auth}; buvid3=${buvid3}`;
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
lastAuth = `buvid3=${buvid3}`;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
102
160
|
this.client = startListen(this.roomId, handler, {
|
|
103
161
|
ws: {
|
|
104
162
|
headers: {
|
|
105
|
-
Cookie:
|
|
163
|
+
Cookie: lastAuth,
|
|
106
164
|
},
|
|
107
165
|
uid: this.uid ?? 0,
|
|
108
166
|
},
|
package/lib/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import mitt from "mitt";
|
|
3
3
|
import { defaultFromJSON, defaultToJSON, genRecorderUUID, genRecordUUID, utils, FFMPEGRecorder, } from "@bililive-tools/manager";
|
|
4
4
|
import { getInfo, getStream, getLiveStatus, getStrictStream } from "./stream.js";
|
|
5
|
-
import { ensureFolderExist } from "./utils.js";
|
|
5
|
+
import { ensureFolderExist, hasKeyword } from "./utils.js";
|
|
6
6
|
import DanmaClient from "./danma.js";
|
|
7
7
|
function createRecorder(opts) {
|
|
8
8
|
// 内部实现时,应该只有 proxy 包裹的那一层会使用这个 recorder 标识符,不应该有直接通过
|
|
@@ -67,7 +67,7 @@ const ffmpegOutputOptions = [
|
|
|
67
67
|
"-movflags",
|
|
68
68
|
"faststart+frag_keyframe+empty_moov",
|
|
69
69
|
"-min_frag_duration",
|
|
70
|
-
"
|
|
70
|
+
"10000000",
|
|
71
71
|
];
|
|
72
72
|
const ffmpegInputOptions = [
|
|
73
73
|
"-reconnect",
|
|
@@ -103,7 +103,21 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, isManualStart, b
|
|
|
103
103
|
return null;
|
|
104
104
|
if (!living)
|
|
105
105
|
return null;
|
|
106
|
-
|
|
106
|
+
// 检查标题是否包含关键词,如果包含则不自动录制
|
|
107
|
+
// 手动开始录制时不检查标题关键词
|
|
108
|
+
if (!isManualStart &&
|
|
109
|
+
this.titleKeywords &&
|
|
110
|
+
typeof this.titleKeywords === "string" &&
|
|
111
|
+
this.titleKeywords.trim()) {
|
|
112
|
+
const hasTitleKeyword = hasKeyword(_title, this.titleKeywords);
|
|
113
|
+
if (hasTitleKeyword) {
|
|
114
|
+
this.emit("DebugLog", {
|
|
115
|
+
type: "common",
|
|
116
|
+
text: `跳过录制:直播间标题 "${_title}" 包含关键词 "${this.titleKeywords}"`,
|
|
117
|
+
});
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
107
121
|
const liveInfo = await getInfo(this.channelId);
|
|
108
122
|
const { owner, title, roomId } = liveInfo;
|
|
109
123
|
this.liveInfo = liveInfo;
|
|
@@ -127,6 +141,7 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, isManualStart, b
|
|
|
127
141
|
strictQuality: strictQuality,
|
|
128
142
|
formatName: this.formatName,
|
|
129
143
|
codecName: this.codecName,
|
|
144
|
+
onlyAudio: this.onlyAudio,
|
|
130
145
|
});
|
|
131
146
|
}
|
|
132
147
|
catch (err) {
|
|
@@ -165,8 +180,13 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, isManualStart, b
|
|
|
165
180
|
});
|
|
166
181
|
}, 50 * 60 * 1000);
|
|
167
182
|
}
|
|
183
|
+
let isCutting = false;
|
|
168
184
|
let isEnded = false;
|
|
169
185
|
const onEnd = (...args) => {
|
|
186
|
+
if (isCutting) {
|
|
187
|
+
isCutting = false;
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
170
190
|
if (isEnded)
|
|
171
191
|
return;
|
|
172
192
|
isEnded = true;
|
|
@@ -182,11 +202,14 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, isManualStart, b
|
|
|
182
202
|
outputOptions: ffmpegOutputOptions,
|
|
183
203
|
inputOptions: ffmpegInputOptions,
|
|
184
204
|
segment: this.segment ?? 0,
|
|
185
|
-
getSavePath: (opts) => getSavePath({ owner, title, startTime: opts.startTime }),
|
|
205
|
+
getSavePath: (opts) => getSavePath({ owner, title: opts.title ?? title, startTime: opts.startTime }),
|
|
186
206
|
isHls: streamOptions.protocol_name === "http_hls",
|
|
187
207
|
disableDanma: this.disableProvideCommentsWhenRecording,
|
|
188
208
|
videoFormat: this.videoFormat,
|
|
189
|
-
}, onEnd)
|
|
209
|
+
}, onEnd, async () => {
|
|
210
|
+
const info = await getInfo(this.channelId);
|
|
211
|
+
return info;
|
|
212
|
+
});
|
|
190
213
|
const savePath = getSavePath({
|
|
191
214
|
owner,
|
|
192
215
|
title,
|
|
@@ -198,14 +221,20 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, isManualStart, b
|
|
|
198
221
|
this.state = "idle";
|
|
199
222
|
throw err;
|
|
200
223
|
}
|
|
201
|
-
const handleVideoCreated = async ({ filename }) => {
|
|
202
|
-
this.emit("videoFileCreated", { filename });
|
|
224
|
+
const handleVideoCreated = async ({ filename, title, cover }) => {
|
|
225
|
+
this.emit("videoFileCreated", { filename, cover });
|
|
226
|
+
if (title && this?.liveInfo) {
|
|
227
|
+
this.liveInfo.title = title;
|
|
228
|
+
}
|
|
229
|
+
if (cover && this?.liveInfo) {
|
|
230
|
+
this.liveInfo.cover = cover;
|
|
231
|
+
}
|
|
203
232
|
const extraDataController = recorder.getExtraDataController();
|
|
204
233
|
extraDataController?.setMeta({
|
|
205
234
|
room_id: String(roomId),
|
|
206
235
|
platform: provider?.id,
|
|
207
236
|
liveStartTimestamp: liveInfo.startTime?.getTime(),
|
|
208
|
-
recordStopTimestamp: Date.now(),
|
|
237
|
+
// recordStopTimestamp: Date.now(),
|
|
209
238
|
title: title,
|
|
210
239
|
user_name: owner,
|
|
211
240
|
});
|
|
@@ -225,7 +254,7 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, isManualStart, b
|
|
|
225
254
|
});
|
|
226
255
|
let danmaClient = new DanmaClient(roomId, this.auth, this.uid);
|
|
227
256
|
if (!this.disableProvideCommentsWhenRecording) {
|
|
228
|
-
danmaClient
|
|
257
|
+
danmaClient.on("Message", (msg) => {
|
|
229
258
|
const extraDataController = recorder.getExtraDataController();
|
|
230
259
|
if (!extraDataController)
|
|
231
260
|
return;
|
|
@@ -236,10 +265,37 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, isManualStart, b
|
|
|
236
265
|
this.emit("Message", msg);
|
|
237
266
|
extraDataController.addMessage(msg);
|
|
238
267
|
});
|
|
268
|
+
danmaClient.on("onRoomInfoChange", (msg) => {
|
|
269
|
+
if (!isManualStart &&
|
|
270
|
+
this.titleKeywords &&
|
|
271
|
+
typeof this.titleKeywords === "string" &&
|
|
272
|
+
this.titleKeywords.trim()) {
|
|
273
|
+
const title = msg?.body?.title ?? "";
|
|
274
|
+
const hasTitleKeyword = hasKeyword(title, this.titleKeywords);
|
|
275
|
+
if (hasTitleKeyword) {
|
|
276
|
+
this.emit("DebugLog", {
|
|
277
|
+
type: "common",
|
|
278
|
+
text: `检测到标题包含关键词,停止录制:直播间标题 "${title}" 包含关键词 "${this.titleKeywords}"`,
|
|
279
|
+
});
|
|
280
|
+
// 停止录制
|
|
281
|
+
this.recordHandle && this.recordHandle.stop("直播间标题包含关键词");
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
});
|
|
239
285
|
danmaClient.start();
|
|
240
286
|
}
|
|
241
287
|
const ffmpegArgs = recorder.getArguments();
|
|
242
288
|
recorder.run();
|
|
289
|
+
const cut = utils.singleton(async () => {
|
|
290
|
+
if (!this.recordHandle)
|
|
291
|
+
return;
|
|
292
|
+
if (isCutting)
|
|
293
|
+
return;
|
|
294
|
+
isCutting = true;
|
|
295
|
+
await recorder.stop();
|
|
296
|
+
recorder.createCommand();
|
|
297
|
+
recorder.run();
|
|
298
|
+
});
|
|
243
299
|
const stop = utils.singleton(async (reason) => {
|
|
244
300
|
if (!this.recordHandle)
|
|
245
301
|
return;
|
|
@@ -271,6 +327,7 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, isManualStart, b
|
|
|
271
327
|
ffmpegArgs,
|
|
272
328
|
savePath: savePath,
|
|
273
329
|
stop,
|
|
330
|
+
cut,
|
|
274
331
|
};
|
|
275
332
|
this.emit("RecordStart", this.recordHandle);
|
|
276
333
|
return this.recordHandle;
|
package/lib/stream.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export declare function getStream(opts: Pick<Recorder, "channelId" | "quality">
|
|
|
30
30
|
strictQuality?: boolean;
|
|
31
31
|
formatName: RecorderCreateOpts["formatName"];
|
|
32
32
|
codecName: RecorderCreateOpts["codecName"];
|
|
33
|
+
onlyAudio?: boolean;
|
|
33
34
|
}): Promise<{
|
|
34
35
|
currentStream: {
|
|
35
36
|
name: string;
|
package/lib/stream.js
CHANGED
|
@@ -155,10 +155,17 @@ async function getLiveInfo(roomIdOrShortId, opts) {
|
|
|
155
155
|
let streamInfo;
|
|
156
156
|
let streamOptions;
|
|
157
157
|
for (const condition of conditons) {
|
|
158
|
-
|
|
158
|
+
const streamList = res.playurl_info.playurl.stream
|
|
159
159
|
.find(({ protocol_name }) => protocol_name === condition.protocol_name)
|
|
160
160
|
?.format.find(({ format_name }) => format_name === condition.format_name)
|
|
161
|
-
?.codec.
|
|
161
|
+
?.codec.filter(({ codec_name }) => codec_name === condition.codec_name);
|
|
162
|
+
if (streamList && streamList.length > 1) {
|
|
163
|
+
// 由于录播姬直推hevc时,指定qn,服务端仍会返回其他画质的流,这里需要指定找一下流
|
|
164
|
+
streamInfo = streamList.find((item) => item.current_qn === opts.qn);
|
|
165
|
+
}
|
|
166
|
+
if (!streamInfo) {
|
|
167
|
+
streamInfo = streamList?.[0];
|
|
168
|
+
}
|
|
162
169
|
if (streamInfo) {
|
|
163
170
|
streamOptions = {
|
|
164
171
|
...condition,
|
|
@@ -167,7 +174,11 @@ async function getLiveInfo(roomIdOrShortId, opts) {
|
|
|
167
174
|
break;
|
|
168
175
|
}
|
|
169
176
|
}
|
|
170
|
-
console.log(
|
|
177
|
+
// console.log(
|
|
178
|
+
// "streamOptions",
|
|
179
|
+
// streamOptions,
|
|
180
|
+
// JSON.stringify(res.playurl_info.playurl.stream, null, 2),
|
|
181
|
+
// );
|
|
171
182
|
assert(streamInfo, "没有找到支持的流");
|
|
172
183
|
const streams = streamInfo.accept_qn.map((qn) => {
|
|
173
184
|
const qnDesc = res.playurl_info.playurl.g_qn_desc.find((item) => item.qn === qn);
|
|
@@ -200,6 +211,7 @@ export async function getStream(opts) {
|
|
|
200
211
|
cookie: opts.cookie,
|
|
201
212
|
formatName: opts.formatName,
|
|
202
213
|
codecName: opts.codecName,
|
|
214
|
+
onlyAudio: opts.onlyAudio,
|
|
203
215
|
});
|
|
204
216
|
// console.log(JSON.stringify(liveInfo, null, 2));
|
|
205
217
|
if (liveInfo.current_qn !== qn && opts.strictQuality) {
|
|
@@ -213,6 +225,7 @@ export async function getStream(opts) {
|
|
|
213
225
|
cookie: opts.cookie,
|
|
214
226
|
formatName: opts.formatName,
|
|
215
227
|
codecName: opts.codecName,
|
|
228
|
+
onlyAudio: opts.onlyAudio,
|
|
216
229
|
});
|
|
217
230
|
}
|
|
218
231
|
let expectSource = liveInfo.sources[0];
|
package/lib/utils.d.ts
CHANGED
|
@@ -21,3 +21,4 @@ export declare function assertStringType(data: unknown, msg?: string): asserts d
|
|
|
21
21
|
export declare function assertNumberType(data: unknown, msg?: string): asserts data is number;
|
|
22
22
|
export declare function assertObjectType(data: unknown, msg?: string): asserts data is object;
|
|
23
23
|
export declare function createInvalidStreamChecker(count?: number): (ffmpegLogLine: string) => boolean;
|
|
24
|
+
export declare function hasKeyword(title: string, titleKeywords: string): boolean;
|
package/lib/utils.js
CHANGED
|
@@ -81,3 +81,11 @@ export function createInvalidStreamChecker(count = 10) {
|
|
|
81
81
|
return false;
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
|
+
export function hasKeyword(title, titleKeywords) {
|
|
85
|
+
const keywords = titleKeywords
|
|
86
|
+
.split(",")
|
|
87
|
+
.map((k) => k.trim())
|
|
88
|
+
.filter((k) => k);
|
|
89
|
+
const hasTitleKeyword = keywords.some((keyword) => title.toLowerCase().includes(keyword.toLowerCase()));
|
|
90
|
+
return hasTitleKeyword;
|
|
91
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bililive-tools/bilibili-recorder",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "bililive-tools bilibili recorder implemention",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"blive-message-listener": "^0.5.0",
|
|
38
38
|
"mitt": "^3.0.1",
|
|
39
|
-
"tiny-bilibili-ws": "^1.0.
|
|
39
|
+
"tiny-bilibili-ws": "^1.0.2",
|
|
40
40
|
"lodash-es": "^4.17.21",
|
|
41
41
|
"axios": "^1.7.8",
|
|
42
|
-
"@bililive-tools/manager": "^1.
|
|
42
|
+
"@bililive-tools/manager": "^1.3.0"
|
|
43
43
|
},
|
|
44
44
|
"scripts": {
|
|
45
45
|
"build": "tsc",
|