// donghub.js — bundle standalone. npm deps: axios, cheerio, crypto, node:url /** * Scraper: donghub.js * Source : kyioapi/donghub.js * * Input : Function parameters / CLI args (e.g. query: string, url: string, options: object) * Output : Promise (JSON formatted scraping response / stream URL / media metadata) * * CLI Run: * node kyioapi/donghub.js [functionName] [args...] * node cli.js kyioapi/donghub.js [functionName] [args...] */ import * as __ns_86 from 'axios' const axios = __ns_86.default import * as __ns_87 from 'cheerio' const cheerio = __ns_87 import * as __ns_88 from 'crypto' const crypto = __ns_88.default import * as __ns_89 from 'node:url' const pathToFileURL = __ns_89.pathToFileURL /** * Donghub Scraper * @baseurl https://donghub.vip */ class Donghub { constructor() { this.baseUrl = 'https://donghub.vip'; this.headers = { 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8', 'Accept-Language': 'id-ID,id;q=0.9,en-US;q=0.8,en;q=0.7', 'Sec-Ch-Ua-Platform': '"Linux"', 'Upgrade-Insecure-Requests': '1' }; this.cookies = this.generateCookies(); } generateCookies() { const ga1 = crypto.randomBytes(4).toString('hex') + '.' + crypto.randomBytes(5).toString('hex'); const ts = Date.now(); return `_ga=GA1.1.${ga1}; HstCfa5009307=${ts}; HstCla5009307=${ts}; HstCmu5009307=${ts}; HstPn5009307=1; _ga_BC9Q6DVLH9=GS2.1.s${ts}$o1$g1$t${ts + 1000}$j35$l0$h0`; } async getEpisodeVideoUrl(episodeUrl) { const response = await axios.get(episodeUrl, { headers: { ...this.headers, 'Cookie': this.cookies } }); const $ = cheerio.load(response.data); const videoSources = []; $('iframe').each((i, el) => { const src = $(el).attr('src'); if (src) videoSources.push({ type: 'iframe', url: src, provider: src.includes('youtube') ? 'youtube' : src.includes('drive') ? 'gdrive' : 'embed' }); }); $('video source').each((i, el) => { if ($(el).attr('src')) videoSources.push({ type: 'video', url: $(el).attr('src'), format: $(el).attr('type') || 'video/mp4' }); }); const playerScript = $('script:contains("player.src")').text(); if (playerScript) { const matches = playerScript.match(/https?:\/\/[^\s"']+\.(?:mp4|mkv|m3u8)[^\s"']*/g); if (matches) matches.forEach(url => videoSources.push({ type: 'script', url: url, quality: 'unknown' })); } return videoSources; } async search(query) { const response = await axios.get(`${this.baseUrl}/?s=${encodeURIComponent(query)}`, { headers: { ...this.headers, 'Cookie': this.cookies, 'Referer': this.baseUrl + '/' } }); const $ = cheerio.load(response.data); const results = []; $('.listupd article.bs').each((i, el) => { const article = $(el); results.push({ title: article.find('a').attr('title') || article.find('h2').text(), url: article.find('a').attr('href'), image: article.find('img').attr('src'), type: article.find('.typez').text(), status: article.find('.epx').text(), subtitle: article.find('.sb').text(), hot: article.find('.hotbadge').length > 0 }); }); return { query, results }; } async getDetail(url) { const response = await axios.get(url, { headers: { ...this.headers, 'Cookie': this.cookies, 'Referer': this.baseUrl + '/' } }); const $ = cheerio.load(response.data); const detail = { title: $('.entry-title').first().text(), url: url, image: $('.thumb img').attr('src') || $('.bigcover img').attr('src'), alternative: $('.alter').text(), status: $('.spe span:contains("Status:")').text().replace('Status:', '').trim(), network: $('.spe a[href*="network"]').text(), released: $('.spe span:contains("Released:")').text().replace('Released:', '').trim(), duration: $('.spe span:contains("Duration:")').text().replace('Duration:', '').trim(), type: $('.spe span:contains("Type:")').text().replace('Type:', '').trim(), episodes: $('.spe span:contains("Episodes:")').text().replace('Episodes:', '').trim(), genres: $('.genxed a').map((i, el) => $(el).text()).get(), synopsis: $('.entry-content p').first().text().replace(/\n/g, ' ').trim(), episodeList: [] }; const epItems = $('.eplister ul li').map((i, el) => ({ episode: $(el).find('.epl-num').text(), title: $(el).find('.epl-title').text(), url: $(el).find('a').attr('href'), releaseDate: $(el).find('.epl-date').text(), subtitle: $(el).find('.epl-sub .status').text() })).get(); detail.episodeList = await Promise.all(epItems.map(async (ep) => ({ ...ep, videoSources: await this.getEpisodeVideoUrl(ep.url) }))); return detail; } } const donghub = new Donghub(); export { Donghub, donghub }; export default Donghub; // ───────────────────────────────────────────────────────────────────── // Self-test: jalankan langsung dengan `node donghub.js [args...]` // ───────────────────────────────────────────────────────────────────── if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { const __exports = { Donghub: Donghub, donghub: donghub } const __fns = Object.fromEntries(Object.entries(__exports).filter(([, v]) => typeof v === 'function')) const __names = Object.keys(__fns) if (__names.length === 0) { console.log('Tidak ada fungsi yang bisa di-demo. Import modul ini dari kode lain.') } else { const __search = __names.find((n) => /search|cari/i.test(n)) const __name = __search || __names[0] const __fn = __fns[__name] const __isClass = /^class\s/.test(Function.prototype.toString.call(__fn)) let __args = process.argv.slice(2).map((a) => { try { return JSON.parse(a) } catch { return a } }) if (__args.length === 0 && __search) __args.push('naruto') if (__args.length === 0 && !__search && !__isClass && __fn.length > 0) { console.log('Fungsi: ' + __names.join(', ')) console.log('Contoh: node ' + "donghub.js" + ' ' + __name + ' ') } else { ;(async () => { try { const r = __isClass ? new __fn(...__args) : await __fn(...__args) console.log(JSON.stringify(r, null, 2)) } catch (e) { console.error('Error: ' + (e && e.message ? e.message : e)) process.exitCode = 1 } })() } } }