aimorph.mjs

javascriptCreated 18 Feb 2026, 04:44181 views
Wrapper from apk AI Morph
#tools#ai#nanobanana
javascript
/*
 @ 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)));