Back to Home
Gistify Snippet
kyiov
13h ago·1 file(s)
Public

Donghub Donghua Anime Stream & Search Scraper

Scraper for Donghub.vip to search Chinese anime (Donghua), extract stream video sources, m3u8 playlist URLs, and episode lists.

#scraper#donghua#anime#streaming#javascript
0
Raw
donghub.jsjavascript
164 lines
1// donghub.js — bundle standalone. npm deps: axios, cheerio, crypto, node:url
2
3/**
4 * Scraper: donghub.js
5 * Source : kyioapi/donghub.js
6 *
7 * Input : Function parameters / CLI args (e.g. query: string, url: string, options: object)
8 * Output : Promise<Object | Array | Buffer> (JSON formatted scraping response / stream URL / media metadata)
9 *
10 * CLI Run:
11 * node kyioapi/donghub.js [functionName] [args...]
12 * node cli.js kyioapi/donghub.js [functionName] [args...]
13 */
14
15import * as __ns_86 from 'axios'
16const axios = __ns_86.default
17import * as __ns_87 from 'cheerio'
18const cheerio = __ns_87
19import * as __ns_88 from 'crypto'
20const crypto = __ns_88.default
21import * as __ns_89 from 'node:url'
22const pathToFileURL = __ns_89.pathToFileURL
23
24
25
26
27
28/**
29 * Donghub Scraper
30 * @baseurl https://donghub.vip
31 */
32class Donghub {
33 constructor() {
34 this.baseUrl = 'https://donghub.vip';
35 this.headers = {
36 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36',
37 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
38 'Accept-Language': 'id-ID,id;q=0.9,en-US;q=0.8,en;q=0.7',
39 'Sec-Ch-Ua-Platform': '"Linux"',
40 'Upgrade-Insecure-Requests': '1'
41 };
42 this.cookies = this.generateCookies();
43 }
44
45 generateCookies() {
46 const ga1 = crypto.randomBytes(4).toString('hex') + '.' + crypto.randomBytes(5).toString('hex');
47 const ts = Date.now();
48 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`;
49 }
50
51 async getEpisodeVideoUrl(episodeUrl) {
52 const response = await axios.get(episodeUrl, { headers: { ...this.headers, 'Cookie': this.cookies } });
53 const $ = cheerio.load(response.data);
54 const videoSources = [];
55
56 $('iframe').each((i, el) => {
57 const src = $(el).attr('src');
58 if (src) videoSources.push({ type: 'iframe', url: src, provider: src.includes('youtube') ? 'youtube' : src.includes('drive') ? 'gdrive' : 'embed' });
59 });
60
61 $('video source').each((i, el) => {
62 if ($(el).attr('src')) videoSources.push({ type: 'video', url: $(el).attr('src'), format: $(el).attr('type') || 'video/mp4' });
63 });
64
65 const playerScript = $('script:contains("player.src")').text();
66 if (playerScript) {
67 const matches = playerScript.match(/https?:\/\/[^\s"']+\.(?:mp4|mkv|m3u8)[^\s"']*/g);
68 if (matches) matches.forEach(url => videoSources.push({ type: 'script', url: url, quality: 'unknown' }));
69 }
70 return videoSources;
71 }
72
73 async search(query) {
74 const response = await axios.get(`${this.baseUrl}/?s=${encodeURIComponent(query)}`, { headers: { ...this.headers, 'Cookie': this.cookies, 'Referer': this.baseUrl + '/' } });
75 const $ = cheerio.load(response.data);
76 const results = [];
77 $('.listupd article.bs').each((i, el) => {
78 const article = $(el);
79 results.push({
80 title: article.find('a').attr('title') || article.find('h2').text(),
81 url: article.find('a').attr('href'),
82 image: article.find('img').attr('src'),
83 type: article.find('.typez').text(),
84 status: article.find('.epx').text(),
85 subtitle: article.find('.sb').text(),
86 hot: article.find('.hotbadge').length > 0
87 });
88 });
89 return { query, results };
90 }
91
92 async getDetail(url) {
93 const response = await axios.get(url, { headers: { ...this.headers, 'Cookie': this.cookies, 'Referer': this.baseUrl + '/' } });
94 const $ = cheerio.load(response.data);
95 const detail = {
96 title: $('.entry-title').first().text(),
97 url: url,
98 image: $('.thumb img').attr('src') || $('.bigcover img').attr('src'),
99 alternative: $('.alter').text(),
100 status: $('.spe span:contains("Status:")').text().replace('Status:', '').trim(),
101 network: $('.spe a[href*="network"]').text(),
102 released: $('.spe span:contains("Released:")').text().replace('Released:', '').trim(),
103 duration: $('.spe span:contains("Duration:")').text().replace('Duration:', '').trim(),
104 type: $('.spe span:contains("Type:")').text().replace('Type:', '').trim(),
105 episodes: $('.spe span:contains("Episodes:")').text().replace('Episodes:', '').trim(),
106 genres: $('.genxed a').map((i, el) => $(el).text()).get(),
107 synopsis: $('.entry-content p').first().text().replace(/\n/g, ' ').trim(),
108 episodeList: []
109 };
110
111 const epItems = $('.eplister ul li').map((i, el) => ({
112 episode: $(el).find('.epl-num').text(),
113 title: $(el).find('.epl-title').text(),
114 url: $(el).find('a').attr('href'),
115 releaseDate: $(el).find('.epl-date').text(),
116 subtitle: $(el).find('.epl-sub .status').text()
117 })).get();
118
119 detail.episodeList = await Promise.all(epItems.map(async (ep) => ({
120 ...ep,
121 videoSources: await this.getEpisodeVideoUrl(ep.url)
122 })));
123
124 return detail;
125 }
126}
127
128const donghub = new Donghub();
129export { Donghub, donghub };
130export default Donghub;
131
132// ─────────────────────────────────────────────────────────────────────
133// Self-test: jalankan langsung dengan `node donghub.js [args...]`
134// ─────────────────────────────────────────────────────────────────────
135if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
136 const __exports = { Donghub: Donghub, donghub: donghub }
137 const __fns = Object.fromEntries(Object.entries(__exports).filter(([, v]) => typeof v === 'function'))
138 const __names = Object.keys(__fns)
139 if (__names.length === 0) {
140 console.log('Tidak ada fungsi yang bisa di-demo. Import modul ini dari kode lain.')
141 } else {
142 const __search = __names.find((n) => /search|cari/i.test(n))
143 const __name = __search || __names[0]
144 const __fn = __fns[__name]
145 const __isClass = /^class\s/.test(Function.prototype.toString.call(__fn))
146 let __args = process.argv.slice(2).map((a) => { try { return JSON.parse(a) } catch { return a } })
147 if (__args.length === 0 && __search) __args.push('naruto')
148 if (__args.length === 0 && !__search && !__isClass && __fn.length > 0) {
149 console.log('Fungsi: ' + __names.join(', '))
150 console.log('Contoh: node ' + "donghub.js" + ' ' + __name + ' <arg>')
151 } else {
152 ;(async () => {
153 try {
154 const r = __isClass ? new __fn(...__args) : await __fn(...__args)
155 console.log(JSON.stringify(r, null, 2))
156 } catch (e) {
157 console.error('Error: ' + (e && e.message ? e.message : e))
158 process.exitCode = 1
159 }
160 })()
161 }
162 }
163}
164
Discussion & Comments0

No comments yet — be the first to leave feedback or start a code discussion.

Sign in with your GitHub account to write comments and join the discussion.