aimorph.mjs
javascript•Created 18 Feb 2026, 04:44•181 views
Wrapper from apk AI Morph
#tools#ai#nanobanana
javascript
0 lines
/*
@ Base: https://play.google.com/store/apps/details?id=photoeditor.aiart.animefilter.snapai/
@ Author: Shannz
@ Note: Wrapper from apk AI Morph
*/
import fs from 'fs';
import path from 'path';
import axios from 'axios';
import crypto from 'crypto';
import { v4 as uuidv4 } from 'uuid';
const PUBLIC_KEY_STRING = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo+yvc35R8VPsfy1ScmQap+vVg/IYTcZCiJP5iiIo0HFLBrfDhwZ30wpvQ8lpezTN3exdZU3edIspp+weCgifbjFEyI7/Ecce7GTYXZyLncBrjzvO6IohPnaz/hx7+Uy6eNw8DNk15sxcJrQeSOULtOWJJ8dJ2IbR1eRIp0PXwJeXqdfoT52WzT/FaNzwh7sWmt4Zl8cw9o9JvdTqdU3WsCsdqsOXWIgyP/UIFWM+uu7P1xJ/DY40nMokHlG+fDdiT0us5Vu4LNUt3Er8OOZynnOESSQUocSvpb9UOcK5SurLCjWsk0RnQY2RBQluBnC9isJK5RC9FyK/5ezjmaQ1hQIDAQAB";
const PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----\n${PUBLIC_KEY_STRING}\n-----END PUBLIC KEY-----`;
function getCurrentDate() {
const d = new Date();
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
function decryptResponse(encryptedData, sessionKey) {
try {
const aesKey = sessionKey.substring(0, 16);
const aesIv = sessionKey.substring(16, 32);
const decipher = crypto.createDecipheriv('aes-128-cbc', Buffer.from(aesKey, 'utf8'), Buffer.from(aesIv, 'utf8'));
let decrypted = decipher.update(encryptedData, 'base64', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
} catch (error) {
console.error("[!] Gagal dekripsi:", error.message);
return null;
}
}
function generateEncryptedBody(payloadJson) {
const sessionKey = uuidv4().replace(/-/g, '');
const aesKey = sessionKey.substring(0, 16);
const aesIv = sessionKey.substring(16, 32);
const cipher = crypto.createCipheriv('aes-128-cbc', Buffer.from(aesKey, 'utf8'), Buffer.from(aesIv, 'utf8'));
let encryptedData = cipher.update(JSON.stringify(payloadJson), 'utf8', 'base64');
encryptedData += cipher.final('base64');
const encryptedKeyBuffer = crypto.publicEncrypt(
{ key: PUBLIC_KEY_PEM, padding: crypto.constants.RSA_PKCS1_PADDING },
Buffer.from(sessionKey, 'utf8')
);
return {
rv: 1,
ki: encryptedKeyBuffer.toString('base64'),
data: encryptedData,
sessionKey: sessionKey
};
}
async function executeRequest(endpoint, payload) {
try {
console.log(`[-] Fetching credentials...`);
const { data } = await axios.get('https://www.kitsulabs.xyz/api/frida-hook/a/bd/jniutils/TokenUtils');
if (!data.success) throw new Error('Gagal mengambil token dari server.');
const { uid, token } = data;
console.log(`[-] Encrypting payload...`);
const encryptedBody = generateEncryptedBody(payload);
const currentTime = Date.now().toString();
await new Promise(res => setTimeout(res, 1000));
console.log(`[-] Sending request to ${endpoint.split('/').slice(-3).join('/')}...`);
const res = await axios.post(endpoint,
{
rv: 1,
ki: encryptedBody.ki,
data: encryptedBody.data
},
{
headers: {
'User-Agent': 'okhttp/4.12.0',
'Accept-Encoding': 'gzip',
'--v2-time': currentTime,
'uid': uid,
'token': token,
'content-type': 'application/json; charset=utf-8'
}
});
console.log(`[-] Decrypting response...`);
const decryptedRaw = decryptResponse(res.data.data, encryptedBody.sessionKey);
if (!decryptedRaw) throw new Error('Gagal mendekripsi respon dari server.');
const responseData = JSON.parse(decryptedRaw);
const baseUrl = 'https://hardstonepte.ltd/hs-us/';
const rawResult = responseData.image_url || responseData.result_url;
if (rawResult) {
if (Array.isArray(rawResult)) {
const finalResult = rawResult.map(relativePath => {
const cleanPath = relativePath.startsWith('/') ? relativePath.slice(1) : relativePath;
return baseUrl + cleanPath;
});
return { success: true, author: 'shannz', result: finalResult };
}
else if (typeof rawResult === 'string') {
const cleanPath = rawResult.startsWith('/') ? rawResult.slice(1) : rawResult;
const finalUrl = baseUrl + cleanPath;
return { success: true, author: 'shannz', result: finalUrl };
}
}
return {
success: false,
author: 'shannz',
message: 'Gagal parsing URL gambar',
debug: responseData
};
} catch (err) {
console.error('Request Error:', err.response?.data || err.message);
return { success: false, author: 'shannz', error: err.message };
}
}
export const morph = {
getRetakeStyles: async () => {
return [
"Funny",
"Calm",
"Smile",
"Surprise",
"Sad"
];
},
getImg2imgStyles: async () => {
try {
console.log('[-] Fetching Create Styles...');
const { data } = await axios.get('https://hardstonepte.ltd/snapAi/avatar/home_config.json');
if (!data || !data.category) {
throw new Error('Invalid JSON structure');
}
const parsedStyles = [];
data.category.forEach(cat => {
if (cat.titleMap?.en?.title === "Banner" || cat.titleMap?.en?.title === "Function") return;
const categoryName = cat.titleMap?.en?.title || cat.titleMap?.en || "Other";
const items = [];
if (cat.items && Array.isArray(cat.items)) {
cat.items.forEach(item => {
if (item.style_name && item.style_id) {
items.push({
name: item.style_name,
id: item.style_id,
pkgId: item.packageID,
description: item.titleMap?.en?.description || "-"
});
}
});
}
if (items.length > 0) {
parsedStyles.push({
category: categoryName,
styles: items
});
}
});
return { success: true, author: 'shannz', data: parsedStyles };
} catch (err) {
console.error('Get Create Styles Error:', err.message);
return { success: false, author: 'shannz', error: err.message };
}
},
upload: async (filePath, folderPrefix = 'snap_img2img/upload') => {
const fileData = fs.readFileSync(filePath);
const fileSize = fileData.length;
const dateStr = getCurrentDate();
const randomUuid = uuidv4();
const storagePath = `${folderPrefix}/${dateStr}/${randomUuid}_0.jpg`;
const encodedPath = encodeURIComponent(storagePath);
console.log(`[-] Init Upload: ${storagePath}`);
const initUrl = `https://firebasestorage.googleapis.com/v0/b/stn2_hs_us/o?name=${encodedPath}&uploadType=resumable`;
const headers = {
'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 15; 25028RN03A Build/AP3A.240905.015.A2)',
'Connection': 'Keep-Alive',
'Accept-Encoding': 'gzip',
'x-firebase-appcheck': 'eyJlcnJvciI6IlVOS05PV05fRVJST1IifQ==',
'X-Firebase-Storage-Version': 'Android/21.0.2',
'x-firebase-gmpid': '1:890704113682:android:4fe6bc1e015020503a28cb',
'Content-Type': 'application/x-www-form-urlencoded'
};
const initRes = await axios.post(initUrl, '', {
headers: { ...headers, 'X-Goog-Upload-Command': 'start', 'X-Goog-Upload-Protocol': 'resumable', 'Content-Length': '0' }
});
const uploadUrl = initRes.headers['x-goog-upload-url'];
if (!uploadUrl) throw new Error('Gagal init upload: Server tidak memberikan URL Upload.');
console.log(`[-] Uploading Data...`);
const uploadRes = await axios.post(uploadUrl, fileData, {
headers: {
...headers,
'X-Goog-Upload-Command': 'upload, finalize',
'X-Goog-Upload-Offset': '0',
'X-Goog-Upload-Protocol': 'resumable',
'Content-Length': fileSize.toString()
}
});
const downloadToken = uploadRes.data.downloadTokens;
if (!downloadToken) throw new Error('Upload sukses tapi tidak ada downloadTokens.');
return { imagePath: storagePath };
},
img2img: async (filePath, style) => {
try {
console.log(`[-] Memulai proses Create...`);
const { imagePath } = await morph.upload(filePath, 'snap_img2img/upload');
console.log(`[+] Upload Berhasil: ${imagePath}`);
const payload = {
"image_name": imagePath,
"nb": "stn2_hs_us",
"pro_t": "",
"style_id": style,
"strength": "50",
"bs": "4",
"ratio": 1,
"is_first": "1",
"gender": "male"
};
return await executeRequest('https://ai.hardstonepte.ltd/snap/img2img/v2/', payload);
} catch (err) {
return { success: false, error: err.message };
}
},
edit: async (filePath, prompt) => {
try {
console.log(`[-] Memulai proses Image Edit...`);
const { imagePath } = await morph.upload(filePath, 'snap_img2img/upload');
console.log(`[+] Upload Berhasil: ${imagePath}`);
const payload = {
"image_name": imagePath,
"nb": "stn2_hs_us",
"prompt": prompt
};
return await executeRequest('https://ai.hardstonepte.ltd/snap/chat/edit/v2/', payload);
} catch (err) {
return { success: false, error: err.message };
}
},
retake: async (filePath, styleName) => {
try {
console.log(`[-] Memulai proses Retake...`);
const { imagePath } = await morph.upload(filePath, 'snap_retake/upload');
console.log(`[+] Upload Berhasil: ${imagePath}`);
const payload = {
"image_name": imagePath,
"nb": "stn2_hs_us",
"style_name": styleName
};
return await executeRequest('https://ai.hardstonepte.ltd/snap/ai/retake/v2/', payload);
} catch (err) {
return { success: false, error: err.message };
}
},
enhance: async (filePath) => {
try {
console.log(`[-] Memulai proses HD/Enhance...`);
const { imagePath } = await morph.upload(filePath, 'snap_single/upload');
console.log(`[+] Upload Berhasil: ${imagePath}`);
const payload = {
"image_name": imagePath,
"nb": "stn2_hs_us"
};
return await executeRequest('https://ai.hardstonepte.ltd/snap/single/enhance/v2/', payload);
} catch (err) {
return { success: false, error: err.message };
}
}
};
// 1. Get Img2img Style
// morph.getImg2imgStyles().then(a => console.log(JSON.stringify(a, null, 2)));
// 2. Img2img
// morph.img2img('./anu.jpeg', 'Roblox').then(a => console.log('Create:', JSON.stringify(a, null, 2)));
// 3. Edit (Ubah dengan Prompt)
// morph.edit('./anu.jpeg', 'make this person hold a sword').then(a => console.log('Edit:', JSON.stringify(a, null, 2)));
// 4. Get Retake Style
// morph.getRetakeStyles().then(a => console.log(a));
// 5. Retake (Ubah Gaya Wajah/Ekspresi)
// morph.retake('./anu.jpeg', 'Funny').then(a => console.log('Retake:', JSON.stringify(a, null, 2)));
// 6. Enhance (HD Image)
// morph.enhance('./anu.jpeg').then(a => console.log('Enhance:', JSON.stringify(a, null, 2)));