Files
WaifuBoard-Extensions/anime/xvideos.js
2026-01-13 17:26:06 +01:00

186 lines
5.3 KiB
JavaScript

class Xvideos {
constructor() {
this.baseUrl = "https://www.xvideos.com";
this.type = "anime-board";
this.version = "1.0";
}
getSettings() {
return {
episodeServers: ["Default"],
supportsDub: false,
};
}
_encodeId(href) {
if (!href) return "";
let id = href.startsWith("/") ? href.substring(1) : href;
return id.replace(/\//g, "___");
}
_decodeId(id) {
if (!id) return "";
return id.replace(/___/g, "/");
}
async search({ query, tag = "", page = 1 }) {
let url;
if (query && query.trim()) {
url = `${this.baseUrl}/?k=${encodeURIComponent(query)}&p=${page}`;
} else if (tag) {
url = `${this.baseUrl}/tags/${tag}/${page}`;
} else {
url = `${this.baseUrl}/new/${page}`;
}
try {
const res = await fetch(url);
const html = await res.text();
const $ = this.cheerio.load(html);
const items = $("div#main div#content div.mozaique.cust-nb-cols > div").toArray();
const results = items.map(el => {
const a = $(el).find("div.thumb-inside div.thumb a");
const img = $(el).find("div.thumb-inside div.thumb a img");
const href = a.attr("href");
if (!href) return null;
const safeId = this._encodeId(href);
const thumbnail = img.attr("data-src") || img.attr("src") || "";
return {
id: safeId,
title: $(el).find("div.thumb-under p.title").text().trim(),
url: this.baseUrl + href,
image: thumbnail,
subOrDub: "sub",
};
}).filter(i => i !== null);
return results;
} catch (e) {
console.error("[ERROR] Fallo en Search:", e);
return [];
}
}
async getMetadata(id) {
const realPath = this._decodeId(id);
const fetchUrl = `${this.baseUrl}/${realPath}`;
try {
const res = await fetch(fetchUrl);
if (res.status === 404) throw new Error("Video no encontrado (404)");
const html = await res.text();
const $ = this.cheerio.load(html);
const title = $("h2.page-title").text().trim();
const genres = $("div.video-metadata ul li a span").toArray().map(e => $(e).text());
let image = "";
image = $('meta[property="og:image"]').attr('content');
if (!image) {
const regex = /html5player\.setThumbUrl\(\s*['"]([^'"]+)['"]/;
const match = html.match(regex);
if (match) image = match[1];
}
return {
title: title || "Unknown Title",
summary: "",
episodes: 1,
characters: [],
season: "",
status: "Completed",
studio: "",
score: null,
year: null,
genres: genres,
image: image || "",
};
} catch (e) {
console.error("[ERROR] Fallo en getMetadata:", e);
throw e;
}
}
async findEpisodes(id) {
return [
{
id: "video_main",
number: 1,
title: "Video",
url: id,
},
];
}
async findEpisodeServer(episode, server, category = "sub") {
const realPath = this._decodeId(episode.url);
const videoUrl = `${this.baseUrl}/${realPath}`;
try {
const res = await fetch(videoUrl);
const html = await res.text();
const videoSources = [];
const extract = (key) => {
const regex = new RegExp(`${key}\\s*\\(\\s*['"]([^'"]+)['"]`);
const match = html.match(regex);
return match ? match[1] : null;
};
const low = extract("html5player.setVideoUrlLow");
const high = extract("html5player.setVideoUrlHigh");
const hls = extract("html5player.setVideoHLS");
if (hls) {
videoSources.push({
url: hls,
type: "m3u8",
quality: "Auto",
subOrDub: category
});
} else if (high) {
videoSources.push({
url: high,
type: "mp4",
quality: "High",
subOrDub: category
});
} else if (low) {
videoSources.push({
url: low,
type: "mp4",
quality: "Low",
subOrDub: category
});
}
if (videoSources.length === 0) throw new Error("No sources found");
return {
server: server || "Default",
headers: { Referer: this.baseUrl },
videoSources: videoSources,
};
} catch (e) {
console.error("[ERROR] Fallo en findEpisodeServer:", e);
throw e;
}
}
}
module.exports = Xvideos;