coomer-downloader 3.4.0 → 3.4.2
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/README.md +1 -1
- package/dist/index.js +378 -341
- package/package.json +1 -1
- package/src/api/index.ts +6 -32
- package/src/api/provider.ts +7 -0
- package/src/api/{bunkr.ts → providers/bunkr.ts} +15 -5
- package/src/api/{coomer-api.ts → providers/coomer.ts} +49 -34
- package/src/api/{gofile.ts → providers/gofile.ts} +20 -11
- package/src/api/providers/plainfile.ts +17 -0
- package/src/api/{nsfw.xxx.ts → providers/reddit.ts} +20 -10
- package/src/api/resolver.ts +23 -0
- package/src/cli/ui/app.tsx +12 -13
- package/src/cli/ui/components/file.tsx +7 -3
- package/src/cli/ui/components/filelist-state.tsx +48 -0
- package/src/cli/ui/components/filelist.tsx +10 -46
- package/src/cli/ui/components/index.ts +2 -1
- package/src/cli/ui/components/preview.tsx +1 -1
- package/src/cli/ui/hooks/downloader.ts +20 -15
- package/src/cli/ui/hooks/input.ts +0 -1
- package/src/cli/ui/store/index.ts +1 -1
- package/src/{services → core}/downloader.ts +10 -7
- package/src/core/file.ts +29 -0
- package/src/{services/file.ts → core/filelist.ts} +7 -33
- package/src/core/index.ts +3 -0
- package/src/index.ts +3 -3
- package/src/api/plain-curl.ts +0 -9
- /package/src/{logger/index.ts → utils/logger.ts} +0 -0
package/dist/index.js
CHANGED
|
@@ -3,28 +3,71 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import process2 from "node:process";
|
|
5
5
|
|
|
6
|
-
// src/api/bunkr.ts
|
|
6
|
+
// src/api/providers/bunkr.ts
|
|
7
7
|
import * as cheerio from "cheerio";
|
|
8
8
|
import { fetch } from "undici";
|
|
9
9
|
|
|
10
|
-
// src/
|
|
10
|
+
// src/utils/io.ts
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
12
|
+
import fs from "node:fs";
|
|
13
|
+
import { access, constants, unlink } from "node:fs/promises";
|
|
14
|
+
import { pipeline } from "node:stream/promises";
|
|
15
|
+
async function getFileSize(filepath) {
|
|
16
|
+
let size = 0;
|
|
17
|
+
if (fs.existsSync(filepath)) {
|
|
18
|
+
size = (await fs.promises.stat(filepath)).size || 0;
|
|
19
|
+
}
|
|
20
|
+
return size;
|
|
21
|
+
}
|
|
22
|
+
async function getFileHash(filepath) {
|
|
23
|
+
const hash = createHash("sha256");
|
|
24
|
+
const filestream = fs.createReadStream(filepath);
|
|
25
|
+
await pipeline(filestream, hash);
|
|
26
|
+
return hash.digest("hex");
|
|
27
|
+
}
|
|
28
|
+
function mkdir(filepath) {
|
|
29
|
+
if (!fs.existsSync(filepath)) {
|
|
30
|
+
fs.mkdirSync(filepath, { recursive: true });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function deleteFile(path2) {
|
|
34
|
+
await access(path2, constants.F_OK);
|
|
35
|
+
await unlink(path2);
|
|
36
|
+
}
|
|
37
|
+
function sanitizeFilename(name) {
|
|
38
|
+
if (!name) return name;
|
|
39
|
+
return name.replace(/[<>:"/\\|?*\x00-\x1F]/g, "-").replace(/\s+/g, " ").trim().replace(/[.]+$/, "");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/core/file.ts
|
|
43
|
+
var CoomerFile = class _CoomerFile {
|
|
44
|
+
constructor(name, url, filepath = "", size, downloaded = 0, content) {
|
|
45
|
+
this.name = name;
|
|
46
|
+
this.url = url;
|
|
47
|
+
this.filepath = filepath;
|
|
48
|
+
this.size = size;
|
|
49
|
+
this.downloaded = downloaded;
|
|
50
|
+
this.content = content;
|
|
51
|
+
}
|
|
52
|
+
active = false;
|
|
53
|
+
hash;
|
|
54
|
+
async calcDownloadedSize() {
|
|
55
|
+
this.downloaded = await getFileSize(this.filepath);
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
get textContent() {
|
|
59
|
+
const text = `${this.name || ""} ${this.content || ""}`.toLowerCase();
|
|
60
|
+
return text;
|
|
61
|
+
}
|
|
62
|
+
static from(f) {
|
|
63
|
+
return new _CoomerFile(f.name, f.url, f.filepath, f.size, f.downloaded, f.content);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// src/core/filelist.ts
|
|
11
68
|
import os from "node:os";
|
|
12
69
|
import path from "node:path";
|
|
13
70
|
|
|
14
|
-
// src/logger/index.ts
|
|
15
|
-
import pino from "pino";
|
|
16
|
-
var logger = pino(
|
|
17
|
-
{
|
|
18
|
-
level: "debug"
|
|
19
|
-
},
|
|
20
|
-
pino.destination({
|
|
21
|
-
dest: "./debug.log",
|
|
22
|
-
append: false,
|
|
23
|
-
sync: true
|
|
24
|
-
})
|
|
25
|
-
);
|
|
26
|
-
var logger_default = logger;
|
|
27
|
-
|
|
28
71
|
// src/utils/duplicates.ts
|
|
29
72
|
function collectUniquesAndDuplicatesBy(xs, k) {
|
|
30
73
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -70,38 +113,6 @@ function parseSizeValue(s) {
|
|
|
70
113
|
return Math.floor(val * mult);
|
|
71
114
|
}
|
|
72
115
|
|
|
73
|
-
// src/utils/io.ts
|
|
74
|
-
import { createHash } from "node:crypto";
|
|
75
|
-
import fs from "node:fs";
|
|
76
|
-
import { access, constants, unlink } from "node:fs/promises";
|
|
77
|
-
import { pipeline } from "node:stream/promises";
|
|
78
|
-
async function getFileSize(filepath) {
|
|
79
|
-
let size = 0;
|
|
80
|
-
if (fs.existsSync(filepath)) {
|
|
81
|
-
size = (await fs.promises.stat(filepath)).size || 0;
|
|
82
|
-
}
|
|
83
|
-
return size;
|
|
84
|
-
}
|
|
85
|
-
async function getFileHash(filepath) {
|
|
86
|
-
const hash = createHash("sha256");
|
|
87
|
-
const filestream = fs.createReadStream(filepath);
|
|
88
|
-
await pipeline(filestream, hash);
|
|
89
|
-
return hash.digest("hex");
|
|
90
|
-
}
|
|
91
|
-
function mkdir(filepath) {
|
|
92
|
-
if (!fs.existsSync(filepath)) {
|
|
93
|
-
fs.mkdirSync(filepath, { recursive: true });
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
async function deleteFile(path2) {
|
|
97
|
-
await access(path2, constants.F_OK);
|
|
98
|
-
await unlink(path2);
|
|
99
|
-
}
|
|
100
|
-
function sanitizeFilename(name) {
|
|
101
|
-
if (!name) return name;
|
|
102
|
-
return name.replace(/[<>"/\\|?*\x00-\x1F]/g, "-").replace(/\s+/g, " ").trim().replace(/[.]+$/, "");
|
|
103
|
-
}
|
|
104
|
-
|
|
105
116
|
// src/utils/mediatypes.ts
|
|
106
117
|
function isImage(name) {
|
|
107
118
|
return /\.(jpg|jpeg|png|gif|bmp|tiff|webp|avif)$/i.test(name);
|
|
@@ -113,36 +124,14 @@ function testMediaType(name, type) {
|
|
|
113
124
|
return type === "image" ? isImage(name) : isVideo(name);
|
|
114
125
|
}
|
|
115
126
|
|
|
116
|
-
// src/
|
|
117
|
-
var CoomerFile = class _CoomerFile {
|
|
118
|
-
constructor(name, url, filepath = "", size, downloaded = 0, content) {
|
|
119
|
-
this.name = name;
|
|
120
|
-
this.url = url;
|
|
121
|
-
this.filepath = filepath;
|
|
122
|
-
this.size = size;
|
|
123
|
-
this.downloaded = downloaded;
|
|
124
|
-
this.content = content;
|
|
125
|
-
}
|
|
126
|
-
active = false;
|
|
127
|
-
hash;
|
|
128
|
-
async getDownloadedSize() {
|
|
129
|
-
this.downloaded = await getFileSize(this.filepath);
|
|
130
|
-
return this;
|
|
131
|
-
}
|
|
132
|
-
get textContent() {
|
|
133
|
-
const text = `${this.name || ""} ${this.content || ""}`.toLowerCase();
|
|
134
|
-
return text;
|
|
135
|
-
}
|
|
136
|
-
static from(f) {
|
|
137
|
-
return new _CoomerFile(f.name, f.url, f.filepath, f.size, f.downloaded, f.content);
|
|
138
|
-
}
|
|
139
|
-
};
|
|
127
|
+
// src/core/filelist.ts
|
|
140
128
|
var CoomerFileList = class {
|
|
141
129
|
constructor(files = []) {
|
|
142
130
|
this.files = files;
|
|
143
131
|
}
|
|
144
132
|
dirPath;
|
|
145
133
|
dirName;
|
|
134
|
+
provider;
|
|
146
135
|
setDirPath(dir, dirName) {
|
|
147
136
|
dirName = dirName || this.dirName;
|
|
148
137
|
if (dir === "./") {
|
|
@@ -172,7 +161,7 @@ var CoomerFileList = class {
|
|
|
172
161
|
}
|
|
173
162
|
async calculateFileSizes() {
|
|
174
163
|
for (const file of this.files) {
|
|
175
|
-
await file.
|
|
164
|
+
await file.calcDownloadedSize();
|
|
176
165
|
}
|
|
177
166
|
return this;
|
|
178
167
|
}
|
|
@@ -187,8 +176,6 @@ var CoomerFileList = class {
|
|
|
187
176
|
file.hash = await getFileHash(file.filepath);
|
|
188
177
|
}
|
|
189
178
|
const { duplicates } = collectUniquesAndDuplicatesBy(this.files, "hash");
|
|
190
|
-
console.log({ duplicates });
|
|
191
|
-
logger_default.debug(`duplicates: ${JSON.stringify(duplicates)}`);
|
|
192
179
|
duplicates.forEach((f) => {
|
|
193
180
|
deleteFile(f.filepath);
|
|
194
181
|
});
|
|
@@ -199,7 +186,7 @@ var CoomerFileList = class {
|
|
|
199
186
|
}
|
|
200
187
|
};
|
|
201
188
|
|
|
202
|
-
// src/api/bunkr.ts
|
|
189
|
+
// src/api/providers/bunkr.ts
|
|
203
190
|
async function getEncryptionData(slug) {
|
|
204
191
|
const response = await fetch("https://bunkr.cr/api/vs", {
|
|
205
192
|
method: "POST",
|
|
@@ -212,7 +199,9 @@ function decryptEncryptedUrl(encryptionData) {
|
|
|
212
199
|
const secretKey = `SECRET_KEY_${Math.floor(encryptionData.timestamp / 3600)}`;
|
|
213
200
|
const encryptedUrlBuffer = Buffer.from(encryptionData.url, "base64");
|
|
214
201
|
const secretKeyBuffer = Buffer.from(secretKey, "utf-8");
|
|
215
|
-
return Array.from(encryptedUrlBuffer).map(
|
|
202
|
+
return Array.from(encryptedUrlBuffer).map(
|
|
203
|
+
(byte, i) => String.fromCharCode(byte ^ secretKeyBuffer[i % secretKeyBuffer.length])
|
|
204
|
+
).join("");
|
|
216
205
|
}
|
|
217
206
|
async function getFileData(url, name) {
|
|
218
207
|
const slug = url.split("/").pop();
|
|
@@ -244,10 +233,15 @@ async function getGalleryFiles(url) {
|
|
|
244
233
|
}
|
|
245
234
|
return filelist;
|
|
246
235
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
236
|
+
var BunkrAPI = class {
|
|
237
|
+
testURL(url) {
|
|
238
|
+
return /bunkr/.test(url.origin);
|
|
239
|
+
}
|
|
240
|
+
async getData(url) {
|
|
241
|
+
const filelist = await getGalleryFiles(url);
|
|
242
|
+
return filelist;
|
|
243
|
+
}
|
|
244
|
+
};
|
|
251
245
|
|
|
252
246
|
// src/utils/requests.ts
|
|
253
247
|
import { CookieAgent } from "http-cookie-agent/undici";
|
|
@@ -278,20 +272,7 @@ function fetchByteRange(url, downloadedSize, signal) {
|
|
|
278
272
|
return fetch2(url, { headers: requestHeaders, signal });
|
|
279
273
|
}
|
|
280
274
|
|
|
281
|
-
// src/api/coomer
|
|
282
|
-
var SERVERS = ["n1", "n2", "n3", "n4"];
|
|
283
|
-
function tryFixCoomerUrl(url, attempts) {
|
|
284
|
-
if (attempts < 2 && isImage(url)) {
|
|
285
|
-
return url.replace(/\/data\//, "/thumbnail/data/").replace(/n\d\./, "img.");
|
|
286
|
-
}
|
|
287
|
-
const server = url.match(/n\d\./)?.[0].slice(0, 2);
|
|
288
|
-
const i = SERVERS.indexOf(server);
|
|
289
|
-
if (i !== -1) {
|
|
290
|
-
const newServer = SERVERS[(i + 1) % SERVERS.length];
|
|
291
|
-
return url.replace(/n\d./, `${newServer}.`);
|
|
292
|
-
}
|
|
293
|
-
return url;
|
|
294
|
-
}
|
|
275
|
+
// src/api/providers/coomer.ts
|
|
295
276
|
async function getUserProfileData(user) {
|
|
296
277
|
const url = `${user.domain}/api/v1/${user.service}/user/${user.id}/profile`;
|
|
297
278
|
const result = await fetchWithGlobalHeader(url).then((r) => r.json());
|
|
@@ -344,15 +325,33 @@ async function parseUser(url) {
|
|
|
344
325
|
const { name } = await getUserProfileData({ domain, service, id });
|
|
345
326
|
return { domain, service, id, name };
|
|
346
327
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
328
|
+
var CoomerAPI = class _CoomerAPI {
|
|
329
|
+
static SERVERS = ["n1", "n2", "n3", "n4"];
|
|
330
|
+
fixURL(url, retries) {
|
|
331
|
+
if (retries < 2 && isImage(url)) {
|
|
332
|
+
return url.replace(/\/data\//, "/thumbnail/data/").replace(/n\d\./, "img.");
|
|
333
|
+
}
|
|
334
|
+
const server = url.match(/n\d\./)?.[0].slice(0, 2);
|
|
335
|
+
const i = _CoomerAPI.SERVERS.indexOf(server);
|
|
336
|
+
if (i !== -1) {
|
|
337
|
+
const newServer = _CoomerAPI.SERVERS[(i + 1) % _CoomerAPI.SERVERS.length];
|
|
338
|
+
return url.replace(/n\d./, `${newServer}.`);
|
|
339
|
+
}
|
|
340
|
+
return url;
|
|
341
|
+
}
|
|
342
|
+
testURL(url) {
|
|
343
|
+
return /coomer|kemono/.test(url.origin);
|
|
344
|
+
}
|
|
345
|
+
async getData(url) {
|
|
346
|
+
setGlobalHeaders({ accept: "text/css" });
|
|
347
|
+
const user = await parseUser(url);
|
|
348
|
+
const filelist = await getUserFiles(user);
|
|
349
|
+
filelist.dirName = `${user.name}-${user.service}`;
|
|
350
|
+
return filelist;
|
|
351
|
+
}
|
|
352
|
+
};
|
|
354
353
|
|
|
355
|
-
// src/api/gofile.ts
|
|
354
|
+
// src/api/providers/gofile.ts
|
|
356
355
|
import { fetch as fetch3 } from "undici";
|
|
357
356
|
async function getToken() {
|
|
358
357
|
const response = await fetch3("https://api.gofile.io/accounts", {
|
|
@@ -362,7 +361,7 @@ async function getToken() {
|
|
|
362
361
|
if (data.status === "ok") {
|
|
363
362
|
return data.data.token;
|
|
364
363
|
}
|
|
365
|
-
throw new Error("
|
|
364
|
+
throw new Error("Token Not Found");
|
|
366
365
|
}
|
|
367
366
|
async function getWebsiteToken() {
|
|
368
367
|
const response = await fetch3("https://gofile.io/dist/js/global.js");
|
|
@@ -392,19 +391,54 @@ async function getFolderFiles(id, token, websiteToken) {
|
|
|
392
391
|
);
|
|
393
392
|
return new CoomerFileList(files);
|
|
394
393
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
394
|
+
var GofileAPI = class {
|
|
395
|
+
testURL(url) {
|
|
396
|
+
return /gofile\.io/.test(url.origin);
|
|
397
|
+
}
|
|
398
|
+
async getData(url) {
|
|
399
|
+
const id = url.match(/gofile.io\/d\/(\w+)/)?.[1];
|
|
400
|
+
const token = await getToken();
|
|
401
|
+
const websiteToken = await getWebsiteToken();
|
|
402
|
+
const filelist = await getFolderFiles(id, token, websiteToken);
|
|
403
|
+
filelist.dirName = `gofile-${id}`;
|
|
404
|
+
setGlobalHeaders({ Cookie: `accountToken=${token}` });
|
|
405
|
+
return filelist;
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
// src/api/providers/plainfile.ts
|
|
410
|
+
var PlainFileAPI = class {
|
|
411
|
+
testURL(url) {
|
|
412
|
+
return /\.\w+/.test(url.pathname);
|
|
413
|
+
}
|
|
414
|
+
async getData(url) {
|
|
415
|
+
const name = url.split("/").pop();
|
|
416
|
+
const file = CoomerFile.from({ name, url });
|
|
417
|
+
const filelist = new CoomerFileList([file]);
|
|
418
|
+
filelist.dirName = "";
|
|
419
|
+
return filelist;
|
|
420
|
+
}
|
|
421
|
+
};
|
|
404
422
|
|
|
405
|
-
// src/api/
|
|
423
|
+
// src/api/providers/reddit.ts
|
|
406
424
|
import * as cheerio2 from "cheerio";
|
|
407
425
|
import { fetch as fetch4 } from "undici";
|
|
426
|
+
|
|
427
|
+
// src/utils/logger.ts
|
|
428
|
+
import pino from "pino";
|
|
429
|
+
var logger = pino(
|
|
430
|
+
{
|
|
431
|
+
level: "debug"
|
|
432
|
+
},
|
|
433
|
+
pino.destination({
|
|
434
|
+
dest: "./debug.log",
|
|
435
|
+
append: false,
|
|
436
|
+
sync: true
|
|
437
|
+
})
|
|
438
|
+
);
|
|
439
|
+
var logger_default = logger;
|
|
440
|
+
|
|
441
|
+
// src/api/providers/reddit.ts
|
|
408
442
|
async function getUserPage(user, offset) {
|
|
409
443
|
const url = `https://nsfw.xxx/page/${offset}?nsfw[]=0&types[]=image&types[]=video&types[]=gallery&slider=1&jsload=1&user=${user}&_=${Date.now()}`;
|
|
410
444
|
return fetch4(url).then((r) => r.text());
|
|
@@ -416,6 +450,7 @@ async function getUserPosts(user) {
|
|
|
416
450
|
if (page.length < 1) break;
|
|
417
451
|
const $ = cheerio2.load(page);
|
|
418
452
|
const newPosts = $("a").map((_, a) => $(a).attr("href")).get().filter((href) => href?.startsWith("https://nsfw.xxx/post"));
|
|
453
|
+
logger_default.debug({ count: posts.length });
|
|
419
454
|
posts.push(...newPosts);
|
|
420
455
|
}
|
|
421
456
|
return posts;
|
|
@@ -431,46 +466,35 @@ async function getPostsData(posts) {
|
|
|
431
466
|
const date = $(".sh-section .sh-section__passed").first().text().replace(/ /g, "-") || "";
|
|
432
467
|
const ext = src.split(".").pop();
|
|
433
468
|
const name = `${slug}-${date}.${ext}`;
|
|
469
|
+
logger_default.debug({ hehe: filelist.files.length, src });
|
|
434
470
|
filelist.files.push(CoomerFile.from({ name, url: src }));
|
|
435
471
|
}
|
|
436
472
|
return filelist;
|
|
437
473
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
const posts = await getUserPosts(user);
|
|
442
|
-
console.log("Fetching posts data...");
|
|
443
|
-
const filelist = await getPostsData(posts);
|
|
444
|
-
filelist.dirName = `${user}-reddit`;
|
|
445
|
-
return filelist;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// src/api/plain-curl.ts
|
|
449
|
-
async function getPlainFileData(url) {
|
|
450
|
-
const name = url.split("/").pop();
|
|
451
|
-
const file = CoomerFile.from({ name, url });
|
|
452
|
-
const filelist = new CoomerFileList([file]);
|
|
453
|
-
filelist.dirName = "";
|
|
454
|
-
return filelist;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
// src/api/index.ts
|
|
458
|
-
async function apiHandler(url_) {
|
|
459
|
-
const url = new URL(url_);
|
|
460
|
-
if (/^u\/\w+$/.test(url.origin)) {
|
|
461
|
-
return getRedditData(url.href);
|
|
474
|
+
var RedditAPI = class {
|
|
475
|
+
testURL(url) {
|
|
476
|
+
return /^\/user\/[\w-]+$/.test(url.pathname);
|
|
462
477
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
if (/gofile\.io/.test(url.origin)) {
|
|
470
|
-
return getGofileData(url.href);
|
|
478
|
+
async getData(url) {
|
|
479
|
+
const user = url.match(/^\/user\/([\w-]+)/)?.[1];
|
|
480
|
+
const posts = await getUserPosts(user);
|
|
481
|
+
const filelist = await getPostsData(posts);
|
|
482
|
+
filelist.dirName = `${user}-reddit`;
|
|
483
|
+
return filelist;
|
|
471
484
|
}
|
|
472
|
-
|
|
473
|
-
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
// src/api/resolver.ts
|
|
488
|
+
var providers = [RedditAPI, CoomerAPI, BunkrAPI, GofileAPI, PlainFileAPI];
|
|
489
|
+
async function resolveAPI(url_) {
|
|
490
|
+
const url = new URL(url_);
|
|
491
|
+
for (const p of providers) {
|
|
492
|
+
const provider = new p();
|
|
493
|
+
if (provider.testURL(url)) {
|
|
494
|
+
const filelist = await provider.getData(url.toString());
|
|
495
|
+
filelist.provider = provider;
|
|
496
|
+
return filelist;
|
|
497
|
+
}
|
|
474
498
|
}
|
|
475
499
|
throw Error("Invalid URL");
|
|
476
500
|
}
|
|
@@ -521,199 +545,13 @@ function argumentHander() {
|
|
|
521
545
|
|
|
522
546
|
// src/cli/ui/index.tsx
|
|
523
547
|
import { render } from "ink";
|
|
524
|
-
import
|
|
548
|
+
import React10 from "react";
|
|
525
549
|
|
|
526
550
|
// src/cli/ui/app.tsx
|
|
527
551
|
import { Box as Box7 } from "ink";
|
|
528
|
-
import
|
|
529
|
-
|
|
530
|
-
// src/cli/ui/components/file.tsx
|
|
531
|
-
import { Box as Box2, Spacer, Text as Text2 } from "ink";
|
|
532
|
-
import React3 from "react";
|
|
533
|
-
|
|
534
|
-
// src/utils/strings.ts
|
|
535
|
-
function b2mb(bytes) {
|
|
536
|
-
return (bytes / 1048576).toFixed(2);
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
// src/cli/ui/components/preview.tsx
|
|
540
|
-
import { Box } from "ink";
|
|
541
|
-
import Image, { TerminalInfoProvider } from "ink-picture";
|
|
542
|
-
import React from "react";
|
|
543
|
-
|
|
544
|
-
// src/cli/ui/store/index.ts
|
|
545
|
-
import { create } from "zustand";
|
|
546
|
-
var useInkStore = create((set) => ({
|
|
547
|
-
preview: false,
|
|
548
|
-
switchPreview: () => set((state) => ({
|
|
549
|
-
preview: !state.preview
|
|
550
|
-
})),
|
|
551
|
-
downloader: void 0,
|
|
552
|
-
setDownloader: (downloader) => set({ downloader })
|
|
553
|
-
}));
|
|
554
|
-
|
|
555
|
-
// src/cli/ui/components/preview.tsx
|
|
556
|
-
function Preview({ file }) {
|
|
557
|
-
const previewEnabled = useInkStore((state) => state.preview);
|
|
558
|
-
const bigEnough = file.downloaded > 50 * 1024;
|
|
559
|
-
const shouldShow = previewEnabled && bigEnough && isImage(file.filepath);
|
|
560
|
-
const imgInfo = `
|
|
561
|
-
can't read partial images yet...
|
|
562
|
-
actual size: ${file.size}}
|
|
563
|
-
downloaded: ${file.downloaded}}
|
|
564
|
-
`;
|
|
565
|
-
return shouldShow && /* @__PURE__ */ React.createElement(Box, { paddingX: 1 }, /* @__PURE__ */ React.createElement(TerminalInfoProvider, null, /* @__PURE__ */ React.createElement(Box, { width: 30, height: 15 }, /* @__PURE__ */ React.createElement(Image, { src: file.filepath, alt: imgInfo }))));
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
// src/cli/ui/components/spinner.tsx
|
|
569
|
-
import spinners from "cli-spinners";
|
|
570
|
-
import { Text } from "ink";
|
|
571
|
-
import React2, { useEffect, useState } from "react";
|
|
572
|
-
function Spinner({ type = "dots" }) {
|
|
573
|
-
const spinner = spinners[type];
|
|
574
|
-
const randomFrame = spinner.frames.length * Math.random() | 0;
|
|
575
|
-
const [frame, setFrame] = useState(randomFrame);
|
|
576
|
-
useEffect(() => {
|
|
577
|
-
const timer = setInterval(() => {
|
|
578
|
-
setFrame((previousFrame) => {
|
|
579
|
-
return (previousFrame + 1) % spinner.frames.length;
|
|
580
|
-
});
|
|
581
|
-
}, spinner.interval);
|
|
582
|
-
return () => {
|
|
583
|
-
clearInterval(timer);
|
|
584
|
-
};
|
|
585
|
-
}, [spinner]);
|
|
586
|
-
return /* @__PURE__ */ React2.createElement(Text, null, spinner.frames[frame]);
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
// src/cli/ui/components/file.tsx
|
|
590
|
-
function FileBox({ file }) {
|
|
591
|
-
const percentage = Number(file.downloaded / file.size * 100).toFixed(2);
|
|
592
|
-
return /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(
|
|
593
|
-
Box2,
|
|
594
|
-
{
|
|
595
|
-
borderStyle: "single",
|
|
596
|
-
borderColor: "magentaBright",
|
|
597
|
-
borderDimColor: true,
|
|
598
|
-
paddingX: 1,
|
|
599
|
-
flexDirection: "column"
|
|
600
|
-
},
|
|
601
|
-
/* @__PURE__ */ React3.createElement(Box2, null, /* @__PURE__ */ React3.createElement(Text2, { color: "blue", dimColor: true, wrap: "truncate-middle" }, file.name)),
|
|
602
|
-
/* @__PURE__ */ React3.createElement(Box2, { flexDirection: "row-reverse" }, /* @__PURE__ */ React3.createElement(Text2, { color: "cyan", dimColor: true }, b2mb(file.downloaded), "/", file.size ? b2mb(file.size) : "\u221E", " MB"), /* @__PURE__ */ React3.createElement(Text2, { color: "redBright", dimColor: true }, file.size ? ` ${percentage}% ` : ""), /* @__PURE__ */ React3.createElement(Spacer, null), /* @__PURE__ */ React3.createElement(Text2, { color: "green", dimColor: true }, /* @__PURE__ */ React3.createElement(Spinner, null)))
|
|
603
|
-
), /* @__PURE__ */ React3.createElement(Preview, { file }));
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
// src/cli/ui/components/filelist.tsx
|
|
607
|
-
import { Box as Box3, Text as Text3 } from "ink";
|
|
608
|
-
import React4 from "react";
|
|
609
|
-
function FileListStateBox({ filelist }) {
|
|
610
|
-
return /* @__PURE__ */ React4.createElement(
|
|
611
|
-
Box3,
|
|
612
|
-
{
|
|
613
|
-
paddingX: 1,
|
|
614
|
-
flexDirection: "column",
|
|
615
|
-
borderStyle: "single",
|
|
616
|
-
borderColor: "magenta",
|
|
617
|
-
borderDimColor: true
|
|
618
|
-
},
|
|
619
|
-
/* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(Box3, { marginRight: 1 }, /* @__PURE__ */ React4.createElement(Text3, { color: "cyanBright", dimColor: true }, "Found:")), /* @__PURE__ */ React4.createElement(Text3, { color: "blue", dimColor: true, wrap: "wrap" }, filelist.files.length)),
|
|
620
|
-
/* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(Box3, { marginRight: 1 }, /* @__PURE__ */ React4.createElement(Text3, { color: "cyanBright", dimColor: true }, "Downloaded:")), /* @__PURE__ */ React4.createElement(Text3, { color: "blue", dimColor: true, wrap: "wrap" }, filelist.getDownloaded().length)),
|
|
621
|
-
/* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(Box3, { width: 9 }, /* @__PURE__ */ React4.createElement(Text3, { color: "cyanBright", dimColor: true }, "Folder:")), /* @__PURE__ */ React4.createElement(Box3, { flexGrow: 1 }, /* @__PURE__ */ React4.createElement(Text3, { color: "blue", dimColor: true, wrap: "truncate-middle" }, filelist.dirPath)))
|
|
622
|
-
);
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
// src/cli/ui/components/keyboardinfo.tsx
|
|
626
|
-
import { Box as Box4, Text as Text4 } from "ink";
|
|
627
|
-
import React5 from "react";
|
|
628
|
-
var info = {
|
|
629
|
-
"s ": "skip current file",
|
|
630
|
-
p: "on/off image preview"
|
|
631
|
-
};
|
|
632
|
-
function KeyboardControlsInfo() {
|
|
633
|
-
const infoRender = Object.entries(info).map(([key, value]) => {
|
|
634
|
-
return /* @__PURE__ */ React5.createElement(Box4, { key }, /* @__PURE__ */ React5.createElement(Box4, { marginRight: 2 }, /* @__PURE__ */ React5.createElement(Text4, { color: "red", dimColor: true, bold: true }, key)), /* @__PURE__ */ React5.createElement(Text4, { dimColor: true, bold: false }, value));
|
|
635
|
-
});
|
|
636
|
-
return /* @__PURE__ */ React5.createElement(
|
|
637
|
-
Box4,
|
|
638
|
-
{
|
|
639
|
-
flexDirection: "column",
|
|
640
|
-
paddingX: 1,
|
|
641
|
-
borderStyle: "single",
|
|
642
|
-
borderColor: "gray",
|
|
643
|
-
borderDimColor: true
|
|
644
|
-
},
|
|
645
|
-
/* @__PURE__ */ React5.createElement(Box4, null, /* @__PURE__ */ React5.createElement(Text4, { color: "red", dimColor: true, bold: true }, "Keyboard controls:")),
|
|
646
|
-
infoRender
|
|
647
|
-
);
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
// src/cli/ui/components/loading.tsx
|
|
651
|
-
import { Box as Box5, Text as Text5 } from "ink";
|
|
652
|
-
import React6 from "react";
|
|
653
|
-
function Loading() {
|
|
654
|
-
return /* @__PURE__ */ React6.createElement(Box5, { paddingX: 1, borderDimColor: true, flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Box5, { alignSelf: "center" }, /* @__PURE__ */ React6.createElement(Text5, { dimColor: true, color: "redBright" }, "Fetching Data")), /* @__PURE__ */ React6.createElement(Box5, { alignSelf: "center" }, /* @__PURE__ */ React6.createElement(Text5, { color: "blueBright", dimColor: true }, /* @__PURE__ */ React6.createElement(Spinner, { type: "grenade" }))));
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
// src/cli/ui/components/titlebar.tsx
|
|
658
|
-
import { Box as Box6, Spacer as Spacer2, Text as Text6 } from "ink";
|
|
659
|
-
import React7 from "react";
|
|
660
|
-
|
|
661
|
-
// package.json
|
|
662
|
-
var version = "3.4.0";
|
|
663
|
-
|
|
664
|
-
// src/cli/ui/components/titlebar.tsx
|
|
665
|
-
function TitleBar() {
|
|
666
|
-
return /* @__PURE__ */ React7.createElement(Box6, null, /* @__PURE__ */ React7.createElement(Spacer2, null), /* @__PURE__ */ React7.createElement(Box6, { borderColor: "magenta", borderStyle: "arrow" }, /* @__PURE__ */ React7.createElement(Text6, { color: "cyanBright" }, "Coomer-Downloader ", version)), /* @__PURE__ */ React7.createElement(Spacer2, null));
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
// src/cli/ui/hooks/downloader.ts
|
|
670
|
-
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
671
|
-
var useDownloaderHook = () => {
|
|
672
|
-
const downloader = useInkStore((state) => state.downloader);
|
|
673
|
-
const filelist = downloader?.filelist;
|
|
674
|
-
const [_, setHelper] = useState2(0);
|
|
675
|
-
useEffect2(() => {
|
|
676
|
-
downloader?.subject.subscribe(({ type }) => {
|
|
677
|
-
if (type === "FILE_DOWNLOADING_START" || type === "FILE_DOWNLOADING_END" || type === "CHUNK_DOWNLOADING_UPDATE") {
|
|
678
|
-
setHelper(Date.now());
|
|
679
|
-
}
|
|
680
|
-
});
|
|
681
|
-
});
|
|
682
|
-
};
|
|
683
|
-
|
|
684
|
-
// src/cli/ui/hooks/input.ts
|
|
685
|
-
import { useInput } from "ink";
|
|
686
|
-
var useInputHook = () => {
|
|
687
|
-
const downloader = useInkStore((state) => state.downloader);
|
|
688
|
-
const switchPreview = useInkStore((state) => state.switchPreview);
|
|
689
|
-
useInput((input) => {
|
|
690
|
-
if (input === "s") {
|
|
691
|
-
downloader?.skip();
|
|
692
|
-
}
|
|
693
|
-
if (input === "p") {
|
|
694
|
-
switchPreview();
|
|
695
|
-
}
|
|
696
|
-
});
|
|
697
|
-
};
|
|
698
|
-
|
|
699
|
-
// src/cli/ui/app.tsx
|
|
700
|
-
function App() {
|
|
701
|
-
useInputHook();
|
|
702
|
-
useDownloaderHook();
|
|
703
|
-
const downloader = useInkStore((state) => state.downloader);
|
|
704
|
-
const filelist = downloader?.filelist;
|
|
705
|
-
const isFilelist = filelist instanceof CoomerFileList;
|
|
706
|
-
return /* @__PURE__ */ React8.createElement(Box7, { borderStyle: "single", flexDirection: "column", borderColor: "blue", width: 80 }, /* @__PURE__ */ React8.createElement(TitleBar, null), !isFilelist ? /* @__PURE__ */ React8.createElement(Loading, null) : /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(Box7, null, /* @__PURE__ */ React8.createElement(Box7, null, /* @__PURE__ */ React8.createElement(FileListStateBox, { filelist })), /* @__PURE__ */ React8.createElement(Box7, { flexBasis: 30 }, /* @__PURE__ */ React8.createElement(KeyboardControlsInfo, null))), filelist.getActiveFiles().map((file) => {
|
|
707
|
-
return /* @__PURE__ */ React8.createElement(FileBox, { file, key: file.name });
|
|
708
|
-
})));
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
// src/cli/ui/index.tsx
|
|
712
|
-
function createReactInk() {
|
|
713
|
-
return render(/* @__PURE__ */ React9.createElement(App, null));
|
|
714
|
-
}
|
|
552
|
+
import React9 from "react";
|
|
715
553
|
|
|
716
|
-
// src/
|
|
554
|
+
// src/core/downloader.ts
|
|
717
555
|
import fs2 from "node:fs";
|
|
718
556
|
import { Readable, Transform } from "node:stream";
|
|
719
557
|
import { pipeline as pipeline2 } from "node:stream/promises";
|
|
@@ -760,7 +598,7 @@ var Timer = class _Timer {
|
|
|
760
598
|
}
|
|
761
599
|
};
|
|
762
600
|
|
|
763
|
-
// src/
|
|
601
|
+
// src/core/downloader.ts
|
|
764
602
|
var Downloader = class {
|
|
765
603
|
constructor(filelist, minSize, maxSize, chunkTimeout = 3e4, chunkFetchRetries = 5, fetchRetries = 7) {
|
|
766
604
|
this.filelist = filelist;
|
|
@@ -855,8 +693,8 @@ var Downloader = class {
|
|
|
855
693
|
if (signal.reason === "FILE_SKIP") return;
|
|
856
694
|
}
|
|
857
695
|
if (retries > 0) {
|
|
858
|
-
if (
|
|
859
|
-
file.url =
|
|
696
|
+
if (this.filelist.provider?.fixURL) {
|
|
697
|
+
file.url = this.filelist.provider.fixURL(file.url, retries);
|
|
860
698
|
}
|
|
861
699
|
await sleep(1e3);
|
|
862
700
|
return await this.downloadFile(file, retries - 1);
|
|
@@ -878,11 +716,210 @@ var Downloader = class {
|
|
|
878
716
|
}
|
|
879
717
|
};
|
|
880
718
|
|
|
719
|
+
// src/cli/ui/components/file.tsx
|
|
720
|
+
import { Box as Box2, Spacer, Text as Text2 } from "ink";
|
|
721
|
+
import React3 from "react";
|
|
722
|
+
|
|
723
|
+
// src/utils/strings.ts
|
|
724
|
+
function b2mb(bytes) {
|
|
725
|
+
return (bytes / 1048576).toFixed(2);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// src/cli/ui/hooks/downloader.ts
|
|
729
|
+
import { useRef, useSyncExternalStore } from "react";
|
|
730
|
+
|
|
731
|
+
// src/cli/ui/store/index.ts
|
|
732
|
+
import { create } from "zustand";
|
|
733
|
+
var useInkStore = create((set) => ({
|
|
734
|
+
preview: false,
|
|
735
|
+
switchPreview: () => set((state) => ({
|
|
736
|
+
preview: !state.preview
|
|
737
|
+
})),
|
|
738
|
+
downloader: void 0,
|
|
739
|
+
setDownloader: (downloader) => set({ downloader })
|
|
740
|
+
}));
|
|
741
|
+
|
|
742
|
+
// src/cli/ui/hooks/downloader.ts
|
|
743
|
+
var useDownloaderHook = (subjectEvents) => {
|
|
744
|
+
const downloader = useInkStore((state) => state.downloader);
|
|
745
|
+
const versionRef = useRef(0);
|
|
746
|
+
useSyncExternalStore(
|
|
747
|
+
(onStoreChange) => {
|
|
748
|
+
if (!downloader) return () => {
|
|
749
|
+
};
|
|
750
|
+
const sub = downloader.subject.subscribe(({ type }) => {
|
|
751
|
+
if (subjectEvents.includes(type)) {
|
|
752
|
+
versionRef.current++;
|
|
753
|
+
onStoreChange();
|
|
754
|
+
}
|
|
755
|
+
});
|
|
756
|
+
return () => sub.unsubscribe();
|
|
757
|
+
},
|
|
758
|
+
() => versionRef.current
|
|
759
|
+
);
|
|
760
|
+
return downloader?.filelist;
|
|
761
|
+
};
|
|
762
|
+
|
|
763
|
+
// src/cli/ui/components/preview.tsx
|
|
764
|
+
import { Box } from "ink";
|
|
765
|
+
import Image, { TerminalInfoProvider } from "ink-picture";
|
|
766
|
+
import React from "react";
|
|
767
|
+
function Preview({ file }) {
|
|
768
|
+
const previewEnabled = useInkStore((state) => state.preview);
|
|
769
|
+
const bigEnough = file.downloaded > 50 * 1024;
|
|
770
|
+
const shouldShow = previewEnabled && bigEnough && isImage(file.filepath);
|
|
771
|
+
const imgInfo = `
|
|
772
|
+
can't read partial images yet...
|
|
773
|
+
actual size: ${file.size}}
|
|
774
|
+
downloaded: ${file.downloaded}}
|
|
775
|
+
`;
|
|
776
|
+
return shouldShow && /* @__PURE__ */ React.createElement(Box, { paddingX: 1 }, /* @__PURE__ */ React.createElement(TerminalInfoProvider, null, /* @__PURE__ */ React.createElement(Box, { width: 30, height: 15 }, /* @__PURE__ */ React.createElement(Image, { src: file.filepath, alt: imgInfo }))));
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// src/cli/ui/components/spinner.tsx
|
|
780
|
+
import spinners from "cli-spinners";
|
|
781
|
+
import { Text } from "ink";
|
|
782
|
+
import React2, { useEffect, useState } from "react";
|
|
783
|
+
function Spinner({ type = "dots" }) {
|
|
784
|
+
const spinner = spinners[type];
|
|
785
|
+
const randomFrame = spinner.frames.length * Math.random() | 0;
|
|
786
|
+
const [frame, setFrame] = useState(randomFrame);
|
|
787
|
+
useEffect(() => {
|
|
788
|
+
const timer = setInterval(() => {
|
|
789
|
+
setFrame((previousFrame) => {
|
|
790
|
+
return (previousFrame + 1) % spinner.frames.length;
|
|
791
|
+
});
|
|
792
|
+
}, spinner.interval);
|
|
793
|
+
return () => {
|
|
794
|
+
clearInterval(timer);
|
|
795
|
+
};
|
|
796
|
+
}, [spinner]);
|
|
797
|
+
return /* @__PURE__ */ React2.createElement(Text, null, spinner.frames[frame]);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// src/cli/ui/components/file.tsx
|
|
801
|
+
var FileBox = React3.memo(({ file }) => {
|
|
802
|
+
useDownloaderHook(["CHUNK_DOWNLOADING_UPDATE"]);
|
|
803
|
+
const percentage = Number(file.downloaded / file.size * 100).toFixed(2);
|
|
804
|
+
return /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(
|
|
805
|
+
Box2,
|
|
806
|
+
{
|
|
807
|
+
borderStyle: "single",
|
|
808
|
+
borderColor: "magentaBright",
|
|
809
|
+
borderDimColor: true,
|
|
810
|
+
paddingX: 1,
|
|
811
|
+
flexDirection: "column"
|
|
812
|
+
},
|
|
813
|
+
/* @__PURE__ */ React3.createElement(Box2, null, /* @__PURE__ */ React3.createElement(Text2, { color: "blue", dimColor: true, wrap: "truncate-middle" }, file.name)),
|
|
814
|
+
/* @__PURE__ */ React3.createElement(Box2, { flexDirection: "row-reverse" }, /* @__PURE__ */ React3.createElement(Text2, { color: "cyan", dimColor: true }, b2mb(file.downloaded), "/", file.size ? b2mb(file.size) : "\u221E", " MB"), /* @__PURE__ */ React3.createElement(Text2, { color: "redBright", dimColor: true }, file.size ? ` ${percentage}% ` : ""), /* @__PURE__ */ React3.createElement(Spacer, null), /* @__PURE__ */ React3.createElement(Text2, { color: "green", dimColor: true }, /* @__PURE__ */ React3.createElement(Spinner, null)))
|
|
815
|
+
), /* @__PURE__ */ React3.createElement(Preview, { file }));
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
// src/cli/ui/components/filelist.tsx
|
|
819
|
+
import React4 from "react";
|
|
820
|
+
function FileListBox() {
|
|
821
|
+
const filelist = useDownloaderHook(["FILE_DOWNLOADING_START", "FILE_DOWNLOADING_END"]);
|
|
822
|
+
return /* @__PURE__ */ React4.createElement(React4.Fragment, null, filelist?.getActiveFiles().map((file) => {
|
|
823
|
+
return /* @__PURE__ */ React4.createElement(FileBox, { file, key: file.name });
|
|
824
|
+
}));
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// src/cli/ui/components/filelist-state.tsx
|
|
828
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
829
|
+
import React5 from "react";
|
|
830
|
+
function FileListStateBox() {
|
|
831
|
+
const filelist = useDownloaderHook(["FILE_DOWNLOADING_START", "FILE_DOWNLOADING_END"]);
|
|
832
|
+
return /* @__PURE__ */ React5.createElement(
|
|
833
|
+
Box3,
|
|
834
|
+
{
|
|
835
|
+
paddingX: 1,
|
|
836
|
+
flexDirection: "column",
|
|
837
|
+
borderStyle: "single",
|
|
838
|
+
borderColor: "magenta",
|
|
839
|
+
borderDimColor: true
|
|
840
|
+
},
|
|
841
|
+
/* @__PURE__ */ React5.createElement(Box3, null, /* @__PURE__ */ React5.createElement(Box3, { marginRight: 1 }, /* @__PURE__ */ React5.createElement(Text3, { color: "cyanBright", dimColor: true }, "Found:")), /* @__PURE__ */ React5.createElement(Text3, { color: "blue", dimColor: true, wrap: "wrap" }, filelist?.files.length)),
|
|
842
|
+
/* @__PURE__ */ React5.createElement(Box3, null, /* @__PURE__ */ React5.createElement(Box3, { marginRight: 1 }, /* @__PURE__ */ React5.createElement(Text3, { color: "cyanBright", dimColor: true }, "Downloaded:")), /* @__PURE__ */ React5.createElement(Text3, { color: "blue", dimColor: true, wrap: "wrap" })),
|
|
843
|
+
/* @__PURE__ */ React5.createElement(Box3, null, /* @__PURE__ */ React5.createElement(Box3, { width: 9 }, /* @__PURE__ */ React5.createElement(Text3, { color: "cyanBright", dimColor: true }, "Folder:")), /* @__PURE__ */ React5.createElement(Box3, { flexGrow: 1 }, /* @__PURE__ */ React5.createElement(Text3, { color: "blue", dimColor: true, wrap: "truncate-middle" }, filelist?.dirPath)))
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// src/cli/ui/components/keyboardinfo.tsx
|
|
848
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
849
|
+
import React6 from "react";
|
|
850
|
+
var info = {
|
|
851
|
+
"s ": "skip current file",
|
|
852
|
+
p: "on/off image preview"
|
|
853
|
+
};
|
|
854
|
+
function KeyboardControlsInfo() {
|
|
855
|
+
const infoRender = Object.entries(info).map(([key, value]) => {
|
|
856
|
+
return /* @__PURE__ */ React6.createElement(Box4, { key }, /* @__PURE__ */ React6.createElement(Box4, { marginRight: 2 }, /* @__PURE__ */ React6.createElement(Text4, { color: "red", dimColor: true, bold: true }, key)), /* @__PURE__ */ React6.createElement(Text4, { dimColor: true, bold: false }, value));
|
|
857
|
+
});
|
|
858
|
+
return /* @__PURE__ */ React6.createElement(
|
|
859
|
+
Box4,
|
|
860
|
+
{
|
|
861
|
+
flexDirection: "column",
|
|
862
|
+
paddingX: 1,
|
|
863
|
+
borderStyle: "single",
|
|
864
|
+
borderColor: "gray",
|
|
865
|
+
borderDimColor: true
|
|
866
|
+
},
|
|
867
|
+
/* @__PURE__ */ React6.createElement(Box4, null, /* @__PURE__ */ React6.createElement(Text4, { color: "red", dimColor: true, bold: true }, "Keyboard controls:")),
|
|
868
|
+
infoRender
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// src/cli/ui/components/loading.tsx
|
|
873
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
874
|
+
import React7 from "react";
|
|
875
|
+
function Loading() {
|
|
876
|
+
return /* @__PURE__ */ React7.createElement(Box5, { paddingX: 1, borderDimColor: true, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Box5, { alignSelf: "center" }, /* @__PURE__ */ React7.createElement(Text5, { dimColor: true, color: "redBright" }, "Fetching Data")), /* @__PURE__ */ React7.createElement(Box5, { alignSelf: "center" }, /* @__PURE__ */ React7.createElement(Text5, { color: "blueBright", dimColor: true }, /* @__PURE__ */ React7.createElement(Spinner, { type: "grenade" }))));
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// src/cli/ui/components/titlebar.tsx
|
|
880
|
+
import { Box as Box6, Spacer as Spacer2, Text as Text6 } from "ink";
|
|
881
|
+
import React8 from "react";
|
|
882
|
+
|
|
883
|
+
// package.json
|
|
884
|
+
var version = "3.4.2";
|
|
885
|
+
|
|
886
|
+
// src/cli/ui/components/titlebar.tsx
|
|
887
|
+
function TitleBar() {
|
|
888
|
+
return /* @__PURE__ */ React8.createElement(Box6, null, /* @__PURE__ */ React8.createElement(Spacer2, null), /* @__PURE__ */ React8.createElement(Box6, { borderColor: "magenta", borderStyle: "arrow" }, /* @__PURE__ */ React8.createElement(Text6, { color: "cyanBright" }, "Coomer-Downloader ", version)), /* @__PURE__ */ React8.createElement(Spacer2, null));
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// src/cli/ui/hooks/input.ts
|
|
892
|
+
import { useInput } from "ink";
|
|
893
|
+
var useInputHook = () => {
|
|
894
|
+
const downloader = useInkStore((state) => state.downloader);
|
|
895
|
+
const switchPreview = useInkStore((state) => state.switchPreview);
|
|
896
|
+
useInput((input) => {
|
|
897
|
+
if (input === "s") {
|
|
898
|
+
downloader?.skip();
|
|
899
|
+
}
|
|
900
|
+
if (input === "p") {
|
|
901
|
+
switchPreview();
|
|
902
|
+
}
|
|
903
|
+
});
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
// src/cli/ui/app.tsx
|
|
907
|
+
function App() {
|
|
908
|
+
useInputHook();
|
|
909
|
+
const filelist = useDownloaderHook(["FILES_DOWNLOADING_START"]);
|
|
910
|
+
return /* @__PURE__ */ React9.createElement(Box7, { borderStyle: "single", flexDirection: "column", borderColor: "blue", width: 80 }, /* @__PURE__ */ React9.createElement(TitleBar, null), !(filelist instanceof CoomerFileList) ? /* @__PURE__ */ React9.createElement(Loading, null) : /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(Box7, null, /* @__PURE__ */ React9.createElement(Box7, null, /* @__PURE__ */ React9.createElement(FileListStateBox, null)), /* @__PURE__ */ React9.createElement(Box7, { flexBasis: 30 }, /* @__PURE__ */ React9.createElement(KeyboardControlsInfo, null))), /* @__PURE__ */ React9.createElement(FileListBox, null)));
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
// src/cli/ui/index.tsx
|
|
914
|
+
function createReactInk() {
|
|
915
|
+
return render(/* @__PURE__ */ React10.createElement(App, null));
|
|
916
|
+
}
|
|
917
|
+
|
|
881
918
|
// src/index.ts
|
|
882
919
|
async function run() {
|
|
883
920
|
createReactInk();
|
|
884
921
|
const { url, dir, media, include, exclude, minSize, maxSize, skip, removeDupilicates } = argumentHander();
|
|
885
|
-
const filelist = await
|
|
922
|
+
const filelist = await resolveAPI(url);
|
|
886
923
|
filelist.setDirPath(dir).skip(skip).filterByText(include, exclude).filterByMediaType(media);
|
|
887
924
|
if (removeDupilicates) {
|
|
888
925
|
filelist.removeURLDuplicates();
|