// apkmody.js — bundle standalone. npm deps: axios, cheerio, node:url /** * Scraper: apkmody.js * Source : mithra/apkmody.js * * Input : Function parameters / CLI args (e.g. query: string, url: string, options: object) * Output : Promise (JSON formatted scraping response / stream URL / media metadata) * * CLI Run: * node mithra/apkmody.js [functionName] [args...] * node cli.js mithra/apkmody.js [functionName] [args...] */ import * as __ns_525 from 'axios' const axios = __ns_525.default import * as __ns_526 from 'cheerio' const cheerio = __ns_526 import * as __ns_527 from 'node:url' const pathToFileURL = __ns_527.pathToFileURL /** * APKMODY Scraper & Downloader * Base: https://apkmody.mobi * Author: phrzy * Channel: https://whatsapp.com/channel/0029VbD1zGq6mYPUbtVh6U0L/121 */ const BASE = 'https://apkmody.mobi' const SLUG_RE = /\/(games|apps)\/[^/]+/ const SIZE_RE = /([\d.,]+)\s*(TB|GB|MB|KB)/i const SIZE_MULT = { b: 1, kb: 1024, mb: 1048576, gb: 1073741824, tb: 1099511627776 } const headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8', 'Accept-Language': 'id-ID,id;q=0.9,en-US;q=0.8,en;q=0.7', 'Referer': BASE + '/' } const client = axios.create({ timeout: 30000, headers, maxRedirects: 5, validateStatus: (s) => s >= 200 && s < 400 }) const clean = (s) => (s ? s.replace(/\s+/g, ' ').trim() : '') function parseSize(text) { const m = String(text || '').match(SIZE_RE) if (!m) return null const num = parseFloat(m[1].replace(/,/g, '')) const unit = m[2].toLowerCase() if (isNaN(num) || !SIZE_MULT[unit]) return null return { readable: clean(m[0]), bytes: Math.round(num * SIZE_MULT[unit]), number: num, unit: unit.toUpperCase() } } function packageFromIcon(url) { const m = String(url || '').match(/topmongo\.com\/packages\/([^/]+)/) return m ? m[1] : null } async function fetchPage(targetUrl) { const fullUrl = targetUrl.startsWith('http') ? targetUrl : BASE + (targetUrl.startsWith('/') ? '' : '/') + targetUrl const res = await client.get(fullUrl) return { url: res.request?.res?.responseUrl || fullUrl, status: res.status, body: res.data } } function parseSearchItems($) { const items = [] const seen = new Set() $('article.card a[href]').each((_, el) => { const m = ($(el).attr('href') || '').match(SLUG_RE) if (!m) return const url = BASE + m[0] if (seen.has(url)) return seen.add(url) const cover = $(el).find('img').first().attr('src') || $(el).find('img').first().attr('data-src') || null const title = clean($(el).find('.card-title .truncate').first().text()) const version = clean($(el).find('.card-excerpt').first().text()) if (title) { items.push({ title, version, cover, url }) } }) return items } function parseFiles($) { const files = [] const seen = new Set() // 1. Direct CDN / APK Links $('a[href]').each((_, el) => { const href = $(el).attr('href') || '' const text = clean($(el).text()) const size = parseSize(text) const isApk = href.endsWith('.apk') || href.includes('packages/') || href.includes('cdn.topmongo.com'); const isExcluded = href.includes('appstore.jooyfun.com') || href.includes('t.me') || href.includes('how-to'); if (isApk && !isExcluded && !seen.has(href)) { seen.add(href) files.push({ title: text || 'Download APK (Direct)', url: href.startsWith('http') ? href : BASE + href, size: size?.readable || 'Direct CDN Link' }) } }) // 2. Download page links if no direct CDN links if (!files.length) { $('a[href*="/download"]').each((_, el) => { const href = $(el).attr('href') || '' const text = clean($(el).text()) if (href && !seen.has(href) && !href.includes('appstore')) { seen.add(href) files.push({ title: text || 'Go to Download Page', url: href.startsWith('http') ? href : BASE + href, size: 'Web Download' }) } }) } return files } function parseDetail($) { const title = clean($('h1 strong, h1.entry-title, h1').first().text()) const version = clean($('.app-specs .spec-item:contains("Version") .spec-val, .has-small-font-size').first().text()) const mod = clean($('.app-specs .spec-item:contains("Mod") .spec-val, .has-accent-color').first().text()) const updated = clean($('.app-specs .spec-item:contains("Updated") .spec-val, time').first().text()) const icon = $('img[src*="topmongo.com/packages/"], .app-icon img').first().attr('src') || null const desc = clean($('.entry-content p, .app-description, .post-content').first().text()) return { title, version, mod: mod || 'Mod Info Available', updated, icon, package: packageFromIcon(icon), description: desc } } async function search(query, page = 1) { const q = String(query || '').trim() if (!q) throw new Error('Query pencarian kosong') const u = new URL(BASE + '/') u.searchParams.set('s', q) if (page && page > 1) u.searchParams.set('page', String(page)) const res = await fetchPage(u.toString()) const $ = cheerio.load(res.body) const items = parseSearchItems($) return { query: q, count: items.length, page, items } } async function detail(url) { const page = await fetchPage(url) const $ = cheerio.load(page.body) const parsed = parseDetail($) // Ambil Download links let files = parseFiles($) if (!files.length) { try { const dlPage = await fetchPage(url.replace(/\/$/, '') + '/download') files = parseFiles(cheerio.load(dlPage.body)) } catch {} } return { ...parsed, url: page.url, downloads: files } } const __dflt_9 = { search, detail, fetchPage, parseSize } export { search, detail, fetchPage, parseSize }; export default __dflt_9; // ───────────────────────────────────────────────────────────────────── // Self-test: jalankan langsung dengan `node apkmody.js [args...]` // ───────────────────────────────────────────────────────────────────── if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { const __exports = { fetchPage: fetchPage, search: search, detail: detail, parseSize: parseSize, packageFromIcon: packageFromIcon, parseSearchItems: parseSearchItems, parseFiles: parseFiles, parseDetail: parseDetail, default: __dflt_9 } const __fns = Object.fromEntries(Object.entries(__exports).filter(([, v]) => typeof v === 'function')) const __names = Object.keys(__fns) if (__names.length === 0) { console.log('Tidak ada fungsi yang bisa di-demo. Import modul ini dari kode lain.') } else { const __search = __names.find((n) => /search|cari/i.test(n)) const __name = __search || __names[0] const __fn = __fns[__name] const __isClass = /^class\s/.test(Function.prototype.toString.call(__fn)) let __args = process.argv.slice(2).map((a) => { try { return JSON.parse(a) } catch { return a } }) if (__args.length === 0 && __search) __args.push('naruto') if (__args.length === 0 && !__search && !__isClass && __fn.length > 0) { console.log('Fungsi: ' + __names.join(', ')) console.log('Contoh: node ' + "apkmody.js" + ' ' + __name + ' ') } else { ;(async () => { try { const r = __isClass ? new __fn(...__args) : await __fn(...__args) console.log(JSON.stringify(r, null, 2)) } catch (e) { console.error('Error: ' + (e && e.message ? e.message : e)) process.exitCode = 1 } })() } } }