Spaces:
Paused
Paused
| 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}`); | |
| }); | |