HerzaJ commited on
Commit
4bfc3de
·
verified ·
1 Parent(s): 88607d7

Create cartoony.js

Browse files
Files changed (1) hide show
  1. plugins/cartoony.js +247 -0
plugins/cartoony.js ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const axios = require('axios');
2
+ const FormData = require('form-data');
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const crypto = require('crypto');
6
+
7
+ const cartoony = {
8
+ api: {
9
+ base: 'https://api.cartoony.app',
10
+ endpoint: {
11
+ generate: '/api/v1/generate/image',
12
+ styles: '/api/v1/styles?platform=android'
13
+ },
14
+ keyX: [
15
+ 97,50,100,98,53,101,54,98,45,100,49,99,99,45,
16
+ 52,50,54,102,45,56,51,51,99,45,50,48,50,53,
17
+ 51,49,102,50,100,50,57,97
18
+ ],
19
+ get key() {
20
+ return String.fromCharCode(...this.keyX);
21
+ }
22
+ },
23
+
24
+ headers: {
25
+ 'user-agent': 'NB Android/1.0.0',
26
+ 'accept': 'application/json',
27
+ 'accept-encoding': 'gzip',
28
+ 'accept-charset': 'UTF-8'
29
+ },
30
+
31
+ now: () => Date.now(),
32
+ genUuid: () => crypto.randomUUID(),
33
+ genDeviceId: () => crypto.randomBytes(8).toString('hex'),
34
+ genRcUserId: () => `$RCAnonymousID:${crypto.randomBytes(16).toString('hex')}`,
35
+
36
+ getStyles: async () => {
37
+ try {
38
+ const res = await axios.get(
39
+ `${cartoony.api.base}${cartoony.api.endpoint.styles}`,
40
+ { headers: { ...cartoony.headers, 'x-api-key': cartoony.api.key } }
41
+ );
42
+ return res.data?.styles ?? [];
43
+ } catch {
44
+ return [];
45
+ }
46
+ },
47
+
48
+ retry: async (fn, retries = 3, delay = 1000) => {
49
+ let lastErr;
50
+ for (let i = 0; i < retries; i++) {
51
+ try {
52
+ return await fn();
53
+ } catch (err) {
54
+ lastErr = err;
55
+ if (i < retries - 1) await new Promise(r => setTimeout(r, delay));
56
+ }
57
+ }
58
+ throw lastErr;
59
+ }
60
+ };
61
+
62
+ async function downloadImage(url) {
63
+ const response = await axios.get(url, {
64
+ responseType: 'arraybuffer',
65
+ timeout: 15000
66
+ });
67
+ const tempPath = path.join('/tmp', `cartoony_${Date.now()}.jpg`);
68
+ fs.writeFileSync(tempPath, response.data);
69
+ return tempPath;
70
+ }
71
+
72
+ async function generateCartoony(imageUrl, styleId, quality = 'low') {
73
+ let tempPath = null;
74
+
75
+ try {
76
+ tempPath = await downloadImage(imageUrl);
77
+
78
+ const stats = fs.statSync(tempPath);
79
+ const maxSize = 5 * 1024 * 1024;
80
+ if (stats.size > maxSize) {
81
+ throw new Error('Image size exceeds 5MB limit');
82
+ }
83
+
84
+ const form = new FormData();
85
+ form.append('image', fs.createReadStream(tempPath), {
86
+ filename: 'user-image',
87
+ contentType: 'multipart/form-data'
88
+ });
89
+
90
+ form.append('style_id', styleId);
91
+ form.append('quality', quality);
92
+ form.append('generation_type', 'image_to_image');
93
+
94
+ const userId = cartoony.genUuid();
95
+ const rcUid = cartoony.genRcUserId();
96
+ const deviceId = cartoony.genDeviceId();
97
+ const timestamp = cartoony.now();
98
+
99
+ form.append('user_id', userId);
100
+ form.append('timestamp', timestamp);
101
+ form.append('platform', 'android');
102
+ form.append('app_version', '1.2.0');
103
+ form.append('locale', 'en-MM');
104
+ form.append('is_pro_user', 'true');
105
+ form.append('daily_count', '1');
106
+ form.append('rc_user_id', rcUid);
107
+ form.append('package_id', 'com.gigantic.cartoony');
108
+ form.append('device_id', deviceId);
109
+
110
+ const res = await cartoony.retry(
111
+ () =>
112
+ axios.post(
113
+ `${cartoony.api.base}${cartoony.api.endpoint.generate}`,
114
+ form,
115
+ {
116
+ headers: {
117
+ ...cartoony.headers,
118
+ ...form.getHeaders(),
119
+ 'x-api-key': cartoony.api.key
120
+ },
121
+ maxBodyLength: Infinity,
122
+ timeout: 60000
123
+ }
124
+ ),
125
+ 2
126
+ );
127
+
128
+ const data = res.data;
129
+ const base64Image = data?.generatedImage?.data ?? null;
130
+ const mimeType = data?.generatedImage?.mimeType ?? 'image/png';
131
+
132
+ if (!base64Image) {
133
+ throw new Error('Failed to generate image');
134
+ }
135
+
136
+ const imageBuffer = Buffer.from(base64Image, 'base64');
137
+ const ext = mimeType.split('/')[1] || 'png';
138
+ const randomName = crypto.randomBytes(16).toString('hex');
139
+ const fileName = `${randomName}.${ext}`;
140
+
141
+ const tmpDir = path.join(__dirname, '../../tmp');
142
+ if (!fs.existsSync(tmpDir)) {
143
+ fs.mkdirSync(tmpDir, { recursive: true });
144
+ }
145
+
146
+ const savePath = path.join(tmpDir, fileName);
147
+ fs.writeFileSync(savePath, imageBuffer);
148
+
149
+ return {
150
+ success: true,
151
+ fileName,
152
+ mimeType,
153
+ styleId,
154
+ quality
155
+ };
156
+
157
+ } finally {
158
+ if (tempPath && fs.existsSync(tempPath)) {
159
+ try {
160
+ fs.unlinkSync(tempPath);
161
+ } catch {}
162
+ }
163
+ }
164
+ }
165
+
166
+ const handler = async (req, res) => {
167
+ try {
168
+ const { image, model, quality, key } = req.query;
169
+
170
+ if (!image || !model || !key) {
171
+ return res.status(400).json({
172
+ author: 'Herza',
173
+ success: false,
174
+ message: 'Missing required parameters: image, model, and key'
175
+ });
176
+ }
177
+
178
+ const styles = await cartoony.getStyles();
179
+ const availableModels = styles.map(s => s.id);
180
+
181
+ const modelExists = availableModels.some(
182
+ m => m.toLowerCase() === model.toLowerCase()
183
+ );
184
+
185
+ if (!modelExists) {
186
+ return res.status(422).json({
187
+ author: 'Herza',
188
+ success: false,
189
+ message: 'Available Models only',
190
+ availableModels: styles.map(s => ({
191
+ id: s.id,
192
+ name: s.name,
193
+ isPro: s.isPro
194
+ }))
195
+ });
196
+ }
197
+
198
+ const validQualities = ['low', 'medium'];
199
+ const selectedQuality = quality?.toLowerCase() || 'low';
200
+
201
+ if (!validQualities.includes(selectedQuality)) {
202
+ return res.status(422).json({
203
+ author: 'Herza',
204
+ success: false,
205
+ message: 'Invalid quality parameter. Available: low, medium',
206
+ availableQualities: validQualities
207
+ });
208
+ }
209
+
210
+ const result = await generateCartoony(image, model, selectedQuality);
211
+
212
+ const protocol = req.protocol || 'http';
213
+ const host = req.get('host');
214
+ const fileUrl = `${protocol}://${host}/tmp/${result.fileName}`;
215
+
216
+ res.json({
217
+ author: 'Herza',
218
+ success: true,
219
+ data: {
220
+ url: fileUrl,
221
+ mimeType: result.mimeType,
222
+ model: result.styleId,
223
+ quality: result.quality
224
+ }
225
+ });
226
+
227
+ } catch (error) {
228
+ console.error('Cartoony API Error:', error);
229
+ res.status(500).json({
230
+ author: 'Herza',
231
+ success: false,
232
+ message: error.message || 'Internal server error'
233
+ });
234
+ }
235
+ };
236
+
237
+ module.exports = {
238
+ name: 'Cartoony AI Image Generator',
239
+ description: 'Convert images to various AI styles using Cartoony API',
240
+ type: 'GET',
241
+ routes: ['api/AI/cartoony'],
242
+ tags: ['ai', 'image', 'cartoony', 'converter', 'style-transfer'],
243
+ main: ['AI'],
244
+ parameters: ['image', 'model', 'quality', 'key'],
245
+ enabled: true,
246
+ handler
247
+ };