HerzaJ commited on
Commit
28cb6f0
·
verified ·
1 Parent(s): a9e7be3

Create img2zombie.js

Browse files
Files changed (1) hide show
  1. plugins/img2zombie.js +412 -0
plugins/img2zombie.js ADDED
@@ -0,0 +1,412 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const axios = require('axios');
2
+ const crypto = require('crypto');
3
+
4
+ class LightXBot {
5
+ constructor() {
6
+ this.tempMailHeaders = {
7
+ 'Content-Type': 'application/json',
8
+ 'Application-Name': 'web',
9
+ 'Application-Version': '4.0.0',
10
+ 'X-CORS-Header': 'iaWg3pchvFx48fY'
11
+ };
12
+
13
+ this.lightxHeaders = {
14
+ 'Accept': 'application/json, text/plain, */*',
15
+ 'Content-Type': 'application/json'
16
+ };
17
+
18
+ this.authPayload = {
19
+ clientHash: '78e7841d9f1891ced5b260c455ea99f3',
20
+ accessToken: '',
21
+ version: '0.1',
22
+ deviceId: '',
23
+ model: '',
24
+ os: 24,
25
+ platform: 'web',
26
+ appname: 'lightx',
27
+ locale: 'en-GB',
28
+ appVersion: 3,
29
+ apiHash: 'fb194c698ecca6bc73d649c3fabee629cfe0e2ccc9805ef5a91b0374e52fe4969cd62e2b9abe8bb3d55738914fedcd50ffbe225740ae84c9e2b1aa5081c511fc'
30
+ };
31
+
32
+ this.zombieStyles = {
33
+ 1: {
34
+ name: "Classic Zombie",
35
+ prompt: "A man dressed as a zombie male costume features torn, bloodstained clothes, creating a creepy, haunted house with a creepy butler for a spooky and pumpkin, halloween night background",
36
+ productId: 10103796,
37
+ productImageId: 123663
38
+ },
39
+ 2: {
40
+ name: "Walking Dead Zombie",
41
+ prompt: "Transform into a decaying walking dead zombie with rotting flesh, pale grey skin, sunken eyes, and tattered bloody clothing in a post-apocalyptic setting",
42
+ productId: 10103796,
43
+ productImageId: 123663
44
+ },
45
+ 3: {
46
+ name: "Horror Zombie",
47
+ prompt: "Become a terrifying horror movie zombie with gruesome makeup, exposed bones, blood dripping, vacant stare, and decomposed skin in a dark spooky graveyard",
48
+ productId: 10103796,
49
+ productImageId: 123663
50
+ },
51
+ 4: {
52
+ name: "Undead Creature",
53
+ prompt: "Transform into an undead zombie creature with greenish rotting skin, missing chunks of flesh, empty soulless eyes, torn clothes covered in dirt and blood",
54
+ productId: 10103796,
55
+ productImageId: 123663
56
+ },
57
+ 5: {
58
+ name: "Nightmare Zombie",
59
+ prompt: "Become a nightmarish zombie with horrifying scars, decaying face, bloodshot eyes, ripped and stained clothing in a foggy haunted cemetery at midnight",
60
+ productId: 10103796,
61
+ productImageId: 123663
62
+ }
63
+ };
64
+
65
+ this.email = '';
66
+ this.token = '';
67
+ this.password = '';
68
+ this.bearerToken = '';
69
+ this.systemRefKey = '';
70
+ }
71
+
72
+ hashPassword(password) {
73
+ return crypto.createHash('sha512').update(password).digest('hex');
74
+ }
75
+
76
+ generateRandomString(length) {
77
+ const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
78
+ let result = '';
79
+ for (let i = 0; i < length; i++) {
80
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
81
+ }
82
+ return result;
83
+ }
84
+
85
+ async createTempEmail() {
86
+ const response = await axios.post(
87
+ 'https://api.internal.temp-mail.io/api/v3/email/new',
88
+ { min_name_length: 10, max_name_length: 10 },
89
+ { headers: this.tempMailHeaders }
90
+ );
91
+
92
+ this.email = response.data.email;
93
+ this.token = response.data.token;
94
+ return { email: this.email, token: this.token };
95
+ }
96
+
97
+ async registerAccount(name, password) {
98
+ this.password = password;
99
+ const hashedPassword = this.hashPassword(password);
100
+
101
+ const response = await axios.post(
102
+ 'https://www.instagraphe.mobi/andor-login-1.0/mobile/emailSignup',
103
+ {
104
+ name: name,
105
+ email: this.email,
106
+ password: hashedPassword
107
+ },
108
+ { headers: { ...this.lightxHeaders, auth: JSON.stringify(this.authPayload) } }
109
+ );
110
+
111
+ return response.data;
112
+ }
113
+
114
+ async waitForVerificationEmail(maxRetries = 20, interval = 5000) {
115
+ for (let i = 0; i < maxRetries; i++) {
116
+ const response = await axios.get(
117
+ `https://api.internal.temp-mail.io/api/v3/email/${this.email}/messages`,
118
+ { headers: this.tempMailHeaders }
119
+ );
120
+
121
+ if (response.data && response.data.length > 0) {
122
+ const verificationEmail = response.data.find(msg =>
123
+ msg.subject === 'Welcome to LightX' && msg.body_text.includes('validate-email')
124
+ );
125
+
126
+ if (verificationEmail) {
127
+ const linkMatch = verificationEmail.body_text.match(/https:\/\/www\.lightxeditor\.com\/validate-email\?[^\s)]+/);
128
+ if (linkMatch) {
129
+ return linkMatch[0];
130
+ }
131
+ }
132
+ }
133
+
134
+ await new Promise(resolve => setTimeout(resolve, interval));
135
+ }
136
+
137
+ throw new Error('Email verifikasi tidak diterima');
138
+ }
139
+
140
+ async verifyEmail(verificationLink) {
141
+ try {
142
+ await axios.get(verificationLink, {
143
+ maxRedirects: 5,
144
+ timeout: 10000
145
+ });
146
+ return 'Success';
147
+ } catch (error) {
148
+ return 'Success';
149
+ }
150
+ }
151
+
152
+ async login() {
153
+ const hashedPassword = this.hashPassword(this.password);
154
+
155
+ const response = await axios.post(
156
+ 'https://www.instagraphe.mobi/andor-login-1.0/mobile/login',
157
+ {
158
+ password: hashedPassword,
159
+ type: 'EMAIL',
160
+ value: this.email
161
+ },
162
+ { headers: { ...this.lightxHeaders, auth: JSON.stringify(this.authPayload) } }
163
+ );
164
+
165
+ this.bearerToken = response.data.body.token.accessToken;
166
+ this.systemRefKey = response.data.body.user.systemRefKey;
167
+ return response.data;
168
+ }
169
+
170
+ async checkCredits() {
171
+ const authWithToken = {
172
+ ...this.authPayload,
173
+ accessToken: this.bearerToken,
174
+ systemRefKey: this.systemRefKey
175
+ };
176
+
177
+ const response = await axios.get(
178
+ 'https://www.instagraphe.mobi/andor-login-1.0/user/getAPIUsageDetails?apiId=1',
179
+ { headers: { ...this.lightxHeaders, auth: JSON.stringify(authWithToken) } }
180
+ );
181
+
182
+ return response.data.body;
183
+ }
184
+
185
+ async uploadImage(imageBuffer, fileName, size) {
186
+ const authWithToken = {
187
+ ...this.authPayload,
188
+ accessToken: this.bearerToken,
189
+ systemRefKey: this.systemRefKey
190
+ };
191
+
192
+ const presignResponse = await axios.post(
193
+ 'https://www.instagraphe.mobi/andor-media-1.0/content/generatePresignedUrls',
194
+ {
195
+ featureType: 'selfie',
196
+ contents: [{
197
+ size: size,
198
+ contentType: 'image/jpeg',
199
+ assetType: 'IMG',
200
+ assetRefId: '1',
201
+ name: fileName
202
+ }]
203
+ },
204
+ { headers: { ...this.lightxHeaders, auth: JSON.stringify(authWithToken) } }
205
+ );
206
+
207
+ const uploadData = presignResponse.data.body.contents[0];
208
+ const presignedUrl = uploadData.presignedUrl;
209
+ const imageUrl = uploadData.contentUrl;
210
+
211
+ await axios.put(presignedUrl, imageBuffer, {
212
+ headers: {
213
+ 'Content-Type': 'image/jpeg',
214
+ 'Content-Length': size
215
+ }
216
+ });
217
+
218
+ return imageUrl;
219
+ }
220
+
221
+ async convertToZombie(imageUrl, styleId) {
222
+ const style = this.zombieStyles[styleId];
223
+ if (!style) {
224
+ throw new Error(`Invalid style ID: ${styleId}. Valid IDs are 1-5`);
225
+ }
226
+
227
+ const authWithToken = {
228
+ ...this.authPayload,
229
+ accessToken: this.bearerToken,
230
+ systemRefKey: this.systemRefKey
231
+ };
232
+
233
+ try {
234
+ const response = await axios.post(
235
+ 'https://www.instagraphe.mobi/andor-media-1.0/aiartweb/generateImage',
236
+ {
237
+ featureType: 'selfie',
238
+ isCustomPrompt: 0,
239
+ quality: 1,
240
+ imageUrl: imageUrl,
241
+ productId: style.productId,
242
+ model: 58,
243
+ textPrompt: style.prompt,
244
+ productImageId: style.productImageId
245
+ },
246
+ { headers: { ...this.lightxHeaders, auth: JSON.stringify(authWithToken) } }
247
+ );
248
+
249
+ if (!response.data || !response.data.body || !response.data.body.assetId) {
250
+ throw new Error(`Invalid response: ${JSON.stringify(response.data)}`);
251
+ }
252
+
253
+ const assetId = response.data.body.assetId;
254
+ return await this.checkGenerationStatus(assetId);
255
+ } catch (error) {
256
+ if (error.response) {
257
+ throw new Error(`API Error: ${JSON.stringify(error.response.data)}`);
258
+ }
259
+ throw error;
260
+ }
261
+ }
262
+
263
+ async checkGenerationStatus(assetId, maxRetries = 30, interval = 3000) {
264
+ const authWithToken = {
265
+ ...this.authPayload,
266
+ accessToken: this.bearerToken,
267
+ systemRefKey: this.systemRefKey
268
+ };
269
+
270
+ for (let i = 0; i < maxRetries; i++) {
271
+ const response = await axios.post(
272
+ 'https://www.instagraphe.mobi/andor-media-1.0/aiart/checkStatus',
273
+ { assetId: assetId },
274
+ { headers: { ...this.lightxHeaders, auth: JSON.stringify(authWithToken) } }
275
+ );
276
+
277
+ const status = response.data.body.status;
278
+
279
+ if (status === 'active') {
280
+ return response.data.body;
281
+ }
282
+
283
+ await new Promise(resolve => setTimeout(resolve, interval));
284
+ }
285
+
286
+ throw new Error('Generation timeout');
287
+ }
288
+ }
289
+
290
+ async function img2zombie(imageUrl, styleId = 1, maxRetries = 3) {
291
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
292
+ const bot = new LightXBot();
293
+
294
+ try {
295
+ await bot.createTempEmail();
296
+
297
+ const randomName = 'User' + bot.generateRandomString(6);
298
+ const randomPassword = 'Pass' + bot.generateRandomString(10) + '!';
299
+
300
+ await bot.registerAccount(randomName, randomPassword);
301
+ const verificationLink = await bot.waitForVerificationEmail();
302
+ await bot.verifyEmail(verificationLink);
303
+ await new Promise(resolve => setTimeout(resolve, 3000));
304
+ await bot.login();
305
+
306
+ const credits = await bot.checkCredits();
307
+
308
+ if (credits.remainingCalls < 1) {
309
+ throw new Error('No credits available, trying new account...');
310
+ }
311
+
312
+ const imageResponse = await axios.get(imageUrl, { responseType: 'arraybuffer' });
313
+ const imageBuffer = Buffer.from(imageResponse.data);
314
+
315
+ const uploadedUrl = await bot.uploadImage(imageBuffer, 'image.jpg', imageBuffer.length);
316
+ const result = await bot.convertToZombie(uploadedUrl, styleId);
317
+
318
+ return {
319
+ success: true,
320
+ styleName: bot.zombieStyles[styleId].name,
321
+ email: bot.email,
322
+ resultUrl: result.imgUrl,
323
+ thumbnailUrl: result.thumbUrl,
324
+ width: result.width,
325
+ height: result.height,
326
+ creditsRemaining: credits.remainingCalls - 1
327
+ };
328
+
329
+ } catch (error) {
330
+ if (attempt === maxRetries) {
331
+ return {
332
+ success: false,
333
+ error: error.message,
334
+ attemptsUsed: attempt
335
+ };
336
+ }
337
+ await new Promise(resolve => setTimeout(resolve, 2000));
338
+ }
339
+ }
340
+ }
341
+
342
+ const handler = async (req, res) => {
343
+ try {
344
+ const { image, style, key } = req.query;
345
+
346
+ if (!image || !key) {
347
+ return res.status(400).json({
348
+ success: false,
349
+ error: 'Missing required parameters: image and key'
350
+ });
351
+ }
352
+
353
+ const styleId = style ? parseInt(style) : 1;
354
+
355
+ if (styleId < 1 || styleId > 5) {
356
+ return res.status(400).json({
357
+ success: false,
358
+ error: 'Invalid style parameter. Must be between 1-5',
359
+ styles: {
360
+ 1: 'Classic Zombie',
361
+ 2: 'Walking Dead Zombie',
362
+ 3: 'Horror Zombie',
363
+ 4: 'Undead Creature',
364
+ 5: 'Nightmare Zombie'
365
+ }
366
+ });
367
+ }
368
+
369
+ const result = await img2zombie(image, styleId);
370
+
371
+ if (!result.success) {
372
+ return res.status(500).json({
373
+ success: false,
374
+ error: result.error,
375
+ attemptsUsed: result.attemptsUsed
376
+ });
377
+ }
378
+
379
+ res.json({
380
+ author: "Herza",
381
+ success: true,
382
+ data: {
383
+ styleName: result.styleName,
384
+ url: result.resultUrl,
385
+ thumbnail: result.thumbnailUrl,
386
+ width: result.width,
387
+ height: result.height,
388
+ creditsUsed: 1,
389
+ email: result.email
390
+ }
391
+ });
392
+
393
+ } catch (error) {
394
+ res.status(500).json({
395
+ success: false,
396
+ error: error.message
397
+ });
398
+ }
399
+ };
400
+
401
+ module.exports = {
402
+ name: 'Image to Zombie Converter',
403
+ description: 'Convert photos to zombie style using AI with 5 different zombie themes',
404
+ type: 'GET',
405
+ routes: ['api/AI/img2zombie'],
406
+ tags: ['ai', 'image', 'zombie', 'converter', 'horror'],
407
+ main: ['AI'],
408
+ parameters: ['image', 'style', 'key'],
409
+ enabled: true,
410
+ limit: 5,
411
+ handler
412
+ };