const https = require('https'); function asura(a, t, cb) { const H = { 'User-Agent': 'Mozilla/5.0', 'Accept': 'application/json, text/html' }; const u = n => { if (Array.isArray(n)) { if (n.length < 2) return null; const g = n[0], v = n[1]; if (g === 1) return Array.isArray(v) ? v.map(u) : v; if (v && typeof v === 'object' && !Array.isArray(v)) { const o = {}; for (const k in v) o[k] = u(v[k]); return o; } if (Array.isArray(v)) return u([1, v]); return v; } if (n && typeof n === 'object') { const o = {}; for (const k in n) o[k] = u(n[k]); return o; } return n; }; const p = (h, f) => { const re = /]*>/g; let m; while ((m = re.exec(h)) !== null) { const c = m[0].match(/component-url="([^"]*)"/); if (!c || !c[1].includes(f)) continue; const pr = m[0].match(/props="([^"]*)"/); if (!pr) continue; let d = pr[1] .replace(/"/g, '"') .replace(/'/g, "'") .replace(/'/g, "'") .replace(/</g, '<') .replace(/>/g, '>') .replace(/&/g, '&'); d = d.replace(/,\s*([}\]])/g, '$1').replace(/undefined/g, 'null'); try { return u(JSON.parse(d)); } catch (e) { return null; } } return null; }; const f = (h, p2, c) => { https.get({ host: h, path: p2, headers: H }, (r) => { let b = ''; r.on('data', (d) => b += d); r.on('end', () => c(null, r.statusCode, b)); }).on('error', (e) => c(e)); }; const ct = s => s ? s.replace(/<[^>]+>/g, '').trim() : s; if (a === 'search') { const q = new URLSearchParams({ search: t, sort: 'latest', order: 'desc', limit: '20', offset: '0' }); f('api.asurascans.com', `/api/series?${q}`, (e, s, b) => { if (e || s !== 200) return cb('Search failed'); try { const ls = JSON.parse(b); const it = (Array.isArray(ls) ? ls : ls.data || []).map((x) => ({ id: x.id, title: x.title, slug: x.slug, url: `https://asurascans.com/comics/${x.slug}`, cover: x.cover, rating: x.rating, status: x.status, chapter_count: x.chapter_count })); cb(null, it); } catch (e) { cb('Search failed'); } }); return; } let fu = t; if (!t.startsWith('http')) { fu = t.startsWith('/') ? `https://asurascans.com${t}` : `https://asurascans.com/${t}`; } const pa = fu.replace(/^https?:\/\/[^/]+/, ''); f('asurascans.com', pa, (e, s, html) => { if (e || s !== 200) return cb('Fetch failed'); if (a === 'series') { const d = p(html, 'DescriptionModal') || {}; const l = p(html, 'ChapterListReact') || {}; const ch = (l.chapters || []).map((c) => ({ id: c.id, number: c.number, title: c.title, slug: c.slug })); const res = { url: fu, title: d.title, description: ct(d.description), cover: d.coverUrl, rating: d.rating, status: d.status, type: d.type, author: d.author, artist: d.artist, genres: (d.genres || []).map((g) => g.name), total_chapters: d.chapterCount || ch.length, chapters: ch }; for (let k in res) if (res[k] === null || res[k] === undefined || (Array.isArray(res[k]) && !res[k].length)) delete res[k]; cb(null, res); } else if (a === 'chapter') { const r = p(html, 'ChapterReader'); if (!r) return cb('No chapter data'); const res = { url: fu, series_name: r.seriesName, chapter_number: r.chapterNumber }; if (r.chapterTitle) res.chapter_title = r.chapterTitle; if (r.commentCount) res.comment_count = r.commentCount; if (r.pages && r.pages.length) { res.pages = r.pages; res.images = r.pages.map((x) => x.url); } if (r.prevChapter && r.prevChapter.number) { res.prev_chapter = { number: r.prevChapter.number }; if (r.prevChapter.slug) res.prev_chapter.slug = r.prevChapter.slug; } if (r.nextChapter && r.nextChapter.number) { res.next_chapter = { number: r.nextChapter.number }; if (r.nextChapter.slug) res.next_chapter.slug = r.nextChapter.slug; } for (let k in res) if (res[k] === null || res[k] === undefined || (Array.isArray(res[k]) && !res[k].length)) delete res[k]; cb(null, res); } else { cb('Unknown action'); } }); } /* asura("search","query",(e,d)=>console.log(e||d)) asura("series","/comics/...",(e,d)=>console.log(e||d)) asura("chapter","/comics/.../chapter/N",(e,d)=>console.log(e||d)) *\