| | |
| | |
| | |
| | |
| |
|
| | 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 }); |
| | }; |