mirror of
https://git.luna-app.eu/50n50/sources
synced 2025-12-21 21:26:19 +01:00
169 lines
5.3 KiB
JavaScript
169 lines
5.3 KiB
JavaScript
// AniLiberty module for Sora (AsyncJS)
|
|
// Author: emp0ry
|
|
// Version: 1.0.3
|
|
// Changes: add skip markers (opening/ending) and average_duration_of_episode -> "Duration: Xm".
|
|
|
|
const IMAGE_HOST = "https://anilibria.top";
|
|
|
|
function fullImg(path) {
|
|
if (!path) return `${IMAGE_HOST}/favicon.ico`;
|
|
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),
|
|
// Pass a details endpoint as href (will be reused by details/episodes)
|
|
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) {
|
|
out.push({
|
|
title: "No results from AniLiberty",
|
|
image: `${IMAGE_HOST}/favicon.ico`,
|
|
href: IMAGE_HOST
|
|
});
|
|
}
|
|
return JSON.stringify(out);
|
|
} catch (e) {
|
|
console.log("AniLiberty search error:", e);
|
|
return JSON.stringify([{ title: "Error", image: `${IMAGE_HOST}/favicon.ico`, href: IMAGE_HOST }]);
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------
|
|
// 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) {
|
|
console.log("AniLiberty details error:", e);
|
|
return JSON.stringify([{
|
|
description: "Error loading details",
|
|
aliases: "Duration: Unknown",
|
|
airdate: "Unknown"
|
|
}]);
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------
|
|
// 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) {
|
|
console.log("AniLiberty episodes error:", e);
|
|
return JSON.stringify([]);
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------
|
|
// Stream -> RAW URL string
|
|
// ------------------------------------------------------------
|
|
async function extractStreamUrl(url) {
|
|
try {
|
|
return url; // direct HLS
|
|
} catch (e) {
|
|
console.log("AniLiberty stream error:", e);
|
|
return null;
|
|
}
|
|
}
|