| const axios = require('axios'); |
| const crypto = require('crypto'); |
|
|
| class LightXBot { |
| constructor() { |
| this.tempMailHeaders = { |
| 'Content-Type': 'application/json', |
| 'Application-Name': 'web', |
| 'Application-Version': '4.0.0', |
| 'X-CORS-Header': 'iaWg3pchvFx48fY' |
| }; |
| |
| this.lightxHeaders = { |
| 'Accept': 'application/json, text/plain, */*', |
| 'Content-Type': 'application/json' |
| }; |
| |
| this.authPayload = { |
| clientHash: '78e7841d9f1891ced5b260c455ea99f3', |
| accessToken: '', |
| version: '0.1', |
| deviceId: '', |
| model: '', |
| os: 24, |
| platform: 'web', |
| appname: 'lightx', |
| locale: 'en-GB', |
| appVersion: 3, |
| apiHash: 'fb194c698ecca6bc73d649c3fabee629cfe0e2ccc9805ef5a91b0374e52fe4969cd62e2b9abe8bb3d55738914fedcd50ffbe225740ae84c9e2b1aa5081c511fc' |
| }; |
| |
| this.zombieStyles = { |
| 1: { |
| name: "Classic Zombie", |
| 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", |
| productId: 10103796, |
| productImageId: 123663 |
| }, |
| 2: { |
| name: "Walking Dead Zombie", |
| 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", |
| productId: 10103796, |
| productImageId: 123663 |
| }, |
| 3: { |
| name: "Horror Zombie", |
| prompt: "Become a terrifying horror movie zombie with gruesome makeup, exposed bones, blood dripping, vacant stare, and decomposed skin in a dark spooky graveyard", |
| productId: 10103796, |
| productImageId: 123663 |
| }, |
| 4: { |
| name: "Undead Creature", |
| 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", |
| productId: 10103796, |
| productImageId: 123663 |
| }, |
| 5: { |
| name: "Nightmare Zombie", |
| prompt: "Become a nightmarish zombie with horrifying scars, decaying face, bloodshot eyes, ripped and stained clothing in a foggy haunted cemetery at midnight", |
| productId: 10103796, |
| productImageId: 123663 |
| } |
| }; |
| |
| this.email = ''; |
| this.token = ''; |
| this.password = ''; |
| this.bearerToken = ''; |
| this.systemRefKey = ''; |
| } |
| |
| hashPassword(password) { |
| return crypto.createHash('sha512').update(password).digest('hex'); |
| } |
| |
| generateRandomString(length) { |
| const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; |
| let result = ''; |
| for (let i = 0; i < length; i++) { |
| result += chars.charAt(Math.floor(Math.random() * chars.length)); |
| } |
| return result; |
| } |
| |
| async createTempEmail() { |
| const response = await axios.post( |
| 'https://api.internal.temp-mail.io/api/v3/email/new', |
| { min_name_length: 10, max_name_length: 10 }, |
| { headers: this.tempMailHeaders } |
| ); |
| |
| this.email = response.data.email; |
| this.token = response.data.token; |
| return { email: this.email, token: this.token }; |
| } |
| |
| async registerAccount(name, password) { |
| this.password = password; |
| const hashedPassword = this.hashPassword(password); |
| |
| const response = await axios.post( |
| 'https://www.instagraphe.mobi/andor-login-1.0/mobile/emailSignup', |
| { |
| name: name, |
| email: this.email, |
| password: hashedPassword |
| }, |
| { headers: { ...this.lightxHeaders, auth: JSON.stringify(this.authPayload) } } |
| ); |
| |
| return response.data; |
| } |
| |
| async waitForVerificationEmail(maxRetries = 20, interval = 5000) { |
| for (let i = 0; i < maxRetries; i++) { |
| const response = await axios.get( |
| `https://api.internal.temp-mail.io/api/v3/email/${this.email}/messages`, |
| { headers: this.tempMailHeaders } |
| ); |
| |
| if (response.data && response.data.length > 0) { |
| const verificationEmail = response.data.find(msg => |
| msg.subject === 'Welcome to LightX' && msg.body_text.includes('validate-email') |
| ); |
| |
| if (verificationEmail) { |
| const linkMatch = verificationEmail.body_text.match(/https:\/\/www\.lightxeditor\.com\/validate-email\?[^\s)]+/); |
| if (linkMatch) { |
| return linkMatch[0]; |
| } |
| } |
| } |
| |
| await new Promise(resolve => setTimeout(resolve, interval)); |
| } |
| |
| throw new Error('Email verifikasi tidak diterima'); |
| } |
| |
| async verifyEmail(verificationLink) { |
| try { |
| await axios.get(verificationLink, { |
| maxRedirects: 5, |
| timeout: 10000 |
| }); |
| return 'Success'; |
| } catch (error) { |
| return 'Success'; |
| } |
| } |
| |
| async login() { |
| const hashedPassword = this.hashPassword(this.password); |
| |
| const response = await axios.post( |
| 'https://www.instagraphe.mobi/andor-login-1.0/mobile/login', |
| { |
| password: hashedPassword, |
| type: 'EMAIL', |
| value: this.email |
| }, |
| { headers: { ...this.lightxHeaders, auth: JSON.stringify(this.authPayload) } } |
| ); |
| |
| this.bearerToken = response.data.body.token.accessToken; |
| this.systemRefKey = response.data.body.user.systemRefKey; |
| return response.data; |
| } |
| |
| async checkCredits() { |
| const authWithToken = { |
| ...this.authPayload, |
| accessToken: this.bearerToken, |
| systemRefKey: this.systemRefKey |
| }; |
| |
| const response = await axios.get( |
| 'https://www.instagraphe.mobi/andor-login-1.0/user/getAPIUsageDetails?apiId=1', |
| { headers: { ...this.lightxHeaders, auth: JSON.stringify(authWithToken) } } |
| ); |
| |
| return response.data.body; |
| } |
| |
| async uploadImage(imageBuffer, fileName, size) { |
| const authWithToken = { |
| ...this.authPayload, |
| accessToken: this.bearerToken, |
| systemRefKey: this.systemRefKey |
| }; |
| |
| const presignResponse = await axios.post( |
| 'https://www.instagraphe.mobi/andor-media-1.0/content/generatePresignedUrls', |
| { |
| featureType: 'selfie', |
| contents: [{ |
| size: size, |
| contentType: 'image/jpeg', |
| assetType: 'IMG', |
| assetRefId: '1', |
| name: fileName |
| }] |
| }, |
| { headers: { ...this.lightxHeaders, auth: JSON.stringify(authWithToken) } } |
| ); |
| |
| const uploadData = presignResponse.data.body.contents[0]; |
| const presignedUrl = uploadData.presignedUrl; |
| const imageUrl = uploadData.contentUrl; |
| |
| await axios.put(presignedUrl, imageBuffer, { |
| headers: { |
| 'Content-Type': 'image/jpeg', |
| 'Content-Length': size |
| } |
| }); |
| |
| return imageUrl; |
| } |
| |
| async convertToZombie(imageUrl, styleId) { |
| const style = this.zombieStyles[styleId]; |
| if (!style) { |
| throw new Error(`Invalid style ID: ${styleId}. Valid IDs are 1-5`); |
| } |
| |
| const authWithToken = { |
| ...this.authPayload, |
| accessToken: this.bearerToken, |
| systemRefKey: this.systemRefKey |
| }; |
| |
| try { |
| const response = await axios.post( |
| 'https://www.instagraphe.mobi/andor-media-1.0/aiartweb/generateImage', |
| { |
| featureType: 'selfie', |
| isCustomPrompt: 0, |
| quality: 1, |
| imageUrl: imageUrl, |
| productId: style.productId, |
| model: 58, |
| textPrompt: style.prompt, |
| productImageId: style.productImageId |
| }, |
| { headers: { ...this.lightxHeaders, auth: JSON.stringify(authWithToken) } } |
| ); |
| |
| if (!response.data || !response.data.body || !response.data.body.assetId) { |
| throw new Error(`Invalid response: ${JSON.stringify(response.data)}`); |
| } |
| |
| const assetId = response.data.body.assetId; |
| return await this.checkGenerationStatus(assetId); |
| } catch (error) { |
| if (error.response) { |
| throw new Error(`API Error: ${JSON.stringify(error.response.data)}`); |
| } |
| throw error; |
| } |
| } |
| |
| async checkGenerationStatus(assetId, maxRetries = 30, interval = 3000) { |
| const authWithToken = { |
| ...this.authPayload, |
| accessToken: this.bearerToken, |
| systemRefKey: this.systemRefKey |
| }; |
| |
| for (let i = 0; i < maxRetries; i++) { |
| const response = await axios.post( |
| 'https://www.instagraphe.mobi/andor-media-1.0/aiart/checkStatus', |
| { assetId: assetId }, |
| { headers: { ...this.lightxHeaders, auth: JSON.stringify(authWithToken) } } |
| ); |
| |
| const status = response.data.body.status; |
| |
| if (status === 'active') { |
| return response.data.body; |
| } |
| |
| await new Promise(resolve => setTimeout(resolve, interval)); |
| } |
| |
| throw new Error('Generation timeout'); |
| } |
| } |
|
|
| async function img2zombie(imageUrl, styleId = 1, maxRetries = 3) { |
| for (let attempt = 1; attempt <= maxRetries; attempt++) { |
| const bot = new LightXBot(); |
| |
| try { |
| await bot.createTempEmail(); |
| |
| const randomName = 'User' + bot.generateRandomString(6); |
| const randomPassword = 'Pass' + bot.generateRandomString(10) + '!'; |
| |
| await bot.registerAccount(randomName, randomPassword); |
| const verificationLink = await bot.waitForVerificationEmail(); |
| await bot.verifyEmail(verificationLink); |
| await new Promise(resolve => setTimeout(resolve, 3000)); |
| await bot.login(); |
| |
| const credits = await bot.checkCredits(); |
| |
| if (credits.remainingCalls < 1) { |
| throw new Error('No credits available, trying new account...'); |
| } |
| |
| const imageResponse = await axios.get(imageUrl, { responseType: 'arraybuffer' }); |
| const imageBuffer = Buffer.from(imageResponse.data); |
| |
| const uploadedUrl = await bot.uploadImage(imageBuffer, 'image.jpg', imageBuffer.length); |
| const result = await bot.convertToZombie(uploadedUrl, styleId); |
| |
| return { |
| success: true, |
| styleName: bot.zombieStyles[styleId].name, |
| email: bot.email, |
| resultUrl: result.imgUrl, |
| thumbnailUrl: result.thumbUrl, |
| width: result.width, |
| height: result.height, |
| creditsRemaining: credits.remainingCalls - 1 |
| }; |
| |
| } catch (error) { |
| if (attempt === maxRetries) { |
| return { |
| success: false, |
| error: error.message, |
| attemptsUsed: attempt |
| }; |
| } |
| await new Promise(resolve => setTimeout(resolve, 2000)); |
| } |
| } |
| } |
|
|
| const handler = async (req, res) => { |
| try { |
| const { image, style, key } = req.query; |
|
|
| if (!image || !key) { |
| return res.status(400).json({ |
| success: false, |
| error: 'Missing required parameters: image and key' |
| }); |
| } |
|
|
| const styleId = style ? parseInt(style) : 1; |
| |
| if (styleId < 1 || styleId > 5) { |
| return res.status(400).json({ |
| success: false, |
| error: 'Invalid style parameter. Must be between 1-5', |
| styles: { |
| 1: 'Classic Zombie', |
| 2: 'Walking Dead Zombie', |
| 3: 'Horror Zombie', |
| 4: 'Undead Creature', |
| 5: 'Nightmare Zombie' |
| } |
| }); |
| } |
|
|
| const result = await img2zombie(image, styleId); |
| |
| if (!result.success) { |
| return res.status(500).json({ |
| success: false, |
| error: result.error, |
| attemptsUsed: result.attemptsUsed |
| }); |
| } |
|
|
| res.json({ |
| author: "Herza", |
| success: true, |
| data: { |
| styleName: result.styleName, |
| url: result.resultUrl, |
| thumbnail: result.thumbnailUrl, |
| width: result.width, |
| height: result.height, |
| creditsUsed: 1, |
| email: result.email |
| } |
| }); |
|
|
| } catch (error) { |
| res.status(500).json({ |
| success: false, |
| error: error.message |
| }); |
| } |
| }; |
|
|
| module.exports = { |
| name: 'Image to Zombie Converter', |
| description: 'Convert photos to zombie style using AI with 5 different zombie themes', |
| type: 'GET', |
| routes: ['api/AI/img2zombie'], |
| tags: ['ai', 'image', 'zombie', 'converter', 'horror'], |
| main: ['AI'], |
| parameters: ['image', 'style', 'key'], |
| enabled: true, |
| limit: 5, |
| handler |
| }; |