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;