diff --git a/aniliberty/aniliberty.js b/aniliberty/aniliberty.js new file mode 100644 index 0000000..99bd9c9 --- /dev/null +++ b/aniliberty/aniliberty.js @@ -0,0 +1,132 @@ +// AniLiberty module for Sora +// Author: emp0ry +// Version: 1.0.0 +// Description: +// Streams anime directly from AniLiberty / AniLibria API v1 +// Endpoints used: +// • /app/status +// • /app/search/releases?query= +// • /anime/releases/{id} +// +// Supports 1080p / 720p / 480p HLS playback + +// ------------------------------------------------------------ +// 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 anime releases +// ------------------------------------------------------------ +async function searchResults(keyword) { + const base = await checkApiStatus(); + const url = `${base}app/search/releases?query=${encodeURIComponent(keyword)}&include=id,name,poster,year,description`; + + try { + const response = await fetchv2(url); + const data = await response.json(); + if (!Array.isArray(data) || !data.length) + return [{ + title: `No results from AniLiberty (${base})`, + image: "https://anilibria.top/favicon.ico", + href: "https://anilibria.top" + }]; + + return data.map(item => { + const title = item.name?.main || item.name?.english || "Unknown title"; + const year = item.year ? ` (${item.year})` : ""; + const img = item.poster?.optimized?.preview + ? `https://anilibria.top${item.poster.optimized.preview}` + : `https://anilibria.top${item.poster?.preview || "/favicon.ico"}`; + return { + title: title + year, + image: img, + href: `${base}anime/releases/${item.id}` + }; + }); + } catch (err) { + console.log("AniLiberty search error:", err); + return [{ + title: "AniLiberty: Error during search", + image: "https://anilibria.top/favicon.ico", + href: "https://anilibria.top" + }]; + } +} + +// ------------------------------------------------------------ +// Extract anime details +// ------------------------------------------------------------ +async function extractDetails(url) { + try { + const response = await fetchv2(url); + const data = await response.json(); + return [{ + description: data.description || "No description available.", + aliases: data.name?.english || "N/A", + airdate: data.year ? String(data.year) : "Unknown" + }]; + } catch (err) { + console.log("AniLiberty details error:", err); + return [{ + description: "Error loading details", + aliases: "N/A", + airdate: "N/A" + }]; + } +} + +// ------------------------------------------------------------ +// Extract episode list (with HLS sources) +// ------------------------------------------------------------ +async function extractEpisodes(url) { + try { + const response = await fetchv2(url); + const data = await response.json(); + const eps = data.episodes || []; + if (!eps.length) return []; + + return eps.map(ep => ({ + number: ep.ordinal || ep.sort_order || 0, + image: ep.preview?.optimized?.preview + ? `https://anilibria.top${ep.preview.optimized.preview}` + : "https://anilibria.top/favicon.ico", + href: + ep.hls_1080 || + ep.hls_720 || + ep.hls_480 || + null + })).filter(e => e.href); + } catch (err) { + console.log("AniLiberty episodes error:", err); + return []; + } +} + +// ------------------------------------------------------------ +// Extract direct stream URL (HLS) +// ------------------------------------------------------------ +async function extractStreamUrl(url) { + try { + // Direct HLS links are already provided; just return the same URL + return url; + } catch (err) { + console.log("AniLiberty stream error:", err); + return null; + } +}