bililive-cli 3.10.1 → 3.11.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.
@@ -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-CuJEbURl.cjs');
7
+ var index = require('./index-aJuLOmr2.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');
@@ -277,7 +277,7 @@ class BaiduPCS extends index.TypedEmitter {
277
277
  try {
278
278
  // 执行上传
279
279
  this.logger.info(`开始上传: ${localFilePath} 到 ${targetDir}`);
280
- const args = ["upload", localFilePath, targetDir];
280
+ const args = ["upload", localFilePath, targetDir, "--norapid"];
281
281
  console.log(options);
282
282
  // if (options?.retry !== undefined) {
283
283
  // args.push("--retry", options.retry.toString());
@@ -368,6 +368,25 @@ class BaiduPCS extends index.TypedEmitter {
368
368
  }
369
369
  }
370
370
 
371
+ const createdDirCache = new Set();
372
+ const pendingDirRequests = new Map();
373
+ const driveIdCache = new Map();
374
+ const pendingDriveIdRequests = new Map();
375
+ const driveLabels = {
376
+ backup: ["备份盘", "文件网盘"],
377
+ resource: ["资源库"],
378
+ };
379
+ function normalizeRemotePath(remotePath) {
380
+ return path$7.posix.normalize(remotePath.replace(/\\/g, "/"));
381
+ }
382
+ function isDirectoryAlreadyExistsError(error) {
383
+ if (!(error instanceof Error))
384
+ return false;
385
+ const errorMessage = error.message.toLowerCase();
386
+ return (errorMessage.includes("already exists") ||
387
+ errorMessage.includes("file exists") ||
388
+ errorMessage.includes("已存在"));
389
+ }
371
390
  /**
372
391
  * 阿里云盘上传类
373
392
  * 使用 aliyunpan 工具进行文件上传
@@ -376,6 +395,7 @@ class AliyunPan extends index.TypedEmitter {
376
395
  binary;
377
396
  remotePath;
378
397
  logger;
398
+ driveType;
379
399
  cmd = null;
380
400
  loginCmd = null; // 专门用于登录的进程
381
401
  constructor(options) {
@@ -383,9 +403,16 @@ class AliyunPan extends index.TypedEmitter {
383
403
  this.binary = options?.binary || "aliyunpan";
384
404
  this.remotePath = options?.remotePath || "/录播";
385
405
  this.logger = options?.logger || index.logObj;
406
+ this.driveType = options?.driveType || "backup";
386
407
  // 检查aliyunpan是否安装
387
408
  // this.checkInstallation();
388
409
  }
410
+ static clearCaches() {
411
+ createdDirCache.clear();
412
+ pendingDirRequests.clear();
413
+ driveIdCache.clear();
414
+ pendingDriveIdRequests.clear();
415
+ }
389
416
  /**
390
417
  * 检查aliyunpan是否已安装
391
418
  */
@@ -410,6 +437,109 @@ class AliyunPan extends index.TypedEmitter {
410
437
  return false;
411
438
  }
412
439
  }
440
+ getDriveIdCacheKey() {
441
+ return `${this.binary}:${this.driveType}`;
442
+ }
443
+ async resolveDriveId() {
444
+ const cacheKey = this.getDriveIdCacheKey();
445
+ const cachedDriveId = driveIdCache.get(cacheKey);
446
+ if (cachedDriveId) {
447
+ return cachedDriveId;
448
+ }
449
+ const pendingRequest = pendingDriveIdRequests.get(cacheKey);
450
+ if (pendingRequest) {
451
+ return pendingRequest;
452
+ }
453
+ const requestPromise = new Promise((resolve, reject) => {
454
+ const driveCmd = require$$1$1.spawn(this.binary, ["drive"], {
455
+ stdio: ["ignore", "pipe", "pipe"],
456
+ windowsHide: true,
457
+ });
458
+ let stdout = "";
459
+ let stderr = "";
460
+ let isSettled = false;
461
+ const timer = setTimeout(() => {
462
+ isSettled = true;
463
+ driveCmd.kill();
464
+ reject(new Error("解析阿里云盘 driveId 超时"));
465
+ }, 10000);
466
+ const cleanup = () => {
467
+ clearTimeout(timer);
468
+ pendingDriveIdRequests.delete(cacheKey);
469
+ };
470
+ const tryResolveDriveId = (data) => {
471
+ stdout += data.toString();
472
+ const driveId = this.parseDriveId(stdout, this.driveType);
473
+ if (!driveId || isSettled)
474
+ return;
475
+ isSettled = true;
476
+ cleanup();
477
+ driveIdCache.set(cacheKey, driveId);
478
+ resolve(driveId);
479
+ driveCmd.kill();
480
+ };
481
+ driveCmd.stdout.on("data", tryResolveDriveId);
482
+ driveCmd.stderr.on("data", (data) => {
483
+ stderr += data.toString();
484
+ });
485
+ driveCmd.on("close", (code) => {
486
+ cleanup();
487
+ if (isSettled)
488
+ return;
489
+ reject(new Error(`解析阿里云盘 driveId 失败: ${stderr || stdout || code}`));
490
+ });
491
+ driveCmd.on("error", (error) => {
492
+ cleanup();
493
+ if (isSettled)
494
+ return;
495
+ reject(error);
496
+ });
497
+ });
498
+ pendingDriveIdRequests.set(cacheKey, requestPromise);
499
+ return requestPromise;
500
+ }
501
+ parseDriveId(output, driveType) {
502
+ const lines = output.split("\n");
503
+ for (const line of lines) {
504
+ if (!driveLabels[driveType].some((label) => line.includes(label)))
505
+ continue;
506
+ const tokens = line.match(/[A-Za-z0-9_-]{6,}/g);
507
+ if (tokens?.length) {
508
+ return tokens.at(-1) ?? null;
509
+ }
510
+ }
511
+ return null;
512
+ }
513
+ getDirCacheKey(remotePath, driveId) {
514
+ return `${this.binary}:${driveId}:${remotePath}`;
515
+ }
516
+ async ensureRemoteDir(remotePath, driveId) {
517
+ const normalizedPath = normalizeRemotePath(remotePath);
518
+ const cacheKey = this.getDirCacheKey(normalizedPath, driveId);
519
+ if (createdDirCache.has(cacheKey)) {
520
+ return;
521
+ }
522
+ const pendingRequest = pendingDirRequests.get(cacheKey);
523
+ if (pendingRequest) {
524
+ return pendingRequest;
525
+ }
526
+ const requestPromise = (async () => {
527
+ try {
528
+ await this.executeCommand(["mkdir", normalizedPath, "--driveId", driveId]);
529
+ }
530
+ catch (error) {
531
+ if (!isDirectoryAlreadyExistsError(error)) {
532
+ throw error;
533
+ }
534
+ this.logger.warn(`目录已存在,跳过创建: ${normalizedPath}`);
535
+ }
536
+ createdDirCache.add(cacheKey);
537
+ })().finally(() => {
538
+ pendingDirRequests.delete(cacheKey);
539
+ });
540
+ pendingDirRequests.set(cacheKey, requestPromise);
541
+ return requestPromise;
542
+ }
413
543
  /**
414
544
  * 执行aliyunpan命令
415
545
  * @param args 命令参数
@@ -511,13 +641,13 @@ class AliyunPan extends index.TypedEmitter {
511
641
  this.emit("error", error);
512
642
  throw error;
513
643
  }
514
- // 确保目标文件夹存在
515
- const targetDir = path$7.join(this.remotePath, remoteDir).replace(/\\/g, "/");
516
- await this.executeCommand(["mkdir", targetDir]);
644
+ const driveId = await this.resolveDriveId();
645
+ const targetDir = normalizeRemotePath(path$7.join(this.remotePath, remoteDir));
646
+ await this.ensureRemoteDir(targetDir, driveId);
517
647
  try {
518
648
  // 执行上传
519
649
  this.logger.info(`开始上传: ${localFilePath} 到 ${targetDir}`);
520
- const args = ["upload", localFilePath, targetDir, "--norapid"];
650
+ const args = ["upload", localFilePath, targetDir, "--norapid", "--driveId", driveId];
521
651
  // 添加覆盖策略选项
522
652
  if (options?.policy === "overwrite") {
523
653
  args.push("--ow");
@@ -596,6 +726,7 @@ class AliyunPan extends index.TypedEmitter {
596
726
  this.loginCmd.on("close", (code) => {
597
727
  if (code === 0) {
598
728
  if (hasLoginSuccess) {
729
+ AliyunPan.clearCaches();
599
730
  this.logger.info("close: 阿里云盘登录成功");
600
731
  this.loginCmd = null;
601
732
  resolve();
@@ -717,8 +848,9 @@ class AliyunPan extends index.TypedEmitter {
717
848
  */
718
849
  async mkdir(remotePath) {
719
850
  try {
720
- const targetPath = path$7.join(this.remotePath, remotePath).replace(/\\/g, "/");
721
- await this.executeCommand(["mkdir", targetPath]);
851
+ const driveId = await this.resolveDriveId();
852
+ const targetPath = normalizeRemotePath(path$7.join(this.remotePath, remotePath));
853
+ await this.ensureRemoteDir(targetPath, driveId);
722
854
  this.logger.info(`创建目录成功: ${targetPath}`);
723
855
  return true;
724
856
  }
@@ -30850,6 +30982,48 @@ async function getLiveInfo(ids) {
30850
30982
  .map((item) => item.value);
30851
30983
  return list;
30852
30984
  }
30985
+ async function batchStartRecord(ids) {
30986
+ const recorderManager = exports.container.resolve("recorderManager");
30987
+ const requests = ids.map(async (id) => {
30988
+ try {
30989
+ await recorderManager.manager.startRecord(id);
30990
+ return { id, success: true };
30991
+ }
30992
+ catch (error) {
30993
+ return { id, success: false, error: error.message || "开始录制失败" };
30994
+ }
30995
+ });
30996
+ const results = await Promise.allSettled(requests);
30997
+ const finalResults = results
30998
+ .filter((item) => item.status === "fulfilled")
30999
+ .map((item) => item.value);
31000
+ return {
31001
+ results: finalResults,
31002
+ successCount: finalResults.filter((r) => r.success).length,
31003
+ failedCount: finalResults.filter((r) => !r.success).length,
31004
+ };
31005
+ }
31006
+ async function batchStopRecord(ids) {
31007
+ const recorderManager = exports.container.resolve("recorderManager");
31008
+ const requests = ids.map(async (id) => {
31009
+ try {
31010
+ await recorderManager.manager.stopRecord(id);
31011
+ return { id, success: true };
31012
+ }
31013
+ catch (error) {
31014
+ return { id, success: false, error: error.message || "停止录制失败" };
31015
+ }
31016
+ });
31017
+ const results = await Promise.allSettled(requests);
31018
+ const finalResults = results
31019
+ .filter((item) => item.status === "fulfilled")
31020
+ .map((item) => item.value);
31021
+ return {
31022
+ results: finalResults,
31023
+ successCount: finalResults.filter((r) => r.success).length,
31024
+ failedCount: finalResults.filter((r) => !r.success).length,
31025
+ };
31026
+ }
30853
31027
  var recorderService = {
30854
31028
  getRecorders,
30855
31029
  getRecorderNum,
@@ -30861,6 +31035,8 @@ var recorderService = {
30861
31035
  stopRecord,
30862
31036
  cutRecord,
30863
31037
  getLiveInfo,
31038
+ batchStartRecord,
31039
+ batchStopRecord,
30864
31040
  resolveChannel,
30865
31041
  batchResolveChannel,
30866
31042
  getBiliStream,
@@ -76719,10 +76895,36 @@ router$9.post("/:id/stop_record", async (ctx) => {
76719
76895
  const { id } = ctx.params;
76720
76896
  ctx.body = { payload: await recorderService.stopRecord({ id }) };
76721
76897
  });
76898
+ /**
76899
+ * 切割录制
76900
+ * @route POST /recorder/:recorderId/cut
76901
+ * @param recorderId 直播间ID
76902
+ * @returns 录制任务信息
76903
+ */
76722
76904
  router$9.post("/:id/cut", async (ctx) => {
76723
76905
  const { id } = ctx.params;
76724
76906
  ctx.body = { payload: await recorderService.cutRecord({ id }) };
76725
76907
  });
76908
+ /**
76909
+ * 批量开始录制
76910
+ * @route POST /recorder/manager/batch_start_record
76911
+ * @param ids 直播间ID列表
76912
+ * @returns 批量操作结果
76913
+ */
76914
+ router$9.post("/manager/batch_start_record", async (ctx) => {
76915
+ const { ids } = ctx.request.body;
76916
+ ctx.body = { payload: await recorderService.batchStartRecord(ids) };
76917
+ });
76918
+ /**
76919
+ * 批量停止录制
76920
+ * @route POST /recorder/manager/batch_stop_record
76921
+ * @param ids 直播间ID列表
76922
+ * @returns 批量操作结果
76923
+ */
76924
+ router$9.post("/manager/batch_stop_record", async (ctx) => {
76925
+ const { ids } = ctx.request.body;
76926
+ ctx.body = { payload: await recorderService.batchStopRecord(ids) };
76927
+ });
76726
76928
  /**
76727
76929
  * 解析直播间地址,获取对应的直播间信息
76728
76930
  * @route GET /recorder/manager/resolveChannel
@@ -77682,14 +77884,27 @@ router$6.get("/download/:id", async (ctx) => {
77682
77884
  async function download$1(output, url, options) {
77683
77885
  if ((await index.fs.pathExists(output)) && !options.override)
77684
77886
  throw new Error(`${output}已存在`);
77887
+ const { dir, name } = require$$0$6.parse(output);
77888
+ const tsOutput = require$$0$6.join(dir, `${name}.ts`);
77685
77889
  const { ffmpegPath } = index.getBinPath();
77686
- const downloader = new index.M3U8Downloader(url, output, {
77687
- convert2Mp4: true,
77890
+ const downloader = new index.M3U8Downloader(url, tsOutput, {
77891
+ convert2Mp4: false,
77688
77892
  ffmpegPath: ffmpegPath,
77689
77893
  segmentsDir: require$$0$6.join(index.getTempPath(), index.uuid()),
77690
77894
  });
77691
77895
  const task = new index.KuaishouDownloadVideoTask(downloader, {
77692
77896
  name: `下载任务:${require$$0$6.parse(output).name}`,
77897
+ }, {
77898
+ onEnd: async () => {
77899
+ const outputName = `${name}.mp4`;
77900
+ await index.transcode(tsOutput, outputName, { encoder: "copy", audioCodec: "copy" }, {
77901
+ saveType: 2,
77902
+ savePath: dir,
77903
+ override: false,
77904
+ removeOrigin: true,
77905
+ autoRun: true,
77906
+ });
77907
+ },
77693
77908
  });
77694
77909
  index.taskQueue.addTask(task, true);
77695
77910
  return task;
@@ -78418,6 +78633,7 @@ const createUploadInstance = async (opts) => {
78418
78633
  return new AliyunPan({
78419
78634
  binary: opts.execPath,
78420
78635
  remotePath: opts.remotePath ?? "",
78636
+ driveType: opts.aliyunpanDriveType,
78421
78637
  });
78422
78638
  }
78423
78639
  else if (opts.type === "alist") {
@@ -78448,7 +78664,7 @@ const createUploadInstance = async (opts) => {
78448
78664
  throw new Error("Unsupported type");
78449
78665
  }
78450
78666
  };
78451
- const addSyncTask = async ({ input, remotePath, execPath, policy, type, removeOrigin, apiUrl, username, password, clientId, clientSecret, stringFilters, }) => {
78667
+ const addSyncTask = async ({ input, remotePath, execPath, policy, type, removeOrigin, apiUrl, username, password, clientId, clientSecret, aliyunpanDriveType, stringFilters, }) => {
78452
78668
  const { binary: binaryPath, apiUrl: iApiUrl, username: iUsername, password: iPassword, clientId: iClientId, clientSecret: iClientSecret, limitRate, retry, } = getConfig(type);
78453
78669
  const instance = await createUploadInstance({
78454
78670
  type,
@@ -78459,6 +78675,7 @@ const addSyncTask = async ({ input, remotePath, execPath, policy, type, removeOr
78459
78675
  password: password ?? iPassword,
78460
78676
  clientId: clientId ?? iClientId,
78461
78677
  clientSecret: clientSecret ?? iClientSecret,
78678
+ aliyunpanDriveType,
78462
78679
  limitRate: limitRate ?? 0,
78463
78680
  stringFilters,
78464
78681
  retry,
@@ -78661,12 +78878,13 @@ router$2.get("/aliyunpanLogin", async (ctx) => {
78661
78878
  router$2.post("/sync", async (ctx) => {
78662
78879
  const params = ctx.request.body;
78663
78880
  // @ts-ignore
78664
- const { file, type, options, targetPath } = params;
78881
+ const { file, type, options, targetPath, aliyunpanDriveType } = params;
78665
78882
  const task = await addSyncTask({
78666
78883
  input: file,
78667
78884
  type: type,
78668
78885
  removeOrigin: options.removeOrigin,
78669
78886
  remotePath: targetPath,
78887
+ aliyunpanDriveType,
78670
78888
  });
78671
78889
  ctx.body = task.taskId;
78672
78890
  });
@@ -80427,6 +80645,7 @@ class WebhookHandler {
80427
80645
  retry: 3,
80428
80646
  policy: "skip",
80429
80647
  type: syncConfig.syncSource,
80648
+ aliyunpanDriveType: syncConfig.aliyunpanDriveType,
80430
80649
  });
80431
80650
  index.logObj.info(`开始同步${fileType}文件: ${filePath}`);
80432
80651
  task.on("task-end", async () => {
@@ -80718,12 +80937,12 @@ class WebhookHandler {
80718
80937
  * 处理审核后删除的引用计数和回调
80719
80938
  * @private
80720
80939
  */
80721
- setupDeleteAfterCheckLock(pathArray) {
80940
+ setupDeleteAfterCheckLock(pathArray, afterUploadDeletAction) {
80722
80941
  // 返回 checkCallback
80723
80942
  return async (status) => {
80724
- console.log(`审核状态: ${status}`, pathArray);
80725
- if (status === "completed") {
80726
- // 审核通过,释放引用(会自动删除)
80943
+ // console.log(`审核状态: ${status}`, pathArray);
80944
+ if (status === "completed" && afterUploadDeletAction === "deleteAfterCheck") {
80945
+ // 审核通过且配置为审核后删除,释放引用(会自动删除)
80727
80946
  for (const { path } of pathArray) {
80728
80947
  await this.fileRefManager.releaseRef(path);
80729
80948
  }
@@ -80731,7 +80950,7 @@ class WebhookHandler {
80731
80950
  };
80732
80951
  }
80733
80952
  addUploadTask = async (uid, pathArray, options, limitedUploadTime, afterUploadDeletAction) => {
80734
- const checkCallback = this.setupDeleteAfterCheckLock(pathArray);
80953
+ const checkCallback = this.setupDeleteAfterCheckLock(pathArray, afterUploadDeletAction);
80735
80954
  const task = await index.biliApi.addMedia(pathArray, options, uid, {
80736
80955
  limitedUploadTime,
80737
80956
  afterUploadDeletAction: "none",
@@ -80740,9 +80959,9 @@ class WebhookHandler {
80740
80959
  });
80741
80960
  return this.handleUploadTask(task, pathArray);
80742
80961
  };
80743
- addEditMediaTask = async (uid, aid, pathArray, limitedUploadTime, afterUploadDeletAction) => {
80744
- const checkCallback = this.setupDeleteAfterCheckLock(pathArray);
80745
- const task = await index.biliApi.editMedia(aid, pathArray, {}, uid, {
80962
+ addEditMediaTask = async (uid, aid, pathArray, uploadPreset, limitedUploadTime, afterUploadDeletAction) => {
80963
+ const checkCallback = this.setupDeleteAfterCheckLock(pathArray, afterUploadDeletAction);
80964
+ const task = await index.biliApi.editMedia(aid, pathArray, uploadPreset, uid, {
80746
80965
  limitedUploadTime,
80747
80966
  afterUploadDeletAction: "none",
80748
80967
  forceCheck: afterUploadDeletAction === "deleteAfterCheck",
@@ -80866,13 +81085,13 @@ class WebhookHandler {
80866
81085
  * 执行续传操作
80867
81086
  * @private
80868
81087
  */
80869
- async performContinueUpload(live, aid, filePaths, type, config, limitedUploadTime) {
81088
+ async performContinueUpload(live, aid, filePaths, type, config, uploadPreset, limitedUploadTime) {
80870
81089
  index.logObj.info("续传", filePaths);
80871
81090
  live.batchUpdateUploadStatus(filePaths.map((item) => item.part), "uploading", type);
80872
81091
  await this.addEditMediaTask(config.uid, aid, filePaths.map((item) => ({
80873
81092
  path: item.path,
80874
81093
  title: item.title,
80875
- })), limitedUploadTime, config.afterUploadDeletAction);
81094
+ })), uploadPreset, limitedUploadTime, config.afterUploadDeletAction);
80876
81095
  live.batchUpdateUploadStatus(filePaths.map((item) => item.part), "uploaded", type);
80877
81096
  }
80878
81097
  /**
@@ -80922,7 +81141,7 @@ class WebhookHandler {
80922
81141
  try {
80923
81142
  // 8. 执行上传(续传或新上传)
80924
81143
  if (live[aidField]) {
80925
- await this.performContinueUpload(live, live[aidField], filePaths, type, config, limitedUploadTime);
81144
+ await this.performContinueUpload(live, live[aidField], filePaths, type, config, uploadPreset, limitedUploadTime);
80926
81145
  }
80927
81146
  else {
80928
81147
  await this.performNewUpload(live, filePaths, type, config, uploadPreset, limitedUploadTime);
@@ -80993,7 +81212,7 @@ exports.handler = void 0;
80993
81212
  exports.appConfig = void 0;
80994
81213
  exports.container = void 0;
80995
81214
  const fileCache = createFileCache();
80996
- 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-DOo0dewT.cjs', document.baseURI).href))));
81215
+ 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-DmrbnwzK.cjs', document.baseURI).href))));
80997
81216
  const authMiddleware = (passKey) => {
80998
81217
  return async (ctx, next) => {
80999
81218
  const authHeader = ctx.headers["authorization"] || ctx.request.query.auth;
@@ -11408,6 +11408,7 @@ const APP_DEFAULT_CONFIG = {
11408
11408
  fileSync: {
11409
11409
  removeOrigin: false,
11410
11410
  syncType: undefined,
11411
+ aliyunpanDriveType: "backup",
11411
11412
  targetPath: "/",
11412
11413
  },
11413
11414
  danmu: {
@@ -11646,7 +11647,7 @@ const APP_DEFAULT_CONFIG = {
11646
11647
  line: undefined,
11647
11648
  checkInterval: 60,
11648
11649
  maxThreadCount: 3,
11649
- waitTime: 0,
11650
+ waitTime: 500,
11650
11651
  disableProvideCommentsWhenRecording: false,
11651
11652
  segment: "90",
11652
11653
  saveGiftDanma: false,
@@ -44555,7 +44556,7 @@ async function trash(paths, options) {
44555
44556
  } else if (process$2.platform === 'win32') {
44556
44557
  module = await Promise.resolve().then(function () { return require('./windows-OmnJ7a39.cjs'); });
44557
44558
  } else {
44558
- module = await Promise.resolve().then(function () { return require('./linux-CINV_k6s.cjs'); });
44559
+ module = await Promise.resolve().then(function () { return require('./linux-fd_XbwNY.cjs'); });
44559
44560
  }
44560
44561
 
44561
44562
  return module.default(paths);
@@ -192718,9 +192719,11 @@ async function download$1(output, decodeData, options) {
192718
192719
  }
192719
192720
  if (!m3u8Url)
192720
192721
  throw new Error("无法找到对应的流");
192722
+ const { dir, name } = path$y.parse(output);
192723
+ const tsOutput = path$y.join(dir, `${name}.ts`);
192721
192724
  const { ffmpegPath } = getBinPath();
192722
- const downloader = new M3U8Downloader(m3u8Url, output, {
192723
- convert2Mp4: true,
192725
+ const downloader = new M3U8Downloader(m3u8Url, tsOutput, {
192726
+ convert2Mp4: false,
192724
192727
  ffmpegPath: ffmpegPath,
192725
192728
  segmentsDir: path$y.join(getTempPath(), uuid$4()),
192726
192729
  });
@@ -192733,6 +192736,14 @@ async function download$1(output, decodeData, options) {
192733
192736
  const xml = convert2Xml(danmu, options.danmuMeta || {});
192734
192737
  fs$k.writeFile(path$y.join(path$y.dirname(output), `${path$y.parse(output).name}.xml`), xml);
192735
192738
  }
192739
+ const outputName = `${name}.mp4`;
192740
+ await transcode(tsOutput, outputName, { encoder: "copy", audioCodec: "copy" }, {
192741
+ saveType: 2,
192742
+ savePath: dir,
192743
+ override: false,
192744
+ removeOrigin: true,
192745
+ autoRun: true,
192746
+ });
192736
192747
  },
192737
192748
  });
192738
192749
  taskQueue.addTask(task, false);
@@ -192774,14 +192785,27 @@ var douyu = { download: download$1, parseVideo: parseVideo$1, getAvailableStream
192774
192785
  async function download(output, url, options) {
192775
192786
  if ((await fs$k.pathExists(output)) && !options.override)
192776
192787
  throw new Error(`${output}已存在`);
192788
+ const { dir, name } = path$y.parse(output);
192789
+ const tsOutput = path$y.join(dir, `${name}.ts`);
192777
192790
  const { ffmpegPath } = getBinPath();
192778
- const downloader = new M3U8Downloader(url, output, {
192779
- convert2Mp4: true,
192791
+ const downloader = new M3U8Downloader(url, tsOutput, {
192792
+ convert2Mp4: false,
192780
192793
  ffmpegPath: ffmpegPath,
192781
192794
  segmentsDir: path$y.join(getTempPath(), uuid$4()),
192782
192795
  });
192783
192796
  const task = new HuyaDownloadVideoTask(downloader, {
192784
192797
  name: `下载任务:${path$y.parse(output).name}`,
192798
+ }, {
192799
+ onEnd: async () => {
192800
+ const outputName = `${name}.mp4`;
192801
+ await transcode(tsOutput, outputName, { encoder: "copy", audioCodec: "copy" }, {
192802
+ saveType: 2,
192803
+ savePath: dir,
192804
+ override: false,
192805
+ removeOrigin: true,
192806
+ autoRun: true,
192807
+ });
192808
+ },
192785
192809
  });
192786
192810
  taskQueue.addTask(task, true);
192787
192811
  return task;
@@ -214068,6 +214092,22 @@ async function getRoomInfoByWeb(webRoomId, opts = {}) {
214068
214092
  "User-Agent": ua,
214069
214093
  },
214070
214094
  });
214095
+ if (res.data.status_code === 30003) {
214096
+ // 直播已结束
214097
+ return {
214098
+ living: false,
214099
+ nickname: "",
214100
+ sec_uid: "",
214101
+ avatar: "",
214102
+ api: "web",
214103
+ room: {
214104
+ title: "",
214105
+ cover: "",
214106
+ id_str: "",
214107
+ stream_url: null,
214108
+ },
214109
+ };
214110
+ }
214071
214111
  assert$i(res.data.status_code === 0, `Unexpected resp, code ${res.data.status_code}, msg ${JSON.stringify(res.data.data)}, id ${webRoomId}, cookies: ${cookies}`);
214072
214112
  const data = res.data.data;
214073
214113
  const room = data?.data?.[0];
@@ -246387,9 +246427,14 @@ const checkLiveStatusAndRecord$1 = async function ({ getSavePath, banLiveId, isM
246387
246427
  const extraDataController = downloader.getExtraDataController();
246388
246428
  if (!extraDataController)
246389
246429
  return;
246430
+ let timestamp = Date.now();
246431
+ if (this.useServerTimestamp && msg.eventTime) {
246432
+ // 某些消息可能没有 eventTime 字段
246433
+ timestamp = Number(msg.eventTime) * 1000;
246434
+ }
246390
246435
  const comment = {
246391
246436
  type: "comment",
246392
- timestamp: this.useServerTimestamp ? Number(msg.eventTime) * 1000 : Date.now(),
246437
+ timestamp: timestamp,
246393
246438
  text: msg.content,
246394
246439
  color: "#ffffff",
246395
246440
  sender: {
@@ -287573,17 +287618,11 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
287573
287618
  roomId = info.roomId;
287574
287619
  liveStartTimeFromSearch = info.liveStartTime;
287575
287620
  }
287621
+ console.log("roomid", roomId);
287576
287622
  if (!roomId)
287577
287623
  return null;
287578
- if (this.liveInfo?.liveId === banLiveId) {
287579
- this.tempStopIntervalCheck = true;
287580
- }
287581
- else {
287582
- this.tempStopIntervalCheck = false;
287583
- }
287584
- if (this.tempStopIntervalCheck)
287585
- return null;
287586
287624
  const liveInfo = await getInfo(roomId);
287625
+ console.log("liveInfo", liveInfo);
287587
287626
  // @ts-ignore
287588
287627
  this.liveInfo = liveInfo;
287589
287628
  if (liveStartTimeFromSearch) {
@@ -287596,6 +287635,14 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
287596
287635
  throw error;
287597
287636
  }
287598
287637
  const { living, owner, title, liveStartTime, recordStartTime } = this.liveInfo;
287638
+ if (this.liveInfo?.liveId === banLiveId) {
287639
+ this.tempStopIntervalCheck = true;
287640
+ }
287641
+ else {
287642
+ this.tempStopIntervalCheck = false;
287643
+ }
287644
+ if (this.tempStopIntervalCheck)
287645
+ return null;
287599
287646
  if (!living)
287600
287647
  return null;
287601
287648
  // 检查标题是否包含关键词
package/lib/index.cjs CHANGED
@@ -3715,7 +3715,7 @@ const {
3715
3715
  Help,
3716
3716
  } = commander;
3717
3717
 
3718
- var version = "3.10.1";
3718
+ var version = "3.11.0";
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-CuJEbURl.cjs'); }).then(function (n) { return n.index; });
3745
- const { serverStart } = await Promise.resolve().then(function () { return require('./index-DOo0dewT.cjs'); });
3744
+ const { init } = await Promise.resolve().then(function () { return require('./index-aJuLOmr2.cjs'); }).then(function (n) { return n.index; });
3745
+ const { serverStart } = await Promise.resolve().then(function () { return require('./index-DmrbnwzK.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-CuJEbURl.cjs');
7
+ var index = require('./index-aJuLOmr2.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.10.1",
3
+ "version": "3.11.0",
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/http": "3.10.1",
43
- "@biliLive-tools/shared": "3.10.1",
44
- "@biliLive-tools/types": "3.10.1"
42
+ "@biliLive-tools/http": "3.11.0",
43
+ "@biliLive-tools/types": "3.11.0",
44
+ "@biliLive-tools/shared": "3.11.0"
45
45
  },
46
46
  "scripts": {
47
47
  "start": "tsx src/index.ts",