/** * Name Scrape: Web2Apk * Credit By Zx * Description: Website To Apk * Sumber Kode: https://whatsapp.com/channel/0029VbDLqe7EquiSF4STU13o * Note: Kalau Mau di sher lagi harap jangan hapus credit atau sumber saluran * **/ import fs from 'fs' import path from 'path' import os from 'os' import axios from 'axios' class Web2Apk { constructor({ apiUrl = 'https://webappcreator.amethystlab.org/api/build-apk', baseUrl = 'https://webappcreator.amethystlab.org' } = {}) { this.apiUrl = apiUrl this.baseUrl = baseUrl } isValidUrl(url) { return /^https?:\/\//i.test(url) } buildPackageName(appName) { const cleaned = appName.toLowerCase().replace(/[^a-z0-9]/g, '') return `com.${cleaned || 'app'}.web2apk` } saveIconBuffer(buffer) { const tempDir = path.join(os.tmpdir(), 'web2apk') if (!fs.existsSync(tempDir)) fs.mkdirSync(tempDir, { recursive: true }) const iconPath = path.join(tempDir, `icon_${Date.now()}.png`) fs.writeFileSync(iconPath, buffer) return iconPath } async build({ url, appName, iconBuffer, versionName = '1.0.0', versionCode = 1 }) { if (!this.isValidUrl(url)) throw new Error('URL harus diawali dengan http:// atau https://') if (!appName) throw new Error('Nama aplikasi tidak boleh kosong.') if (!iconBuffer) throw new Error('Icon aplikasi wajib disertakan.') const packageName = this.buildPackageName(appName) const iconPath = this.saveIconBuffer(iconBuffer) let projectId = null try { const FormDataNode = (await import('form-data')).default const form = new FormDataNode() form.append('websiteUrl', url) form.append('appName', appName) form.append('icon', fs.createReadStream(iconPath), { filename: 'icon.png', contentType: 'image/png' }) form.append('packageName', packageName) form.append('versionName', versionName) form.append('versionCode', String(versionCode)) // Pastikan string console.log('📤 Mengirim file ke server (Server lambat, mohon tunggu 1-5 menit)...') const response = await axios.post(this.apiUrl, form, { headers: { 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36', 'Accept': 'application/json, text/plain, */*', 'Origin': this.baseUrl, 'Referer': `${this.baseUrl}/`, ...form.getHeaders() }, maxContentLength: Infinity, maxBodyLength: Infinity, timeout: 600000, validateStatus: (status) => status >= 200 && status < 600 }) const data = response.data console.log(`✅ Server merespons dengan status HTTP ${response.status}`) if (response.status >= 400) { const errorMsg = (data && (data.error || data.message)) || `Server mengembalikan error HTTP ${response.status}` throw new Error(errorMsg) } if (data.success && data.downloadUrl) { return { success: true, appName, packageName, downloadUrl: data.downloadUrl.startsWith('http') ? data.downloadUrl : `${this.baseUrl}${data.downloadUrl}` } } if (data.queued || data.projectId) { projectId = data.projectId console.log(`✅ Request diterima! Project ID: ${projectId}`) } else { throw new Error('Respon tidak dikenali dari server: ' + JSON.stringify(data)) } console.log(`⏳ Build masuk antrian. Memulai polling status...`) const statusUrl = `${this.baseUrl}/api/build-status/${projectId}` const maxAttempts = 120 // Maksimal polling 10 menit let attempt = 0 while (attempt < maxAttempts) { await new Promise(resolve => setTimeout(resolve, 5000)) // Tunggu 5 detik attempt++ try { const statusResponse = await axios.get(statusUrl, { headers: { 'Accept': 'application/json', 'Origin': this.baseUrl, 'Referer': `${this.baseUrl}/`, 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36' }, timeout: 15000 }) const status = statusResponse.data if (status.status === 'completed') { console.log('\n✅ Build Selesai!') const downloadPath = status.downloadUrl || `/download/${projectId}.apk` return { success: true, appName, packageName, downloadUrl: downloadPath.startsWith('http') ? downloadPath : `${this.baseUrl}${downloadPath}` } } else if (status.status === 'failed') { throw new Error(status.error || 'Build gagal di server.') } else { const queueInfo = status.queueInfo ? `(${status.queueInfo.currentBuilds}/${status.queueInfo.maxConcurrent} slot)` : '' const pos = status.position ? `Posisi: ${status.position}` : 'Sedang diproses' process.stdout.write( `\r⏳ Status: ${status.status.toUpperCase().padEnd(10)} | ` + `${pos.padEnd(15)} | ${queueInfo.padEnd(20)} | Cek ke-${attempt} ` ) } } catch (err) { console.warn(`\n⚠️ Gagal cek status (percobaan ${attempt}): ${err.message}`) } } throw new Error('Timeout: Build memakan waktu lebih dari 10 menit.') } finally { if (fs.existsSync(iconPath)) fs.unlinkSync(iconPath) } } } export default Web2Apk async function run() { console.log('⏳ Memulai proses pembuatan APK...\n'); try { const web2apk = new Web2Apk(); console.log('📥 Mengunduh icon aplikasi'); const iconResponse = await axios.get( 'https://placehold.co/512x512/4285F4/FFFFFF/png?text=GoogleKu', { responseType: 'arraybuffer', timeout: 30000 } ); const iconBuffer = Buffer.from(iconResponse.data); const appConfig = { url: 'https://google.com', appName: 'GoogleKu', iconBuffer: iconBuffer, versionName: '1.0.0', versionCode: 1 }; console.log('🚀 Mengirim request build ke server AmethystLab...'); const result = await web2apk.build(appConfig); console.log('\n\n✅ Build Berhasil!'); console.log('-------------------'); console.log(`📱 Nama Aplikasi : ${result.appName}`); console.log(`📦 Package Name : ${result.packageName}`); console.log(`🔗 Link Download : ${result.downloadUrl}`); console.log('-------------------'); } catch (error) { console.error('\n\n❌ Terjadi kesalahan saat mem-build APK:'); console.error(error.message); if (error.response) { console.error('Response status:', error.response.status); console.error('Response data:', error.response.data); } } } run();