/* * Lokasi: features/ghibli-remix.js * Versi: v1 */ const axios = require('axios'); const { createDecipheriv } = require('crypto'); async function sendCallback(url, payload) { try { await axios.post(url, payload, { headers: { 'Content-Type': 'application/json' } }); } catch (error) { console.error('Callback failed:', error.response ? error.response.data : error.message); } } const ghibai = { api: { base: 'https://generate-api.ghibli-gpt.net', endpoints: { generate: '/v1/gpt4o-image/generate', task: '/v1/gpt4o-image/record-info', }, }, headers: { accept: '*/*', 'content-type': 'application/json', origin: 'https://ghibli-gpt.net', referer: 'https://ghibli-gpt.net/', 'user-agent': 'NB Android/1.0.0', authorization: '' }, state: { token: null }, security: { keyBase64: 'UBsnTxs80g8p4iW72eYyPaDvGZbpzun8K2cnoSSEz1Y', ivBase64: 'fG1SBDUyE2IG8kPw', ciphertextBase64: '2QpqZCkOD/WMHixMqt46AvhdKRYgy5aUMLXi6D0nOPGuDbH4gbNKDV0ZW/+9w9I=', decrypt: async () => { if (ghibai.state.token) return ghibai.state.token; const buf = k => Buffer.from(ghibai.security[k], 'base64'); const [key, iv, ciphertext] = ['keyBase64', 'ivBase64', 'ciphertextBase64'].map(buf); const decipher = createDecipheriv('aes-256-gcm', key, iv); decipher.setAuthTag(ciphertext.slice(-16)); const decrypted = decipher.update(ciphertext.slice(0, -16), undefined, 'utf8') + decipher.final('utf8'); ghibai.state.token = decrypted; ghibai.headers.authorization = `Bearer ${decrypted}`; return decrypted; } }, getTask: async (jobDetails) => { const { taskId, prompt, jobId, callbackUrl, callbackKey } = jobDetails; await ghibai.security.decrypt(); for (let i = 0; i < 60; i++) { try { const { data } = await axios.get(`${ghibai.api.base}${ghibai.api.endpoints.task}?taskId=${taskId}`, { headers: ghibai.headers }); const d = data?.data || {}; const status = d.status || 'unknown'; if (status === 'SUCCESS' && d.response?.resultUrls?.length) { return await sendCallback(callbackUrl, { jobId, callbackKey, status: 'success', result: { prompt, imageUrl: d.response.resultUrls[0], thumbnailUrl: d.response.thumbnailUrls?.[0], } }); } await new Promise(r => setTimeout(r, 3000)); } catch (err) { if (err.response?.status === 429) { await new Promise(r => setTimeout(r, 5000)); continue; } return await sendCallback(callbackUrl, { jobId, callbackKey, status: 'failed', result: { error: 'Failed to poll task status.', details: err.message } }); } } await sendCallback(callbackUrl, { jobId, callbackKey, status: 'failed', result: { error: 'Task polling timed out after several attempts.' } }); }, generate: async (jobDetails) => { const { imageDataUri, prompt, size } = jobDetails; await ghibai.security.decrypt(); try { const { data } = await axios.post( `${ghibai.api.base}${ghibai.api.endpoints.generate}`, { filesUrl: [''], files: [imageDataUri], prompt, size, nVariants: 1 }, { headers: ghibai.headers } ); const taskId = data?.data?.taskId; if (!taskId) { throw new Error('Failed to retrieve Task ID from the generation API.'); } await ghibai.getTask({ ...jobDetails, taskId }); } catch (err) { if (err.response?.status === 429) { await new Promise(r => setTimeout(r, 5000)); return await ghibai.generate(jobDetails); } await sendCallback(jobDetails.callbackUrl, { jobId: jobDetails.jobId, callbackKey: jobDetails.callbackKey, status: 'failed', result: { error: 'Failed to initiate image generation.', details: err.message } }); } } }; module.exports = async function(req, res) { const { imageDataUri, prompt, size, jobId, callbackUrl, callbackKey } = req.body; if (!imageDataUri || !prompt || !jobId || !callbackUrl || !callbackKey) { return res.status(400).json({ error: 'Missing required parameters for worker.' }); } res.status(202).json({ message: 'Job accepted by worker and is being processed in the background.' }); ghibai.generate({ imageDataUri, prompt, size, jobId, callbackUrl, callbackKey }); };