const axios = require('axios');
const cheerio = require('cheerio');
class Tube8 {
constructor(proxy = 'https://uncors.netlify.app/?destination=') {
this.proxy = proxy;
this.baseUrl = 'https://www.tube8.com';
}
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 url = `${this.baseUrl}/searches.html/?q=${encodeURIComponent(query)}&page=${page}`;
const { data } = await axios.get(`${this.proxy}${url}`);
const $ = cheerio.load(data);
const results = [];
$('article.video-box').each((_, el) => {
const title = $(el).find('a.video-title-text span').text().trim();
const video_href = $(el).find('a.tm_video_link').attr('href');
const video_url = video_href ? `${this.baseUrl}${video_href}` : null;
const cover = $(el).find('img.thumb-image').attr('data-src') || null;
const preview = $(el).find('img.thumb-image').attr('data-mediabook') || null;
const duration = $(el).find('div.tm_video_duration span').text().replace(/\s+/g, '').trim() || null;
const viewRatingContainer = $(el).find('div.view-rating-container');
const views = viewRatingContainer.find('span.info-views').eq(0).clone().children('span.sr-only').remove().end().text().trim() || null;
const rating = viewRatingContainer.find('span.info-views').eq(1).clone().children('span.sr-only').remove().end().text().trim() || null;
const author_el = $(el).find('div.author-title-container a.author-title-text');
const author_name = author_el.text().trim() || null;
const author_href = author_el.attr('href') || null;
const author_url = author_href ? (author_href.startsWith('http') ? author_href : `${this.baseUrl}${author_href}`) : null;
const is_channel = $(el).find('i.icon-clapper-open').length > 0;
const is_verified = $(el).find('i.icon-checkmark-performer').length > 0;
const performers = [];
$(el).find('div.performers-list a.channel-performer').each((_, perf_el) => {
const perf_name = $(perf_el).text().trim();
const perf_href = $(perf_el).attr('href');
if (perf_name) {
performers.push({
name: perf_name,
url: perf_href ? `${this.baseUrl}${perf_href}` : null,
});
}
});
const video_id = $(el).attr('data-video-id') || null;
const uploader_id = $(el).attr('data-uploader-id') || null;
const uploader_type = $(el).attr('data-uploader-type') || null;
if (title && video_url) {
results.push({
id: video_id,
title,
uploader: {
id: uploader_id,
name: author_name,
url: author_url,
type: uploader_type,
is_channel,
is_verified,
},
performers,
duration,
views,
rating,
thumbnail: {
cover,
preview,
},
url: video_url,
});
}
});
const total_pages = (() => {
const pages = [];
$('nav#pagination ul.pagination_pages_list li.page-number').each((_, el) => {
const num = parseInt($(el).find('a').text().trim() || $(el).find('div').text().trim());
if (!isNaN(num)) pages.push(num);
});
return pages.length ? Math.max(...pages) : null;
})();
if (total_pages && parseInt(page) > total_pages) throw new Error(`Page ${page} exceeds total pages (${total_pages}).`);
return {
page: parseInt(page),
total_pages,
results,
};
} catch (error) {
throw new Error(error.message);
}
};
detail = async function (url) {
try {
if (!url.includes('www.tube8.com')) throw new Error('Invalid url.');
const { data } = await axios.get(`${this.proxy}${url}`);
const $ = cheerio.load(data);
const watch_container = $('div#watch-container');
const video_id = watch_container.attr('data-video-id') || null;
const title = $('h1.videoTitle.tm_videoTitle').text().trim() || null;
const uploader_el = $('div.va-info-text div.submitByLink a');
const uploader_name = uploader_el.text().trim() || null;
const uploader_href = uploader_el.attr('href') || null;
const uploader_url = uploader_href ? (uploader_href.startsWith('http') ? uploader_href : `${this.baseUrl}${uploader_href}`) : null;
const is_verified = $('div.va-info-text span.verified-wrapper').length > 0;
const uploader_avatar = $('div.image-wrapper.userAvatar img.userAvatar').attr('src') || null;
const rating = $('span.tm_rating_percent').text().trim() || null;
const views = $('div.feature-actionViews span.tm_infoValue').text().trim() || null;
const date_raw = $('span.publishedDate').text().trim();
const date_published = date_raw.replace('Published on', '').trim() || null;
const categories = [];
$('div.js_categoriesWrapper a.categories-tags').each((_, el) => {
const name = $(el).text().trim();
if (name) categories.push(name);
});
const tags = [];
$('div.js_categoriesWrapper a.bubble-porntag:not(.categories-tags)').each((_, el) => {
const name = $(el).text().trim();
if (name) tags.push(name);
});
const performers = [];
$('div.suggest-pornstars-wrapper a').each((_, el) => {
const name = $(el).text().trim();
const href = $(el).attr('href');
if (name) performers.push({
name,
url: href ? `${this.baseUrl}${href}` : null,
});
});
const player_script = $('script#tm_pc_player_setup').html() || '';
const duration_match = player_script.match(/"video_duration"\s*:\s*"(\d+)"/);
const duration_seconds = duration_match ? parseInt(duration_match[1]) : null;
const duration = duration_seconds ? `${Math.floor(duration_seconds / 60)}:${String(duration_seconds % 60).padStart(2, '0')}` : null;
const thumbnail = (() => {
const match = player_script.match(/"image_url"\s*:\s*"([^"]+)"/);
return match ? match[1].replace(/\\\//g, '/') : null;
})();
let download_urls = [];
const media_def_match = player_script.match(/"mediaDefinitions"\s*:\s*(\[.*?\])\s*[,\]]/s);
if (media_def_match) {
try {
const media_definitions = JSON.parse(media_def_match[1]);
const mp4_entry = media_definitions.find(m => m.format === 'mp4' && m.videoUrl);
if (mp4_entry) {
const mp4_url = mp4_entry.videoUrl.replace(/\\\//g, '/');
const { data: download_list } = await axios.get(`${this.proxy}${mp4_url}`);
download_urls = (Array.isArray(download_list) ? download_list : []).map(item => ({
format: item.format || 'mp4',
quality: item.quality || null,
video_url: item.videoUrl || null,
}));
}
} catch (_) {}
}
const related_videos = [];
$('div#relatedVideosWrapper article.video-box').each((_, el) => {
const title = $(el).find('a.video-title-text span').text().trim();
const video_href = $(el).find('a.tm_video_link').attr('href');
const video_url = video_href ? `${this.baseUrl}${video_href}` : null;
const cover = $(el).find('img.thumb-image').attr('data-src') || null;
const preview = $(el).find('img.thumb-image').attr('data-mediabook') || null;
const duration = $(el).find('div.tm_video_duration span').text().replace(/\s+/g, '').trim() || null;
const viewRatingContainer = $(el).find('div.view-rating-container');
const views = viewRatingContainer.find('span.info-views').eq(0).clone().children('span.sr-only').remove().end().text().trim() || null;
const rating = viewRatingContainer.find('span.info-views').eq(1).clone().children('span.sr-only').remove().end().text().trim() || null;
const author_el = $(el).find('div.author-title-container a.author-title-text');
const author_name = author_el.text().trim() || null;
const author_href = author_el.attr('href') || null;
const author_url = author_href ? (author_href.startsWith('http') ? author_href : `${this.baseUrl}${author_href}`) : null;
const is_channel = $(el).find('i.icon-clapper-open').length > 0;
const is_verified = $(el).find('i.icon-checkmark-performer').length > 0;
const performers = [];
$(el).find('div.performers-list a.channel-performer').each((_, perf_el) => {
const perf_name = $(perf_el).text().trim();
const perf_href = $(perf_el).attr('href');
if (perf_name) {
performers.push({
name: perf_name,
url: perf_href ? `${this.baseUrl}${perf_href}` : null,
});
}
});
const video_id = $(el).attr('data-video-id') || null;
const uploader_id = $(el).attr('data-uploader-id') || null;
const uploader_type = $(el).attr('data-uploader-type') || null;
if (title && video_url) {
related_videos.push({
id: video_id,
title,
uploader: {
id: uploader_id,
name: author_name,
url: author_url,
type: uploader_type,
is_channel,
is_verified,
},
performers,
duration,
views,
rating,
thumbnail: {
cover,
preview,
},
url: video_url,
});
}
});
return {
id: video_id,
title,
uploader: {
name: uploader_name,
url: uploader_url,
avatar: uploader_avatar,
is_verified,
},
views,
rating,
date_published,
duration,
thumbnail,
categories,
tags,
performers,
download_urls,
related_videos,
};
} catch (error) {
throw new Error(error.message);
}
};
}
const t = new Tube8();
t.detail('https://www.tube8.com/porn-video/81879981/').then(res => console.log(JSON.stringify(res, null, 2)));