api / src /server /plugins /ai /ai_image_utils.js
OhMyDitzzy
fix: image editor
4c8cf27
import axios from "axios";
import { fileTypeFromBuffer } from "file-type";
import { readFileSync } from "fs";
import CryptoJS from "crypto-js";
const ModelMap = {
nano_banana: 2,
seed_dream: 5,
flux: 8,
qwen_image: 9
};
const IMAGE_TO_ANIME_PRESETS = {
manga: {
size: "2K",
aspect_ratio: "match_input_image",
output_format: "jpg",
sequential_image_generation: "disabled",
max_images: 1,
prompt: "Convert the provided image into a KOREAN-STYLE MANGA illustration. Apply strong stylization with clear and noticeable differences from the original image."
},
anime: {
size: "2K",
aspect_ratio: "match_input_image",
output_format: "jpg",
sequential_image_generation: "disabled",
max_images: 1,
prompt: "Convert the provided image into an ANIME-STYLE illustration. Apply strong stylization with clear and noticeable differences from the original image."
},
ghibli: {
size: "2K",
aspect_ratio: "match_input_image",
output_format: "jpg",
sequential_image_generation: "disabled",
max_images: 1,
prompt: "Convert the provided image into a STUDIO GHIBLI-STYLE illustration. Apply strong stylization with clear and noticeable differences from the original image."
},
cyberpunk: {
size: "2K",
aspect_ratio: "match_input_image",
output_format: "jpg",
sequential_image_generation: "disabled",
max_images: 1,
prompt: "Convert the provided image into a CYBERPUNK-STYLE illustration with neon colors, futuristic elements, and dark atmosphere."
},
watercolor: {
size: "2K",
aspect_ratio: "match_input_image",
output_format: "png",
sequential_image_generation: "disabled",
max_images: 1,
prompt: "Convert the provided image into a WATERCOLOR painting style with soft brush strokes and pastel colors."
},
pixelart: {
size: "2K",
aspect_ratio: "match_input_image",
output_format: "png",
sequential_image_generation: "disabled",
max_images: 1,
prompt: "Convert the provided image into PIXEL ART style with 8-bit retro gaming aesthetic."
},
sketch: {
size: "2K",
aspect_ratio: "match_input_image",
output_format: "jpg",
sequential_image_generation: "disabled",
max_images: 1,
prompt: "Convert the provided image into a detailed PENCIL SKETCH with realistic shading and artistic strokes."
},
oilpainting: {
size: "2K",
aspect_ratio: "match_input_image",
output_format: "jpg",
sequential_image_generation: "disabled",
max_images: 1,
prompt: "Convert the provided image into an OIL PAINTING style with thick brush strokes and rich colors."
}
};
const IMAGE_TO_ANIME_ENCRYPTED_PAYLOADS = {
manga: "L7p91uXhVyp5OOJthAyqjSqhlbM+RPZ8+h2Uq9tz6Y+4Agarugz8f4JjxjEycxEzuj/7+6Q0YY9jUvrfmqkucENhHAkMq1EOilzosQlw2msQpW2yRqV3C/WqvP/jrmSu3aUVAyeFhSbK3ARzowBzQYPVHtxwBbTWwlSR4tehnodUasnmftnY77c8gIFtL2ArNdzmPLx5H8O9un2U8WE4s0+xiFV3y4sbetHMN7rHh7DRIpuIQD4rKISR/vE+HeaHpRavXfsilr5P7Y6bsIo+RRFIPgX2ofbYYiATziqsjDeie4IlcOAVf1Pudqz8uk6YKM78CGxjF9iPLYQnkW+c6j96PNsg1Yk4Xz8/ZcdmHF4GGZe8ILYH/D0yyM1dsCkK1zY8ciL+6pAk4dHIZ/4k9A==",
ghibli: "L7p91uXhVyp5OOJthAyqjSqhlbM+RPZ8+h2Uq9tz6Y+4Agarugz8f4JjxjEycxEzuj/7+6Q0YY9jUvrfmqkucENhHAkMq1EOilzosQlw2msQpW2yRqV3C/WqvP/jrmSu3aUVAyeFhSbK3ARzowBzQYPVHtxwBbTWwlSR4tehnodUasnmftnY77c8gIFtL2ArNdzmPLx5H8O9un2U8WE4syzL5EYHGJWC1rlQM9xhNe1PViOsBSxmwHVwOdqtxZtcAJmGuzTgG7JVU7Hr9ZRwajhYK5yxQwSdJGwwR4jjS1yF9s9wKUQqgI+fYxaw7FZziLS+9JG5pTEjch4D0fpl+LO7vIynHN4cyu4DDeAUwNeYfbGMn2QQs+5OgMdViCAM1GkJk2jhlQm10rESTjDryw==",
anime: "L7p91uXhVyp5OOJthAyqjSqhlbM+RPZ8+h2Uq9tz6Y+4Agarugz8f4JjxjEycxEzuj/7+6Q0YY9jUvrfmqkucENhHAkMq1EOilzosQlw2msQpW2yRqV3C/WqvP/jrmSu3aUVAyeFhSbK3ARzowBzQYPVHtxwBbTWwlSR4tehnodUasnmftnY77c8gIFtL2ArNdzmPLx5H8O9un2U8WE4s7O2FxvQPCjt2uGmHPMOx1DsNSnLvzCKPVdz8Ob1cPHePmmquQZlsb/p+8gGv+cizSiOL4ts6GD2RxWN+K5MmpA/F3rQXanFUm4EL0g7qZCQbChRRQyaAyZuxtIdTKsmsMzkVKM5Sx96eV7bEjUAJ52j6NcP96INv2DhnWTP7gB6tltFQe8B8SPS2LuLRuPghA=="
};
export class AIEnhancer {
constructor() {
this.CREATED_BY = "Ditzzy";
this.NOTE = "Thank you for using this scrape, I hope you appreciate me for making this scrape by not deleting wm";
this.AES_KEY = "ai-enhancer-web__aes-key";
this.AES_IV = "aienhancer-aesiv";
this.HEADERS = {
"accept": "*/*",
"content-type": "application/json",
"Referer": "https://aienhancer.ai",
"Referrer-Policy": "strict-origin-when-cross-origin"
};
this.POLLING_INTERVAL = 2000;
this.MAX_POLLING_ATTEMPTS = 120;
}
encrypt(data) {
const plaintext = typeof data === "string" ? data : JSON.stringify(data);
return CryptoJS.AES.encrypt(
plaintext,
CryptoJS.enc.Utf8.parse(this.AES_KEY),
{
iv: CryptoJS.enc.Utf8.parse(this.AES_IV),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}
).toString();
}
decrypt(encryptedData) {
const decrypted = CryptoJS.AES.decrypt(
encryptedData,
CryptoJS.enc.Utf8.parse(this.AES_KEY),
{
iv: CryptoJS.enc.Utf8.parse(this.AES_IV),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}
);
return decrypted.toString(CryptoJS.enc.Utf8);
}
decryptToJSON(encryptedData) {
const decrypted = this.decrypt(encryptedData);
return JSON.parse(decrypted);
}
wrapResponse(data) {
return {
created_by: this.CREATED_BY,
note: this.NOTE,
results: data
};
}
async sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async processImage(image) {
let img;
let type;
if (typeof image === "string") {
img = readFileSync(image);
type = await fileTypeFromBuffer(img);
} else if (Buffer.isBuffer(image)) {
img = image;
type = await fileTypeFromBuffer(image);
} else {
throw new Error("Invalid image input: must be file path (string) or Buffer");
}
if (!type) {
throw new Error("Could not detect file type");
}
const allowedImageTypes = [
'image/jpeg',
'image/jpg',
'image/png',
'image/webp',
'image/gif',
'image/bmp'
];
if (!allowedImageTypes.includes(type.mime)) {
throw new Error(
`Unsupported format: ${type.mime}. Allowed: jpeg, jpg, png, webp, gif, bmp`
);
}
const imgbase64 = img.toString("base64");
return {
base64: `data:${type.mime};base64,${imgbase64}`,
mime: type.mime
};
}
async createTask(apiUrl, model, image, config) {
const { base64 } = await this.processImage(image);
const settings = typeof config === "string"
? config
: this.encrypt(config);
const requestConfig = {
url: apiUrl,
method: "POST",
headers: this.HEADERS,
data: {
model,
image: base64,
settings
}
};
const { data } = await axios.request(requestConfig);
if (data.code !== 100000 || !data.data.id) {
throw new Error(`Task creation failed: ${data.message}`);
}
return data.data.id;
}
async checkTaskStatus(resultUrl, taskId) {
const config = {
url: resultUrl,
method: "POST",
headers: this.HEADERS,
data: { task_id: taskId }
};
const { data } = await axios.request(config);
return data;
}
async pollTaskResult(resultUrl, taskId) {
let attempts = 0;
while (attempts < this.MAX_POLLING_ATTEMPTS) {
const response = await this.checkTaskStatus(resultUrl, taskId);
if (response.code !== 100000) {
throw new Error(`Status check failed: ${response.message}`);
}
const { status, error, output, input, completed_at } = response.data;
if ((status === "succeeded" || status === "success") && output && input) {
return {
id: taskId,
output,
input,
error,
status,
created_at: response.data.created_at,
started_at: response.data.started_at,
completed_at: completed_at
};
}
if (status === "failed" || status === "fail" || error) {
throw new Error(`Task failed: ${error || "Unknown error"}`);
}
console.log(`[${taskId} | ${status}] Polling attempt ${attempts + 1}/${this.MAX_POLLING_ATTEMPTS}`);
await this.sleep(this.POLLING_INTERVAL);
attempts++;
}
throw new Error(`Task timeout after ${this.MAX_POLLING_ATTEMPTS} attempts`);
}
async imageToAnime(image, preset = "anime") {
try {
const apiUrl = "https://aienhancer.ai/api/v1/r/image-enhance/create";
const resultUrl = "https://aienhancer.ai/api/v1/r/image-enhance/result";
const model = 5;
let config;
if (typeof preset === "string") {
if (IMAGE_TO_ANIME_PRESETS[preset]) {
config = IMAGE_TO_ANIME_PRESETS[preset];
}
else if (IMAGE_TO_ANIME_ENCRYPTED_PAYLOADS[preset]) {
config = IMAGE_TO_ANIME_ENCRYPTED_PAYLOADS[preset];
}
else {
config = preset;
}
} else {
config = preset;
}
const taskId = await this.createTask(apiUrl, model, image, config);
console.log(`✓ Task created: ${taskId}`);
const result = await this.pollTaskResult(resultUrl, taskId);
return this.wrapResponse(result);
} catch (error) {
throw new Error(`Image to anime conversion failed: ${error}`);
}
}
async ImageAIEditor(image, model, preset) {
try {
const apiUrl = "https://aienhancer.ai/api/v1/k/image-enhance/create";
const resultUrl = "https://aienhancer.ai/api/v1/k/image-enhance/result";
const modelId = ModelMap[model];
const taskId = await this.createTask(apiUrl, modelId, image, preset);
const result = await this.pollTaskResult(resultUrl, taskId);
return this.wrapResponse(result);
} catch (e) {
throw new Error("Image editor failed: " + e);
}
}
async AIImageRestoration(image, model, config) {
try {
const apiUrl = "https://aienhancer.ai/api/v1/r/image-enhance/create";
const resultUrl = "https://aienhancer.ai/api/v1/r/image-enhance/result";
const taskId = await this.createTask(apiUrl, model, image, config);
const result = await this.pollTaskResult(resultUrl, taskId);
return this.wrapResponse(result);
} catch (e) {
throw new Error(`An error occurred while upscaling the image: ${e}`);
}
}
async RemoveBackground(image, config = {}) {
try {
const apiUrl = "https://aienhancer.ai/api/v1/r/image-enhance/create";
const resultUrl = "https://aienhancer.ai/api/v1/r/image-enhance/result";
const model = 4;
const payloadConfig = config ? config : {
threshold: 0,
reverse: false,
background_type: "rgba",
format: "png",
};
const taskId = await this.createTask(apiUrl, model, image, payloadConfig);
const result = await this.pollTaskResult(resultUrl, taskId);
return this.wrapResponse(result);
} catch (e) {
throw new Error(`Remove background error: ${e}`);
}
}
decryptPayload(encryptedPayload) {
return this.decryptToJSON(encryptedPayload);
}
encryptConfig(config) {
return this.encrypt(config);
}
}