// mangadex.js — bundle standalone. npm deps: axios, node:url /** * Scraper: mangadex.js * Source : kyioapi/mangadex.js * * Input : Function parameters / CLI args (e.g. query: string, url: string, options: object) * Output : Promise (JSON formatted scraping response / stream URL / media metadata) * * CLI Run: * node kyioapi/mangadex.js [functionName] [args...] * node cli.js kyioapi/mangadex.js [functionName] [args...] */ import * as __ns_227 from 'axios' const axios = __ns_227.default import * as __ns_228 from 'node:url' const pathToFileURL = __ns_228.pathToFileURL /** * MangaDex Search Scraper */ async function searchMangaDex(query, limit = 10) { try { const url = 'https://api.mangadex.org/manga'; const params = { title: query, limit: Math.min(limit, 100), // Cap limit to 100 as per common API practices contentRating: ['safe', 'suggestive', 'erotica'], includes: ['cover_art'], order: { followedCount: 'desc', relevance: 'desc' } }; const response = await axios.get(url, { params }); const data = response.data; const results = data.data.map(manga => { const coverRel = manga.relationships.find(rel => rel.type === 'cover_art'); const coverFile = coverRel?.attributes?.fileName || null; return { id: manga.id, title: manga.attributes.title.en || Object.values(manga.attributes.title)[0] || 'No title', altTitles: manga.attributes.altTitles.map(t => Object.values(t)[0]).filter(Boolean), status: manga.attributes.status, year: manga.attributes.year, demographic: manga.attributes.publicationDemographic, contentRating: manga.attributes.contentRating, lastChapter: manga.attributes.lastChapter, description: manga.attributes.description.en?.substring(0, 200) + '...' || null, tags: manga.attributes.tags.map(t => t.attributes.name.en), coverUrl: coverFile ? `https://uploads.mangadex.org/covers/${manga.id}/${coverFile}` : null, externalLinks: manga.attributes.links || {} }; }); return { query: query, limit: params.limit, total: data.total, offset: data.offset, count: results.length, results: results }; } catch (error) { throw new Error(`MangaDex Search Error: ${error.message}`); } } export { searchMangaDex }; export default searchMangaDex; // ───────────────────────────────────────────────────────────────────── // Self-test: jalankan langsung dengan `node mangadex.js [args...]` // ───────────────────────────────────────────────────────────────────── if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { const __exports = { searchMangaDex: searchMangaDex } const __fns = Object.fromEntries(Object.entries(__exports).filter(([, v]) => typeof v === 'function')) const __names = Object.keys(__fns) if (__names.length === 0) { console.log('Tidak ada fungsi yang bisa di-demo. Import modul ini dari kode lain.') } else { const __search = __names.find((n) => /search|cari/i.test(n)) const __name = __search || __names[0] const __fn = __fns[__name] const __isClass = /^class\s/.test(Function.prototype.toString.call(__fn)) let __args = process.argv.slice(2).map((a) => { try { return JSON.parse(a) } catch { return a } }) if (__args.length === 0 && __search) __args.push('naruto') if (__args.length === 0 && !__search && !__isClass && __fn.length > 0) { console.log('Fungsi: ' + __names.join(', ')) console.log('Contoh: node ' + "mangadex.js" + ' ' + __name + ' ') } else { ;(async () => { try { const r = __isClass ? new __fn(...__args) : await __fn(...__args) console.log(JSON.stringify(r, null, 2)) } catch (e) { console.error('Error: ' + (e && e.message ? e.message : e)) process.exitCode = 1 } })() } } }