Back to Home
Gistify Snippet
kyiov
Sep 3·1 file(s)
Public

Scraper apkmody

Scraper apkmody

#github-gist#javascript#gist:8823ce78fab8cbeec73db1484270ff4b#gistsync:2026-09-02T12:40:34Z
1
Raw
apkmody.jsjavascript
225 lines
1
2
3import * as __ns_525 from 'axios'
4const axios = __ns_525.default
5import * as __ns_526 from 'cheerio'
6const cheerio = __ns_526
7import * as __ns_527 from 'node:url'
8const pathToFileURL = __ns_527.pathToFileURL
9
10const BASE = 'https://apkmody.mobi'
11const SLUG_RE = /\/(games|apps)\/[^/]+/
12const SIZE_RE = /([\d.,]+)\s*(TB|GB|MB|KB)/i
13const SIZE_MULT = { b: 1, kb: 1024, mb: 1048576, gb: 1073741824, tb: 1099511627776 }
14
15const headers = {
16 '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',
17 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
18 'Accept-Language': 'id-ID,id;q=0.9,en-US;q=0.8,en;q=0.7',
19 'Referer': BASE + '/'
20}
21
22const client = axios.create({
23 timeout: 30000,
24 headers,
25 maxRedirects: 5,
26 validateStatus: (s) => s >= 200 && s < 400
27})
28
29const clean = (s) => (s ? s.replace(/\s+/g, ' ').trim() : '')
30
31function parseSize(text) {
32 const m = String(text || '').match(SIZE_RE)
33 if (!m) return null
34 const num = parseFloat(m[1].replace(/,/g, ''))
35 const unit = m[2].toLowerCase()
36 if (isNaN(num) || !SIZE_MULT[unit]) return null
37 return {
38 readable: clean(m[0]),
39 bytes: Math.round(num * SIZE_MULT[unit]),
40 number: num,
41 unit: unit.toUpperCase()
42 }
43}
44
45function packageFromIcon(url) {
46 const m = String(url || '').match(/topmongo\.com\/packages\/([^/]+)/)
47 return m ? m[1] : null
48}
49
50async function fetchPage(targetUrl) {
51 const fullUrl = targetUrl.startsWith('http') ? targetUrl : BASE + (targetUrl.startsWith('/') ? '' : '/') + targetUrl
52 const res = await client.get(fullUrl)
53 return {
54 url: res.request?.res?.responseUrl || fullUrl,
55 status: res.status,
56 body: res.data
57 }
58}
59
60function parseSearchItems($) {
61 const items = []
62 const seen = new Set()
63 $('article.card a[href]').each((_, el) => {
64 const m = ($(el).attr('href') || '').match(SLUG_RE)
65 if (!m) return
66 const url = BASE + m[0]
67 if (seen.has(url)) return
68 seen.add(url)
69 const cover = $(el).find('img').first().attr('src') || $(el).find('img').first().attr('data-src') || null
70 const title = clean($(el).find('.card-title .truncate').first().text())
71 const version = clean($(el).find('.card-excerpt').first().text())
72 if (title) {
73 items.push({ title, version, cover, url })
74 }
75 })
76 return items
77}
78
79function parseFiles($) {
80 const files = []
81 const seen = new Set()
82
83 $('a[href]').each((_, el) => {
84 const href = $(el).attr('href') || ''
85 const text = clean($(el).text())
86 const size = parseSize(text)
87
88 const isApk = href.endsWith('.apk') || href.includes('packages/') || href.includes('cdn.topmongo.com');
89 const isExcluded = href.includes('appstore.jooyfun.com') || href.includes('t.me') || href.includes('how-to');
90
91 if (isApk && !isExcluded && !seen.has(href)) {
92 seen.add(href)
93 files.push({
94 title: text || 'Download APK (Direct)',
95 url: href.startsWith('http') ? href : BASE + href,
96 size: size?.readable || 'Direct CDN Link'
97 })
98 }
99 })
100
101 if (!files.length) {
102 $('a[href*="/download"]').each((_, el) => {
103 const href = $(el).attr('href') || ''
104 const text = clean($(el).text())
105 if (href && !seen.has(href) && !href.includes('appstore')) {
106 seen.add(href)
107 files.push({
108 title: text || 'Go to Download Page',
109 url: href.startsWith('http') ? href : BASE + href,
110 size: 'Web Download'
111 })
112 }
113 })
114 }
115
116 return files
117}
118
119function parseDetail($) {
120 const title = clean($('h1 strong, h1.entry-title, h1').first().text())
121 const version = clean($('.app-specs .spec-item:contains("Version") .spec-val, .has-small-font-size').first().text())
122 const mod = clean($('.app-specs .spec-item:contains("Mod") .spec-val, .has-accent-color').first().text())
123 const updated = clean($('.app-specs .spec-item:contains("Updated") .spec-val, time').first().text())
124 const icon = $('img[src*="topmongo.com/packages/"], .app-icon img').first().attr('src') || null
125 const desc = clean($('.entry-content p, .app-description, .post-content').first().text())
126
127 return {
128 title,
129 version,
130 mod: mod || 'Mod Info Available',
131 updated,
132 icon,
133 package: packageFromIcon(icon),
134 description: desc
135 }
136}
137
138async function search(query, page = 1) {
139 const q = String(query || '').trim()
140 if (!q) throw new Error('Query pencarian kosong')
141 const u = new URL(BASE + '/')
142 u.searchParams.set('s', q)
143 if (page && page > 1) u.searchParams.set('page', String(page))
144 const res = await fetchPage(u.toString())
145 const $ = cheerio.load(res.body)
146 const items = parseSearchItems($)
147 return {
148 query: q,
149 count: items.length,
150 page,
151 items
152 }
153}
154
155async function detail(url) {
156 const page = await fetchPage(url)
157 const $ = cheerio.load(page.body)
158 const parsed = parseDetail($)
159
160 let files = parseFiles($)
161 if (!files.length) {
162 try {
163 const dlPage = await fetchPage(url.replace(/\/$/, '') + '/download')
164 files = parseFiles(cheerio.load(dlPage.body))
165 } catch {}
166 }
167
168 return {
169 ...parsed,
170 url: page.url,
171 downloads: files
172 }
173}
174
175const __dflt_9 = {
176 search,
177 detail,
178 fetchPage,
179 parseSize
180}
181
182export { search, detail, fetchPage, parseSize };
183export default __dflt_9;
184
185if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
186 const __exports = { fetchPage: fetchPage, search: search, detail: detail, parseSize: parseSize, packageFromIcon: packageFromIcon, parseSearchItems: parseSearchItems, parseFiles: parseFiles, parseDetail: parseDetail, default: __dflt_9 }
187 const __fns = Object.fromEntries(Object.entries(__exports).filter(([, v]) => typeof v === 'function'))
188 const __names = Object.keys(__fns)
189 if (__names.length === 0) {
190 console.log('Tidak ada fungsi yang bisa di-demo. Import modul ini dari kode lain.')
191 } else {
192 const __search = __names.find((n) => /search|cari/i.test(n))
193 const __name = __search || __names[0]
194 const __fn = __fns[__name]
195 const __isClass = /^class\s/.test(Function.prototype.toString.call(__fn))
196 const __defaultArgs = (() => {
197 const n = __name.toLowerCase()
198 if (/emoji/.test(n)) return [{ emoji: '😀😃' }]
199 if (/textpro|generate.*text|effect/.test(n)) return ['neon', 'Hello']
200 if (/image|wallpaper|search|query|anime|movie|manga|news|lyrics|wiki|quote|stalk|detail/.test(n)) return ['naruto']
201 if (/download|youtube|video|audio|music|spotify|tiktok|instagram|facebook|twitter|reels|terabox|media/.test(n)) return ['https://www.youtube.com/watch?v=dQw4w9WgXcQ']
202 if (/dork/.test(n)) return ['example.com', 'documents']
203 if (/tts|speech|prompt|chat|ask|translate/.test(n)) return ['Hello world']
204 if (/parse|format|normalize|list|features/.test(n)) return []
205 return ['test']
206 })()
207 let __args = __defaultArgs
208 if (process.argv.length > 2) __args = process.argv.slice(2).map((a) => { try { return JSON.parse(a) } catch { return a } })
209 if (__args.length === 0 && !__search && !__isClass && __fn.length > 0) {
210 console.log('Fungsi: ' + __names.join(', '))
211 console.log('Contoh: node ' + "apkmody.js" + ' ' + __name + ' <arg>')
212 } else {
213 ;(async () => {
214 try {
215 const r = __isClass ? new __fn(...__args) : await __fn(...__args)
216 console.log(JSON.stringify(r, null, 2))
217 } catch (e) {
218 console.error('Error: ' + (e && e.message ? e.message : e))
219 process.exitCode = 1
220 }
221 })()
222 }
223 }
224}
225
Discussion & Comments0

No comments yet — be the first to leave feedback or start a code discussion.

Sign in with your GitHub account to write comments and join the discussion.