import { load as parseHTML } from 'cheerio'; import { fetchApi } from '@libs/fetch'; import { Plugin } from '@/types/plugin'; import { defaultCover } from '@libs/defaultCover'; import dayjs from 'dayjs'; import { storage } from '@libs/storage'; import { NovelStatus } from '@libs/novelStatus'; class Novelight implements Plugin.PagePlugin { id = 'novelight'; name = 'Novelight'; version = '1.1.1'; icon = 'src/en/novelight/icon.png'; site = 'https://novelight.net/'; hideLocked = storage.get('hideLocked'); pluginSettings = { hideLocked: { value: '', label: 'Hide locked chapters', type: 'Switch', }, }; async popularNovels(page: number): Promise { const url = `${this.site}catalog/?ordering=popularity&page=${page}`; const body = await fetchApi(url).then(r => r.text()); const loadedCheerio = parseHTML(body); const novels: Plugin.NovelItem[] = []; loadedCheerio('a.item').each((idx, ele) => { const novelName = loadedCheerio(ele).find('div.title').text().trim(); const novelUrl = ele.attribs.href; const bareNovelCover = loadedCheerio(ele).find('img').attr('src'); const novelCover = bareNovelCover ? this.site + bareNovelCover : defaultCover; if (!novelUrl) return; const novel = { name: novelName, cover: novelCover ?? defaultCover, path: novelUrl.replace('/', ''), }; novels.push(novel); }); return novels; } async parseNovel( novelPath: string, ): Promise { const body = await fetchApi(this.site + novelPath).then(r => r.text()); const loadedCheerio = parseHTML(body); const novel: Plugin.SourceNovel & { totalPages: number } = { path: novelPath, name: loadedCheerio('h1').text() || 'Untitled', cover: this.site + loadedCheerio('.poster > img').attr('src'), summary: loadedCheerio('section.text-info.section > p').text(), totalPages: loadedCheerio('#select-pagination-chapter > option').length, chapters: [], }; const info = loadedCheerio('div.mini-info > .item').toArray(); let status = ''; let translation = ''; for (const child of info) { const type = loadedCheerio(child).find('.sub-header').text().trim(); if (type === 'Status') { status = loadedCheerio(child).find('div.info').text().trim(); } if (type === 'Translation') { translation = loadedCheerio(child).find('div.info').text().trim(); } if (type === 'Author') { novel.author = loadedCheerio(child).find('div.info').text().trim(); } if (type === 'Genres') { novel.genres = loadedCheerio(child) .find('div.info > a') .map((i, el) => loadedCheerio(el).text()) .toArray() .join(', '); } } if (status === 'cancelled') novel.status = NovelStatus.Cancelled; else if (status === 'releasing' || translation === 'ongoing') novel.status = NovelStatus.Ongoing; else if (status === 'completed' && translation === 'completed') novel.status = NovelStatus.Completed; return novel; } async parsePage(novelPath: string, page: string): Promise { const rawBody = await fetchApi(this.site + novelPath).then(r => r.text()); const csrftoken = rawBody?.match(/window\.CSRF_TOKEN = "([^"]+)"/)?.[1]; const bookId = rawBody?.match(/const OBJECT_BY_COMMENT = ([0-9]+)/)?.[1]; const totalPages = parseInt( rawBody ?.match(/