// AniLiberty module for Sora (AsyncJS) // Author: emp0ry // Version: 1.0.1 const IMAGE_HOST = "https://anilibria.top"; function fullImg(path) { if (!path) return; return path.startsWith("http") ? path : `${IMAGE_HOST}${path}`; } function pickBestHls(ep) { return ep?.hls_1080 || ep?.hls_720 || ep?.hls_480 || null; } // ------------------------------------------------------------ // Detect working API domain // ------------------------------------------------------------ async function checkApiStatus() { const domains = [ "https://anilibria.top/api/v1/", "https://aniliberty.top/api/v1/", "https://anilibria.wtf/api/v1/" ]; for (const base of domains) { try { const res = await fetchv2(base + "app/status"); const data = await res.json(); if (data?.is_alive || data?.result === "ok") return base; } catch (_) {} } return "https://anilibria.top/api/v1/"; } // ------------------------------------------------------------ // Search -> JSON string // ------------------------------------------------------------ async function searchResults(keyword) { try { const base = await checkApiStatus(); const url = `${base}app/search/releases?query=${encodeURIComponent(keyword)}&include=id,name.main,poster.src`; const res = await fetchv2(url); const data = await res.json(); const out = (Array.isArray(data) ? data : []).map(it => ({ title: it?.name?.main || "Unknown title", image: fullImg(it?.poster?.src), href: `${base}anime/releases/${it.id}?` + [ "include=name.main,poster.src,description,average_duration_of_episode", "episodes.ordinal,episodes.name,episodes.duration", "episodes.preview.src", "episodes.opening.start,episodes.opening.stop", "episodes.ending.start,episodes.ending.stop", "episodes.hls_1080,episodes.hls_720,episodes.hls_480" ].join(",") })); if (!out.length) { return JSON.stringify([]);; } return JSON.stringify(out); } catch (e) { return JSON.stringify([]);; } } // ------------------------------------------------------------ // Details -> JSON string (Duration: Xm in aliases) // ------------------------------------------------------------ async function extractDetails(url) { try { const res = await fetchv2(url); const data = await res.json(); const description = data?.description || "No description available."; const mins = Number.isFinite(data?.average_duration_of_episode) ? `${data.average_duration_of_episode}m` : "Unknown"; const out = [{ description, aliases: `Duration: ${mins}`, airdate: "Unknown" }]; return JSON.stringify(out); } catch (e) { return JSON.stringify([]);; } } // ------------------------------------------------------------ // Episodes -> JSON string (with opening/ending skips) // ------------------------------------------------------------ async function extractEpisodes(url) { try { const res = await fetchv2(url); const data = await res.json(); const seriesPoster = fullImg(data?.poster?.src); const eps = Array.isArray(data?.episodes) ? data.episodes : []; const out = eps.map((ep, idx) => { const href = pickBestHls(ep); if (!href) return null; const num = Number.isFinite(ep?.ordinal) ? ep.ordinal : (Number.isFinite(ep?.sort_order) ? ep.sort_order : (idx + 1)); const title = ep?.name ? String(ep.name) : `Episode ${num}`; const image = fullImg(ep?.preview?.src) || seriesPoster; // Build skip blocks only if numbers present const opening = (ep?.opening && Number.isFinite(ep.opening.start) && Number.isFinite(ep.opening.stop)) ? { start: ep.opening.start, stop: ep.opening.stop } : undefined; const ending = (ep?.ending && Number.isFinite(ep.ending.start) && Number.isFinite(ep.ending.stop)) ? { start: ep.ending.start, stop: ep.ending.stop } : undefined; const entry = { href, number: num, title, image }; // Attach only when available if (opening) entry.opening = opening; if (ending) entry.ending = ending; if (Number.isFinite(ep?.duration)) entry.duration = ep.duration; // seconds (optional) return entry; }).filter(Boolean); return JSON.stringify(out); } catch (e) { return JSON.stringify([]); } } // ------------------------------------------------------------ // Stream -> RAW URL string // ------------------------------------------------------------ async function extractStreamUrl(url) { try { return url; // direct HLS } catch (e) { return null; } }