@lzwme/m3u8-dl 1.3.0 → 1.3.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/cjs/cli.js CHANGED
@@ -89,7 +89,9 @@ commander_1.program
89
89
  const opts = getOptions();
90
90
  if (opts.debug)
91
91
  options.debug = true;
92
- console.log(opts, options);
92
+ if (opts.cacheDir)
93
+ options.cacheDir = opts.cacheDir;
94
+ utils_js_1.logger.debug('[cli][server]', opts, options);
93
95
  Promise.resolve().then(() => __importStar(require('./server/download-server.js'))).then(m => {
94
96
  new m.DLServer(options);
95
97
  });
@@ -8,6 +8,8 @@ interface DLServerOptions {
8
8
  debug?: boolean;
9
9
  /** 登录 token,默认取环境变量 DS_SECRET */
10
10
  token?: string;
11
+ /** 是否限制文件访问(localplay视频资源等仅可读取下载和缓存目录) */
12
+ limitFileAccess?: boolean;
11
13
  }
12
14
  interface CacheItem extends Partial<M3u8DLProgressStats> {
13
15
  url: string;
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.DLServer = void 0;
37
37
  const node_fs_1 = require("node:fs");
38
38
  const node_path_1 = require("node:path");
39
+ const node_os_1 = require("node:os");
39
40
  const fe_utils_1 = require("@lzwme/fe-utils");
40
41
  const console_log_colors_1 = require("console-log-colors");
41
42
  const file_download_js_1 = require("../lib/file-download.js");
@@ -50,9 +51,10 @@ class DLServer {
50
51
  /** DS 参数 */
51
52
  options = {
52
53
  port: Number(process.env.DS_PORT) || 6600,
53
- cacheDir: (0, node_path_1.resolve)(process.cwd(), './cache'),
54
+ cacheDir: process.env.DS_CACHE_DIR || (0, node_path_1.resolve)((0, node_os_1.homedir)(), '.m3u8-dl/cache'),
54
55
  token: process.env.DS_SECRET || process.env.DS_TOKEN || '',
55
56
  debug: process.env.DS_DEBUG === '1',
57
+ limitFileAccess: !['0', 'false'].includes(process.env.DS_LIMTE_FILE_ACCESS),
56
58
  };
57
59
  serverInfo = {
58
60
  version: '',
@@ -102,7 +104,7 @@ class DLServer {
102
104
  this.loadCache();
103
105
  await this.createApp();
104
106
  this.initRouters();
105
- utils_js_1.logger.debug('Server initialized', this.options, this.cfg.dlOptions);
107
+ utils_js_1.logger.debug('Server initialized', 'cacheSize:', this.dlCache.size, this.options, this.cfg.dlOptions);
106
108
  }
107
109
  loadCache() {
108
110
  const cacheFile = (0, node_path_1.resolve)(this.options.cacheDir, 'cache.json');
@@ -195,6 +197,7 @@ class DLServer {
195
197
  if ((0, node_fs_1.existsSync)((0, node_path_1.resolve)(rootDir, 'client/local/cdn'))) {
196
198
  indexHtml = indexHtml
197
199
  .replaceAll('https://s4.zstatic.net/ajax/libs', 'local/cdn')
200
+ .replaceAll(/integrity=.+\n/g, '')
198
201
  .replace('https://cdn.tailwindcss.com/3.4.16', 'local/cdn/tailwindcss/3.4.16/tailwindcss.min.js');
199
202
  }
200
203
  res.setHeader('content-type', 'text/html').send(indexHtml);
@@ -248,16 +251,16 @@ class DLServer {
248
251
  startDownload(url, options) {
249
252
  const dlOptions = (0, format_options_js_1.formatOptions)(url, { ...this.cfg.dlOptions, ...options, cacheDir: this.options.cacheDir })[1];
250
253
  const cacheItem = this.dlCache.get(url) || { options, dlOptions, status: 'pending', url };
251
- utils_js_1.logger.debug('startDownload', url, dlOptions, cacheItem?.status);
254
+ utils_js_1.logger.debug('startDownload', url, dlOptions, cacheItem.status);
252
255
  if (cacheItem.status === 'resume')
253
256
  return cacheItem.options;
254
- if (this.downloading >= this.cfg.webOptions.maxDownloads) {
255
- cacheItem.status = 'pending';
256
- this.dlCache.set(url, cacheItem);
257
- return cacheItem.options;
258
- }
259
- cacheItem.status = 'resume';
257
+ if (cacheItem.localVideo && !(0, node_fs_1.existsSync)(cacheItem.localVideo))
258
+ delete cacheItem.localVideo;
259
+ cacheItem.status = this.downloading >= this.cfg.webOptions.maxDownloads ? 'pending' : 'resume';
260
260
  this.dlCache.set(url, cacheItem);
261
+ this.wsSend('progress', url);
262
+ if (cacheItem.status === 'pending')
263
+ return cacheItem.options;
261
264
  let workPoll = cacheItem.workPoll;
262
265
  const opts = {
263
266
  ...dlOptions,
@@ -291,12 +294,10 @@ class DLServer {
291
294
  this.wsSend('progress', url);
292
295
  this.saveCache();
293
296
  // 找到一个 pending 的任务,开始下载
294
- for (const [url, item] of this.dlCache.entries()) {
295
- if (item.status === 'pending') {
296
- this.startDownload(url, item.options);
297
- this.wsSend('progress', url);
298
- break;
299
- }
297
+ const nextItem = this.dlCache.entries().find(([_url, d]) => d.status === 'pending');
298
+ if (nextItem) {
299
+ this.startDownload(nextItem[0], nextItem[1].options);
300
+ this.wsSend('progress', nextItem[0]);
300
301
  }
301
302
  };
302
303
  try {
@@ -323,12 +324,10 @@ class DLServer {
323
324
  }
324
325
  else if (type === 'progress' && typeof data === 'string') {
325
326
  const item = this.dlCache.get(data);
326
- if (item) {
327
- const { workPoll, ...stats } = item;
328
- data = [{ ...stats, url: data }];
329
- }
330
- else
327
+ if (!item)
331
328
  return;
329
+ const { workPoll, ...stats } = item;
330
+ data = [{ ...stats, url: data }];
332
331
  }
333
332
  // 广播进度信息给所有客户端
334
333
  this.wss.clients.forEach(client => {
@@ -484,8 +483,9 @@ class DLServer {
484
483
  if (!(0, node_fs_1.existsSync)(filepath))
485
484
  filepath += '.m3u8';
486
485
  }
486
+ const allowedDirs = [this.options.cacheDir, this.cfg.dlOptions.saveDir];
487
487
  if (!(0, node_fs_1.existsSync)(filepath)) {
488
- for (const dir of [this.options.cacheDir, this.cfg.dlOptions.saveDir]) {
488
+ for (const dir of allowedDirs) {
489
489
  const tpath = (0, node_path_1.resolve)(dir, filepath);
490
490
  if ((0, node_fs_1.existsSync)(tpath)) {
491
491
  filepath = tpath;
@@ -495,7 +495,9 @@ class DLServer {
495
495
  }
496
496
  else {
497
497
  filepath = (0, node_path_1.resolve)(filepath);
498
- if ([this.options.cacheDir, this.cfg.dlOptions.saveDir].some(d => filepath.startsWith((0, node_path_1.resolve)(d)))) {
498
+ const isAllow = !this.options.limitFileAccess || allowedDirs.some(d => filepath.startsWith((0, node_path_1.resolve)(d)));
499
+ if (!isAllow) {
500
+ utils_js_1.logger.error('[Localplay] Access denied:', filepath);
499
501
  res.send({ message: 'Access denied', code: 403 });
500
502
  return;
501
503
  }
package/client/index.html CHANGED
@@ -6,7 +6,7 @@
6
6
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
7
7
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
8
  <meta name="apple-mobile-web-app-capable" content="yes">
9
- <title>M3U8 下载管理</title>
9
+ <title>M3U8 下载器</title>
10
10
  <link rel="icon" type="image/svg+xml" href="logo.svg">
11
11
  <link rel="stylesheet" href="https://s4.zstatic.net/ajax/libs/font-awesome/6.7.2/css/all.min.css"
12
12
  integrity="sha512-Evv84Mr4kqVGRNSgIGL/F/aIDqQb7xQ2vcrdIwxfjThSH8CSR7PBEakCr51Ck+w+/U6swU2Im1vVX0SVk9ABhg=="
@@ -406,6 +406,14 @@
406
406
  class="text-blue-500 hover:text-blue-600">
407
407
  {{serverInfo.version}}</a>
408
408
  </p>
409
+ <p class="text-gray-600"><strong>检测版本:</strong>
410
+ <button @click="checkNewVersion" v-if="!serverInfo.appUpdateMessage"
411
+ class="px-2 py-1 text-sm bg-green-600 hover:bg-green-700 text-white rounded">
412
+ <i class="fas fa-check mr-1"></i>检测新版本
413
+ </button>
414
+ <span v-if="serverInfo.newVersion" class="text-blue-600">发现新版本![{{serverInfo.newVersion}}]</span>
415
+ <span v-if="serverInfo.appUpdateMessage" class="text-green-600">{{serverInfo.appUpdateMessage}}</span>
416
+ </p>
409
417
  </div>
410
418
  </div>
411
419
 
@@ -513,7 +521,10 @@ services:
513
521
  alert(msg, p) {
514
522
  p = typeof msg === 'object' ? msg : Object.assign({ text: msg }, p);
515
523
  if (!p.toast) p.allowOutsideClick = false;
516
- Swal.fire(Object.assign({ icon: 'info', showConfirmButton: false, showCloseButton: true }, p));
524
+ return Swal.fire(Object.assign({ icon: 'info', showConfirmButton: false, showCloseButton: true }, p));
525
+ },
526
+ confirm(msg, p) {
527
+ return this.alert(msg, { showConfirmButton: true, showCancelButton: true, showCloseButton: true, confirmButtonText: '确认', cancelButtonText: '取消' });
517
528
  },
518
529
  toast(msg, p) {
519
530
  p = (typeof msg === 'object' ? msg : Object.assign({ text: msg }, p));
@@ -602,6 +613,8 @@ services:
602
613
  serverInfo: {
603
614
  version: '{{version}}',
604
615
  ariang: false,
616
+ newVersion: '',
617
+ appUpdateMessage: '',
605
618
  },
606
619
  config: {
607
620
  /** 并发下载线程数。取决于服务器限制,过多可能会容易下载失败。一般建议不超过 8 个。默认为 cpu数 * 2,但不超过 8 */
@@ -661,7 +674,7 @@ services:
661
674
 
662
675
  // 排序:resume > pending > pause > error > done
663
676
  const statusOrder = { resume: 0, pending: 1, pause: 2, error: 3, done: 4 };
664
- tasks.sort((a, b) => (statusOrder[a.status] - statusOrder[b.status]) || a.status === 'done' ? (b.filename - a.filename) : (b.endTime - a.endTime));
677
+ tasks.sort((a, b) => (statusOrder[a.status] - statusOrder[b.status]) || (a.status === 'done' ? (b.filename - a.filename) : (b.endTime - a.endTime)));
665
678
 
666
679
  // 更新 queueStatus
667
680
  const queueStatus = {
@@ -682,6 +695,38 @@ services:
682
695
  }
683
696
  },
684
697
  methods: {
698
+ initEventsForApp() {
699
+ if (!window.electron) return;
700
+ const ipc = window.electron.ipc;
701
+ ipc.on('message', (ev) => {
702
+ if (typeof ev.data === 'string') T.toast(ev.data, { icon: 'info' });
703
+ else console.log(ev.data);
704
+ });
705
+
706
+ ipc.on('downloadProgress', (data) => {
707
+ console.log('downloadProgress', data);
708
+ this.serverInfo.appUpdateMessage = `下载中:${Number(data.percent).toFixed(2)}% [${T.formatSpeed(data.bytesPerSecond)}] [${T.formatSize(data.transferred)}/${T.formatSize(data.total)}]`;
709
+ });
710
+ },
711
+ async checkNewVersion() {
712
+ try {
713
+ const r = await fetch(`https://registry.npmmirror.com/@lzwme/m3u8-dl/latest`).then(r => r.json());
714
+ if (r.version) {
715
+ if (r.version === this.serverInfo.version) T.toast(`已是最新版本,无需更新[${r.version}]`);
716
+ else {
717
+ this.serverInfo.newVersion = r.version;
718
+ if (window.electron) {
719
+ window.electron.ipc.send('checkForUpdate');
720
+ } else {
721
+ T.alert(`发现新版本[${r.version}],请前往 https://github.com/lzwme/m3u8-dl/releases 下载最新版本`, { icon: 'success' });
722
+ }
723
+ }
724
+ }
725
+ } catch (error) {
726
+ console.error('检查新版本失败:', error);
727
+ T.alert(`版本检查失败:${error.message}`, { icon: 'error' });
728
+ }
729
+ },
685
730
  forceUpdate: function () {
686
731
  const now = Date.now();
687
732
  if (now - this.forceUpdateTime > 500) {
@@ -705,7 +750,7 @@ services:
705
750
 
706
751
  switch (type) {
707
752
  case 'serverInfo':
708
- this.serverInfo = data;
753
+ Object.assign(this.serverInfo, data);
709
754
  break;
710
755
  case 'tasks':
711
756
  this.tasks = data;
@@ -1058,6 +1103,7 @@ services:
1058
1103
  T.reqHeaders.authorization = this.token ? `${this.token}` : '';
1059
1104
  this.fetchConfig().then(d => d && this.wsConnect());
1060
1105
  window.addEventListener('resize', this.handleResize);
1106
+ this.initEventsForApp();
1061
1107
  },
1062
1108
  beforeDestroy() {
1063
1109
  window.removeEventListener('resize', this.handleResize);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lzwme/m3u8-dl",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "Batch download of m3u8 files and convert to mp4",
5
5
  "main": "cjs/index.js",
6
6
  "types": "cjs/index.d.ts",
@@ -44,24 +44,24 @@
44
44
  "registry": "https://registry.npmjs.com"
45
45
  },
46
46
  "devDependencies": {
47
- "@biomejs/biome": "^1.9.4",
48
- "@eslint/js": "^9.28.0",
47
+ "@biomejs/biome": "^2.0.0",
48
+ "@eslint/js": "^9.29.0",
49
49
  "@lzwme/fed-lint-helper": "^2.6.6",
50
50
  "@types/express": "^5.0.3",
51
51
  "@types/m3u8-parser": "^7.2.2",
52
- "@types/node": "^24.0.1",
52
+ "@types/node": "^24.0.3",
53
53
  "@types/ws": "^8.18.1",
54
- "@typescript-eslint/eslint-plugin": "^8.34.0",
55
- "@typescript-eslint/parser": "^8.34.0",
56
- "eslint": "^9.28.0",
54
+ "@typescript-eslint/eslint-plugin": "^8.34.1",
55
+ "@typescript-eslint/parser": "^8.34.1",
56
+ "eslint": "^9.29.0",
57
57
  "eslint-config-prettier": "^10.1.5",
58
- "eslint-plugin-prettier": "^5.4.1",
58
+ "eslint-plugin-prettier": "^5.5.0",
59
59
  "express": "^5.1.0",
60
60
  "husky": "^9.1.7",
61
61
  "prettier": "^3.5.3",
62
62
  "standard-version": "^9.5.0",
63
63
  "typescript": "^5.8.3",
64
- "typescript-eslint": "^8.34.0",
64
+ "typescript-eslint": "^8.34.1",
65
65
  "ws": "^8.18.2"
66
66
  },
67
67
  "dependencies": {