Back to Home
Gistify Snippet
Public
MangaDex Search & Metadata Scraper
Fast MangaDex v5 API client for searching manga titles, cover art URLs, chapter status, genres, and publication metadata.
#mangadex#manga#scraper#anime#javascript#api
0
Rawmangadex.jsjavascript
111 lines| 1 | // mangadex.js — bundle standalone. npm deps: axios, node:url |
| 2 | |
| 3 | /** |
| 4 | * Scraper: mangadex.js |
| 5 | * Source : kyioapi/mangadex.js |
| 6 | * |
| 7 | * Input : Function parameters / CLI args (e.g. query: string, url: string, options: object) |
| 8 | * Output : Promise<Object | Array | Buffer> (JSON formatted scraping response / stream URL / media metadata) |
| 9 | * |
| 10 | * CLI Run: |
| 11 | * node kyioapi/mangadex.js [functionName] [args...] |
| 12 | * node cli.js kyioapi/mangadex.js [functionName] [args...] |
| 13 | */ |
| 14 | |
| 15 | import * as __ns_227 from 'axios' |
| 16 | const axios = __ns_227.default |
| 17 | import * as __ns_228 from 'node:url' |
| 18 | const pathToFileURL = __ns_228.pathToFileURL |
| 19 | |
| 20 | |
| 21 | |
| 22 | /** |
| 23 | * MangaDex Search Scraper |
| 24 | */ |
| 25 | async function searchMangaDex(query, limit = 10) { |
| 26 | try { |
| 27 | const url = 'https://api.mangadex.org/manga'; |
| 28 | const params = { |
| 29 | title: query, |
| 30 | limit: Math.min(limit, 100), // Cap limit to 100 as per common API practices |
| 31 | contentRating: ['safe', 'suggestive', 'erotica'], |
| 32 | includes: ['cover_art'], |
| 33 | order: { |
| 34 | followedCount: 'desc', |
| 35 | relevance: 'desc' |
| 36 | } |
| 37 | }; |
| 38 | |
| 39 | const response = await axios.get(url, { params }); |
| 40 | const data = response.data; |
| 41 | |
| 42 | const results = data.data.map(manga => { |
| 43 | const coverRel = manga.relationships.find(rel => rel.type === 'cover_art'); |
| 44 | const coverFile = coverRel?.attributes?.fileName || null; |
| 45 | |
| 46 | return { |
| 47 | id: manga.id, |
| 48 | title: manga.attributes.title.en || Object.values(manga.attributes.title)[0] || 'No title', |
| 49 | altTitles: manga.attributes.altTitles.map(t => Object.values(t)[0]).filter(Boolean), |
| 50 | status: manga.attributes.status, |
| 51 | year: manga.attributes.year, |
| 52 | demographic: manga.attributes.publicationDemographic, |
| 53 | contentRating: manga.attributes.contentRating, |
| 54 | lastChapter: manga.attributes.lastChapter, |
| 55 | description: manga.attributes.description.en?.substring(0, 200) + '...' || null, |
| 56 | tags: manga.attributes.tags.map(t => t.attributes.name.en), |
| 57 | coverUrl: coverFile ? `https://uploads.mangadex.org/covers/${manga.id}/${coverFile}` : null, |
| 58 | externalLinks: manga.attributes.links || {} |
| 59 | }; |
| 60 | }); |
| 61 | |
| 62 | return { |
| 63 | query: query, |
| 64 | limit: params.limit, |
| 65 | total: data.total, |
| 66 | offset: data.offset, |
| 67 | count: results.length, |
| 68 | results: results |
| 69 | }; |
| 70 | |
| 71 | } catch (error) { |
| 72 | throw new Error(`MangaDex Search Error: ${error.message}`); |
| 73 | } |
| 74 | } |
| 75 | |
| 76 | export { searchMangaDex }; |
| 77 | export default searchMangaDex; |
| 78 | |
| 79 | // ───────────────────────────────────────────────────────────────────── |
| 80 | // Self-test: jalankan langsung dengan `node mangadex.js [args...]` |
| 81 | // ───────────────────────────────────────────────────────────────────── |
| 82 | if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { |
| 83 | const __exports = { searchMangaDex: searchMangaDex } |
| 84 | const __fns = Object.fromEntries(Object.entries(__exports).filter(([, v]) => typeof v === 'function')) |
| 85 | const __names = Object.keys(__fns) |
| 86 | if (__names.length === 0) { |
| 87 | console.log('Tidak ada fungsi yang bisa di-demo. Import modul ini dari kode lain.') |
| 88 | } else { |
| 89 | const __search = __names.find((n) => /search|cari/i.test(n)) |
| 90 | const __name = __search || __names[0] |
| 91 | const __fn = __fns[__name] |
| 92 | const __isClass = /^class\s/.test(Function.prototype.toString.call(__fn)) |
| 93 | let __args = process.argv.slice(2).map((a) => { try { return JSON.parse(a) } catch { return a } }) |
| 94 | if (__args.length === 0 && __search) __args.push('naruto') |
| 95 | if (__args.length === 0 && !__search && !__isClass && __fn.length > 0) { |
| 96 | console.log('Fungsi: ' + __names.join(', ')) |
| 97 | console.log('Contoh: node ' + "mangadex.js" + ' ' + __name + ' <arg>') |
| 98 | } else { |
| 99 | ;(async () => { |
| 100 | try { |
| 101 | const r = __isClass ? new __fn(...__args) : await __fn(...__args) |
| 102 | console.log(JSON.stringify(r, null, 2)) |
| 103 | } catch (e) { |
| 104 | console.error('Error: ' + (e && e.message ? e.message : e)) |
| 105 | process.exitCode = 1 |
| 106 | } |
| 107 | })() |
| 108 | } |
| 109 | } |
| 110 | } |
| 111 | |
Discussion & Comments0
No comments yet — be the first to leave feedback or start a code discussion.
Sign in with your GitHub account to write comments and join the discussion.