const axios = require('axios');
const cheerio = require('cheerio');
class Xvideos {
constructor(proxy = 'https://uncors.netlify.app/?destination=') {
this.proxy = proxy;
}
search = async function (query, page = 1) {
try {
if (!query) throw new Error('Query is required.');
if (isNaN(page)) throw new Error('Page must be a number.');
const { data } = await axios.get(`${this.proxy}https://www.xvideos.com/?k=${query}&p=${page}`);
const $ = cheerio.load(data);
const results = [];
$('div[id*="video_"]').each((_, el) => {
const id = $(el).attr('data-id');
const eid = $(el).attr('data-eid');
const isChannel = $(el).attr('data-is-channel') === '1';
const titleEl = $(el).find('.thumb-under p.title a');
const title = titleEl.attr('title') || titleEl.clone().children('span').remove().end().text().trim();
const url = $(el).find('.thumb-inside .thumb a').attr('href');
const cover = $(el).find('.thumb-inside .thumb img').attr('data-src');
const preview = $(el).find('.thumb-inside .thumb img').attr('data-pvv') || null;
const resolution = $(el).find('.top-right-tags span').first().text().trim() || null;
const duration = $(el).find('.thumb-under p.title a .duration').text().trim() || null;
const metaBg = $(el).find('.thumb-under p.metadata .bg');
const uploaderEl = metaBg.find('a').first();
const uploaderName = uploaderEl.find('.name').text().trim() || null;
const uploaderHref = uploaderEl.attr('href') || null;
const metaText = metaBg.text();
const viewsMatch = metaText.match(/([\d,.]+[kMBT]?)\s*Views/i);
const views = viewsMatch ? viewsMatch[1].trim() : null;
results.push({
id,
eid,
title,
uploader: {
name: uploaderName,
url: uploaderHref ? `https://www.xvideos.com${uploaderHref}` : null,
isChannel,
},
duration,
views,
resolution,
thumbnail: {
cover: cover || null,
preview: preview || null,
},
url: url ? `https://www.xvideos.com${url}` : null
});
});
const totalResults = (() => {
const txt = $('h2.page-title .sub').text().trim();
const match = txt.match(/([\d,]+)/);
return match ? parseInt(match[1].replace(/,/g, '')) : null;
})();
const lastPageEl = $('div.pagination ul li a.last-page').first();
const totalPages = lastPageEl.length ? parseInt(lastPageEl.text().trim()) : null;
if (totalPages && parseInt(page) > totalPages) throw new Error(`Page ${page} exceeds total pages (${totalPages}).`);
return {
page: parseInt(page),
totalResults,
totalPages,
results,
};
} catch (error) {
throw new Error(error.message);
}
}
detail = async function (url) {
try {
if (!url.includes('www.xvideos.com')) throw new Error('Invalid url.');
const { data } = await axios.get(`${this.proxy}${url}`);
const $ = cheerio.load(data);
let scriptContent = null;
$('#video-player-bg script').each((_, el) => {
const html = $(el).html() || '';
if (html.includes('html5player.setThumbUrl')) {
scriptContent = html;
return false;
}
});
const ex = (regex) => (scriptContent?.match(regex) || [])[1] || null;
const confMatch = data.match(/window\.xv\.conf\s*=\s*(\{[\s\S]+?\});\s*<\/script>/);
const conf = confMatch ? JSON.parse(confMatch[1]) : {};
const videoData = conf?.data || {};
const dynData = conf?.dyn || {};
const title = dynData.video_title || $('meta[property="og:title"]').attr('content') || null;
const tags = videoData.video_tags || [];
const uploaderName = videoData.uploader || null;
const uploaderUrl = videoData.uploader_url ? `https://www.xvideos.com${videoData.uploader_url}` : null;
const h2Text = $('h2.page-title').text().trim();
const durationRaw = $('h2.page-title .duration').text().trim() || null;
const resolutionRaw = $('h2.page-title .video-hd-mark').text().trim() || null;
const viewsRaw = $('#video-tabs #v-views strong').first().text().trim() || null;
const ratingGoodPerc = $('#video-tabs .rating-good-perc').first().text().trim() || null;
const ratingBadPerc = $('#video-tabs .rating-bad-perc').first().text().trim() || null;
const votesGood = $('#video-tabs .rating-good-nbr').text().trim() || null;
const votesBad = $('#video-tabs .rating-bad-nbr').text().trim() || null;
const totalVotes = $('#video-tabs .rating-total-txt').text().trim() || null;
const ldJsonRaw = $('script[type="application/ld+json"]').first().html();
let uploadDateParsed = null;
let durationISO = null;
if (ldJsonRaw) {
try {
const ld = JSON.parse(ldJsonRaw);
uploadDateParsed = ld.uploadDate || null;
durationISO = ld.duration || null;
} catch (_) {}
}
return {
id: String(videoData.id_video || dynData.id || ''),
eid: videoData.encoded_id_video || null,
title,
uploader: {
name: uploaderName,
url: uploaderUrl,
},
duration: durationRaw || durationISO || null,
views: viewsRaw || null,
resolution: resolutionRaw || null,
rating: {
good: ratingGoodPerc,
bad: ratingBadPerc,
totalVotes,
},
votes: {
good: votesGood,
bad: votesBad,
},
uploadDate: uploadDateParsed,
tags,
thumbnail: {
cover: ex(/html5player\.setThumbUrl\('(.*?)'\);/) || null,
cover169: ex(/html5player\.setThumbUrl169\('(.*?)'\);/) || null,
slide: ex(/html5player\.setThumbSlide\('(.*?)'\);/) || null,
slideBig: ex(/html5player\.setThumbSlideBig\('(.*?)'\);/) || null,
},
videos: (() => {
const low = ex(/html5player\.setVideoUrlLow\('(.*?)'\);/);
const high = ex(/html5player\.setVideoUrlHigh\('(.*?)'\);/);
const HLS = ex(/html5player\.setVideoHLS\('(.*?)'\);/);
const getRes = u => u?.match(/video_(\d+p)/)?.[1] || null;
return {
[`${getRes(low) || 'low'}`]: low || null,
[`${getRes(high) || 'high'}`]: high || null,
HLS: HLS || null,
};
})()
};
} catch (error) {
throw new Error(error.message);
}
}
};
const x = new Xvideos();
x.search('girl').then(console.log);