const express = require('express'); const cors = require('cors'); const { gotScraping } = require('got-scraping'); const cheerio = require('cheerio'); const axios = require('axios'); const app = express(); const PORT = process.env.PORT || 7860; // Port default Hugging Face app.use(cors()); app.use(express.json()); // --- Copy & Paste Fungsi Scraping Kamu di Sini --- const BASE_URL = 'https://04x.manhwaland.land'; const SCRAPER_HEADERS = { 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'referer': BASE_URL, 'accept-language': 'en-US,en;q=0.9,id;q=0.8', }; async function fetchHtml(url) { const response = await gotScraping({ url, headers: SCRAPER_HEADERS, retry: { limit: 3, methods: ['GET'] }, timeout: { request: 10000 } }); return response.body; } async function scrapeHome() { const html = await fetchHtml(BASE_URL); const $ = cheerio.load(html); const results = []; $('.listupd .bs').each((i, el) => { results.push({ title: $(el).find('.tt').text().trim(), url: $(el).find('a').attr('href'), image: $(el).find('img').attr('src'), type: $(el).find('.type').text().trim(), chapter: $(el).find('.epxs').text().trim() }); }); return results; } async function searchManga(query) { const url = `${BASE_URL}/?s=${encodeURIComponent(query)}`; const html = await fetchHtml(url); const $ = cheerio.load(html); const results = []; $('.listupd .bs').each((i, el) => { results.push({ title: $(el).find('.tt').text().trim(), url: $(el).find('a').attr('href'), image: $(el).find('img').attr('src'), rating: $(el).find('.numscore').text().trim() }); }); return results; } async function scrapeMangaDetail(url) { const html = await fetchHtml(url); const $ = cheerio.load(html); const chapters = []; $('#chapterlist li').each((i, el) => { chapters.push({ title: $(el).find('.chapternum').text().trim(), url: $(el).find('a').attr('href'), date: $(el).find('.chapterdate').text().trim() }); }); return { title: $('.entry-title').text().trim(), alternative: $('.alternative').text().trim(), description: $('.entry-content p').text().trim(), cover: $('.thumb img').attr('src'), genres: $('.mgen a').map((i, el) => $(el).text()).get(), status: $('.imptdt:contains("Status") i').text().trim(), author: $('.imptdt:contains("Author") i').text().trim(), chapters: chapters.reverse() }; } async function scrapeChapterImages(chapterUrl) { try { const { data: html } = await axios.get(chapterUrl, { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36' } }); // 1. Ambil teks di antara 'ts_reader.run(' dan ');' const startMarker = 'ts_reader.run('; const endMarker = ');'; const startIndex = html.indexOf(startMarker); const endIndex = html.indexOf(endMarker, startIndex); if (startIndex === -1 || endIndex === -1) { console.log("Gagal menemukan pola ts_reader.run"); return []; } // 2. Potong string untuk mendapatkan JSON-nya saja const jsonString = html.substring(startIndex + startMarker.length, endIndex); // 3. Parse JSON const data = JSON.parse(jsonString); // 4. Ambil array images dari sources // Sesuai source code, data.sources[0].images adalah array URL-nya if (data.sources && data.sources.length > 0) { return data.sources[0].images; } return []; } catch (error) { console.error("Error scraping:", error.message); return []; } } app.get('/home', async (req, res) => { try { const data = await scrapeHome(); res.json({ status: 'success', data }); } catch (err) { res.status(500).json({ status: 'error', message: err.message }); } }); app.get('/search', async (req, res) => { try { const { q } = req.query; if (!q) return res.status(400).json({ message: 'Query parameter "q" is required' }); const data = await searchManga(q); res.json({ status: 'success', data }); } catch (err) { res.status(500).json({ status: 'error', message: err.message }); } }); app.get('/detail', async (req, res) => { try { const { url } = req.query; if (!url) return res.status(400).json({ message: 'URL is required' }); const data = await scrapeMangaDetail(url); res.json({ status: 'success', data }); } catch (err) { res.status(500).json({ status: 'error', message: err.message }); } }); app.get('/chapter', async (req, res) => { try { const { url } = req.query; if (!url) return res.status(400).json({ message: 'URL is required' }); const data = await scrapeChapterImages(url); res.json({ status: 'success', data }); } catch (err) { res.status(500).json({ status: 'error', message: err.message }); } }); app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); });