@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,679 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Módulo de Banner de Post do Facebook
|
|
5
|
+
*
|
|
6
|
+
* Este módulo gera banners no estilo de posts do Facebook com
|
|
7
|
+
* elementos visuais característicos da plataforma.
|
|
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
|
+
const {
|
|
33
|
+
DEFAULT_COLORS,
|
|
34
|
+
LAYOUT,
|
|
35
|
+
DEFAULT_DIMENSIONS
|
|
36
|
+
} = require("./constants");
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @class FacebookPost
|
|
40
|
+
* @classdesc Gera um banner no estilo de post do Facebook.
|
|
41
|
+
* @example const post = new FacebookPost()
|
|
42
|
+
* .setName("Nome Completo")
|
|
43
|
+
* .setContent("Conteúdo do post")
|
|
44
|
+
* .setImage("imagem.jpg")
|
|
45
|
+
* .setLikes(1500)
|
|
46
|
+
* .setComments(200)
|
|
47
|
+
* .setShares(50)
|
|
48
|
+
* .build();
|
|
49
|
+
*/
|
|
50
|
+
module.exports = class FacebookPost {
|
|
51
|
+
constructor(options) {
|
|
52
|
+
// Dados Principais
|
|
53
|
+
this.name = "Nome Completo";
|
|
54
|
+
this.avatar = null;
|
|
55
|
+
this.content = "Conteúdo do post";
|
|
56
|
+
this.image = null;
|
|
57
|
+
this.likes = 0;
|
|
58
|
+
this.comments = 0;
|
|
59
|
+
this.shares = 0;
|
|
60
|
+
this.postTime = "1h";
|
|
61
|
+
this.isVerified = false;
|
|
62
|
+
this.privacy = "public"; // public, friends, private
|
|
63
|
+
this.isPagePost = false;
|
|
64
|
+
this.pageName = null;
|
|
65
|
+
this.pageLogo = null;
|
|
66
|
+
this.reactions = {
|
|
67
|
+
like: 0,
|
|
68
|
+
love: 0,
|
|
69
|
+
care: 0,
|
|
70
|
+
haha: 0,
|
|
71
|
+
wow: 0,
|
|
72
|
+
sad: 0,
|
|
73
|
+
angry: 0
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Personalização Visual
|
|
77
|
+
this.font = { name: options?.font?.name ?? DEFAULT_FONT_FAMILY, path: options?.font?.path };
|
|
78
|
+
this.theme = "light"; // light, dark
|
|
79
|
+
this.postType = "standard"; // standard, photo, video, shared, event
|
|
80
|
+
this.cornerRadius = LAYOUT.cornerRadius.small;
|
|
81
|
+
|
|
82
|
+
// Configurações de Layout
|
|
83
|
+
this.cardWidth = DEFAULT_DIMENSIONS.post.width;
|
|
84
|
+
this.cardHeight = 700;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// --- Setters para Dados Principais ---
|
|
88
|
+
/**
|
|
89
|
+
* Define o nome completo
|
|
90
|
+
* @param {string} text - Nome completo
|
|
91
|
+
* @returns {FacebookPost} - Instância atual para encadeamento
|
|
92
|
+
*/
|
|
93
|
+
setName(text) {
|
|
94
|
+
if (!text || typeof text !== "string") throw new Error("O nome completo deve ser uma string não vazia.");
|
|
95
|
+
this.name = text;
|
|
96
|
+
return this;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Define o avatar
|
|
101
|
+
* @param {string|Buffer|Object} image - URL, Buffer ou caminho da imagem do avatar
|
|
102
|
+
* @returns {FacebookPost} - Instância atual para encadeamento
|
|
103
|
+
*/
|
|
104
|
+
setAvatar(image) {
|
|
105
|
+
if (!image) throw new Error("A fonte da imagem do avatar não pode estar vazia.");
|
|
106
|
+
this.avatar = image;
|
|
107
|
+
return this;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Define o conteúdo do post
|
|
112
|
+
* @param {string} text - Texto do conteúdo
|
|
113
|
+
* @returns {FacebookPost} - Instância atual para encadeamento
|
|
114
|
+
*/
|
|
115
|
+
setContent(text) {
|
|
116
|
+
if (!text || typeof text !== "string") throw new Error("O conteúdo deve ser uma string não vazia.");
|
|
117
|
+
this.content = text;
|
|
118
|
+
return this;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Define a imagem principal do post
|
|
123
|
+
* @param {string|Buffer|Object} image - URL, Buffer ou caminho da imagem
|
|
124
|
+
* @returns {FacebookPost} - Instância atual para encadeamento
|
|
125
|
+
*/
|
|
126
|
+
setImage(image) {
|
|
127
|
+
if (!image) throw new Error("A fonte da imagem não pode estar vazia.");
|
|
128
|
+
this.image = image;
|
|
129
|
+
return this;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Define o número de curtidas
|
|
134
|
+
* @param {number} count - Número de curtidas
|
|
135
|
+
* @returns {FacebookPost} - Instância atual para encadeamento
|
|
136
|
+
*/
|
|
137
|
+
setLikes(count) {
|
|
138
|
+
if (typeof count !== "number" || count < 0) throw new Error("O número de curtidas deve ser um número não negativo.");
|
|
139
|
+
this.likes = count;
|
|
140
|
+
return this;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Define o número de comentários
|
|
145
|
+
* @param {number} count - Número de comentários
|
|
146
|
+
* @returns {FacebookPost} - Instância atual para encadeamento
|
|
147
|
+
*/
|
|
148
|
+
setComments(count) {
|
|
149
|
+
if (typeof count !== "number" || count < 0) throw new Error("O número de comentários deve ser um número não negativo.");
|
|
150
|
+
this.comments = count;
|
|
151
|
+
return this;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Define o número de compartilhamentos
|
|
156
|
+
* @param {number} count - Número de compartilhamentos
|
|
157
|
+
* @returns {FacebookPost} - Instância atual para encadeamento
|
|
158
|
+
*/
|
|
159
|
+
setShares(count) {
|
|
160
|
+
if (typeof count !== "number" || count < 0) throw new Error("O número de compartilhamentos deve ser um número não negativo.");
|
|
161
|
+
this.shares = count;
|
|
162
|
+
return this;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Define o tempo de publicação
|
|
167
|
+
* @param {string} text - Tempo de publicação (ex: "1h", "2d", "1sem")
|
|
168
|
+
* @returns {FacebookPost} - Instância atual para encadeamento
|
|
169
|
+
*/
|
|
170
|
+
setPostTime(text) {
|
|
171
|
+
if (!text || typeof text !== "string") throw new Error("O tempo de publicação deve ser uma string não vazia.");
|
|
172
|
+
this.postTime = text;
|
|
173
|
+
return this;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Define se o usuário é verificado
|
|
178
|
+
* @param {boolean} isVerified - Se o usuário é verificado
|
|
179
|
+
* @returns {FacebookPost} - Instância atual para encadeamento
|
|
180
|
+
*/
|
|
181
|
+
setVerified(isVerified = true) {
|
|
182
|
+
this.isVerified = !!isVerified;
|
|
183
|
+
return this;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Define a privacidade do post
|
|
188
|
+
* @param {string} privacy - Privacidade ('public', 'friends', 'private')
|
|
189
|
+
* @returns {FacebookPost} - Instância atual para encadeamento
|
|
190
|
+
*/
|
|
191
|
+
setPrivacy(privacy) {
|
|
192
|
+
const validPrivacy = ["public", "friends", "private"];
|
|
193
|
+
if (!privacy || !validPrivacy.includes(privacy.toLowerCase())) {
|
|
194
|
+
throw new Error(`Privacidade inválida. Use uma das seguintes: ${validPrivacy.join(", ")}`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
this.privacy = privacy.toLowerCase();
|
|
198
|
+
return this;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Define se é um post de página
|
|
203
|
+
* @param {boolean} isPagePost - Se é um post de página
|
|
204
|
+
* @param {string} pageName - Nome da página
|
|
205
|
+
* @param {string|Buffer|Object} pageLogo - URL, Buffer ou caminho do logo da página
|
|
206
|
+
* @returns {FacebookPost} - Instância atual para encadeamento
|
|
207
|
+
*/
|
|
208
|
+
setPagePost(isPagePost = true, pageName = null, pageLogo = null) {
|
|
209
|
+
this.isPagePost = !!isPagePost;
|
|
210
|
+
|
|
211
|
+
if (isPagePost) {
|
|
212
|
+
if (!pageName || typeof pageName !== "string") {
|
|
213
|
+
throw new Error("O nome da página deve ser uma string não vazia.");
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
this.pageName = pageName;
|
|
217
|
+
this.pageLogo = pageLogo;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return this;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Define as reações do post
|
|
225
|
+
* @param {Object} reactions - Objeto com as contagens de reações
|
|
226
|
+
* @returns {FacebookPost} - Instância atual para encadeamento
|
|
227
|
+
*/
|
|
228
|
+
setReactions(reactions) {
|
|
229
|
+
if (!reactions || typeof reactions !== "object") {
|
|
230
|
+
throw new Error("As reações devem ser um objeto.");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const validReactions = ["like", "love", "care", "haha", "wow", "sad", "angry"];
|
|
234
|
+
|
|
235
|
+
for (const [key, value] of Object.entries(reactions)) {
|
|
236
|
+
if (validReactions.includes(key)) {
|
|
237
|
+
if (typeof value !== "number" || value < 0) {
|
|
238
|
+
throw new Error(`O valor da reação "${key}" deve ser um número não negativo.`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
this.reactions[key] = value;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Atualiza o total de curtidas
|
|
246
|
+
this.likes = Object.values(this.reactions).reduce((a, b) => a + b, 0);
|
|
247
|
+
|
|
248
|
+
return this;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// --- Setters para Personalização Visual ---
|
|
252
|
+
/**
|
|
253
|
+
* Define o tema
|
|
254
|
+
* @param {string} theme - Tema ('light', 'dark')
|
|
255
|
+
* @returns {FacebookPost} - Instância atual para encadeamento
|
|
256
|
+
*/
|
|
257
|
+
setTheme(theme) {
|
|
258
|
+
const validThemes = ["light", "dark"];
|
|
259
|
+
if (!theme || !validThemes.includes(theme.toLowerCase())) {
|
|
260
|
+
throw new Error(`Tema inválido. Use um dos seguintes: ${validThemes.join(", ")}`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
this.theme = theme.toLowerCase();
|
|
264
|
+
return this;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Define o tipo de post
|
|
269
|
+
* @param {string} type - Tipo de post ('standard', 'photo', 'video', 'shared', 'event')
|
|
270
|
+
* @returns {FacebookPost} - Instância atual para encadeamento
|
|
271
|
+
*/
|
|
272
|
+
setPostType(type) {
|
|
273
|
+
const validTypes = ["standard", "photo", "video", "shared", "event"];
|
|
274
|
+
if (!type || !validTypes.includes(type.toLowerCase())) {
|
|
275
|
+
throw new Error(`Tipo de post inválido. Use um dos seguintes: ${validTypes.join(", ")}`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
this.postType = type.toLowerCase();
|
|
279
|
+
return this;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Define o raio dos cantos arredondados
|
|
284
|
+
* @param {number} radius - Raio dos cantos em pixels
|
|
285
|
+
* @returns {FacebookPost} - Instância atual para encadeamento
|
|
286
|
+
*/
|
|
287
|
+
setCornerRadius(radius) {
|
|
288
|
+
if (typeof radius !== "number" || radius < 0) throw new Error("O raio dos cantos deve ser um número não negativo.");
|
|
289
|
+
this.cornerRadius = radius;
|
|
290
|
+
return this;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Define as dimensões do card
|
|
295
|
+
* @param {number} width - Largura do card em pixels
|
|
296
|
+
* @param {number} height - Altura do card em pixels
|
|
297
|
+
* @returns {FacebookPost} - Instância atual para encadeamento
|
|
298
|
+
*/
|
|
299
|
+
setCardDimensions(width, height) {
|
|
300
|
+
if (typeof width !== "number" || width < 400 || width > 1200) {
|
|
301
|
+
throw new Error("A largura do card deve estar entre 400 e 1200 pixels.");
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (typeof height !== "number" || height < 400 || height > 1200) {
|
|
305
|
+
throw new Error("A altura do card deve estar entre 400 e 1200 pixels.");
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
this.cardWidth = width;
|
|
309
|
+
this.cardHeight = height;
|
|
310
|
+
|
|
311
|
+
return this;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// --- Método de Construção ---
|
|
315
|
+
/**
|
|
316
|
+
* Constrói o banner e retorna um buffer de imagem
|
|
317
|
+
* @returns {Promise<Buffer>} - Buffer contendo a imagem do banner
|
|
318
|
+
*/
|
|
319
|
+
async build() {
|
|
320
|
+
// --- Registro de Fonte ---
|
|
321
|
+
const registeredFontName = await registerFontIfNeeded(this.font);
|
|
322
|
+
|
|
323
|
+
// --- Configuração do Canvas ---
|
|
324
|
+
const cardWidth = this.cardWidth;
|
|
325
|
+
const cardHeight = this.cardHeight;
|
|
326
|
+
const cornerRadius = this.cornerRadius;
|
|
327
|
+
const padding = 16;
|
|
328
|
+
|
|
329
|
+
const canvas = pureimage.make(cardWidth, cardHeight);
|
|
330
|
+
const ctx = canvas.getContext("2d");
|
|
331
|
+
|
|
332
|
+
// --- Configuração de Cores com base no Tema ---
|
|
333
|
+
const colors = this._getThemeColors();
|
|
334
|
+
|
|
335
|
+
// --- Desenha Plano de Fundo ---
|
|
336
|
+
ctx.fillStyle = colors.background;
|
|
337
|
+
roundRect(ctx, 0, 0, cardWidth, cardHeight, cornerRadius, true, false);
|
|
338
|
+
|
|
339
|
+
// --- Desenha Cabeçalho do Post ---
|
|
340
|
+
const headerHeight = 70;
|
|
341
|
+
|
|
342
|
+
// Avatar
|
|
343
|
+
const avatarSize = 50;
|
|
344
|
+
const avatarX = padding;
|
|
345
|
+
const avatarY = padding;
|
|
346
|
+
|
|
347
|
+
ctx.save();
|
|
348
|
+
ctx.beginPath();
|
|
349
|
+
ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2, 0, Math.PI * 2);
|
|
350
|
+
ctx.closePath();
|
|
351
|
+
ctx.clip();
|
|
352
|
+
|
|
353
|
+
try {
|
|
354
|
+
const avatarImg = await loadImageWithAxios(this.avatar || path.join(__dirname, "../assets/placeholders/avatar.png"));
|
|
355
|
+
ctx.drawImage(avatarImg, avatarX, avatarY, avatarSize, avatarSize);
|
|
356
|
+
} catch (e) {
|
|
357
|
+
console.error("Falha ao desenhar avatar:", e.message);
|
|
358
|
+
|
|
359
|
+
// Avatar de fallback
|
|
360
|
+
ctx.fillStyle = "#1877F2";
|
|
361
|
+
ctx.fillRect(avatarX, avatarY, avatarSize, avatarSize);
|
|
362
|
+
|
|
363
|
+
ctx.fillStyle = "#FFFFFF";
|
|
364
|
+
ctx.font = `bold ${avatarSize / 3}px ${registeredFontName}-Bold`;
|
|
365
|
+
ctx.textAlign = "center";
|
|
366
|
+
ctx.textBaseline = "middle";
|
|
367
|
+
ctx.fillText(this.name.charAt(0).toUpperCase(), avatarX + avatarSize / 2, avatarY + avatarSize / 2);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
ctx.restore();
|
|
371
|
+
|
|
372
|
+
// Informações do usuário
|
|
373
|
+
const infoX = avatarX + avatarSize + 10;
|
|
374
|
+
let infoY = avatarY + 5;
|
|
375
|
+
|
|
376
|
+
// Nome
|
|
377
|
+
ctx.fillStyle = colors.text;
|
|
378
|
+
ctx.font = `bold 16px ${registeredFontName}-Bold`;
|
|
379
|
+
ctx.textAlign = "left";
|
|
380
|
+
ctx.textBaseline = "top";
|
|
381
|
+
|
|
382
|
+
const nameText = this.name;
|
|
383
|
+
const nameWidth = ctx.measureText(nameText).width;
|
|
384
|
+
ctx.fillText(nameText, infoX, infoY);
|
|
385
|
+
|
|
386
|
+
// Ícone de verificado (se aplicável)
|
|
387
|
+
if (this.isVerified) {
|
|
388
|
+
const verifiedSize = 16;
|
|
389
|
+
const verifiedX = infoX + nameWidth + 5;
|
|
390
|
+
|
|
391
|
+
ctx.fillStyle = "#1877F2";
|
|
392
|
+
ctx.beginPath();
|
|
393
|
+
ctx.arc(verifiedX + verifiedSize / 2, infoY + verifiedSize / 2, verifiedSize / 2, 0, Math.PI * 2);
|
|
394
|
+
ctx.fill();
|
|
395
|
+
|
|
396
|
+
ctx.fillStyle = "#FFFFFF";
|
|
397
|
+
ctx.font = `bold 12px ${registeredFontName}-Bold`;
|
|
398
|
+
ctx.textAlign = "center";
|
|
399
|
+
ctx.fillText("✓", verifiedX + verifiedSize / 2, infoY + verifiedSize / 2);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Página (se aplicável)
|
|
403
|
+
if (this.isPagePost) {
|
|
404
|
+
infoY += 20;
|
|
405
|
+
ctx.fillStyle = colors.textSecondary;
|
|
406
|
+
ctx.font = `regular 14px ${registeredFontName}-Regular`;
|
|
407
|
+
ctx.textAlign = "left";
|
|
408
|
+
ctx.fillText(this.pageName, infoX, infoY);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Tempo e privacidade
|
|
412
|
+
infoY = this.isPagePost ? infoY + 20 : infoY + 25;
|
|
413
|
+
ctx.fillStyle = colors.textSecondary;
|
|
414
|
+
ctx.font = `regular 14px ${registeredFontName}-Regular`;
|
|
415
|
+
ctx.textAlign = "left";
|
|
416
|
+
|
|
417
|
+
let privacyIcon = "🌎";
|
|
418
|
+
if (this.privacy === "friends") {
|
|
419
|
+
privacyIcon = "👥";
|
|
420
|
+
} else if (this.privacy === "private") {
|
|
421
|
+
privacyIcon = "🔒";
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
ctx.fillText(`${this.postTime} • ${privacyIcon}`, infoX, infoY);
|
|
425
|
+
|
|
426
|
+
// Botão de mais opções
|
|
427
|
+
const moreButtonX = cardWidth - padding - 20;
|
|
428
|
+
const moreButtonY = padding + 25;
|
|
429
|
+
|
|
430
|
+
ctx.fillStyle = colors.textSecondary;
|
|
431
|
+
ctx.textAlign = "center";
|
|
432
|
+
ctx.textBaseline = "middle";
|
|
433
|
+
ctx.fillText("•••", moreButtonX, moreButtonY);
|
|
434
|
+
|
|
435
|
+
// --- Desenha Conteúdo do Post ---
|
|
436
|
+
let contentY = headerHeight + padding;
|
|
437
|
+
|
|
438
|
+
// Texto do post
|
|
439
|
+
if (this.content) {
|
|
440
|
+
ctx.fillStyle = colors.text;
|
|
441
|
+
ctx.font = `regular 16px ${registeredFontName}-Regular`;
|
|
442
|
+
ctx.textAlign = "left";
|
|
443
|
+
ctx.textBaseline = "top";
|
|
444
|
+
|
|
445
|
+
contentY = wrapText(ctx, this.content, padding, contentY, cardWidth - padding * 2, 24, registeredFontName);
|
|
446
|
+
contentY += padding;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Imagem (se fornecida)
|
|
450
|
+
if (this.image) {
|
|
451
|
+
try {
|
|
452
|
+
const imageHeight = 300;
|
|
453
|
+
const imageY = contentY;
|
|
454
|
+
|
|
455
|
+
ctx.save();
|
|
456
|
+
|
|
457
|
+
// Recorta a imagem com cantos arredondados
|
|
458
|
+
roundRect(ctx, padding, imageY, cardWidth - padding * 2, imageHeight, cornerRadius, false, false);
|
|
459
|
+
ctx.clip();
|
|
460
|
+
|
|
461
|
+
const img = await loadImageWithAxios(this.image);
|
|
462
|
+
const aspect = img.width / img.height;
|
|
463
|
+
const imageWidth = cardWidth - padding * 2;
|
|
464
|
+
|
|
465
|
+
// Ajusta as dimensões para manter a proporção
|
|
466
|
+
let drawWidth = imageWidth;
|
|
467
|
+
let drawHeight = imageWidth / aspect;
|
|
468
|
+
|
|
469
|
+
if (drawHeight > imageHeight) {
|
|
470
|
+
drawHeight = imageHeight;
|
|
471
|
+
drawWidth = imageHeight * aspect;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const offsetX = padding + (imageWidth - drawWidth) / 2;
|
|
475
|
+
const offsetY = imageY + (imageHeight - drawHeight) / 2;
|
|
476
|
+
|
|
477
|
+
ctx.drawImage(img, offsetX, offsetY, drawWidth, drawHeight);
|
|
478
|
+
|
|
479
|
+
ctx.restore();
|
|
480
|
+
|
|
481
|
+
contentY = imageY + imageHeight + padding;
|
|
482
|
+
} catch (e) {
|
|
483
|
+
console.error("Falha ao desenhar imagem:", e.message);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// --- Desenha Contadores de Reações ---
|
|
488
|
+
const reactionsY = contentY;
|
|
489
|
+
const reactionsHeight = 30;
|
|
490
|
+
|
|
491
|
+
// Ícones de reações
|
|
492
|
+
const reactionIcons = {
|
|
493
|
+
like: "👍",
|
|
494
|
+
love: "❤️",
|
|
495
|
+
care: "🤗",
|
|
496
|
+
haha: "😄",
|
|
497
|
+
wow: "😮",
|
|
498
|
+
sad: "😢",
|
|
499
|
+
angry: "😠"
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
// Desenha ícones de reações
|
|
503
|
+
let hasReactions = false;
|
|
504
|
+
const activeReactions = Object.entries(this.reactions).filter(([_, count]) => count > 0);
|
|
505
|
+
|
|
506
|
+
if (activeReactions.length > 0) {
|
|
507
|
+
hasReactions = true;
|
|
508
|
+
|
|
509
|
+
// Fundo dos ícones
|
|
510
|
+
ctx.fillStyle = colors.reactionBackground;
|
|
511
|
+
roundRect(ctx, padding, reactionsY, 80, reactionsHeight, reactionsHeight / 2, true, false);
|
|
512
|
+
|
|
513
|
+
// Desenha até 3 ícones de reações
|
|
514
|
+
const iconSize = 20;
|
|
515
|
+
const iconSpacing = 15;
|
|
516
|
+
let iconX = padding + 10;
|
|
517
|
+
|
|
518
|
+
activeReactions.slice(0, 3).forEach(([reaction, _]) => {
|
|
519
|
+
ctx.fillStyle = colors.text;
|
|
520
|
+
ctx.font = `regular 16px ${registeredFontName}-Regular`;
|
|
521
|
+
ctx.textAlign = "center";
|
|
522
|
+
ctx.textBaseline = "middle";
|
|
523
|
+
ctx.fillText(reactionIcons[reaction], iconX, reactionsY + reactionsHeight / 2);
|
|
524
|
+
|
|
525
|
+
iconX += iconSpacing;
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
// Contador de reações
|
|
529
|
+
ctx.fillStyle = colors.textSecondary;
|
|
530
|
+
ctx.font = `regular 14px ${registeredFontName}-Regular`;
|
|
531
|
+
ctx.textAlign = "left";
|
|
532
|
+
ctx.textBaseline = "middle";
|
|
533
|
+
ctx.fillText(formatNumber(this.likes), padding + 65, reactionsY + reactionsHeight / 2);
|
|
534
|
+
|
|
535
|
+
// Contadores de comentários e compartilhamentos
|
|
536
|
+
ctx.textAlign = "right";
|
|
537
|
+
|
|
538
|
+
let counterText = "";
|
|
539
|
+
if (this.comments > 0 && this.shares > 0) {
|
|
540
|
+
counterText = `${formatNumber(this.comments)} comentários • ${formatNumber(this.shares)} compartilhamentos`;
|
|
541
|
+
} else if (this.comments > 0) {
|
|
542
|
+
counterText = `${formatNumber(this.comments)} comentários`;
|
|
543
|
+
} else if (this.shares > 0) {
|
|
544
|
+
counterText = `${formatNumber(this.shares)} compartilhamentos`;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (counterText) {
|
|
548
|
+
ctx.fillText(counterText, cardWidth - padding, reactionsY + reactionsHeight / 2);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
contentY = reactionsY + reactionsHeight + padding;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// --- Desenha Barra de Interações ---
|
|
555
|
+
const interactionBarY = contentY;
|
|
556
|
+
const interactionBarHeight = 50;
|
|
557
|
+
|
|
558
|
+
// Linha separadora
|
|
559
|
+
ctx.strokeStyle = colors.separator;
|
|
560
|
+
ctx.lineWidth = 1;
|
|
561
|
+
ctx.beginPath();
|
|
562
|
+
ctx.moveTo(padding, interactionBarY);
|
|
563
|
+
ctx.lineTo(cardWidth - padding, interactionBarY);
|
|
564
|
+
ctx.stroke();
|
|
565
|
+
|
|
566
|
+
// Ícones de interação
|
|
567
|
+
const iconSpacing = (cardWidth - padding * 2) / 3;
|
|
568
|
+
const iconY = interactionBarY + interactionBarHeight / 2;
|
|
569
|
+
|
|
570
|
+
// Ícone de curtida
|
|
571
|
+
ctx.fillStyle = colors.textSecondary;
|
|
572
|
+
ctx.font = `regular 14px ${registeredFontName}-Regular`;
|
|
573
|
+
ctx.textAlign = "center";
|
|
574
|
+
ctx.textBaseline = "middle";
|
|
575
|
+
|
|
576
|
+
ctx.fillText("👍 Curtir", padding + iconSpacing / 2, iconY);
|
|
577
|
+
|
|
578
|
+
// Ícone de comentário
|
|
579
|
+
ctx.fillText("💬 Comentar", padding + iconSpacing * 1.5, iconY);
|
|
580
|
+
|
|
581
|
+
// Ícone de compartilhamento
|
|
582
|
+
ctx.fillText("↗ Compartilhar", padding + iconSpacing * 2.5, iconY);
|
|
583
|
+
|
|
584
|
+
// --- Desenha Caixa de Comentário ---
|
|
585
|
+
const commentBoxY = interactionBarY + interactionBarHeight + padding;
|
|
586
|
+
const commentBoxHeight = 60;
|
|
587
|
+
|
|
588
|
+
if (commentBoxY + commentBoxHeight <= cardHeight - padding) {
|
|
589
|
+
// Linha separadora
|
|
590
|
+
ctx.strokeStyle = colors.separator;
|
|
591
|
+
ctx.lineWidth = 1;
|
|
592
|
+
ctx.beginPath();
|
|
593
|
+
ctx.moveTo(padding, commentBoxY);
|
|
594
|
+
ctx.lineTo(cardWidth - padding, commentBoxY);
|
|
595
|
+
ctx.stroke();
|
|
596
|
+
|
|
597
|
+
// Avatar do usuário
|
|
598
|
+
const commentAvatarSize = 40;
|
|
599
|
+
const commentAvatarX = padding;
|
|
600
|
+
const commentAvatarY = commentBoxY + padding;
|
|
601
|
+
|
|
602
|
+
ctx.save();
|
|
603
|
+
ctx.beginPath();
|
|
604
|
+
ctx.arc(commentAvatarX + commentAvatarSize / 2, commentAvatarY + commentAvatarSize / 2, commentAvatarSize / 2, 0, Math.PI * 2);
|
|
605
|
+
ctx.closePath();
|
|
606
|
+
ctx.clip();
|
|
607
|
+
|
|
608
|
+
try {
|
|
609
|
+
const avatarImg = await loadImageWithAxios(this.avatar || path.join(__dirname, "../assets/placeholders/avatar.png"));
|
|
610
|
+
ctx.drawImage(avatarImg, commentAvatarX, commentAvatarY, commentAvatarSize, commentAvatarSize);
|
|
611
|
+
} catch (e) {
|
|
612
|
+
console.error("Falha ao desenhar avatar de comentário:", e.message);
|
|
613
|
+
|
|
614
|
+
// Avatar de fallback
|
|
615
|
+
ctx.fillStyle = "#1877F2";
|
|
616
|
+
ctx.fillRect(commentAvatarX, commentAvatarY, commentAvatarSize, commentAvatarSize);
|
|
617
|
+
|
|
618
|
+
ctx.fillStyle = "#FFFFFF";
|
|
619
|
+
ctx.font = `bold ${commentAvatarSize / 3}px ${registeredFontName}-Bold`;
|
|
620
|
+
ctx.textAlign = "center";
|
|
621
|
+
ctx.textBaseline = "middle";
|
|
622
|
+
ctx.fillText(this.name.charAt(0).toUpperCase(), commentAvatarX + commentAvatarSize / 2, commentAvatarY + commentAvatarSize / 2);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
ctx.restore();
|
|
626
|
+
|
|
627
|
+
// Caixa de comentário
|
|
628
|
+
const commentBoxWidth = cardWidth - padding * 2 - commentAvatarSize - 10;
|
|
629
|
+
const commentBoxX = commentAvatarX + commentAvatarSize + 10;
|
|
630
|
+
|
|
631
|
+
ctx.fillStyle = colors.commentBackground;
|
|
632
|
+
roundRect(ctx, commentBoxX, commentAvatarY, commentBoxWidth, commentAvatarSize, commentAvatarSize / 2, true, false);
|
|
633
|
+
|
|
634
|
+
ctx.fillStyle = colors.textSecondary;
|
|
635
|
+
ctx.font = `regular 14px ${registeredFontName}-Regular`;
|
|
636
|
+
ctx.textAlign = "left";
|
|
637
|
+
ctx.textBaseline = "middle";
|
|
638
|
+
ctx.fillText("Escreva um comentário...", commentBoxX + 15, commentAvatarY + commentAvatarSize / 2);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// --- Codifica e Retorna Buffer ---
|
|
642
|
+
try {
|
|
643
|
+
return await encodeToBuffer(canvas);
|
|
644
|
+
} catch (err) {
|
|
645
|
+
console.error("Falha ao codificar o Post do Facebook:", err);
|
|
646
|
+
throw new Error("Não foi possível gerar o buffer de imagem do Post do Facebook.");
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// --- Métodos Auxiliares Privados ---
|
|
651
|
+
/**
|
|
652
|
+
* Obtém as cores com base no tema selecionado
|
|
653
|
+
* @private
|
|
654
|
+
*/
|
|
655
|
+
_getThemeColors() {
|
|
656
|
+
switch (this.theme) {
|
|
657
|
+
case "dark":
|
|
658
|
+
return {
|
|
659
|
+
background: "#242526",
|
|
660
|
+
text: "#E4E6EB",
|
|
661
|
+
textSecondary: "#B0B3B8",
|
|
662
|
+
separator: "#3E4042",
|
|
663
|
+
reactionBackground: "#3A3B3C",
|
|
664
|
+
commentBackground: "#3A3B3C"
|
|
665
|
+
};
|
|
666
|
+
case "light":
|
|
667
|
+
default:
|
|
668
|
+
return {
|
|
669
|
+
background: "#FFFFFF",
|
|
670
|
+
text: "#050505",
|
|
671
|
+
textSecondary: "#65676B",
|
|
672
|
+
separator: "#CED0D4",
|
|
673
|
+
reactionBackground: "#E4E6EB",
|
|
674
|
+
commentBackground: "#F0F2F5"
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
|