Files
sources/aniliberty/aniliberty.js
T

256 lines
7.1 KiB
JavaScript
Raw Normal View History

2025-10-12 19:37:50 +00:00
// AniLiberty module for Sora (AsyncJS)
2025-10-12 17:07:42 +00:00
// Author: emp0ry
2026-02-09 13:42:08 +00:00
// Version: 1.0.2
2025-10-12 19:37:50 +00:00
2026-02-09 13:42:08 +00:00
const DEFAULT_IMAGE_HOST = "https://aniliberty.top";
2025-10-12 19:37:50 +00:00
2026-02-09 13:42:08 +00:00
function originFromApi(urlOrBase) {
if (!urlOrBase) return DEFAULT_IMAGE_HOST;
const raw = String(urlOrBase);
const m = raw.match(/^(https?:\/\/[^/]+)\/api\/v1\/?/i);
if (m && m[1]) return m[1];
const m2 = raw.match(/^(https?:\/\/[^/]+)/i);
return (m2 && m2[1]) ? m2[1] : DEFAULT_IMAGE_HOST;
}
function fullImg(path, host) {
2025-10-12 22:08:11 +00:00
if (!path) return;
2026-02-09 13:42:08 +00:00
if (path.startsWith("http")) return path;
const base = host || DEFAULT_IMAGE_HOST;
return `${base}${path}`;
2025-10-12 19:37:50 +00:00
}
function pickBestHls(ep) {
return ep?.hls_1080 || ep?.hls_720 || ep?.hls_480 || null;
}
2025-10-12 17:07:42 +00:00
2026-04-24 16:27:23 +02:00
function _packEpisode(payload) {
return "aniliberty:" + encodeURIComponent(JSON.stringify(payload || {}));
}
function _unpackEpisode(href) {
const s = String(href || "");
if (!s.startsWith("aniliberty:")) return null;
return _safeJsonParse(decodeURIComponent(s.slice("aniliberty:".length)), null);
}
function _safeJsonParse(s, fallback) {
try {
return JSON.parse(s);
} catch (_) {
return fallback;
}
}
2025-10-12 17:07:42 +00:00
// ------------------------------------------------------------
// Detect working API domain
// ------------------------------------------------------------
async function checkApiStatus() {
const domains = [
"https://aniliberty.top/api/v1/",
2026-02-09 13:42:08 +00:00
"https://anilibria.top/api/v1/",
2025-10-12 17:07:42 +00:00
"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 (_) {}
}
2026-02-09 13:42:08 +00:00
return "https://aniliberty.top/api/v1/";
2025-10-12 17:07:42 +00:00
}
// ------------------------------------------------------------
2025-10-12 19:37:50 +00:00
// Search -> JSON string
2025-10-12 17:07:42 +00:00
// ------------------------------------------------------------
async function searchResults(keyword) {
try {
2025-10-12 19:37:50 +00:00
const base = await checkApiStatus();
2026-02-09 13:42:08 +00:00
const origin = originFromApi(base);
2025-10-12 19:37:50 +00:00
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",
2026-02-09 13:42:08 +00:00
image: fullImg(it?.poster?.src, origin),
2025-10-12 19:37:50 +00:00
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) {
2025-10-12 22:08:11 +00:00
return JSON.stringify([]);;
2025-10-12 19:37:50 +00:00
}
return JSON.stringify(out);
} catch (e) {
2025-10-12 22:08:11 +00:00
return JSON.stringify([]);;
2025-10-12 17:07:42 +00:00
}
}
// ------------------------------------------------------------
2025-10-12 19:37:50 +00:00
// Details -> JSON string (Duration: Xm in aliases)
2025-10-12 17:07:42 +00:00
// ------------------------------------------------------------
async function extractDetails(url) {
try {
2025-10-12 19:37:50 +00:00
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"
2025-10-12 17:07:42 +00:00
}];
2025-10-12 19:37:50 +00:00
return JSON.stringify(out);
} catch (e) {
2025-10-12 22:08:11 +00:00
return JSON.stringify([]);;
2025-10-12 17:07:42 +00:00
}
}
// ------------------------------------------------------------
2025-10-12 19:37:50 +00:00
// Episodes -> JSON string (with opening/ending skips)
2025-10-12 17:07:42 +00:00
// ------------------------------------------------------------
async function extractEpisodes(url) {
try {
2025-10-12 19:37:50 +00:00
const res = await fetchv2(url);
const data = await res.json();
2026-02-09 13:42:08 +00:00
const origin = originFromApi(url);
const seriesPoster = fullImg(data?.poster?.src, origin);
2025-10-12 19:37:50 +00:00
const eps = Array.isArray(data?.episodes) ? data.episodes : [];
const out = eps.map((ep, idx) => {
2026-04-24 16:27:23 +02:00
const url1080 = ep?.hls_1080 || null;
const url720 = ep?.hls_720 || null;
const url480 = ep?.hls_480 || null;
const best = url1080 || url720 || url480;
if (!best) return null;
2025-10-12 19:37:50 +00:00
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}`;
2026-02-09 13:42:08 +00:00
const image = fullImg(ep?.preview?.src, origin) || seriesPoster;
2025-10-12 19:37:50 +00:00
// 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 = {
2026-04-24 16:27:23 +02:00
href: _packEpisode({
url1080,
url720,
url480,
fallback: best
}),
2025-10-12 19:37:50 +00:00
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([]);
2025-10-12 17:07:42 +00:00
}
}
// ------------------------------------------------------------
2025-10-12 19:37:50 +00:00
// Stream -> RAW URL string
2025-10-12 17:07:42 +00:00
// ------------------------------------------------------------
async function extractStreamUrl(url) {
try {
2026-04-24 16:27:23 +02:00
const payload = _unpackEpisode(url);
if (!payload) {
return url; // backward compatibility for old episode href format
}
const url1080 = (payload.url1080 || "").toString().trim() || null;
const url720 = (payload.url720 || "").toString().trim() || null;
const url480 = (payload.url480 || "").toString().trim() || null;
const fallback = (payload.fallback || "").toString().trim() || null;
const streams = [];
if (url1080) {
streams.push({
title: "1080p",
streamUrl: url1080,
url1080,
url720,
url480,
headers: {
"User-Agent": "Mozilla/5.0",
"Referer": DEFAULT_IMAGE_HOST + "/"
}
});
}
if (url720) {
streams.push({
title: "720p",
streamUrl: url720,
url1080,
url720,
url480,
headers: {
"User-Agent": "Mozilla/5.0",
"Referer": DEFAULT_IMAGE_HOST + "/"
}
});
}
if (url480) {
streams.push({
title: "480p",
streamUrl: url480,
url1080,
url720,
url480,
headers: {
"User-Agent": "Mozilla/5.0",
"Referer": DEFAULT_IMAGE_HOST + "/"
}
});
}
if (!streams.length) {
return fallback;
}
return JSON.stringify({
streams,
subtitle: "https://none.com"
});
2025-10-12 19:37:50 +00:00
} catch (e) {
2025-10-12 17:07:42 +00:00
return null;
}
2025-10-12 22:08:11 +00:00
}