// mega.js — bundle standalone. npm deps: crypto, node:url /** * Scraper: mega.js * Source : mithra/mega.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/mega.js [functionName] [args...] * node cli.js mithra/mega.js [functionName] [args...] */ import * as __ns_685 from 'crypto' const crypto = __ns_685.default import * as __ns_686 from 'node:url' const pathToFileURL = __ns_686.pathToFileURL /** * Mega.nz link resolver — bypass link mega.nz menjadi direct download URL. * * Mendukung format: * - https://mega.nz/#!! * - https://mega.nz/file/# * * Menggunakan API publik g.api.mega.co.nz (a='g', g=1) untuk mengambil * URL download + atribut terenkripsi, lalu mendekripsi nama file dengan key. */ const API = 'https://g.api.mega.co.nz/cs' const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' function base64UrlDecode(str) { const b64 = String(str).replace(/-/g, '+').replace(/_/g, '/') const pad = b64.length % 4 ? '='.repeat(4 - (b64.length % 4)) : '' return Buffer.from(b64 + pad, 'base64') } function parseMegaUrl(url) { const u = String(url || '').trim() let m = u.match(/mega\.nz\/#!([A-Za-z0-9_-]+)!([A-Za-z0-9_,-]+)/) if (!m) m = u.match(/mega\.nz\/file\/([A-Za-z0-9_-]+)#([A-Za-z0-9_,-]+)/) if (!m) m = u.match(/mega\.nz\/folder\/([A-Za-z0-9_-]+)#([A-Za-z0-9_,-]+)/) if (!m) return null return { id: m[1], key: m[2], isFolder: u.includes('/folder/') } } function decryptAttributes(at, keyBytes) { // key dari link = 32 byte: 16 byte AES key + 8 byte IV + 8 byte checksum // atribut dienkripsi AES-128-CBC dengan key 16 byte pertama & IV dari 8 byte berikutnya try { const aesKey = keyBytes.slice(0, 16) const iv = Buffer.concat([keyBytes.slice(16, 24), Buffer.alloc(8)]) const decipher = crypto.createDecipheriv('aes-128-cbc', aesKey, iv) let out = Buffer.concat([decipher.update(at), decipher.final()]) // hilangkan padding nol di akhir let end = out.length while (end > 0 && out[end - 1] === 0) end-- out = out.slice(0, end) const text = out.toString('utf8') if (!text.startsWith('MEGA')) return null return JSON.parse(text.slice(4)) } catch { return null } } /** * Resolve link mega.nz → { ok, name, size, url, direct_url, host, isFolder } */ async function resolveMega(url) { const parsed = parseMegaUrl(url) if (!parsed) return { ok: false, error: 'Format link mega tidak dikenali.' } const seq = Math.floor(Math.random() * 1e6) const body = [{ a: 'g', g: 1, p: parsed.id }] try { const r = await fetch(`${API}?id=${seq}&ak=&k=`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'User-Agent': UA, 'Origin': 'https://mega.nz', 'Referer': 'https://mega.nz/', }, body: JSON.stringify(body), signal: AbortSignal.timeout(30000), }) if (!r.ok) throw new Error(`HTTP ${r.status}`) const json = await r.json() const item = Array.isArray(json) ? json[0] : json if (!item || item === -9) return { ok: false, error: 'File tidak ditemukan di Mega (node tidak valid).' } if (item === -3) return { ok: false, error: 'Link Mega telah kadaluarsa.' } if (typeof item !== 'object' || !item.g) { return { ok: false, error: `Mega API error: ${JSON.stringify(item)}` } } const keyBytes = base64UrlDecode(parsed.key) const attrs = decryptAttributes(Buffer.from(item.at || '', 'base64'), keyBytes) const size = item.s ? Number(item.s) : 0 return { ok: true, id: parsed.id, name: attrs?.n || `mega-file-${parsed.id.slice(0, 6)}`, size, sizeText: size ? (size / 1024 / 1024).toFixed(1) + ' MB' : '?', direct_url: item.g, url, host: 'mega.nz', isFolder: parsed.isFolder, } } catch (e) { return { ok: false, error: e.message || String(e) } } } export { resolveMega }; export default resolveMega; // ───────────────────────────────────────────────────────────────────── // Self-test: jalankan langsung dengan `node mega.js [args...]` // ───────────────────────────────────────────────────────────────────── if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { const __exports = { resolveMega: resolveMega, default: resolveMega } 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 ' + "mega.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 } })() } } }