VinOS Agent commited on
Commit
7236c19
·
1 Parent(s): 57e413d

feat: Automate SVG Instagram Carousels

Browse files
ai-carousel.js ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const apiCaller = require('./skills/api_caller');
2
+
3
+ class AICarousel {
4
+ async generateContent(topic) {
5
+ const prompt = `You are a viral Instagram carousel copywriter.
6
+ Topic: "${topic}"
7
+
8
+ Provide a 5 to 7 slide educational carousel.
9
+ Output ONLY a valid JSON array matching this strict schema:
10
+ [
11
+ { "slide": 1, "type": "hook", "title": "Main bold hook (max 8 words)", "subtitle": "A sub-hook or pain point (max 12 words)" },
12
+ { "slide": 2, "type": "value", "title": "Point 1", "subtitle": "Detailed, actionable explanation (max 15 words)" },
13
+ { "slide": 3, "type": "value", "title": "Point 2", "subtitle": "Detailed, actionable explanation (max 15 words)" },
14
+ { "slide": 4, "type": "cta", "title": "Save this post!", "subtitle": "Follow for more actionable insights." }
15
+ ]
16
+ Keep the text extremely concise and punchy for Instagram slides.
17
+ DO NOT WRITE MARKDOWN. OUTPUT RAW VALID JSON ONLY.`;
18
+
19
+ // Use premium model for the heavy copywriting
20
+ const res = await apiCaller.callOpenRouter([{ role: 'user', content: prompt }], 'meta-llama/llama-3.3-70b-instruct:free');
21
+
22
+ if (!res.success) return { success: false, error: res.error };
23
+
24
+ try {
25
+ const raw = res.data.replace(/```json|```/gi, '').trim();
26
+ const slides = JSON.parse(raw);
27
+ return { success: true, slides };
28
+ } catch (e) {
29
+ console.error('[AICarousel] JSON Parse Error:', e.message, '\nRaw Data:', res.data);
30
+ return { success: false, error: 'Failed to parse AI output: ' + e.message };
31
+ }
32
+ }
33
+ }
34
+
35
+ module.exports = new AICarousel();
carousel-gen.js ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const sharp = require('sharp');
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+
5
+ class CarouselGen {
6
+ constructor() {
7
+ this.outputDir = path.join(__dirname, 'public', 'tmp');
8
+ if (!fs.existsSync(this.outputDir)) {
9
+ fs.mkdirSync(this.outputDir, { recursive: true });
10
+ }
11
+ }
12
+
13
+ wrapText(text, maxChars) {
14
+ if (!text) return [];
15
+ const words = text.split(' ');
16
+ const lines = [];
17
+ let currentLine = '';
18
+
19
+ words.forEach(word => {
20
+ if ((currentLine + word).length > maxChars) {
21
+ if (currentLine) lines.push(currentLine.trim());
22
+ currentLine = word + ' ';
23
+ } else {
24
+ currentLine += word + ' ';
25
+ }
26
+ });
27
+ if (currentLine) lines.push(currentLine.trim());
28
+ return lines;
29
+ }
30
+
31
+ escapeXml(unsafe) {
32
+ if (!unsafe) return '';
33
+ return unsafe.replace(/[<>&'"]/g, (c) => {
34
+ switch (c) {
35
+ case '<': return '&lt;';
36
+ case '>': return '&gt;';
37
+ case '&': return '&amp;';
38
+ case '\'': return '&apos;';
39
+ case '"': return '&quot;';
40
+ }
41
+ });
42
+ }
43
+
44
+ async generateSlides(slides, topicId) {
45
+ const slideUrls = [];
46
+
47
+ for (let i = 0; i < slides.length; i++) {
48
+ const slide = slides[i];
49
+
50
+ // Text formatting
51
+ const isHook = slide.type === 'hook';
52
+ const titleFontSize = isHook ? 75 : 60;
53
+ const subFontSize = isHook ? 40 : 36;
54
+
55
+ const maxTitleChars = isHook ? 22 : 28;
56
+ const maxSubChars = isHook ? 40 : 45;
57
+
58
+ const titleLines = this.wrapText(this.escapeXml(slide.title), maxTitleChars);
59
+ const subLines = this.wrapText(this.escapeXml(slide.subtitle), maxSubChars);
60
+
61
+ // Generate Y-coordinates to push text down based on line counts
62
+ let currentY = 400;
63
+ let titleTspans = titleLines.map(line => {
64
+ const span = `<tspan x="100" y="${currentY}">${line}</tspan>`;
65
+ currentY += (titleFontSize * 1.3);
66
+ return span;
67
+ }).join('\n');
68
+
69
+ currentY += (isHook ? 80 : 60); // Gap between title and sub
70
+
71
+ let subTspans = subLines.map(line => {
72
+ const span = `<tspan x="100" y="${currentY}">${line}</tspan>`;
73
+ currentY += (subFontSize * 1.4);
74
+ return span;
75
+ }).join('\n');
76
+
77
+ const slideTypeLabel = this.escapeXml((slide.type || 'slide').toUpperCase());
78
+ const footerLabel = 'VinOS Autopilot';
79
+ const pageCounter = `${i + 1} / ${slides.length}`;
80
+
81
+ // Create Raw SVG structure utilizing pure vector shapes
82
+ const svgContent = `
83
+ <svg width="1080" height="1350" viewBox="0 0 1080 1350" xmlns="http://www.w3.org/2000/svg">
84
+ <!-- Background Layer -->
85
+ <rect width="1080" height="1350" fill="#0B0E14" />
86
+
87
+ <!-- Ambient Glow Orbs -->
88
+ <circle cx="850" cy="-50" r="400" fill="#E13B6B" opacity="0.15" filter="blur(150px)" />
89
+ <circle cx="-100" cy="1200" r="500" fill="#205CF6" opacity="0.12" filter="blur(180px)" />
90
+
91
+ <!-- Header Elements -->
92
+ <text x="100" y="180" font-family="Arial, sans-serif" font-size="28" fill="#7C88A8" font-weight="bold" letter-spacing="3">
93
+ ${slideTypeLabel}
94
+ </text>
95
+ <text x="980" y="180" font-family="Arial, sans-serif" font-size="28" fill="#7C88A8" font-weight="bold" text-anchor="end">
96
+ ${pageCounter}
97
+ </text>
98
+
99
+ <!-- Title Block -->
100
+ <text font-family="Arial, sans-serif" font-size="${titleFontSize}" fill="#FFFFFF" font-weight="900" letter-spacing="0">
101
+ ${titleTspans}
102
+ </text>
103
+
104
+ <!-- Subtitle Block -->
105
+ <text font-family="Arial, sans-serif" font-size="${subFontSize}" fill="#A0ABCB" font-weight="400">
106
+ ${subTspans}
107
+ </text>
108
+
109
+ <!-- Footer Bar -->
110
+ <rect x="100" y="1200" width="880" height="2" fill="#202534" />
111
+ <text x="100" y="1260" font-family="Arial, sans-serif" font-size="24" fill="#E13B6B" font-weight="bold" letter-spacing="2">
112
+ ${footerLabel.toUpperCase()}
113
+ </text>
114
+ <text x="980" y="1260" font-family="Arial, sans-serif" font-size="24" fill="#7C88A8" text-anchor="end">
115
+ ${i === slides.length - 1 ? 'Save & Share' : 'Swipe ➡'}
116
+ </text>
117
+ </svg>`;
118
+
119
+ // Compile SVG via Sharp
120
+ const filename = `carousel_${topicId}_${i}.jpg`;
121
+ const outPath = path.join(this.outputDir, filename);
122
+
123
+ await sharp(Buffer.from(svgContent))
124
+ .jpeg({ quality: 90 })
125
+ .toFile(outPath);
126
+
127
+ // For now, return absolute file paths.
128
+ // In a real flow, these will be uploaded immediately.
129
+ slideUrls.push(outPath);
130
+ }
131
+
132
+ return slideUrls;
133
+ }
134
+ }
135
+
136
+ module.exports = new CarouselGen();
package-lock.json CHANGED
@@ -19,7 +19,18 @@
19
  "form-data": "^4.0.5",
20
  "googleapis": "^129.0.0",
21
  "node-cron": "^3.0.3",
22
- "pdfkit": "^0.18.0"
 
 
 
 
 
 
 
 
 
 
 
23
  }
24
  },
25
  "node_modules/@heyputer/kv.js": {
@@ -84,6 +95,471 @@
84
  "integrity": "sha512-nfV9luJbvwGQ/5oKXkKhCV9h4X7mwh1YaGG3ORd6UMLDSwr1OFSSatcBX0O9OtBtmNK19aGSjbLFqqgcIR6+IA==",
85
  "license": "MIT"
86
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  "node_modules/@noble/ciphers": {
88
  "version": "1.3.0",
89
  "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz",
@@ -466,6 +942,15 @@
466
  "npm": "1.2.8000 || >= 1.4.16"
467
  }
468
  },
 
 
 
 
 
 
 
 
 
469
  "node_modules/dfa": {
470
  "version": "1.2.0",
471
  "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz",
@@ -1475,6 +1960,18 @@
1475
  "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
1476
  "license": "MIT"
1477
  },
 
 
 
 
 
 
 
 
 
 
 
 
1478
  "node_modules/send": {
1479
  "version": "0.19.2",
1480
  "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
@@ -1526,6 +2023,50 @@
1526
  "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
1527
  "license": "ISC"
1528
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1529
  "node_modules/side-channel": {
1530
  "version": "1.1.0",
1531
  "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
 
19
  "form-data": "^4.0.5",
20
  "googleapis": "^129.0.0",
21
  "node-cron": "^3.0.3",
22
+ "pdfkit": "^0.18.0",
23
+ "sharp": "^0.34.5"
24
+ }
25
+ },
26
+ "node_modules/@emnapi/runtime": {
27
+ "version": "1.9.1",
28
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz",
29
+ "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==",
30
+ "license": "MIT",
31
+ "optional": true,
32
+ "dependencies": {
33
+ "tslib": "^2.4.0"
34
  }
35
  },
36
  "node_modules/@heyputer/kv.js": {
 
95
  "integrity": "sha512-nfV9luJbvwGQ/5oKXkKhCV9h4X7mwh1YaGG3ORd6UMLDSwr1OFSSatcBX0O9OtBtmNK19aGSjbLFqqgcIR6+IA==",
96
  "license": "MIT"
97
  },
98
+ "node_modules/@img/colour": {
99
+ "version": "1.1.0",
100
+ "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
101
+ "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
102
+ "license": "MIT",
103
+ "engines": {
104
+ "node": ">=18"
105
+ }
106
+ },
107
+ "node_modules/@img/sharp-darwin-arm64": {
108
+ "version": "0.34.5",
109
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
110
+ "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
111
+ "cpu": [
112
+ "arm64"
113
+ ],
114
+ "license": "Apache-2.0",
115
+ "optional": true,
116
+ "os": [
117
+ "darwin"
118
+ ],
119
+ "engines": {
120
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
121
+ },
122
+ "funding": {
123
+ "url": "https://opencollective.com/libvips"
124
+ },
125
+ "optionalDependencies": {
126
+ "@img/sharp-libvips-darwin-arm64": "1.2.4"
127
+ }
128
+ },
129
+ "node_modules/@img/sharp-darwin-x64": {
130
+ "version": "0.34.5",
131
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
132
+ "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
133
+ "cpu": [
134
+ "x64"
135
+ ],
136
+ "license": "Apache-2.0",
137
+ "optional": true,
138
+ "os": [
139
+ "darwin"
140
+ ],
141
+ "engines": {
142
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
143
+ },
144
+ "funding": {
145
+ "url": "https://opencollective.com/libvips"
146
+ },
147
+ "optionalDependencies": {
148
+ "@img/sharp-libvips-darwin-x64": "1.2.4"
149
+ }
150
+ },
151
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
152
+ "version": "1.2.4",
153
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
154
+ "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
155
+ "cpu": [
156
+ "arm64"
157
+ ],
158
+ "license": "LGPL-3.0-or-later",
159
+ "optional": true,
160
+ "os": [
161
+ "darwin"
162
+ ],
163
+ "funding": {
164
+ "url": "https://opencollective.com/libvips"
165
+ }
166
+ },
167
+ "node_modules/@img/sharp-libvips-darwin-x64": {
168
+ "version": "1.2.4",
169
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
170
+ "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
171
+ "cpu": [
172
+ "x64"
173
+ ],
174
+ "license": "LGPL-3.0-or-later",
175
+ "optional": true,
176
+ "os": [
177
+ "darwin"
178
+ ],
179
+ "funding": {
180
+ "url": "https://opencollective.com/libvips"
181
+ }
182
+ },
183
+ "node_modules/@img/sharp-libvips-linux-arm": {
184
+ "version": "1.2.4",
185
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
186
+ "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
187
+ "cpu": [
188
+ "arm"
189
+ ],
190
+ "license": "LGPL-3.0-or-later",
191
+ "optional": true,
192
+ "os": [
193
+ "linux"
194
+ ],
195
+ "funding": {
196
+ "url": "https://opencollective.com/libvips"
197
+ }
198
+ },
199
+ "node_modules/@img/sharp-libvips-linux-arm64": {
200
+ "version": "1.2.4",
201
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
202
+ "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
203
+ "cpu": [
204
+ "arm64"
205
+ ],
206
+ "license": "LGPL-3.0-or-later",
207
+ "optional": true,
208
+ "os": [
209
+ "linux"
210
+ ],
211
+ "funding": {
212
+ "url": "https://opencollective.com/libvips"
213
+ }
214
+ },
215
+ "node_modules/@img/sharp-libvips-linux-ppc64": {
216
+ "version": "1.2.4",
217
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
218
+ "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
219
+ "cpu": [
220
+ "ppc64"
221
+ ],
222
+ "license": "LGPL-3.0-or-later",
223
+ "optional": true,
224
+ "os": [
225
+ "linux"
226
+ ],
227
+ "funding": {
228
+ "url": "https://opencollective.com/libvips"
229
+ }
230
+ },
231
+ "node_modules/@img/sharp-libvips-linux-riscv64": {
232
+ "version": "1.2.4",
233
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
234
+ "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
235
+ "cpu": [
236
+ "riscv64"
237
+ ],
238
+ "license": "LGPL-3.0-or-later",
239
+ "optional": true,
240
+ "os": [
241
+ "linux"
242
+ ],
243
+ "funding": {
244
+ "url": "https://opencollective.com/libvips"
245
+ }
246
+ },
247
+ "node_modules/@img/sharp-libvips-linux-s390x": {
248
+ "version": "1.2.4",
249
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
250
+ "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
251
+ "cpu": [
252
+ "s390x"
253
+ ],
254
+ "license": "LGPL-3.0-or-later",
255
+ "optional": true,
256
+ "os": [
257
+ "linux"
258
+ ],
259
+ "funding": {
260
+ "url": "https://opencollective.com/libvips"
261
+ }
262
+ },
263
+ "node_modules/@img/sharp-libvips-linux-x64": {
264
+ "version": "1.2.4",
265
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
266
+ "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
267
+ "cpu": [
268
+ "x64"
269
+ ],
270
+ "license": "LGPL-3.0-or-later",
271
+ "optional": true,
272
+ "os": [
273
+ "linux"
274
+ ],
275
+ "funding": {
276
+ "url": "https://opencollective.com/libvips"
277
+ }
278
+ },
279
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
280
+ "version": "1.2.4",
281
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
282
+ "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
283
+ "cpu": [
284
+ "arm64"
285
+ ],
286
+ "license": "LGPL-3.0-or-later",
287
+ "optional": true,
288
+ "os": [
289
+ "linux"
290
+ ],
291
+ "funding": {
292
+ "url": "https://opencollective.com/libvips"
293
+ }
294
+ },
295
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
296
+ "version": "1.2.4",
297
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
298
+ "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
299
+ "cpu": [
300
+ "x64"
301
+ ],
302
+ "license": "LGPL-3.0-or-later",
303
+ "optional": true,
304
+ "os": [
305
+ "linux"
306
+ ],
307
+ "funding": {
308
+ "url": "https://opencollective.com/libvips"
309
+ }
310
+ },
311
+ "node_modules/@img/sharp-linux-arm": {
312
+ "version": "0.34.5",
313
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
314
+ "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
315
+ "cpu": [
316
+ "arm"
317
+ ],
318
+ "license": "Apache-2.0",
319
+ "optional": true,
320
+ "os": [
321
+ "linux"
322
+ ],
323
+ "engines": {
324
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
325
+ },
326
+ "funding": {
327
+ "url": "https://opencollective.com/libvips"
328
+ },
329
+ "optionalDependencies": {
330
+ "@img/sharp-libvips-linux-arm": "1.2.4"
331
+ }
332
+ },
333
+ "node_modules/@img/sharp-linux-arm64": {
334
+ "version": "0.34.5",
335
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
336
+ "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
337
+ "cpu": [
338
+ "arm64"
339
+ ],
340
+ "license": "Apache-2.0",
341
+ "optional": true,
342
+ "os": [
343
+ "linux"
344
+ ],
345
+ "engines": {
346
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
347
+ },
348
+ "funding": {
349
+ "url": "https://opencollective.com/libvips"
350
+ },
351
+ "optionalDependencies": {
352
+ "@img/sharp-libvips-linux-arm64": "1.2.4"
353
+ }
354
+ },
355
+ "node_modules/@img/sharp-linux-ppc64": {
356
+ "version": "0.34.5",
357
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
358
+ "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
359
+ "cpu": [
360
+ "ppc64"
361
+ ],
362
+ "license": "Apache-2.0",
363
+ "optional": true,
364
+ "os": [
365
+ "linux"
366
+ ],
367
+ "engines": {
368
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
369
+ },
370
+ "funding": {
371
+ "url": "https://opencollective.com/libvips"
372
+ },
373
+ "optionalDependencies": {
374
+ "@img/sharp-libvips-linux-ppc64": "1.2.4"
375
+ }
376
+ },
377
+ "node_modules/@img/sharp-linux-riscv64": {
378
+ "version": "0.34.5",
379
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
380
+ "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
381
+ "cpu": [
382
+ "riscv64"
383
+ ],
384
+ "license": "Apache-2.0",
385
+ "optional": true,
386
+ "os": [
387
+ "linux"
388
+ ],
389
+ "engines": {
390
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
391
+ },
392
+ "funding": {
393
+ "url": "https://opencollective.com/libvips"
394
+ },
395
+ "optionalDependencies": {
396
+ "@img/sharp-libvips-linux-riscv64": "1.2.4"
397
+ }
398
+ },
399
+ "node_modules/@img/sharp-linux-s390x": {
400
+ "version": "0.34.5",
401
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
402
+ "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
403
+ "cpu": [
404
+ "s390x"
405
+ ],
406
+ "license": "Apache-2.0",
407
+ "optional": true,
408
+ "os": [
409
+ "linux"
410
+ ],
411
+ "engines": {
412
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
413
+ },
414
+ "funding": {
415
+ "url": "https://opencollective.com/libvips"
416
+ },
417
+ "optionalDependencies": {
418
+ "@img/sharp-libvips-linux-s390x": "1.2.4"
419
+ }
420
+ },
421
+ "node_modules/@img/sharp-linux-x64": {
422
+ "version": "0.34.5",
423
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
424
+ "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
425
+ "cpu": [
426
+ "x64"
427
+ ],
428
+ "license": "Apache-2.0",
429
+ "optional": true,
430
+ "os": [
431
+ "linux"
432
+ ],
433
+ "engines": {
434
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
435
+ },
436
+ "funding": {
437
+ "url": "https://opencollective.com/libvips"
438
+ },
439
+ "optionalDependencies": {
440
+ "@img/sharp-libvips-linux-x64": "1.2.4"
441
+ }
442
+ },
443
+ "node_modules/@img/sharp-linuxmusl-arm64": {
444
+ "version": "0.34.5",
445
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
446
+ "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
447
+ "cpu": [
448
+ "arm64"
449
+ ],
450
+ "license": "Apache-2.0",
451
+ "optional": true,
452
+ "os": [
453
+ "linux"
454
+ ],
455
+ "engines": {
456
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
457
+ },
458
+ "funding": {
459
+ "url": "https://opencollective.com/libvips"
460
+ },
461
+ "optionalDependencies": {
462
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
463
+ }
464
+ },
465
+ "node_modules/@img/sharp-linuxmusl-x64": {
466
+ "version": "0.34.5",
467
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
468
+ "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
469
+ "cpu": [
470
+ "x64"
471
+ ],
472
+ "license": "Apache-2.0",
473
+ "optional": true,
474
+ "os": [
475
+ "linux"
476
+ ],
477
+ "engines": {
478
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
479
+ },
480
+ "funding": {
481
+ "url": "https://opencollective.com/libvips"
482
+ },
483
+ "optionalDependencies": {
484
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.4"
485
+ }
486
+ },
487
+ "node_modules/@img/sharp-wasm32": {
488
+ "version": "0.34.5",
489
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
490
+ "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
491
+ "cpu": [
492
+ "wasm32"
493
+ ],
494
+ "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
495
+ "optional": true,
496
+ "dependencies": {
497
+ "@emnapi/runtime": "^1.7.0"
498
+ },
499
+ "engines": {
500
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
501
+ },
502
+ "funding": {
503
+ "url": "https://opencollective.com/libvips"
504
+ }
505
+ },
506
+ "node_modules/@img/sharp-win32-arm64": {
507
+ "version": "0.34.5",
508
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
509
+ "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
510
+ "cpu": [
511
+ "arm64"
512
+ ],
513
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
514
+ "optional": true,
515
+ "os": [
516
+ "win32"
517
+ ],
518
+ "engines": {
519
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
520
+ },
521
+ "funding": {
522
+ "url": "https://opencollective.com/libvips"
523
+ }
524
+ },
525
+ "node_modules/@img/sharp-win32-ia32": {
526
+ "version": "0.34.5",
527
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
528
+ "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
529
+ "cpu": [
530
+ "ia32"
531
+ ],
532
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
533
+ "optional": true,
534
+ "os": [
535
+ "win32"
536
+ ],
537
+ "engines": {
538
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
539
+ },
540
+ "funding": {
541
+ "url": "https://opencollective.com/libvips"
542
+ }
543
+ },
544
+ "node_modules/@img/sharp-win32-x64": {
545
+ "version": "0.34.5",
546
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
547
+ "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
548
+ "cpu": [
549
+ "x64"
550
+ ],
551
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
552
+ "optional": true,
553
+ "os": [
554
+ "win32"
555
+ ],
556
+ "engines": {
557
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
558
+ },
559
+ "funding": {
560
+ "url": "https://opencollective.com/libvips"
561
+ }
562
+ },
563
  "node_modules/@noble/ciphers": {
564
  "version": "1.3.0",
565
  "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz",
 
942
  "npm": "1.2.8000 || >= 1.4.16"
943
  }
944
  },
945
+ "node_modules/detect-libc": {
946
+ "version": "2.1.2",
947
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
948
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
949
+ "license": "Apache-2.0",
950
+ "engines": {
951
+ "node": ">=8"
952
+ }
953
+ },
954
  "node_modules/dfa": {
955
  "version": "1.2.0",
956
  "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz",
 
1960
  "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
1961
  "license": "MIT"
1962
  },
1963
+ "node_modules/semver": {
1964
+ "version": "7.7.4",
1965
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
1966
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
1967
+ "license": "ISC",
1968
+ "bin": {
1969
+ "semver": "bin/semver.js"
1970
+ },
1971
+ "engines": {
1972
+ "node": ">=10"
1973
+ }
1974
+ },
1975
  "node_modules/send": {
1976
  "version": "0.19.2",
1977
  "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
 
2023
  "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
2024
  "license": "ISC"
2025
  },
2026
+ "node_modules/sharp": {
2027
+ "version": "0.34.5",
2028
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
2029
+ "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
2030
+ "hasInstallScript": true,
2031
+ "license": "Apache-2.0",
2032
+ "dependencies": {
2033
+ "@img/colour": "^1.0.0",
2034
+ "detect-libc": "^2.1.2",
2035
+ "semver": "^7.7.3"
2036
+ },
2037
+ "engines": {
2038
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
2039
+ },
2040
+ "funding": {
2041
+ "url": "https://opencollective.com/libvips"
2042
+ },
2043
+ "optionalDependencies": {
2044
+ "@img/sharp-darwin-arm64": "0.34.5",
2045
+ "@img/sharp-darwin-x64": "0.34.5",
2046
+ "@img/sharp-libvips-darwin-arm64": "1.2.4",
2047
+ "@img/sharp-libvips-darwin-x64": "1.2.4",
2048
+ "@img/sharp-libvips-linux-arm": "1.2.4",
2049
+ "@img/sharp-libvips-linux-arm64": "1.2.4",
2050
+ "@img/sharp-libvips-linux-ppc64": "1.2.4",
2051
+ "@img/sharp-libvips-linux-riscv64": "1.2.4",
2052
+ "@img/sharp-libvips-linux-s390x": "1.2.4",
2053
+ "@img/sharp-libvips-linux-x64": "1.2.4",
2054
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
2055
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.4",
2056
+ "@img/sharp-linux-arm": "0.34.5",
2057
+ "@img/sharp-linux-arm64": "0.34.5",
2058
+ "@img/sharp-linux-ppc64": "0.34.5",
2059
+ "@img/sharp-linux-riscv64": "0.34.5",
2060
+ "@img/sharp-linux-s390x": "0.34.5",
2061
+ "@img/sharp-linux-x64": "0.34.5",
2062
+ "@img/sharp-linuxmusl-arm64": "0.34.5",
2063
+ "@img/sharp-linuxmusl-x64": "0.34.5",
2064
+ "@img/sharp-wasm32": "0.34.5",
2065
+ "@img/sharp-win32-arm64": "0.34.5",
2066
+ "@img/sharp-win32-ia32": "0.34.5",
2067
+ "@img/sharp-win32-x64": "0.34.5"
2068
+ }
2069
+ },
2070
  "node_modules/side-channel": {
2071
  "version": "1.1.0",
2072
  "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
package.json CHANGED
@@ -18,6 +18,7 @@
18
  "form-data": "^4.0.5",
19
  "googleapis": "^129.0.0",
20
  "node-cron": "^3.0.3",
21
- "pdfkit": "^0.18.0"
 
22
  }
23
  }
 
18
  "form-data": "^4.0.5",
19
  "googleapis": "^129.0.0",
20
  "node-cron": "^3.0.3",
21
+ "pdfkit": "^0.18.0",
22
+ "sharp": "^0.34.5"
23
  }
24
  }
public/social-dashboard.html CHANGED
@@ -293,10 +293,17 @@
293
 
294
  <div class="post-draft-grid">
295
  <div id="preview-${post.id}">
296
- <div class="image-preview" style="background-image: url('${v1.mediaUrl}');"></div>
 
 
 
 
 
 
 
297
  <div style="margin-top: 10px; font-weight: bold;">Draft Caption:</div>
298
  <textarea id="text-${post.id}" class="variant-box" style="width: 100%; height: 120px; background: rgba(0,0,0,0.3); color: white; border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; padding: 10px; font-size: 13px;" ${isApproved ? 'readonly' : ''}>${v1.ig_id || v1.ig_en}</textarea>
299
- ${hasCarousel ? `<p style="font-size: 10px; color: #3b82f6; margin-top:5px;">+ ${v2.slides.length} Carousel Slides (Suggested in Calendar)</p>` : ''}
300
  </div>
301
  <div>
302
  <div style="font-weight: bold; margin-bottom: 5px;">Status: <span style="color: ${isApproved ? '#10b981' : 'var(--accent-pink)'};">${post.status.toUpperCase()}</span></div>
 
293
 
294
  <div class="post-draft-grid">
295
  <div id="preview-${post.id}">
296
+ ${post.type === 'carousel' && post.mediaUrls ? `
297
+ <div style="display: flex; gap: 10px; overflow-x: auto; padding-bottom: 15px; white-space: nowrap;">
298
+ ${post.mediaUrls.map(url => `<div class="image-preview" style="display: inline-block; flex: 0 0 auto; width: 140px; height: 175px; background-image: url('${url}'); background-size: cover; border-radius: 8px;"></div>`).join('')}
299
+ </div>
300
+ <p style="font-size: 11px; color: var(--accent-blue); margin-top:-5px; margin-bottom:10px;">${post.mediaUrls.length} Slides Carousel (Scroll ➡)</p>
301
+ ` : `
302
+ <div class="image-preview" style="background-image: url('${v1.mediaUrl}');"></div>
303
+ `}
304
  <div style="margin-top: 10px; font-weight: bold;">Draft Caption:</div>
305
  <textarea id="text-${post.id}" class="variant-box" style="width: 100%; height: 120px; background: rgba(0,0,0,0.3); color: white; border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; padding: 10px; font-size: 13px;" ${isApproved ? 'readonly' : ''}>${v1.ig_id || v1.ig_en}</textarea>
306
+ ${hasCarousel && post.type !== 'carousel' ? `<p style="font-size: 10px; color: #3b82f6; margin-top:5px;">+ ${v2.slides.length} Text Carousel Slides</p>` : ''}
307
  </div>
308
  <div>
309
  <div style="font-weight: bold; margin-bottom: 5px;">Status: <span style="color: ${isApproved ? '#10b981' : 'var(--accent-pink)'};">${post.status.toUpperCase()}</span></div>
scheduler.js CHANGED
@@ -161,7 +161,7 @@ class SocialScheduler {
161
  if (zPlatforms.length > 0) {
162
  const zResult = await zernioPoster.createPost({
163
  content: contentData.ig_id || contentData.ig_en,
164
- mediaUrls: contentData.mediaUrl ? [contentData.mediaUrl] : [],
165
  platforms: zPlatforms,
166
  scheduledFor: scheduleTime,
167
  publishNow: !scheduleTime
@@ -189,7 +189,7 @@ class SocialScheduler {
189
  // Unified PostProxy call:
190
  const ppResult = await postProxyPoster.createPost({
191
  content: contentData.linkedin_id || contentData.linkedin_en || contentData.x_id,
192
- mediaUrls: contentData.mediaUrl ? [contentData.mediaUrl] : [],
193
  profiles: targetProfileIds
194
  });
195
  ppSuccess = ppResult.success;
 
161
  if (zPlatforms.length > 0) {
162
  const zResult = await zernioPoster.createPost({
163
  content: contentData.ig_id || contentData.ig_en,
164
+ mediaUrls: post.type === 'carousel' ? post.mediaUrls : (contentData.mediaUrl ? [contentData.mediaUrl] : []),
165
  platforms: zPlatforms,
166
  scheduledFor: scheduleTime,
167
  publishNow: !scheduleTime
 
189
  // Unified PostProxy call:
190
  const ppResult = await postProxyPoster.createPost({
191
  content: contentData.linkedin_id || contentData.linkedin_en || contentData.x_id,
192
+ mediaUrls: post.type === 'carousel' ? post.mediaUrls : (contentData.mediaUrl ? [contentData.mediaUrl] : []),
193
  profiles: targetProfileIds
194
  });
195
  ppSuccess = ppResult.success;
server.js CHANGED
@@ -535,6 +535,96 @@ Tell the AI what emotion or 'edge' you want.
535
  return;
536
  }
537
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
538
  if (userText === '/turbo') {
539
  const db = memory.readDB();
540
  if (!db.user_profile_snapshot) db.user_profile_snapshot = {};
 
535
  return;
536
  }
537
 
538
+ if (userText && userText.toLowerCase().startsWith('/carousel')) {
539
+ const topic = userText.replace(/^\/carousel\s*/i, '').trim();
540
+ if (!topic) {
541
+ await apiCaller.sendTelegramMessage(chatId, "❌ Usage: /carousel [topic]");
542
+ return;
543
+ }
544
+ await apiCaller.sendTelegramMessage(chatId, `🎠 <b>Carousel Generator Auto-Pilot</b>\nTopic: ${topic}\n<i>Drafting structure...</i>`);
545
+
546
+ const aiCarousel = require('./ai-carousel');
547
+ const carouselGen = require('./carousel-gen');
548
+ const contentRes = await aiCarousel.generateContent(topic);
549
+
550
+ if (!contentRes.success) {
551
+ await apiCaller.sendTelegramMessage(chatId, `❌ AI Drafting failed: ${contentRes.error}`);
552
+ return;
553
+ }
554
+
555
+ await apiCaller.sendTelegramMessage(chatId, `🎨 <b>Content drafted!</b> Rendering ${contentRes.slides.length} slides with SVG templating...`);
556
+
557
+ const topicId = Date.now().toString().slice(-6);
558
+ const slidePaths = await carouselGen.generateSlides(contentRes.slides, topicId);
559
+
560
+ const FormData = require('form-data');
561
+ const fs = require('fs');
562
+ const path = require('path');
563
+
564
+ // Limit Telegram MediaGroup to max 10 photos
565
+ const photosToUpload = slidePaths.slice(0, 10);
566
+ const mediaGroup = photosToUpload.map(p => ({
567
+ type: 'photo',
568
+ media: `attach://${path.basename(p)}`
569
+ }));
570
+
571
+ const fForm = new FormData();
572
+ fForm.append('chat_id', chatId);
573
+ fForm.append('media', JSON.stringify(mediaGroup));
574
+ photosToUpload.forEach(p => {
575
+ fForm.append(path.basename(p), fs.createReadStream(p));
576
+ });
577
+
578
+ try {
579
+ await apiCaller.axiosIPv4.post(`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendMediaGroup`, fForm, { headers: fForm.getHeaders() });
580
+ await apiCaller.sendTelegramMessage(chatId, `✅ <b>Carousel Draft Complete</b>\nUse the Dashboard to review and post!`);
581
+ } catch (err) {
582
+ console.error("[Carousel] Album upload error:", err.response?.data || err.message);
583
+ await apiCaller.sendTelegramMessage(chatId, `❌ Failed to send carousel preview to Telegram. Check dashboard.`);
584
+ }
585
+
586
+ // Save to Scheduler Drafts
587
+ try {
588
+ const scheduler = require('./scheduler');
589
+ const zernioPoster = require('./zernio-poster');
590
+
591
+ // Upload each local slide to Zernio for public URL
592
+ const publicUrls = [];
593
+ for (let p of slidePaths) {
594
+ try {
595
+ const upRes = await zernioPoster.uploadMedia(p, 'image/jpeg');
596
+ if (upRes && upRes.success) publicUrls.push(upRes.publicUrl);
597
+ else publicUrls.push(`/tmp/${path.basename(p)}`);
598
+ } catch (e) {
599
+ publicUrls.push(`/tmp/${path.basename(p)}`);
600
+ }
601
+ }
602
+
603
+ // Compile Text
604
+ const captionText = contentRes.slides.map(s => `${s.type.toUpperCase()}: ${s.title}\n${s.subtitle}`).join('\n\n');
605
+
606
+ const draftPost = {
607
+ id: `caro_${topicId}`,
608
+ status: 'draft',
609
+ created: new Date().toISOString(),
610
+ input: `Carousel: ${topic}`,
611
+ platform: 'instagram',
612
+ type: 'carousel',
613
+ mediaUrls: publicUrls,
614
+ v1: {
615
+ ig_en: `${contentRes.slides[0].title}\n\n${contentRes.slides[0].subtitle}\n\n#VinOS #Carousel\n\n(View slides on profile)`
616
+ }
617
+ };
618
+
619
+ scheduler.config.posts.push(draftPost);
620
+ scheduler.saveDB();
621
+ } catch (e) {
622
+ console.error("[Carousel] Draft save error:", e.message);
623
+ }
624
+
625
+ return;
626
+ }
627
+
628
  if (userText === '/turbo') {
629
  const db = memory.readDB();
630
  if (!db.user_profile_snapshot) db.user_profile_snapshot = {};