neekuro 2.0.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/package.json +64 -0
- package/readme.md +123 -0
- package/sources/assets/arial.ttf +0 -0
- package/sources/assets/calibri.ttf +0 -0
- package/sources/assets/example.png +0 -0
- package/sources/classes/errors/apierror.js +60 -0
- package/sources/classes/errors/error.js +25 -0
- package/sources/classes/package/nekogif.js +50 -0
- package/sources/classes/package/welcome.js +540 -0
- package/sources/classes/sfw.js +45 -0
- package/sources/classes/user/baseuser.js +27 -0
- package/sources/classes/user/user.js +41 -0
- package/sources/main/index.js +24 -0
- package/sources/rest/apiclient.js +65 -0
- package/sources/typings/index.d.ts +384 -0
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const Canvas = require('canvas');
|
|
3
|
+
const NekoError = require('../errors/error');
|
|
4
|
+
const hex_reg = require('hex-color-regex')({ strict: true })
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {import('../../typings').FontNames} FontNames
|
|
9
|
+
* @typedef {import('../../typings').WelcomeTextData} WelcomeTextData
|
|
10
|
+
* @typedef {import('../../typings').WelcomeAvatarData} WelcomeAvatarData
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Clase para generar imágenes de bienvenida personalizadas para Discord
|
|
15
|
+
* @class Welcome
|
|
16
|
+
* @example
|
|
17
|
+
* const Welcome = require('neekuro').Welcome;
|
|
18
|
+
* const welcome = new Welcome()
|
|
19
|
+
* .setResolution(1200, 600)
|
|
20
|
+
* .setFont('arial')
|
|
21
|
+
* .setTitle('¡Bienvenido!', { text_color: '#FF5733' })
|
|
22
|
+
* .setDescription('Al servidor de la comunidad', { font_size: 24 });
|
|
23
|
+
*/
|
|
24
|
+
class Welcome {
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Construye una imagen
|
|
28
|
+
*/
|
|
29
|
+
constructor() {
|
|
30
|
+
/**
|
|
31
|
+
* @property {Object} data - Configuración actual de la imagen
|
|
32
|
+
* @property {string} data.font - Fuente actualmente seleccionada
|
|
33
|
+
* @property {number} data.width - Ancho actual de la imagen
|
|
34
|
+
* @property {number} data.height - Alto actual de la imagen
|
|
35
|
+
* @property {Object} data.background - Configuración del fondo
|
|
36
|
+
* @property {'color'|'image'} data.background.type - Tipo de fondo
|
|
37
|
+
* @property {string} data.background.value - Color o ruta de imagen
|
|
38
|
+
* @property {Object} data.avatar - Configuración del avatar
|
|
39
|
+
* @property {?string} data.avatar.source - Ruta a la imagen del avatar
|
|
40
|
+
* @property {number} data.avatar.x - Posición X del avatar
|
|
41
|
+
* @property {number} data.avatar.y - Posición Y del avatar
|
|
42
|
+
* @property {number} data.avatar.radio - Radio para avatar circular
|
|
43
|
+
* @property {string} data.avatar.border - Color del borde del avatar
|
|
44
|
+
* @property {Object} data.title - Configuración del título
|
|
45
|
+
* @property {?string} data.title.content - Contenido del título
|
|
46
|
+
* @property {number} data.title.x - Posición X del título
|
|
47
|
+
* @property {number} data.title.y - Posición Y del título
|
|
48
|
+
* @property {number} data.title.font_size - Tamaño de fuente del título
|
|
49
|
+
* @property {string} data.title.text_color - Color del texto del título
|
|
50
|
+
* @property {Object} data.description - Configuración de la descripción
|
|
51
|
+
* @property {?string} data.description.content - Contenido de la descripción
|
|
52
|
+
* @property {number} data.description.x - Posición X de la descripción
|
|
53
|
+
* @property {number} data.description.y - Posición Y de la descripción
|
|
54
|
+
* @property {number} data.description.font_size - Tamaño de fuente de la descripción
|
|
55
|
+
* @property {string} data.description.text_color - Color del texto de la descripción
|
|
56
|
+
*/
|
|
57
|
+
this.data = {
|
|
58
|
+
font: 'arial',
|
|
59
|
+
width: 1024,
|
|
60
|
+
height: 450,
|
|
61
|
+
background: {
|
|
62
|
+
type: 'color',
|
|
63
|
+
value: '#23272A'
|
|
64
|
+
},
|
|
65
|
+
avatar: {
|
|
66
|
+
source: null,
|
|
67
|
+
x: 455,
|
|
68
|
+
y: 80,
|
|
69
|
+
radio: 120,
|
|
70
|
+
border: "#F7F7F7"
|
|
71
|
+
},
|
|
72
|
+
title: {
|
|
73
|
+
content: "¡Bienvenido!",
|
|
74
|
+
x: 590,
|
|
75
|
+
y: 450,
|
|
76
|
+
font_size: 40,
|
|
77
|
+
text_color: '#FFFFFF'
|
|
78
|
+
},
|
|
79
|
+
description: {
|
|
80
|
+
content: 'Al mejor servidor de discord',
|
|
81
|
+
x: 590,
|
|
82
|
+
y: 500,
|
|
83
|
+
font_size: 30,
|
|
84
|
+
text_color: "#F7F7F7"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Establece la resolución de la imagen de bienvenida
|
|
91
|
+
* @param {number|'default'} width - Ancho en píxeles o 'default' para 1024px
|
|
92
|
+
* @param {number|'default'} height - Alto en píxeles o 'default' para 450px
|
|
93
|
+
* @returns {Welcome}
|
|
94
|
+
* @throws {NekoError} Si los parámetros son inválidos
|
|
95
|
+
* @example
|
|
96
|
+
* // Establecer resolución personalizada
|
|
97
|
+
* welcome.setResolution(1920, 1080);
|
|
98
|
+
*
|
|
99
|
+
* // Restablecer a valores por defecto
|
|
100
|
+
* welcome.setResolution('default', 'default');
|
|
101
|
+
*/
|
|
102
|
+
setResolution(width, height) {
|
|
103
|
+
if (!width) throw new NekoError('ResolutionError', 'el parametro <width> no puede quedar vacio');
|
|
104
|
+
if (!height) throw new NekoError('ResolutionError', 'el parametro <height> no puede quedar vacio');
|
|
105
|
+
|
|
106
|
+
if (width == 'default') width = this.data.width; else {
|
|
107
|
+
if (typeof width != 'number') throw new NekoError('TypeError', 'el parametro <width> solo acepta tipo \'number\', recibió ' + typeof width);
|
|
108
|
+
this.data.width = width;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (height == 'default') height = this.data.height; else {
|
|
112
|
+
if (typeof height != 'number') throw new NekoError('TypeError', 'el parametro <height> solo acepta tipo \'number\', recibió ' + typeof height);
|
|
113
|
+
this.data.height = height;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return this
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Configura la tipografía para el texto de la imagen
|
|
121
|
+
* @param {FontNames} name - Nombre de la fuente predefinida o 'custom' para personalizada
|
|
122
|
+
* @param {string} [dir] - Ruta al archivo .ttf (requerido si name='custom')
|
|
123
|
+
* @returns {Welcome}
|
|
124
|
+
* @throws {NekoError} Si la fuente no existe o los parámetros son inválidos
|
|
125
|
+
* @example
|
|
126
|
+
* // Usar fuente predefinida
|
|
127
|
+
* welcome.setFont('calibri');
|
|
128
|
+
*
|
|
129
|
+
* // Usar fuente personalizada
|
|
130
|
+
* welcome.setFont('custom', './fonts/mi_fuente.ttf');
|
|
131
|
+
*/
|
|
132
|
+
setFont(name, dir) {
|
|
133
|
+
let ruta = null;
|
|
134
|
+
|
|
135
|
+
if (!name) throw new NekoError('FontError', 'el parametro <name> no puede quedar vacio');
|
|
136
|
+
if (typeof name != 'string') throw new NekoError('TypeError', 'el parametro <name> solo acepta tipo \'string\', recibió ' + typeof name);
|
|
137
|
+
|
|
138
|
+
switch (name) {
|
|
139
|
+
case 'arial':
|
|
140
|
+
ruta = path.join(__dirname, '../../assets/arial.ttf');
|
|
141
|
+
break;
|
|
142
|
+
case 'calibri':
|
|
143
|
+
ruta = path.join(__dirname, '../../assets/calibri.ttf');
|
|
144
|
+
break;
|
|
145
|
+
case 'custom':
|
|
146
|
+
if (!dir) throw new NekoError('FontError', 'el parametro <dir> no puede quedar vacio en un tipo de fuente customizada');
|
|
147
|
+
if (typeof dir != 'string') throw new NekoError('TypeError', 'el parametro <dir> solo acepta tipo \'string\', recibió ' + typeof dir);
|
|
148
|
+
if (!dir.endsWith('ttf')) throw new NekoError('FontError', 'se nesecita un archivo ttf en el parametro <dir>')
|
|
149
|
+
ruta = path.join(__dirname, '../../../../' + dir.replace('./', ''));
|
|
150
|
+
break;
|
|
151
|
+
default: throw new NekoError('FontError', 'el parametro <name> no acepta ningun tipo diferente a los preterminados');
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
Canvas.registerFont(ruta, { family: name })
|
|
155
|
+
} catch (error) {
|
|
156
|
+
throw new NekoError('DirError', 'no se puede encontrar el directorio especificado')
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
this.data.font = name
|
|
160
|
+
return this;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Agrega un título a la imagen de bienvenida
|
|
165
|
+
* @param {string} text - Texto del título
|
|
166
|
+
* @param {WelcomeTextData} data - Configuración del título
|
|
167
|
+
* @throws {NekoError} Arroja un error si parámetros son inválidos
|
|
168
|
+
* @returns {Welcome}
|
|
169
|
+
*/
|
|
170
|
+
setTitle(text, data) {
|
|
171
|
+
// Validaciones del texto
|
|
172
|
+
if (!text) {
|
|
173
|
+
throw new NekoError('TitleError', 'El parámetro <text> no puede estar vacío');
|
|
174
|
+
}
|
|
175
|
+
if (typeof text !== 'string') {
|
|
176
|
+
throw new NekoError('TypeError', `El parámetro <text> solo acepta tipo 'string', recibió ${typeof text}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
this.data.title.content = text;
|
|
180
|
+
|
|
181
|
+
// Validaciones para las propiedades adicionales
|
|
182
|
+
if (data) {
|
|
183
|
+
// Validación para text_color
|
|
184
|
+
if (data.text_color) {
|
|
185
|
+
if (typeof data.text_color !== 'string') {
|
|
186
|
+
throw new NekoError('TypeError', `La propiedad <text_color> solo acepta tipo 'string', recibió ${typeof data.text_color}`);
|
|
187
|
+
}
|
|
188
|
+
if (!data.text_color.startsWith('#')) {
|
|
189
|
+
throw new NekoError('TitleError', 'El valor de <text_color> debe comenzar con \'#\'');
|
|
190
|
+
}
|
|
191
|
+
if (!hex_reg.test(data.text_color)) {
|
|
192
|
+
throw new NekoError('TitleError', 'El valor de <text_color> no es un color válido');
|
|
193
|
+
}
|
|
194
|
+
this.data.title.text_color = data.text_color;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// validacion del tamaño de texto
|
|
198
|
+
if (data.font_size) {
|
|
199
|
+
if (typeof data.font_size !== 'number') {
|
|
200
|
+
throw new NekoError('TypeError', `La propiedad <font_size> solo acepta tipo 'number', recibió ${typeof data.font_size}`);
|
|
201
|
+
}
|
|
202
|
+
if (data.font_size <= 0) {
|
|
203
|
+
throw new NekoError('TitleError', 'El valor de <font_size> debe ser mayor que 0');
|
|
204
|
+
}
|
|
205
|
+
this.data.title.font_size = data.font_size;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// validacion de posiciones
|
|
209
|
+
if (data.x) {
|
|
210
|
+
if (typeof data.x !== 'number') {
|
|
211
|
+
throw new NekoError('TypeError', `La propiedad <x> solo acepta tipo 'number', recibió ${typeof data.x}`);
|
|
212
|
+
}
|
|
213
|
+
this.data.title.x = data.x;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (data.y) {
|
|
217
|
+
if (typeof data.y !== 'number') {
|
|
218
|
+
throw new NekoError('TypeError', `La propiedad <y> solo acepta tipo 'number', recibió ${typeof data.y}`);
|
|
219
|
+
}
|
|
220
|
+
this.data.title.y = data.y;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return this
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Agrega una descripción a la imagen de bienvenida
|
|
229
|
+
* @param {string} text - Texto de la descripción
|
|
230
|
+
* @param {WelcomeTextData} data - Configuración de la descripción
|
|
231
|
+
* @throws {NekoError} Arroja un error si parámetros son inválidos
|
|
232
|
+
* @returns {Welcome}
|
|
233
|
+
*/
|
|
234
|
+
setDescription(text, data) {
|
|
235
|
+
// Validaciones del texto
|
|
236
|
+
if (!text) {
|
|
237
|
+
throw new NekoError('DescriptionError', 'El parámetro <text> no puede estar vacío');
|
|
238
|
+
}
|
|
239
|
+
if (typeof text !== 'string') {
|
|
240
|
+
throw new NekoError('TypeError', `El parámetro <text> solo acepta tipo 'string', recibió ${typeof text}`);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
this.data.description.content = text;
|
|
244
|
+
|
|
245
|
+
// Validaciones para las propiedades adicionales
|
|
246
|
+
if (data) {
|
|
247
|
+
// Validación para text_color
|
|
248
|
+
if (data.text_color) {
|
|
249
|
+
if (typeof data.text_color !== 'string') {
|
|
250
|
+
throw new NekoError('TypeError', `La propiedad <text_color> solo acepta tipo 'string', recibió ${typeof data.text_color}`);
|
|
251
|
+
}
|
|
252
|
+
if (!data.text_color.startsWith('#')) {
|
|
253
|
+
throw new NekoError('DescriptionError', 'El valor de <text_color> debe comenzar con \'#\'');
|
|
254
|
+
}
|
|
255
|
+
if (!hex_reg.test(data.text_color)) {
|
|
256
|
+
throw new NekoError('DescriptionError', 'El valor de <text_color> no es un color válido');
|
|
257
|
+
}
|
|
258
|
+
this.data.description.text_color = data.text_color;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// validacion del tamaño de texto
|
|
262
|
+
if (data.font_size) {
|
|
263
|
+
if (typeof data.font_size !== 'number') {
|
|
264
|
+
throw new NekoError('TypeError', `La propiedad <font_size> solo acepta tipo 'number', recibió ${typeof data.font_size}`);
|
|
265
|
+
}
|
|
266
|
+
if (data.font_size <= 0) {
|
|
267
|
+
throw new NekoError('DescriptionError', 'El valor de <font_size> debe ser mayor que 0');
|
|
268
|
+
}
|
|
269
|
+
this.data.description.font_size = data.font_size;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// validacion de posiciones
|
|
273
|
+
if (data.x) {
|
|
274
|
+
if (typeof data.x !== 'number') {
|
|
275
|
+
throw new NekoError('TypeError', `La propiedad <x> solo acepta tipo 'number', recibió ${typeof data.x}`);
|
|
276
|
+
}
|
|
277
|
+
this.data.description.x = data.x;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (data.y) {
|
|
281
|
+
if (typeof data.y !== 'number') {
|
|
282
|
+
throw new NekoError('TypeError', `La propiedad <y> solo acepta tipo 'number', recibió ${typeof data.y}`);
|
|
283
|
+
}
|
|
284
|
+
this.data.description.y = data.y;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return this
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Establece el fondo de la imagen de bienvenida (color o imagen)
|
|
293
|
+
* @param {'color'|'image'} type - Tipo de fondo ('color' o 'image')
|
|
294
|
+
* @param {string|Buffer} data - Color hexadecimal (ej. '#FF5733') o imagen (URL/Buffer)
|
|
295
|
+
* @returns {Welcome}
|
|
296
|
+
* @throws {NekoError} Si los parámetros son inválidos
|
|
297
|
+
* @example
|
|
298
|
+
* // Fondo con color
|
|
299
|
+
* welcome.setBackground('color', '#23272A');
|
|
300
|
+
*
|
|
301
|
+
* // Fondo con imagen desde URL
|
|
302
|
+
* welcome.setBackground('image', 'https://ejemplo.com/fondo.jpg');
|
|
303
|
+
*
|
|
304
|
+
* // Fondo con imagen desde Buffer
|
|
305
|
+
* const imageBuffer = fs.readFileSync('fondo.jpg');
|
|
306
|
+
* welcome.setBackground('image', imageBuffer);
|
|
307
|
+
*/
|
|
308
|
+
setBackground(type, data) {
|
|
309
|
+
if (!type) throw new NekoError('BackgroundError', 'El parámetro <type> no puede estar vacío');
|
|
310
|
+
if (typeof type != 'string') throw new NekoError('TypeError', `El parámetro <type> debe ser string, recibió ${typeof type}`);
|
|
311
|
+
if (!['color', 'image'].includes(type)) throw new NekoError('BackgroundError', 'El tipo de fondo debe ser "color" o "image"');
|
|
312
|
+
|
|
313
|
+
if (!data) throw new NekoError('BackgroundError', 'El parámetro <data> no puede estar vacío');
|
|
314
|
+
|
|
315
|
+
if (type == 'color') {
|
|
316
|
+
if (typeof data != 'string') throw new NekoError('TypeError', 'Para fondo de color, <data> debe ser string');
|
|
317
|
+
if (!data.startsWith('#')) throw new NekoError('BackgroundError', 'El color debe comenzar con \'#\'');
|
|
318
|
+
if (!hex_reg.test(data)) throw new NekoError('BackgroundError', 'El valor no es un color hexadecimal válido');
|
|
319
|
+
|
|
320
|
+
this.data.background = { type, data };
|
|
321
|
+
return this;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (type == 'image') {
|
|
325
|
+
if (typeof data != 'string' && !Buffer.isBuffer(data)) {
|
|
326
|
+
throw new NekoError('TypeError', 'Para fondo de imagen, <data> debe ser URL (string) o Buffer');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (typeof data == 'string') {
|
|
330
|
+
try {
|
|
331
|
+
new URL(data);
|
|
332
|
+
} catch (error) {
|
|
333
|
+
throw new NekoError('BackgroundError', 'La URL proporcionada no es válida');
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
this.data.background = { type, data };
|
|
338
|
+
return this;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Establece el avatar y su configuración en la imagen de bienvenida
|
|
344
|
+
* @param {string|Buffer} source - URL del avatar o Buffer de la imagen
|
|
345
|
+
* @param {WelcomeAvatarData} [data] - Configuración opcional del avatar
|
|
346
|
+
* @returns {Welcome}
|
|
347
|
+
* @throws {NekoError} Si los parámetros son inválidos
|
|
348
|
+
* @example
|
|
349
|
+
* // Configuración básica con URL
|
|
350
|
+
* welcome.setAvatar('https://example.com/avatar.jpg');
|
|
351
|
+
*
|
|
352
|
+
* // Configuración completa con Buffer
|
|
353
|
+
* const avatarBuffer = fs.readFileSync('avatar.png');
|
|
354
|
+
* welcome.setAvatar(avatarBuffer, {
|
|
355
|
+
* x: 300,
|
|
356
|
+
* y: 100,
|
|
357
|
+
* border: '#FF5733',
|
|
358
|
+
* radio: 120
|
|
359
|
+
* });
|
|
360
|
+
*/
|
|
361
|
+
setAvatar(source, data) {
|
|
362
|
+
// Validación del source
|
|
363
|
+
if (!source) {
|
|
364
|
+
throw new NekoError('AvatarError', 'El parámetro <source> no puede estar vacío');
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (typeof source != 'string' && !Buffer.isBuffer(source)) {
|
|
368
|
+
throw new NekoError('TypeError', 'El parámetro <source> debe ser string (URL) o Buffer');
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (typeof source === 'string') {
|
|
372
|
+
try {
|
|
373
|
+
const url = new URL(source);
|
|
374
|
+
|
|
375
|
+
if (!['http:', 'https:'].includes(url.protocol)) {
|
|
376
|
+
throw new NekoError('AvatarError', 'La URL debe usar HTTP/HTTPS');
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const extension = url.pathname.toLowerCase().split('.').pop();
|
|
380
|
+
if (!['jpg', 'jpeg', 'png', 'gif'].includes(extension)) {
|
|
381
|
+
throw new NekoError('AvatarError', 'Formato de imagen no soportado. Usa JPEG, PNG o GIF');
|
|
382
|
+
}
|
|
383
|
+
} catch (error) {
|
|
384
|
+
if (error instanceof NekoError) throw error;
|
|
385
|
+
throw new NekoError('AvatarError', 'La URL del avatar no es válida');
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (Buffer.isBuffer(source)) {
|
|
390
|
+
if (source.length < 8) {
|
|
391
|
+
throw new NekoError('AvatarError', 'El Buffer de imagen es demasiado pequeño');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const header = source.toString('hex', 0, 8);
|
|
395
|
+
const isJPEG = header.startsWith('ffd8ff');
|
|
396
|
+
const isPNG = header.startsWith('89504e470d0a1a0a');
|
|
397
|
+
const isGIF = header.startsWith('47494638');
|
|
398
|
+
|
|
399
|
+
if (!isJPEG && !isPNG && !isGIF) {
|
|
400
|
+
throw new NekoError('AvatarError', 'Formato de imagen no soportado. El Buffer debe ser JPEG, PNG o GIF');
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
this.data.avatar.source = source;
|
|
405
|
+
|
|
406
|
+
if (data) {
|
|
407
|
+
if (data.x) {
|
|
408
|
+
if (typeof data.x != 'number') {
|
|
409
|
+
throw new NekoError('TypeError', 'La propiedad <x> debe ser un número');
|
|
410
|
+
}
|
|
411
|
+
if (data.x < 0) {
|
|
412
|
+
throw new NekoError('AvatarError', 'La posición X no puede ser negativa');
|
|
413
|
+
}
|
|
414
|
+
this.data.avatar.x = data.x;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (data.y) {
|
|
418
|
+
if (typeof data.y != 'number') {
|
|
419
|
+
throw new NekoError('TypeError', 'La propiedad <y> debe ser un número');
|
|
420
|
+
}
|
|
421
|
+
this.data.avatar.y = data.y;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (data.border) {
|
|
425
|
+
if (typeof data.border != 'string') {
|
|
426
|
+
throw new NekoError('TypeError', 'La propiedad <border> debe ser un string');
|
|
427
|
+
}
|
|
428
|
+
if (!data.border.startsWith('#')) {
|
|
429
|
+
throw new NekoError('AvatarError', 'El color del borde debe comenzar con #');
|
|
430
|
+
}
|
|
431
|
+
if (!hex_reg.test(data.border)) {
|
|
432
|
+
throw new NekoError('AvatarError', 'El color del borde no es un hexadecimal válido');
|
|
433
|
+
}
|
|
434
|
+
this.data.avatar.border = data.border;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (data.radio) {
|
|
438
|
+
if (typeof data.radio != 'number') {
|
|
439
|
+
throw new NekoError('TypeError', 'La propiedad <radio> debe ser un número');
|
|
440
|
+
}
|
|
441
|
+
this.data.avatar.radio = data.radio;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
return this;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Genera la imagen de bienvenida con la configuracion actual
|
|
450
|
+
* @returns {Promise<Buffer>} Buffer de la imagen generada
|
|
451
|
+
* @throws {NekoError} Si faltan datos
|
|
452
|
+
* @throws {Error} Si hay problemas al cargar imágenes
|
|
453
|
+
*/
|
|
454
|
+
async build() {
|
|
455
|
+
if (!this.data.avatar?.source) {
|
|
456
|
+
throw new NekoError('BuildError', 'No se ha podido cargar un avatar. Usa el metodo .setAvatar()')
|
|
457
|
+
}
|
|
458
|
+
if (!this.data.title?.content) {
|
|
459
|
+
throw new NekoError('BuildError', 'No se puede dar un titulo vacío. Usa el metodo .setTitle()')
|
|
460
|
+
}
|
|
461
|
+
if (!this.data.description?.content) {
|
|
462
|
+
throw new NekoError('BuildError', 'No se puede dar una descripcion vacía. Usa el metodo .setDescription()')
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
try {
|
|
466
|
+
const [bg, avatar] = await Promise.all([
|
|
467
|
+
this.data.background.type == 'image' ? Canvas.loadImage(this.data.background.value) : null,
|
|
468
|
+
Canvas.loadImage(this.data.avatar.source)
|
|
469
|
+
])
|
|
470
|
+
|
|
471
|
+
const canvas = Canvas.createCanvas(this.data.width, this.data.height)
|
|
472
|
+
const ctx = canvas.getContext('2d')
|
|
473
|
+
|
|
474
|
+
if (bg) {
|
|
475
|
+
let ratio = Math.max(canvas.width / bg.width, canvas.height / bg.height);
|
|
476
|
+
let width = bg.width * ratio;
|
|
477
|
+
let height = bg.height * ratio;
|
|
478
|
+
ctx.drawImage(bg, (canvas.width - width) / 2, (canvas.height - height) / 2, width, height);
|
|
479
|
+
} else {
|
|
480
|
+
ctx.fillStyle = this.data.background.value;
|
|
481
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const maxTitleWidth = this.data.width * 0.8;
|
|
485
|
+
let titleFontSize = this.data.title.font_size;
|
|
486
|
+
ctx.font = `${titleFontSize}px ${this.data.font}`;
|
|
487
|
+
|
|
488
|
+
while (ctx.measureText(this.data.title.content).width > maxTitleWidth && titleFontSize > 10) {
|
|
489
|
+
titleFontSize -= 1;
|
|
490
|
+
ctx.font = `${titleFontSize}px ${this.data.font}`;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
ctx.fillStyle = this.data.title.text_color;
|
|
494
|
+
ctx.textAlign = "center";
|
|
495
|
+
ctx.fillText(this.data.title.content, this.data.title.x, this.data.title.y);
|
|
496
|
+
|
|
497
|
+
const maxDescWidth = this.data.width * 0.9;
|
|
498
|
+
let descFontSize = this.data.description.font_size;
|
|
499
|
+
ctx.font = `${descFontSize}px ${this.data.font}`;
|
|
500
|
+
|
|
501
|
+
while (ctx.measureText(this.data.description.content).width > maxDescWidth && descFontSize > 8) {
|
|
502
|
+
descFontSize -= 1;
|
|
503
|
+
ctx.font = `${descFontSize}px ${this.data.font}`;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
ctx.fillStyle = this.data.description.text_color;
|
|
507
|
+
ctx.textAlign = "center";
|
|
508
|
+
ctx.fillText(this.data.description.content, this.data.description.x, this.data.description.y);
|
|
509
|
+
|
|
510
|
+
const x = this.data.avatar.x;
|
|
511
|
+
const y = this.data.avatar.y;
|
|
512
|
+
const radio = this.data.avatar.radio;
|
|
513
|
+
|
|
514
|
+
if (this.data.avatar.border) {
|
|
515
|
+
ctx.beginPath();
|
|
516
|
+
ctx.fillStyle = this.data.avatar.border;
|
|
517
|
+
ctx.arc(x + radio, y + radio, radio + 6, 0, 2 * Math.PI);
|
|
518
|
+
ctx.fill();
|
|
519
|
+
ctx.closePath();
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
ctx.save();
|
|
523
|
+
ctx.beginPath();
|
|
524
|
+
ctx.arc(x + radio, y + radio, radio, 0, Math.PI * 2, true);
|
|
525
|
+
ctx.closePath();
|
|
526
|
+
ctx.clip();
|
|
527
|
+
ctx.drawImage(avatar, x, y, radio * 2, radio * 2);
|
|
528
|
+
ctx.restore();
|
|
529
|
+
|
|
530
|
+
return canvas.toBuffer();
|
|
531
|
+
} catch (error) {
|
|
532
|
+
if(error instanceof NekoError) {
|
|
533
|
+
throw error;
|
|
534
|
+
}
|
|
535
|
+
throw new NekoError('GenerateError', 'Error al generar la imagen: '+error.message)
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
module.exports = Welcome;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('../typings').SfwCategories} SfwCategories
|
|
3
|
+
* @typedef {import('../typings').GifMap} GifMap
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const APIClient = require('../rest/apiclient');
|
|
7
|
+
const NekoError = require('./errors/error');
|
|
8
|
+
const NekoGif = require('./package/nekogif');
|
|
9
|
+
const { getToken } = require('./user/baseuser');
|
|
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
|
+
let solicitud = new APIClient(base, { token: getToken() }), response = null;
|
|
31
|
+
switch (cat) {
|
|
32
|
+
case 'action':
|
|
33
|
+
response = solicitud.get('/action/' + gif + '?random=true')
|
|
34
|
+
break;
|
|
35
|
+
case 'reaction':
|
|
36
|
+
response = solicitud.get('/reaction/' + gif + '?random=true')
|
|
37
|
+
break;
|
|
38
|
+
default:
|
|
39
|
+
throw new NekoError('InvalidInput', 'la categoria que ingresas es invalida')
|
|
40
|
+
}
|
|
41
|
+
return new NekoGif(await response);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = SFW
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
let tokenGlobal = null;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Clase BaseUser
|
|
7
|
+
* Solamente gestiona el token por todo el paquete.
|
|
8
|
+
*/
|
|
9
|
+
class BaseUser {
|
|
10
|
+
/**
|
|
11
|
+
* Establece el token "global" en todo el paquete.
|
|
12
|
+
* @param {string} token token a establecer.
|
|
13
|
+
*/
|
|
14
|
+
static setToken(token) {
|
|
15
|
+
tokenGlobal = token;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Obten el token del usuario si este ya se estableció
|
|
20
|
+
* @returns {string} Token de usuario
|
|
21
|
+
*/
|
|
22
|
+
static getToken() {
|
|
23
|
+
return tokenGlobal;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = BaseUser;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { setToken } = require("./baseuser");
|
|
4
|
+
const NekoError = require('../errors/error')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Clase Usuario
|
|
8
|
+
* Instancia al usuario que hara uso de la api
|
|
9
|
+
*/
|
|
10
|
+
class User {
|
|
11
|
+
/**
|
|
12
|
+
* Puedes establecer el token aqui, sin embargo, recomiendo hacerlo en el metodo dedicado
|
|
13
|
+
* @param {string} token Token de la api
|
|
14
|
+
*/
|
|
15
|
+
constructor(token) {
|
|
16
|
+
this.token_ = token
|
|
17
|
+
setToken(this.#getToken());
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Establece el token para usar la api
|
|
22
|
+
* @param {string} token token de la api
|
|
23
|
+
* @throws {NekoError} si el token no fue dado o no es una cadena
|
|
24
|
+
*/
|
|
25
|
+
token(token) {
|
|
26
|
+
if(!token) throw new NekoError('NoToken', 'el parametro <token> es requerido');
|
|
27
|
+
if(typeof(token) !== 'string') throw new NekoError('InvalidType', `Token invaldo... el tipo de dato es String no ${typeof(token)}`);
|
|
28
|
+
this.token_ = token;
|
|
29
|
+
setToken(this.#getToken());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Obtiene el token
|
|
34
|
+
* @returns {string|undefined} token de la api
|
|
35
|
+
*/
|
|
36
|
+
#getToken() {
|
|
37
|
+
return this.token_;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = User
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
//#region Clases
|
|
4
|
+
|
|
5
|
+
exports.User = require('../classes/user/user')
|
|
6
|
+
|
|
7
|
+
// paquete
|
|
8
|
+
|
|
9
|
+
exports.NekoGif = require('../classes/package/nekogif')
|
|
10
|
+
exports.BaseUser = require('../classes/user/baseuser')
|
|
11
|
+
exports.Welcome = require('../classes/package/welcome')
|
|
12
|
+
|
|
13
|
+
// rest
|
|
14
|
+
|
|
15
|
+
exports.APIClient = require('../rest/apiclient')
|
|
16
|
+
|
|
17
|
+
// errores
|
|
18
|
+
|
|
19
|
+
exports.APIError = require('../classes/errors/apierror')
|
|
20
|
+
exports.Error = require('../classes/errors/error')
|
|
21
|
+
|
|
22
|
+
//#region "constantes"
|
|
23
|
+
|
|
24
|
+
exports.SFW = require('../classes/sfw')
|