@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,544 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Módulo de Banner de Cabeçalho do Telegram
|
|
5
|
+
*
|
|
6
|
+
* Este módulo gera banners no estilo de cabeçalho de canal do Telegram com
|
|
7
|
+
* imagem de capa, avatar, título, descrição e contadores.
|
|
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
|
+
const {
|
|
32
|
+
DEFAULT_COLORS,
|
|
33
|
+
LAYOUT,
|
|
34
|
+
DEFAULT_DIMENSIONS
|
|
35
|
+
} = require("./constants");
|
|
36
|
+
|
|
37
|
+
const {
|
|
38
|
+
applyGlassmorphism
|
|
39
|
+
} = require("./effects");
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @class TelegramHeader
|
|
43
|
+
* @classdesc Gera um banner no estilo de cabeçalho de canal do Telegram.
|
|
44
|
+
* @example const header = new TelegramHeader()
|
|
45
|
+
* .setTitle("Canal de Notícias")
|
|
46
|
+
* .setDescription("Atualizações diárias sobre tecnologia e ciência")
|
|
47
|
+
* .setAvatar("avatar.png")
|
|
48
|
+
* .setCoverImage("cover.jpg")
|
|
49
|
+
* .setSubscribers(15000)
|
|
50
|
+
* .build();
|
|
51
|
+
*/
|
|
52
|
+
module.exports = class TelegramHeader {
|
|
53
|
+
constructor(options) {
|
|
54
|
+
// Dados Principais
|
|
55
|
+
this.title = "Nome do Canal";
|
|
56
|
+
this.description = null;
|
|
57
|
+
this.avatar = null;
|
|
58
|
+
this.coverImage = null;
|
|
59
|
+
this.subscribers = 0;
|
|
60
|
+
this.posts = 0;
|
|
61
|
+
this.isVerified = false;
|
|
62
|
+
this.isPublic = true;
|
|
63
|
+
this.link = null;
|
|
64
|
+
|
|
65
|
+
// Personalização Visual
|
|
66
|
+
this.font = { name: options?.font?.name ?? DEFAULT_FONT_FAMILY, path: options?.font?.path };
|
|
67
|
+
this.primaryColor = DEFAULT_COLORS.telegram.primary;
|
|
68
|
+
this.backgroundColor = DEFAULT_COLORS.telegram.light;
|
|
69
|
+
this.textColor = DEFAULT_COLORS.text.dark;
|
|
70
|
+
this.secondaryTextColor = DEFAULT_COLORS.text.muted;
|
|
71
|
+
this.useGlassmorphism = false;
|
|
72
|
+
this.useTextShadow = false;
|
|
73
|
+
|
|
74
|
+
// Configurações de Layout
|
|
75
|
+
this.cardWidth = DEFAULT_DIMENSIONS.banner.width;
|
|
76
|
+
this.cardHeight = 300;
|
|
77
|
+
this.cornerRadius = LAYOUT.cornerRadius.medium;
|
|
78
|
+
this.avatarSize = 100;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// --- Setters para Dados Principais ---
|
|
82
|
+
/**
|
|
83
|
+
* Define o título do canal
|
|
84
|
+
* @param {string} text - Título do canal
|
|
85
|
+
* @returns {TelegramHeader} - Instância atual para encadeamento
|
|
86
|
+
*/
|
|
87
|
+
setTitle(text) {
|
|
88
|
+
if (!text || typeof text !== "string") throw new Error("O título do canal deve ser uma string não vazia.");
|
|
89
|
+
this.title = text;
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Define a descrição do canal
|
|
95
|
+
* @param {string} text - Descrição do canal
|
|
96
|
+
* @returns {TelegramHeader} - Instância atual para encadeamento
|
|
97
|
+
*/
|
|
98
|
+
setDescription(text) {
|
|
99
|
+
if (!text || typeof text !== "string") throw new Error("A descrição do canal deve ser uma string não vazia.");
|
|
100
|
+
this.description = text;
|
|
101
|
+
return this;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Define o avatar do canal
|
|
106
|
+
* @param {string|Buffer|Object} image - URL, Buffer ou caminho da imagem do avatar
|
|
107
|
+
* @returns {TelegramHeader} - Instância atual para encadeamento
|
|
108
|
+
*/
|
|
109
|
+
setAvatar(image) {
|
|
110
|
+
if (!image) throw new Error("A fonte da imagem do avatar não pode estar vazia.");
|
|
111
|
+
this.avatar = image;
|
|
112
|
+
return this;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Define a imagem de capa do canal
|
|
117
|
+
* @param {string|Buffer|Object} image - URL, Buffer ou caminho da imagem de capa
|
|
118
|
+
* @returns {TelegramHeader} - Instância atual para encadeamento
|
|
119
|
+
*/
|
|
120
|
+
setCoverImage(image) {
|
|
121
|
+
if (!image) throw new Error("A fonte da imagem de capa não pode estar vazia.");
|
|
122
|
+
this.coverImage = image;
|
|
123
|
+
return this;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Define o número de inscritos
|
|
128
|
+
* @param {number} count - Número de inscritos
|
|
129
|
+
* @returns {TelegramHeader} - Instância atual para encadeamento
|
|
130
|
+
*/
|
|
131
|
+
setSubscribers(count) {
|
|
132
|
+
if (typeof count !== "number" || count < 0) throw new Error("O número de inscritos deve ser um número não negativo.");
|
|
133
|
+
this.subscribers = count;
|
|
134
|
+
return this;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Define o número de posts
|
|
139
|
+
* @param {number} count - Número de posts
|
|
140
|
+
* @returns {TelegramHeader} - Instância atual para encadeamento
|
|
141
|
+
*/
|
|
142
|
+
setPosts(count) {
|
|
143
|
+
if (typeof count !== "number" || count < 0) throw new Error("O número de posts deve ser um número não negativo.");
|
|
144
|
+
this.posts = count;
|
|
145
|
+
return this;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Define se o canal é verificado
|
|
150
|
+
* @param {boolean} isVerified - Se o canal é verificado
|
|
151
|
+
* @returns {TelegramHeader} - Instância atual para encadeamento
|
|
152
|
+
*/
|
|
153
|
+
setVerified(isVerified = true) {
|
|
154
|
+
this.isVerified = !!isVerified;
|
|
155
|
+
return this;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Define se o canal é público
|
|
160
|
+
* @param {boolean} isPublic - Se o canal é público
|
|
161
|
+
* @returns {TelegramHeader} - Instância atual para encadeamento
|
|
162
|
+
*/
|
|
163
|
+
setPublic(isPublic = true) {
|
|
164
|
+
this.isPublic = !!isPublic;
|
|
165
|
+
return this;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Define o link do canal
|
|
170
|
+
* @param {string} link - Link do canal (ex: "t.me/canalexemplo")
|
|
171
|
+
* @returns {TelegramHeader} - Instância atual para encadeamento
|
|
172
|
+
*/
|
|
173
|
+
setLink(link) {
|
|
174
|
+
if (!link || typeof link !== "string") throw new Error("O link do canal deve ser uma string não vazia.");
|
|
175
|
+
this.link = link;
|
|
176
|
+
return this;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// --- Setters para Personalização Visual ---
|
|
180
|
+
/**
|
|
181
|
+
* Define a cor primária
|
|
182
|
+
* @param {string} color - Cor hexadecimal
|
|
183
|
+
* @returns {TelegramHeader} - Instância atual para encadeamento
|
|
184
|
+
*/
|
|
185
|
+
setPrimaryColor(color) {
|
|
186
|
+
if (!color || !isValidHexColor(color)) throw new Error("Cor primária inválida. Use o formato hexadecimal.");
|
|
187
|
+
this.primaryColor = color;
|
|
188
|
+
return this;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Define a cor de fundo
|
|
193
|
+
* @param {string} color - Cor hexadecimal
|
|
194
|
+
* @returns {TelegramHeader} - Instância atual para encadeamento
|
|
195
|
+
*/
|
|
196
|
+
setBackgroundColor(color) {
|
|
197
|
+
if (!color || !isValidHexColor(color)) throw new Error("Cor de fundo inválida. Use o formato hexadecimal.");
|
|
198
|
+
this.backgroundColor = color;
|
|
199
|
+
return this;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Define a cor do texto principal
|
|
204
|
+
* @param {string} color - Cor hexadecimal
|
|
205
|
+
* @returns {TelegramHeader} - Instância atual para encadeamento
|
|
206
|
+
*/
|
|
207
|
+
setTextColor(color) {
|
|
208
|
+
if (!color || !isValidHexColor(color)) throw new Error("Cor de texto inválida. Use o formato hexadecimal.");
|
|
209
|
+
this.textColor = color;
|
|
210
|
+
return this;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Define a cor do texto secundário
|
|
215
|
+
* @param {string} color - Cor hexadecimal
|
|
216
|
+
* @returns {TelegramHeader} - Instância atual para encadeamento
|
|
217
|
+
*/
|
|
218
|
+
setSecondaryTextColor(color) {
|
|
219
|
+
if (!color || !isValidHexColor(color)) throw new Error("Cor de texto secundário inválida. Use o formato hexadecimal.");
|
|
220
|
+
this.secondaryTextColor = color;
|
|
221
|
+
return this;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Ativa ou desativa o efeito de glassmorphism
|
|
226
|
+
* @param {boolean} enabled - Se o efeito deve ser ativado
|
|
227
|
+
* @returns {TelegramHeader} - Instância atual para encadeamento
|
|
228
|
+
*/
|
|
229
|
+
enableGlassmorphism(enabled = true) {
|
|
230
|
+
this.useGlassmorphism = enabled;
|
|
231
|
+
return this;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Ativa ou desativa a sombra de texto
|
|
236
|
+
* @param {boolean} enabled - Se a sombra de texto deve ser ativada
|
|
237
|
+
* @returns {TelegramHeader} - Instância atual para encadeamento
|
|
238
|
+
*/
|
|
239
|
+
enableTextShadow(enabled = true) {
|
|
240
|
+
this.useTextShadow = enabled;
|
|
241
|
+
return this;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Define as dimensões do card
|
|
246
|
+
* @param {number} width - Largura do card em pixels
|
|
247
|
+
* @param {number} height - Altura do card em pixels
|
|
248
|
+
* @returns {TelegramHeader} - Instância atual para encadeamento
|
|
249
|
+
*/
|
|
250
|
+
setCardDimensions(width, height) {
|
|
251
|
+
if (typeof width !== "number" || width < 600 || width > 1920) {
|
|
252
|
+
throw new Error("A largura do card deve estar entre 600 e 1920 pixels.");
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (typeof height !== "number" || height < 200 || height > 600) {
|
|
256
|
+
throw new Error("A altura do card deve estar entre 200 e 600 pixels.");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
this.cardWidth = width;
|
|
260
|
+
this.cardHeight = height;
|
|
261
|
+
|
|
262
|
+
return this;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Define o raio dos cantos arredondados
|
|
267
|
+
* @param {number} radius - Raio dos cantos em pixels
|
|
268
|
+
* @returns {TelegramHeader} - Instância atual para encadeamento
|
|
269
|
+
*/
|
|
270
|
+
setCornerRadius(radius) {
|
|
271
|
+
if (typeof radius !== "number" || radius < 0) throw new Error("O raio dos cantos deve ser um número não negativo.");
|
|
272
|
+
this.cornerRadius = radius;
|
|
273
|
+
return this;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Define o tamanho do avatar
|
|
278
|
+
* @param {number} size - Tamanho do avatar em pixels
|
|
279
|
+
* @returns {TelegramHeader} - Instância atual para encadeamento
|
|
280
|
+
*/
|
|
281
|
+
setAvatarSize(size) {
|
|
282
|
+
if (typeof size !== "number" || size < 60 || size > 200) {
|
|
283
|
+
throw new Error("O tamanho do avatar deve estar entre 60 e 200 pixels.");
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
this.avatarSize = size;
|
|
287
|
+
return this;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// --- Método de Construção ---
|
|
291
|
+
/**
|
|
292
|
+
* Constrói o banner e retorna um buffer de imagem
|
|
293
|
+
* @returns {Promise<Buffer>} - Buffer contendo a imagem do banner
|
|
294
|
+
*/
|
|
295
|
+
async build() {
|
|
296
|
+
// --- Registro de Fonte ---
|
|
297
|
+
const registeredFontName = await registerFontIfNeeded(this.font);
|
|
298
|
+
|
|
299
|
+
// --- Configuração do Canvas ---
|
|
300
|
+
const cardWidth = this.cardWidth;
|
|
301
|
+
const cardHeight = this.cardHeight;
|
|
302
|
+
const coverHeight = cardHeight * 0.6;
|
|
303
|
+
const avatarSize = this.avatarSize;
|
|
304
|
+
const padding = 20;
|
|
305
|
+
const cornerRadius = this.cornerRadius;
|
|
306
|
+
|
|
307
|
+
const canvas = pureimage.make(cardWidth, cardHeight);
|
|
308
|
+
const ctx = canvas.getContext("2d");
|
|
309
|
+
|
|
310
|
+
// --- Desenha Plano de Fundo ---
|
|
311
|
+
ctx.fillStyle = this.backgroundColor;
|
|
312
|
+
ctx.fillRect(0, 0, cardWidth, cardHeight);
|
|
313
|
+
|
|
314
|
+
// --- Desenha Imagem de Capa ---
|
|
315
|
+
if (this.coverImage) {
|
|
316
|
+
try {
|
|
317
|
+
ctx.save();
|
|
318
|
+
|
|
319
|
+
// Define o caminho de recorte para a capa (apenas cantos superiores arredondados)
|
|
320
|
+
ctx.beginPath();
|
|
321
|
+
ctx.moveTo(0, coverHeight); // Inicia no canto inferior esquerdo
|
|
322
|
+
ctx.lineTo(0, cornerRadius); // Borda esquerda até o raio
|
|
323
|
+
ctx.quadraticCurveTo(0, 0, cornerRadius, 0); // Canto superior esquerdo
|
|
324
|
+
ctx.lineTo(cardWidth - cornerRadius, 0); // Borda superior
|
|
325
|
+
ctx.quadraticCurveTo(cardWidth, 0, cardWidth, cornerRadius); // Canto superior direito
|
|
326
|
+
ctx.lineTo(cardWidth, coverHeight); // Borda direita para baixo
|
|
327
|
+
ctx.closePath(); // Fecha o caminho de volta para o canto inferior direito (implicitamente)
|
|
328
|
+
ctx.clip();
|
|
329
|
+
|
|
330
|
+
const img = await loadImageWithAxios(this.coverImage);
|
|
331
|
+
const aspect = img.width / img.height;
|
|
332
|
+
let drawWidth = cardWidth;
|
|
333
|
+
let drawHeight = cardWidth / aspect;
|
|
334
|
+
|
|
335
|
+
// Ajusta as dimensões para cobrir toda a área da capa
|
|
336
|
+
if (drawHeight < coverHeight) {
|
|
337
|
+
drawHeight = coverHeight;
|
|
338
|
+
drawWidth = coverHeight * aspect;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const offsetX = (cardWidth - drawWidth) / 2;
|
|
342
|
+
const offsetY = 0;
|
|
343
|
+
|
|
344
|
+
ctx.drawImage(img, offsetX, offsetY, drawWidth, drawHeight);
|
|
345
|
+
|
|
346
|
+
// Aplica sobreposição para melhorar legibilidade
|
|
347
|
+
ctx.fillStyle = hexToRgba("#000000", 0.3);
|
|
348
|
+
ctx.fillRect(0, 0, cardWidth, coverHeight);
|
|
349
|
+
|
|
350
|
+
ctx.restore();
|
|
351
|
+
} catch (e) {
|
|
352
|
+
console.error("Falha ao desenhar imagem de capa:", e.message);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// --- Desenha Avatar ---
|
|
357
|
+
const avatarX = padding;
|
|
358
|
+
const avatarY = coverHeight - avatarSize / 2;
|
|
359
|
+
|
|
360
|
+
ctx.save();
|
|
361
|
+
ctx.beginPath();
|
|
362
|
+
ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2, 0, Math.PI * 2);
|
|
363
|
+
ctx.closePath();
|
|
364
|
+
ctx.clip();
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
const avatarImg = await loadImageWithAxios(this.avatar || path.join(__dirname, "../assets/placeholders/avatar.png"));
|
|
368
|
+
ctx.drawImage(avatarImg, avatarX, avatarY, avatarSize, avatarSize);
|
|
369
|
+
} catch (e) {
|
|
370
|
+
console.error("Falha ao desenhar avatar:", e.message);
|
|
371
|
+
|
|
372
|
+
// Avatar de fallback
|
|
373
|
+
ctx.fillStyle = this.primaryColor;
|
|
374
|
+
ctx.fillRect(avatarX, avatarY, avatarSize, avatarSize);
|
|
375
|
+
|
|
376
|
+
ctx.fillStyle = "#FFFFFF";
|
|
377
|
+
ctx.font = `bold ${avatarSize / 3}px ${registeredFontName}-Bold`;
|
|
378
|
+
ctx.textAlign = "center";
|
|
379
|
+
ctx.textBaseline = "middle";
|
|
380
|
+
ctx.fillText(this.title.charAt(0).toUpperCase(), avatarX + avatarSize / 2, avatarY + avatarSize / 2);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
ctx.restore();
|
|
384
|
+
|
|
385
|
+
// Borda do avatar
|
|
386
|
+
ctx.strokeStyle = this.backgroundColor;
|
|
387
|
+
ctx.lineWidth = 4;
|
|
388
|
+
ctx.beginPath();
|
|
389
|
+
ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2 + ctx.lineWidth / 2, 0, Math.PI * 2);
|
|
390
|
+
ctx.stroke();
|
|
391
|
+
ctx.closePath();
|
|
392
|
+
|
|
393
|
+
// --- Desenha Área de Informações ---
|
|
394
|
+
const infoX = avatarX + avatarSize + padding;
|
|
395
|
+
const infoY = coverHeight + padding;
|
|
396
|
+
const infoWidth = cardWidth - infoX - padding;
|
|
397
|
+
|
|
398
|
+
// Aplica efeito de glassmorphism se ativado
|
|
399
|
+
if (this.useGlassmorphism) {
|
|
400
|
+
applyGlassmorphism(
|
|
401
|
+
ctx,
|
|
402
|
+
infoX - padding / 2,
|
|
403
|
+
coverHeight - padding / 2,
|
|
404
|
+
cardWidth - infoX,
|
|
405
|
+
cardHeight - coverHeight + padding,
|
|
406
|
+
cornerRadius,
|
|
407
|
+
0.2,
|
|
408
|
+
"#FFFFFF"
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// --- Desenha Título ---
|
|
413
|
+
ctx.fillStyle = this.textColor;
|
|
414
|
+
ctx.font = `bold 24px ${registeredFontName}-Bold`;
|
|
415
|
+
ctx.textAlign = "left";
|
|
416
|
+
ctx.textBaseline = "top";
|
|
417
|
+
|
|
418
|
+
// Aplica sombra de texto se ativada
|
|
419
|
+
if (this.useTextShadow) {
|
|
420
|
+
applyTextShadow(ctx, "rgba(0, 0, 0, 0.5)", 3, 1, 1);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const titleText = this.title;
|
|
424
|
+
const titleWidth = ctx.measureText(titleText).width;
|
|
425
|
+
ctx.fillText(titleText, infoX, infoY);
|
|
426
|
+
|
|
427
|
+
// Desenha ícone de verificado (se aplicável)
|
|
428
|
+
if (this.isVerified) {
|
|
429
|
+
const verifiedSize = 20;
|
|
430
|
+
const verifiedX = infoX + titleWidth + 10;
|
|
431
|
+
|
|
432
|
+
ctx.fillStyle = this.primaryColor;
|
|
433
|
+
ctx.beginPath();
|
|
434
|
+
ctx.arc(verifiedX + verifiedSize / 2, infoY + 12, verifiedSize / 2, 0, Math.PI * 2);
|
|
435
|
+
ctx.fill();
|
|
436
|
+
|
|
437
|
+
ctx.fillStyle = "#FFFFFF";
|
|
438
|
+
ctx.font = `bold 14px ${registeredFontName}-Bold`;
|
|
439
|
+
ctx.textAlign = "center";
|
|
440
|
+
ctx.fillText("✓", verifiedX + verifiedSize / 2, infoY + 12);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Remove sombra para o próximo texto
|
|
444
|
+
if (this.useTextShadow) {
|
|
445
|
+
clearShadow(ctx);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// --- Desenha Descrição ---
|
|
449
|
+
if (this.description) {
|
|
450
|
+
ctx.fillStyle = this.secondaryTextColor;
|
|
451
|
+
ctx.font = `regular 16px ${registeredFontName}-Regular`;
|
|
452
|
+
ctx.textAlign = "left";
|
|
453
|
+
|
|
454
|
+
const descriptionY = infoY + 35;
|
|
455
|
+
wrapText(ctx, this.description, infoX, descriptionY, infoWidth, 20, registeredFontName);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// --- Desenha Contadores ---
|
|
459
|
+
const counterY = cardHeight - padding - 20;
|
|
460
|
+
let counterX = infoX;
|
|
461
|
+
|
|
462
|
+
// Contador de inscritos
|
|
463
|
+
if (this.subscribers > 0) {
|
|
464
|
+
ctx.fillStyle = this.secondaryTextColor;
|
|
465
|
+
ctx.font = `medium 16px ${registeredFontName}-Medium`;
|
|
466
|
+
ctx.textAlign = "left";
|
|
467
|
+
|
|
468
|
+
const subscribersText = `${this._formatNumber(this.subscribers)} inscritos`;
|
|
469
|
+
ctx.fillText(subscribersText, counterX, counterY);
|
|
470
|
+
|
|
471
|
+
counterX += ctx.measureText(subscribersText).width + 20;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Contador de posts
|
|
475
|
+
if (this.posts > 0) {
|
|
476
|
+
ctx.fillStyle = this.secondaryTextColor;
|
|
477
|
+
ctx.font = `medium 16px ${registeredFontName}-Medium`;
|
|
478
|
+
ctx.textAlign = "left";
|
|
479
|
+
|
|
480
|
+
const postsText = `${this._formatNumber(this.posts)} publicações`;
|
|
481
|
+
ctx.fillText(postsText, counterX, counterY);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// --- Desenha Link ---
|
|
485
|
+
if (this.link) {
|
|
486
|
+
ctx.fillStyle = this.primaryColor;
|
|
487
|
+
ctx.font = `medium 16px ${registeredFontName}-Medium`;
|
|
488
|
+
ctx.textAlign = "right";
|
|
489
|
+
ctx.textBaseline = "bottom";
|
|
490
|
+
|
|
491
|
+
ctx.fillText(this.link, cardWidth - padding, cardHeight - padding);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// --- Desenha Ícone de Privacidade ---
|
|
495
|
+
const privacyIconSize = 16;
|
|
496
|
+
const privacyIconX = cardWidth - padding - privacyIconSize;
|
|
497
|
+
const privacyIconY = infoY;
|
|
498
|
+
|
|
499
|
+
ctx.fillStyle = this.isPublic ? this.primaryColor : this.secondaryTextColor;
|
|
500
|
+
ctx.beginPath();
|
|
501
|
+
|
|
502
|
+
if (this.isPublic) {
|
|
503
|
+
// Ícone de canal público (globo)
|
|
504
|
+
ctx.arc(privacyIconX + privacyIconSize / 2, privacyIconY + privacyIconSize / 2, privacyIconSize / 2, 0, Math.PI * 2);
|
|
505
|
+
ctx.fill();
|
|
506
|
+
|
|
507
|
+
ctx.strokeStyle = "#FFFFFF";
|
|
508
|
+
ctx.lineWidth = 1;
|
|
509
|
+
ctx.beginPath();
|
|
510
|
+
ctx.arc(privacyIconX + privacyIconSize / 2, privacyIconY + privacyIconSize / 2, privacyIconSize / 3, 0, Math.PI * 2);
|
|
511
|
+
ctx.stroke();
|
|
512
|
+
} else {
|
|
513
|
+
// Ícone de canal privado (cadeado)
|
|
514
|
+
ctx.fillRect(privacyIconX, privacyIconY + privacyIconSize / 3, privacyIconSize, privacyIconSize * 2/3);
|
|
515
|
+
ctx.beginPath();
|
|
516
|
+
ctx.arc(privacyIconX + privacyIconSize / 2, privacyIconY + privacyIconSize / 3, privacyIconSize / 3, Math.PI, 0);
|
|
517
|
+
ctx.fill();
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// --- Codifica e Retorna Buffer ---
|
|
521
|
+
try {
|
|
522
|
+
return await encodeToBuffer(canvas);
|
|
523
|
+
} catch (err) {
|
|
524
|
+
console.error("Falha ao codificar o Cabeçalho do Telegram:", err);
|
|
525
|
+
throw new Error("Não foi possível gerar o buffer de imagem do Cabeçalho do Telegram.");
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// --- Métodos Auxiliares Privados ---
|
|
530
|
+
/**
|
|
531
|
+
* Formata um número para exibição amigável
|
|
532
|
+
* @private
|
|
533
|
+
*/
|
|
534
|
+
_formatNumber(num) {
|
|
535
|
+
if (num >= 1000000) {
|
|
536
|
+
return (num / 1000000).toFixed(1).replace(/\.0$/, '') + 'M';
|
|
537
|
+
}
|
|
538
|
+
if (num >= 1000) {
|
|
539
|
+
return (num / 1000).toFixed(1).replace(/\.0$/, '') + 'K';
|
|
540
|
+
}
|
|
541
|
+
return num.toString();
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
|