Loading snippets...
/**
* Shinigami
* base: https://g.shinigami.asia/
* Creator: ShanMolvyr
* Jangan Hapus Kreator hargai rakyat kecil
* Note: cek https://snippet.vyr.my.id/shanmolvyr/shinigami/README.md
* Sumber: https://whatsapp.com/channel/0029VbB4Kw8EFeXfeExaXc3Q
*/
const BASE_API = 'https://api.shngm.io';
const ASSETS_BASE = 'https://assets.shngm.id';
const HEADERS = {
Accept: 'application/json',
'Content-Type': 'application/json',
Origin: 'https://g.shinigami.asia',
Referer: 'https://g.shinigami.asia/',
'User-Agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36',
};
async function api(path) {
const res = await fetch(`${BASE_API}${path}`, { headers: HEADERS });
if (!res.ok) throw new Error(`HTTP ${res.status}: ${BASE_API}${path}`);
return res.json();
}
function getMangaList(p = {}) {
const q = new URLSearchParams({
page: p.page ?? 1,
page_size: p.pageSize ?? 24,
sort: p.sort ?? 'latest',
sort_order: p.sortOrder ?? 'desc',
});
if (p.format != null) q.set('format', p.format);
if (p.isUpdate != null) q.set('is_update', p.isUpdate);
if (p.isRecommended != null) q.set('is_recommended', p.isRecommended);
if (p.type != null) q.set('type', p.type);
return api(`/v1/manga/list?${q}`);
}
function getMangaTop(p = {}) {
const q = new URLSearchParams({
filter: p.filter ?? 'daily',
page: p.page ?? 1,
page_size: p.pageSize ?? 10,
});
return api(`/v1/manga/top?${q}`);
}
const getMangaDetail = (mangaId) => api(`/v1/manga/detail/${mangaId}`);
const getChapterDetail = (chapterId) => api(`/v1/chapter/detail/${chapterId}`);
function getChapterList(mangaId, p = {}) {
const q = new URLSearchParams({
page: p.page ?? 1,
page_size: p.pageSize ?? 100,
sort_by: 'chapter_number',
sort_order: p.sortOrder ?? 'desc',
});
return api(`/v1/chapter/${mangaId}/list?${q}`);
}
function getImageUrls(chapterData) {
const base = chapterData.base_url + chapterData.chapter.path;
return chapterData.chapter.data.map(f => base + f);
}
function searchManga(p = {}) {
const q = new URLSearchParams({
page: p.page ?? 1,
page_size: p.pageSize ?? 24,
sort: 'latest',
sort_order: 'desc',
genre_include_mode: 'or',
genre_exclude_mode: 'or',
q: p.q ?? '',
});
if (p.format != null) q.set('format', p.format);
if (p.genreInclude != null) q.set('genre_include', p.genreInclude);
if (p.genreExclude != null) q.set('genre_exclude', p.genreExclude);
return api(`/v1/manga/list?${q}`);
}
const getGenreList = () => api('/v1/genre/list');
const getFormatList = () => api('/v1/format/list?page=1');
const getAuthorList = (q = '') => api(`/v1/author/list?q=${encodeURIComponent(q)}`);
function getMangaNotice(mangaId, p = {}) {
const q = new URLSearchParams({ page: p.page ?? 1, page_size: p.pageSize ?? 3 });
return api(`/v1/notice/${mangaId}/list?${q}`);
}
function getAnnouncements(p = {}) {
const q = new URLSearchParams({ page: p.page ?? 1, page_size: p.pageSize ?? 10 });
return api(`/v1/announcement/list?${q}`);
}
if (require.main === module) {
const [,, cmd, ...args] = process.argv;
async function run() {
switch (cmd) {
case 'home':
console.log(JSON.stringify(await getMangaList({ pageSize: 10 }), null, 2));
break;
case 'trending':
console.log(JSON.stringify(await getMangaTop({ filter: args[0] || 'daily' }), null, 2));
break;
case 'detail':
if (!args[0]) throw new Error('Usage: node index.js detail <manga_id>');
console.log(JSON.stringify(await getMangaDetail(args[0]), null, 2));
break;
case 'chapters':
if (!args[0]) throw new Error('Usage: node index.js chapters <manga_id> [asc|desc]');
(await getChapterList(args[0], { sortOrder: args[1] || 'desc' })).data
.forEach(ch => console.log(`Ch.${String(ch.chapter_number).padEnd(6)} | ${ch.chapter_id} | ${ch.release_date}`));
break;
case 'read': {
if (!args[0]) throw new Error('Usage: node index.js read <chapter_id>');
const r = await getChapterDetail(args[0]);
const ch = r.data;
console.log(`Chapter : ${ch.chapter_number}`);
console.log(`Manga : ${ch.manga_id}`);
console.log(`Prev : Ch.${ch.prev_chapter_number ?? '-'} (${ch.prev_chapter_id ?? '-'})`);
console.log(`Next : Ch.${ch.next_chapter_number ?? '-'} (${ch.next_chapter_id ?? '-'})`);
console.log(`Pages : ${ch.chapter.data.length}\n`);
getImageUrls(ch).forEach(url => console.log(url));
break;
}
case 'search':
if (!args[0]) throw new Error('Usage: node index.js search <query>');
console.log(JSON.stringify(await searchManga({ q: args.join(' ') }), null, 2));
break;
case 'genres':
console.log(JSON.stringify(await getGenreList(), null, 2));
break;
case 'formats':
console.log(JSON.stringify(await getFormatList(), null, 2));
break;
case 'announcements':
console.log(JSON.stringify(await getAnnouncements(), null, 2));
break;
default:
console.log(`Usage: node index.js <command> [args]
home
trending [daily|weekly|monthly]
detail <manga_id>
chapters <manga_id> [asc|desc]
read <chapter_id>
search <query>
genres
formats
announcements`);
}
}
run().catch(err => { console.error(err.message); process.exit(1); });
}
module.exports = {
getMangaList, getMangaTop, getMangaDetail,
getChapterList, getChapterDetail, getImageUrls,
searchManga, getGenreList, getFormatList, getAuthorList,
getMangaNotice, getAnnouncements,
BASE_API, ASSETS_BASE,
};
bashnode shinigami.js # lihat semua command node shinigami.js home node shinigami.js trending daily node shinigami.js detail <manga_id> node shinigami.js chapters <manga_id> asc node shinigami.js read <chapter_id> node shinigami.js search eleceed node shinigami.js genres node shinigami.js formats node shinigami.js announcements
bashby ShanMolvyr