| const fs = require('fs'); | |
| const path = require('path'); | |
| const axios = require('axios'); | |
| const https = require('https'); | |
| const FormData = require('form-data'); | |
| const UPLOAD_URL = 'https://api.unblurimage.ai/api/common/upload/upload-image'; | |
| const CREATE_JOB_URL = 'https://api.unblurimage.ai/api/imgupscaler/v1/ai-image-upscaler-v2/create-job'; | |
| const PRODUCT_SERIAL = '79661c82d918475e7458944c2f66857e'; | |
| const TMP_DIR = '/tmp/upscaler'; | |
| const axiosInstance = axios.create({ | |
| timeout: 60000, | |
| httpsAgent: new https.Agent({ | |
| rejectUnauthorized: false, | |
| }), | |
| }); | |
| function ensureTmpDir() { | |
| if (!fs.existsSync(TMP_DIR)) { | |
| fs.mkdirSync(TMP_DIR, { recursive: true }); | |
| } | |
| } | |
| async function downloadImage(imageUrl) { | |
| const response = await axiosInstance.get(imageUrl, { | |
| responseType: 'arraybuffer', | |
| }); | |
| return Buffer.from(response.data); | |
| } | |
| function saveImageToTmp(imageBuffer) { | |
| ensureTmpDir(); | |
| const fileName = `image_${Date.now()}_${Math.random().toString(36).substr(2, 9)}.jpg`; | |
| const filePath = path.join(TMP_DIR, fileName); | |
| fs.writeFileSync(filePath, imageBuffer); | |
| return filePath; | |
| } | |
| function deleteFileAfterDelay(filePath, delayMs = 10000) { | |
| setTimeout(() => { | |
| try { | |
| if (fs.existsSync(filePath)) { | |
| fs.unlinkSync(filePath); | |
| } | |
| } catch (error) { | |
| console.error(`Failed to delete file ${filePath}:`, error.message); | |
| } | |
| }, delayMs); | |
| } | |
| async function uploadImage(filePath) { | |
| const imageBuffer = fs.readFileSync(filePath); | |
| const fileName = path.basename(filePath); | |
| const contentType = 'image/jpeg'; | |
| const form = new FormData(); | |
| form.append('file_name', fileName); | |
| form.append('file', imageBuffer, fileName); | |
| const uploadResponse = await axiosInstance.post(UPLOAD_URL, form, { | |
| headers: { | |
| ...form.getHeaders(), | |
| 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Mobile Safari/537.36', | |
| }, | |
| }); | |
| if (uploadResponse.data.code !== 100000) { | |
| throw new Error(`Upload failed: ${uploadResponse.data.message.en}`); | |
| } | |
| const uploadUrl = uploadResponse.data.result.url; | |
| await axiosInstance.put(uploadUrl, imageBuffer, { | |
| headers: { | |
| 'Content-Type': contentType, | |
| 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Mobile Safari/537.36', | |
| }, | |
| }); | |
| return { | |
| url: uploadUrl.split('?')[0], | |
| }; | |
| } | |
| async function createJob(imageUrl, upscaleLevel) { | |
| const form = new FormData(); | |
| form.append('original_image_url', imageUrl); | |
| form.append('upscale_type', upscaleLevel.toString()); | |
| const response = await axiosInstance.post(CREATE_JOB_URL, form, { | |
| headers: { | |
| ...form.getHeaders(), | |
| 'Product-Serial': PRODUCT_SERIAL, | |
| 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Mobile Safari/537.36', | |
| }, | |
| }); | |
| if (response.data.code !== 100000) { | |
| throw new Error(`Job creation failed: ${response.data.message.en}`); | |
| } | |
| return response.data.result; | |
| } | |
| async function upscaleImage(imageUrl, scaleNumber) { | |
| let tmpFilePath = null; | |
| try { | |
| if (!imageUrl) { | |
| throw new Error('Image URL is required'); | |
| } | |
| if (![2, 4, 8, 16].includes(scaleNumber)) { | |
| throw new Error('Scale number must be 2, 4, 8, or 16'); | |
| } | |
| const imageBuffer = await downloadImage(imageUrl); | |
| tmpFilePath = saveImageToTmp(imageBuffer); | |
| const uploadResult = await uploadImage(tmpFilePath); | |
| const result = await createJob(uploadResult.url, scaleNumber); | |
| deleteFileAfterDelay(tmpFilePath, 10000); | |
| return result; | |
| } catch (error) { | |
| if (tmpFilePath && fs.existsSync(tmpFilePath)) { | |
| fs.unlinkSync(tmpFilePath); | |
| } | |
| throw error; | |
| } | |
| } | |
| const handler = async (req, res) => { | |
| try { | |
| const { image_url, scale, key } = req.query; | |
| if (!image_url) { | |
| return res.status(400).json({ | |
| success: false, | |
| error: 'Missing required parameter: image_url' | |
| }); | |
| } | |
| if (!scale) { | |
| return res.status(400).json({ | |
| success: false, | |
| error: 'Missing required parameter: scale' | |
| }); | |
| } | |
| const scaleNum = parseInt(scale); | |
| if (![2, 4, 8, 16].includes(scaleNum)) { | |
| return res.status(400).json({ | |
| success: false, | |
| error: 'Scale must be 2, 4, 8, or 16' | |
| }); | |
| } | |
| const result = await upscaleImage(image_url, scaleNum); | |
| return res.json({ | |
| author: "Herza", | |
| success: true, | |
| data: result | |
| }); | |
| } catch (error) { | |
| res.status(500).json({ | |
| success: false, | |
| error: error.message, | |
| timestamp: new Date().toISOString() | |
| }); | |
| } | |
| }; | |
| module.exports = { | |
| name: 'Image Upscaler', | |
| description: 'Upscale images to 2x, 4x, 8x, or 16x resolution', | |
| type: 'GET', | |
| routes: ['api/tools/upscale'], | |
| tags: ['image', 'upscale', 'enhancement'], | |
| parameters: ['image_url', 'scale', 'key'], | |
| limit: 5, | |
| enabled: true, | |
| main: ['tools'], | |
| handler | |
| }; |