neekuro 2.2.0 → 2.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/CHANGELOG.md +39 -14
- package/README.md +117 -117
- package/package.json +64 -64
- package/sources/classes/SFW.js +26 -5
- package/sources/classes/errors/APIError.js +49 -49
- package/sources/classes/errors/Error.js +21 -21
- package/sources/classes/package/NekoGif.js +46 -46
- package/sources/classes/package/welcome.js +446 -446
- package/sources/classes/user/BaseUser.js +20 -20
- package/sources/classes/user/User.js +34 -34
- package/sources/main/index.js +20 -20
- package/sources/rest/APIClient.js +34 -34
- package/sources/typings/index.d.ts +374 -355
- package/sources/utils/imageLoader.js +38 -26
|
@@ -4,7 +4,9 @@ const dns = require('node:dns').promises;
|
|
|
4
4
|
const net = require('node:net');
|
|
5
5
|
const Canvas = require('canvas');
|
|
6
6
|
const NekoError = require('../classes/errors/Error.js');
|
|
7
|
+
const { version } = require('../../package.json');
|
|
7
8
|
|
|
9
|
+
const USER_AGENT = `neekuro/${version} image-loader`;
|
|
8
10
|
const MAX_IMAGE_BYTES = 10 * 1024 * 1024;
|
|
9
11
|
const IMAGE_TIMEOUT_MS = 10_000;
|
|
10
12
|
const MAX_REDIRECTS = 3;
|
|
@@ -25,43 +27,48 @@ async function downloadImage(source, label = 'imagen') {
|
|
|
25
27
|
await assertPublicHostname(currentURL.hostname, label);
|
|
26
28
|
const controller = new AbortController();
|
|
27
29
|
const timeoutId = setTimeout(() => controller.abort(), IMAGE_TIMEOUT_MS);
|
|
28
|
-
|
|
30
|
+
// El timeout cubre tanto las cabeceras como la lectura completa del cuerpo.
|
|
29
31
|
try {
|
|
30
|
-
response = await fetch(currentURL, {
|
|
32
|
+
const response = await fetch(currentURL, {
|
|
31
33
|
redirect: 'manual',
|
|
32
34
|
signal: controller.signal,
|
|
33
|
-
headers: { 'User-Agent':
|
|
35
|
+
headers: { 'User-Agent': USER_AGENT }
|
|
34
36
|
});
|
|
37
|
+
|
|
38
|
+
if (response.status >= 300 && response.status < 400) {
|
|
39
|
+
await discardBody(response);
|
|
40
|
+
const location = response.headers.get('location');
|
|
41
|
+
if (!location || redirects === MAX_REDIRECTS) {
|
|
42
|
+
throw new NekoError('ImageDownloadError', `Demasiadas redirecciones al descargar ${label}`);
|
|
43
|
+
}
|
|
44
|
+
currentURL = parseRemoteURL(new URL(location, currentURL).href, label);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
await discardBody(response);
|
|
49
|
+
throw new NekoError('ImageDownloadError', `No se pudo descargar ${label}: HTTP ${response.status}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const contentType = response.headers.get('content-type')?.split(';', 1)[0].toLowerCase();
|
|
53
|
+
if (!contentType || !IMAGE_CONTENT_TYPES.has(contentType)) {
|
|
54
|
+
await discardBody(response);
|
|
55
|
+
throw new NekoError('ImageFormatError', `${label} no tiene un formato de imagen compatible`);
|
|
56
|
+
}
|
|
57
|
+
const declaredLength = Number(response.headers.get('content-length'));
|
|
58
|
+
if (Number.isFinite(declaredLength) && declaredLength > MAX_IMAGE_BYTES) {
|
|
59
|
+
await discardBody(response);
|
|
60
|
+
throw new NekoError('ImageSizeError', `${label} supera el límite de 10 MB`);
|
|
61
|
+
}
|
|
62
|
+
return await readLimitedBody(response, label);
|
|
35
63
|
} catch (error) {
|
|
36
|
-
|
|
64
|
+
if (error instanceof NekoError) throw error;
|
|
65
|
+
const message = controller.signal.aborted
|
|
37
66
|
? `La descarga de ${label} excedió ${IMAGE_TIMEOUT_MS} ms`
|
|
38
67
|
: `No se pudo descargar ${label}: ${error?.message ?? 'error desconocido'}`;
|
|
39
68
|
throw new NekoError('ImageDownloadError', message);
|
|
40
69
|
} finally {
|
|
41
70
|
clearTimeout(timeoutId);
|
|
42
71
|
}
|
|
43
|
-
|
|
44
|
-
if (response.status >= 300 && response.status < 400) {
|
|
45
|
-
const location = response.headers.get('location');
|
|
46
|
-
if (!location || redirects === MAX_REDIRECTS) {
|
|
47
|
-
throw new NekoError('ImageDownloadError', `Demasiadas redirecciones al descargar ${label}`);
|
|
48
|
-
}
|
|
49
|
-
currentURL = parseRemoteURL(new URL(location, currentURL).href, label);
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
if (!response.ok) {
|
|
53
|
-
throw new NekoError('ImageDownloadError', `No se pudo descargar ${label}: HTTP ${response.status}`);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const contentType = response.headers.get('content-type')?.split(';', 1)[0].toLowerCase();
|
|
57
|
-
if (!contentType || !IMAGE_CONTENT_TYPES.has(contentType)) {
|
|
58
|
-
throw new NekoError('ImageFormatError', `${label} no tiene un formato de imagen compatible`);
|
|
59
|
-
}
|
|
60
|
-
const declaredLength = Number(response.headers.get('content-length'));
|
|
61
|
-
if (Number.isFinite(declaredLength) && declaredLength > MAX_IMAGE_BYTES) {
|
|
62
|
-
throw new NekoError('ImageSizeError', `${label} supera el límite de 10 MB`);
|
|
63
|
-
}
|
|
64
|
-
return readLimitedBody(response, label);
|
|
65
72
|
}
|
|
66
73
|
throw new NekoError('ImageDownloadError', `No se pudo descargar ${label}`);
|
|
67
74
|
}
|
|
@@ -112,6 +119,11 @@ function isPrivateAddress(address) {
|
|
|
112
119
|
return true;
|
|
113
120
|
}
|
|
114
121
|
|
|
122
|
+
/** Libera la conexión cuando la respuesta no se va a leer. */
|
|
123
|
+
async function discardBody(response) {
|
|
124
|
+
await response.body?.cancel().catch(() => undefined);
|
|
125
|
+
}
|
|
126
|
+
|
|
115
127
|
async function readLimitedBody(response, label) {
|
|
116
128
|
if (!response.body) throw new NekoError('ImageDownloadError', `${label} no contiene datos`);
|
|
117
129
|
const chunks = [];
|