Spaces:
Sleeping
Sleeping
| const express = require('express'); | |
| const multer = require('multer'); | |
| const { Storage, File } = require('megajs'); | |
| const fs = require('fs'); | |
| require('dotenv').config(); | |
| const app = express(); | |
| const port = process.env.PORT || 7860; | |
| // Setup penyimpanan sementara untuk upload transit | |
| const upload = multer({ dest: '/tmp/' }); | |
| app.use(express.json()); | |
| let megaStorage; | |
| // Variabel tracker statis sederhana (akan ter-reset jika space restart/tidur) | |
| let totalFiles = 0; | |
| let totalBytes = 0; | |
| let totalDownloads = 0; | |
| // Fungsi format ukuran biner | |
| function formatBytes(bytes, decimals = 2) { | |
| if (!bytes) return '0 B'; | |
| const k = 1024; | |
| const dm = decimals < 0 ? 0 : decimals; | |
| const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; | |
| const i = Math.floor(Math.log(bytes) / Math.log(k)); | |
| return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; | |
| } | |
| async function connectStorage() { | |
| return new Promise((resolve, reject) => { | |
| const email = process.env.MEGA_EMAIL; | |
| const password = process.env.MEGA_PASSWORD; | |
| if (!email || !password) { | |
| console.error("ERROR: Variabel lingkungan kredensial belum diatur!"); | |
| return reject("Kredensial kosong"); | |
| } | |
| const storage = new Storage({ | |
| email: email, | |
| password: password, | |
| keepalive: true | |
| }, (err) => { | |
| if (err) return reject(err); | |
| console.log('✓ Sistem backend CDN siap dan terhubung!'); | |
| resolve(storage); | |
| }); | |
| }); | |
| } | |
| connectStorage().then(storage => { | |
| megaStorage = storage; | |
| }).catch(err => { | |
| console.error('⚠️ Gagal menginisialisasi storage:', err); | |
| }); | |
| // Endpoint 1: Frontend Dashboard Utama (Tema Putih Bersih Premium) | |
| app.get('/', (req, res) => { | |
| const hostUrl = `${req.protocol}://${req.get('host')}`; | |
| res.send(` | |
| <!DOCTYPE html> | |
| <html lang="id"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>CDN FAREL - Secure CDN Gateway</title> | |
| <style> | |
| :root { | |
| --bg-main: #f8fafc; | |
| --bg-card: #ffffff; | |
| --text-main: #0f172a; | |
| --text-muted: #64748b; | |
| --border-color: #e2e8f0; | |
| --accent: #ff4757; | |
| --accent-hover: #ff6b81; | |
| --indigo: #4f46e5; | |
| --success: #10b981; | |
| } | |
| * { box-sizing: border-box; margin: 0; padding: 0; font-family: 'Segoe UI', system-ui, -apple-system, sans-serif; } | |
| body { background: var(--bg-main); color: var(--text-main); padding: 40px 20px; line-height: 1.5; } | |
| .container { max-width: 1000px; margin: 0 auto; } | |
| /* Header */ | |
| header { text-align: center; margin-bottom: 40px; } | |
| header h1 { font-size: 2.2rem; font-weight: 800; tracking-spacing: -0.05em; color: var(--text-main); } | |
| header h1 span { color: var(--accent); } | |
| header p.tagline { font-size: 1.1rem; color: var(--text-muted); font-weight: 500; margin-top: 5px; } | |
| header p.desc { max-width: 600px; margin: 10px auto 0; color: var(--text-muted); font-size: 0.95rem; } | |
| /* Grid Statistik */ | |
| .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 20px; margin-bottom: 40px; } | |
| .stat-card { background: var(--bg-card); padding: 24px; border-radius: 12px; border: 1px solid var(--border-color); box-shadow: 0 1px 3px rgba(0,0,0,0.02); } | |
| .stat-card h3 { font-size: 2rem; font-weight: 700; color: var(--text-main); margin-bottom: 4px; } | |
| .stat-card p.label { font-size: 0.9rem; font-weight: 600; color: var(--text-main); } | |
| .stat-card p.sub { font-size: 0.8rem; color: var(--text-muted); } | |
| /* Card Utama */ | |
| .card { background: var(--bg-card); padding: 40px; border-radius: 16px; border: 1px solid var(--border-color); box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05), 0 2px 4px -1px rgba(0,0,0,0.03); margin-bottom: 40px; } | |
| .card-title { font-size: 1.4rem; font-weight: 700; margin-bottom: 6px; } | |
| .card-subtitle { font-size: 0.95rem; color: var(--text-muted); margin-bottom: 25px; } | |
| /* Dropzone */ | |
| .upload-box { border: 2px dashed #cbd5e1; padding: 50px 20px; text-align: center; border-radius: 12px; cursor: pointer; transition: all 0.2s ease; background: #f8fafc; } | |
| .upload-box:hover { border-color: var(--accent); background: #fff5f5; } | |
| .upload-box p.primary-text { font-size: 1.05rem; font-weight: 600; color: var(--text-main); margin-bottom: 4px; } | |
| .upload-box p.secondary-text { font-size: 0.85rem; color: var(--text-muted); margin-bottom: 15px; } | |
| .upload-box p.info-text { font-size: 0.85rem; color: var(--text-muted); line-height: 1.4; background: #fff; padding: 8px 12px; border-radius: 6px; display: inline-block; border: 1px solid var(--border-color); } | |
| input[type="file"] { display: none; } | |
| button.btn-upload { background: var(--accent); color: white; border: none; padding: 14px 30px; border-radius: 8px; font-weight: 700; cursor: pointer; width: 100%; margin-top: 20px; font-size: 16px; transition: background 0.2s; } | |
| button.btn-upload:hover { background: var(--accent-hover); } | |
| /* Hasil Upload */ | |
| .result { margin-top: 30px; display: none; background: #f0fdf4; padding: 25px; border-radius: 10px; border: 1px solid #bbf7d0; } | |
| .result h3 { margin-bottom: 12px; color: #166534; font-size: 1.15rem; display: flex; align-items: center; gap: 8px; } | |
| .result p { font-size: 0.95rem; margin-bottom: 10px; color: var(--text-main); } | |
| .result-link-box { background: #ffffff; padding: 12px; border-radius: 6px; border: 1px solid #dcfce7; display: flex; align-items: center; justify-content: space-between; margin-top: 10px; gap: 10px; } | |
| .result-link-box a { color: var(--indigo); word-break: break-all; font-weight: 600; text-decoration: none; font-size: 0.9rem; overflow: hidden; text-overflow: ellipsis; } | |
| .result-link-box a:hover { text-decoration: underline; } | |
| .btn-copy-link { background: var(--text-main); color: #fff; border: none; padding: 6px 12px; border-radius: 4px; font-size: 0.8rem; font-weight: 600; cursor: pointer; white-space: nowrap; } | |
| .status { text-align: center; margin-top: 15px; color: var(--indigo); font-weight: 600; font-size: 0.95rem; display: none; } | |
| /* Fitur & Manfaat Kelebihan */ | |
| .section-title { font-size: 1.6rem; font-weight: 800; text-align: center; margin-bottom: 6px; } | |
| .section-subtitle { text-align: center; color: var(--text-muted); font-size: 0.95rem; margin-bottom: 35px; } | |
| .features-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 25px; margin-bottom: 5px; } | |
| .feature-item { background: var(--bg-card); padding: 30px; border-radius: 12px; border: 1px solid var(--border-color); } | |
| .feature-item h4 { font-size: 1.1rem; font-weight: 700; margin-bottom: 8px; color: var(--text-main); } | |
| .feature-item p { font-size: 0.9rem; color: var(--text-muted); line-height: 1.5; } | |
| /* Dokumentasi API Section */ | |
| .api-card { background: var(--bg-card); border-radius: 14px; border: 1px solid var(--border-color); margin-top: 40px; overflow: hidden; } | |
| .api-header { padding: 25px; border-bottom: 1px solid var(--border-color); background: #fafafa; } | |
| .api-badge-row { display: flex; align-items: center; gap: 12px; margin-bottom: 8px; } | |
| .badge-method { background: var(--success); color: white; padding: 4px 10px; border-radius: 4px; font-weight: 700; font-size: 0.8rem; } | |
| .badge-type { background: #e2e8f0; color: var(--text-main); padding: 4px 10px; border-radius: 4px; font-weight: 600; font-size: 0.8rem; } | |
| .api-endpoint { font-family: monospace; font-size: 1.1rem; font-weight: 700; color: var(--text-main); } | |
| .api-desc { padding: 0 25px 20px; font-size: 0.9rem; color: var(--text-muted); border-bottom: 1px solid var(--border-color); background: #fafafa; } | |
| /* Code tabs */ | |
| .api-body { padding: 25px; background: #1e293b; color: #f8fafc; } | |
| .tab-header { display: flex; gap: 8px; margin-bottom: 15px; border-bottom: 1px solid #334155; padding-bottom: 10px; } | |
| .tab-btn { background: transparent; color: #94a3b8; border: none; padding: 6px 14px; font-size: 0.85rem; font-weight: 600; cursor: pointer; border-radius: 4px; } | |
| .tab-btn.active { background: #334155; color: #fff; } | |
| .code-container { position: relative; } | |
| pre { font-family: 'Courier New', Courier, monospace; font-size: 0.85rem; overflow-x: auto; white-space: pre-wrap; word-break: break-all; color: #e2e8f0; line-height: 1.5; } | |
| .btn-copy-code { position: absolute; top: 0; right: 0; background: #334155; color: #f8fafc; border: none; padding: 5px 10px; border-radius: 4px; font-size: 0.75rem; cursor: pointer; font-weight: 600; } | |
| .btn-copy-code:hover { background: #475569; } | |
| .cors-text { font-size: 0.75rem; font-weight: 700; color: #38bdf8; letter-spacing: 0.05em; margin-top: 15px; display: inline-block; } | |
| /* Footer */ | |
| footer { border-top: 1px solid var(--border-color); margin-top: 60px; padding-top: 30px; color: var(--text-muted); font-size: 0.85rem; } | |
| .footer-top { display: flex; justify-content: space-between; align-items: flex-start; flex-wrap: wrap; gap: 20px; margin-bottom: 20px; } | |
| .footer-brand h5 { font-size: 1.1rem; font-weight: 800; color: var(--text-main); margin-bottom: 4px; } | |
| .footer-meta { display: flex; gap: 15px; font-weight: 600; color: var(--text-main); font-size: 0.85rem; } | |
| .footer-bottom { display: flex; justify-content: space-between; align-items: center; border-top: 1px solid var(--border-color); padding-top: 20px; color: var(--text-muted); font-size: 0.8rem; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <!-- Header --> | |
| <header> | |
| <h1>CDN <span>FAREL</span></h1> | |
| <p class="tagline">Secure CDN Gateway</p> | |
| <p class="desc">Unggah Berkas, Dapatkan Direct Link</p> | |
| <p class="desc" style="font-size: 0.85rem; margin-top: 5px; max-width: 500px;">Sistem direct link CDN mandiri berkecepatan tinggi dengan enkripsi awan ganda yang aman dan global.</p> | |
| </header> | |
| <!-- Grid Statistik --> | |
| <div class="stats-grid"> | |
| <div class="stat-card"> | |
| <h3 id="statFiles">${totalFiles}</h3> | |
| <p class="label">Total File Aktif</p> | |
| <p class="sub">Seluruh file yang tersimpan di CDN</p> | |
| </div> | |
| <div class="stat-card"> | |
| <h3 id="statBytes">${formatBytes(totalBytes)}</h3> | |
| <p class="label">Kapasitas Terpakai</p> | |
| <p class="sub">Total kapasitas cloud terpakai</p> | |
| </div> | |
| <div class="stat-card"> | |
| <h3 id="statDownloads">${totalDownloads}</h3> | |
| <p class="label">Total Pengunduhan</p> | |
| <p class="sub">File dikonsumsi via link CDN</p> | |
| </div> | |
| <div class="stat-card"> | |
| <h3>1</h3> | |
| <p class="label">Node Storage Cluster</p> | |
| <p class="sub">Penyimpanan cloud node terenkripsi aktif</p> | |
| </div> | |
| </div> | |
| <!-- Card Upload Dropzone --> | |
| <div class="card"> | |
| <h2 class="card-title">Unggah Berkas ke CDN Farel</h2> | |
| <p class="card-subtitle">Dapatkan direct link, embedding, dan direct download berkecepatan tinggi instan dengan mengunggah berkas ke gateway kami.</p> | |
| <form id="uploadForm"> | |
| <div class="upload-box" onclick="document.getElementById('fileInput').click()" id="dropzone"> | |
| <p class="primary-text" id="boxText">Seret & taruh berkas di sini, atau pilih manual</p> | |
| <p class="secondary-text">Semua format file didukung (Maks 100 MB)</p> | |
| <p class="info-text">Pilih atau seret berkas ke dropzone untuk mempersiapkan pratinjau dan mengaktifkan tautan CDN.</p> | |
| <input type="file" id="fileInput" name="file" required onchange="updateBoxText()"> | |
| </div> | |
| <button type="submit" class="btn-upload">UNGGAH KE CDN</button> | |
| </form> | |
| <div class="status" id="statusText">Sedang memproses berkas... Harap tunggu...</div> | |
| <!-- Box Hasil Output --> | |
| <div class="result" id="resultBox"> | |
| <h3>🎉 Berkas Berhasil Diproses!</h3> | |
| <p><strong>Nama Berkas:</strong> <span id="resName"></span></p> | |
| <p style="font-weight: 600; margin-top: 15px; font-size: 0.9rem; color: #15803d;">Link CDN Anda:</p> | |
| <div class="result-link-box"> | |
| <a id="resCdn" href="#" target="_blank"></a> | |
| <button class="btn-copy-link" onclick="copyLinkOutput()">Copy Link</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Section Kelebihan --> | |
| <h2 class="section-title">Manfaat & Kelebihan CDN FAREL</h2> | |
| <p class="section-subtitle">Infrastruktur andal yang dioptimalkan untuk performa pengiriman aset digital terbaik.</p> | |
| <div class="features-grid"> | |
| <div class="feature-item"> | |
| <h4>Direct Streaming Link</h4> | |
| <p>Dapatkan link bersih langsung tanpa halaman pengalihan, iklan, atau timer. Sempurna untuk disematkan langsung pada HTML, Markdown, stylesheet, atau tag media web.</p> | |
| </div> | |
| <div class="feature-item"> | |
| <h4>Bebas Hambatan CORS</h4> | |
| <p>Mendukung kebijakan penuh Cross-Origin Resource Sharing (CORS). Integrasikan aset seperti gambar kustom, font kustom, atau file JSON dari domain manapun secara mulus.</p> | |
| </div> | |
| <div class="feature-item"> | |
| <h4>Penyimpanan MEGA Aman</h4> | |
| <p>Semua aset diunggah langsung dan disimpan ke dalam penyimpanan awan MEGA terenkripsi yang aman untuk menjamin ketersediaan data Anda tetap utuh, aman, dan dapat diakses terus.</p> | |
| </div> | |
| <div class="feature-item"> | |
| <h4>MIME Type yang Tepat</h4> | |
| <p>Deteksi tipe konten otomatis secara cerdas. Server menyajikan berkas dengan header <code>Content-Type</code> yang akurat agar dapat di-render atau diputar langsung di browser.</p> | |
| </div> | |
| <div class="feature-item"> | |
| <h4>Kapasitas Besar 100MB</h4> | |
| <p>Kirim berkas media berukuran besar seperti video klip, track musik berkualitas tinggi, dokumen PDF tebal, hingga kompresi zip tanpa takut batasan ukuran yang kecil.</p> | |
| </div> | |
| <div class="feature-item"> | |
| <h4>Arsip Selamanya (Persistent)</h4> | |
| <p>Berkas tidak memiliki batas kedaluwarsa waktu. File Anda tetap tersimpan utuh di gateway awan tanpa kekhawatiran akan terhapus otomatis setelah beberapa hari.</p> | |
| </div> | |
| </div> | |
| <!-- Section API Dokumentasi --> | |
| <div class="api-card"> | |
| <div class="api-header"> | |
| <div class="api-badge-row"> | |
| <span class="badge-method">POST</span> | |
| <span class="badge-type">multipart/form-data</span> | |
| </div> | |
| <div class="api-endpoint">/api/upload</div> | |
| </div> | |
| <div class="api-desc"> | |
| Gunakan endpoint pengunggahan file dari CDN FAREL untuk mengotomatiskan proses upload file langsung dari aplikasi, skrip backend, website, atau bot Anda. Kirim berkas Anda pada parameter body bernama <code>file</code>. Respon akan mengembalikan URL streaming langsung secara instan. | |
| </div> | |
| <div class="api-body"> | |
| <div class="tab-header"> | |
| <button class="tab-btn active" onclick="switchTab('curl')">cURL</button> | |
| <button class="tab-btn" onclick="switchTab('js')">javascript</button> | |
| <button class="tab-btn" onclick="switchTab('py')">python</button> | |
| </div> | |
| <div class="code-container"> | |
| <button class="btn-copy-code" onclick="copyCodeSnippet()">Copy Snippet</button> | |
| <pre id="codeBlock">curl -X POST "${hostUrl}/api/upload" \\\n -F "file=@/path/to/your/file.png"</pre> | |
| </div> | |
| <span class="cors-text">CORS ENABLED</span> | |
| </div> | |
| </div> | |
| <!-- Footer --> | |
| <footer> | |
| <div class="footer-top"> | |
| <div class="footer-brand"> | |
| <h5>CDN FAREL</h5> | |
| <p>Penyimpanan awan performa tinggi dan gerbang distribusi direct link aman.</p> | |
| </div> | |
| <div class="footer-meta"> | |
| <span>Node Kluster Utama Aktif</span> | |
| <span>|</span> | |
| <span>SSL Terenkripsi</span> | |
| <span>|</span> | |
| <span>CORS Terbuka</span> | |
| </div> | |
| </div> | |
| <div class="footer-bottom"> | |
| <p>© 2026 CDN FAREL. Hak Cipta Dilindungi.</p> | |
| <p>v1.4.0 Production • SLA 99.9%</p> | |
| </div> | |
| </footer> | |
| </div> | |
| <script> | |
| const hostBase = "${hostUrl}"; | |
| let currentTab = 'curl'; | |
| // Snippets generator dinamis sesuai host saat ini | |
| const snippets = { | |
| curl: \`curl -X POST "\${hostBase}/api/upload" \\\n -F "file=@/path/to/your/file.png"\`, | |
| js: \`const data = new FormData();\ndata.append('file', fileInput.files[0]);\n\nfetch('\${hostBase}/api/upload', {\n method: 'POST',\n body: data\n})\n.then(res => res.json())\n.then(json => console.log(json.cdnLink));\`, | |
| py: \`import requests\n\nurl = "\${hostBase}/api/upload"\nfiles = {'file': open('file.png', 'rb')}\nr = requests.post(url, files=files)\nprint(r.json()['cdnLink'])\` | |
| }; | |
| function switchTab(lang) { | |
| currentTab = lang; | |
| document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active')); | |
| event.target.classList.add('active'); | |
| document.getElementById('codeBlock').innerText = snippets[lang]; | |
| } | |
| function copyCodeSnippet() { | |
| navigator.clipboard.writeText(snippets[currentTab]); | |
| alert('Snippet kode berhasil disalin!'); | |
| } | |
| function copyLinkOutput() { | |
| const linkText = document.getElementById('resCdn').innerText; | |
| navigator.clipboard.writeText(linkText); | |
| alert('Tautan CDN berhasil disalin!'); | |
| } | |
| function updateBoxText() { | |
| const input = document.getElementById('fileInput'); | |
| if(input.files.length > 0) { | |
| document.getElementById('boxText').innerText = "Berkas terpilih: " + input.files[0].name; | |
| } | |
| } | |
| // Setup Drag and Drop | |
| const dropzone = document.getElementById('dropzone'); | |
| const fileInput = document.getElementById('fileInput'); | |
| ['dragenter', 'dragover'].forEach(eventName => { | |
| dropzone.addEventListener(eventName, (e) => { e.preventDefault(); dropzone.style.borderColor = 'var(--accent)'; }, false); | |
| }); | |
| ['dragleave', 'drop'].forEach(eventName => { | |
| dropzone.addEventListener(eventName, (e) => { e.preventDefault(); dropzone.style.borderColor = '#cbd5e1'; }, false); | |
| }); | |
| dropzone.addEventListener('drop', (e) => { | |
| fileInput.files = e.dataTransfer.files; | |
| updateBoxText(); | |
| }); | |
| // Submit form lewat AJAX | |
| document.getElementById('uploadForm').addEventListener('submit', async (e) => { | |
| e.preventDefault(); | |
| if (fileInput.files.length === 0) return; | |
| const formData = new FormData(); | |
| formData.append('file', fileInput.files[0]); | |
| const statusText = document.getElementById('statusText'); | |
| const resultBox = document.getElementById('resultBox'); | |
| statusText.style.display = "block"; | |
| resultBox.style.display = "none"; | |
| try { | |
| const response = await fetch('/api/upload', { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| const data = await response.json(); | |
| if (data.success) { | |
| statusText.style.display = "none"; | |
| document.getElementById('resName').innerText = data.fileName; | |
| document.getElementById('resCdn').innerText = data.cdnLink; | |
| document.getElementById('resCdn').href = data.cdnLink; | |
| resultBox.style.display = "block"; | |
| // Update UI statistik secara real-time di halaman | |
| if(data.stats) { | |
| document.getElementById('statFiles').innerText = data.stats.files; | |
| document.getElementById('statBytes').innerText = data.stats.formattedBytes; | |
| } | |
| } else { | |
| statusText.innerText = "Gagal memproses: " + data.error; | |
| } | |
| } catch (err) { | |
| statusText.innerText = "Terjadi kesalahan sistem saat memproses berkas."; | |
| console.error(err); | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> | |
| `); | |
| }); | |
| // Endpoint 2: API Proses Upload File (Update data stat secara real-time) | |
| app.post('/api/upload', upload.single('file'), async (req, res) => { | |
| if (!megaStorage) return res.status(503).json({ error: 'Storage backend belum siap' }); | |
| if (!req.file) return res.status(400).json({ error: 'Tidak ada file yang dikirim' }); | |
| try { | |
| const filePath = req.file.path; | |
| const fileName = req.file.originalname; | |
| const fileSize = req.file.size; | |
| const fileStream = fs.createReadStream(filePath); | |
| // Upload berkas ke cloud storage MEGA | |
| const megaFile = await megaStorage.upload({ | |
| name: fileName, | |
| size: fileSize | |
| }, fileStream).complete; | |
| // Dapatkan tautan cloud publik internal | |
| const downloadLink = await megaFile.link(); | |
| // Hapus file transit di lokal (/tmp/) | |
| fs.unlinkSync(filePath); | |
| // Ubah '#' menjadi '-' agar aman dilewati via URL Express Gateway | |
| const fileId = downloadLink.split('/file/')[1].replace('#', '-'); | |
| // Tambah counter statistik internal server | |
| totalFiles += 1; | |
| totalBytes += fileSize; | |
| res.json({ | |
| success: true, | |
| fileName: fileName, | |
| cdnLink: `${req.protocol}://${req.get('host')}/cdn/${fileId}`, | |
| stats: { | |
| files: totalFiles, | |
| formattedBytes: formatBytes(totalBytes) | |
| } | |
| }); | |
| } catch (error) { | |
| console.error(error); | |
| res.status(500).json({ error: 'Gagal memproses penyimpanan: ' + error.message }); | |
| } | |
| }); | |
| // Endpoint 3: Proxy CDN Gateway (Streaming Data Langsung + Dukungan CORS) | |
| app.get('/cdn/:fileIdAndKey', async (req, res) => { | |
| try { | |
| const fullId = req.params.fileIdAndKey; | |
| // Kembalikan karakter '-' menjadi '#' untuk menyusun URL asli MEGA | |
| const correctedId = fullId.replace('-', '#'); | |
| const targetUrl = `https://mega.nz/file/${correctedId}`; | |
| const file = File.fromURL(targetUrl); | |
| await file.loadAttributes(); | |
| // Dukungan Penuh Kebijakan CORS | |
| res.setHeader('Access-Control-Allow-Origin', '*'); | |
| res.setHeader('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS'); | |
| res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); | |
| // Atur header disposisi agar browser langsung memutar/merender inline aset | |
| res.setHeader('Content-Disposition', `inline; filename="${file.name}"`); | |
| // Tambah hit counter unduhan | |
| totalDownloads += 1; | |
| // Alirkan data secara streaming waktu nyata ke pengguna luar | |
| const stream = file.download(); | |
| stream.pipe(res); | |
| } catch (error) { | |
| console.error(error); | |
| res.status(404).send('Berkas tidak ditemukan atau tautan CDN salah.'); | |
| } | |
| }); | |
| app.listen(port, () => { | |
| console.log(`Server CDN Farel berjalan aktif pada port ${port}`); | |
| }); |