bililive-cli 3.13.0 → 3.13.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.
@@ -54494,7 +54494,7 @@ async function trash(paths, options) {
54494
54494
  } else if (process$2.platform === 'win32') {
54495
54495
  module = await Promise.resolve().then(function () { return require('./windows-OmnJ7a39.cjs'); });
54496
54496
  } else {
54497
- module = await Promise.resolve().then(function () { return require('./linux-CXmRE85w.cjs'); });
54497
+ module = await Promise.resolve().then(function () { return require('./linux-B6245EX2.cjs'); });
54498
54498
  }
54499
54499
 
54500
54500
  return module.default(paths);
@@ -186234,71 +186234,80 @@ async function getBiliStatusInfoByRoomIds(RoomIds) {
186234
186234
 
186235
186235
  /**
186236
186236
  * XML流式写入控制器,用于实时写入弹幕、礼物等信息到XML文件
186237
- * 相比原有的json方案,这个实现每隔5秒就会写入数据,减少内存占用和数据丢失风险
186237
+ * 相比原有的json方案,这个实现每隔10秒就会写入数据,减少内存占用和数据丢失风险
186238
186238
  */
186239
+ const METADATA_PLACEHOLDER = "<!--METADATA_PLACEHOLDER-->";
186240
+ const XML_FILE_HEADER = `<?xml version="1.0" encoding="utf-8"?>\n<?xml-stylesheet type="text/xsl" href="#s"?>\n<i>\n${METADATA_PLACEHOLDER}\n<RecorderXmlStyle><z:stylesheet version="1.0" id="s" xml:id="s" xmlns:z="http://www.w3.org/1999/XSL/Transform"><z:output method="html"/><z:template match="/"><html><meta name="viewport" content="width=device-width"/><title>弹幕文件 <z:value-of select="/i/metadata/user_name/text()"/></title><style>body{margin:0}h1,h2,p,table{margin-left:5px}table{border-spacing:0}td,th{border:1px solid grey;padding:1px 5px}th{position:sticky;top:0;background:#4098de}tr:hover{background:#d9f4ff}div{overflow:auto;max-height:80vh;max-width:100vw;width:fit-content}</style><h1>弹幕XML文件</h1><p>本文件不支持在 IE 浏览器里预览,请使用 Chrome Firefox Edge 等浏览器。</p><p>文件用法参考文档 <a href="https://rec.danmuji.org/user/danmaku/">https://rec.danmuji.org/user/danmaku/</a></p><table><tr><td>房间号</td><td><z:value-of select="/i/metadata/room_id/text()"/></td></tr><tr><td>主播名</td><td><z:value-of select="/i/metadata/user_name/text()"/></td></tr><tr><td><a href="#d">弹幕</a></td><td>共<z:value-of select="count(/i/d)"/>条记录</td></tr><tr><td><a href="#guard">上船</a></td><td>共<z:value-of select="count(/i/guard)"/>条记录</td></tr><tr><td><a href="#sc">SC</a></td><td>共<z:value-of select="count(/i/sc)"/>条记录</td></tr><tr><td><a href="#gift">礼物</a></td><td>共<z:value-of select="count(/i/gift)"/>条记录</td></tr></table><h2 id="d">弹幕</h2><div id="dm"><table><tr><th>用户名</th><th>出现时间</th><th>用户ID</th><th>弹幕</th><th>参数</th></tr><z:for-each select="/i/d"><tr><td><z:value-of select="@user"/></td><td></td><td></td><td><z:value-of select="."/></td><td><z:value-of select="@p"/></td></tr></z:for-each></table></div><script>Array.from(document.querySelectorAll('#dm tr')).slice(1).map(t=>t.querySelectorAll('td')).forEach(t=>{let p=t[4].textContent.split(','),a=p[0];t[1].textContent=\`\u0024{(Math.floor(a/60/60)+'').padStart(2,0)}:\u0024{(Math.floor(a/60%60)+'').padStart(2,0)}:\u0024{(a%60).toFixed(3).padStart(6,0)}\`;t[2].innerHTML=\`&lt;a target=_blank rel="nofollow noreferrer" "&gt;\u0024{p[6]}&lt;/a&gt;\`})</script><h2 id="guard">舰长购买</h2><div><table><tr><th>用户名</th><th>用户ID</th><th>舰长等级</th><th>购买数量</th><th>出现时间</th></tr><z:for-each select="/i/guard"><tr><td><z:value-of select="@user"/></td><td><a rel="nofollow noreferrer"><z:attribute name="href"><z:text></z:text><z:value-of select="@uid" /></z:attribute><z:value-of select="@uid"/></a></td><td><z:value-of select="@level"/></td><td><z:value-of select="@count"/></td><td><z:value-of select="@ts"/></td></tr></z:for-each></table></div><h2 id="sc">SuperChat 醒目留言</h2><div><table><tr><th>用户名</th><th>用户ID</th><th>内容</th><th>显示时长</th><th>价格</th><th>出现时间</th></tr><z:for-each select="/i/sc"><tr><td><z:value-of select="@user"/></td><td><a rel="nofollow noreferrer"><z:attribute name="href"><z:text></z:text><z:value-of select="@uid" /></z:attribute><z:value-of select="@uid"/></a></td><td><z:value-of select="."/></td><td><z:value-of select="@time"/></td><td><z:value-of select="@price"/></td><td><z:value-of select="@ts"/></td></tr></z:for-each></table></div><h2 id="gift">礼物</h2><div><table><tr><th>用户名</th><th>用户ID</th><th>礼物名</th><th>礼物数量</th><th>出现时间</th></tr><z:for-each select="/i/gift"><tr><td><z:value-of select="@user"/></td><td><span rel="nofollow noreferrer"><z:attribute name="href"></z:attribute><z:value-of select="@uid"/></span></td><td><z:value-of select="@giftname"/></td><td><z:value-of select="@giftcount"/></td><td><z:value-of select="@ts"/></td></tr></z:for-each></table></div></html></z:template></z:stylesheet></RecorderXmlStyle>\n`;
186239
186241
  function createRecordExtraDataController(savePath) {
186240
186242
  const data = {
186243
+ header: XML_FILE_HEADER,
186241
186244
  meta: {
186242
186245
  recordStartTimestamp: Date.now(),
186243
186246
  },
186244
186247
  pendingMessages: [],
186245
186248
  };
186246
186249
  let hasCompleted = false;
186247
- let isWriting = false;
186248
- let isInitialized = false;
186249
- // 初始化文件
186250
- const initializeFile = async () => {
186251
- if (isInitialized)
186252
- return;
186253
- isInitialized = true;
186254
- try {
186255
- // 创建XML文件头,使用占位符预留metadata位置
186256
- const header = `<?xml version="1.0" encoding="utf-8"?>\n<?xml-stylesheet type="text/xsl" href="#s"?>\n<i>\n<!--METADATA_PLACEHOLDER-->\n<RecorderXmlStyle><z:stylesheet version="1.0" id="s" xml:id="s" xmlns:z="http://www.w3.org/1999/XSL/Transform"><z:output method="html"/><z:template match="/"><html><meta name="viewport" content="width=device-width"/><title>弹幕文件 <z:value-of select="/i/metadata/user_name/text()"/></title><style>body{margin:0}h1,h2,p,table{margin-left:5px}table{border-spacing:0}td,th{border:1px solid grey;padding:1px 5px}th{position:sticky;top:0;background:#4098de}tr:hover{background:#d9f4ff}div{overflow:auto;max-height:80vh;max-width:100vw;width:fit-content}</style><h1>弹幕XML文件</h1><p>本文件不支持在 IE 浏览器里预览,请使用 Chrome Firefox Edge 等浏览器。</p><p>文件用法参考文档 <a href="https://rec.danmuji.org/user/danmaku/">https://rec.danmuji.org/user/danmaku/</a></p><table><tr><td>房间号</td><td><z:value-of select="/i/metadata/room_id/text()"/></td></tr><tr><td>主播名</td><td><z:value-of select="/i/metadata/user_name/text()"/></td></tr><tr><td><a href="#d">弹幕</a></td><td>共<z:value-of select="count(/i/d)"/>条记录</td></tr><tr><td><a href="#guard">上船</a></td><td>共<z:value-of select="count(/i/guard)"/>条记录</td></tr><tr><td><a href="#sc">SC</a></td><td>共<z:value-of select="count(/i/sc)"/>条记录</td></tr><tr><td><a href="#gift">礼物</a></td><td>共<z:value-of select="count(/i/gift)"/>条记录</td></tr></table><h2 id="d">弹幕</h2><div id="dm"><table><tr><th>用户名</th><th>出现时间</th><th>用户ID</th><th>弹幕</th><th>参数</th></tr><z:for-each select="/i/d"><tr><td><z:value-of select="@user"/></td><td></td><td></td><td><z:value-of select="."/></td><td><z:value-of select="@p"/></td></tr></z:for-each></table></div><script>Array.from(document.querySelectorAll('#dm tr')).slice(1).map(t=>t.querySelectorAll('td')).forEach(t=>{let p=t[4].textContent.split(','),a=p[0];t[1].textContent=\`\u0024{(Math.floor(a/60/60)+'').padStart(2,0)}:\u0024{(Math.floor(a/60%60)+'').padStart(2,0)}:\u0024{(a%60).toFixed(3).padStart(6,0)}\`;t[2].innerHTML=\`&lt;a target=_blank rel="nofollow noreferrer" "&gt;\u0024{p[6]}&lt;/a&gt;\`})</script><h2 id="guard">舰长购买</h2><div><table><tr><th>用户名</th><th>用户ID</th><th>舰长等级</th><th>购买数量</th><th>出现时间</th></tr><z:for-each select="/i/guard"><tr><td><z:value-of select="@user"/></td><td><a rel="nofollow noreferrer"><z:attribute name="href"><z:text></z:text><z:value-of select="@uid" /></z:attribute><z:value-of select="@uid"/></a></td><td><z:value-of select="@level"/></td><td><z:value-of select="@count"/></td><td><z:value-of select="@ts"/></td></tr></z:for-each></table></div><h2 id="sc">SuperChat 醒目留言</h2><div><table><tr><th>用户名</th><th>用户ID</th><th>内容</th><th>显示时长</th><th>价格</th><th>出现时间</th></tr><z:for-each select="/i/sc"><tr><td><z:value-of select="@user"/></td><td><a rel="nofollow noreferrer"><z:attribute name="href"><z:text></z:text><z:value-of select="@uid" /></z:attribute><z:value-of select="@uid"/></a></td><td><z:value-of select="."/></td><td><z:value-of select="@time"/></td><td><z:value-of select="@price"/></td><td><z:value-of select="@ts"/></td></tr></z:for-each></table></div><h2 id="gift">礼物</h2><div><table><tr><th>用户名</th><th>用户ID</th><th>礼物名</th><th>礼物数量</th><th>出现时间</th></tr><z:for-each select="/i/gift"><tr><td><z:value-of select="@user"/></td><td><span rel="nofollow noreferrer"><z:attribute name="href"></z:attribute><z:value-of select="@uid"/></span></td><td><z:value-of select="@giftname"/></td><td><z:value-of select="@giftcount"/></td><td><z:value-of select="@ts"/></td></tr></z:for-each></table></div></html></z:template></z:stylesheet></RecorderXmlStyle>`;
186257
- await fs$D.promises.writeFile(savePath, header);
186258
- }
186259
- catch (error) {
186260
- console.error("初始化XML文件失败:", error);
186261
- isInitialized = false;
186262
- throw error;
186263
- }
186264
- };
186265
- // 每10秒写入一次数据
186266
- const scheduleWrite = asyncThrottle(() => writeToFile(), 10e3, {
186267
- immediateRunWhenEndOfDefer: true,
186250
+ let hasPersistedHeader = false;
186251
+ let danmaNum = 0;
186252
+ let scNum = 0;
186253
+ let guardNum = 0;
186254
+ const interactedUsers = new Set();
186255
+ const getStats = () => ({
186256
+ danmaNum,
186257
+ uniqMember: interactedUsers.size,
186258
+ scNum,
186259
+ guardNum,
186268
186260
  });
186269
- const writeToFile = async () => {
186270
- if (isWriting || hasCompleted || data.pendingMessages.length === 0) {
186261
+ const trackInteractedUser = (message) => {
186262
+ const userName = message.sender?.name?.trim();
186263
+ if (!userName)
186271
186264
  return;
186265
+ interactedUsers.add(userName);
186266
+ };
186267
+ const initializeFile = async (content) => {
186268
+ // 这里有个假设,那就是第一次保存必然存在metatdata信息
186269
+ const initialContent = data.header.replace(METADATA_PLACEHOLDER, generateMetadataXml(data.meta));
186270
+ await fs$D.promises.writeFile(savePath, initialContent + content);
186271
+ hasPersistedHeader = true;
186272
+ };
186273
+ const writeToFile = async (force = false) => {
186274
+ if (!force && data.pendingMessages.length === 0) {
186275
+ return Promise.resolve();
186272
186276
  }
186273
- // 确保文件已初始化
186274
- await initializeFile();
186275
- isWriting = true;
186277
+ const messagesToWrite = [...data.pendingMessages];
186278
+ data.pendingMessages = [];
186276
186279
  try {
186277
- // 获取待写入的消息
186278
- const messagesToWrite = [...data.pendingMessages];
186279
- data.pendingMessages = [];
186280
- // 生成XML内容
186281
186280
  const xmlContent = generateXmlContent(data.meta, messagesToWrite);
186282
- // 追加写入文件
186283
- await appendToXmlFile(savePath, xmlContent);
186281
+ if (!hasPersistedHeader) {
186282
+ await initializeFile(xmlContent);
186283
+ }
186284
+ else if (xmlContent) {
186285
+ await appendToXmlFile(savePath, xmlContent);
186286
+ }
186284
186287
  }
186285
186288
  catch (error) {
186286
186289
  console.error("写入XML文件失败:", error);
186287
- // 如果写入失败,将消息重新加入队列
186288
- data.pendingMessages = [...data.pendingMessages];
186289
- }
186290
- finally {
186291
- isWriting = false;
186290
+ data.pendingMessages = [...messagesToWrite, ...data.pendingMessages];
186292
186291
  }
186293
186292
  };
186293
+ // 每10秒写入一次数据
186294
+ const writeTimer = setInterval(() => {
186295
+ writeToFile();
186296
+ }, 10e3);
186294
186297
  const addMessage = (message) => {
186295
186298
  if (hasCompleted)
186296
186299
  return;
186297
- // if (!isInitialized) return;
186300
+ if (message.type === "comment") {
186301
+ danmaNum += 1;
186302
+ }
186303
+ else if (message.type === "super_chat") {
186304
+ scNum += 1;
186305
+ }
186306
+ else if (message.type === "guard") {
186307
+ guardNum += 1;
186308
+ }
186309
+ trackInteractedUser(message);
186298
186310
  data.pendingMessages.push(message);
186299
- // 确保文件已初始化
186300
- initializeFile().catch(console.error);
186301
- scheduleWrite();
186302
186311
  };
186303
186312
  const setMeta = async (meta) => {
186304
186313
  if (hasCompleted)
@@ -186307,30 +186316,31 @@ function createRecordExtraDataController(savePath) {
186307
186316
  ...data.meta,
186308
186317
  ...meta,
186309
186318
  };
186310
- // 确保文件已初始化,然后立即更新文件中的metadata
186311
- await initializeFile().catch(console.error);
186312
- await updateMetadataInFile(savePath, data.meta).catch(console.error);
186313
186319
  };
186314
186320
  const flush = async () => {
186315
186321
  if (hasCompleted)
186316
186322
  return;
186317
186323
  hasCompleted = true;
186318
- scheduleWrite.cancel();
186319
- await initializeFile().catch(console.error);
186320
- // 写入剩余的数据
186321
- if (data.pendingMessages.length > 0) {
186322
- await writeToFile();
186323
- }
186324
- // 完成XML文件(添加结束标签等)
186325
- await finalizeXmlFile(savePath);
186326
- // 清理内存
186327
- data.pendingMessages = [];
186324
+ writeTimer && clearInterval(writeTimer);
186325
+ try {
186326
+ await writeToFile(true);
186327
+ await appendToXmlFile(savePath, "</i>");
186328
+ }
186329
+ catch (error) {
186330
+ console.error("完成XML文件写入失败:", error);
186331
+ }
186332
+ finally {
186333
+ // 清理内存
186334
+ data.pendingMessages = [];
186335
+ interactedUsers.clear();
186336
+ }
186328
186337
  };
186329
186338
  return {
186330
186339
  data,
186331
186340
  addMessage,
186332
186341
  setMeta,
186333
186342
  flush,
186343
+ getStats,
186334
186344
  };
186335
186345
  }
186336
186346
  /**
@@ -186346,7 +186356,7 @@ function generateXmlContent(metadata, messages) {
186346
186356
  .filter((item) => item.type === "comment")
186347
186357
  .map((ele) => {
186348
186358
  const progress = Math.max((ele.timestamp - metadata.recordStartTimestamp) / 1000, 0);
186349
- const data = {
186359
+ const attrs = {
186350
186360
  "@@p": "",
186351
186361
  "@@progress": progress,
186352
186362
  "@@mode": String(ele.mode ?? 1),
@@ -186361,18 +186371,18 @@ function generateXmlContent(metadata, messages) {
186361
186371
  "@@uid": String(ele?.sender?.uid),
186362
186372
  "@@timestamp": String(ele.timestamp),
186363
186373
  };
186364
- data["@@p"] = [
186365
- data["@@progress"],
186366
- data["@@mode"],
186367
- data["@@fontsize"],
186368
- data["@@color"],
186369
- data["@@ctime"],
186370
- data["@@pool"],
186371
- data["@@midHash"],
186372
- data["@@uid"],
186373
- data["@@weight"],
186374
+ attrs["@@p"] = [
186375
+ attrs["@@progress"],
186376
+ attrs["@@mode"],
186377
+ attrs["@@fontsize"],
186378
+ attrs["@@color"],
186379
+ attrs["@@ctime"],
186380
+ attrs["@@pool"],
186381
+ attrs["@@midHash"],
186382
+ attrs["@@uid"],
186383
+ attrs["@@weight"],
186374
186384
  ].join(",");
186375
- return pick$1(data, ["@@p", "#text", "@@user", "@@uid", "@@timestamp"]);
186385
+ return pick$1(attrs, ["@@p", "#text", "@@user", "@@uid", "@@timestamp"]);
186376
186386
  });
186377
186387
  const gifts = messages
186378
186388
  .filter((item) => item.type === "give_gift")
@@ -186438,55 +186448,22 @@ async function appendToXmlFile(filePath, content) {
186438
186448
  throw error;
186439
186449
  }
186440
186450
  }
186441
- /**
186442
- * 更新XML文件中的metadata
186443
- */
186444
- async function updateMetadataInFile(filePath, metadata) {
186445
- try {
186446
- const builder = new fxp.XMLBuilder({
186447
- ignoreAttributes: false,
186448
- attributeNamePrefix: "@@",
186449
- format: true,
186450
- });
186451
- // 生成metadata XML
186452
- const metadataXml = builder.build({
186453
- metadata: {
186454
- platform: metadata.platform,
186455
- video_start_time: metadata.recordStartTimestamp,
186456
- live_start_time: metadata.liveStartTimestamp,
186457
- room_title: metadata.title,
186458
- user_name: metadata.user_name,
186459
- room_id: metadata.room_id,
186460
- },
186461
- });
186462
- // 读取文件内容
186463
- const content = await fs$D.promises.readFile(filePath, "utf-8");
186464
- // 替换占位符为实际的metadata
186465
- const updatedContent = content.replace("<!--METADATA_PLACEHOLDER-->", metadataXml);
186466
- // 写回文件
186467
- await fs$D.promises.writeFile(filePath, updatedContent);
186468
- }
186469
- catch (error) {
186470
- console.error(`更新XML文件metadata失败: ${filePath}`, error);
186471
- throw error;
186472
- }
186473
- }
186474
- /**
186475
- * 完成XML文件写入
186476
- */
186477
- async function finalizeXmlFile(filePath) {
186478
- try {
186479
- // 读取文件内容
186480
- const content = await fs$D.promises.readFile(filePath, "utf-8");
186481
- // 添加结束标签
186482
- const finalContent = content + "</i>";
186483
- // 写回文件
186484
- await fs$D.promises.writeFile(filePath, finalContent);
186485
- }
186486
- catch (error) {
186487
- console.error(`完成XML文件写入失败: ${filePath}`, error);
186488
- throw error;
186489
- }
186451
+ function generateMetadataXml(metadata) {
186452
+ const builder = new fxp.XMLBuilder({
186453
+ ignoreAttributes: false,
186454
+ attributeNamePrefix: "@@",
186455
+ format: true,
186456
+ });
186457
+ return builder.build({
186458
+ metadata: {
186459
+ platform: metadata.platform,
186460
+ video_start_time: metadata.recordStartTimestamp,
186461
+ live_start_time: metadata.liveStartTimestamp,
186462
+ room_title: metadata.title,
186463
+ user_name: metadata.user_name,
186464
+ room_id: metadata.room_id,
186465
+ },
186466
+ });
186490
186467
  }
186491
186468
 
186492
186469
  class Segment extends EventEmitter$j {
@@ -186505,6 +186482,12 @@ class Segment extends EventEmitter$j {
186505
186482
  this.disableDanma = disableDanma;
186506
186483
  this.videoExt = videoExt;
186507
186484
  }
186485
+ getVideoFileCompletedPayload() {
186486
+ return {
186487
+ filename: this.outputFilePath,
186488
+ stats: this.extraDataController?.getStats(),
186489
+ };
186490
+ }
186508
186491
  async handleSegmentEnd() {
186509
186492
  if (!this.outputVideoFilePath) {
186510
186493
  this.emit("DebugLog", {
@@ -186513,6 +186496,7 @@ class Segment extends EventEmitter$j {
186513
186496
  });
186514
186497
  return;
186515
186498
  }
186499
+ const data = this.getVideoFileCompletedPayload();
186516
186500
  try {
186517
186501
  this.emit("DebugLog", {
186518
186502
  type: "info",
@@ -186522,7 +186506,7 @@ class Segment extends EventEmitter$j {
186522
186506
  retry$1(() => fs$E.rename(this.rawRecordingVideoPath, this.outputFilePath), 20, 1000),
186523
186507
  this.extraDataController?.flush(),
186524
186508
  ]);
186525
- this.emit("videoFileCompleted", { filename: this.outputFilePath });
186509
+ this.emit("videoFileCompleted", data);
186526
186510
  }
186527
186511
  catch (err) {
186528
186512
  this.emit("DebugLog", {
@@ -186530,7 +186514,7 @@ class Segment extends EventEmitter$j {
186530
186514
  text: "videoFileCompleted error " + String(err),
186531
186515
  });
186532
186516
  // 虽然重命名失败了,但是也当作完成处理,避免卡住录制流程
186533
- this.emit("videoFileCompleted", { filename: this.outputFilePath });
186517
+ this.emit("videoFileCompleted", data);
186534
186518
  }
186535
186519
  }
186536
186520
  async onSegmentStart(stderrLine, callBack) {
@@ -186680,8 +186664,13 @@ class StreamManager extends EventEmitter$j {
186680
186664
  }
186681
186665
  else {
186682
186666
  if (this.recordStartTime) {
186683
- await this.getExtraDataController()?.flush();
186684
- this.emit("videoFileCompleted", { filename: this.videoFilePath });
186667
+ const stats = this.extraDataController?.getStats();
186668
+ const extraDataController = this.getExtraDataController();
186669
+ await extraDataController?.flush();
186670
+ this.emit("videoFileCompleted", {
186671
+ filename: this.videoFilePath,
186672
+ stats: stats,
186673
+ });
186685
186674
  }
186686
186675
  }
186687
186676
  }
@@ -186845,7 +186834,7 @@ function createRecorderManager$1(opts) {
186845
186834
  }
186846
186835
  this.emit("videoFileCreated", { recorder: recorder.toJSON(), filename, rawFilename });
186847
186836
  });
186848
- recorder.on("videoFileCompleted", ({ filename }) => this.emit("videoFileCompleted", { recorder: recorder.toJSON(), filename }));
186837
+ recorder.on("videoFileCompleted", ({ filename, stats }) => this.emit("videoFileCompleted", { recorder: recorder.toJSON(), filename, stats }));
186849
186838
  recorder.on("Message", (message) => this.emit("Message", { recorder: recorder.toJSON(), message }));
186850
186839
  recorder.on("Updated", (keys) => this.emit("RecorderUpdated", { recorder: recorder.toJSON(), keys }));
186851
186840
  recorder.on("DebugLog", (log) => this.emit("RecorderDebugLog", { recorder: recorder, ...log }));
@@ -186990,18 +186979,9 @@ function createRecorderManager$1(opts) {
186990
186979
  }
186991
186980
  }
186992
186981
  else {
186993
- // 检查该 provider 是否还有 recorder
186994
- const hasRecorders = this.recorders.some((r) => r.providerId === providerId);
186995
- if (hasRecorders) {
186996
- // 继续循环
186997
- const timer = setTimeout(checkLoop, providerConfig.autoCheckInterval);
186998
- checkLoopTimers.set(providerId, timer);
186999
- }
187000
- else {
187001
- // 没有 recorder 了,停止该 provider 的检查循环
187002
- // TODO: 也许不需要删除定时器
187003
- checkLoopTimers.delete(providerId);
187004
- }
186982
+ // 即使当前 provider 暂时没有 recorder,也保留轮询,避免后续新增 recorder 时漏掉自动检查。
186983
+ const timer = setTimeout(checkLoop, providerConfig.autoCheckInterval);
186984
+ checkLoopTimers.set(providerId, timer);
187005
186985
  }
187006
186986
  }
187007
186987
  };
@@ -187237,8 +187217,8 @@ class mesioDownloader extends EventEmitter$j {
187237
187217
  this.streamManager.on("videoFileCreated", ({ filename, cover, rawFilename, title }) => {
187238
187218
  this.emit("videoFileCreated", { filename, cover, rawFilename, title });
187239
187219
  });
187240
- this.streamManager.on("videoFileCompleted", ({ filename }) => {
187241
- this.emit("videoFileCompleted", { filename });
187220
+ this.streamManager.on("videoFileCompleted", (data) => {
187221
+ this.emit("videoFileCompleted", data);
187242
187222
  });
187243
187223
  this.streamManager.on("DebugLog", (data) => {
187244
187224
  this.emit("DebugLog", data);
@@ -187440,8 +187420,8 @@ class BililiveDownloader extends EventEmitter$j {
187440
187420
  this.streamManager.on("videoFileCreated", ({ filename, cover, rawFilename, title }) => {
187441
187421
  this.emit("videoFileCreated", { filename, cover, rawFilename, title });
187442
187422
  });
187443
- this.streamManager.on("videoFileCompleted", ({ filename }) => {
187444
- this.emit("videoFileCompleted", { filename });
187423
+ this.streamManager.on("videoFileCompleted", (data) => {
187424
+ this.emit("videoFileCompleted", data);
187445
187425
  });
187446
187426
  this.streamManager.on("DebugLog", (data) => {
187447
187427
  this.emit("DebugLog", data);
@@ -187671,8 +187651,8 @@ class FFmpegDownloader extends EventEmitter$j {
187671
187651
  this.streamManager.on("videoFileCreated", ({ filename, cover, rawFilename, title }) => {
187672
187652
  this.emit("videoFileCreated", { filename, cover, rawFilename, title });
187673
187653
  });
187674
- this.streamManager.on("videoFileCompleted", ({ filename }) => {
187675
- this.emit("videoFileCompleted", { filename });
187654
+ this.streamManager.on("videoFileCompleted", (data) => {
187655
+ this.emit("videoFileCompleted", data);
187676
187656
  });
187677
187657
  this.streamManager.on("DebugLog", (data) => {
187678
187658
  this.emit("DebugLog", data);
@@ -195234,8 +195214,8 @@ const checkLiveStatusAndRecord$4 = async function ({ getSavePath, banLiveId, isM
195234
195214
  });
195235
195215
  };
195236
195216
  downloader.on("videoFileCreated", handleVideoCreated);
195237
- downloader.on("videoFileCompleted", ({ filename }) => {
195238
- this.emit("videoFileCompleted", { filename });
195217
+ downloader.on("videoFileCompleted", (data) => {
195218
+ this.emit("videoFileCompleted", data);
195239
195219
  });
195240
195220
  downloader.on("DebugLog", (data) => {
195241
195221
  this.emit("DebugLog", data);
@@ -211397,8 +211377,8 @@ const checkLiveStatusAndRecord$3 = async function ({ getSavePath, banLiveId, isM
211397
211377
  });
211398
211378
  };
211399
211379
  downloader.on("videoFileCreated", handleVideoCreated);
211400
- downloader.on("videoFileCompleted", ({ filename }) => {
211401
- this.emit("videoFileCompleted", { filename });
211380
+ downloader.on("videoFileCompleted", (data) => {
211381
+ this.emit("videoFileCompleted", data);
211402
211382
  });
211403
211383
  downloader.on("DebugLog", (data) => {
211404
211384
  this.emit("DebugLog", data);
@@ -213080,8 +213060,8 @@ const checkLiveStatusAndRecord$2 = async function ({ getSavePath, isManualStart,
213080
213060
  });
213081
213061
  };
213082
213062
  downloader.on("videoFileCreated", handleVideoCreated);
213083
- downloader.on("videoFileCompleted", ({ filename }) => {
213084
- this.emit("videoFileCompleted", { filename });
213063
+ downloader.on("videoFileCompleted", (data) => {
213064
+ this.emit("videoFileCompleted", data);
213085
213065
  });
213086
213066
  downloader.on("DebugLog", (data) => {
213087
213067
  this.emit("DebugLog", data);
@@ -216690,6 +216670,9 @@ async function getRoomInfoByUserWeb(secUserId, opts = {}) {
216690
216670
  if (res.data.includes("验证码")) {
216691
216671
  throw new Error("需要验证码,请在浏览器中打开链接获取" + url);
216692
216672
  }
216673
+ if (!res.data.includes("抖音号")) {
216674
+ throw new Error("userHTML页面没有正常加载" + String(res.data));
216675
+ }
216693
216676
  if (!res.data.includes("直播中")) {
216694
216677
  return {
216695
216678
  living: false,
@@ -216881,6 +216864,7 @@ async function getRoomInfoByWeb(webRoomId, opts = {}) {
216881
216864
  }
216882
216865
  async function getRoomInfoByMobile(secUserId, opts = {}) {
216883
216866
  if (!secUserId) {
216867
+ console.error(opts);
216884
216868
  throw new Error("Mobile API need secUserId, please set uid field");
216885
216869
  }
216886
216870
  if (typeof secUserId === "number") {
@@ -216897,7 +216881,7 @@ async function getRoomInfoByMobile(secUserId, opts = {}) {
216897
216881
  const res = await requester.get(`https://webcast.amemv.com/webcast/room/reflow/info/`, {
216898
216882
  params,
216899
216883
  headers: {
216900
- cookie: opts.auth,
216884
+ // cookie: opts.auth,
216901
216885
  },
216902
216886
  });
216903
216887
  // @ts-ignore
@@ -249155,8 +249139,8 @@ const checkLiveStatusAndRecord$1 = async function ({ getSavePath, banLiveId, isM
249155
249139
  });
249156
249140
  };
249157
249141
  downloader.on("videoFileCreated", handleVideoCreated);
249158
- downloader.on("videoFileCompleted", ({ filename }) => {
249159
- this.emit("videoFileCompleted", { filename });
249142
+ downloader.on("videoFileCompleted", (data) => {
249143
+ this.emit("videoFileCompleted", data);
249160
249144
  });
249161
249145
  downloader.on("DebugLog", (data) => {
249162
249146
  this.emit("DebugLog", data);
@@ -290470,8 +290454,8 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
290470
290454
  });
290471
290455
  };
290472
290456
  downloader.on("videoFileCreated", handleVideoCreated);
290473
- downloader.on("videoFileCompleted", ({ filename }) => {
290474
- this.emit("videoFileCompleted", { filename });
290457
+ downloader.on("videoFileCompleted", (data) => {
290458
+ this.emit("videoFileCompleted", data);
290475
290459
  });
290476
290460
  downloader.on("DebugLog", (data) => {
290477
290461
  this.emit("DebugLog", data);
@@ -291119,7 +291103,7 @@ async function createRecorderManager(appConfig) {
291119
291103
  return;
291120
291104
  if (recorder.recordHandle) {
291121
291105
  const logFilePath = utils$2.replaceExtName(`${recorder.recordHandle.savePath}_${recorder.id}`, ".recorder.log");
291122
- fs$k.appendFileSync(logFilePath, log.text + "\n");
291106
+ fs$k.appendFile(logFilePath, log.text + "\n").catch(() => { });
291123
291107
  return;
291124
291108
  }
291125
291109
  else {
@@ -291198,8 +291182,8 @@ async function createRecorderManager(appConfig) {
291198
291182
  platform: recorder.providerId,
291199
291183
  });
291200
291184
  });
291201
- manager.on("videoFileCompleted", async ({ recorder, filename }) => {
291202
- logObj.info("Manager videoFileCompleted", { recorder, filename });
291185
+ manager.on("videoFileCompleted", async ({ recorder, filename, stats }) => {
291186
+ logObj.info("Manager videoFileCompleted", { recorder, filename, stats });
291203
291187
  const endTime = new Date();
291204
291188
  const data = recorderConfig.get(recorder.id);
291205
291189
  const title = recorder?.liveInfo?.title;
@@ -291230,7 +291214,16 @@ async function createRecorderManager(appConfig) {
291230
291214
  video_filename: videoFilename,
291231
291215
  quick_hash: quickHash,
291232
291216
  });
291233
- if (xmlFile && (await fs$k.pathExists(xmlFile))) {
291217
+ if (stats) {
291218
+ recordHistory.upadteLive({
291219
+ video_file: filename,
291220
+ live_id: liveId,
291221
+ }, {
291222
+ danma_num: stats.danmaNum,
291223
+ interact_num: stats.uniqMember,
291224
+ });
291225
+ }
291226
+ else if (xmlFile && (await fs$k.pathExists(xmlFile))) {
291234
291227
  const { uniqMember, danmaNum } = await danmaReport(xmlFile);
291235
291228
  recordHistory.upadteLive({
291236
291229
  video_file: filename,
@@ -4,7 +4,7 @@ var path$7 = require('node:path');
4
4
  var require$$2$1 = require('node:http');
5
5
  var require$$1$2 = require('node:url');
6
6
  var a = require('node:https');
7
- var index = require('./index-Bw3nOSUH.cjs');
7
+ var index = require('./index-CgpPN0zA.cjs');
8
8
  var require$$0$4 = require('tty');
9
9
  var require$$1$3 = require('util');
10
10
  var require$$0$b = require('assert');
@@ -55050,55 +55050,47 @@ const coerce$1 = (version, options) => {
55050
55050
  };
55051
55051
  var coerce_1 = coerce$1;
55052
55052
 
55053
- var lrucache;
55054
- var hasRequiredLrucache;
55055
-
55056
- function requireLrucache () {
55057
- if (hasRequiredLrucache) return lrucache;
55058
- hasRequiredLrucache = 1;
55059
- class LRUCache {
55060
- constructor () {
55061
- this.max = 1000;
55062
- this.map = new Map();
55063
- }
55064
-
55065
- get (key) {
55066
- const value = this.map.get(key);
55067
- if (value === undefined) {
55068
- return undefined
55069
- } else {
55070
- // Remove the key from the map and add it to the end
55071
- this.map.delete(key);
55072
- this.map.set(key, value);
55073
- return value
55074
- }
55075
- }
55053
+ class LRUCache {
55054
+ constructor () {
55055
+ this.max = 1000;
55056
+ this.map = new Map();
55057
+ }
55076
55058
 
55077
- delete (key) {
55078
- return this.map.delete(key)
55079
- }
55059
+ get (key) {
55060
+ const value = this.map.get(key);
55061
+ if (value === undefined) {
55062
+ return undefined
55063
+ } else {
55064
+ // Remove the key from the map and add it to the end
55065
+ this.map.delete(key);
55066
+ this.map.set(key, value);
55067
+ return value
55068
+ }
55069
+ }
55080
55070
 
55081
- set (key, value) {
55082
- const deleted = this.delete(key);
55071
+ delete (key) {
55072
+ return this.map.delete(key)
55073
+ }
55083
55074
 
55084
- if (!deleted && value !== undefined) {
55085
- // If cache is full, delete the least recently used item
55086
- if (this.map.size >= this.max) {
55087
- const firstKey = this.map.keys().next().value;
55088
- this.delete(firstKey);
55089
- }
55075
+ set (key, value) {
55076
+ const deleted = this.delete(key);
55090
55077
 
55091
- this.map.set(key, value);
55092
- }
55078
+ if (!deleted && value !== undefined) {
55079
+ // If cache is full, delete the least recently used item
55080
+ if (this.map.size >= this.max) {
55081
+ const firstKey = this.map.keys().next().value;
55082
+ this.delete(firstKey);
55083
+ }
55093
55084
 
55094
- return this
55095
- }
55096
- }
55085
+ this.map.set(key, value);
55086
+ }
55097
55087
 
55098
- lrucache = LRUCache;
55099
- return lrucache;
55088
+ return this
55089
+ }
55100
55090
  }
55101
55091
 
55092
+ var lrucache = LRUCache;
55093
+
55102
55094
  var range;
55103
55095
  var hasRequiredRange;
55104
55096
 
@@ -55319,7 +55311,7 @@ function requireRange () {
55319
55311
 
55320
55312
  range = Range;
55321
55313
 
55322
- const LRU = requireLrucache();
55314
+ const LRU = lrucache;
55323
55315
  const cache = new LRU();
55324
55316
 
55325
55317
  const parseOptions = parseOptions_1;
@@ -81473,7 +81465,7 @@ exports.handler = void 0;
81473
81465
  exports.appConfig = void 0;
81474
81466
  exports.container = void 0;
81475
81467
  const fileCache = createFileCache();
81476
- path$7.dirname(require$$1$2.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-BeK_zfM-.cjs', document.baseURI).href))));
81468
+ path$7.dirname(require$$1$2.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-DG03bxlI.cjs', document.baseURI).href))));
81477
81469
  const authMiddleware = (passKey) => {
81478
81470
  return async (ctx, next) => {
81479
81471
  const authHeader = ctx.headers["authorization"] || ctx.request.query.auth;
package/lib/index.cjs CHANGED
@@ -3715,7 +3715,7 @@ const {
3715
3715
  Help,
3716
3716
  } = commander;
3717
3717
 
3718
- var version = "3.13.0";
3718
+ var version = "3.13.1";
3719
3719
 
3720
3720
  process.on("uncaughtException", function (error) {
3721
3721
  console.error(`${new Date().toISOString()} uncaughtException`, error);
@@ -3741,8 +3741,8 @@ program
3741
3741
  throw new Error(`${c.configFolder}参数不存在,请先重新运行 config gen 命令`);
3742
3742
  }
3743
3743
  // 下面两行顺序不能换(
3744
- const { init } = await Promise.resolve().then(function () { return require('./index-Bw3nOSUH.cjs'); }).then(function (n) { return n.index; });
3745
- const { serverStart } = await Promise.resolve().then(function () { return require('./index-BeK_zfM-.cjs'); });
3744
+ const { init } = await Promise.resolve().then(function () { return require('./index-CgpPN0zA.cjs'); }).then(function (n) { return n.index; });
3745
+ const { serverStart } = await Promise.resolve().then(function () { return require('./index-DG03bxlI.cjs'); });
3746
3746
  const globalConfig = {
3747
3747
  ffmpegPresetPath: path$1.join(c.configFolder, "ffmpeg_presets.json"),
3748
3748
  videoPresetPath: path$1.join(c.configFolder, "presets.json"),
@@ -4,7 +4,7 @@ var os$3 = require('node:os');
4
4
  var path$5 = require('node:path');
5
5
  var fs$3 = require('node:fs');
6
6
  var crypto = require('node:crypto');
7
- var index = require('./index-Bw3nOSUH.cjs');
7
+ var index = require('./index-CgpPN0zA.cjs');
8
8
  var require$$0$1 = require('fs');
9
9
  var require$$0 = require('path');
10
10
  var require$$0$2 = require('child_process');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bililive-cli",
3
- "version": "3.13.0",
3
+ "version": "3.13.1",
4
4
  "type": "module",
5
5
  "description": "biliLive-tools的cli程序",
6
6
  "main": "./lib/index.js",
@@ -39,9 +39,9 @@
39
39
  "commander": "^12.1.0",
40
40
  "rimraf": "^6.0.1",
41
41
  "tsx": "^4.19.2",
42
- "@biliLive-tools/shared": "3.13.0",
43
- "@biliLive-tools/types": "3.13.0",
44
- "@biliLive-tools/http": "3.13.0"
42
+ "@biliLive-tools/http": "3.13.1",
43
+ "@biliLive-tools/types": "3.13.1",
44
+ "@biliLive-tools/shared": "3.13.1"
45
45
  },
46
46
  "scripts": {
47
47
  "start": "tsx src/index.ts",