// Script Migrasi Anime JSON -> Supabase & GitHub Releases // Menggunakan GitHub Releases sebagai "Hosting Video" gratis. const { createClient } = require('@supabase/supabase-js'); const { Octokit } = require('octokit'); const fs = require('fs'); const path = require('path'); const axios = require('axios'); const { execSync } = require('child_process'); const mime = require('mime-types'); // --- HARDCODED KEYS (GITHUB) --- const SUPABASE_URL = "https://ajeukqjcqweuofclpdjo.supabase.co"; const SUPABASE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImFqZXVrcWpjcXdldW9mY2xwZGpvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzA5MTA5NjMsImV4cCI6MjA4NjQ4Njk2M30.DfSpKC2u3u3MKynG9gkZsG_a4M5_AtQbs9QI1OYpwZQ"; // --- GITHUB CONFIG (WAJIB DIGANTI) --- const GH_TOKEN = "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"; // Token GitHub Tuan const GH_OWNER = "yanzzv520-cyber"; // Username Pemilik Repo const GH_REPO = "mikunime"; // Nama Repo // Init const supabase = createClient(SUPABASE_URL, SUPABASE_KEY); const octokit = new Octokit({ auth: GH_TOKEN }); // Config Clone const PROJEK_REPO_URL = "https://github.com/Yanzz15/Projek.git"; const TEMP_DIR = "./temp_projek"; const JSON_DIR = path.join(TEMP_DIR, "anime_json"); async function main() { console.log("🚀 Memulai Migrasi Anime (GITHUB RELEASES)..."); // 1. Cek & Init Repo GitHub (Wajib ada file biar bisa Release) try { await octokit.request('GET /repos/{owner}/{repo}/contents/{path}', { owner: GH_OWNER, repo: GH_REPO, path: 'README.md' }); console.log("✅ Repo GitHub sudah siap (ada README.md)."); } catch (err) { if (err.status === 404) { console.log("⚠️ Repo GitHub kosong! Membuat README.md..."); try { await octokit.request('PUT /repos/{owner}/{repo}/contents/{path}', { owner: GH_OWNER, repo: GH_REPO, path: 'README.md', message: 'Initial commit for Anime Vault', content: Buffer.from('# Anime Vault\n\nTempat penyimpanan video anime.').toString('base64') }); console.log("✅ Berhasil inisialisasi Repo GitHub!"); } catch (createErr) { console.error("❌ Gagal Init Repo:", createErr.message); process.exit(1); } } else { console.error("❌ Gagal Cek Repo GitHub:", err.message); } } // 2. Clone Repo Projek (Sumber Data) if (!fs.existsSync(TEMP_DIR)) { console.log("📥 Cloning Repo Projek..."); try { execSync(`git clone ${PROJEK_REPO_URL} ${TEMP_DIR}`); } catch (e) { console.error("❌ Gagal Clone:", e.message); process.exit(1); } } // 2. Baca Semua Anime const files = fs.readdirSync(JSON_DIR).filter(f => f.endsWith('.json')); console.log(`📑 Total Anime: ${files.length}`); // 3. Loop Anime for (const file of files) { try { const rawData = fs.readFileSync(path.join(JSON_DIR, file), 'utf-8'); const animeData = JSON.parse(rawData); const cleanTitle = cleanMetadata(animeData.title); const cleanSlug = cleanMetadataSlug(animeData.slug); console.log(` 🎬 [${cleanTitle}] (${cleanSlug})`); // Cek DB dulu (Skip kalau udah ada) const { data: existingAnime } = await supabase .from('animes') .select('id') .eq('slug', cleanSlug) .maybeSingle(); if (existingAnime) { console.log(`⏭️ Anime sudah ada di DB, skip.`); continue; } // --- BUAT RELEASE BARU PER ANIME (Atau pakai release 'all-videos' biar simple?) --- // Karena GitHub Release limit 2GB/asset, tapi 1 release bisa banyak asset. // Biar rapi, kita buat 1 Release = 1 Anime. Tag = Slug Anime. let releaseData; try { // Cek apakah release tag sudah ada? releaseData = await octokit.request('GET /repos/{owner}/{repo}/releases/tags/{tag}', { owner: GH_OWNER, repo: GH_REPO, tag: cleanSlug }); console.log(`🏷️ Release '${cleanSlug}' sudah ada, pakai yang lama.`); releaseData = releaseData.data; } catch (err) { if (err.status === 404) { // Buat Release Baru console.log(`✨ Membuat Release Baru: ${cleanSlug}`); const newRelease = await octokit.request('POST /repos/{owner}/{repo}/releases', { owner: GH_OWNER, repo: GH_REPO, tag_name: cleanSlug, name: cleanTitle, body: `Video streaming untuk anime ${cleanTitle}`, draft: false, prerelease: false }); releaseData = newRelease.data; } else { console.error(`❌ Gagal Cek Release: ${err.message}`); continue; // Skip anime ini kalau error GitHub } } // --- UPLOAD VIDEO KE RELEASE --- let allEpisodes = []; const episodes = animeData.episodes || []; for (const ep of episodes) { console.log(`📹 Processing Ep ${ep.episode}...`); let epMetadata = { number: ep.episode, title: cleanMetadata(ep.title), streams: { "480p": null, "720p": null } }; // Proses Upload (480p & 720p) if (ep.streams?.["480p"]) { epMetadata.streams["480p"] = await uploadToGithub(ep.streams["480p"], releaseData, `ep${ep.episode}-480p.mp4`); } if (ep.streams?.["720p"]) { epMetadata.streams["720p"] = await uploadToGithub(ep.streams["720p"], releaseData, `ep${ep.episode}-720p.mp4`); } allEpisodes.push(epMetadata); } // --- SIMPAN KE SUPABASE --- const { error: insertError } = await supabase .from('animes') .insert({ slug: cleanSlug, title: cleanTitle, poster: animeData.poster, synopsis: animeData.synopsis, metadata: { original_url: animeData.url }, episodes: allEpisodes }); if (insertError) console.error(`❌ Gagal Insert DB: ${insertError.message}`); else console.log(`💾 Anime & Episodes tersimpan di DB!`); } catch (err) { console.error(`❌ Error File ${file}:`, err.message); } } console.log("🎉 SELESAI SEMUA!"); } // --- FUNGSI BANTUAN --- function cleanMetadata(str) { if (!str) return ""; return str.replace(/Subtitle Indonesia/gi, '').replace(/Sub Indo/gi, '').replace(/\(END\)/gi, '').trim(); } function cleanMetadataSlug(str) { if (!str) return ""; return str.replace(/-subtitle-indonesia/gi, '').replace(/-sub-indo/gi, '').replace(/-end-/gi, '-').replace(/-$/gi, '').trim(); } async function uploadToGithub(url, release, filename) { // Cek apakah asset sudah ada di release ini? (Biar gak double upload) const existingAsset = release.assets?.find(a => a.name === filename); if (existingAsset) { // console.log(`⏩ Asset ${filename} sudah ada.`); return existingAsset.browser_download_url; } try { // Download Video ke Memory const response = await axios({ method: 'get', url: url, responseType: 'arraybuffer', timeout: 60000, headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Referer': 'https://x3.sokuja.uk/' } }); // Upload ke GitHub Release // console.log(`📤 Uploading ${filename} (${response.data.byteLength} bytes)...`); const upload = await octokit.request('POST ' + release.upload_url, { headers: { 'content-type': 'video/mp4', 'content-length': response.data.byteLength }, name: filename, data: response.data }); return upload.data.browser_download_url; } catch (error) { console.error(`⚠️ Gagal Upload GitHub ${filename}: ${error.message}`); return null; } } main();