@mashakujou/manhwaweb-scraper
Version:
Scraper ligero para obtener datos de capítulos de Manhwaweb desde su API pública
147 lines (126 loc) • 3.91 kB
JavaScript
const axios = require('axios');
let debug = false;
function setDebug(flag) {
debug = !!flag;
}
function logDebug(...args) {
if (debug) console.log('[DEBUG]', ...args);
}
function slug(url) {
const s = url.split('/').pop();
logDebug('slug:', s);
return s;
}
function urls(id) {
const base = 'https://manhwawebbackend-production.up.railway.app/chapters';
const result = {
see: `${base}/see/${id}`,
seeprev: `${base}/seeprevpost/${id}`
};
logDebug('urls:', result);
return result;
}
async function full(url) {
const id = slug(url);
const { see, seeprev } = urls(id);
logDebug('full: fetching see and seeprev');
try {
const [seeRes, seeprevRes] = await Promise.all([
axios.get(see),
axios.get(seeprev)
]);
logDebug('full: responses received');
return {
title: `${seeRes.data.name} ${seeRes.data.chapter?.chapter || ''}`,
imgs: seeRes.data.chapter?.img || [],
prev: seeprevRes.data.chapterAnterior || null,
next: seeprevRes.data.chapterSiguiente || null,
raw: { see: seeRes.data, seeprevpost: seeprevRes.data }
};
} catch (err) {
logDebug('full: error', err.message);
throw new Error('Error: ' + err.message);
}
}
async function images(url) {
const id = slug(url);
const { see } = urls(id);
logDebug('images: fetching images');
try {
const res = await axios.get(see);
logDebug('images: response received, images count:', (res.data.chapter?.img || []).length);
return res.data.chapter?.img || [];
} catch (err) {
logDebug('images: error', err.message);
throw new Error('Error: ' + err.message);
}
}
async function prevNext(url) {
const id = slug(url);
const { seeprev } = urls(id);
logDebug('prevNext: fetching prev and next chapters');
try {
const res = await axios.get(seeprev);
logDebug('prevNext: response received');
return {
prev: res.data.chapterAnterior || null,
next: res.data.chapterSiguiente || null
};
} catch (err) {
logDebug('prevNext: error', err.message);
throw new Error('Error: ' + err.message);
}
}
async function seriesInfo(input) {
const id = typeof input === 'string' && input.includes('manhwa/')
? input.split('/').pop()
: input;
const base = 'https://manhwawebbackend-production.up.railway.app/series/info';
logDebug('seriesInfo: fetching info for id', id);
try {
const res = await axios.get(`${base}/${id}`);
logDebug('seriesInfo: response received');
return res.data;
} catch (err) {
logDebug('seriesInfo: error', err.message);
throw new Error('Error: ' + err.message);
}
}
async function search(q, p = 0, includeFirstChapter = false) {
const url = `https://manhwawebbackend-production.up.railway.app/manhwa/library?buscar=${encodeURIComponent(q)}&estado=&tipo=&erotico=&demografia=&order_item=alfabetico&order_dir=desc&page=${p}&generes=`;
logDebug('search: fetching search results for query', q);
try {
const { data } = await axios.get(url);
if (!data?.data) {
logDebug('search: no data found');
return { results: [], totalPages: 0 };
}
const perPage = 20;
const totalItems = data.total || data.data.length;
const totalPages = Math.ceil(totalItems / perPage);
const results = data.data.map(item => {
const serieUrl = `https://manhwaweb.com/manhwa/${item._id}`;
const chapterUrl = `https://manhwaweb.com/leer/${item._id}-1`;
return {
title: item.the_real_name,
url: serieUrl,
...(includeFirstChapter && { firstChapterUrl: chapterUrl })
};
});
logDebug('search: results count', results.length);
return { results, totalPages };
} catch (e) {
logDebug('search: error', e.message);
return { results: [], totalPages: 0 };
}
}
module.exports = {
slug,
urls,
full,
images,
prevNext,
seriesInfo,
search,
setDebug
};