@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.
Files changed (48) hide show
  1. package/assets/fonts/Manrope/Manrope-Bold.ttf +0 -0
  2. package/assets/fonts/Manrope/Manrope-Regular.ttf +0 -0
  3. package/assets/fonts/Others/AbyssinicaSIL-Regular.ttf +0 -0
  4. package/assets/fonts/Others/ChirpRegular.ttf +0 -0
  5. package/assets/fonts/Poppins/Poppins-Bold.ttf +0 -0
  6. package/assets/fonts/Poppins/Poppins-Medium.ttf +0 -0
  7. package/assets/fonts/Poppins/Poppins-Regular.ttf +0 -0
  8. package/assets/placeholders/album_art.png +0 -0
  9. package/assets/placeholders/avatar.png +0 -0
  10. package/assets/placeholders/badge.jpg +0 -0
  11. package/assets/placeholders/badge.png +0 -0
  12. package/assets/placeholders/badge_2.jpg +0 -0
  13. package/assets/placeholders/badge_3.jpg +0 -0
  14. package/assets/placeholders/badge_4.jpg +0 -0
  15. package/assets/placeholders/badge_5.jpg +0 -0
  16. package/assets/placeholders/banner.jpeg +0 -0
  17. package/assets/placeholders/images.jpeg +0 -0
  18. package/index.js +153 -0
  19. package/package.json +34 -0
  20. package/src/animation-effects.js +631 -0
  21. package/src/cache-manager.js +258 -0
  22. package/src/community-banner.js +1536 -0
  23. package/src/constants.js +208 -0
  24. package/src/discord-profile.js +584 -0
  25. package/src/e-commerce-banner.js +1214 -0
  26. package/src/effects.js +355 -0
  27. package/src/error-handler.js +305 -0
  28. package/src/event-banner.js +1319 -0
  29. package/src/facebook-post.js +679 -0
  30. package/src/gradient-welcome.js +430 -0
  31. package/src/image-filters.js +1034 -0
  32. package/src/image-processor.js +1014 -0
  33. package/src/instagram-post.js +504 -0
  34. package/src/interactive-elements.js +1208 -0
  35. package/src/linkedin-post.js +658 -0
  36. package/src/marketing-banner.js +1089 -0
  37. package/src/minimalist-banner.js +892 -0
  38. package/src/modern-profile.js +755 -0
  39. package/src/performance-optimizer.js +216 -0
  40. package/src/telegram-header.js +544 -0
  41. package/src/test-runner.js +645 -0
  42. package/src/tiktok-post.js +713 -0
  43. package/src/twitter-header.js +604 -0
  44. package/src/validator.js +442 -0
  45. package/src/welcome-leave.js +445 -0
  46. package/src/whatsapp-status.js +386 -0
  47. package/src/youtube-thumbnail.js +681 -0
  48. package/utils.js +710 -0
@@ -0,0 +1,504 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Módulo de Banner de Post do Instagram
5
+ *
6
+ * Este módulo gera banners no estilo de posts do Instagram com suporte a
7
+ * imagem principal, informações de usuário e comentários.
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
+ formatNumber
30
+ } = require("../utils");
31
+
32
+ /**
33
+ * @class InstagramPost
34
+ * @classdesc Gera um banner no estilo de post do Instagram.
35
+ * @example const postCard = new InstagramPost()
36
+ * .setUsername("usuario_instagram")
37
+ * .setUserAvatar("avatar.png")
38
+ * .setImage("post.jpg")
39
+ * .setCaption("Curtindo o dia na praia! #verao #ferias")
40
+ * .setLikes(1250)
41
+ * .addComment("amigo1", "Que lugar incrível!")
42
+ * .build();
43
+ */
44
+ module.exports = class InstagramPost {
45
+ constructor(options) {
46
+ // Dados Principais
47
+ this.username = "usuario";
48
+ this.userAvatar = null;
49
+ this.verified = false;
50
+ this.location = null;
51
+ this.image = null;
52
+ this.caption = null;
53
+ this.likes = 0;
54
+ this.comments = [];
55
+ this.timestamp = "há 1 hora";
56
+
57
+ // Personalização Visual
58
+ this.font = { name: options?.font?.name ?? DEFAULT_FONT_FAMILY, path: options?.font?.path };
59
+ this.backgroundColor = "#FFFFFF";
60
+ this.textColor = "#262626";
61
+ this.secondaryTextColor = "#8e8e8e";
62
+ this.accentColor = "#0095F6";
63
+ this.showHeader = true;
64
+ this.showFooter = true;
65
+
66
+ // Configurações de Layout
67
+ this.cardWidth = 600;
68
+ this.imageHeight = 600;
69
+ this.cornerRadius = 0;
70
+ }
71
+
72
+ // --- Setters para Dados Principais ---
73
+ /**
74
+ * Define o nome de usuário
75
+ * @param {string} name - Nome do usuário
76
+ * @returns {InstagramPost} - Instância atual para encadeamento
77
+ */
78
+ setUsername(name) {
79
+ if (!name || typeof name !== "string") throw new Error("O nome de usuário deve ser uma string não vazia.");
80
+ this.username = name;
81
+ return this;
82
+ }
83
+
84
+ /**
85
+ * Define o avatar do usuário
86
+ * @param {string|Buffer|Object} image - URL, Buffer ou caminho da imagem do avatar
87
+ * @returns {InstagramPost} - Instância atual para encadeamento
88
+ */
89
+ setUserAvatar(image) {
90
+ if (!image) throw new Error("A fonte da imagem do avatar não pode estar vazia.");
91
+ this.userAvatar = image;
92
+ return this;
93
+ }
94
+
95
+ /**
96
+ * Define se o usuário é verificado
97
+ * @param {boolean} isVerified - Se o usuário é verificado
98
+ * @returns {InstagramPost} - Instância atual para encadeamento
99
+ */
100
+ setVerified(isVerified = true) {
101
+ this.verified = !!isVerified;
102
+ return this;
103
+ }
104
+
105
+ /**
106
+ * Define a localização do post
107
+ * @param {string} location - Nome da localização
108
+ * @returns {InstagramPost} - Instância atual para encadeamento
109
+ */
110
+ setLocation(location) {
111
+ if (!location || typeof location !== "string") throw new Error("A localização deve ser uma string não vazia.");
112
+ this.location = location;
113
+ return this;
114
+ }
115
+
116
+ /**
117
+ * Define a imagem principal do post
118
+ * @param {string|Buffer|Object} image - URL, Buffer ou caminho da imagem do post
119
+ * @returns {InstagramPost} - Instância atual para encadeamento
120
+ */
121
+ setImage(image) {
122
+ if (!image) throw new Error("A fonte da imagem do post não pode estar vazia.");
123
+ this.image = image;
124
+ return this;
125
+ }
126
+
127
+ /**
128
+ * Define a legenda do post
129
+ * @param {string} text - Texto da legenda
130
+ * @returns {InstagramPost} - Instância atual para encadeamento
131
+ */
132
+ setCaption(text) {
133
+ if (!text || typeof text !== "string") throw new Error("A legenda deve ser uma string não vazia.");
134
+ this.caption = text;
135
+ return this;
136
+ }
137
+
138
+ /**
139
+ * Define o número de curtidas
140
+ * @param {number} count - Número de curtidas
141
+ * @returns {InstagramPost} - Instância atual para encadeamento
142
+ */
143
+ setLikes(count) {
144
+ if (typeof count !== "number" || count < 0) throw new Error("O número de curtidas deve ser um número não negativo.");
145
+ this.likes = count;
146
+ return this;
147
+ }
148
+
149
+ /**
150
+ * Adiciona um comentário ao post
151
+ * @param {string} username - Nome do usuário que comentou
152
+ * @param {string} text - Texto do comentário
153
+ * @returns {InstagramPost} - Instância atual para encadeamento
154
+ */
155
+ addComment(username, text) {
156
+ if (!username || typeof username !== "string") throw new Error("O nome de usuário do comentário deve ser uma string não vazia.");
157
+ if (!text || typeof text !== "string") throw new Error("O texto do comentário deve ser uma string não vazia.");
158
+
159
+ this.comments.push({ username, text });
160
+ return this;
161
+ }
162
+
163
+ /**
164
+ * Define o timestamp do post
165
+ * @param {string} time - Texto do timestamp (ex: "há 2 horas")
166
+ * @returns {InstagramPost} - Instância atual para encadeamento
167
+ */
168
+ setTimestamp(time) {
169
+ if (!time || typeof time !== "string") throw new Error("O timestamp deve ser uma string não vazia.");
170
+ this.timestamp = time;
171
+ return this;
172
+ }
173
+
174
+ // --- Setters para Personalização Visual ---
175
+ /**
176
+ * Define a cor de fundo
177
+ * @param {string} color - Cor hexadecimal
178
+ * @returns {InstagramPost} - Instância atual para encadeamento
179
+ */
180
+ setBackgroundColor(color) {
181
+ if (!color || !isValidHexColor(color)) throw new Error("Cor de fundo inválida. Use o formato hexadecimal.");
182
+ this.backgroundColor = color;
183
+ return this;
184
+ }
185
+
186
+ /**
187
+ * Define a cor do texto principal
188
+ * @param {string} color - Cor hexadecimal
189
+ * @returns {InstagramPost} - Instância atual para encadeamento
190
+ */
191
+ setTextColor(color) {
192
+ if (!color || !isValidHexColor(color)) throw new Error("Cor de texto inválida. Use o formato hexadecimal.");
193
+ this.textColor = color;
194
+ return this;
195
+ }
196
+
197
+ /**
198
+ * Define a cor do texto secundário
199
+ * @param {string} color - Cor hexadecimal
200
+ * @returns {InstagramPost} - Instância atual para encadeamento
201
+ */
202
+ setSecondaryTextColor(color) {
203
+ if (!color || !isValidHexColor(color)) throw new Error("Cor de texto secundário inválida. Use o formato hexadecimal.");
204
+ this.secondaryTextColor = color;
205
+ return this;
206
+ }
207
+
208
+ /**
209
+ * Define a cor de destaque
210
+ * @param {string} color - Cor hexadecimal
211
+ * @returns {InstagramPost} - Instância atual para encadeamento
212
+ */
213
+ setAccentColor(color) {
214
+ if (!color || !isValidHexColor(color)) throw new Error("Cor de destaque inválida. Use o formato hexadecimal.");
215
+ this.accentColor = color;
216
+ return this;
217
+ }
218
+
219
+ /**
220
+ * Ativa ou desativa o cabeçalho
221
+ * @param {boolean} show - Se o cabeçalho deve ser exibido
222
+ * @returns {InstagramPost} - Instância atual para encadeamento
223
+ */
224
+ setShowHeader(show = true) {
225
+ this.showHeader = show;
226
+ return this;
227
+ }
228
+
229
+ /**
230
+ * Ativa ou desativa o rodapé
231
+ * @param {boolean} show - Se o rodapé deve ser exibido
232
+ * @returns {InstagramPost} - Instância atual para encadeamento
233
+ */
234
+ setShowFooter(show = true) {
235
+ this.showFooter = show;
236
+ return this;
237
+ }
238
+
239
+ /**
240
+ * Define as dimensões do card
241
+ * @param {number} width - Largura do card em pixels
242
+ * @returns {InstagramPost} - Instância atual para encadeamento
243
+ */
244
+ setCardWidth(width) {
245
+ if (typeof width !== "number" || width < 400 || width > 1080) {
246
+ throw new Error("A largura do card deve estar entre 400 e 1080 pixels.");
247
+ }
248
+
249
+ this.cardWidth = width;
250
+ return this;
251
+ }
252
+
253
+ /**
254
+ * Define a altura da imagem
255
+ * @param {number} height - Altura da imagem em pixels
256
+ * @returns {InstagramPost} - Instância atual para encadeamento
257
+ */
258
+ setImageHeight(height) {
259
+ if (typeof height !== "number" || height < 400 || height > 1080) {
260
+ throw new Error("A altura da imagem deve estar entre 400 e 1080 pixels.");
261
+ }
262
+
263
+ this.imageHeight = height;
264
+ return this;
265
+ }
266
+
267
+ // --- Método de Construção ---
268
+ /**
269
+ * Constrói o banner e retorna um buffer de imagem
270
+ * @returns {Promise<Buffer>} - Buffer contendo a imagem do banner
271
+ */
272
+ async build() {
273
+ if (!this.image) throw new Error("A imagem do post deve ser definida usando setImage().");
274
+
275
+ // --- Registro de Fonte ---
276
+ const registeredFontName = await registerFontIfNeeded(this.font);
277
+
278
+ // --- Configuração do Canvas ---
279
+ const cardWidth = this.cardWidth;
280
+ const headerHeight = this.showHeader ? 60 : 0;
281
+ const imageHeight = this.imageHeight;
282
+
283
+ // Calcula a altura do rodapé com base no conteúdo
284
+ let footerHeight = 0;
285
+ if (this.showFooter) {
286
+ footerHeight += 50; // Área de ícones e curtidas
287
+
288
+ if (this.caption) {
289
+ footerHeight += 60; // Espaço para legenda
290
+ }
291
+
292
+ footerHeight += this.comments.length * 40; // Espaço para comentários
293
+ footerHeight += 30; // Espaço para timestamp
294
+ }
295
+
296
+ const cardHeight = headerHeight + imageHeight + footerHeight;
297
+ const padding = 15;
298
+ const avatarSize = 32;
299
+
300
+ const canvas = pureimage.make(cardWidth, cardHeight);
301
+ const ctx = canvas.getContext("2d");
302
+
303
+ // --- Desenha Plano de Fundo ---
304
+ ctx.fillStyle = this.backgroundColor;
305
+ ctx.fillRect(0, 0, cardWidth, cardHeight);
306
+
307
+ // --- Desenha Cabeçalho (se ativado) ---
308
+ if (this.showHeader) {
309
+ // Desenha linha divisória
310
+ ctx.fillStyle = "#DBDBDB";
311
+ ctx.fillRect(0, headerHeight - 1, cardWidth, 1);
312
+
313
+ // Desenha avatar do usuário
314
+ if (this.userAvatar) {
315
+ try {
316
+ ctx.save();
317
+ ctx.beginPath();
318
+ ctx.arc(padding + avatarSize / 2, headerHeight / 2, avatarSize / 2, 0, Math.PI * 2);
319
+ ctx.closePath();
320
+ ctx.clip();
321
+
322
+ const avatarImg = await loadImageWithAxios(this.userAvatar);
323
+ ctx.drawImage(avatarImg, padding, headerHeight / 2 - avatarSize / 2, avatarSize, avatarSize);
324
+
325
+ ctx.restore();
326
+ } catch (e) {
327
+ console.error("Falha ao desenhar avatar do usuário:", e.message);
328
+
329
+ // Avatar de fallback
330
+ ctx.fillStyle = "#DBDBDB";
331
+ ctx.beginPath();
332
+ ctx.arc(padding + avatarSize / 2, headerHeight / 2, avatarSize / 2, 0, Math.PI * 2);
333
+ ctx.fill();
334
+
335
+ ctx.fillStyle = "#8e8e8e";
336
+ ctx.font = `bold 16px ${registeredFontName}-Bold`;
337
+ ctx.textAlign = "center";
338
+ ctx.textBaseline = "middle";
339
+ ctx.fillText("?", padding + avatarSize / 2, headerHeight / 2);
340
+ }
341
+ }
342
+
343
+ // Desenha nome de usuário
344
+ ctx.fillStyle = this.textColor;
345
+ ctx.font = `bold 14px ${registeredFontName}-Bold`;
346
+ ctx.textAlign = "left";
347
+ ctx.textBaseline = "middle";
348
+
349
+ const usernameX = padding + avatarSize + 10;
350
+ ctx.fillText(this.username, usernameX, headerHeight / 2 - (this.location ? 7 : 0));
351
+
352
+ // Desenha ícone de verificado (se aplicável)
353
+ if (this.verified) {
354
+ const verifiedSize = 14;
355
+ const verifiedX = usernameX + ctx.measureText(this.username).width + 5;
356
+
357
+ ctx.fillStyle = this.accentColor;
358
+ ctx.beginPath();
359
+ ctx.arc(verifiedX + verifiedSize / 2, headerHeight / 2 - (this.location ? 7 : 0), verifiedSize / 2, 0, Math.PI * 2);
360
+ ctx.fill();
361
+
362
+ ctx.fillStyle = "#FFFFFF";
363
+ ctx.font = `bold 10px ${registeredFontName}-Bold`;
364
+ ctx.textAlign = "center";
365
+ ctx.fillText("✓", verifiedX + verifiedSize / 2, headerHeight / 2 - (this.location ? 7 : 0));
366
+ }
367
+
368
+ // Desenha localização (se fornecida)
369
+ if (this.location) {
370
+ ctx.fillStyle = this.secondaryTextColor;
371
+ ctx.font = `regular 12px ${registeredFontName}-Regular`;
372
+ ctx.textAlign = "left";
373
+ ctx.fillText(this.location, usernameX, headerHeight / 2 + 10);
374
+ }
375
+
376
+ // Desenha ícone de opções
377
+ ctx.fillStyle = this.textColor;
378
+ ctx.font = `bold 18px ${registeredFontName}-Bold`;
379
+ ctx.textAlign = "center";
380
+ ctx.fillText("•••", cardWidth - padding - 10, headerHeight / 2);
381
+ }
382
+
383
+ // --- Desenha Imagem Principal ---
384
+ try {
385
+ const img = await loadImageWithAxios(this.image);
386
+ const aspect = img.width / img.height;
387
+ let drawWidth = cardWidth;
388
+ let drawHeight = drawWidth / aspect;
389
+
390
+ // Ajusta as dimensões para manter a proporção e preencher a altura desejada
391
+ if (drawHeight < imageHeight) {
392
+ drawHeight = imageHeight;
393
+ drawWidth = drawHeight * aspect;
394
+ }
395
+
396
+ const offsetX = (cardWidth - drawWidth) / 2;
397
+ const offsetY = headerHeight;
398
+
399
+ ctx.drawImage(img, offsetX, offsetY, drawWidth, imageHeight);
400
+ } catch (e) {
401
+ console.error("Falha ao desenhar imagem principal:", e.message);
402
+
403
+ // Imagem de fallback
404
+ ctx.fillStyle = "#DBDBDB";
405
+ ctx.fillRect(0, headerHeight, cardWidth, imageHeight);
406
+
407
+ ctx.fillStyle = "#8e8e8e";
408
+ ctx.font = `bold 24px ${registeredFontName}-Bold`;
409
+ ctx.textAlign = "center";
410
+ ctx.textBaseline = "middle";
411
+ ctx.fillText("Imagem não disponível", cardWidth / 2, headerHeight + imageHeight / 2);
412
+ }
413
+
414
+ // --- Desenha Rodapé (se ativado) ---
415
+ if (this.showFooter) {
416
+ let currentY = headerHeight + imageHeight;
417
+
418
+ // Desenha linha divisória
419
+ ctx.fillStyle = "#DBDBDB";
420
+ ctx.fillRect(0, currentY, cardWidth, 1);
421
+ currentY += 1;
422
+
423
+ // Desenha ícones de ação
424
+ const iconSize = 24;
425
+ const iconSpacing = 15;
426
+ let iconX = padding;
427
+
428
+ // Ícone de curtir
429
+ ctx.fillStyle = this.textColor;
430
+ ctx.font = `bold ${iconSize}px Arial`;
431
+ ctx.textAlign = "center";
432
+ ctx.textBaseline = "middle";
433
+ ctx.fillText("♡", iconX + iconSize / 2, currentY + 25);
434
+ iconX += iconSize + iconSpacing;
435
+
436
+ // Ícone de comentar
437
+ ctx.fillText("💬", iconX + iconSize / 2, currentY + 25);
438
+ iconX += iconSize + iconSpacing;
439
+
440
+ // Ícone de compartilhar
441
+ ctx.fillText("➤", iconX + iconSize / 2, currentY + 25);
442
+
443
+ // Ícone de salvar (à direita)
444
+ ctx.fillText("⊕", cardWidth - padding - iconSize / 2, currentY + 25);
445
+
446
+ currentY += 50;
447
+
448
+ // Desenha contador de curtidas
449
+ ctx.fillStyle = this.textColor;
450
+ ctx.font = `bold 14px ${registeredFontName}-Bold`;
451
+ ctx.textAlign = "left";
452
+ ctx.textBaseline = "top";
453
+ ctx.fillText(`${formatNumber(this.likes)} curtidas`, padding, currentY);
454
+ currentY += 25;
455
+
456
+ // Desenha legenda (se fornecida)
457
+ if (this.caption) {
458
+ ctx.fillStyle = this.textColor;
459
+ ctx.font = `bold 14px ${registeredFontName}-Bold`;
460
+ ctx.textAlign = "left";
461
+ ctx.fillText(this.username, padding, currentY);
462
+
463
+ const usernameWidth = ctx.measureText(this.username).width;
464
+
465
+ ctx.font = `regular 14px ${registeredFontName}-Regular`;
466
+ const captionY = wrapText(ctx, this.caption, padding + usernameWidth + 5, currentY, cardWidth - padding * 2 - usernameWidth - 5, 18, registeredFontName);
467
+
468
+ currentY = captionY;
469
+ }
470
+
471
+ // Desenha comentários (se houver)
472
+ if (this.comments.length > 0) {
473
+ for (const comment of this.comments) {
474
+ ctx.fillStyle = this.textColor;
475
+ ctx.font = `bold 14px ${registeredFontName}-Bold`;
476
+ ctx.textAlign = "left";
477
+ ctx.fillText(comment.username, padding, currentY);
478
+
479
+ const commentUsernameWidth = ctx.measureText(comment.username).width;
480
+
481
+ ctx.font = `regular 14px ${registeredFontName}-Regular`;
482
+ const commentY = wrapText(ctx, comment.text, padding + commentUsernameWidth + 5, currentY, cardWidth - padding * 2 - commentUsernameWidth - 5, 18, registeredFontName);
483
+
484
+ currentY = commentY;
485
+ }
486
+ }
487
+
488
+ // Desenha timestamp
489
+ ctx.fillStyle = this.secondaryTextColor;
490
+ ctx.font = `regular 12px ${registeredFontName}-Regular`;
491
+ ctx.textAlign = "left";
492
+ ctx.fillText(this.timestamp, padding, currentY);
493
+ }
494
+
495
+ // --- Codifica e Retorna Buffer ---
496
+ try {
497
+ return await encodeToBuffer(canvas);
498
+ } catch (err) {
499
+ console.error("Falha ao codificar o card de Post do Instagram:", err);
500
+ throw new Error("Não foi possível gerar o buffer de imagem do card de Post do Instagram.");
501
+ }
502
+ }
503
+ };
504
+