@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 +3 -1
- package/cjs/server/download-server.d.ts +2 -0
- package/cjs/server/download-server.js +24 -22
- package/client/index.html +50 -4
- package/package.json +9 -9
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
|
-
|
|
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
|
});
|
|
@@ -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)(
|
|
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
|
|
254
|
+
utils_js_1.logger.debug('startDownload', url, dlOptions, cacheItem.status);
|
|
252
255
|
if (cacheItem.status === 'resume')
|
|
253
256
|
return cacheItem.options;
|
|
254
|
-
if (
|
|
255
|
-
cacheItem.
|
|
256
|
-
|
|
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
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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": "^
|
|
48
|
-
"@eslint/js": "^9.
|
|
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.
|
|
52
|
+
"@types/node": "^24.0.3",
|
|
53
53
|
"@types/ws": "^8.18.1",
|
|
54
|
-
"@typescript-eslint/eslint-plugin": "^8.34.
|
|
55
|
-
"@typescript-eslint/parser": "^8.34.
|
|
56
|
-
"eslint": "^9.
|
|
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.
|
|
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.
|
|
64
|
+
"typescript-eslint": "^8.34.1",
|
|
65
65
|
"ws": "^8.18.2"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|