import { CheerioAPI, load as parseHTML } from 'cheerio';
import { fetchApi } from '@libs/fetch';
import { FilterTypes, Filters } from '@libs/filterInputs';
import { Plugin } from '@/types/plugin';
class EarlyNovelPlugin implements Plugin.PagePlugin {
id = 'earlynovel';
name = 'Early Novel';
version = '1.0.1';
icon = 'src/en/earlynovel/icon.png';
site = 'https://earlynovel.net/';
parseNovels(loadedCheerio: CheerioAPI) {
const novels: Plugin.NovelItem[] = [];
loadedCheerio('.col-truyen-main > .list-truyen > .row').each((i, el) => {
const novelUrl = loadedCheerio(el)
.find('h3.truyen-title > a')
.attr('href');
const novelName = loadedCheerio(el).find('h3.truyen-title > a').text();
const novelCover = loadedCheerio(el).find('.lazyimg').attr('data-image');
if (!novelUrl) return;
novels.push({
path: novelUrl,
name: novelName,
cover: novelCover,
});
});
return novels;
}
parseChapters(loadedCheerio: CheerioAPI) {
const chapter: Plugin.ChapterItem[] = [];
loadedCheerio('ul.list-chapter > li').each((i, el) => {
const chapterName = loadedCheerio(el).find('.chapter-text').text().trim();
const chapterUrl = loadedCheerio(el).find('a').attr('href')?.slice(1);
if (!chapterUrl) return;
chapter.push({
name: chapterName,
path: chapterUrl,
});
});
return chapter;
}
async popularNovels(
pageNo: number,
{ filters }: Plugin.PopularNovelsOptions,
): Promise {
let link = this.site;
if (filters.genres.value.length) link += filters.genres.value;
else link += filters.order.value;
link += `?page=${pageNo}`;
const body = await fetchApi(link).then(res => res.text());
const loadedCheerio = parseHTML(body);
return this.parseNovels(loadedCheerio);
}
async parseNovel(
novelPath: string,
): Promise {
const result = await fetchApi(this.site + novelPath);
const body = await result.text();
const loadedCheerio = parseHTML(body);
loadedCheerio('.glyphicon-menu-right').closest('li').remove();
const pagenav = loadedCheerio('.page-nav').prev().find('a');
const lastPageStr = pagenav.attr('title')?.match(/(\d+)/);
const lastPage = Number(lastPageStr?.[1] || '0');
const novel: Plugin.SourceNovel & { totalPages: number } = {
path: novelPath,
name: loadedCheerio('.book > img').attr('alt') || 'Untitled',
cover: loadedCheerio('.book > img').attr('src'),
summary: loadedCheerio('.desc-text').text().trim(),
chapters: [],
totalPages: lastPage,
};
loadedCheerio('.info > div > h3').each(function () {
const detailName = loadedCheerio(this).text();
const detail = loadedCheerio(this)
.siblings()
.map((i, el) => loadedCheerio(el).text())
.toArray()
.join(',');
switch (detailName) {
case 'Author:':
novel.author = detail;
break;
case 'Status:':
novel.status = detail;
break;
case 'Genre:':
novel.genres = detail;
break;
}
});
novel.chapters = this.parseChapters(loadedCheerio);
return novel;
}
async parsePage(novelPath: string, page: string): Promise {
const url = this.site + novelPath + '?page=' + page;
const body = await fetchApi(url).then(res => res.text());
const loadedCheerio = parseHTML(body);
const chapters = this.parseChapters(loadedCheerio);
return { chapters };
}
async parseChapter(chapterPath: string): Promise {
const result = await fetchApi(this.site + chapterPath);
const body = await result.text();
const loadedCheerio = parseHTML(body);
const chapterText = loadedCheerio('#chapter-c').html() || '';
return chapterText;
}
async searchNovels(searchTerm: string): Promise {
const url = `${this.site}search?keyword=${encodeURIComponent(searchTerm)}`;
const result = await fetchApi(url);
const body = await result.text();
const loadedCheerio = parseHTML(body);
return this.parseNovels(loadedCheerio);
}
filters = {
order: {
value: '/most-popular',
label: 'Order by',
options: [
{ label: 'Latest Release', value: '/latest-release-novel' },
{ label: 'Hot Novel', value: '/hot-novel' },
{ label: 'Completed Novel', value: '/completed-novel' },
{ label: 'Most Popular', value: '/most-popular' },
],
type: FilterTypes.Picker,
},
genres: {
value: '',
label: 'Genre',
options: [
{ label: 'None', value: '' },
{ label: 'Action', value: '/genre/action-1' },
{ label: 'Adult', value: '/genre/adult-2' },
{ label: 'Adventure', value: '/genre/adventure-3' },
{ label: 'Comedy', value: '/genre/comedy-4' },
{ label: 'Drama', value: '/genre/drama-5' },
{ label: 'Ecchi', value: '/genre/ecchi-6' },
{ label: 'Fantasy', value: '/genre/fantasy-7' },
{ label: 'Gender Bender', value: '/genre/gender-bender-8' },
{ label: 'Harem', value: '/genre/harem-9' },
{ label: 'Historical', value: '/genre/historical-10' },
{ label: 'Horror', value: '/genre/horror-11' },
{ label: 'Josei', value: '/genre/josei-12' },
{ label: 'Martial Arts', value: '/genre/martial-arts-13' },
{ label: 'Mature', value: '/genre/mature-14' },
{ label: 'Mecha', value: '/genre/mecha-15' },
{ label: 'Mystery', value: '/genre/mystery-16' },
{ label: 'Psychological', value: '/genre/psychological-17' },
{ label: 'Romance', value: '/genre/romance-18' },
{ label: 'School Life', value: '/genre/school-life-19' },
{ label: 'Sci-fi', value: '/genre/sci-fi-20' },
{ label: 'Seinen', value: '/genre/seinen-21' },
{ label: 'Shoujo', value: '/genre/shoujo-22' },
{ label: 'Shoujo Ai', value: '/genre/shoujo-ai-23' },
{ label: 'Shounen', value: '/genre/shounen-24' },
{ label: 'Shounen Ai', value: '/genre/shounen-ai-25' },
{ label: 'Slice of Life', value: '/genre/slice-of-life-26' },
{ label: 'Smut', value: '/genre/smut-27' },
{ label: 'Sports', value: '/genre/sports-28' },
{ label: 'Supernatural', value: '/genre/supernatural-29' },
{ label: 'Tragedy', value: '/genre/tragedy-30' },
{ label: 'Wuxia', value: '/genre/wuxia-31' },
{ label: 'Xianxia', value: '/genre/xianxia-32' },
{ label: 'Xuanhuan', value: '/genre/xuanhuan-33' },
{ label: 'Yaoi', value: '/genre/yaoi-34' },
{ label: 'Yuri', value: '/genre/yuri-35' },
{ label: 'Video Games', value: '/genre/video-games-36' },
{ label: 'Magical Realism', value: '/genre/magical-realism-37' },
],
type: FilterTypes.Picker,
},
} satisfies Filters;
}
export default new EarlyNovelPlugin();