updated marketplace and extensions

This commit is contained in:
2026-01-01 20:44:41 +01:00
parent 1f52ac678e
commit ff24046f61
9 changed files with 1285 additions and 186 deletions

164
anime/AniZone.js Normal file
View File

@@ -0,0 +1,164 @@
class Anizone {
constructor() {
this.type = "anime-board";
this.version = "1.0";
this.api = "https://anizone.to";
}
getSettings() {
return {
episodeServers: ["HLS"],
supportsDub: true,
};
}
async search(queryObj) {
const query = queryObj.query ?? "";
const res = await fetch(
`${this.api}/anime?search=${encodeURIComponent(query)}`,
{
headers: {
accept: "*/*",
referer: "https://anizone.to/",
"user-agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36",
},
}
);
const html = await res.text();
const itemRegex =
/<div[^>]*class="relative overflow-hidden h-26 rounded-lg[\s\S]*?<img[^>]*src="([^"]+)"[^>]*alt="([^"]+)"[\s\S]*?<a[^>]*href="([^"]+)"[^>]*title="([^"]+)"/g;
const results = [];
let match;
while ((match = itemRegex.exec(html)) !== null) {
const [, image, altTitle, href, title] = match;
const animeId = href.split("/").pop();
// detectar sub / dub desde el episodio 1
let subOrDub = "sub";
try {
const epHtml = await fetch(`${this.api}/anime/${animeId}/1`, {
headers: {
referer: "https://anizone.to/",
"user-agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36",
},
}).then(r => r.text());
const audioMatch = epHtml.match(
/<div class="text-xs flex flex-wrap gap-1">([\s\S]*?)<\/div>/
);
if (audioMatch) {
const block = audioMatch[1];
const hasJP = /Japanese/i.test(block);
const hasOther = /(English|Spanish|French|German|Italian)/i.test(block);
if (hasJP && hasOther) subOrDub = "both";
else if (hasOther) subOrDub = "dub";
}
} catch {}
results.push({
id: animeId,
title: title || altTitle,
image,
url: href,
subOrDub,
});
}
return results;
}
async getMetadata(id) {
// HARDCODED de momento
return {
id,
title: "Unknown",
summary: "",
episodes: 0,
status: "unknown",
season: null,
year: null,
genres: [],
score: 0,
image: null,
};
}
async findEpisodes(id) {
const html = await fetch(`${this.api}/anime/${id}/1`).then(r => r.text());
const regex =
/<a[^>]*href="([^"]*\/anime\/[^"]+?)"[^>]*>\s*<div[^>]*>\s*<div[^>]*class='[^']*min-w-10[^']*'[^>]*>(\d+)<\/div>\s*<div[^>]*class="[^"]*line-clamp-1[^"]*"[^>]*>([^<]+)<\/div>/g;
const episodes = [];
let match;
while ((match = regex.exec(html)) !== null) {
const [, href, num, title] = match;
episodes.push({
id: href.split("/").pop(),
number: Number(num),
title: title.trim(),
url: href,
});
}
return episodes;
}
async findEpisodeServer(episode, server) {
const html = await fetch(episode.url).then(r => r.text());
const srcMatch = html.match(
/<media-player[^>]+src="([^"]+\.m3u8)"/i
);
if (!srcMatch) throw new Error("No m3u8 found");
const masterUrl = srcMatch[1];
const subtitles = [];
const trackRegex =
/<track[^>]+src=([^ >]+)[^>]*label="([^"]+)"[^>]*srclang="([^"]+)"[^>]*(default)?/gi;
let match;
while ((match = trackRegex.exec(html)) !== null) {
const [, src, label, lang, def] = match;
subtitles.push({
id: lang,
url: src,
language: label.trim(),
isDefault: Boolean(def),
});
}
return {
server,
headers: {
Origin: "https://anizone.to",
Referer: "https://anizone.to/",
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36",
},
videoSources: [
{
url: masterUrl,
type: "m3u8",
quality: "auto",
subtitles,
},
],
};
}
}
module.exports = Anizone;