@cognima/banners 0.0.1-beta
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/assets/fonts/Manrope/Manrope-Bold.ttf +0 -0
- package/assets/fonts/Manrope/Manrope-Regular.ttf +0 -0
- package/assets/fonts/Others/AbyssinicaSIL-Regular.ttf +0 -0
- package/assets/fonts/Others/ChirpRegular.ttf +0 -0
- package/assets/fonts/Poppins/Poppins-Bold.ttf +0 -0
- package/assets/fonts/Poppins/Poppins-Medium.ttf +0 -0
- package/assets/fonts/Poppins/Poppins-Regular.ttf +0 -0
- package/assets/placeholders/album_art.png +0 -0
- package/assets/placeholders/avatar.png +0 -0
- package/assets/placeholders/badge.jpg +0 -0
- package/assets/placeholders/badge.png +0 -0
- package/assets/placeholders/badge_2.jpg +0 -0
- package/assets/placeholders/badge_3.jpg +0 -0
- package/assets/placeholders/badge_4.jpg +0 -0
- package/assets/placeholders/badge_5.jpg +0 -0
- package/assets/placeholders/banner.jpeg +0 -0
- package/assets/placeholders/images.jpeg +0 -0
- package/index.js +153 -0
- package/package.json +34 -0
- package/src/animation-effects.js +631 -0
- package/src/cache-manager.js +258 -0
- package/src/community-banner.js +1536 -0
- package/src/constants.js +208 -0
- package/src/discord-profile.js +584 -0
- package/src/e-commerce-banner.js +1214 -0
- package/src/effects.js +355 -0
- package/src/error-handler.js +305 -0
- package/src/event-banner.js +1319 -0
- package/src/facebook-post.js +679 -0
- package/src/gradient-welcome.js +430 -0
- package/src/image-filters.js +1034 -0
- package/src/image-processor.js +1014 -0
- package/src/instagram-post.js +504 -0
- package/src/interactive-elements.js +1208 -0
- package/src/linkedin-post.js +658 -0
- package/src/marketing-banner.js +1089 -0
- package/src/minimalist-banner.js +892 -0
- package/src/modern-profile.js +755 -0
- package/src/performance-optimizer.js +216 -0
- package/src/telegram-header.js +544 -0
- package/src/test-runner.js +645 -0
- package/src/tiktok-post.js +713 -0
- package/src/twitter-header.js +604 -0
- package/src/validator.js +442 -0
- package/src/welcome-leave.js +445 -0
- package/src/whatsapp-status.js +386 -0
- package/src/youtube-thumbnail.js +681 -0
- package/utils.js +710 -0
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Módulo de Banner de Perfil do Discord
|
|
5
|
+
*
|
|
6
|
+
* Este módulo gera banners de perfil no estilo do Discord com suporte a banner de fundo,
|
|
7
|
+
* avatar, status, distintivos e informações personalizadas.
|
|
8
|
+
*
|
|
9
|
+
* @author Cognima Team (melhorado)
|
|
10
|
+
* @version 2.0.0
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
|
|
15
|
+
const pureimage = require("pureimage");
|
|
16
|
+
const path = require("path");
|
|
17
|
+
const {
|
|
18
|
+
loadImageWithAxios,
|
|
19
|
+
encodeToBuffer,
|
|
20
|
+
roundRect,
|
|
21
|
+
wrapText,
|
|
22
|
+
registerFontIfNeeded,
|
|
23
|
+
isValidHexColor,
|
|
24
|
+
DEFAULT_FONT_FAMILY,
|
|
25
|
+
applyTextShadow,
|
|
26
|
+
clearShadow,
|
|
27
|
+
createLinearGradient,
|
|
28
|
+
hexToRgba
|
|
29
|
+
} = require("../utils");
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @class DiscordProfile
|
|
33
|
+
* @classdesc Gera um banner de perfil no estilo do Discord com suporte completo a banner de fundo.
|
|
34
|
+
* @example const profileCard = new DiscordProfile()
|
|
35
|
+
* .setUsername("UsuárioDiscord")
|
|
36
|
+
* .setDiscriminator("1234")
|
|
37
|
+
* .setAvatar("avatar.png")
|
|
38
|
+
* .setBanner("banner.png")
|
|
39
|
+
* .setAboutMe("Desenvolvedor | Gamer | Entusiasta de IA")
|
|
40
|
+
* .addBadge({ url: "badge1.png", description: "Nitro" })
|
|
41
|
+
* .setStatus("online")
|
|
42
|
+
* .build();
|
|
43
|
+
*/
|
|
44
|
+
module.exports = class DiscordProfile {
|
|
45
|
+
constructor(options) {
|
|
46
|
+
// Dados Principais
|
|
47
|
+
this.username = "Usuário";
|
|
48
|
+
this.discriminator = "0000";
|
|
49
|
+
this.avatar = null;
|
|
50
|
+
this.banner = null;
|
|
51
|
+
this.aboutMe = null;
|
|
52
|
+
this.badges = [];
|
|
53
|
+
this.customFields = {};
|
|
54
|
+
this.memberSince = null;
|
|
55
|
+
this.serverMemberSince = null;
|
|
56
|
+
|
|
57
|
+
// Personalização Visual
|
|
58
|
+
this.font = { name: options?.font?.name ?? DEFAULT_FONT_FAMILY, path: options?.font?.path };
|
|
59
|
+
this.backgroundColor = "#36393f";
|
|
60
|
+
this.secondaryColor = "#2f3136";
|
|
61
|
+
this.accentColor = "#5865f2";
|
|
62
|
+
this.textColor = "#ffffff";
|
|
63
|
+
this.secondaryTextColor = "#b9bbbe";
|
|
64
|
+
this.badgeBackgroundColor = "#2f3136";
|
|
65
|
+
this.avatarBorderColor = null;
|
|
66
|
+
this.overlayOpacity = 0.4;
|
|
67
|
+
this.status = { type: "offline", color: "#747F8D" };
|
|
68
|
+
|
|
69
|
+
// Configurações de Layout
|
|
70
|
+
this.cornerRadius = 0;
|
|
71
|
+
this.avatarSize = 128;
|
|
72
|
+
this.avatarBorderWidth = 5;
|
|
73
|
+
this.bannerHeight = 360;
|
|
74
|
+
this.cardWidth = 1200;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// --- Setters para Dados Principais ---
|
|
78
|
+
/**
|
|
79
|
+
* Define o nome de usuário
|
|
80
|
+
* @param {string} name - Nome do usuário
|
|
81
|
+
* @returns {DiscordProfile} - Instância atual para encadeamento
|
|
82
|
+
*/
|
|
83
|
+
setUsername(name) {
|
|
84
|
+
if (!name || typeof name !== "string") throw new Error("O nome de usuário deve ser uma string não vazia.");
|
|
85
|
+
this.username = name;
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Define o discriminador (tag) do usuário
|
|
91
|
+
* @param {string} discrim - Discriminador (ex: "1234")
|
|
92
|
+
* @returns {DiscordProfile} - Instância atual para encadeamento
|
|
93
|
+
*/
|
|
94
|
+
setDiscriminator(discrim) {
|
|
95
|
+
if (!discrim || typeof discrim !== "string") throw new Error("O discriminador deve ser uma string não vazia.");
|
|
96
|
+
this.discriminator = discrim;
|
|
97
|
+
return this;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Define a imagem do avatar
|
|
102
|
+
* @param {string|Buffer|Object} image - URL, Buffer ou caminho da imagem do avatar
|
|
103
|
+
* @returns {DiscordProfile} - Instância atual para encadeamento
|
|
104
|
+
*/
|
|
105
|
+
setAvatar(image) {
|
|
106
|
+
if (!image) throw new Error("A fonte da imagem do avatar não pode estar vazia.");
|
|
107
|
+
this.avatar = image;
|
|
108
|
+
return this;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Define a imagem do banner de fundo
|
|
113
|
+
* @param {string|Buffer|Object} image - URL, Buffer ou caminho da imagem do banner
|
|
114
|
+
* @returns {DiscordProfile} - Instância atual para encadeamento
|
|
115
|
+
*/
|
|
116
|
+
setBanner(image) {
|
|
117
|
+
if (!image) throw new Error("A fonte da imagem do banner não pode estar vazia.");
|
|
118
|
+
this.banner = image;
|
|
119
|
+
return this;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Define o texto "Sobre mim"
|
|
124
|
+
* @param {string} text - Texto de descrição
|
|
125
|
+
* @returns {DiscordProfile} - Instância atual para encadeamento
|
|
126
|
+
*/
|
|
127
|
+
setAboutMe(text) {
|
|
128
|
+
if (text && typeof text !== "string") throw new Error("O texto 'Sobre mim' deve ser uma string se fornecido.");
|
|
129
|
+
this.aboutMe = text;
|
|
130
|
+
return this;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Adiciona um distintivo ao perfil
|
|
135
|
+
* @param {Object} badge - Objeto do distintivo com url e descrição
|
|
136
|
+
* @returns {DiscordProfile} - Instância atual para encadeamento
|
|
137
|
+
*/
|
|
138
|
+
addBadge(badge) {
|
|
139
|
+
if (!badge || typeof badge !== "object" || !badge.url) throw new Error("O distintivo deve ser um objeto com pelo menos uma propriedade \"url\".");
|
|
140
|
+
this.badges.push({ url: badge.url, description: badge.description || "Distintivo" });
|
|
141
|
+
return this;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Define um campo personalizado
|
|
146
|
+
* @param {string} title - Título do campo
|
|
147
|
+
* @param {string} value - Valor do campo
|
|
148
|
+
* @returns {DiscordProfile} - Instância atual para encadeamento
|
|
149
|
+
*/
|
|
150
|
+
setCustomField(title, value) {
|
|
151
|
+
if (!title || typeof title !== "string") throw new Error("O título do campo personalizado deve ser uma string não vazia.");
|
|
152
|
+
if (!value || typeof value !== "string") throw new Error("O valor do campo personalizado deve ser uma string não vazia.");
|
|
153
|
+
this.customFields[title] = value;
|
|
154
|
+
return this;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Define a data de entrada no Discord
|
|
159
|
+
* @param {string} date - Data de entrada (ex: "25 Mai 2020")
|
|
160
|
+
* @returns {DiscordProfile} - Instância atual para encadeamento
|
|
161
|
+
*/
|
|
162
|
+
setMemberSince(date) {
|
|
163
|
+
if (!date || typeof date !== "string") throw new Error("A data de entrada deve ser uma string não vazia.");
|
|
164
|
+
this.memberSince = date;
|
|
165
|
+
return this;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Define a data de entrada no servidor
|
|
170
|
+
* @param {string} date - Data de entrada no servidor (ex: "10 Jun 2021")
|
|
171
|
+
* @returns {DiscordProfile} - Instância atual para encadeamento
|
|
172
|
+
*/
|
|
173
|
+
setServerMemberSince(date) {
|
|
174
|
+
if (!date || typeof date !== "string") throw new Error("A data de entrada no servidor deve ser uma string não vazia.");
|
|
175
|
+
this.serverMemberSince = date;
|
|
176
|
+
return this;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// --- Setters para Personalização Visual ---
|
|
180
|
+
/**
|
|
181
|
+
* Define a cor de fundo principal
|
|
182
|
+
* @param {string} color - Cor hexadecimal
|
|
183
|
+
* @returns {DiscordProfile} - Instância atual para encadeamento
|
|
184
|
+
*/
|
|
185
|
+
setBackgroundColor(color) {
|
|
186
|
+
if (!color || !isValidHexColor(color)) throw new Error("Cor de fundo inválida. Use o formato hexadecimal.");
|
|
187
|
+
this.backgroundColor = color;
|
|
188
|
+
return this;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Define a cor secundária
|
|
193
|
+
* @param {string} color - Cor hexadecimal
|
|
194
|
+
* @returns {DiscordProfile} - Instância atual para encadeamento
|
|
195
|
+
*/
|
|
196
|
+
setSecondaryColor(color) {
|
|
197
|
+
if (!color || !isValidHexColor(color)) throw new Error("Cor secundária inválida. Use o formato hexadecimal.");
|
|
198
|
+
this.secondaryColor = color;
|
|
199
|
+
return this;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Define a cor de destaque
|
|
204
|
+
* @param {string} color - Cor hexadecimal
|
|
205
|
+
* @returns {DiscordProfile} - Instância atual para encadeamento
|
|
206
|
+
*/
|
|
207
|
+
setAccentColor(color) {
|
|
208
|
+
if (!color || !isValidHexColor(color)) throw new Error("Cor de destaque inválida. Use o formato hexadecimal.");
|
|
209
|
+
this.accentColor = color;
|
|
210
|
+
return this;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Define a cor do texto principal
|
|
215
|
+
* @param {string} color - Cor hexadecimal
|
|
216
|
+
* @returns {DiscordProfile} - Instância atual para encadeamento
|
|
217
|
+
*/
|
|
218
|
+
setTextColor(color) {
|
|
219
|
+
if (!color || !isValidHexColor(color)) throw new Error("Cor de texto inválida. Use o formato hexadecimal.");
|
|
220
|
+
this.textColor = color;
|
|
221
|
+
return this;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Define a cor do texto secundário
|
|
226
|
+
* @param {string} color - Cor hexadecimal
|
|
227
|
+
* @returns {DiscordProfile} - Instância atual para encadeamento
|
|
228
|
+
*/
|
|
229
|
+
setSecondaryTextColor(color) {
|
|
230
|
+
if (!color || !isValidHexColor(color)) throw new Error("Cor de texto secundário inválida. Use o formato hexadecimal.");
|
|
231
|
+
this.secondaryTextColor = color;
|
|
232
|
+
return this;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Define a cor da borda do avatar
|
|
237
|
+
* @param {string} color - Cor hexadecimal
|
|
238
|
+
* @returns {DiscordProfile} - Instância atual para encadeamento
|
|
239
|
+
*/
|
|
240
|
+
setAvatarBorderColor(color) {
|
|
241
|
+
if (color && !isValidHexColor(color)) throw new Error("Cor de borda do avatar inválida. Use o formato hexadecimal.");
|
|
242
|
+
this.avatarBorderColor = color;
|
|
243
|
+
return this;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Define a opacidade da sobreposição
|
|
248
|
+
* @param {number} opacity - Valor de opacidade (0-1)
|
|
249
|
+
* @returns {DiscordProfile} - Instância atual para encadeamento
|
|
250
|
+
*/
|
|
251
|
+
setOverlayOpacity(opacity) {
|
|
252
|
+
if (typeof opacity !== "number" || opacity < 0 || opacity > 1) throw new Error("A opacidade da sobreposição deve estar entre 0 e 1.");
|
|
253
|
+
this.overlayOpacity = opacity;
|
|
254
|
+
return this;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Define o status do usuário
|
|
259
|
+
* @param {string} type - Tipo de status ('online', 'idle', 'dnd', 'streaming', 'offline')
|
|
260
|
+
* @returns {DiscordProfile} - Instância atual para encadeamento
|
|
261
|
+
*/
|
|
262
|
+
setStatus(type) {
|
|
263
|
+
const validTypes = { online: "#43B581", idle: "#FAA61A", dnd: "#F04747", streaming: "#593695", offline: "#747F8D" };
|
|
264
|
+
if (!type || !validTypes[type.toLowerCase()]) throw new Error(`Tipo de status inválido. Use um dos seguintes: ${Object.keys(validTypes).join(", ")}`);
|
|
265
|
+
this.status = { type: type.toLowerCase(), color: validTypes[type.toLowerCase()] };
|
|
266
|
+
return this;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Define o raio dos cantos arredondados
|
|
271
|
+
* @param {number} radius - Raio dos cantos em pixels
|
|
272
|
+
* @returns {DiscordProfile} - Instância atual para encadeamento
|
|
273
|
+
*/
|
|
274
|
+
setCornerRadius(radius) {
|
|
275
|
+
if (typeof radius !== "number" || radius < 0) throw new Error("O raio dos cantos deve ser um número não negativo.");
|
|
276
|
+
this.cornerRadius = radius;
|
|
277
|
+
return this;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Define o tamanho do avatar
|
|
282
|
+
* @param {number} size - Tamanho do avatar em pixels
|
|
283
|
+
* @returns {DiscordProfile} - Instância atual para encadeamento
|
|
284
|
+
*/
|
|
285
|
+
setAvatarSize(size) {
|
|
286
|
+
if (typeof size !== "number" || size < 64 || size > 256) throw new Error("O tamanho do avatar deve estar entre 64 e 256 pixels.");
|
|
287
|
+
this.avatarSize = size;
|
|
288
|
+
return this;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Define a largura da borda do avatar
|
|
293
|
+
* @param {number} width - Largura da borda em pixels
|
|
294
|
+
* @returns {DiscordProfile} - Instância atual para encadeamento
|
|
295
|
+
*/
|
|
296
|
+
setAvatarBorderWidth(width) {
|
|
297
|
+
if (typeof width !== "number" || width < 0) throw new Error("A largura da borda do avatar deve ser um número não negativo.");
|
|
298
|
+
this.avatarBorderWidth = width;
|
|
299
|
+
return this;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Define a altura do banner
|
|
304
|
+
* @param {number} height - Altura do banner em pixels
|
|
305
|
+
* @returns {DiscordProfile} - Instância atual para encadeamento
|
|
306
|
+
*/
|
|
307
|
+
setBannerHeight(height) {
|
|
308
|
+
if (typeof height !== "number" || height < 100 || height > 300) throw new Error("A altura do banner deve estar entre 100 e 300 pixels.");
|
|
309
|
+
this.bannerHeight = height;
|
|
310
|
+
return this;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Define a largura do card
|
|
315
|
+
* @param {number} width - Largura do card em pixels
|
|
316
|
+
* @returns {DiscordProfile} - Instância atual para encadeamento
|
|
317
|
+
*/
|
|
318
|
+
setCardWidth(width) {
|
|
319
|
+
if (typeof width !== "number" || width < 400 || width > 1000) throw new Error("A largura do card deve estar entre 400 e 1000 pixels.");
|
|
320
|
+
this.cardWidth = width;
|
|
321
|
+
return this;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// --- Método de Construção ---
|
|
325
|
+
/**
|
|
326
|
+
* Constrói o banner de perfil e retorna um buffer de imagem
|
|
327
|
+
* @returns {Promise<Buffer>} - Buffer contendo a imagem do banner
|
|
328
|
+
*/
|
|
329
|
+
async build() {
|
|
330
|
+
if (!this.avatar) throw new Error("A imagem do avatar deve ser definida usando setAvatar().");
|
|
331
|
+
|
|
332
|
+
// --- Registro de Fonte ---
|
|
333
|
+
const registeredFontName = await registerFontIfNeeded(this.font);
|
|
334
|
+
|
|
335
|
+
// --- Configuração do Canvas ---
|
|
336
|
+
const cardWidth = this.cardWidth;
|
|
337
|
+
const headerHeight = this.bannerHeight;
|
|
338
|
+
const avatarSize = this.avatarSize;
|
|
339
|
+
const avatarOverlap = 40;
|
|
340
|
+
const bodyPadding = 25;
|
|
341
|
+
const contentStartY = headerHeight - avatarOverlap + avatarSize / 2 + bodyPadding;
|
|
342
|
+
const badgeAreaHeight = this.badges.length > 0 ? 80 : 0;
|
|
343
|
+
|
|
344
|
+
// Estima a altura necessária para o conteúdo
|
|
345
|
+
let estimatedContentHeight = 0;
|
|
346
|
+
if (this.aboutMe) estimatedContentHeight += 80;
|
|
347
|
+
estimatedContentHeight += Object.keys(this.customFields).length * 45;
|
|
348
|
+
if (this.memberSince) estimatedContentHeight += 30;
|
|
349
|
+
if (this.serverMemberSince) estimatedContentHeight += 30;
|
|
350
|
+
|
|
351
|
+
let cardHeight = contentStartY + Math.max(100, estimatedContentHeight) + badgeAreaHeight + bodyPadding;
|
|
352
|
+
|
|
353
|
+
const borderRadius = this.cornerRadius;
|
|
354
|
+
const statusIndicatorSize = 32;
|
|
355
|
+
|
|
356
|
+
const canvas = pureimage.make(cardWidth, cardHeight);
|
|
357
|
+
const ctx = canvas.getContext("2d");
|
|
358
|
+
|
|
359
|
+
// --- Desenha Plano de Fundo Principal ---
|
|
360
|
+
ctx.fillStyle = this.backgroundColor;
|
|
361
|
+
roundRect(ctx, 0, 0, cardWidth, cardHeight, borderRadius, true, false);
|
|
362
|
+
|
|
363
|
+
// --- Desenha Banner de Fundo ---
|
|
364
|
+
ctx.save();
|
|
365
|
+
// Define o caminho de recorte para o cabeçalho (apenas cantos superiores arredondados)
|
|
366
|
+
ctx.beginPath();
|
|
367
|
+
ctx.moveTo(0, headerHeight); // Inicia no canto inferior esquerdo
|
|
368
|
+
ctx.lineTo(0, borderRadius); // Borda esquerda até o raio
|
|
369
|
+
ctx.quadraticCurveTo(0, 0, borderRadius, 0); // Canto superior esquerdo
|
|
370
|
+
ctx.lineTo(cardWidth - borderRadius, 0); // Borda superior
|
|
371
|
+
ctx.quadraticCurveTo(cardWidth, 0, cardWidth, borderRadius); // Canto superior direito
|
|
372
|
+
ctx.lineTo(cardWidth, headerHeight); // Borda direita para baixo
|
|
373
|
+
ctx.closePath(); // Fecha o caminho de volta para o canto inferior direito (implicitamente)
|
|
374
|
+
ctx.clip();
|
|
375
|
+
|
|
376
|
+
ctx.globalAlpha = 1;
|
|
377
|
+
|
|
378
|
+
if (this.banner) {
|
|
379
|
+
try {
|
|
380
|
+
const img = await loadImageWithAxios(this.banner);
|
|
381
|
+
const aspect = img.width / img.height;
|
|
382
|
+
let drawWidth = cardWidth;
|
|
383
|
+
let drawHeight = cardWidth / aspect;
|
|
384
|
+
|
|
385
|
+
if (drawHeight < headerHeight) {
|
|
386
|
+
drawHeight = headerHeight;
|
|
387
|
+
drawWidth = headerHeight * aspect;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const offsetX = (cardWidth - drawWidth) / 2;
|
|
391
|
+
const offsetY = 0;
|
|
392
|
+
|
|
393
|
+
ctx.drawImage(img, offsetX, offsetY, drawWidth, drawHeight);
|
|
394
|
+
|
|
395
|
+
} catch (e) {
|
|
396
|
+
console.error("Falha ao desenhar imagem de banner:", e.message);
|
|
397
|
+
ctx.fillStyle = this.accentColor;
|
|
398
|
+
ctx.fillRect(0, 0, cardWidth, headerHeight);
|
|
399
|
+
}
|
|
400
|
+
} else {
|
|
401
|
+
// Banner de cor sólida se nenhuma imagem for fornecida
|
|
402
|
+
ctx.fillStyle = this.accentColor;
|
|
403
|
+
ctx.fillRect(0, 0, cardWidth, headerHeight);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
ctx.restore();
|
|
407
|
+
ctx.globalAlpha = 1;
|
|
408
|
+
|
|
409
|
+
// --- Desenha Avatar ---
|
|
410
|
+
const avatarX = bodyPadding;
|
|
411
|
+
const avatarY = headerHeight - avatarOverlap - avatarSize / 2;
|
|
412
|
+
|
|
413
|
+
ctx.save();
|
|
414
|
+
ctx.beginPath();
|
|
415
|
+
ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2, 0, Math.PI * 2);
|
|
416
|
+
ctx.closePath();
|
|
417
|
+
ctx.clip();
|
|
418
|
+
|
|
419
|
+
try {
|
|
420
|
+
const avatarImg = await loadImageWithAxios(this.avatar);
|
|
421
|
+
ctx.drawImage(avatarImg, avatarX, avatarY, avatarSize, avatarSize);
|
|
422
|
+
} catch (e) {
|
|
423
|
+
console.error("Falha ao desenhar imagem do avatar:", e.message);
|
|
424
|
+
ctx.fillStyle = "#555";
|
|
425
|
+
ctx.fillRect(avatarX, avatarY, avatarSize, avatarSize);
|
|
426
|
+
ctx.fillStyle = "#FFF";
|
|
427
|
+
ctx.font = `bold 30px ${registeredFontName}-Bold`;
|
|
428
|
+
ctx.textAlign = "center";
|
|
429
|
+
ctx.textBaseline = "middle";
|
|
430
|
+
ctx.fillText("?", avatarX + avatarSize / 2, avatarY + avatarSize / 2);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
ctx.restore();
|
|
434
|
+
|
|
435
|
+
// --- Desenha Borda do Avatar ---
|
|
436
|
+
if (this.avatarBorderColor) {
|
|
437
|
+
ctx.strokeStyle = this.avatarBorderColor;
|
|
438
|
+
} else {
|
|
439
|
+
ctx.strokeStyle = this.backgroundColor; // Usa a cor de fundo como padrão
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
ctx.lineWidth = this.avatarBorderWidth;
|
|
443
|
+
ctx.beginPath();
|
|
444
|
+
ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2 + ctx.lineWidth / 2, 0, Math.PI * 2);
|
|
445
|
+
ctx.stroke();
|
|
446
|
+
ctx.closePath();
|
|
447
|
+
|
|
448
|
+
// --- Desenha Indicador de Status ---
|
|
449
|
+
ctx.fillStyle = this.status.color;
|
|
450
|
+
ctx.beginPath();
|
|
451
|
+
const statusX = avatarX + avatarSize - statusIndicatorSize * 0.7;
|
|
452
|
+
const statusY = avatarY + avatarSize - statusIndicatorSize * 0.7;
|
|
453
|
+
ctx.arc(statusX, statusY, statusIndicatorSize / 2, 0, Math.PI * 2);
|
|
454
|
+
ctx.fill();
|
|
455
|
+
ctx.closePath();
|
|
456
|
+
ctx.strokeStyle = this.backgroundColor;
|
|
457
|
+
ctx.lineWidth = 3;
|
|
458
|
+
ctx.stroke();
|
|
459
|
+
|
|
460
|
+
// --- Desenha Nome de Usuário e Discriminador ---
|
|
461
|
+
ctx.fillStyle = this.textColor;
|
|
462
|
+
const usernameFont = `28px ${registeredFontName}-Bold`;
|
|
463
|
+
ctx.font = usernameFont;
|
|
464
|
+
ctx.textAlign = "start";
|
|
465
|
+
ctx.textBaseline = "top";
|
|
466
|
+
const usernameX = avatarX + avatarSize + bodyPadding;
|
|
467
|
+
const usernameY = headerHeight + 5;
|
|
468
|
+
|
|
469
|
+
// Aplica sombra de texto para melhor legibilidade
|
|
470
|
+
applyTextShadow(ctx);
|
|
471
|
+
|
|
472
|
+
const usernameText = this.username.length > 20 ? this.username.slice(0, 17) + "..." : this.username;
|
|
473
|
+
ctx.fillText(usernameText, usernameX, usernameY);
|
|
474
|
+
|
|
475
|
+
// Desenha o discriminador
|
|
476
|
+
ctx.fillStyle = this.secondaryTextColor;
|
|
477
|
+
const discrimX = usernameX + ctx.measureText(usernameText).width + 5;
|
|
478
|
+
ctx.font = `20px ${registeredFontName}-Regular`;
|
|
479
|
+
ctx.fillText(`#${this.discriminator}`, discrimX, usernameY + 5);
|
|
480
|
+
|
|
481
|
+
clearShadow(ctx);
|
|
482
|
+
|
|
483
|
+
// --- Desenha Conteúdo Abaixo do Avatar ---
|
|
484
|
+
let currentY = contentStartY;
|
|
485
|
+
const contentX = bodyPadding;
|
|
486
|
+
const contentWidth = cardWidth - 2 * bodyPadding;
|
|
487
|
+
|
|
488
|
+
// Seção "Sobre mim"
|
|
489
|
+
if (this.aboutMe) {
|
|
490
|
+
// Título da seção
|
|
491
|
+
ctx.fillStyle = this.textColor;
|
|
492
|
+
ctx.font = `18px ${registeredFontName}-Bold`;
|
|
493
|
+
ctx.textAlign = "start";
|
|
494
|
+
ctx.textBaseline = "top";
|
|
495
|
+
ctx.fillText("SOBRE MIM", contentX, currentY);
|
|
496
|
+
currentY += 25;
|
|
497
|
+
|
|
498
|
+
// Conteúdo da seção
|
|
499
|
+
ctx.fillStyle = this.secondaryTextColor;
|
|
500
|
+
ctx.font = `16px ${registeredFontName}-Regular`;
|
|
501
|
+
currentY = wrapText(ctx, this.aboutMe, contentX, currentY, contentWidth, 22, registeredFontName) + 15;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Campos Personalizados
|
|
505
|
+
if (Object.keys(this.customFields).length > 0) {
|
|
506
|
+
currentY += 10;
|
|
507
|
+
ctx.textBaseline = "top";
|
|
508
|
+
|
|
509
|
+
for (const title in this.customFields) {
|
|
510
|
+
// Título do campo
|
|
511
|
+
ctx.fillStyle = this.textColor;
|
|
512
|
+
ctx.font = `16px ${registeredFontName}-Bold`;
|
|
513
|
+
ctx.fillText(title.toUpperCase(), contentX, currentY);
|
|
514
|
+
currentY += 22;
|
|
515
|
+
|
|
516
|
+
// Valor do campo
|
|
517
|
+
ctx.fillStyle = this.secondaryTextColor;
|
|
518
|
+
ctx.font = `16px ${registeredFontName}-Regular`;
|
|
519
|
+
const valueText = this.customFields[title];
|
|
520
|
+
currentY = wrapText(ctx, valueText, contentX, currentY, contentWidth, 20, registeredFontName) + 15;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Informações de Membro
|
|
525
|
+
if (this.memberSince || this.serverMemberSince) {
|
|
526
|
+
currentY += 10;
|
|
527
|
+
ctx.fillStyle = this.textColor;
|
|
528
|
+
ctx.font = `16px ${registeredFontName}-Bold`;
|
|
529
|
+
ctx.fillText("MEMBRO DESDE", contentX, currentY);
|
|
530
|
+
currentY += 22;
|
|
531
|
+
|
|
532
|
+
if (this.memberSince) {
|
|
533
|
+
ctx.fillStyle = this.secondaryTextColor;
|
|
534
|
+
ctx.font = `16px ${registeredFontName}-Regular`;
|
|
535
|
+
ctx.fillText(`Discord: ${this.memberSince}`, contentX, currentY);
|
|
536
|
+
currentY += 22;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (this.serverMemberSince) {
|
|
540
|
+
ctx.fillStyle = this.secondaryTextColor;
|
|
541
|
+
ctx.font = `16px ${registeredFontName}-Regular`;
|
|
542
|
+
ctx.fillText(`Servidor: ${this.serverMemberSince}`, contentX, currentY);
|
|
543
|
+
currentY += 22;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// --- Desenha Área de Distintivos ---
|
|
548
|
+
if (this.badges.length > 0) {
|
|
549
|
+
const badgeAreaY = cardHeight - badgeAreaHeight;
|
|
550
|
+
ctx.fillStyle = this.secondaryColor;
|
|
551
|
+
roundRect(ctx, 0, badgeAreaY, cardWidth, badgeAreaHeight,
|
|
552
|
+
{ tl: 0, tr: 0, br: borderRadius, bl: borderRadius }, true, false);
|
|
553
|
+
|
|
554
|
+
const badgeSize = 40;
|
|
555
|
+
const badgePadding = 15;
|
|
556
|
+
let currentBadgeX = bodyPadding;
|
|
557
|
+
const badgeY = badgeAreaY + (badgeAreaHeight - badgeSize) / 2;
|
|
558
|
+
|
|
559
|
+
// Desenha os distintivos
|
|
560
|
+
currentBadgeX = bodyPadding;
|
|
561
|
+
for (const badge of this.badges.slice(0, 10)) {
|
|
562
|
+
try {
|
|
563
|
+
const badgeImg = await loadImageWithAxios(badge.url);
|
|
564
|
+
ctx.drawImage(badgeImg, currentBadgeX, badgeY, badgeSize, badgeSize);
|
|
565
|
+
currentBadgeX += badgeSize + badgePadding;
|
|
566
|
+
} catch (e) {
|
|
567
|
+
console.warn(`Falha ao carregar imagem do distintivo: ${badge.url}`, e.message);
|
|
568
|
+
ctx.fillStyle = "#555";
|
|
569
|
+
ctx.fillRect(currentBadgeX, badgeY, badgeSize, badgeSize);
|
|
570
|
+
currentBadgeX += badgeSize + badgePadding;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// --- Codifica e Retorna Buffer ---
|
|
576
|
+
try {
|
|
577
|
+
return await encodeToBuffer(canvas);
|
|
578
|
+
} catch (err) {
|
|
579
|
+
console.error("Falha ao codificar o card de Perfil do Discord:", err);
|
|
580
|
+
throw new Error("Não foi possível gerar o buffer de imagem do card de Perfil do Discord.");
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
|