removal
This commit is contained in:
@@ -1,309 +1,309 @@
|
|||||||
// AnimeVost for Sora (AsyncJS)
|
// AnimeVost for Sora (AsyncJS)
|
||||||
// Author: emp0ry
|
// Author: emp0ry
|
||||||
// Version: 1.0.2
|
// Version: 1.0.2
|
||||||
|
|
||||||
const API_BASE = "https://api.animevost.org/v1/";
|
const API_BASE = "https://api.animevost.org/v1/";
|
||||||
const FORM_CT = "application/x-www-form-urlencoded; charset=UTF-8";
|
const FORM_CT = "application/x-www-form-urlencoded; charset=UTF-8";
|
||||||
const SITE_BASE = "https://animevost.org";
|
const SITE_BASE = "https://animevost.org";
|
||||||
const DEFAULT_SUBTITLE = "https://none.com";
|
const DEFAULT_SUBTITLE = "https://none.com";
|
||||||
|
|
||||||
// --- utils ---
|
// --- utils ---
|
||||||
function encodeForm(fields) {
|
function encodeForm(fields) {
|
||||||
return Object.keys(fields)
|
return Object.keys(fields)
|
||||||
.map(k => `${encodeURIComponent(k)}=${encodeURIComponent(fields[k])}`)
|
.map(k => `${encodeURIComponent(k)}=${encodeURIComponent(fields[k])}`)
|
||||||
.join("&");
|
.join("&");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function postForm(url, fields) {
|
async function postForm(url, fields) {
|
||||||
const bodyStr = encodeForm(fields);
|
const bodyStr = encodeForm(fields);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resA = await fetchv2(url, { "Content-Type": FORM_CT }, "POST", bodyStr);
|
const resA = await fetchv2(url, { "Content-Type": FORM_CT }, "POST", bodyStr);
|
||||||
if (resA && typeof resA.text === "function") return resA;
|
if (resA && typeof resA.text === "function") return resA;
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
||||||
return await fetchv2(url, {
|
return await fetchv2(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": FORM_CT },
|
headers: { "Content-Type": FORM_CT },
|
||||||
body: bodyStr
|
body: bodyStr
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function parseJsonSafe(res) {
|
async function parseJsonSafe(res) {
|
||||||
const txt = await res.text();
|
const txt = await res.text();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return JSON.parse(txt);
|
return JSON.parse(txt);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return JSON.parse(txt.replace(/^\uFEFF/, "").trim());
|
return JSON.parse(txt.replace(/^\uFEFF/, "").trim());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanTitle(raw) {
|
function cleanTitle(raw) {
|
||||||
if (!raw || typeof raw !== "string") return "Unknown title";
|
if (!raw || typeof raw !== "string") return "Unknown title";
|
||||||
|
|
||||||
let t = raw.split(" /")[0];
|
let t = raw.split(" /")[0];
|
||||||
return t.replace(/\s*\[.*?\]\s*$/g, "").trim() || "Unknown title";
|
return t.replace(/\s*\[.*?\]\s*$/g, "").trim() || "Unknown title";
|
||||||
}
|
}
|
||||||
|
|
||||||
function htmlToText(html) {
|
function htmlToText(html) {
|
||||||
if (!html || typeof html !== "string") return "";
|
if (!html || typeof html !== "string") return "";
|
||||||
|
|
||||||
return html
|
return html
|
||||||
.replace(/<br\s*\/?>/gi, "\n")
|
.replace(/<br\s*\/?>/gi, "\n")
|
||||||
.replace(/<\/p>/gi, "\n")
|
.replace(/<\/p>/gi, "\n")
|
||||||
.replace(/<[^>]+>/g, "")
|
.replace(/<[^>]+>/g, "")
|
||||||
.replace(/\n{3,}/g, "\n\n")
|
.replace(/\n{3,}/g, "\n\n")
|
||||||
.trim();
|
.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanUrl(url) {
|
function cleanUrl(url) {
|
||||||
const s = String(url || "").trim();
|
const s = String(url || "").trim();
|
||||||
return s || null;
|
return s || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _safeJsonParse(value, fallback) {
|
function _safeJsonParse(value, fallback) {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(value);
|
return JSON.parse(value);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pack payload into href to avoid relying on global state.
|
// Pack payload into href to avoid relying on global state.
|
||||||
function makeHrefFromPayload(obj) {
|
function makeHrefFromPayload(obj) {
|
||||||
return `animevost://payload/${encodeURIComponent(JSON.stringify(obj || {}))}`;
|
return `animevost://payload/${encodeURIComponent(JSON.stringify(obj || {}))}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function readPayloadFromHref(href) {
|
function readPayloadFromHref(href) {
|
||||||
const m = String(href || "").match(/^animevost:\/\/payload\/(.+)$/);
|
const m = String(href || "").match(/^animevost:\/\/payload\/(.+)$/);
|
||||||
if (!m) return null;
|
if (!m) return null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return JSON.parse(decodeURIComponent(m[1]));
|
return JSON.parse(decodeURIComponent(m[1]));
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseIdFromAny(hrefOrId) {
|
function parseIdFromAny(hrefOrId) {
|
||||||
const p = readPayloadFromHref(hrefOrId);
|
const p = readPayloadFromHref(hrefOrId);
|
||||||
if (p && p.id) return parseInt(p.id, 10);
|
if (p && p.id) return parseInt(p.id, 10);
|
||||||
|
|
||||||
const m1 = String(hrefOrId || "").match(/^animevost:\/\/release\/(\d+)$/);
|
const m1 = String(hrefOrId || "").match(/^animevost:\/\/release\/(\d+)$/);
|
||||||
if (m1) return parseInt(m1[1], 10);
|
if (m1) return parseInt(m1[1], 10);
|
||||||
|
|
||||||
const m2 = String(hrefOrId || "").match(/[?&]id=(\d+)/);
|
const m2 = String(hrefOrId || "").match(/[?&]id=(\d+)/);
|
||||||
if (m2) return parseInt(m2[1], 10);
|
if (m2) return parseInt(m2[1], 10);
|
||||||
|
|
||||||
if (/^\d+$/.test(String(hrefOrId || ""))) {
|
if (/^\d+$/.test(String(hrefOrId || ""))) {
|
||||||
return parseInt(hrefOrId, 10);
|
return parseInt(hrefOrId, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Same packing style as AniLiberty / SameBand.
|
// Same packing style as AniLiberty / SameBand.
|
||||||
function _packEpisode(payload) {
|
function _packEpisode(payload) {
|
||||||
return "animevost:" + encodeURIComponent(JSON.stringify(payload || {}));
|
return "animevost:" + encodeURIComponent(JSON.stringify(payload || {}));
|
||||||
}
|
}
|
||||||
|
|
||||||
function _unpackEpisode(href) {
|
function _unpackEpisode(href) {
|
||||||
const raw = String(href || "");
|
const raw = String(href || "");
|
||||||
if (!raw.startsWith("animevost:")) return null;
|
if (!raw.startsWith("animevost:")) return null;
|
||||||
|
|
||||||
return _safeJsonParse(decodeURIComponent(raw.slice("animevost:".length)), null);
|
return _safeJsonParse(decodeURIComponent(raw.slice("animevost:".length)), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _streamHeaders() {
|
function _streamHeaders() {
|
||||||
return {
|
return {
|
||||||
"User-Agent": "Mozilla/5.0",
|
"User-Agent": "Mozilla/5.0",
|
||||||
"Referer": SITE_BASE + "/"
|
"Referer": SITE_BASE + "/"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- search: POST /search ---
|
// --- search: POST /search ---
|
||||||
async function searchResults(keyword) {
|
async function searchResults(keyword) {
|
||||||
try {
|
try {
|
||||||
let res = await postForm(API_BASE + "search", { name: String(keyword) });
|
let res = await postForm(API_BASE + "search", { name: String(keyword) });
|
||||||
let json = await parseJsonSafe(res);
|
let json = await parseJsonSafe(res);
|
||||||
|
|
||||||
if (json?.error || !Array.isArray(json?.data) || json.data.length === 0) {
|
if (json?.error || !Array.isArray(json?.data) || json.data.length === 0) {
|
||||||
res = await postForm(API_BASE + "search", { name: `"${String(keyword)}"` });
|
res = await postForm(API_BASE + "search", { name: `"${String(keyword)}"` });
|
||||||
json = await parseJsonSafe(res);
|
json = await parseJsonSafe(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (json?.error) {
|
if (json?.error) {
|
||||||
return JSON.stringify([]);
|
return JSON.stringify([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const list = Array.isArray(json?.data) ? json.data : [];
|
const list = Array.isArray(json?.data) ? json.data : [];
|
||||||
if (!list.length) {
|
if (!list.length) {
|
||||||
return JSON.stringify([]);
|
return JSON.stringify([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tiles = list.map(item => {
|
const tiles = list.map(item => {
|
||||||
const payload = {
|
const payload = {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
title: cleanTitle(item.title),
|
title: cleanTitle(item.title),
|
||||||
description: htmlToText(item.description || ""),
|
description: htmlToText(item.description || ""),
|
||||||
year: item.year || "",
|
year: item.year || "",
|
||||||
type: item.type || "",
|
type: item.type || "",
|
||||||
image: item.urlImagePreview || ""
|
image: item.urlImagePreview || ""
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: payload.title,
|
title: payload.title,
|
||||||
image: payload.image,
|
image: payload.image,
|
||||||
href: makeHrefFromPayload(payload)
|
href: makeHrefFromPayload(payload)
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return JSON.stringify(tiles);
|
return JSON.stringify(tiles);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return JSON.stringify([]);
|
return JSON.stringify([]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- details: from payload ---
|
// --- details: from payload ---
|
||||||
async function extractDetails(href) {
|
async function extractDetails(href) {
|
||||||
try {
|
try {
|
||||||
const p = readPayloadFromHref(href);
|
const p = readPayloadFromHref(href);
|
||||||
|
|
||||||
const out = [{
|
const out = [{
|
||||||
description: p?.description || "No description available.",
|
description: p?.description || "No description available.",
|
||||||
aliases: `Type: ${p?.type || "Unknown"}`,
|
aliases: `Type: ${p?.type || "Unknown"}`,
|
||||||
airdate: p?.year ? String(p.year) : "Unknown"
|
airdate: p?.year ? String(p.year) : "Unknown"
|
||||||
}];
|
}];
|
||||||
|
|
||||||
return JSON.stringify(out);
|
return JSON.stringify(out);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return JSON.stringify([]);
|
return JSON.stringify([]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- episodes: POST /playlist ---
|
// --- episodes: POST /playlist ---
|
||||||
async function extractEpisodes(href) {
|
async function extractEpisodes(href) {
|
||||||
try {
|
try {
|
||||||
const p = readPayloadFromHref(href);
|
const p = readPayloadFromHref(href);
|
||||||
const id = p?.id ?? parseIdFromAny(href);
|
const id = p?.id ?? parseIdFromAny(href);
|
||||||
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return JSON.stringify([]);
|
return JSON.stringify([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await postForm(API_BASE + "playlist", { id: String(id) });
|
const res = await postForm(API_BASE + "playlist", { id: String(id) });
|
||||||
const arr = await parseJsonSafe(res);
|
const arr = await parseJsonSafe(res);
|
||||||
|
|
||||||
if (!Array.isArray(arr)) {
|
if (!Array.isArray(arr)) {
|
||||||
return JSON.stringify([]);
|
return JSON.stringify([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const out = arr.map((ep, idx) => {
|
const out = arr.map((ep, idx) => {
|
||||||
const name = ep?.name || "";
|
const name = ep?.name || "";
|
||||||
const m = name.match(/(\d+)/);
|
const m = name.match(/(\d+)/);
|
||||||
const num = m ? parseInt(m[1], 10) : (idx + 1);
|
const num = m ? parseInt(m[1], 10) : (idx + 1);
|
||||||
|
|
||||||
// AnimeVost usually provides only hd/std.
|
// AnimeVost usually provides only hd/std.
|
||||||
// Match Sora picker format used in AniLiberty/SameBand:
|
// Match Sora picker format used in AniLiberty/SameBand:
|
||||||
// hd -> 720p
|
// hd -> 720p
|
||||||
// std -> 480p
|
// std -> 480p
|
||||||
const url1080 = null;
|
const url1080 = null;
|
||||||
const url720 = cleanUrl(ep?.hd);
|
const url720 = cleanUrl(ep?.hd);
|
||||||
const url480 = cleanUrl(ep?.std);
|
const url480 = cleanUrl(ep?.std);
|
||||||
|
|
||||||
const fallback = url720 || url480;
|
const fallback = url720 || url480;
|
||||||
if (!fallback) return null;
|
if (!fallback) return null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
href: _packEpisode({
|
href: _packEpisode({
|
||||||
url1080,
|
url1080,
|
||||||
url720,
|
url720,
|
||||||
url480,
|
url480,
|
||||||
fallback
|
fallback
|
||||||
}),
|
}),
|
||||||
number: num,
|
number: num,
|
||||||
title: name || `Episode ${num}`,
|
title: name || `Episode ${num}`,
|
||||||
image: ep?.preview || ""
|
image: ep?.preview || ""
|
||||||
};
|
};
|
||||||
}).filter(Boolean);
|
}).filter(Boolean);
|
||||||
|
|
||||||
return JSON.stringify(out);
|
return JSON.stringify(out);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return JSON.stringify([]);
|
return JSON.stringify([]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- stream: returns quality picker JSON ---
|
// --- stream: returns quality picker JSON ---
|
||||||
async function extractStreamUrl(href) {
|
async function extractStreamUrl(href) {
|
||||||
try {
|
try {
|
||||||
const payload = _unpackEpisode(href);
|
const payload = _unpackEpisode(href);
|
||||||
|
|
||||||
// Backward compatibility for old AnimeVost episode href format.
|
// Backward compatibility for old AnimeVost episode href format.
|
||||||
if (!payload) {
|
if (!payload) {
|
||||||
return href;
|
return href;
|
||||||
}
|
}
|
||||||
|
|
||||||
const url1080 = cleanUrl(payload.url1080);
|
const url1080 = cleanUrl(payload.url1080);
|
||||||
const url720 = cleanUrl(payload.url720);
|
const url720 = cleanUrl(payload.url720);
|
||||||
const url480 = cleanUrl(payload.url480);
|
const url480 = cleanUrl(payload.url480);
|
||||||
const fallback = cleanUrl(payload.fallback);
|
const fallback = cleanUrl(payload.fallback);
|
||||||
|
|
||||||
const headers = _streamHeaders();
|
const headers = _streamHeaders();
|
||||||
const streams = [];
|
const streams = [];
|
||||||
|
|
||||||
if (url1080) {
|
if (url1080) {
|
||||||
streams.push({
|
streams.push({
|
||||||
title: "1080p",
|
title: "1080p",
|
||||||
streamUrl: url1080,
|
streamUrl: url1080,
|
||||||
url1080,
|
url1080,
|
||||||
url720,
|
url720,
|
||||||
url480,
|
url480,
|
||||||
headers
|
headers
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url720) {
|
if (url720) {
|
||||||
streams.push({
|
streams.push({
|
||||||
title: "720p",
|
title: "720p",
|
||||||
streamUrl: url720,
|
streamUrl: url720,
|
||||||
url1080,
|
url1080,
|
||||||
url720,
|
url720,
|
||||||
url480,
|
url480,
|
||||||
headers
|
headers
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url480) {
|
if (url480) {
|
||||||
streams.push({
|
streams.push({
|
||||||
title: "480p",
|
title: "480p",
|
||||||
streamUrl: url480,
|
streamUrl: url480,
|
||||||
url1080,
|
url1080,
|
||||||
url720,
|
url720,
|
||||||
url480,
|
url480,
|
||||||
headers
|
headers
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!streams.length && fallback) {
|
if (!streams.length && fallback) {
|
||||||
streams.push({
|
streams.push({
|
||||||
title: "720p",
|
title: "720p",
|
||||||
streamUrl: fallback,
|
streamUrl: fallback,
|
||||||
url1080: null,
|
url1080: null,
|
||||||
url720: fallback,
|
url720: fallback,
|
||||||
url480: null,
|
url480: null,
|
||||||
headers
|
headers
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
streams,
|
streams,
|
||||||
subtitle: DEFAULT_SUBTITLE
|
subtitle: DEFAULT_SUBTITLE
|
||||||
});
|
});
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
streams: [],
|
streams: [],
|
||||||
subtitle: DEFAULT_SUBTITLE
|
subtitle: DEFAULT_SUBTITLE
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user