neekuro 2.1.0 → 2.3.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/CHANGELOG.md +68 -0
- package/README.md +155 -121
- package/package.json +69 -68
- package/sources/classes/SFW.js +313 -47
- 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 +37 -26
- package/sources/classes/user/User.js +34 -34
- package/sources/main/index.js +20 -20
- package/sources/rest/APIClient.js +37 -36
- package/sources/typings/index.d.ts +408 -370
- package/sources/utils/imageLoader.js +38 -26
package/sources/classes/SFW.js
CHANGED
|
@@ -1,47 +1,313 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('../typings').SfwCategories} SfwCategories
|
|
5
|
+
* @typedef {import('../typings').GifMap} GifMap
|
|
6
|
+
* @typedef {import('../typings').GifData} GifData
|
|
7
|
+
* @typedef {import('../typings').GifListResponse} GifListResponse
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const APIClient = require('../rest/APIClient.js');
|
|
11
|
+
const NekoError = require('./errors/Error.js');
|
|
12
|
+
const NekoGif = require('./package/NekoGif.js');
|
|
13
|
+
const { getToken, getTokenVersion } = require('./user/BaseUser.js');
|
|
14
|
+
|
|
15
|
+
const DEFAULT_BASE_URL = 'https://www.nexatdc.work.gd/api/sfw';
|
|
16
|
+
const DEFAULT_CACHE_TTL = 5 * 60 * 1000;
|
|
17
|
+
const MAX_CACHE_ENTRIES = 100;
|
|
18
|
+
|
|
19
|
+
let baseURL = DEFAULT_BASE_URL;
|
|
20
|
+
let cacheTTL = DEFAULT_CACHE_TTL;
|
|
21
|
+
let globalCacheVersion = 0;
|
|
22
|
+
|
|
23
|
+
/** @type {Map<string, {items: GifData[], expiresAt: number, tokenVersion: number}>} */
|
|
24
|
+
const gifCache = new Map();
|
|
25
|
+
/** @type {Map<string, {promise: Promise<GifData[]>, tokenVersion: number}>} */
|
|
26
|
+
const gifRequests = new Map();
|
|
27
|
+
/** @type {Map<string, number>} */
|
|
28
|
+
const cacheVersions = new Map();
|
|
29
|
+
/** @type {Map<string, number>} */
|
|
30
|
+
const lastSelections = new Map();
|
|
31
|
+
|
|
32
|
+
/** Cliente para obtener imágenes SFW. */
|
|
33
|
+
class SFW {
|
|
34
|
+
/**
|
|
35
|
+
* Obtiene un GIF aleatorio. La lista puede provenir de caché, pero la
|
|
36
|
+
* selección se realiza nuevamente en cada llamada.
|
|
37
|
+
* @template {SfwCategories} T
|
|
38
|
+
* @param {T} category categoría principal
|
|
39
|
+
* @param {GifMap[T]} gif subcategoría solicitada
|
|
40
|
+
* @returns {Promise<NekoGif>}
|
|
41
|
+
*/
|
|
42
|
+
static async getGif(category, gif) {
|
|
43
|
+
const key = getCacheKey(category, gif);
|
|
44
|
+
const items = await getGifData(category, gif, key);
|
|
45
|
+
const previousIndex = lastSelections.get(key);
|
|
46
|
+
const index = getRandomIndex(items.length, previousIndex);
|
|
47
|
+
|
|
48
|
+
lastSelections.set(key, index);
|
|
49
|
+
return createNekoGif(items[index]);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Obtiene todos los GIF disponibles para una subcategoría.
|
|
54
|
+
* Se devuelven instancias nuevas para impedir que el consumidor modifique
|
|
55
|
+
* accidentalmente los datos conservados en caché.
|
|
56
|
+
* @template {SfwCategories} T
|
|
57
|
+
* @param {T} category categoría principal
|
|
58
|
+
* @param {GifMap[T]} gif subcategoría solicitada
|
|
59
|
+
* @returns {Promise<NekoGif[]>}
|
|
60
|
+
*/
|
|
61
|
+
static async getGifs(category, gif) {
|
|
62
|
+
const key = getCacheKey(category, gif);
|
|
63
|
+
const items = await getGifData(category, gif, key);
|
|
64
|
+
return items.map(createNekoGif);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Limpia toda la caché, una categoría principal o una subcategoría.
|
|
69
|
+
* @template {SfwCategories} T
|
|
70
|
+
* @param {T} [category]
|
|
71
|
+
* @param {GifMap[T]} [gif]
|
|
72
|
+
*/
|
|
73
|
+
static clearCache(category, gif) {
|
|
74
|
+
if (category === undefined) {
|
|
75
|
+
globalCacheVersion += 1;
|
|
76
|
+
gifCache.clear();
|
|
77
|
+
gifRequests.clear();
|
|
78
|
+
cacheVersions.clear();
|
|
79
|
+
lastSelections.clear();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
validateCategory(category);
|
|
84
|
+
if (gif === undefined) {
|
|
85
|
+
const prefix = `${category}:`;
|
|
86
|
+
for (const key of collectKnownKeys(prefix)) clearCacheKey(key);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
clearCacheKey(getCacheKey(category, gif));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Cambia la URL base utilizada por SFW y vacía la caché anterior.
|
|
95
|
+
* @param {string} url
|
|
96
|
+
*/
|
|
97
|
+
static setBaseURL(url) {
|
|
98
|
+
baseURL = validateBaseURL(url);
|
|
99
|
+
this.clearCache();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Configura la duración de la caché. Un valor de cero la desactiva.
|
|
104
|
+
* @param {number} milliseconds
|
|
105
|
+
*/
|
|
106
|
+
static setCacheTTL(milliseconds) {
|
|
107
|
+
if (!Number.isFinite(milliseconds) || milliseconds < 0) {
|
|
108
|
+
throw new NekoError('InvalidCacheTTL', 'El tiempo de caché debe ser un número mayor o igual a cero');
|
|
109
|
+
}
|
|
110
|
+
cacheTTL = Math.floor(milliseconds);
|
|
111
|
+
this.clearCache();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @template {SfwCategories} T
|
|
117
|
+
* @param {T} category
|
|
118
|
+
* @param {GifMap[T]} gif
|
|
119
|
+
* @param {string} key
|
|
120
|
+
* @returns {Promise<GifData[]>}
|
|
121
|
+
*/
|
|
122
|
+
async function getGifData(category, gif, key) {
|
|
123
|
+
const token = getToken();
|
|
124
|
+
if (!token) throw new NekoError('NoToken', 'Debes establecer un token antes de solicitar un GIF');
|
|
125
|
+
const currentTokenVersion = getTokenVersion();
|
|
126
|
+
|
|
127
|
+
const cached = gifCache.get(key);
|
|
128
|
+
if (
|
|
129
|
+
cached
|
|
130
|
+
&& cached.tokenVersion === currentTokenVersion
|
|
131
|
+
&& cached.expiresAt > Date.now()
|
|
132
|
+
) return cached.items;
|
|
133
|
+
if (cached) gifCache.delete(key);
|
|
134
|
+
|
|
135
|
+
const pending = gifRequests.get(key);
|
|
136
|
+
if (pending?.tokenVersion === currentTokenVersion) return pending.promise;
|
|
137
|
+
|
|
138
|
+
const requestGlobalVersion = globalCacheVersion;
|
|
139
|
+
const requestKeyVersion = cacheVersions.get(key) ?? 0;
|
|
140
|
+
const request = requestGifList(category, gif, token)
|
|
141
|
+
.then(items => {
|
|
142
|
+
if (
|
|
143
|
+
cacheTTL > 0
|
|
144
|
+
&& requestGlobalVersion === globalCacheVersion
|
|
145
|
+
&& requestKeyVersion === (cacheVersions.get(key) ?? 0)
|
|
146
|
+
&& currentTokenVersion === getTokenVersion()
|
|
147
|
+
) {
|
|
148
|
+
storeCacheEntry(key, items, currentTokenVersion);
|
|
149
|
+
}
|
|
150
|
+
return items;
|
|
151
|
+
})
|
|
152
|
+
.finally(() => {
|
|
153
|
+
if (gifRequests.get(key)?.promise === request) gifRequests.delete(key);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
gifRequests.set(key, { promise: request, tokenVersion: currentTokenVersion });
|
|
157
|
+
return request;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @template {SfwCategories} T
|
|
162
|
+
* @param {T} category
|
|
163
|
+
* @param {GifMap[T]} gif
|
|
164
|
+
* @param {string} token
|
|
165
|
+
* @returns {Promise<GifData[]>}
|
|
166
|
+
*/
|
|
167
|
+
async function requestGifList(category, gif, token) {
|
|
168
|
+
const client = new APIClient(baseURL, { token });
|
|
169
|
+
const endpoint = `/${category}/${encodeURIComponent(String(gif))}`;
|
|
170
|
+
/** @type {GifListResponse} */
|
|
171
|
+
const response = await client.get(endpoint);
|
|
172
|
+
|
|
173
|
+
if (!Array.isArray(response?.data) || response.data.length === 0) {
|
|
174
|
+
throw new NekoError('EmptyResponse', `La API no devolvió GIF para ${category}/${gif}`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const items = response.data.map((item, index) => normalizeGif(item, category, gif, index));
|
|
178
|
+
return removeDuplicateURLs(items);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const UNKNOWN_ANIME = 'Desconocido';
|
|
182
|
+
// Valores que la base de datos usa para "sin dato". Se tratan igual que un
|
|
183
|
+
// campo vacío para no mostrar, por ejemplo, "Unknown" en un embed en español.
|
|
184
|
+
const PLACEHOLDER_ANIME = new Set(['unknown', 'desconocido']);
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* @param {unknown} item
|
|
188
|
+
* @param {string} category
|
|
189
|
+
* @param {string} gif
|
|
190
|
+
* @param {number} index
|
|
191
|
+
* @returns {GifData}
|
|
192
|
+
*/
|
|
193
|
+
function normalizeGif(item, category, gif, index) {
|
|
194
|
+
if (!item || typeof item !== 'object' || typeof item.url !== 'string' || !item.url.trim()) {
|
|
195
|
+
throw new NekoError('InvalidResponse', `El GIF ${index} de ${category}/${gif} no contiene una URL válida`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const anime = typeof item.anime === 'string' ? item.anime.trim() : '';
|
|
199
|
+
return Object.freeze({
|
|
200
|
+
url: item.url.trim(),
|
|
201
|
+
anime: anime && !PLACEHOLDER_ANIME.has(anime.toLowerCase()) ? anime : UNKNOWN_ANIME
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Conserva la primera aparición de cada URL. Una URL repetida pesa doble en el
|
|
207
|
+
* sorteo y puede salir dos veces seguidas, porque la selección evita repetir
|
|
208
|
+
* posiciones, no URLs.
|
|
209
|
+
* @param {GifData[]} items
|
|
210
|
+
* @returns {GifData[]}
|
|
211
|
+
*/
|
|
212
|
+
function removeDuplicateURLs(items) {
|
|
213
|
+
const seen = new Set();
|
|
214
|
+
return items.filter(item => {
|
|
215
|
+
if (seen.has(item.url)) return false;
|
|
216
|
+
seen.add(item.url);
|
|
217
|
+
return true;
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** @param {GifData} item @returns {NekoGif} */
|
|
222
|
+
function createNekoGif(item) {
|
|
223
|
+
return new NekoGif({ code: 200, data: { ...item } });
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Elige uniformemente entre todas las posiciones, excepto la selección
|
|
228
|
+
* anterior cuando existen al menos dos alternativas.
|
|
229
|
+
* @param {number} length
|
|
230
|
+
* @param {number|undefined} previousIndex
|
|
231
|
+
*/
|
|
232
|
+
function getRandomIndex(length, previousIndex) {
|
|
233
|
+
if (length === 1) return 0;
|
|
234
|
+
if (previousIndex === undefined || previousIndex < 0 || previousIndex >= length) {
|
|
235
|
+
return Math.floor(Math.random() * length);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const index = Math.floor(Math.random() * (length - 1));
|
|
239
|
+
return index >= previousIndex ? index + 1 : index;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* @template {SfwCategories} T
|
|
244
|
+
* @param {T} category
|
|
245
|
+
* @param {GifMap[T]} gif
|
|
246
|
+
*/
|
|
247
|
+
function getCacheKey(category, gif) {
|
|
248
|
+
validateCategory(category);
|
|
249
|
+
if (typeof gif !== 'string' || !gif.trim()) {
|
|
250
|
+
throw new NekoError('NoInput', 'el parámetro <gif> es requerido');
|
|
251
|
+
}
|
|
252
|
+
return `${category}:${gif}`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** @param {unknown} category */
|
|
256
|
+
function validateCategory(category) {
|
|
257
|
+
if (category === undefined || category === null || category === '') {
|
|
258
|
+
throw new NekoError('NoInput', 'el parámetro <category> es requerido');
|
|
259
|
+
}
|
|
260
|
+
if (category !== 'action' && category !== 'reaction') {
|
|
261
|
+
throw new NekoError('InvalidInput', 'la categoría que ingresas es inválida');
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** @param {string} url */
|
|
266
|
+
function validateBaseURL(url) {
|
|
267
|
+
if (typeof url !== 'string' || !url.trim()) {
|
|
268
|
+
throw new NekoError('InvalidBaseURL', 'La URL base debe ser una cadena HTTPS válida');
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
let parsed;
|
|
272
|
+
try {
|
|
273
|
+
parsed = new URL(url);
|
|
274
|
+
} catch {
|
|
275
|
+
throw new NekoError('InvalidBaseURL', 'La URL base debe ser una cadena HTTPS válida');
|
|
276
|
+
}
|
|
277
|
+
if (parsed.protocol !== 'https:' || parsed.username || parsed.password) {
|
|
278
|
+
throw new NekoError('InvalidBaseURL', 'La URL base debe utilizar HTTPS y no incluir credenciales');
|
|
279
|
+
}
|
|
280
|
+
return url.trim().replace(/\/$/, '');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** @param {string} key @param {GifData[]} items @param {number} tokenVersion */
|
|
284
|
+
function storeCacheEntry(key, items, tokenVersion) {
|
|
285
|
+
if (!gifCache.has(key) && gifCache.size >= MAX_CACHE_ENTRIES) {
|
|
286
|
+
const oldest = gifCache.keys().next().value;
|
|
287
|
+
if (oldest) {
|
|
288
|
+
gifCache.delete(oldest);
|
|
289
|
+
lastSelections.delete(oldest);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
gifCache.set(key, { items, expiresAt: Date.now() + cacheTTL, tokenVersion });
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** @param {string} key */
|
|
296
|
+
function clearCacheKey(key) {
|
|
297
|
+
gifCache.delete(key);
|
|
298
|
+
gifRequests.delete(key);
|
|
299
|
+
lastSelections.delete(key);
|
|
300
|
+
cacheVersions.set(key, (cacheVersions.get(key) ?? 0) + 1);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** @param {string} prefix */
|
|
304
|
+
function collectKnownKeys(prefix) {
|
|
305
|
+
return new Set([
|
|
306
|
+
...[...gifCache.keys()].filter(key => key.startsWith(prefix)),
|
|
307
|
+
...[...gifRequests.keys()].filter(key => key.startsWith(prefix)),
|
|
308
|
+
...[...lastSelections.keys()].filter(key => key.startsWith(prefix)),
|
|
309
|
+
...[...cacheVersions.keys()].filter(key => key.startsWith(prefix))
|
|
310
|
+
]);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
module.exports = SFW;
|
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @typedef {import('../../typings').ApiResponse} ApiResponse
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('../../typings').ApiResponse} ApiResponse
|
|
5
5
|
* @typedef {{url?: string, status?: number}} ResponseLike
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Clase APIError
|
|
10
|
-
* Errores personalizados respecto a la api rest
|
|
11
|
-
* @extends Error
|
|
12
|
-
*/
|
|
13
|
-
class APIError extends Error {
|
|
14
|
-
/**
|
|
15
|
-
* Crea la instancia del error
|
|
16
|
-
* @param {string} endpoint la ruta a la que se realizo la peticion
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Clase APIError
|
|
10
|
+
* Errores personalizados respecto a la api rest
|
|
11
|
+
* @extends Error
|
|
12
|
+
*/
|
|
13
|
+
class APIError extends Error {
|
|
14
|
+
/**
|
|
15
|
+
* Crea la instancia del error
|
|
16
|
+
* @param {string} endpoint la ruta a la que se realizo la peticion
|
|
17
17
|
* @param {ResponseLike} response respuesta HTTP o representación de un error de red
|
|
18
|
-
* @param {ApiResponse} data respuesta de la api
|
|
19
|
-
*/
|
|
18
|
+
* @param {ApiResponse} data respuesta de la api
|
|
19
|
+
*/
|
|
20
20
|
constructor(endpoint, response, data) {
|
|
21
21
|
super(APIError.determineMessage(data));
|
|
22
|
-
|
|
23
|
-
this.name = `NekoREST Error [${endpoint}]`
|
|
24
|
-
/**
|
|
25
|
-
* @type {string}
|
|
26
|
-
*/
|
|
27
|
-
this.url = response?.url ?? 'unknown';
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* @type {number}
|
|
31
|
-
*/
|
|
22
|
+
|
|
23
|
+
this.name = `NekoREST Error [${endpoint}]`
|
|
24
|
+
/**
|
|
25
|
+
* @type {string}
|
|
26
|
+
*/
|
|
27
|
+
this.url = response?.url ?? 'unknown';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @type {number}
|
|
31
|
+
*/
|
|
32
32
|
this.statusCode = Number.parseInt(response?.status ?? 500, 10);
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* @type {ApiResponse}
|
|
36
|
-
*/
|
|
37
|
-
this.data = data
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Determina el mensaje del error basado en el codigo de la api
|
|
43
|
-
* @param {ApiResponse} response respuesta de la api
|
|
44
|
-
* @returns {string} mensaje de error
|
|
45
|
-
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @type {ApiResponse}
|
|
36
|
+
*/
|
|
37
|
+
this.data = data
|
|
38
|
+
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Determina el mensaje del error basado en el codigo de la api
|
|
43
|
+
* @param {ApiResponse} response respuesta de la api
|
|
44
|
+
* @returns {string} mensaje de error
|
|
45
|
+
*/
|
|
46
46
|
static determineMessage(response = {}) {
|
|
47
|
-
if(response.code === 403) {
|
|
48
|
-
return 'TOKEN ERROR: el token que haz proporcionado es invalido'
|
|
49
|
-
} else
|
|
50
|
-
if(response.code === 401) {
|
|
51
|
-
return 'TOKEN ERROR: no se proporciono un token'
|
|
52
|
-
} else {
|
|
53
|
-
return response.message ?? 'descripcion no dada'
|
|
54
|
-
}
|
|
47
|
+
if(response.code === 403) {
|
|
48
|
+
return 'TOKEN ERROR: el token que haz proporcionado es invalido'
|
|
49
|
+
} else
|
|
50
|
+
if(response.code === 401) {
|
|
51
|
+
return 'TOKEN ERROR: no se proporciono un token'
|
|
52
|
+
} else {
|
|
53
|
+
return response.message ?? 'descripcion no dada'
|
|
54
|
+
}
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
static fromNetworkError(endpoint, url, message) {
|
|
58
58
|
return new APIError(endpoint, { url, status: 503 }, { code: 503, message });
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
module.exports = APIError
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Clase de erorres para la API
|
|
5
|
-
* @extends Error
|
|
6
|
-
*/
|
|
7
|
-
class NekoError extends Error {
|
|
8
|
-
/**
|
|
9
|
-
* Crea una instancia para errores del paquete
|
|
10
|
-
* @param {string} name Corresponde al nombre del error
|
|
11
|
-
* @param {string} message Mensaje de error
|
|
12
|
-
*/
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Clase de erorres para la API
|
|
5
|
+
* @extends Error
|
|
6
|
+
*/
|
|
7
|
+
class NekoError extends Error {
|
|
8
|
+
/**
|
|
9
|
+
* Crea una instancia para errores del paquete
|
|
10
|
+
* @param {string} name Corresponde al nombre del error
|
|
11
|
+
* @param {string} message Mensaje de error
|
|
12
|
+
*/
|
|
13
13
|
constructor(name, message) {
|
|
14
14
|
super(message);
|
|
15
|
-
|
|
16
|
-
this.name = `NeeKuro Error [${name}]`
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @type {string}
|
|
20
|
-
*/
|
|
15
|
+
|
|
16
|
+
this.name = `NeeKuro Error [${name}]`
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @type {string}
|
|
20
|
+
*/
|
|
21
21
|
this.message = message
|
|
22
22
|
Error.captureStackTrace?.(this, NekoError);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
26
|
module.exports = NekoError
|
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @typedef {import("../../typings").GifResponse} GifResponse
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Clase NekoGif
|
|
9
|
-
* crea un objeto Gif a partir de la respuesta del servidor
|
|
10
|
-
*/
|
|
11
|
-
class NekoGif {
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Recibe la respuesta de la api
|
|
15
|
-
* @param {GifResponse} response respuesta de la api
|
|
16
|
-
*/
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import("../../typings").GifResponse} GifResponse
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Clase NekoGif
|
|
9
|
+
* crea un objeto Gif a partir de la respuesta del servidor
|
|
10
|
+
*/
|
|
11
|
+
class NekoGif {
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Recibe la respuesta de la api
|
|
15
|
+
* @param {GifResponse} response respuesta de la api
|
|
16
|
+
*/
|
|
17
17
|
constructor(response) {
|
|
18
18
|
if (!response || typeof response !== 'object' || !response.data) {
|
|
19
19
|
throw new TypeError('La respuesta no contiene datos de imagen');
|
|
20
20
|
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* URL del gif
|
|
24
|
-
* @type {string|null}
|
|
25
|
-
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* URL del gif
|
|
24
|
+
* @type {string|null}
|
|
25
|
+
*/
|
|
26
26
|
this.url = typeof response.data.url === 'string' ? response.data.url : null
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Anime del gif
|
|
30
|
-
* @type {string|null}
|
|
31
|
-
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Anime del gif
|
|
30
|
+
* @type {string|null}
|
|
31
|
+
*/
|
|
32
32
|
this.anime = typeof response.data.anime === 'string' ? response.data.anime : null
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Obten la url del Gif
|
|
37
|
-
* @returns {string|null} URL
|
|
38
|
-
*/
|
|
39
|
-
getUrl() {
|
|
40
|
-
return this.url;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Obten el anime del Gif
|
|
45
|
-
* @returns {string|null} Anime
|
|
46
|
-
*/
|
|
47
|
-
getAnime() {
|
|
48
|
-
return this.anime;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Obten la url del Gif
|
|
37
|
+
* @returns {string|null} URL
|
|
38
|
+
*/
|
|
39
|
+
getUrl() {
|
|
40
|
+
return this.url;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Obten el anime del Gif
|
|
45
|
+
* @returns {string|null} Anime
|
|
46
|
+
*/
|
|
47
|
+
getAnime() {
|
|
48
|
+
return this.anime;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
}
|
|
52
|
+
|
|
53
53
|
module.exports = NekoGif
|