""" 【 Donghub API Scraper 】 Creator : ShanMolvyr Base : https://donghub.vip Desc : untuk mengambil data dari situs DonghuaStream, termasuk homepage (latest episode & slider), detail anime (sinopsis, genre, episode list), streaming link, search, dan jadwal rilis. Channel : https://whatsapp.com/channel/0029VbB4Kw8EFeXfeExaXc3Q Note : Kembangin sendiri """ import requests from bs4 import BeautifulSoup from flask import Flask, jsonify, request from flask_cors import CORS app = Flask(__name__) CORS(app) BASE_URL = "https://donghub.vip" HEADERS = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } class DonghubScraper: def get_home(self): """Scrape homepage - latest episodes & popular series""" response = requests.get(BASE_URL, headers=HEADERS) soup = BeautifulSoup(response.text, 'html.parser') latest_episodes = [] for item in soup.select('.listupd.normal .styleegg'): title_el = item.select_one('.eggtitle') episode_el = item.select_one('.eggepisode') link_el = item.select_one('a') img_el = item.select_one('img') if link_el: latest_episodes.append({ 'title': title_el.text.strip() if title_el else 'Unknown', 'episode': episode_el.text.strip() if episode_el else 'Episode', 'url': link_el['href'], 'thumbnail': img_el['src'] if img_el else None }) popular = [] for item in soup.select('.serieslist.pop ul li'): title_el = item.select_one('.leftseries h4 a') img_el = item.select_one('.imgseries img') if title_el: popular.append({ 'title': title_el.text.strip(), 'url': title_el['href'], 'thumbnail': img_el['src'] if img_el else None }) return { 'latest_episodes': latest_episodes[:20], 'popular_series': popular[:10] } def get_detail(self, slug): """Get series detail and episode list""" url = f"{BASE_URL}/{slug}/" response = requests.get(url, headers=HEADERS) soup = BeautifulSoup(response.text, 'html.parser') # Basic info title = soup.select_one('.infox h1') if not title: return {'error': 'Series not found'} # Extract metadata status = None total_episodes = None for span in soup.select('.spe span'): text = span.get_text(strip=True) if 'Status:' in text: status = text.replace('Status:', '').strip() if 'Episodes:' in text: total_episodes = text.replace('Episodes:', '').strip() # Genre genres = [a.text.strip() for a in soup.select('.genxed a')] # Synopsis synopsis_el = soup.select_one('.entry-content p') synopsis = synopsis_el.text.strip() if synopsis_el else '' # Poster poster = soup.select_one('.thumb img') poster_url = poster['src'] if poster else None # Episode list episodes = [] for ep in soup.select('.eplister ul li a'): num_el = ep.select_one('.epl-num') title_el = ep.select_one('.epl-title') date_el = ep.select_one('.epl-date') episodes.append({ 'episode': num_el.text.strip() if num_el else '0', 'title': title_el.text.strip() if title_el else '', 'url': ep['href'], 'release_date': date_el.text.strip() if date_el else None }) return { 'title': title.text.strip(), 'status': status, 'total_episodes': total_episodes, 'genres': genres, 'synopsis': synopsis, 'poster': poster_url, 'episodes': episodes } def get_watch(self, episode_slug): """Get video URL and navigation for specific episode""" url = f"{BASE_URL}/{episode_slug}" response = requests.get(url, headers=HEADERS) soup = BeautifulSoup(response.text, 'html.parser') # Episode title title = soup.select_one('h1.entry-title') # Video URL from iframe iframe = soup.select_one('#embed_holder iframe') video_url = iframe['src'] if iframe else None # Video servers (mirrors) servers = [] for option in soup.select('.mirror option'): value = option.get('value') if value and value.strip(): # Value is base64 encoded iframe import base64 try: decoded = base64.b64decode(value).decode('utf-8') # Extract src from iframe src_start = decoded.find('src="') if src_start != -1: src_end = decoded.find('"', src_start + 5) server_url = decoded[src_start + 5:src_end] servers.append({ 'name': option.text.strip(), 'url': server_url }) except: servers.append({ 'name': option.text.strip(), 'url': value }) # Navigation nav_links = soup.select('.naveps .nvs a') prev_url = None next_url = None for link in nav_links: href = link['href'] text = link.text.lower() if 'next' in text or '›' in text: next_url = href elif 'prev' in text or '‹' in text: prev_url = href # Series info series_title = soup.select_one('.single-info .infox h2') series_link = soup.select_one('.single-info .infox h2 a') return { 'title': title.text.strip() if title else episode_slug, 'video_url': video_url, 'servers': servers, 'prev_episode': prev_url, 'next_episode': next_url, 'series': { 'title': series_title.text.strip() if series_title else None, 'url': series_link['href'] if series_link else None } } def search(self, query): """Search for series""" url = f"{BASE_URL}/?s={query}" response = requests.get(url, headers=HEADERS) soup = BeautifulSoup(response.text, 'html.parser') results = [] for item in soup.select('.listupd .bsx'): link = item.select_one('a') title = item.select_one('.tt h2') img = item.select_one('img') if link and title: results.append({ 'title': title.text.strip(), 'url': link['href'], 'thumbnail': img['src'] if img else None }) return {'query': query, 'results': results} # Flask API Endpoints scraper = DonghubScraper() @app.route('/api/home') def api_home(): return jsonify(scraper.get_home()) @app.route('/api/detail/') def api_detail(slug): return jsonify(scraper.get_detail(slug)) @app.route('/api/watch/') def api_watch(episode_slug): return jsonify(scraper.get_watch(episode_slug)) @app.route('/api/search') def api_search(): query = request.args.get('q', '') if not query: return jsonify({'error': 'Query parameter q required'}), 400 return jsonify(scraper.search(query)) if __name__ == '__main__': print("🚀 Donghub API running on http://localhost:5000") print("📋 Available endpoints:") print(" GET /api/home") print(" GET /api/detail/ (ex: renegade-immortal)") print(" GET /api/watch/ (ex: renegade-immortal-episode-75-subtitle-indonesia)") print(" GET /api/search?q=") app.run(debug=True, host='0.0.0.0', port=5000)