coomer-downloader 3.0.10 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +365 -242
- package/package.json +1 -1
- package/src/api/bunkr.ts +16 -17
- package/src/api/coomer-api.ts +25 -26
- package/src/api/gofile.ts +18 -14
- package/src/api/index.ts +16 -16
- package/src/api/nsfw.xxx.ts +9 -10
- package/src/api/plain-curl.ts +7 -11
- package/src/args-handler.ts +1 -4
- package/src/index.ts +13 -14
- package/src/types/index.ts +0 -22
- package/src/utils/downloader.ts +76 -69
- package/src/utils/file.ts +75 -26
- package/src/utils/filters.ts +27 -16
- package/src/utils/index.ts +9 -4
- package/src/utils/multibar.ts +20 -21
- package/src/utils/promise.ts +53 -0
- package/src/utils/requests.ts +2 -16
- package/src/utils/strings.ts +1 -10
- package/src/utils/timer.ts +19 -1
- /package/src/utils/{files.ts → io.ts} +0 -0
package/src/utils/file.ts
CHANGED
|
@@ -1,26 +1,75 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import type { MediaType } from '../types';
|
|
4
|
+
import { filterString, testMediaType } from './filters';
|
|
5
|
+
|
|
6
|
+
interface ICoomerFile {
|
|
7
|
+
name: string;
|
|
8
|
+
url: string;
|
|
9
|
+
filepath?: string;
|
|
10
|
+
size?: number;
|
|
11
|
+
downloaded?: number;
|
|
12
|
+
content?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class CoomerFile {
|
|
16
|
+
public state: 'downloading' | 'pause' = 'pause';
|
|
17
|
+
|
|
18
|
+
constructor(
|
|
19
|
+
public name: string,
|
|
20
|
+
public url: string,
|
|
21
|
+
public filepath?: string,
|
|
22
|
+
public size?: number,
|
|
23
|
+
public downloaded?: number,
|
|
24
|
+
public content?: string,
|
|
25
|
+
) {}
|
|
26
|
+
|
|
27
|
+
get textContent() {
|
|
28
|
+
const text = `${this.name || ''} ${this.content || ''}`.toLowerCase();
|
|
29
|
+
return text;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public static from(f: ICoomerFile) {
|
|
33
|
+
return new CoomerFile(f.name, f.url, f.filepath, f.size, f.downloaded, f.content);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class CoomerFileList {
|
|
38
|
+
public dirPath?: string;
|
|
39
|
+
public dirName?: string;
|
|
40
|
+
|
|
41
|
+
constructor(public files: CoomerFile[] = []) {}
|
|
42
|
+
|
|
43
|
+
public setDirPath(dir: string, dirName?: string) {
|
|
44
|
+
dirName = dirName || (this.dirName as string);
|
|
45
|
+
|
|
46
|
+
if (dir === './') {
|
|
47
|
+
this.dirPath = path.resolve(dir, dirName);
|
|
48
|
+
} else {
|
|
49
|
+
this.dirPath = path.join(os.homedir(), path.join(dir, dirName));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this.files.forEach((file) => {
|
|
53
|
+
file.filepath = path.join(this.dirPath as string, file.name);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public filterByText(include: string, exclude: string) {
|
|
60
|
+
this.files = this.files.filter((f) => filterString(f.textContent, include, exclude));
|
|
61
|
+
return this;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public filterByMediaType(media?: string) {
|
|
65
|
+
if (media) {
|
|
66
|
+
this.files = this.files.filter((f) => testMediaType(f.name, media as MediaType));
|
|
67
|
+
}
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public skip(n: number) {
|
|
72
|
+
this.files = this.files.slice(n);
|
|
73
|
+
return this;
|
|
74
|
+
}
|
|
75
|
+
}
|
package/src/utils/filters.ts
CHANGED
|
@@ -1,23 +1,34 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { MediaType } from '../types';
|
|
2
2
|
|
|
3
|
-
export
|
|
3
|
+
export function isImage(name: string) {
|
|
4
|
+
return /\.(jpg|jpeg|png|gif|bmp|tiff|webp|avif)$/i.test(name);
|
|
5
|
+
}
|
|
4
6
|
|
|
5
|
-
export
|
|
6
|
-
/\.(mp4|m4v|avi|mov|mkv|webm|flv|wmv|mpeg|mpg|3gp)$/i.test(name);
|
|
7
|
+
export function isVideo(name: string) {
|
|
8
|
+
return /\.(mp4|m4v|avi|mov|mkv|webm|flv|wmv|mpeg|mpg|3gp)$/i.test(name);
|
|
9
|
+
}
|
|
7
10
|
|
|
8
|
-
export
|
|
9
|
-
type === 'all' ? true : type === 'image' ? isImage(name) : isVideo(name);
|
|
11
|
+
export function testMediaType(name: string, type: MediaType) {
|
|
12
|
+
return type === 'all' ? true : type === 'image' ? isImage(name) : isVideo(name);
|
|
13
|
+
}
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
function includesAllWords(str: string, words: string[]) {
|
|
16
|
+
if (!words.length) return true;
|
|
17
|
+
return words.every((w) => str.includes(w));
|
|
18
|
+
}
|
|
14
19
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
function includesNoWords(str: string, words: string[]) {
|
|
21
|
+
if (!words.length) return true;
|
|
22
|
+
return words.every((w) => !str.includes(w));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parseQuery(query: string) {
|
|
26
|
+
return query
|
|
27
|
+
.split(',')
|
|
28
|
+
.map((x) => x.toLowerCase().trim())
|
|
29
|
+
.filter((_) => _);
|
|
30
|
+
}
|
|
18
31
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return isValid(text);
|
|
22
|
-
});
|
|
32
|
+
export function filterString(text: string, include: string, exclude: string): boolean {
|
|
33
|
+
return includesAllWords(text, parseQuery(include)) && includesNoWords(text, parseQuery(exclude));
|
|
23
34
|
}
|
package/src/utils/index.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
1
|
+
export { Downloader } from './downloader';
|
|
2
|
+
export { isImage, isVideo, testMediaType } from './filters';
|
|
3
|
+
export { getFileSize, mkdir } from './io';
|
|
4
4
|
export { createMultibar } from './multibar';
|
|
5
|
-
export {
|
|
5
|
+
export {
|
|
6
|
+
fetchByteRange,
|
|
7
|
+
fetchWithGlobalHeader,
|
|
8
|
+
HeadersDefault,
|
|
9
|
+
setGlobalHeaders,
|
|
10
|
+
} from './requests';
|
|
6
11
|
export { b2mb } from './strings';
|
package/src/utils/multibar.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { MultiBar, type SingleBar } from 'cli-progress';
|
|
2
|
-
import {
|
|
1
|
+
import { MultiBar, type Options, type SingleBar } from 'cli-progress';
|
|
2
|
+
import type { Downloader } from './downloader';
|
|
3
3
|
import { b2mb, formatNameStdout } from './strings';
|
|
4
4
|
|
|
5
|
-
const config = {
|
|
5
|
+
const config: Options = {
|
|
6
6
|
clearOnComplete: true,
|
|
7
7
|
gracefulExit: true,
|
|
8
8
|
autopadding: true,
|
|
@@ -10,44 +10,39 @@ const config = {
|
|
|
10
10
|
format: '{percentage}% | {filename} | {value}/{total}{size}',
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
// totalFiles: number;
|
|
15
|
-
// totalDownloadedFiles: number;
|
|
16
|
-
// filesInProcess: File[];
|
|
17
|
-
// }
|
|
18
|
-
|
|
19
|
-
export function createMultibar() {
|
|
13
|
+
export function createMultibar(downloader: Downloader) {
|
|
20
14
|
const multibar = new MultiBar(config);
|
|
21
15
|
let bar: SingleBar;
|
|
22
16
|
let minibar: SingleBar;
|
|
23
17
|
let filename: string;
|
|
18
|
+
let index = 0;
|
|
24
19
|
|
|
25
|
-
subject.subscribe({
|
|
26
|
-
next: ({ type, filesCount,
|
|
20
|
+
downloader.subject.subscribe({
|
|
21
|
+
next: ({ type, filesCount, file }) => {
|
|
27
22
|
switch (type) {
|
|
28
|
-
case '
|
|
23
|
+
case 'FILES_DOWNLOADING_START':
|
|
29
24
|
bar?.stop();
|
|
30
25
|
bar = multibar.create(filesCount as number, 0);
|
|
31
26
|
break;
|
|
32
27
|
|
|
33
|
-
case '
|
|
28
|
+
case 'FILES_DOWNLOADING_END':
|
|
34
29
|
bar?.stop();
|
|
35
30
|
break;
|
|
36
31
|
|
|
37
|
-
case '
|
|
38
|
-
bar?.update(
|
|
32
|
+
case 'FILE_DOWNLOADING_START':
|
|
33
|
+
bar?.update(++index, { filename: 'Downloaded files', size: '' });
|
|
39
34
|
break;
|
|
40
35
|
|
|
41
|
-
case '
|
|
36
|
+
case 'FILE_DOWNLOADING_END':
|
|
37
|
+
multibar.remove(minibar);
|
|
38
|
+
break;
|
|
39
|
+
|
|
40
|
+
case 'CHUNK_DOWNLOADING_START':
|
|
42
41
|
multibar?.remove(minibar);
|
|
43
42
|
filename = formatNameStdout(file?.filepath as string);
|
|
44
43
|
minibar = multibar.create(b2mb(file?.size as number), b2mb(file?.downloaded as number));
|
|
45
44
|
break;
|
|
46
45
|
|
|
47
|
-
case 'FILE_DOWNLOADING_FINISHED':
|
|
48
|
-
multibar.remove(minibar);
|
|
49
|
-
break;
|
|
50
|
-
|
|
51
46
|
case 'CHUNK_DOWNLOADING_UPDATE':
|
|
52
47
|
minibar?.update(b2mb(file?.downloaded as number), {
|
|
53
48
|
filename: filename as string,
|
|
@@ -55,6 +50,10 @@ export function createMultibar() {
|
|
|
55
50
|
});
|
|
56
51
|
break;
|
|
57
52
|
|
|
53
|
+
case 'CHUNK_DOWNLOADING_END':
|
|
54
|
+
multibar?.remove(minibar);
|
|
55
|
+
break;
|
|
56
|
+
|
|
58
57
|
default:
|
|
59
58
|
break;
|
|
60
59
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export async function sleep(time: number) {
|
|
2
|
+
return new Promise((resolve) => setTimeout(resolve, time));
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
type PromiseRetryCallback = (retries: number, error: Error) => void | { newRetries?: number };
|
|
6
|
+
|
|
7
|
+
interface PromiseRetryOptions {
|
|
8
|
+
retries?: number;
|
|
9
|
+
callback?: PromiseRetryCallback;
|
|
10
|
+
delay?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class PromiseRetry {
|
|
14
|
+
private retries: number;
|
|
15
|
+
private delay: number;
|
|
16
|
+
private callback?: PromiseRetryCallback;
|
|
17
|
+
|
|
18
|
+
constructor(options: PromiseRetryOptions) {
|
|
19
|
+
this.retries = options.retries || 3;
|
|
20
|
+
this.delay = options.delay || 1000;
|
|
21
|
+
this.callback = options.callback;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async execute(fn: () => Promise<void>) {
|
|
25
|
+
let retries = this.retries;
|
|
26
|
+
|
|
27
|
+
while (true) {
|
|
28
|
+
try {
|
|
29
|
+
return await fn();
|
|
30
|
+
} catch (error) {
|
|
31
|
+
if (retries <= 0) {
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (this.callback) {
|
|
36
|
+
const res = this.callback(retries, error as Error);
|
|
37
|
+
if (res) {
|
|
38
|
+
const { newRetries } = res;
|
|
39
|
+
if (newRetries === 0) throw error;
|
|
40
|
+
this.retries = newRetries || retries;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
await sleep(this.delay);
|
|
45
|
+
retries--;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static create(options: PromiseRetryOptions) {
|
|
51
|
+
return new PromiseRetry(options);
|
|
52
|
+
}
|
|
53
|
+
}
|
package/src/utils/requests.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CookieAgent } from 'http-cookie-agent/undici';
|
|
2
2
|
import { CookieJar } from 'tough-cookie';
|
|
3
|
-
import { fetch,
|
|
3
|
+
import { fetch, interceptors, setGlobalDispatcher } from 'undici';
|
|
4
4
|
|
|
5
5
|
function setCookieJarDispatcher() {
|
|
6
6
|
const jar = new CookieJar();
|
|
@@ -12,20 +12,6 @@ function setCookieJarDispatcher() {
|
|
|
12
12
|
|
|
13
13
|
setCookieJarDispatcher();
|
|
14
14
|
|
|
15
|
-
export function setRetryDispatcher(maxRetries = 3) {
|
|
16
|
-
setGlobalDispatcher(
|
|
17
|
-
getGlobalDispatcher().compose(
|
|
18
|
-
interceptors.retry({
|
|
19
|
-
maxRetries,
|
|
20
|
-
// minTimeout: 1000,
|
|
21
|
-
// maxTimeout: 10000,
|
|
22
|
-
timeoutFactor: 2,
|
|
23
|
-
retryAfter: true,
|
|
24
|
-
}),
|
|
25
|
-
),
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
15
|
export const HeadersDefault = new Headers({
|
|
30
16
|
accept: 'application/json, text/css',
|
|
31
17
|
'User-Agent':
|
|
@@ -38,7 +24,7 @@ export function setGlobalHeaders(headers: Record<string, string>) {
|
|
|
38
24
|
});
|
|
39
25
|
}
|
|
40
26
|
|
|
41
|
-
export function
|
|
27
|
+
export function fetchWithGlobalHeader(url: string) {
|
|
42
28
|
const requestHeaders = new Headers(HeadersDefault);
|
|
43
29
|
return fetch(url, { headers: requestHeaders });
|
|
44
30
|
}
|
package/src/utils/strings.ts
CHANGED
|
@@ -2,20 +2,11 @@ export function b2mb(bytes: number) {
|
|
|
2
2
|
return Number.parseFloat((bytes / 1048576).toFixed(2));
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
-
export function sanitizeString(str: string) {
|
|
6
|
-
return (
|
|
7
|
-
str
|
|
8
|
-
.match(/(\w| |-)/g)
|
|
9
|
-
?.join('')
|
|
10
|
-
.replace(/ +/g, ' ') || ''
|
|
11
|
-
);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
5
|
export function formatNameStdout(pathname: string) {
|
|
15
6
|
const name = pathname.split('/').pop() || '';
|
|
16
7
|
const consoleWidth = process.stdout.columns;
|
|
17
8
|
const width = Math.max((consoleWidth / 2) | 0, 40);
|
|
18
9
|
if (name.length < width) return name.trim();
|
|
19
|
-
const result = `${name.slice(0,width-15)} ... ${name.slice(-10)}`.replace(/ +/g, ' ');
|
|
10
|
+
const result = `${name.slice(0, width - 15)} ... ${name.slice(-10)}`.replace(/ +/g, ' ');
|
|
20
11
|
return result;
|
|
21
12
|
}
|
package/src/utils/timer.ts
CHANGED
|
@@ -9,7 +9,10 @@ export class Timer {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
start() {
|
|
12
|
-
this.timer = setTimeout(
|
|
12
|
+
this.timer = setTimeout(() => {
|
|
13
|
+
this.stop();
|
|
14
|
+
this.timeoutCallback();
|
|
15
|
+
}, this.timeout);
|
|
13
16
|
return this;
|
|
14
17
|
}
|
|
15
18
|
|
|
@@ -26,4 +29,19 @@ export class Timer {
|
|
|
26
29
|
this.start();
|
|
27
30
|
return this;
|
|
28
31
|
}
|
|
32
|
+
|
|
33
|
+
static withSignal(timeout?: number, message?: string) {
|
|
34
|
+
const controller = new AbortController();
|
|
35
|
+
|
|
36
|
+
const callback = () => {
|
|
37
|
+
controller.abort(message);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const timer = new Timer(timeout, callback).start();
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
timer,
|
|
44
|
+
signal: controller.signal,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
29
47
|
}
|
|
File without changes
|