asurascans.com

by RFour · Jul 26, 2026 ·javascript

Basically search stuff and uhh chapter and uhh series, this is uhh for manhwa and manga I think mangatoon, if bored and uhhh idk, use this I think to read stuff, I'm lazy to add turn the chapter into pdf cuz uhh fuck it I'm lazy as fuck.

ManhwaMangaSearchChapterSeries
asura.jsjavascript
1const https = require('https');
2
3function asura(a, t, cb) {
4    const H = { 'User-Agent': 'Mozilla/5.0', 'Accept': 'application/json, text/html' };
5
6    const u = n => {
7        if (Array.isArray(n)) {
8            if (n.length < 2) return null;
9            const g = n[0], v = n[1];
10            if (g === 1) return Array.isArray(v) ? v.map(u) : v;
11            if (v && typeof v === 'object' && !Array.isArray(v)) {
12                const o = {};
13                for (const k in v) o[k] = u(v[k]);
14                return o;
15            }
16            if (Array.isArray(v)) return u([1, v]);
17            return v;
18        }
19        if (n && typeof n === 'object') {
20            const o = {};
21            for (const k in n) o[k] = u(n[k]);
22            return o;
23        }
24        return n;
25    };
26
27    const p = (h, f) => {
28        const re = /<astro-island\b[^>]*>/g;
29        let m;
30        while ((m = re.exec(h)) !== null) {
31            const c = m[0].match(/component-url="([^"]*)"/);
32            if (!c || !c[1].includes(f)) continue;
33            const pr = m[0].match(/props="([^"]*)"/);
34            if (!pr) continue;
35            let d = pr[1]
36                .replace(/&quot;/g, '"')
37                .replace(/&#39;/g, "'")
38                .replace(/&apos;/g, "'")
39                .replace(/&lt;/g, '<')
40                .replace(/&gt;/g, '>')
41                .replace(/&amp;/g, '&');
42            d = d.replace(/,\s*([}\]])/g, '$1').replace(/undefined/g, 'null');
43            try { return u(JSON.parse(d)); } catch (e) { return null; }
44        }
45        return null;
46    };
47
48    const f = (h, p2, c) => {
49        https.get({ host: h, path: p2, headers: H }, (r) => {
50            let b = '';
51            r.on('data', (d) => b += d);
52            r.on('end', () => c(null, r.statusCode, b));
53        }).on('error', (e) => c(e));
54    };
55
56    const ct = s => s ? s.replace(/<[^>]+>/g, '').trim() : s;
57
58    if (a === 'search') {
59        const q = new URLSearchParams({ search: t, sort: 'latest', order: 'desc', limit: '20', offset: '0' });
60        f('api.asurascans.com', `/api/series?${q}`, (e, s, b) => {
61            if (e || s !== 200) return cb('Search failed');
62            try {
63                const ls = JSON.parse(b);
64                const it = (Array.isArray(ls) ? ls : ls.data || []).map((x) => ({
65                    id: x.id,
66                    title: x.title,
67                    slug: x.slug,
68                    url: `https://asurascans.com/comics/${x.slug}`,
69                    cover: x.cover,
70                    rating: x.rating,
71                    status: x.status,
72                    chapter_count: x.chapter_count
73                }));
74                cb(null, it);
75            } catch (e) { cb('Search failed'); }
76        });
77        return;
78    }
79
80    let fu = t;
81    if (!t.startsWith('http')) {
82        fu = t.startsWith('/') ? `https://asurascans.com${t}` : `https://asurascans.com/${t}`;
83    }
84    const pa = fu.replace(/^https?:\/\/[^/]+/, '');
85
86    f('asurascans.com', pa, (e, s, html) => {
87        if (e || s !== 200) return cb('Fetch failed');
88
89        if (a === 'series') {
90            const d = p(html, 'DescriptionModal') || {};
91            const l = p(html, 'ChapterListReact') || {};
92            const ch = (l.chapters || []).map((c) => ({ id: c.id, number: c.number, title: c.title, slug: c.slug }));
93            const res = {
94                url: fu,
95                title: d.title,
96                description: ct(d.description),
97                cover: d.coverUrl,
98                rating: d.rating,
99                status: d.status,
100                type: d.type,
101                author: d.author,
102                artist: d.artist,
103                genres: (d.genres || []).map((g) => g.name),
104                total_chapters: d.chapterCount || ch.length,
105                chapters: ch
106            };
107            for (let k in res) if (res[k] === null || res[k] === undefined || (Array.isArray(res[k]) && !res[k].length)) delete res[k];
108            cb(null, res);
109        } else if (a === 'chapter') {
110            const r = p(html, 'ChapterReader');
111            if (!r) return cb('No chapter data');
112            const res = {
113                url: fu,
114                series_name: r.seriesName,
115                chapter_number: r.chapterNumber
116            };
117            if (r.chapterTitle) res.chapter_title = r.chapterTitle;
118            if (r.commentCount) res.comment_count = r.commentCount;
119            if (r.pages && r.pages.length) {
120                res.pages = r.pages;
121                res.images = r.pages.map((x) => x.url);
122            }
123            if (r.prevChapter && r.prevChapter.number) {
124                res.prev_chapter = { number: r.prevChapter.number };
125                if (r.prevChapter.slug) res.prev_chapter.slug = r.prevChapter.slug;
126            }
127            if (r.nextChapter && r.nextChapter.number) {
128                res.next_chapter = { number: r.nextChapter.number };
129                if (r.nextChapter.slug) res.next_chapter.slug = r.nextChapter.slug;
130            }
131            for (let k in res) if (res[k] === null || res[k] === undefined || (Array.isArray(res[k]) && !res[k].length)) delete res[k];
132            cb(null, res);
133        } else {
134            cb('Unknown action');
135        }
136    });
137}
138/*
139asura("search","query",(e,d)=>console.log(e||d))
140asura("series","/comics/...",(e,d)=>console.log(e||d))
141asura("chapter","/comics/.../chapter/N",(e,d)=>console.log(e||d))
142*\
raw