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);
}
}
}
const ct = new CosplayTele();
ct.detail('https://cosplaytele.com/nahida-33/').then(console.log);