neekuro 2.1.0 → 2.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/CHANGELOG.md ADDED
@@ -0,0 +1,51 @@
1
+ # Changelog
2
+
3
+ ## [2.2.0] - 28-08-2026
4
+
5
+ ### Added
6
+ - `SFW.getGifs()` permite obtener todas las imágenes de una subcategoría.
7
+ - `SFW.clearCache()` permite invalidar toda la caché, una categoría o una subcategoría.
8
+ - `SFW.setBaseURL()` permite configurar un endpoint HTTPS alternativo.
9
+ - `SFW.setCacheTTL()` permite configurar o desactivar el tiempo de caché.
10
+
11
+ ### Changed
12
+ - `SFW.getGif()` conserva listas en memoria durante cinco minutos y selecciona aleatoriamente en cada llamada.
13
+ - Las solicitudes concurrentes para la misma subcategoría se agrupan en una sola petición HTTP.
14
+ - Cuando existen varias imágenes se evita repetir inmediatamente la selección anterior.
15
+ - Cambiar el token invalida automáticamente las listas obtenidas con el token previo.
16
+ - El cliente REST admite respuestas tipadas distintas a un único GIF.
17
+
18
+ ### Compatibility
19
+ - `SFW.getGif(category, gif)` mantiene la misma firma y continúa devolviendo `NekoGif`.
20
+ - La caché solamente cambia la frecuencia de consulta; no fija una imagen durante el TTL.
21
+
22
+ ## [2.1.0] - 28-08-2026
23
+
24
+ ### Changed
25
+ - Se corrigieron imports sensibles a mayúsculas para funcionar en Linux.
26
+ - Se eliminó la dependencia circular del paquete hacia sí mismo y `node-fetch`.
27
+ - `Welcome` valida resolución, coordenadas, radios, fuentes e imágenes con límites seguros.
28
+ - Avatar, título y descripción utilizan una columna centrada automática con modo manual opcional.
29
+ - Las imágenes remotas requieren HTTPS, tienen timeout, límite de 10 MB y bloqueo de redes privadas.
30
+ - El cliente REST usa `fetch` nativo, timeout y conserva el error concreto de red.
31
+ - Se alinearon los exports, la implementación y las declaraciones TypeScript.
32
+
33
+ ### Compatibility
34
+ - Se conserva la API encadenable de `Welcome`.
35
+ - `Error` continúa disponible como alias de `NekoError`.
36
+ - Las URLs HTTP de imágenes dejan de aceptarse; deben utilizar HTTPS.
37
+
38
+ ## [2.0.3] - 07-04-2025
39
+
40
+ ### Fixed
41
+ - **Error de Importaciones**
42
+ - Corregido el error de importaciones require('...')`
43
+ - Causaba que no se pudiera usar el paquete
44
+ - Se uso el mismo nombre para todas las importaciones respecto al archivo
45
+ - Afectaba a todos los usuarios que hacian uso de la api
46
+
47
+ **Impacto**:
48
+ - La actualizacion prevee que resuelva el error
49
+ - No requiere cambios en implementaciones existentes
50
+
51
+ Commit: (1e7380a)
package/README.md CHANGED
@@ -56,7 +56,7 @@ const neekuro = require('neekuro');
56
56
  */
57
57
 
58
58
  // es necesario escribir todo siempre dentro de una funcion asincrona
59
- async function ObtenerImagen() {
59
+ async function ObtenerImagen() {
60
60
  // obtenemos un gif con el metodo getGif de SFW
61
61
  const cuddle = await neekuro.SFW.getGif('action', 'cuddle');
62
62
 
@@ -65,11 +65,45 @@ async function ObtenerImagen() {
65
65
  }
66
66
 
67
67
  // ejecutamos la funcion
68
- ObtenerImagen();
68
+ ObtenerImagen();
69
69
 
70
70
  // nota: no te aguites por el gif y subcategoria, todo viene en documentado con typescript, solo tienes que completar los pasos :)
71
- ```
72
- #### Ejemplo de imagenes de bienvenida
71
+ ```
72
+
73
+ `getGif()` selecciona una imagen nuevamente en cada llamada. Internamente, NeeKuro
74
+ conserva durante cinco minutos la lista de la subcategoría para reducir solicitudes
75
+ HTTP; no conserva un único GIF durante ese tiempo. Cuando existen varias imágenes,
76
+ evita mostrar la misma dos veces consecutivas.
77
+
78
+ También puedes obtener la lista completa:
79
+
80
+ ```javascript
81
+ const gifs = await neekuro.SFW.getGifs('action', 'cuddle');
82
+ console.log(gifs.map(gif => gif.getUrl()));
83
+ ```
84
+
85
+ Si actualizaste el catálogo y necesitas consultarlo inmediatamente, puedes limpiar
86
+ una subcategoría, una categoría completa o toda la caché:
87
+
88
+ ```javascript
89
+ neekuro.SFW.clearCache('action', 'cuddle');
90
+ neekuro.SFW.clearCache('action');
91
+ neekuro.SFW.clearCache();
92
+ ```
93
+
94
+ El TTL se expresa en milisegundos y un valor de cero desactiva la caché:
95
+
96
+ ```javascript
97
+ neekuro.SFW.setCacheTTL(5 * 60 * 1000);
98
+ ```
99
+
100
+ Para pruebas o instalaciones propias de la API puede configurarse otra URL HTTPS:
101
+
102
+ ```javascript
103
+ neekuro.SFW.setBaseURL('https://api.example.com/api/sfw');
104
+ ```
105
+
106
+ #### Ejemplo de imagenes de bienvenida
73
107
  Las imagenes de bienvenida son otra de las alternativas que otorga NeeKuro, puedes hacer una imagen de la siguiente manera:
74
108
  ```javascript
75
109
  // obtenemos directamente la clase del paquete
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "neekuro",
4
- "version": "2.1.0",
4
+ "version": "2.2.0",
5
5
  "description": "Una biblioteca de imágenes con temática de anime para bots de Discord.",
6
6
  "main": "sources/main/index.js",
7
7
  "types": "sources/typings/index.d.ts",
@@ -20,10 +20,11 @@
20
20
  "directories": {
21
21
  "test": "test"
22
22
  },
23
- "files": [
24
- "sources",
25
- "README.md"
26
- ],
23
+ "files": [
24
+ "sources",
25
+ "README.md",
26
+ "CHANGELOG.md"
27
+ ],
27
28
  "contributors": [
28
29
  "cacao0027 <kmzkuro@gmail.com>"
29
30
  ],
@@ -1,47 +1,292 @@
1
- /**
2
- * @typedef {import('../typings').SfwCategories} SfwCategories
3
- * @typedef {import('../typings').GifMap} GifMap
4
- */
5
-
6
- const APIClient = require('../rest/APIClient.js');
7
- const NekoError = require('./errors/Error.js');
8
- const NekoGif = require('./package/NekoGif.js');
9
- const { getToken } = require('./user/BaseUser.js');
10
-
11
- // url base de la api para SFW
12
- const base = 'https://www.nexatdc.work.gd/api/sfw'
13
-
14
- /**
15
- * Clase SFW
16
- * No se necesita instanciar para acceder a sus metodos
17
- */
18
- class SFW {
19
- /**
20
- * Funcion principal para obtener gifs de cierta categoria
21
- * @template {SfwCategories} T
22
- * @param {T} cat corresponde a la subcategoria de SFW
23
- * @param {GifMap[T]} gif corresponde al gif que quieras obtener por categoria
24
- * @returns {Promise<NekoGif>}
25
- */
26
- static async getGif(cat, gif) {
27
- if (!cat) throw new NekoError('NoInput', 'el parametro <cat> es requerido');
28
- if (!gif) throw new NekoError('NoInput', 'el parametro <gif> es requerido');
29
-
30
- const token = getToken();
31
- if (!token) throw new NekoError('NoToken', 'Debes establecer un token antes de solicitar un GIF');
32
- let solicitud = new APIClient(base, { token }), response = null;
33
- switch (cat) {
34
- case 'action':
35
- response = solicitud.get('/action/' + gif + '?random=true')
36
- break;
37
- case 'reaction':
38
- response = solicitud.get('/reaction/' + gif + '?random=true')
39
- break;
40
- default:
41
- throw new NekoError('InvalidInput', 'la categoria que ingresas es invalida')
42
- }
43
- return new NekoGif(await response);
44
- }
45
- }
46
-
47
- module.exports = SFW
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
+ return response.data.map((item, index) => normalizeGif(item, category, gif, index));
178
+ }
179
+
180
+ /**
181
+ * @param {unknown} item
182
+ * @param {string} category
183
+ * @param {string} gif
184
+ * @param {number} index
185
+ * @returns {GifData}
186
+ */
187
+ function normalizeGif(item, category, gif, index) {
188
+ if (!item || typeof item !== 'object' || typeof item.url !== 'string' || !item.url.trim()) {
189
+ throw new NekoError('InvalidResponse', `El GIF ${index} de ${category}/${gif} no contiene una URL válida`);
190
+ }
191
+
192
+ return Object.freeze({
193
+ url: item.url.trim(),
194
+ anime: typeof item.anime === 'string' && item.anime.trim()
195
+ ? item.anime.trim()
196
+ : 'Desconocido'
197
+ });
198
+ }
199
+
200
+ /** @param {GifData} item @returns {NekoGif} */
201
+ function createNekoGif(item) {
202
+ return new NekoGif({ code: 200, data: { ...item } });
203
+ }
204
+
205
+ /**
206
+ * Elige uniformemente entre todas las posiciones, excepto la selección
207
+ * anterior cuando existen al menos dos alternativas.
208
+ * @param {number} length
209
+ * @param {number|undefined} previousIndex
210
+ */
211
+ function getRandomIndex(length, previousIndex) {
212
+ if (length === 1) return 0;
213
+ if (previousIndex === undefined || previousIndex < 0 || previousIndex >= length) {
214
+ return Math.floor(Math.random() * length);
215
+ }
216
+
217
+ const index = Math.floor(Math.random() * (length - 1));
218
+ return index >= previousIndex ? index + 1 : index;
219
+ }
220
+
221
+ /**
222
+ * @template {SfwCategories} T
223
+ * @param {T} category
224
+ * @param {GifMap[T]} gif
225
+ */
226
+ function getCacheKey(category, gif) {
227
+ validateCategory(category);
228
+ if (typeof gif !== 'string' || !gif.trim()) {
229
+ throw new NekoError('NoInput', 'el parámetro <gif> es requerido');
230
+ }
231
+ return `${category}:${gif}`;
232
+ }
233
+
234
+ /** @param {unknown} category */
235
+ function validateCategory(category) {
236
+ if (category === undefined || category === null || category === '') {
237
+ throw new NekoError('NoInput', 'el parámetro <category> es requerido');
238
+ }
239
+ if (category !== 'action' && category !== 'reaction') {
240
+ throw new NekoError('InvalidInput', 'la categoría que ingresas es inválida');
241
+ }
242
+ }
243
+
244
+ /** @param {string} url */
245
+ function validateBaseURL(url) {
246
+ if (typeof url !== 'string' || !url.trim()) {
247
+ throw new NekoError('InvalidBaseURL', 'La URL base debe ser una cadena HTTPS válida');
248
+ }
249
+
250
+ let parsed;
251
+ try {
252
+ parsed = new URL(url);
253
+ } catch {
254
+ throw new NekoError('InvalidBaseURL', 'La URL base debe ser una cadena HTTPS válida');
255
+ }
256
+ if (parsed.protocol !== 'https:' || parsed.username || parsed.password) {
257
+ throw new NekoError('InvalidBaseURL', 'La URL base debe utilizar HTTPS y no incluir credenciales');
258
+ }
259
+ return url.trim().replace(/\/$/, '');
260
+ }
261
+
262
+ /** @param {string} key @param {GifData[]} items @param {number} tokenVersion */
263
+ function storeCacheEntry(key, items, tokenVersion) {
264
+ if (!gifCache.has(key) && gifCache.size >= MAX_CACHE_ENTRIES) {
265
+ const oldest = gifCache.keys().next().value;
266
+ if (oldest) {
267
+ gifCache.delete(oldest);
268
+ lastSelections.delete(oldest);
269
+ }
270
+ }
271
+ gifCache.set(key, { items, expiresAt: Date.now() + cacheTTL, tokenVersion });
272
+ }
273
+
274
+ /** @param {string} key */
275
+ function clearCacheKey(key) {
276
+ gifCache.delete(key);
277
+ gifRequests.delete(key);
278
+ lastSelections.delete(key);
279
+ cacheVersions.set(key, (cacheVersions.get(key) ?? 0) + 1);
280
+ }
281
+
282
+ /** @param {string} prefix */
283
+ function collectKnownKeys(prefix) {
284
+ return new Set([
285
+ ...[...gifCache.keys()].filter(key => key.startsWith(prefix)),
286
+ ...[...gifRequests.keys()].filter(key => key.startsWith(prefix)),
287
+ ...[...lastSelections.keys()].filter(key => key.startsWith(prefix)),
288
+ ...[...cacheVersions.keys()].filter(key => key.startsWith(prefix))
289
+ ]);
290
+ }
291
+
292
+ module.exports = SFW;
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- let tokenGlobal = null;
3
+ let tokenGlobal = null;
4
+ let tokenVersion = 0;
4
5
 
5
6
  /**
6
7
  * Clase BaseUser
@@ -15,16 +16,26 @@ class BaseUser {
15
16
  if (typeof token !== 'string' || !token.trim()) {
16
17
  throw new TypeError('El token debe ser una cadena no vacía');
17
18
  }
18
- tokenGlobal = token;
19
+ const normalizedToken = token.trim();
20
+ if (tokenGlobal !== normalizedToken) tokenVersion += 1;
21
+ tokenGlobal = normalizedToken;
19
22
  }
20
23
 
21
24
  /**
22
25
  * Obten el token del usuario si este ya se estableció
23
26
  * @returns {string} Token de usuario
24
27
  */
25
- static getToken() {
26
- return tokenGlobal;
27
- }
28
- }
28
+ static getToken() {
29
+ return tokenGlobal;
30
+ }
31
+
32
+ /**
33
+ * Identificador interno que cambia cada vez que se establece otro token.
34
+ * @returns {number}
35
+ */
36
+ static getTokenVersion() {
37
+ return tokenVersion;
38
+ }
39
+ }
29
40
 
30
41
  module.exports = BaseUser;
@@ -51,7 +51,8 @@ class APIClient {
51
51
  /**
52
52
  * Obtiene el objeto compartido por la api a traves de la ruta y con autorizacion por token
53
53
  * @param {string} endpoint establece la ruta a la que se quiere comunicar
54
- * @returns {Promise<GifResponse>}
54
+ * @template {import('../typings').ApiResponse} [T=GifResponse]
55
+ * @returns {Promise<T>}
55
56
  * @throws {APIError} solo si sucede un error entre solicitudes
56
57
  */
57
58
  async get(endpoint, options = {}) {
@@ -80,7 +81,7 @@ class APIClient {
80
81
  throw new APIError(endpoint, response, data);
81
82
  }
82
83
 
83
- return data
84
+ return /** @type {T} */ (data)
84
85
  } catch (error) {
85
86
  if (error instanceof APIError) {
86
87
  throw error;
@@ -7,7 +7,7 @@
7
7
  * // obtiene el objeto SFW
8
8
  * const SFW = require('neekuro').SFW
9
9
  */
10
- export const SFW: {
10
+ export const SFW: {
11
11
  /**
12
12
  * Funcion principal para obtener gifs de cierta categoria
13
13
  * @param category category: corresponde a la subcategoria de SFW
@@ -23,8 +23,18 @@ export const SFW: {
23
23
  * // obtener anime
24
24
  * cuddle.getAnime();
25
25
  */
26
- getGif<T extends SfwCategories>(category: T, gif: GifMap[T]): Promise<NekoGif>
27
- }
26
+ getGif<T extends SfwCategories>(category: T, gif: GifMap[T]): Promise<NekoGif>
27
+ /** Obtiene todos los GIF disponibles y utiliza la caché interna. */
28
+ getGifs<T extends SfwCategories>(category: T, gif: GifMap[T]): Promise<NekoGif[]>
29
+ /** Limpia toda la caché de imágenes. */
30
+ clearCache(): void
31
+ /** Limpia la caché de una categoría completa o de una subcategoría. */
32
+ clearCache<T extends SfwCategories>(category: T, gif?: GifMap[T]): void
33
+ /** Cambia la URL HTTPS utilizada para consultar SFW. */
34
+ setBaseURL(url: string): void
35
+ /** Configura el TTL en milisegundos. El valor cero desactiva la caché. */
36
+ setCacheTTL(milliseconds: number): void
37
+ }
28
38
 
29
39
  //#endregion
30
40
 
@@ -136,10 +146,15 @@ export interface WelcomeData {
136
146
  };
137
147
  }
138
148
 
139
- export interface NekoGifInterface {
140
- url: string | null
141
- anime?: string | null
142
- }
149
+ export interface NekoGifInterface {
150
+ url: string | null
151
+ anime?: string | null
152
+ }
153
+
154
+ export interface GifData {
155
+ url: string
156
+ anime: string
157
+ }
143
158
 
144
159
  export declare interface GifMap {
145
160
  action: ActionGifs
@@ -147,11 +162,12 @@ export declare interface GifMap {
147
162
  }
148
163
 
149
164
  export interface GifResponse extends ApiResponse {
150
- data: {
151
- url: string;
152
- anime: string;
153
- }
154
- }
165
+ data: GifData
166
+ }
167
+
168
+ export interface GifListResponse extends ApiResponse {
169
+ data: GifData[]
170
+ }
155
171
 
156
172
  export interface ApiResponse {
157
173
  message?: string,
@@ -303,7 +319,10 @@ export class BaseUser {
303
319
  * Obten el token del usuario si este ya se estableció
304
320
  * @returns {string} Token de usuario
305
321
  */
306
- public static getToken(): string
322
+ public static getToken(): string | null
323
+
324
+ /** Versión interna que cambia cuando se establece un token diferente. */
325
+ public static getTokenVersion(): number
307
326
 
308
327
  /**
309
328
  * Establece el token "global" para todo el paquete
@@ -330,8 +349,8 @@ export class APIClient {
330
349
  * @param endpoint establece la ruta a la que se quiere comunicar en la api
331
350
  * @throws {APIError} solo si sucede un error entre solicitudes
332
351
  */
333
- public get(endpoint: string, options?: { timeout?: number }): Promise<GifResponse>
334
- }
352
+ public get<T extends ApiResponse = GifResponse>(endpoint: string, options?: { timeout?: number }): Promise<T>
353
+ }
335
354
 
336
355
  /**
337
356
  * Clase APIError