Edit: dongub
Nama Worker
Kode Sumber
--6b359262326afb36cfcb1cfd942623d8f0afcd145ecf6e32263d91c1c1c8 Content-Disposition: form-data; name="worker.js" /** * ANIMEPLAY FINAL - FULL GRAPHIC VERSION * Menggabungkan Backend Proxy & Frontend HTML dalam satu file. */ // === 1. KONFIGURASI SUMBER DATA (Sesuai Data Valid Kamu) === const CONFIG = { CDN: "https://cdn.anibox.id", API: "https://ap.cloudly.id", KEY: "b4c88e94b909a53f322c1da8699d310e", IV: "hxNg4rQ-xvXPQatt", TOKEN: "Bearer MvlykAnXjrdvlAhd7Wi4liFnqoQM/vZ/k0AUDQ20NRQ=" }; const HEADERS_API = { "Authorization": CONFIG.TOKEN, "User-Agent": "AnimePlay/1.1.8 (Android 33) 2312DRAABG/Xiaomi", "package-name": "dev.animeplay.app", "accept": "application/json", "Content-Type": "application/json" }; const HEADERS_CDN = { "User-Agent": "AnimePlay/1.1.8 (Android 33) 2312DRAABG/Xiaomi" }; // === 2. ENGINE DEKRIPSI AES (Untuk Video) === async function decryptAES(str) { try { if (!str || str.length < 20 || str.includes("http")) return str; const enc = new TextEncoder(); const key = await crypto.subtle.importKey("raw", enc.encode(CONFIG.KEY), { name: "AES-CBC" }, false, ["decrypt"]); const iv = enc.encode(CONFIG.IV); const binStr = atob(str); const bytes = new Uint8Array(binStr.length); for (let i = 0; i < binStr.length; i++) bytes[i] = binStr.charCodeAt(i); const buf = await crypto.subtle.decrypt({ name: "AES-CBC", iv: iv }, key, bytes); const dec = new TextDecoder().decode(buf); // Bersihkan karakter padding & tanda kutip return dec.replace(/[\x00-\x1F\x7F-\x9F]/g, "").replace(/"/g, ''); } catch (e) { return str; } } // === 3. FUNGSI FETCH UTAMA === async function fetchData(url, isApi) { try { const res = await fetch(url, { headers: isApi ? HEADERS_API : HEADERS_CDN }); if (!res.ok) return { error: true, code: res.status }; const txt = await res.text(); try { return JSON.parse(txt); } catch (e) {} const dec = await decryptAES(txt.trim()); try { return JSON.parse(dec); } catch (e) { return { result: dec }; } } catch (e) { return { error: true, msg: e.message }; } } // === 4. TAMPILAN WEBSITE (HTML + CSS + JS) === const HTML = ` <!DOCTYPE html> <html lang="id"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <title>AnimePlay</title> <script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> <style> body { background-color: #0f0f0f; color: #fff; font-family: sans-serif; padding-bottom: 80px; -webkit-tap-highlight-color: transparent; } .hide { display: none !important; } .poster { aspect-ratio: 2/3; object-fit: cover; border-radius: 6px; } .scroll-hide::-webkit-scrollbar { display: none; } .line-clamp-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } </style> </head> <body> <div class="fixed top-0 w-full bg-black/95 z-50 px-4 py-3 flex justify-between items-center border-b border-gray-800 backdrop-blur"> <h1 onclick="goHome()" class="text-xl font-bold text-red-600 tracking-tighter cursor-pointer">ANIME<span class="text-white">PLAY</span></h1> <button onclick="toggleSearch()" class="text-white"><i class="fas fa-search"></i></button> </div> <div id="search-box" class="fixed top-14 w-full bg-gray-900 p-2 z-40 hide border-b border-gray-700"> <form onsubmit="event.preventDefault(); doSearch();" class="flex gap-2"> <input id="q" type="text" placeholder="Cari Anime..." class="w-full bg-gray-800 text-white px-3 py-2 rounded focus:outline-none border border-gray-700"> <button class="bg-red-600 px-4 py-2 rounded text-white font-bold">GO</button> </form> </div> <div id="app" class="pt-16 px-3 max-w-md mx-auto md:max-w-xl"> <div id="loading" class="flex flex-col items-center justify-center mt-20"> <i class="fas fa-circle-notch fa-spin text-red-600 text-4xl mb-4"></i> <p class="text-xs text-gray-500">Memuat Data...</p> </div> <div id="page-home" class="hide"> <h2 class="text-xs font-bold text-gray-400 mb-2 mt-2">FEATURED</h2> <div id="featured-list" class="flex overflow-x-auto gap-3 pb-4 mb-2 scroll-hide"></div> <div class="flex gap-2 overflow-x-auto pb-2 mb-4 scroll-hide"> <button onclick="loadSection('latest')" class="bg-gray-800 px-4 py-1.5 rounded-full text-xs border border-gray-700 whitespace-nowrap">Episode Baru</button> <button onclick="loadSection('popular')" class="bg-gray-800 px-4 py-1.5 rounded-full text-xs border border-gray-700 whitespace-nowrap">Populer</button> <button onclick="loadSection('movies')" class="bg-gray-800 px-4 py-1.5 rounded-full text-xs border border-gray-700 whitespace-nowrap">Movie</button> <button onclick="loadSection('completed')" class="bg-gray-800 px-4 py-1.5 rounded-full text-xs border border-gray-700 whitespace-nowrap">Tamat</button> </div> <h2 id="section-title" class="text-sm font-bold text-white mb-3 border-l-4 border-red-600 pl-2">Episode Terbaru</h2> <div id="content-grid" class="grid grid-cols-3 gap-2"></div> </div> <div id="page-detail" class="hide"> <button onclick="goHome()" class="mb-3 text-gray-400 text-xs flex items-center gap-1"><i class="fas fa-arrow-left"></i> KEMBALI</button> <div id="player-wrapper" class="w-full bg-black rounded-lg overflow-hidden shadow-lg mb-4 hide border border-gray-800"> <video id="video" controls class="w-full aspect-video" poster="https://via.placeholder.com/640x360?text=AnimePlay"></video> </div> <div id="anime-info" class="mb-4"></div> <div class="flex justify-between items-center mb-2 border-b border-gray-800 pb-2"> <h3 class="font-bold text-white text-sm">Pilih Episode</h3> <span id="ep-total" class="text-[10px] bg-gray-800 px-2 py-0.5 rounded text-gray-400">0 Eps</span> </div> <div id="episode-list" class="grid grid-cols-4 gap-2 max-h-80 overflow-y-auto pr-1"></div> </div> </div> <script> // API POINTING KE WORKER INI SENDIRI const API = window.location.origin + "/api"; function safe(d) { return Array.isArray(d) ? d : []; } function hideAll() { ['page-home','page-detail','loading','player-wrapper'].forEach(i=>document.getElementById(i).classList.add('hide')); } // --- LOAD HOME --- async function loadHome() { hideAll(); document.getElementById('loading').classList.remove('hide'); try { // Fetch Slider fetch(API + "/home").then(r=>r.json()).then(data => { document.getElementById('featured-list').innerHTML = safe(data).map(i => \` <div onclick="openDetail('\${i.id}')" class="min-w-[85%] relative rounded-lg overflow-hidden cursor-pointer shadow-lg border border-gray-800"> <img src="\${i.image_url}" class="w-full h-40 object-cover opacity-80"> <div class="absolute bottom-0 p-3 bg-gradient-to-t from-black w-full"> <h3 class="font-bold text-white text-xs truncate">\${i.title}</h3> <span class="text-[9px] bg-red-600 px-1 rounded text-white">\${i.type}</span> </div> </div>\`).join(''); }); // Fetch Default Latest loadSection('latest'); } catch(e) { alert("Gagal Memuat: "+e); } } // --- LOAD SECTION (Grid Bawah) --- async function loadSection(type) { document.getElementById('content-grid').innerHTML = ''; document.getElementById('loading').classList.remove('hide'); let title = "Episode Terbaru"; if(type === 'popular') title = "Sedang Populer"; if(type === 'movies') title = "Film (Movie)"; if(type === 'completed') title = "Sudah Tamat"; document.getElementById('section-title').innerText = title; try { const res = await fetch(API + "/" + type); const data = await res.json(); hideAll(); document.getElementById('page-home').classList.remove('hide'); document.getElementById('content-grid').innerHTML = safe(data).map(i => \` <div onclick="\${i.episode ? \`openWatch('\${i.id}')\` : \`openDetail('\${i.id}')\`}" class="bg-gray-800 rounded-lg p-2 cursor-pointer relative hover:bg-gray-700 transition"> <img src="\${i.image_url}" class="w-full poster mb-2 bg-gray-700"> <div class="text-[10px] font-bold leading-tight line-clamp-2 text-gray-200">\${i.title}</div> \${i.episode ? \`<span class="absolute top-1 right-1 bg-red-600 text-[9px] px-1.5 py-0.5 rounded text-white shadow">Ep \${i.episode}</span>\` : ''} \${i.rating ? \`<span class="absolute top-1 left-1 bg-black/60 text-[9px] px-1.5 py-0.5 rounded text-yellow-400">★ \${i.rating}</span>\` : ''} </div>\`).join(''); } catch(e) { console.log(e); } } // --- DETAIL PAGE --- async function openDetail(id) { hideAll(); document.getElementById('loading').classList.remove('hide'); const res = await fetch(API + "/series/" + id); const data = await res.json(); document.getElementById('page-detail').classList.remove('hide'); document.getElementById('loading').classList.add('hide'); document.getElementById('anime-info').innerHTML = \` <div class="flex gap-3"> <img src="\${data.image_url}" class="w-24 poster shadow-lg border border-gray-700"> <div class="flex-1"> <h1 class="text-sm font-bold text-white mb-1 leading-tight">\${data.title}</h1> <div class="flex flex-wrap gap-1 mb-2"> <span class="text-[9px] bg-gray-700 px-2 py-0.5 rounded text-gray-300">\${data.type}</span> <span class="text-[9px] bg-gray-700 px-2 py-0.5 rounded text-yellow-400">★ \${data.rating||'-'}</span> </div> <p class="text-[10px] text-gray-400 line-clamp-4 leading-relaxed">\${data.synopsis || 'Tidak ada sinopsis.'}</p> </div> </div>\`; const eps = safe(data.episodes); document.getElementById('ep-total').innerText = eps.length + " Items"; document.getElementById('episode-list').innerHTML = eps.map(ep => \` <button onclick="openWatch('\${ep.id}')" class="bg-gray-800 hover:bg-red-600 text-gray-300 py-2 rounded text-[10px] font-bold border border-gray-700"> \${ep.number} </button>\`).join(''); } // --- WATCH VIDEO --- async function openWatch(id) { hideAll(); document.getElementById('page-detail').classList.remove('hide'); document.getElementById('loading').classList.remove('hide'); const res = await fetch(API + "/watch/" + id); let data = await res.json(); if(Array.isArray(data)) data = data[0]; document.getElementById('loading').classList.add('hide'); document.getElementById('player-wrapper').classList.remove('hide'); const video = document.getElementById('video'); if(Hls.isSupported() && data.url && data.url.includes('.m3u8')) { const hls = new Hls(); hls.loadSource(data.url); hls.attachMedia(video); hls.on(Hls.Events.MANIFEST_PARSED, () => video.play()); } else { video.src = data.url; video.play(); } window.scrollTo(0,0); } function doSearch() { const q = document.getElementById('q').value; toggleSearch(); document.getElementById('section-title').innerText = "Hasil Pencarian: " + q; loadSection('search?q=' + q); } function goHome() { document.getElementById('video').pause(); loadHome(); } function toggleSearch() { document.getElementById('search-box').classList.toggle('hide'); } // Start loadHome(); </script> </body> </html> `; // === 5. ROUTER UTAMA (BACKEND) === export default { async fetch(req) { const url = new URL(req.url); const path = url.pathname; // Serve HTML jika akses root if (path === "/") return new Response(HTML, { headers: { "Content-Type": "text/html" } }); // Routing sesuai Data Valid User const routes = { "/api/home": `${CONFIG.CDN}/catalog/home_all.json`, "/api/latest": `${CONFIG.CDN}/catalog/episodes_all.json`, "/api/completed": `${CONFIG.CDN}/catalog/completed_all.json`, "/api/movies": `${CONFIG.CDN}/catalog/movies_all.json`, "/api/popular": `${CONFIG.CDN}/catalog/trending_items_all.json` }; if (routes[path]) { const data = await fetchData(routes[path], false); return Response.json(data); } // Schedule if (path === "/api/schedule") { const target = `${CONFIG.API}/items/series?filter[type][_in]=TV,ONA&filter[season_status][_eq]=Ongoing&sort=-date_created&page=1&limit=50&fields=id,title,rating,latest_episode,image_url,broadcast,type`; const res = await fetchData(target, true); return Response.json(res.data || []); } // Search if (path.startsWith("/api/search")) { const q = url.searchParams.get("q"); const target = `${CONFIG.API}/items/series?search=${q}&page=1&sort=title&limit=25&filter[status][_eq]=published&fields=id,title,rating,latest_episode,image_url,type`; const res = await fetchData(target, true); return Response.json(res.data || []); } // Detail if (path.startsWith("/api/series/")) { const id = path.split("/").pop(); // Fields lengkap sesuai request kamu const fields = "*,season.id,genres.genre.name,episodes.id,episodes.number,episodes.title_indonesian,episodes.thumbnail_url"; const target = `${CONFIG.API}/items/series/${id}?fields=${fields}&deep[episodes][_limit]=-1`; const res = await fetchData(target, true); return Response.json(res.data || {}); } // Watch (Decrypt) if (path.startsWith("/api/watch/")) { const id = path.split("/").pop(); const target = `${CONFIG.API}/episodes/${id}/videos`; const res = await fetchData(target, true); let data = res.data || res || []; if (Array.isArray(data)) { for (let item of data) { let raw = item.url || item.link || item.data; if (raw && !raw.includes("http")) item.url = await decryptAES(raw); } } return Response.json(data); } return new Response("Not Found", { status: 404 }); } }; --6b359262326afb36cfcb1cfd942623d8f0afcd145ecf6e32263d91c1c1c8--
Bindings
Tambah KV
Tambah R2
Tambah D1
← Kembali
Simpan & Deploy
Hapus Script Worker Ini