rynn-k / gists
cosplaytele.js javascript
const axios = require('axios');
const cheerio = require('cheerio');

class CosplayTele {
    constructor(proxy = 'https://uncors.netlify.app/?destination=') {
        this.proxy = proxy;
        this.base = 'https://cosplaytele.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 = page > 1 ? `${this.base}/page/${page}/?s=${encodeURIComponent(query)}` : `${this.base}/?s=${encodeURIComponent(query)}`;
            const { data } = await axios.get(`${this.proxy}${url}`);
            const $ = cheerio.load(data);
            
            const results = await Promise.all($('#post-list .col.post-item').toArray().map(async (el) => {
                const a = $(el).find('.box-image a').first();
                const postUrl = a.attr('href') || null;
                const title = ($(el).find('.post-title a').text().trim() || a.attr('aria-label')).replace(/\s*[\u201c\u201d"'「」]\d.*$/, '').trim() || null;
                const thumbnail = $(el).find('.box-image img').attr('src') || null;
                const excerptRaw = $(el).find('.from_the_blog_excerpt').text().trim() || null;
                const cosplayer = excerptRaw?.match(/Cosplayer:\s*([^\n]+?)(?:\s{2,}|Character:|$)/)?.[1]?.trim() || null;
                const character = excerptRaw?.match(/Character:\s*([^\n]+?)(?:\s{2,}|Appear In:|$)/)?.[1]?.trim() || null;
                const appears_in = excerptRaw?.match(/Appear In:\s*([^\n]+?)(?:\s{2,}|Photos:|$)/)?.[1]?.trim() || null;
                const media = excerptRaw?.match(/Photos:\s*([^\n\[]+?)(?:\s{2,}|File|\[|$)/)?.[1]?.trim() || null;
                const slug = postUrl ? postUrl.replace(/https?:\/\/[^/]+\//, '').replace(/\/$/, '') : null;
                
                let id = null;
                if (slug) {
                    try {
                        const { data: apiData } = await axios.get(`${this.proxy}${this.base}/wp-json/wp/v2/posts?slug=${slug}`);
                        if (Array.isArray(apiData) && apiData.length > 0) id = apiData[0].id || null;
                    } catch (_) {}
                }
                
                return {
                    id,
                    title,
                    slug,
                    cosplayer,
                    character,
                    appears_in,
                    media,
                    thumbnail,
                    url: postUrl
                };
            }));
            
            const pageLinks = $('.page-numbers.nav-pagination a.page-number');
            let totalPages = 1;
            pageLinks.each((_, el) => {
                const n = parseInt($(el).text().trim());
                if (!isNaN(n) && n > totalPages) totalPages = n;
            });
            
            const currentPage = parseInt($('.page-numbers.nav-pagination .current').text().trim()) || parseInt(page);
            if (parseInt(page) > totalPages) throw new Error(`Page ${page} exceeds total pages (${totalPages}).`);
            
            return {
                page: currentPage,
                total_pages: totalPages,
                results,
            };
        } catch (error) {
            throw new Error(error.message);
        }
    }
    
    detail = async function (url) {
        try {
            if (!url.includes('cosplaytele.com')) throw new Error('Invalid url.');
            
            const { data } = await axios.get(`${this.proxy}${url}`);
            const $ = cheerio.load(data);
            
            const articleId = $('article[id^="post-"]').attr('id');
            const id = articleId ? parseInt(articleId.replace('post-', '')) || null : null;
            const title = $('h1.entry-title').text().trim().replace(/\s*[\u201c\u201d"'「」]\d.*$/, '').trim() || null;
            const date = $('time.entry-date').attr('datetime') || null;
            
            const categories = [];
            $('.entry-category a').each((_, el) => {
                categories.push($(el).text().trim());
            });
            
            const tags = [];
            $('.entry-meta a[rel="tag"]').each((_, el) => {
                tags.push($(el).text().trim());
            });
            
            const bq = $('blockquote');
            let cosplayer = null, character = null, appears_in = null, photos = null, fileSize = null;
            bq.find('strong').each((_, el) => {
                const text = $(el).text().trim();
                if (text.startsWith('Cosplayer:')) cosplayer = text.replace('Cosplayer:', '').trim();
                else if (text.startsWith('Character:')) character = $(el).find('a').text().trim() || text.replace('Character:', '').trim();
                else if (text.startsWith('Appear In:')) appears_in = $(el).find('a').text().trim() || text.replace('Appear In:', '').trim();
                else if (text.startsWith('Photos:')) photos = text.replace('Photos:', '').trim();
                else if (text.startsWith('File Size:')) fileSize = text.replace('File Size:', '').trim();
            });
            
            const unzipPassword = bq.find('input[type="text"]').attr('value') || null;
            
            const downloads = {};
            $('.entry-content a.button').each((_, el) => {
                const href = $(el).attr('href') || null;
                const label = $(el).text().trim().toLowerCase();
                if (!href) return;
                if (label.includes('mediafire')) downloads.mediafire = href;
                else if (label.includes('telegram')) downloads.telegram = href;
                else if (label.includes('gofile')) downloads.gofile = href;
                else if (label.includes('sorafolder')) downloads.sorafolder = href;
                else downloads[label.replace(/^download\s*/i, '').trim() || 'other'] = href;
            });
            
            const images = [];
            $('#gallery-1 .gallery-item a').each((_, el) => {
                images.push($(el).attr('href') || null);
            });
            
            const videoEmbed = $('iframe[src*="cossora.stream"]').attr('src') || null;
            const thumbnail = $('meta[property="og:image"]').attr('content') || $('.page-title .title-bg img').attr('src') || images[0] || null;
            
            return {
                id,
                title,
                date,
                categories,
                tags,
                cosplayer,
                character,
                appears_in,
                photos,
                file_size: fileSize,
                unzip_password: unzipPassword,
                thumbnail,
                downloads,
                video_embed: videoEmbed,
                images,
            };
        } catch (error) {
            throw new Error(error.message);
        }
    }
}

// Usage:
const ct = new CosplayTele();
ct.detail('https://cosplaytele.com/nahida-33/').then(console.log);
7092 bytes · Updated Mar 22, 2026