rynn-k / gists
tube8.js javascript
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);
        }
    };
}

// Usage:
const t = new Tube8();
t.detail('https://www.tube8.com/porn-video/81879981/').then(res => console.log(JSON.stringify(res, null, 2)));
12721 bytes ยท Updated Mar 21, 2026