@bililive-tools/bilibili-recorder 1.3.0 → 1.5.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/README.md CHANGED
@@ -43,6 +43,7 @@ interface Options {
43
43
  disableProvideCommentsWhenRecording?: boolean; // 禁用弹幕录制
44
44
  saveGiftDanma?: boolean; // 保存礼物弹幕,包含舰长
45
45
  saveSCDanma?: boolean; // 保存SC
46
+ useServerTimestamp?: boolean; // 控制弹幕是否使用服务端时间戳,默认为true
46
47
  saveCover?: boolean; // 保存封面
47
48
  auth?: string; // 登录所需cookie
48
49
  uid?: number; // cookie所有者uid,用于弹幕录制
@@ -51,6 +52,7 @@ interface Options {
51
52
  useM3U8Proxy?: boolean; // 是否使用m3u8代理,由于hls及fmp4存在一个小时超时时间,需自行实现代理避免
52
53
  m3u8ProxyUrl?: string; // 代理链接,文档待补充
53
54
  videoFormat?: "auto"; // 视频格式: "auto", "ts", "mkv" ,auto模式下, 分段使用 "ts",不分段使用 "mp4"
55
+ onlyAudio?: boolean; // 只录制音频,默认为否
54
56
  }
55
57
  ```
56
58
 
@@ -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[];
@@ -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
@@ -5,8 +5,13 @@ declare class DanmaClient extends EventEmitter {
5
5
  private auth;
6
6
  private uid;
7
7
  private retryCount;
8
- constructor(roomId: number, auth: string | undefined, uid: number | undefined);
9
- start(): void;
8
+ private useServerTimestamp;
9
+ constructor(roomId: number, { auth, uid, useServerTimestamp, }: {
10
+ auth: string | undefined;
11
+ uid: number | undefined;
12
+ useServerTimestamp?: boolean;
13
+ });
14
+ start(): Promise<void>;
10
15
  stop(): void;
11
16
  }
12
17
  export default DanmaClient;
package/lib/danma.js CHANGED
@@ -1,18 +1,63 @@
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;
6
47
  auth;
7
48
  uid;
8
49
  retryCount = 10;
9
- constructor(roomId, auth, uid) {
50
+ useServerTimestamp;
51
+ constructor(roomId, { auth, uid, useServerTimestamp, }) {
10
52
  super();
11
53
  this.roomId = roomId;
12
54
  this.auth = auth;
13
55
  this.uid = uid;
56
+ this.useServerTimestamp = useServerTimestamp ?? true;
14
57
  }
15
- start() {
58
+ async start() {
59
+ const info = await getCachedBuvidConf();
60
+ const buvid3 = info.data.b_3;
16
61
  const handler = {
17
62
  onIncomeDanmu: (msg) => {
18
63
  let content = msg.body.content;
@@ -21,7 +66,7 @@ class DanmaClient extends EventEmitter {
21
66
  return;
22
67
  const comment = {
23
68
  type: "comment",
24
- timestamp: msg.body.timestamp,
69
+ timestamp: this.useServerTimestamp ? msg.body.timestamp : Date.now(),
25
70
  text: content,
26
71
  color: msg.body.content_color,
27
72
  mode: msg.body.type,
@@ -41,7 +86,7 @@ class DanmaClient extends EventEmitter {
41
86
  const content = msg.body.content.replaceAll(/[\r\n]/g, "");
42
87
  const comment = {
43
88
  type: "super_chat",
44
- timestamp: msg.raw.send_time,
89
+ timestamp: this.useServerTimestamp ? msg.raw.send_time : Date.now(),
45
90
  text: content,
46
91
  price: msg.body.price,
47
92
  sender: {
@@ -59,7 +104,7 @@ class DanmaClient extends EventEmitter {
59
104
  onGuardBuy: (msg) => {
60
105
  const gift = {
61
106
  type: "guard",
62
- timestamp: msg.timestamp,
107
+ timestamp: this.useServerTimestamp ? msg.timestamp : Date.now(),
63
108
  name: msg.body.gift_name,
64
109
  price: msg.body.price,
65
110
  count: 1,
@@ -79,7 +124,7 @@ class DanmaClient extends EventEmitter {
79
124
  onGift: (msg) => {
80
125
  const gift = {
81
126
  type: "give_gift",
82
- timestamp: msg.raw.send_time,
127
+ timestamp: this.useServerTimestamp ? msg?.raw?.data?.timestamp * 1000 : Date.now(),
83
128
  name: msg.body.gift_name,
84
129
  count: msg.body.amount,
85
130
  price: msg.body.coin_type === "silver" ? 0 : msg.body.price / 1000,
@@ -102,10 +147,22 @@ class DanmaClient extends EventEmitter {
102
147
  this.emit("RoomInfoChange", msg);
103
148
  },
104
149
  };
150
+ let lastAuth = "";
151
+ if (this.auth?.includes("buvid3")) {
152
+ lastAuth = this.auth;
153
+ }
154
+ else {
155
+ if (this.auth) {
156
+ lastAuth = `${this.auth}; buvid3=${buvid3}`;
157
+ }
158
+ else {
159
+ lastAuth = `buvid3=${buvid3}`;
160
+ }
161
+ }
105
162
  this.client = startListen(this.roomId, handler, {
106
163
  ws: {
107
164
  headers: {
108
- Cookie: this.auth ?? "",
165
+ Cookie: lastAuth,
109
166
  },
110
167
  uid: this.uid ?? 0,
111
168
  },
package/lib/index.js CHANGED
@@ -19,6 +19,7 @@ function createRecorder(opts) {
19
19
  qualityMaxRetry: opts.qualityRetry ?? 0,
20
20
  qualityRetry: opts.qualityRetry ?? 0,
21
21
  useM3U8Proxy: opts.useM3U8Proxy ?? false,
22
+ useServerTimestamp: opts.useServerTimestamp ?? true,
22
23
  m3u8ProxyUrl: opts.m3u8ProxyUrl,
23
24
  formatName: opts.formatName ?? "auto",
24
25
  codecName: opts.codecName ?? "auto",
@@ -141,6 +142,7 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, isManualStart, b
141
142
  strictQuality: strictQuality,
142
143
  formatName: this.formatName,
143
144
  codecName: this.codecName,
145
+ onlyAudio: this.onlyAudio,
144
146
  });
145
147
  }
146
148
  catch (err) {
@@ -233,7 +235,7 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, isManualStart, b
233
235
  room_id: String(roomId),
234
236
  platform: provider?.id,
235
237
  liveStartTimestamp: liveInfo.startTime?.getTime(),
236
- recordStopTimestamp: Date.now(),
238
+ // recordStopTimestamp: Date.now(),
237
239
  title: title,
238
240
  user_name: owner,
239
241
  });
@@ -251,7 +253,11 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, isManualStart, b
251
253
  }
252
254
  this.emit("progress", progress);
253
255
  });
254
- let danmaClient = new DanmaClient(roomId, this.auth, this.uid);
256
+ let danmaClient = new DanmaClient(roomId, {
257
+ auth: this.auth,
258
+ uid: this.uid,
259
+ useServerTimestamp: this.useServerTimestamp,
260
+ });
255
261
  if (!this.disableProvideCommentsWhenRecording) {
256
262
  danmaClient.on("Message", (msg) => {
257
263
  const extraDataController = recorder.getExtraDataController();
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
@@ -211,6 +211,7 @@ export async function getStream(opts) {
211
211
  cookie: opts.cookie,
212
212
  formatName: opts.formatName,
213
213
  codecName: opts.codecName,
214
+ onlyAudio: opts.onlyAudio,
214
215
  });
215
216
  // console.log(JSON.stringify(liveInfo, null, 2));
216
217
  if (liveInfo.current_qn !== qn && opts.strictQuality) {
@@ -224,6 +225,7 @@ export async function getStream(opts) {
224
225
  cookie: opts.cookie,
225
226
  formatName: opts.formatName,
226
227
  codecName: opts.codecName,
228
+ onlyAudio: opts.onlyAudio,
227
229
  });
228
230
  }
229
231
  let expectSource = liveInfo.sources[0];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bililive-tools/bilibili-recorder",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "description": "bililive-tools bilibili recorder implemention",
5
5
  "main": "./lib/index.js",
6
6
  "type": "module",
@@ -39,7 +39,7 @@
39
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.3.0"
42
+ "@bililive-tools/manager": "^1.4.0"
43
43
  },
44
44
  "scripts": {
45
45
  "build": "tsc",