@lzwme/m3u8-dl 1.2.2 → 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 +4 -1
- package/cjs/lib/m3u8-download.js +28 -1
- package/cjs/lib/parseM3u8.js +1 -1
- package/cjs/server/download-server.d.ts +2 -0
- package/cjs/server/download-server.js +54 -40
- package/cjs/types/m3u8.d.ts +4 -0
- package/client/index.html +80 -156
- package/client/play.html +34 -33
- package/client/style.css +129 -0
- package/package.json +12 -10
package/cjs/cli.js
CHANGED
|
@@ -67,6 +67,7 @@ commander_1.program
|
|
|
67
67
|
.option('--no-convert', '下载成功后,是否不合并转换为 mp4 文件。默认为 true。')
|
|
68
68
|
.option('-H, --headers <headers>', '自定义请求头。格式为 key1=value1\nkey2=value2')
|
|
69
69
|
.option('-T, --type <type>', '指定下载类型。默认根据URL自动识别,如果是批量下载多个不同 URL 类型,请不要设置。可选值:m3u8, file, parser')
|
|
70
|
+
.option('-I, --ignore-segments <time-segments>', '忽略的视频片段,用-分割起始时间点,多个用逗号分隔。如:0-10,20-30')
|
|
70
71
|
.action(async (urls) => {
|
|
71
72
|
const options = getOptions();
|
|
72
73
|
utils_js_1.logger.debug(urls, options);
|
|
@@ -88,7 +89,9 @@ commander_1.program
|
|
|
88
89
|
const opts = getOptions();
|
|
89
90
|
if (opts.debug)
|
|
90
91
|
options.debug = true;
|
|
91
|
-
|
|
92
|
+
if (opts.cacheDir)
|
|
93
|
+
options.cacheDir = opts.cacheDir;
|
|
94
|
+
utils_js_1.logger.debug('[cli][server]', opts, options);
|
|
92
95
|
Promise.resolve().then(() => __importStar(require('./server/download-server.js'))).then(m => {
|
|
93
96
|
new m.DLServer(options);
|
|
94
97
|
});
|
package/cjs/lib/m3u8-download.js
CHANGED
|
@@ -105,8 +105,35 @@ async function m3u8InfoParse(url, options = {}) {
|
|
|
105
105
|
utils_js_1.logger.error('[parseM3U8][failed]', e.message);
|
|
106
106
|
console.log(e);
|
|
107
107
|
});
|
|
108
|
-
if (m3u8Info && m3u8Info?.tsCount > 0)
|
|
108
|
+
if (m3u8Info && m3u8Info?.tsCount > 0) {
|
|
109
109
|
result.m3u8Info = m3u8Info;
|
|
110
|
+
if (options.ignoreSegments) {
|
|
111
|
+
const timeSegments = options.ignoreSegments
|
|
112
|
+
.split(',')
|
|
113
|
+
.map(d => d.split(/[- ]+/).map(d => +d.trim()))
|
|
114
|
+
.filter(d => d[0] && d[1] && d[0] !== d[1]);
|
|
115
|
+
if (timeSegments.length) {
|
|
116
|
+
const total = m3u8Info.data.length;
|
|
117
|
+
m3u8Info.data = m3u8Info.data.filter(item => {
|
|
118
|
+
for (let [start, end] of timeSegments) {
|
|
119
|
+
if (start > end)
|
|
120
|
+
[start, end] = [end, start];
|
|
121
|
+
if (item.timeline + item.duration / 2 >= start && item.timeline + item.duration / 2 <= end) {
|
|
122
|
+
m3u8Info.duration -= item.duration;
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return true;
|
|
127
|
+
});
|
|
128
|
+
const ignoredCount = total - m3u8Info.data.length;
|
|
129
|
+
if (ignoredCount) {
|
|
130
|
+
m3u8Info.tsCount = m3u8Info.data.length;
|
|
131
|
+
utils_js_1.logger.info(`[parseM3U8][ignoreSegments] ignored ${(0, console_log_colors_1.cyanBright)(ignoredCount)} segments`);
|
|
132
|
+
m3u8Info.duration = +Number(m3u8Info.duration).toFixed(2);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
110
137
|
return result;
|
|
111
138
|
}
|
|
112
139
|
/**
|
package/cjs/lib/parseM3u8.js
CHANGED
|
@@ -83,7 +83,7 @@ async function parseM3U8(content, cacheDir = './cache', headers) {
|
|
|
83
83
|
result.data.push({
|
|
84
84
|
index: i,
|
|
85
85
|
duration: item.duration,
|
|
86
|
-
timeline: item.timeline,
|
|
86
|
+
timeline: item.timeline || result.duration,
|
|
87
87
|
uri: item.uri,
|
|
88
88
|
tsOut: (0, node_path_1.resolve)(cacheDir, `${(0, fe_utils_1.md5)(item.uri)}.ts`),
|
|
89
89
|
keyUri: item.key?.uri || '',
|
|
@@ -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");
|
|
@@ -43,19 +44,21 @@ const format_options_js_1 = require("../lib/format-options.js");
|
|
|
43
44
|
const m3u8_download_js_1 = require("../lib/m3u8-download.js");
|
|
44
45
|
const utils_js_1 = require("../lib/utils.js");
|
|
45
46
|
const index_js_1 = require("../video-parser/index.js");
|
|
47
|
+
const rootDir = (0, node_path_1.resolve)(__dirname, '../..');
|
|
46
48
|
class DLServer {
|
|
47
49
|
app = null;
|
|
48
50
|
wss = null;
|
|
49
51
|
/** DS 参数 */
|
|
50
52
|
options = {
|
|
51
53
|
port: Number(process.env.DS_PORT) || 6600,
|
|
52
|
-
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'),
|
|
53
55
|
token: process.env.DS_SECRET || process.env.DS_TOKEN || '',
|
|
54
56
|
debug: process.env.DS_DEBUG === '1',
|
|
57
|
+
limitFileAccess: !['0', 'false'].includes(process.env.DS_LIMTE_FILE_ACCESS),
|
|
55
58
|
};
|
|
56
59
|
serverInfo = {
|
|
57
60
|
version: '',
|
|
58
|
-
ariang: (0, node_fs_1.existsSync)((0, node_path_1.resolve)(
|
|
61
|
+
ariang: (0, node_fs_1.existsSync)((0, node_path_1.resolve)(rootDir, 'client/ariang/index.html')),
|
|
59
62
|
};
|
|
60
63
|
cfg = {
|
|
61
64
|
/** 支持 web 设置修改的参数 */
|
|
@@ -85,7 +88,7 @@ class DLServer {
|
|
|
85
88
|
opts.cacheDir = (0, node_path_1.resolve)(opts.cacheDir);
|
|
86
89
|
if (!opts.configPath)
|
|
87
90
|
opts.configPath = (0, node_path_1.resolve)(opts.cacheDir, 'config.json');
|
|
88
|
-
const pkgFile = (0, node_path_1.resolve)(
|
|
91
|
+
const pkgFile = (0, node_path_1.resolve)(rootDir, 'package.json');
|
|
89
92
|
if ((0, node_fs_1.existsSync)(pkgFile)) {
|
|
90
93
|
const pkg = JSON.parse((0, node_fs_1.readFileSync)(pkgFile, 'utf8'));
|
|
91
94
|
this.serverInfo.version = pkg.version;
|
|
@@ -101,7 +104,7 @@ class DLServer {
|
|
|
101
104
|
this.loadCache();
|
|
102
105
|
await this.createApp();
|
|
103
106
|
this.initRouters();
|
|
104
|
-
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);
|
|
105
108
|
}
|
|
106
109
|
loadCache() {
|
|
107
110
|
const cacheFile = (0, node_path_1.resolve)(this.options.cacheDir, 'cache.json');
|
|
@@ -187,8 +190,24 @@ class DLServer {
|
|
|
187
190
|
const wss = new WebSocketServer({ server });
|
|
188
191
|
this.app = app;
|
|
189
192
|
this.wss = wss;
|
|
193
|
+
app.use((req, res, next) => {
|
|
194
|
+
if (['/', '/index.html'].includes(req.path)) {
|
|
195
|
+
const version = this.serverInfo.version;
|
|
196
|
+
let indexHtml = (0, node_fs_1.readFileSync)((0, node_path_1.resolve)(rootDir, 'client/index.html'), 'utf-8').replaceAll('{{version}}', version);
|
|
197
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.resolve)(rootDir, 'client/local/cdn'))) {
|
|
198
|
+
indexHtml = indexHtml
|
|
199
|
+
.replaceAll('https://s4.zstatic.net/ajax/libs', 'local/cdn')
|
|
200
|
+
.replaceAll(/integrity=.+\n/g, '')
|
|
201
|
+
.replace('https://cdn.tailwindcss.com/3.4.16', 'local/cdn/tailwindcss/3.4.16/tailwindcss.min.js');
|
|
202
|
+
}
|
|
203
|
+
res.setHeader('content-type', 'text/html').send(indexHtml);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
next();
|
|
207
|
+
}
|
|
208
|
+
});
|
|
190
209
|
app.use(express.json());
|
|
191
|
-
app.use(express.static((0, node_path_1.resolve)(
|
|
210
|
+
app.use(express.static((0, node_path_1.resolve)(rootDir, 'client')));
|
|
192
211
|
app.use((req, res, next) => {
|
|
193
212
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
194
213
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
|
|
@@ -230,20 +249,19 @@ class DLServer {
|
|
|
230
249
|
return { app, wss };
|
|
231
250
|
}
|
|
232
251
|
startDownload(url, options) {
|
|
233
|
-
const cacheItem = this.dlCache.get(url);
|
|
234
252
|
const dlOptions = (0, format_options_js_1.formatOptions)(url, { ...this.cfg.dlOptions, ...options, cacheDir: this.options.cacheDir })[1];
|
|
235
|
-
|
|
236
|
-
|
|
253
|
+
const cacheItem = this.dlCache.get(url) || { options, dlOptions, status: 'pending', url };
|
|
254
|
+
utils_js_1.logger.debug('startDownload', url, dlOptions, cacheItem.status);
|
|
255
|
+
if (cacheItem.status === 'resume')
|
|
237
256
|
return cacheItem.options;
|
|
238
|
-
if (
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
let workPoll = cacheItem
|
|
246
|
-
const defaultItem = { options, dlOptions, status: 'resume', url };
|
|
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
|
+
this.dlCache.set(url, cacheItem);
|
|
261
|
+
this.wsSend('progress', url);
|
|
262
|
+
if (cacheItem.status === 'pending')
|
|
263
|
+
return cacheItem.options;
|
|
264
|
+
let workPoll = cacheItem.workPoll;
|
|
247
265
|
const opts = {
|
|
248
266
|
...dlOptions,
|
|
249
267
|
showProgress: dlOptions.debug || this.options.debug,
|
|
@@ -251,8 +269,8 @@ class DLServer {
|
|
|
251
269
|
workPoll = wp;
|
|
252
270
|
},
|
|
253
271
|
onProgress: (_finished, _total, current, stats) => {
|
|
254
|
-
const item = this.dlCache.get(url) ||
|
|
255
|
-
const status = item
|
|
272
|
+
const item = this.dlCache.get(url) || cacheItem;
|
|
273
|
+
const status = item.status || 'resume';
|
|
256
274
|
Object.assign(item, { ...stats, current, options: dlOptions, status, workPoll, url });
|
|
257
275
|
this.dlCache.set(url, item);
|
|
258
276
|
this.saveCache();
|
|
@@ -261,7 +279,7 @@ class DLServer {
|
|
|
261
279
|
},
|
|
262
280
|
};
|
|
263
281
|
const afterDownload = (r, url) => {
|
|
264
|
-
const item = this.dlCache.get(url) ||
|
|
282
|
+
const item = this.dlCache.get(url) || cacheItem;
|
|
265
283
|
if (r.filepath && (0, node_fs_1.existsSync)(r.filepath)) {
|
|
266
284
|
item.localVideo = r.filepath;
|
|
267
285
|
item.downloadedSize = (0, node_fs_1.statSync)(r.filepath).size;
|
|
@@ -276,18 +294,12 @@ class DLServer {
|
|
|
276
294
|
this.wsSend('progress', url);
|
|
277
295
|
this.saveCache();
|
|
278
296
|
// 找到一个 pending 的任务,开始下载
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
break;
|
|
284
|
-
}
|
|
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]);
|
|
285
301
|
}
|
|
286
302
|
};
|
|
287
|
-
if (cacheItem)
|
|
288
|
-
cacheItem.status = 'resume';
|
|
289
|
-
else
|
|
290
|
-
this.dlCache.set(url, defaultItem);
|
|
291
303
|
try {
|
|
292
304
|
if (dlOptions.type === 'parser') {
|
|
293
305
|
const vp = new index_js_1.VideoParser();
|
|
@@ -312,12 +324,10 @@ class DLServer {
|
|
|
312
324
|
}
|
|
313
325
|
else if (type === 'progress' && typeof data === 'string') {
|
|
314
326
|
const item = this.dlCache.get(data);
|
|
315
|
-
if (item)
|
|
316
|
-
const { workPoll, ...stats } = item;
|
|
317
|
-
data = [{ ...stats, url: data }];
|
|
318
|
-
}
|
|
319
|
-
else
|
|
327
|
+
if (!item)
|
|
320
328
|
return;
|
|
329
|
+
const { workPoll, ...stats } = item;
|
|
330
|
+
data = [{ ...stats, url: data }];
|
|
321
331
|
}
|
|
322
332
|
// 广播进度信息给所有客户端
|
|
323
333
|
this.wss.clients.forEach(client => {
|
|
@@ -404,11 +414,12 @@ class DLServer {
|
|
|
404
414
|
const urlsToPause = all ? [...this.dlCache.keys()] : urls;
|
|
405
415
|
const list = [];
|
|
406
416
|
for (const url of urlsToPause) {
|
|
407
|
-
const
|
|
417
|
+
const item = this.dlCache.get(url);
|
|
408
418
|
if (['resume', 'pending'].includes(item?.status)) {
|
|
409
|
-
(0, m3u8_download_js_1.m3u8DLStop)(url, workPoll);
|
|
419
|
+
(0, m3u8_download_js_1.m3u8DLStop)(url, item.workPoll);
|
|
410
420
|
item.status = item.tsSuccess === item.tsCount ? 'done' : 'pause';
|
|
411
|
-
|
|
421
|
+
const { workPoll, ...tItem } = item;
|
|
422
|
+
list.push(tItem);
|
|
412
423
|
}
|
|
413
424
|
}
|
|
414
425
|
if (list.length)
|
|
@@ -472,8 +483,9 @@ class DLServer {
|
|
|
472
483
|
if (!(0, node_fs_1.existsSync)(filepath))
|
|
473
484
|
filepath += '.m3u8';
|
|
474
485
|
}
|
|
486
|
+
const allowedDirs = [this.options.cacheDir, this.cfg.dlOptions.saveDir];
|
|
475
487
|
if (!(0, node_fs_1.existsSync)(filepath)) {
|
|
476
|
-
for (const dir of
|
|
488
|
+
for (const dir of allowedDirs) {
|
|
477
489
|
const tpath = (0, node_path_1.resolve)(dir, filepath);
|
|
478
490
|
if ((0, node_fs_1.existsSync)(tpath)) {
|
|
479
491
|
filepath = tpath;
|
|
@@ -483,7 +495,9 @@ class DLServer {
|
|
|
483
495
|
}
|
|
484
496
|
else {
|
|
485
497
|
filepath = (0, node_path_1.resolve)(filepath);
|
|
486
|
-
|
|
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);
|
|
487
501
|
res.send({ message: 'Access denied', code: 403 });
|
|
488
502
|
return;
|
|
489
503
|
}
|
package/cjs/types/m3u8.d.ts
CHANGED
|
@@ -6,7 +6,9 @@ export interface TsItemInfo {
|
|
|
6
6
|
m3u8: string;
|
|
7
7
|
/** ts 文件次序 */
|
|
8
8
|
index: number;
|
|
9
|
+
/** 视频片段时长 */
|
|
9
10
|
duration: number;
|
|
11
|
+
/** 时间线(起点) */
|
|
10
12
|
timeline: number;
|
|
11
13
|
/** ts 文件下载 url 地址 */
|
|
12
14
|
uri: string;
|
|
@@ -119,6 +121,8 @@ export interface M3u8DLOptions {
|
|
|
119
121
|
saveDir?: string;
|
|
120
122
|
/** 临时文件保存目录。默认为 cache/<md5(url)> */
|
|
121
123
|
cacheDir?: string;
|
|
124
|
+
/** 忽略的时间片段,单位为秒,多段以逗号分割。示例: 0-10,100-110 */
|
|
125
|
+
ignoreSegments?: string;
|
|
122
126
|
/** 下载成功后是否删除临时文件。默认为 true。保存临时文件可以在重复下载时识别缓存 */
|
|
123
127
|
delCache?: boolean;
|
|
124
128
|
/** 文件已存在时是否仍强制下载和生成。默认为 false,文件已存在则跳过 */
|
package/client/index.html
CHANGED
|
@@ -6,156 +6,26 @@
|
|
|
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
|
+
integrity="sha512-Evv84Mr4kqVGRNSgIGL/F/aIDqQb7xQ2vcrdIwxfjThSH8CSR7PBEakCr51Ck+w+/U6swU2Im1vVX0SVk9ABhg=="
|
|
13
|
+
crossorigin="anonymous" referrerpolicy="no-referrer" />
|
|
14
|
+
<link rel="stylesheet" href="https://s4.zstatic.net/ajax/libs/sweetalert2/11.16.1/sweetalert2.min.css"
|
|
15
|
+
integrity="sha512-WnmDqbbAeHb7Put2nIAp7KNlnMup0FXVviOctducz1omuXB/hHK3s2vd3QLffK/CvvFUKrpioxdo+/Jo3k/xIw=="
|
|
16
|
+
crossorigin="anonymous" referrerpolicy="no-referrer" />
|
|
17
|
+
<link rel="stylesheet" href="style.css?v={{version}}">
|
|
18
|
+
|
|
19
|
+
<script src="https://cdn.tailwindcss.com/3.4.16"></script>
|
|
12
20
|
<script src="https://s4.zstatic.net/ajax/libs/vue/2.7.16/vue.min.js"
|
|
13
21
|
integrity="sha512-Wx8niGbPNCD87mSuF0sBRytwW2+2ZFr7HwVDF8krCb3egstCc4oQfig+/cfg2OHd82KcUlOYxlSDAqdHqK5TCw=="
|
|
14
22
|
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
|
15
23
|
<script src="https://s4.zstatic.net/ajax/libs/sweetalert2/11.16.1/sweetalert2.min.js"
|
|
16
24
|
integrity="sha512-LGHBR+kJ5jZSIzhhdfytPoEHzgaYuTRifq9g5l6ja6/k9NAOsAi5dQh4zQF6JIRB8cAYxTRedERUF+97/KuivQ=="
|
|
17
25
|
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
|
18
|
-
<script src="https://s4.zstatic.net/ajax/libs/blueimp-md5/2.19.0/js/md5.min.js"
|
|
19
|
-
|
|
20
|
-
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
|
21
|
-
<link rel="stylesheet" href="https://s4.zstatic.net/ajax/libs/sweetalert2/11.16.1/sweetalert2.min.css"
|
|
22
|
-
integrity="sha512-WnmDqbbAeHb7Put2nIAp7KNlnMup0FXVviOctducz1omuXB/hHK3s2vd3QLffK/CvvFUKrpioxdo+/Jo3k/xIw=="
|
|
23
|
-
crossorigin="anonymous" referrerpolicy="no-referrer" />
|
|
24
|
-
<link rel="stylesheet" href="https://s4.zstatic.net/ajax/libs/font-awesome/6.7.2/css/all.min.css"
|
|
25
|
-
integrity="sha512-Evv84Mr4kqVGRNSgIGL/F/aIDqQb7xQ2vcrdIwxfjThSH8CSR7PBEakCr51Ck+w+/U6swU2Im1vVX0SVk9ABhg=="
|
|
26
|
-
crossorigin="anonymous" referrerpolicy="no-referrer" />
|
|
27
|
-
|
|
28
|
-
<style>
|
|
29
|
-
#app {
|
|
30
|
-
max-width: 1400px;
|
|
31
|
-
margin: auto;
|
|
32
|
-
min-height: 100vh;
|
|
33
|
-
background-color: #f5f5f5;
|
|
34
|
-
position: relative;
|
|
35
|
-
}
|
|
26
|
+
<script src="https://s4.zstatic.net/ajax/libs/blueimp-md5/2.19.0/js/md5.min.js" crossorigin="anonymous"
|
|
27
|
+
referrerpolicy="no-referrer"></script>
|
|
36
28
|
|
|
37
|
-
.sidebar {
|
|
38
|
-
background-color: #fff;
|
|
39
|
-
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
|
|
40
|
-
transition: all 0.3s ease;
|
|
41
|
-
position: absolute;
|
|
42
|
-
left: 0;
|
|
43
|
-
top: 0;
|
|
44
|
-
bottom: 0;
|
|
45
|
-
width: 16rem;
|
|
46
|
-
z-index: 1;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
.download-item {
|
|
50
|
-
transition: all 0.3s ease;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
.download-item:hover {
|
|
54
|
-
background-color: #f8f9fa;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
.progress-bar {
|
|
58
|
-
height: 4px;
|
|
59
|
-
transition: width 0.3s ease;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
.nav-item {
|
|
63
|
-
transition: all 0.3s ease;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
.nav-item:hover {
|
|
67
|
-
background-color: #f0f0f0;
|
|
68
|
-
transform: translateX(4px);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
.nav-item.active {
|
|
72
|
-
background-color: #e6f3ff;
|
|
73
|
-
color: #1890ff;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/* 移动端适配 */
|
|
77
|
-
@media (max-width: 768px) {
|
|
78
|
-
.sidebar {
|
|
79
|
-
transform: translateX(-100%);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
.sidebar.show {
|
|
83
|
-
transform: translateX(0);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
.menu-toggle {
|
|
87
|
-
display: block !important;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
.main-content {
|
|
91
|
-
margin-left: 0 !important;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
.menu-toggle {
|
|
96
|
-
display: none;
|
|
97
|
-
position: fixed;
|
|
98
|
-
top: 1rem;
|
|
99
|
-
left: 1rem;
|
|
100
|
-
z-index: 51;
|
|
101
|
-
padding: 0.5rem;
|
|
102
|
-
background: white;
|
|
103
|
-
border-radius: 0.5rem;
|
|
104
|
-
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
105
|
-
box-shadow: 0 2px 4px #9bdff5;
|
|
106
|
-
width: 42px;
|
|
107
|
-
height: 42px;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
.main-content {
|
|
111
|
-
transition: margin-left 0.3s ease;
|
|
112
|
-
margin-left: 16rem;
|
|
113
|
-
width: calc(100% - 16rem);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/* 自定义toast样式 */
|
|
117
|
-
.custom-toast {
|
|
118
|
-
position: fixed;
|
|
119
|
-
top: 20px;
|
|
120
|
-
right: 20px;
|
|
121
|
-
min-width: 250px;
|
|
122
|
-
max-width: 400px;
|
|
123
|
-
padding: 15px;
|
|
124
|
-
border-radius: 4px;
|
|
125
|
-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
126
|
-
background: #fff;
|
|
127
|
-
color: #333;
|
|
128
|
-
z-index: 99999;
|
|
129
|
-
display: flex;
|
|
130
|
-
align-items: center;
|
|
131
|
-
transform: translateX(150%);
|
|
132
|
-
transition: transform 0.3s ease;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
.custom-toast.show {
|
|
136
|
-
transform: translateX(0);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
.custom-toast.hide {
|
|
140
|
-
transform: translateX(150%);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
.custom-toast-success {
|
|
144
|
-
border-left: 4px solid #28a745;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
.custom-toast-error {
|
|
148
|
-
border-left: 4px solid #dc3545;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
.custom-toast-warning {
|
|
152
|
-
border-left: 4px solid #ffc107;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
.custom-toast-info {
|
|
156
|
-
border-left: 4px solid #17a2b8;
|
|
157
|
-
}
|
|
158
|
-
</style>
|
|
159
29
|
</head>
|
|
160
30
|
|
|
161
31
|
<body>
|
|
@@ -343,16 +213,6 @@
|
|
|
343
213
|
<i class="fas fa-hourglass-half mr-1"></i>
|
|
344
214
|
<span>剩余: {{ T.formatTimeCost(task.remainingTime || 0) }}</span>
|
|
345
215
|
</span>
|
|
346
|
-
<!-- <span class="flex items-center">
|
|
347
|
-
<i class="fas fa-sort-amount-up mr-1"></i>
|
|
348
|
-
<select v-model="task.priority" @change="updatePriority(url, task.priority)"
|
|
349
|
-
class="text-sm border rounded px-1 py-0.5 focus:ring-blue-500 focus:border-blue-500"
|
|
350
|
-
aria-label="设置下载优先级">
|
|
351
|
-
<option value="0">普通优先级</option>
|
|
352
|
-
<option value="1">高优先级</option>
|
|
353
|
-
<option value="2">最高优先级</option>
|
|
354
|
-
</select>
|
|
355
|
-
</span> -->
|
|
356
216
|
</div>
|
|
357
217
|
</div>
|
|
358
218
|
<div class="flex space-x-2">
|
|
@@ -546,6 +406,14 @@
|
|
|
546
406
|
class="text-blue-500 hover:text-blue-600">
|
|
547
407
|
{{serverInfo.version}}</a>
|
|
548
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>
|
|
549
417
|
</div>
|
|
550
418
|
</div>
|
|
551
419
|
|
|
@@ -622,6 +490,13 @@ services:
|
|
|
622
490
|
'content-type': 'application/json',
|
|
623
491
|
authorization: localStorage.getItem('token') || '',
|
|
624
492
|
},
|
|
493
|
+
initTJ() {
|
|
494
|
+
if (!window._hmt) window._hmt = [];
|
|
495
|
+
const hm = document.createElement("script");
|
|
496
|
+
hm.src = "https://hm.baidu.com/hm.js?0b21eda331ac9677a4c546dea88616d0";
|
|
497
|
+
const s = document.getElementsByTagName("script")[0];
|
|
498
|
+
s.parentNode.insertBefore(hm, s);
|
|
499
|
+
},
|
|
625
500
|
request(method, url, data, headers = {}) {
|
|
626
501
|
return fetch(url, {
|
|
627
502
|
method,
|
|
@@ -646,7 +521,10 @@ services:
|
|
|
646
521
|
alert(msg, p) {
|
|
647
522
|
p = typeof msg === 'object' ? msg : Object.assign({ text: msg }, p);
|
|
648
523
|
if (!p.toast) p.allowOutsideClick = false;
|
|
649
|
-
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: '取消' });
|
|
650
528
|
},
|
|
651
529
|
toast(msg, p) {
|
|
652
530
|
p = (typeof msg === 'object' ? msg : Object.assign({ text: msg }, p));
|
|
@@ -727,13 +605,16 @@ services:
|
|
|
727
605
|
};
|
|
728
606
|
|
|
729
607
|
Vue.prototype.T = T;
|
|
608
|
+
T.initTJ();
|
|
730
609
|
window.APP = new Vue({
|
|
731
610
|
el: '#app',
|
|
732
611
|
data: {
|
|
733
612
|
ws: null,
|
|
734
613
|
serverInfo: {
|
|
735
|
-
version: '',
|
|
614
|
+
version: '{{version}}',
|
|
736
615
|
ariang: false,
|
|
616
|
+
newVersion: '',
|
|
617
|
+
appUpdateMessage: '',
|
|
737
618
|
},
|
|
738
619
|
config: {
|
|
739
620
|
/** 并发下载线程数。取决于服务器限制,过多可能会容易下载失败。一般建议不超过 8 个。默认为 cpu数 * 2,但不超过 8 */
|
|
@@ -793,7 +674,7 @@ services:
|
|
|
793
674
|
|
|
794
675
|
// 排序:resume > pending > pause > error > done
|
|
795
676
|
const statusOrder = { resume: 0, pending: 1, pause: 2, error: 3, done: 4 };
|
|
796
|
-
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)));
|
|
797
678
|
|
|
798
679
|
// 更新 queueStatus
|
|
799
680
|
const queueStatus = {
|
|
@@ -814,10 +695,43 @@ services:
|
|
|
814
695
|
}
|
|
815
696
|
},
|
|
816
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
|
+
},
|
|
817
730
|
forceUpdate: function () {
|
|
818
731
|
const now = Date.now();
|
|
819
732
|
if (now - this.forceUpdateTime > 500) {
|
|
820
733
|
this.forceUpdateTime = now;
|
|
734
|
+
this.tasks = { ...this.tasks };
|
|
821
735
|
this.$forceUpdate();
|
|
822
736
|
} else {
|
|
823
737
|
if (this.forceUpdateTimeout) clearTimeout(this.forceUpdateTimeout);
|
|
@@ -836,7 +750,7 @@ services:
|
|
|
836
750
|
|
|
837
751
|
switch (type) {
|
|
838
752
|
case 'serverInfo':
|
|
839
|
-
this.serverInfo
|
|
753
|
+
Object.assign(this.serverInfo, data);
|
|
840
754
|
break;
|
|
841
755
|
case 'tasks':
|
|
842
756
|
this.tasks = data;
|
|
@@ -930,6 +844,11 @@ services:
|
|
|
930
844
|
<input id="saveDir" class="w-full p-2 border rounded-lg focus:ring-blue-500" placeholder="请输入保存路径" value="${this.config.saveDir || ''}">
|
|
931
845
|
</div>
|
|
932
846
|
|
|
847
|
+
<div class="mt-4">
|
|
848
|
+
<label class="block text-sm font-bold text-gray-700 mb-1">删除时间片段(适用于移除广告片段的情况)</label>
|
|
849
|
+
<input id="ignoreSegments" class="w-full p-2 border rounded-lg focus:ring-blue-500" placeholder="以-分割起止时间,多个以逗号分隔。示例:0-10,20-100">
|
|
850
|
+
</div>
|
|
851
|
+
|
|
933
852
|
<div class="mt-4">
|
|
934
853
|
<label class="block text-sm font-bold text-gray-700 mb-1">自定义请求头</label>
|
|
935
854
|
<textarea id="headers" class="w-full p-2 border rounded-lg focus:ring-blue-500" rows="4" placeholder="每行一个请求头(微博视频必须设置 Cookie),格式:Key: Value 例如: Referer: https://example.com Cookie: token=123"></textarea>
|
|
@@ -947,6 +866,7 @@ services:
|
|
|
947
866
|
const filename = document.getElementById('filename').value.trim();
|
|
948
867
|
const saveDir = document.getElementById('saveDir').value.trim();
|
|
949
868
|
const headersText = document.getElementById('headers').value.trim();
|
|
869
|
+
const ignoreSegments = document.getElementById('ignoreSegments').value.trim();
|
|
950
870
|
|
|
951
871
|
if (!urlsText) {
|
|
952
872
|
Swal.showValidationMessage('请输入至少一个 M3U8 链接');
|
|
@@ -971,6 +891,7 @@ services:
|
|
|
971
891
|
filename: item.name || (filename ? `${filename}第${idx + 1}集` : ''),
|
|
972
892
|
saveDir,
|
|
973
893
|
headers,
|
|
894
|
+
ignoreSegments,
|
|
974
895
|
}));
|
|
975
896
|
}
|
|
976
897
|
}).then((result) => {
|
|
@@ -981,6 +902,7 @@ services:
|
|
|
981
902
|
startBatchDownload: async function (list) {
|
|
982
903
|
try {
|
|
983
904
|
list.forEach(async (item, idx) => {
|
|
905
|
+
Object.entries(item).forEach(([key, value]) => !value && delete item[key]);
|
|
984
906
|
this.tasks[item.url] = { status: 'resume', progress: 0, speed: 0, remainingTime: 0, size: 0 };
|
|
985
907
|
});
|
|
986
908
|
const r = await T.post('/download', { list });
|
|
@@ -1048,6 +970,7 @@ services:
|
|
|
1048
970
|
deleteCache: result.value.deleteCache,
|
|
1049
971
|
deleteVideo: result.value.deleteVideo
|
|
1050
972
|
});
|
|
973
|
+
|
|
1051
974
|
if (!r.code) {
|
|
1052
975
|
T.toast(r.message || '已删除选中的下载');
|
|
1053
976
|
urls.forEach(url => (delete this.tasks[url]));
|
|
@@ -1180,6 +1103,7 @@ services:
|
|
|
1180
1103
|
T.reqHeaders.authorization = this.token ? `${this.token}` : '';
|
|
1181
1104
|
this.fetchConfig().then(d => d && this.wsConnect());
|
|
1182
1105
|
window.addEventListener('resize', this.handleResize);
|
|
1106
|
+
this.initEventsForApp();
|
|
1183
1107
|
},
|
|
1184
1108
|
beforeDestroy() {
|
|
1185
1109
|
window.removeEventListener('resize', this.handleResize);
|
package/client/play.html
CHANGED
|
@@ -2,43 +2,44 @@
|
|
|
2
2
|
<html lang="zh">
|
|
3
3
|
|
|
4
4
|
<head>
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
<title>M3U8 在线播放</title>
|
|
6
|
+
<meta charset="utf-8">
|
|
7
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
8
|
+
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0,viewport-fit=cover"
|
|
9
|
+
name="viewport" />
|
|
10
|
+
<style>
|
|
11
|
+
body {
|
|
12
|
+
margin: 0;
|
|
13
|
+
}
|
|
14
|
+
</style>
|
|
15
15
|
</head>
|
|
16
16
|
|
|
17
17
|
<body>
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
<div id="dplayer"></div>
|
|
19
|
+
<script src="https://s4.zstatic.net/ajax/libs/hls.js/1.5.18/hls.min.js"
|
|
20
|
+
integrity="sha512-hARxLWym80kd0Bzl5/93OuW1ujaKfvmJ90yTKak/RB67JuNIjtErU2H7H3bteyfzMuqiSK0tXarT7eK6lEWBBA=="
|
|
21
|
+
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
|
22
|
+
<script src="https://s4.zstatic.net/ajax/libs/dplayer/1.26.0/DPlayer.min.js" crossorigin="anonymous"
|
|
23
|
+
referrerpolicy="no-referrer"></script>
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
25
|
+
<script>
|
|
26
|
+
const url = location.href.split('url=')[1];
|
|
27
|
+
if (!url) {
|
|
28
|
+
document.getElementById('dplayer').innerText = '请传入播放地址参数 url=';
|
|
29
|
+
} else {
|
|
30
|
+
const dp = new DPlayer({
|
|
31
|
+
container: document.getElementById('dplayer'),
|
|
32
|
+
autoplay: true,
|
|
33
|
+
video: {
|
|
34
|
+
url: decodeURIComponent(url),
|
|
35
|
+
type: 'auto',
|
|
36
|
+
},
|
|
37
|
+
pluginOptions: {
|
|
38
|
+
hls: {},
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
</script>
|
|
42
43
|
</body>
|
|
43
44
|
|
|
44
45
|
</html>
|
package/client/style.css
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#app {
|
|
2
|
+
max-width: 1400px;
|
|
3
|
+
margin: auto;
|
|
4
|
+
min-height: 100vh;
|
|
5
|
+
background-color: #f5f5f5;
|
|
6
|
+
position: relative;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.sidebar {
|
|
10
|
+
background-color: #fff;
|
|
11
|
+
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
|
|
12
|
+
transition: all 0.3s ease;
|
|
13
|
+
position: absolute;
|
|
14
|
+
left: 0;
|
|
15
|
+
top: 0;
|
|
16
|
+
bottom: 0;
|
|
17
|
+
width: 16rem;
|
|
18
|
+
z-index: 1;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.download-item {
|
|
22
|
+
transition: all 0.3s ease;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.download-item:hover {
|
|
26
|
+
background-color: #f8f9fa;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.progress-bar {
|
|
30
|
+
height: 4px;
|
|
31
|
+
transition: width 0.3s ease;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.nav-item {
|
|
35
|
+
transition: all 0.3s ease;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.nav-item:hover {
|
|
39
|
+
background-color: #f0f0f0;
|
|
40
|
+
transform: translateX(4px);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.nav-item.active {
|
|
44
|
+
background-color: #e6f3ff;
|
|
45
|
+
color: #1890ff;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* 移动端适配 */
|
|
49
|
+
@media (max-width: 768px) {
|
|
50
|
+
.sidebar {
|
|
51
|
+
transform: translateX(-100%);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.sidebar.show {
|
|
55
|
+
transform: translateX(0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.menu-toggle {
|
|
59
|
+
display: block !important;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.main-content {
|
|
63
|
+
margin-left: 0 !important;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.menu-toggle {
|
|
68
|
+
display: none;
|
|
69
|
+
position: fixed;
|
|
70
|
+
top: 1rem;
|
|
71
|
+
left: 1rem;
|
|
72
|
+
z-index: 51;
|
|
73
|
+
padding: 0.5rem;
|
|
74
|
+
background: white;
|
|
75
|
+
border-radius: 0.5rem;
|
|
76
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
77
|
+
box-shadow: 0 2px 4px #9bdff5;
|
|
78
|
+
width: 42px;
|
|
79
|
+
height: 42px;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.main-content {
|
|
83
|
+
transition: margin-left 0.3s ease;
|
|
84
|
+
margin-left: 16rem;
|
|
85
|
+
width: calc(100% - 16rem);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* 自定义toast样式 */
|
|
89
|
+
.custom-toast {
|
|
90
|
+
position: fixed;
|
|
91
|
+
top: 20px;
|
|
92
|
+
right: 20px;
|
|
93
|
+
min-width: 250px;
|
|
94
|
+
max-width: 400px;
|
|
95
|
+
padding: 15px 25px 15px 15px;
|
|
96
|
+
border-radius: 4px;
|
|
97
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
98
|
+
background: #fff;
|
|
99
|
+
color: #333;
|
|
100
|
+
z-index: 99999;
|
|
101
|
+
display: flex;
|
|
102
|
+
align-items: center;
|
|
103
|
+
transform: translateX(150%);
|
|
104
|
+
transition: transform 0.3s ease;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.custom-toast.show {
|
|
108
|
+
transform: translateX(0);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.custom-toast.hide {
|
|
112
|
+
transform: translateX(150%);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.custom-toast-success {
|
|
116
|
+
border-left: 4px solid #28a745;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.custom-toast-error {
|
|
120
|
+
border-left: 4px solid #dc3545;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.custom-toast-warning {
|
|
124
|
+
border-left: 4px solid #ffc107;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.custom-toast-info {
|
|
128
|
+
border-left: 4px solid #17a2b8;
|
|
129
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lzwme/m3u8-dl",
|
|
3
|
-
"version": "1.
|
|
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": "^
|
|
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": {
|
|
@@ -73,7 +73,9 @@
|
|
|
73
73
|
},
|
|
74
74
|
"files": [
|
|
75
75
|
"cjs",
|
|
76
|
-
"client",
|
|
76
|
+
"client/",
|
|
77
|
+
"!client/ariang",
|
|
78
|
+
"!client/local",
|
|
77
79
|
"!cjs/type.js",
|
|
78
80
|
"!cjs/cli.d.ts",
|
|
79
81
|
"bin"
|