169 lines
5.3 KiB
JavaScript
169 lines
5.3 KiB
JavaScript
class AnimeAV1 {
|
|
constructor() {
|
|
this.type = "anime-board";
|
|
this.version = "1.3";
|
|
this.api = "https://animeav1.com";
|
|
}
|
|
|
|
getSettings() {
|
|
return {
|
|
episodeServers: ["HLS"],
|
|
supportsDub: true,
|
|
};
|
|
}
|
|
|
|
async search(query) {
|
|
const res = await fetch(`${this.api}/api/search`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ query: query.query }),
|
|
});
|
|
|
|
if (!res.ok) return [];
|
|
const data = await res.json();
|
|
|
|
return data.map((anime) => ({
|
|
id: anime.slug,
|
|
title: anime.title,
|
|
url: `${this.api}/media/${anime.slug}`,
|
|
subOrDub: "both",
|
|
image: `https://cdn.animeav1.com/covers/${anime.id}.jpg`,
|
|
}));
|
|
}
|
|
|
|
async getMetadata(id) {
|
|
const html = await fetch(`${this.api}/media/${id}`).then((r) => r.text());
|
|
const parsed = this.parseSvelteData(html);
|
|
const media = parsed.find((x) => x?.data?.media)?.data.media ?? {};
|
|
|
|
const imageMatch = html.match(
|
|
/<img[^>]*class="aspect-poster[^"]*"[^>]*src="([^"]+)"/i
|
|
);
|
|
const image = imageMatch ? imageMatch[1] : null;
|
|
|
|
return {
|
|
title: media.title ?? "Unknown",
|
|
summary: media.synopsis ?? "No summary available",
|
|
episodes: media.episodesCount ?? 0,
|
|
characters: [],
|
|
season: media.seasons ?? null,
|
|
status: media.status ?? "Unknown",
|
|
studio: "Unknown",
|
|
score: media.score ?? 0,
|
|
year: media.startDate
|
|
? Number(media.startDate.slice(0, 4))
|
|
: null,
|
|
genres: media.genres?.map((g) => g.name) ?? [],
|
|
image,
|
|
};
|
|
}
|
|
|
|
async findEpisodes(id) {
|
|
const html = await fetch(`${this.api}/media/${id}`).then((r) => r.text());
|
|
const parsed = this.parseSvelteData(html);
|
|
|
|
const media = parsed.find((x) => x?.data?.media)?.data?.media;
|
|
if (!media?.episodes) throw new Error("No se encontró media.episodes");
|
|
|
|
return media.episodes.map((ep, i) => ({
|
|
id: `${media.slug}$${ep.number ?? i + 1}`,
|
|
number: ep.number ?? i + 1,
|
|
title: ep.title ?? `Episode ${ep.number ?? i + 1}`,
|
|
url: `${this.api}/media/${media.slug}/${ep.number ?? i + 1}`,
|
|
}));
|
|
}
|
|
|
|
async findEpisodeServer(episodeOrId, _server, category = "sub") {
|
|
const ep =
|
|
typeof episodeOrId === "string"
|
|
? (() => {
|
|
try {
|
|
return JSON.parse(episodeOrId);
|
|
} catch {
|
|
return { id: episodeOrId };
|
|
}
|
|
})()
|
|
: episodeOrId;
|
|
|
|
|
|
let pageUrl = ep.url;
|
|
|
|
if (!pageUrl && typeof ep.id === "string") {
|
|
if (ep.id.includes("$")) {
|
|
const [slug, num] = ep.id.split("$");
|
|
pageUrl = `${this.api}/media/${slug}/${ep.number ?? num}`;
|
|
} else {
|
|
pageUrl = `${this.api}/media/${ep.id}/${ep.number}`;
|
|
}
|
|
}
|
|
|
|
if (!pageUrl) {
|
|
throw new Error(
|
|
`No se pudo determinar la URL del episodio (id=${ep.id}, number=${ep.number})`
|
|
);
|
|
}
|
|
|
|
if (!pageUrl) throw new Error("No se pudo determinar la URL del episodio.");
|
|
|
|
const html = await fetch(pageUrl).then((r) => r.text());
|
|
const parsedData = this.parseSvelteData(html);
|
|
const entry = parsedData.find((x) => x?.data?.embeds);
|
|
const embeds = entry?.data?.embeds;
|
|
if (!embeds) throw new Error("No embeds encontrados");
|
|
|
|
const list =
|
|
category === "dub"
|
|
? embeds.DUB
|
|
: embeds.SUB;
|
|
|
|
if (!Array.isArray(list))
|
|
throw new Error(`No hay streams ${category.toUpperCase()}`);
|
|
|
|
const hls = list.find(
|
|
(m) =>
|
|
m.server === "HLS" &&
|
|
m.url?.includes("zilla-networks.com/play/")
|
|
);
|
|
|
|
if (!hls)
|
|
throw new Error(`No se encontró stream HLS ${category.toUpperCase()}`);
|
|
|
|
return {
|
|
server: "HLS",
|
|
headers: { Referer: "null" },
|
|
videoSources: [
|
|
{
|
|
url: hls.url.replace("/play/", "/m3u8/"),
|
|
type: "m3u8",
|
|
quality: "auto",
|
|
subtitles: [],
|
|
subOrDub: category,
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
parseSvelteData(html) {
|
|
const scriptMatch = html.match(
|
|
/<script[^>]*>\s*({[^<]*__sveltekit_[\s\S]*?)<\/script>/i
|
|
);
|
|
if (!scriptMatch) throw new Error("SvelteKit block not found");
|
|
|
|
const dataMatch = scriptMatch[1].match(
|
|
/data:\s*(\[[\s\S]*?\])\s*,\s*form:/
|
|
);
|
|
if (!dataMatch) throw new Error("SvelteKit data block not found");
|
|
|
|
const jsArray = dataMatch[1];
|
|
try {
|
|
return new Function(`"use strict"; return (${jsArray});`)();
|
|
} catch {
|
|
const cleaned = jsArray
|
|
.replace(/\bvoid 0\b/g, "null")
|
|
.replace(/undefined/g, "null");
|
|
return new Function(`"use strict"; return (${cleaned});`)();
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = AnimeAV1; |