/***
@ Base: https://live3d.io/deepnude-ai-generator/
@ Author: Shannz
@ Note: Live3D cloth remover generator
***/
import axios from 'axios';
import FormData from 'form-data';
import fs from 'fs';
import crypto from 'crypto';
import path from 'path';
const CONFIG = {
BASE_URL: 'https://app.live3d.io',
CDN_URL: 'https://temp.live3d.io/',
ENDPOINTS: {
UPLOAD: '/aitools/upload-img',
CREATE: '/aitools/of/create',
STATUS: '/aitools/of/check-status'
},
SECRETS: {
FP: '78dc286eaeb7fb88586e07f0d18bf61b',
APP_ID: 'aifaceswap',
PUBLIC_KEY: `-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCwlO+boC6cwRo3UfXVBadaYwcX
0zKS2fuVNY2qZ0dgwb1NJ+/Q9FeAosL4ONiosD71on3PVYqRUlL5045mvH2K9i8b
AFVMEip7E6RMK6tKAAif7xzZrXnP1GZ5Rijtqdgwh+YmzTo39cuBCsZqK9oEoeQ3
r/myG9S+9cR5huTuFQIDAQAB
-----END PUBLIC KEY-----`
},
HEADERS: {
'User-Agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Mobile Safari/537.36',
'Accept': 'application/json, text/plain, */*',
'sec-ch-ua-platform': '"Android"',
'theme-version': '83EmcUoQTUv50LhNx0VrdcK8rcGexcP35FcZDcpgWsAXEyO4xqL5shCY6sFIWB2Q',
'origin': 'https://live3d.io',
'referer': 'https://live3d.io/',
'priority': 'u=1, i'
}
};
const utils = {
genHex: (bytes) => crypto.randomBytes(bytes).toString('hex'),
genRandomString: (length) => {
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let result = "";
for (let i = 0; i < length; i++) result += chars.charAt(Math.floor(Math.random() * chars.length));
return result;
},
aesEncrypt: (plaintext, keyStr, ivStr) => {
const key = Buffer.from(keyStr, 'utf8');
const iv = Buffer.from(ivStr, 'utf8');
const cipher = crypto.createCipheriv('aes-128-cbc', key, iv);
let encrypted = cipher.update(plaintext, 'utf8', 'base64');
encrypted += cipher.final('base64');
return encrypted;
},
rsaEncrypt: (data) => {
const buffer = Buffer.from(data, 'utf8');
const encrypted = crypto.publicEncrypt({
key: CONFIG.SECRETS.PUBLIC_KEY,
padding: crypto.constants.RSA_PKCS1_PADDING,
}, buffer);
return encrypted.toString('base64');
},
generateHeaders: () => {
const aesKey = utils.genRandomString(16);
const xCode = Date.now().toString();
const xGuide = utils.rsaEncrypt(aesKey);
const plaintextFp = `${CONFIG.SECRETS.APP_ID}:${CONFIG.SECRETS.FP}`;
const fp1 = utils.aesEncrypt(plaintextFp, aesKey, aesKey);
return {
...CONFIG.HEADERS,
'x-code': xCode,
'x-guide': xGuide,
'fp': CONFIG.SECRETS.FP,
'fp1': fp1
};
}
};
export const live3d = {
generate: async (imagePath) => {
const originFrom = utils.genHex(8);
const requestFrom = 9;
console.log(`\n=== Live3D Generator [Hex: ${originFrom}] ===`);
try {
if (!fs.existsSync(imagePath)) return console.log(`[!] File ${imagePath} tidak ditemukan.`);
process.stdout.write(`[1/3] Uploading image... `);
const form = new FormData();
form.append('file', fs.createReadStream(imagePath));
form.append('fn_name', 'cloth-change');
form.append('request_from', requestFrom.toString());
form.append('origin_from', originFrom);
const uploadHeaders = { ...utils.generateHeaders(), ...form.getHeaders() };
const uploadRes = await axios.post(CONFIG.BASE_URL + CONFIG.ENDPOINTS.UPLOAD, form, { headers: uploadHeaders });
let serverPath = uploadRes.data?.data;
if (typeof serverPath === 'object' && serverPath.path) serverPath = serverPath.path;
if (!serverPath) throw new Error("Gagal mendapatkan path server.");
console.log(`Done.`);
process.stdout.write(`[2/3] Submitting task... `);
const submitPayload = {
"fn_name": "cloth-change",
"call_type": 3,
"input": {
"source_image": serverPath,
"prompt": "best quality, naked, nude",
"cloth_type": "full_outfits",
"request_from": requestFrom,
"type": 1
},
"request_from": requestFrom,
"origin_from": originFrom
};
const submitRes = await axios.post(CONFIG.BASE_URL + CONFIG.ENDPOINTS.CREATE, submitPayload, {
headers: { ...utils.generateHeaders(), 'Content-Type': 'application/json' }
});
const taskId = submitRes.data?.data?.task_id;
if (!taskId) throw new Error("Gagal mendapatkan Task ID.");
console.log(`Done (ID: ${taskId})`);
let isCompleted = false;
let attempts = 0;
let resultUrl = null;
const maxAttempts = 40;
while (!isCompleted && attempts < maxAttempts) {
attempts++;
await new Promise(r => setTimeout(r, 3000));
const statusPayload = {
"task_id": taskId,
"fn_name": "cloth-change",
"call_type": 3,
"consume_type": 0,
"request_from": requestFrom,
"origin_from": originFrom
};
const statusRes = await axios.post(CONFIG.BASE_URL + CONFIG.ENDPOINTS.STATUS, statusPayload, {
headers: { ...utils.generateHeaders(), 'Content-Type': 'application/json' }
});
const data = statusRes.data?.data;
if (!data) continue;
const status = data.status;
process.stdout.clearLine();
process.stdout.cursorTo(0);
if (status === 2) {
resultUrl = data.result_image;
if (resultUrl && !resultUrl.startsWith('http')) {
resultUrl = CONFIG.CDN_URL + resultUrl;
}
process.stdout.write(`[3/3] Status: Success!\n`);
isCompleted = true;
} else if (status === 1) {
const dots = ".".repeat((attempts % 3) + 1);
process.stdout.write(`[3/3] Status: Generating${dots} `);
} else {
process.stdout.write(`[3/3] Status: Queue (Rank ${data.rank})... `);
}
}
if (!resultUrl) throw new Error("Timeout generating image.");
console.log(`\n[RESULT] ${resultUrl}\n`);
return resultUrl;
} catch (error) {
console.log(`\n[X] Error: ${error.message}`);
if (error.response) console.log(` Server Msg: ${JSON.stringify(error.response.data)}`);
return null;
}
}
};