almtools 2.0.0 → 2.0.1

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/tools/qc.js DELETED
@@ -1,325 +0,0 @@
1
- const { createCanvas, loadImage } = require('canvas');
2
- const fs = require('fs');
3
- const axios = require('axios');
4
-
5
- async function loadImageSafe(source) {
6
- if (!source) return null;
7
- try {
8
- if (source.startsWith("http")) {
9
- const response = await axios.get(source, { responseType: 'arraybuffer' });
10
- return await loadImage(response.data);
11
- } else {
12
- return await loadImage(source);
13
- }
14
- } catch (e) {
15
- console.warn("Failed to load image:", source);
16
- return null;
17
- }
18
- }
19
-
20
- function wrapText(ctx, text, maxWidth) {
21
- const words = text.split(' ');
22
- const lines = [];
23
- let currentLine = words[0];
24
-
25
- for (let i = 1; i < words.length; i++) {
26
- const word = words[i];
27
- const width = ctx.measureText(currentLine + " " + word).width;
28
- if (width < maxWidth) {
29
- currentLine += " " + word;
30
- } else {
31
- lines.push(currentLine);
32
- currentLine = word;
33
- }
34
- }
35
- lines.push(currentLine);
36
- return lines;
37
- }
38
-
39
- function drawRoundedRect(ctx, x, y, width, height, radius) {
40
- ctx.beginPath();
41
- ctx.moveTo(x + radius, y);
42
- ctx.lineTo(x + width - radius, y);
43
- ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
44
- ctx.lineTo(x + width, y + height - radius);
45
- ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
46
- ctx.lineTo(x + radius, y + height);
47
- ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
48
- ctx.lineTo(x, y + radius);
49
- ctx.quadraticCurveTo(x, y, x + radius, y);
50
- ctx.closePath();
51
- }
52
-
53
-
54
- function drawTopLeftBubbleTail(ctx, x, y, size, mode) {
55
- ctx.beginPath();
56
- ctx.moveTo(x, y);
57
- ctx.lineTo(x - size, y - size / 3);
58
- ctx.lineTo(x, y - size);
59
- ctx.closePath();
60
- ctx.fillStyle = mode === "bright" ? '#F0F0F0' : '#2D2D30';
61
- ctx.fill();
62
- ctx.strokeStyle = 'transparent';
63
- ctx.stroke();
64
- }
65
-
66
- async function generateQC({ name, color, text, background, profilePicture, quoted, mode = "bright" }) {
67
- mode = mode == "dark" ? "dark" : "bright";
68
- const size = 512;
69
- const canvas = createCanvas(size, size);
70
- const ctx = canvas.getContext('2d');
71
-
72
- ctx.imageSmoothingEnabled = true;
73
- ctx.imageSmoothingQuality = 'high';
74
-
75
- const colorScheme = mode === "bright" ? {
76
- defaultBg: '#F5F5F5',
77
- overlayBg: 'rgba(255, 255, 255, 0.05)',
78
- bubbleGradientStart: '#FFFFFF',
79
- bubbleGradientEnd: '#F0F0F0',
80
- bubbleBorder: 'rgba(0, 0, 0, 0.1)',
81
- nameColor: color ? color : getRandomColor(),
82
- mainTextColor: '#000000',
83
- quotedBg: '#E8E8E8',
84
- quotedBorder: 'rgba(245, 244, 242, 1)',
85
- quotedNameColor: quoted?.color ? quoted.color : getRandomColor(),
86
- quotedTextColor: '#2E2E2E',
87
- timeColor: 'rgba(46, 46, 46, 1)',
88
- shadowColor: 'rgba(0, 0, 0, 0.15)'
89
- } : {
90
- defaultBg: '#0F0F23',
91
- overlayBg: 'rgba(0, 0, 0, 0.05)',
92
- bubbleGradientStart: '#3A3A3D',
93
- bubbleGradientEnd: '#2D2D30',
94
- bubbleBorder: 'rgba(255, 255, 255, 0.1)',
95
- nameColor: color ? color : getRandomColor(),
96
- mainTextColor: '#FFFFFF',
97
- quotedBg: '#1E1E1E',
98
- quotedBorder: 'rgba(255, 255, 255, 0.1)',
99
- quotedNameColor: quoted?.color ? quoted.color : getRandomColor(),
100
- quotedTextColor: '#CCCCCC',
101
- timeColor: 'rgba(255, 255, 255, 0.6)',
102
- shadowColor: 'rgba(0, 0, 0, 0.3)'
103
- };
104
-
105
-
106
- if (background.startsWith("#")) {
107
- ctx.fillStyle = background;
108
- ctx.fillRect(0, 0, size, size);
109
- } else {
110
- const bgImg = await loadImageSafe(background?.startsWith("http") ? background : mode == 'bright' ? "https://files.catbox.moe/srjg8h.jpg" : "https://files.catbox.moe/nq9pfq.jpg");
111
- if (bgImg) {
112
- ctx.drawImage(bgImg, 0, 0, size, size);
113
- ctx.fillStyle = colorScheme.overlayBg;
114
- ctx.fillRect(0, 0, size, size);
115
- }
116
- }
117
-
118
-
119
- const maxBubbleWidth = size - 120;
120
- const minBubbleWidth = 200;
121
- const paddingX = 25;
122
- const paddingY = 16;
123
- const quotedPaddingX = 25;
124
- const quotedPaddingY = 14;
125
- const quotedBorderWidth = 3;
126
-
127
-
128
- ctx.font = 'bold 28px Roboto';
129
- const nameWidth = ctx.measureText(name).width;
130
- const nameHeight = 46;
131
-
132
-
133
- ctx.font = '26px Roboto';
134
- const maxMainTextWidth = maxBubbleWidth - (paddingX * 2);
135
- const textLines = wrapText(ctx, text, maxMainTextWidth);
136
- const textHeight = 44;
137
- const lineSpacing = 10;
138
- const totalTextHeight = (textLines.length * textHeight) + ((textLines.length - 1) * lineSpacing);
139
-
140
-
141
- let quotedHeight = 0;
142
- let quotedLines = [];
143
- let quotedTextWidth = 0;
144
- let quotedNameWidth = 0;
145
-
146
- if (quoted) {
147
- ctx.font = 'bold 20px Roboto';
148
- quotedNameWidth = ctx.measureText(quoted.name).width;
149
-
150
- const maxQuotedContentWidth = maxBubbleWidth - (paddingX * 2) - (quotedPaddingX * 2) - quotedBorderWidth - 20;
151
-
152
- if (quoted.message.text) {
153
- ctx.font = '18px Roboto';
154
- quotedLines = wrapText(ctx, quoted.message.text, maxQuotedContentWidth);
155
- quotedTextWidth = Math.max(...quotedLines.map(line => ctx.measureText(line).width));
156
- }
157
-
158
- const singleQuotedLineHeight = 26;
159
- const quotedTextTotalHeight = quotedLines.length * singleQuotedLineHeight;
160
- quotedHeight = quotedPaddingY * 2 + 34 + quotedTextTotalHeight + (quotedLines.length > 1 ? (quotedLines.length - 1) * 6 : 0);
161
- }
162
-
163
-
164
- const contentWidths = [
165
- nameWidth,
166
- ...textLines.map(line => {
167
- ctx.font = '26px Roboto';
168
- return ctx.measureText(line).width;
169
- })
170
- ];
171
-
172
- if (quoted) {
173
- const quotedContentWidth = Math.max(quotedNameWidth, quotedTextWidth) + quotedPaddingX * 2 + quotedBorderWidth + 10;
174
- contentWidths.push(quotedContentWidth);
175
- }
176
-
177
- const maxContentWidth = Math.max(...contentWidths);
178
- const bubbleWidth = Math.max(minBubbleWidth, Math.min(maxContentWidth + paddingX * 2, maxBubbleWidth));
179
- const bubbleHeight = nameHeight + totalTextHeight + paddingY * 2 + 40 + quotedHeight + (quoted ? 16 : 0);
180
-
181
- const minBubbleHeight = 120;
182
- const finalBubbleHeight = Math.max(bubbleHeight, minBubbleHeight);
183
-
184
-
185
- const bubbleX = (size * 0.6) - (bubbleWidth / 2);
186
- const bubbleY = (size - finalBubbleHeight) / 2 + 20;
187
-
188
-
189
- const pfpImg = await loadImageSafe(profilePicture);
190
- const pfpSize = Math.min(Math.max(finalBubbleHeight * 0.6, 80), 110);
191
- const pfpBorderSize = Math.max(4, Math.round(pfpSize * 0.045));
192
- const pfpX = bubbleX - pfpSize - 18;
193
- const pfpY = bubbleY;
194
-
195
- if (pfpImg) {
196
- ctx.save();
197
- ctx.shadowColor = colorScheme.shadowColor;
198
- ctx.shadowBlur = 10;
199
- ctx.shadowOffsetY = 3;
200
-
201
- ctx.fillStyle = mode === "bright" ? '#FFFFFF' : '#FFFFFF';
202
- ctx.beginPath();
203
- ctx.arc(pfpX + pfpSize / 2, pfpY + pfpSize / 2, (pfpSize / 2) + pfpBorderSize, 0, Math.PI * 2);
204
- ctx.fill();
205
-
206
- ctx.beginPath();
207
- ctx.arc(pfpX + pfpSize / 2, pfpY + pfpSize / 2, pfpSize / 2, 0, Math.PI * 2);
208
- ctx.closePath();
209
- ctx.clip();
210
- ctx.drawImage(pfpImg, pfpX, pfpY, pfpSize, pfpSize);
211
- ctx.restore();
212
- }
213
-
214
-
215
- ctx.save();
216
- ctx.shadowColor = colorScheme.shadowColor;
217
- ctx.shadowBlur = 18;
218
- ctx.shadowOffsetY = 4;
219
-
220
- const gradient = ctx.createLinearGradient(bubbleX, bubbleY, bubbleX, bubbleY + finalBubbleHeight);
221
- gradient.addColorStop(0, colorScheme.bubbleGradientStart);
222
- gradient.addColorStop(1, colorScheme.bubbleGradientEnd);
223
-
224
- ctx.fillStyle = gradient;
225
- drawRoundedRect(ctx, bubbleX, bubbleY, bubbleWidth, finalBubbleHeight, 20);
226
- ctx.fill();
227
-
228
-
229
- const tailSize = 14;
230
- const tailX = bubbleX;
231
- const tailY = bubbleY + 18;
232
-
233
- drawTopLeftBubbleTail(ctx, tailX, tailY, tailSize, mode);
234
-
235
- ctx.restore();
236
-
237
- ctx.strokeStyle = colorScheme.bubbleBorder;
238
- ctx.lineWidth = 1;
239
- drawRoundedRect(ctx, bubbleX, bubbleY, bubbleWidth, finalBubbleHeight, 20);
240
- ctx.stroke();
241
-
242
- let currentContentY = bubbleY + 32;
243
-
244
-
245
- ctx.font = 'bold 28px Roboto';
246
- ctx.fillStyle = colorScheme.nameColor;
247
- ctx.fillText(name, bubbleX + paddingX, currentContentY);
248
- currentContentY += 50;
249
-
250
-
251
- if (quoted) {
252
- const quotedY = currentContentY;
253
- const quotedX = bubbleX + paddingX;
254
-
255
- const maxQuotedBgWidth = bubbleWidth - (paddingX * 2);
256
- const quotedBgWidth = Math.min(
257
- Math.max(quotedNameWidth, quotedTextWidth) + quotedPaddingX * 2 + quotedBorderWidth + 10,
258
- maxQuotedBgWidth
259
- );
260
-
261
- ctx.fillStyle = colorScheme.quotedBg;
262
- drawRoundedRect(ctx, quotedX, quotedY - 10, quotedBgWidth, quotedHeight, 10);
263
- ctx.fill();
264
-
265
- ctx.strokeStyle = colorScheme.quotedBorder;
266
- ctx.lineWidth = 1;
267
- drawRoundedRect(ctx, quotedX, quotedY - 10, quotedBgWidth, quotedHeight, 10);
268
- ctx.stroke();
269
-
270
- ctx.fillStyle = colorScheme.quotedNameColor;
271
- ctx.fillRect(quotedX + 10, quotedY - 6, 4, quotedHeight - 8);
272
-
273
- ctx.font = 'bold 20px Roboto';
274
- ctx.fillStyle = colorScheme.quotedNameColor;
275
- ctx.fillText(quoted.name, quotedX + quotedPaddingX, quotedY + quotedPaddingY + 10);
276
-
277
- if (quoted.message.text) {
278
- ctx.font = '18px Roboto';
279
- ctx.fillStyle = colorScheme.quotedTextColor;
280
-
281
- ctx.save();
282
- drawRoundedRect(ctx, quotedX + quotedBorderWidth + 6, quotedY - 10, quotedBgWidth - quotedBorderWidth - 12, quotedHeight, 10);
283
- ctx.clip();
284
-
285
- quotedLines.forEach((line, index) => {
286
- ctx.fillText(line, quotedX + quotedPaddingX, quotedY + quotedPaddingY + 36 + (index * 26));
287
- });
288
-
289
- ctx.restore();
290
- }
291
-
292
- currentContentY += quotedHeight + 16;
293
- }
294
-
295
-
296
- ctx.font = '26px Roboto';
297
- ctx.fillStyle = colorScheme.mainTextColor;
298
- textLines.forEach((line, index) => {
299
- ctx.fillText(line, bubbleX + paddingX, currentContentY + 5 + (index * (textHeight + lineSpacing)));
300
- });
301
-
302
-
303
- const now = new Date();
304
- const hours = now.getHours().toString().padStart(2, '0');
305
- const minutes = now.getMinutes().toString().padStart(2, '0');
306
- const time = `${hours}.${minutes}`;
307
- ctx.font = '22px Roboto';
308
- ctx.fillStyle = colorScheme.timeColor;
309
- const timeWidth = ctx.measureText(time).width;
310
- ctx.fillText(time, bubbleX + bubbleWidth - timeWidth - paddingX, bubbleY + finalBubbleHeight - 18);
311
-
312
- const buffer = canvas.toBuffer('image/png');
313
- return buffer;
314
- }
315
-
316
- module.exports = generateQC;
317
-
318
- function getRandomColor() {
319
- const letters = '0123456789ABCDEF';
320
- let color = '#';
321
- for (let i = 0; i < 6; i++) {
322
- color += letters[Math.floor(Math.random() * 16)];
323
- }
324
- return color;
325
- }