Back to Home
Gistify Snippet
Public
Mega.nz Direct Link Resolver & AES Decryptor
Bypasses Mega.nz file & folder share links into direct CDN download URLs by querying Mega API (g.api.mega.co.nz) and decrypting file attributes using 128-bit AES keys.
#mega#downloader#aes#crypto#javascript#tools
2
Rawmega.jsjavascript
157 lines| 1 | // mega.js — bundle standalone. npm deps: crypto, node:url |
| 2 | |
| 3 | /** |
| 4 | * Scraper: mega.js |
| 5 | * Source : mithra/mega.js |
| 6 | * |
| 7 | * Input : Function parameters / CLI args (e.g. query: string, url: string, options: object) |
| 8 | * Output : Promise<Object | Array | Buffer> (JSON formatted scraping response / stream URL / media metadata) |
| 9 | * |
| 10 | * CLI Run: |
| 11 | * node mithra/mega.js [functionName] [args...] |
| 12 | * node cli.js mithra/mega.js [functionName] [args...] |
| 13 | */ |
| 14 | |
| 15 | import * as __ns_685 from 'crypto' |
| 16 | const crypto = __ns_685.default |
| 17 | import * as __ns_686 from 'node:url' |
| 18 | const pathToFileURL = __ns_686.pathToFileURL |
| 19 | |
| 20 | /** |
| 21 | * Mega.nz link resolver — bypass link mega.nz menjadi direct download URL. |
| 22 | * |
| 23 | * Mendukung format: |
| 24 | * - https://mega.nz/#!<id>!<key> |
| 25 | * - https://mega.nz/file/<id>#<key> |
| 26 | * |
| 27 | * Menggunakan API publik g.api.mega.co.nz (a='g', g=1) untuk mengambil |
| 28 | * URL download + atribut terenkripsi, lalu mendekripsi nama file dengan key. |
| 29 | */ |
| 30 | |
| 31 | const API = 'https://g.api.mega.co.nz/cs' |
| 32 | 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' |
| 33 | |
| 34 | function base64UrlDecode(str) { |
| 35 | const b64 = String(str).replace(/-/g, '+').replace(/_/g, '/') |
| 36 | const pad = b64.length % 4 ? '='.repeat(4 - (b64.length % 4)) : '' |
| 37 | return Buffer.from(b64 + pad, 'base64') |
| 38 | } |
| 39 | |
| 40 | function parseMegaUrl(url) { |
| 41 | const u = String(url || '').trim() |
| 42 | let m = u.match(/mega\.nz\/#!([A-Za-z0-9_-]+)!([A-Za-z0-9_,-]+)/) |
| 43 | if (!m) m = u.match(/mega\.nz\/file\/([A-Za-z0-9_-]+)#([A-Za-z0-9_,-]+)/) |
| 44 | if (!m) m = u.match(/mega\.nz\/folder\/([A-Za-z0-9_-]+)#([A-Za-z0-9_,-]+)/) |
| 45 | if (!m) return null |
| 46 | return { id: m[1], key: m[2], isFolder: u.includes('/folder/') } |
| 47 | } |
| 48 | |
| 49 | function decryptAttributes(at, keyBytes) { |
| 50 | // key dari link = 32 byte: 16 byte AES key + 8 byte IV + 8 byte checksum |
| 51 | // atribut dienkripsi AES-128-CBC dengan key 16 byte pertama & IV dari 8 byte berikutnya |
| 52 | try { |
| 53 | const aesKey = keyBytes.slice(0, 16) |
| 54 | const iv = Buffer.concat([keyBytes.slice(16, 24), Buffer.alloc(8)]) |
| 55 | const decipher = crypto.createDecipheriv('aes-128-cbc', aesKey, iv) |
| 56 | let out = Buffer.concat([decipher.update(at), decipher.final()]) |
| 57 | // hilangkan padding nol di akhir |
| 58 | let end = out.length |
| 59 | while (end > 0 && out[end - 1] === 0) end-- |
| 60 | out = out.slice(0, end) |
| 61 | const text = out.toString('utf8') |
| 62 | if (!text.startsWith('MEGA')) return null |
| 63 | return JSON.parse(text.slice(4)) |
| 64 | } catch { |
| 65 | return null |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | /** |
| 70 | * Resolve link mega.nz → { ok, name, size, url, direct_url, host, isFolder } |
| 71 | */ |
| 72 | async function resolveMega(url) { |
| 73 | const parsed = parseMegaUrl(url) |
| 74 | if (!parsed) return { ok: false, error: 'Format link mega tidak dikenali.' } |
| 75 | |
| 76 | const seq = Math.floor(Math.random() * 1e6) |
| 77 | const body = [{ a: 'g', g: 1, p: parsed.id }] |
| 78 | |
| 79 | try { |
| 80 | const r = await fetch(`${API}?id=${seq}&ak=&k=`, { |
| 81 | method: 'POST', |
| 82 | headers: { |
| 83 | 'Content-Type': 'application/json', |
| 84 | 'User-Agent': UA, |
| 85 | 'Origin': 'https://mega.nz', |
| 86 | 'Referer': 'https://mega.nz/', |
| 87 | }, |
| 88 | body: JSON.stringify(body), |
| 89 | signal: AbortSignal.timeout(30000), |
| 90 | }) |
| 91 | if (!r.ok) throw new Error(`HTTP ${r.status}`) |
| 92 | const json = await r.json() |
| 93 | const item = Array.isArray(json) ? json[0] : json |
| 94 | |
| 95 | if (!item || item === -9) return { ok: false, error: 'File tidak ditemukan di Mega (node tidak valid).' } |
| 96 | if (item === -3) return { ok: false, error: 'Link Mega telah kadaluarsa.' } |
| 97 | if (typeof item !== 'object' || !item.g) { |
| 98 | return { ok: false, error: `Mega API error: ${JSON.stringify(item)}` } |
| 99 | } |
| 100 | |
| 101 | const keyBytes = base64UrlDecode(parsed.key) |
| 102 | const attrs = decryptAttributes(Buffer.from(item.at || '', 'base64'), keyBytes) |
| 103 | const size = item.s ? Number(item.s) : 0 |
| 104 | |
| 105 | return { |
| 106 | ok: true, |
| 107 | id: parsed.id, |
| 108 | name: attrs?.n || `mega-file-${parsed.id.slice(0, 6)}`, |
| 109 | size, |
| 110 | sizeText: size ? (size / 1024 / 1024).toFixed(1) + ' MB' : '?', |
| 111 | direct_url: item.g, |
| 112 | url, |
| 113 | host: 'mega.nz', |
| 114 | isFolder: parsed.isFolder, |
| 115 | } |
| 116 | } catch (e) { |
| 117 | return { ok: false, error: e.message || String(e) } |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | export { resolveMega }; |
| 122 | export default resolveMega; |
| 123 | |
| 124 | |
| 125 | // ───────────────────────────────────────────────────────────────────── |
| 126 | // Self-test: jalankan langsung dengan `node mega.js [args...]` |
| 127 | // ───────────────────────────────────────────────────────────────────── |
| 128 | if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { |
| 129 | const __exports = { resolveMega: resolveMega, default: resolveMega } |
| 130 | const __fns = Object.fromEntries(Object.entries(__exports).filter(([, v]) => typeof v === 'function')) |
| 131 | const __names = Object.keys(__fns) |
| 132 | if (__names.length === 0) { |
| 133 | console.log('Tidak ada fungsi yang bisa di-demo. Import modul ini dari kode lain.') |
| 134 | } else { |
| 135 | const __search = __names.find((n) => /search|cari/i.test(n)) |
| 136 | const __name = __search || __names[0] |
| 137 | const __fn = __fns[__name] |
| 138 | const __isClass = /^class\s/.test(Function.prototype.toString.call(__fn)) |
| 139 | let __args = process.argv.slice(2).map((a) => { try { return JSON.parse(a) } catch { return a } }) |
| 140 | if (__args.length === 0 && __search) __args.push('naruto') |
| 141 | if (__args.length === 0 && !__search && !__isClass && __fn.length > 0) { |
| 142 | console.log('Fungsi: ' + __names.join(', ')) |
| 143 | console.log('Contoh: node ' + "mega.js" + ' ' + __name + ' <arg>') |
| 144 | } else { |
| 145 | ;(async () => { |
| 146 | try { |
| 147 | const r = __isClass ? new __fn(...__args) : await __fn(...__args) |
| 148 | console.log(JSON.stringify(r, null, 2)) |
| 149 | } catch (e) { |
| 150 | console.error('Error: ' + (e && e.message ? e.message : e)) |
| 151 | process.exitCode = 1 |
| 152 | } |
| 153 | })() |
| 154 | } |
| 155 | } |
| 156 | } |
| 157 | |
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.