From d53258657eeb81d26d14ec5bf3c4bd3f6a278f3b Mon Sep 17 00:00:00 2001 From: fuyu-hub Date: Mon, 30 Mar 2026 17:01:55 -0300 Subject: [PATCH] feat: Add Illusia (#2091) --- plugins/portuguese/illusia.ts | 170 +++++++++++++++++++++++ public/static/src/pt-br/illusia/icon.png | Bin 0 -> 2569 bytes 2 files changed, 170 insertions(+) create mode 100644 plugins/portuguese/illusia.ts create mode 100644 public/static/src/pt-br/illusia/icon.png diff --git a/plugins/portuguese/illusia.ts b/plugins/portuguese/illusia.ts new file mode 100644 index 0000000..1dd5985 --- /dev/null +++ b/plugins/portuguese/illusia.ts @@ -0,0 +1,170 @@ +import { fetchApi } from '@libs/fetch'; +import { Plugin } from '@/types/plugin'; +import { Filters } from '@libs/filterInputs'; +import { load as loadCheerio } from 'cheerio'; +import { NovelStatus } from '@libs/novelStatus'; + +class Illusia implements Plugin.PluginBase { + id = 'illusia'; + name = 'Illusia'; + icon = 'src/pt-br/illusia/icon.png'; + site = 'https://illusia.com.br'; + version = '1.0.0'; + filters: Filters | undefined = undefined; + + async popularNovels( + pageNo: number, + { + showLatestNovels, + filters, + }: Plugin.PopularNovelsOptions, + ): Promise { + // Usando o sistema de busca nativo para evitar o Erro 500 das rotas customizadas + const orderby = showLatestNovels ? 'modified' : 'views'; + + const url = `${this.site}/${pageNo === 1 ? '' : 'page/' + pageNo + '/'}?s=&post_type=fcn_story&orderby=${orderby}&order=desc`; + + const req = await fetchApi(url); + const body = await req.text(); + const loadedCheerio = loadCheerio(body); + + return loadedCheerio('#search-result-list > li > div > div') + .map((i, el) => { + const item = loadedCheerio(el); + + const novelName = item.find('h3 > a').text().trim(); + const novelUrl = item.find('h3 > a').attr('href'); + const novelCover = + item.find('a.cell-img img').attr('src') || + item.find('a.cell-img').attr('href'); + + if (!novelName || !novelUrl) return null; + + return { + name: novelName, + cover: novelCover, + path: novelUrl.replace(this.site + '/', '').replace(/\/$/, ''), + }; + }) + .toArray() + .filter(novel => novel !== null) as Plugin.NovelItem[]; + } + + async parseNovel(novelPath: string): Promise { + const req = await fetchApi(`${this.site}/${novelPath}/`); + const body = await req.text(); + const loadedCheerio = loadCheerio(body); + + const novel: Plugin.SourceNovel = { + path: novelPath, + name: loadedCheerio('h1.story__identity-title').text().trim(), + }; + + novel.author = loadedCheerio('div.story__identity-meta') + .text() + .split('|')[0] + .replace('Author: ', '') + .replace('by ', '') + .trim(); + + novel.cover = + loadedCheerio('figure.story__thumbnail img').attr('src') || + loadedCheerio('figure.story__thumbnail > a').attr('href'); + + novel.genres = loadedCheerio('div.tag-group > a, section.tag-group > a') + .map((i, el) => loadedCheerio(el).text().trim()) + .toArray() + .join(','); + + novel.summary = loadedCheerio('section.story__summary').text().trim(); + + let chapterElements = loadedCheerio('li.chapter-group__list-item'); + + if (chapterElements.length === 0) { + chapterElements = loadedCheerio( + 'ul.chapter-list li, .chapters li, .chapter-item', + ); + } + + novel.chapters = chapterElements + .filter((i, el) => { + const className = el.attribs['class'] || ''; + return !className.includes('_password'); + }) + .filter( + (i, el) => + !loadedCheerio(el) + .find('i') + .first() + ?.attr('class') + ?.includes('fa-lock'), + ) + .map((i, el) => { + const aTag = loadedCheerio(el).find('a').first(); + const chapterName = aTag.text().trim(); + const chapterUrl = aTag + .attr('href') + ?.replace(this.site + '/', '') + .replace(/\/$/, ''); + + return { + name: chapterName, + path: chapterUrl || '', + }; + }) + .toArray() + .filter(chapter => chapter.path !== ''); + + novel.chapters.reverse(); + + const status = loadedCheerio('span.story__status').text().trim(); + if (status === 'Ongoing') novel.status = NovelStatus.Ongoing; + if (status === 'Completed') novel.status = NovelStatus.Completed; + if (status === 'Cancelled') novel.status = NovelStatus.Cancelled; + if (status === 'Hiatus') novel.status = NovelStatus.OnHiatus; + + return novel; + } + + async parseChapter(chapterPath: string): Promise { + // Você mencionou usar node-fetch para o capítulo. + // Se o fetchApi estiver travando aqui, você pode trocá-lo pelo fetch do NodeJS (neste caso eu mantive a base padrão para testarmos). + const req = await fetchApi(`${this.site}/${chapterPath}/`); + const body = await req.text(); + const loadedCheerio = loadCheerio(body); + + return loadedCheerio('section#chapter-content > div').html() || ''; + } + + async searchNovels( + searchTerm: string, + pageNo: number, + ): Promise { + const req = await fetchApi( + `${this.site}/${pageNo === 1 ? '' : 'page/' + pageNo + '/'}?s=${encodeURIComponent(searchTerm)}&post_type=fcn_story`, + ); + const body = await req.text(); + const loadedCheerio = loadCheerio(body); + + return loadedCheerio('#search-result-list > li > div > div') + .map((i, el) => { + const novelName = loadedCheerio(el).find('h3 > a').text().trim(); + const novelCover = + loadedCheerio(el).find('a.cell-img img').attr('src') || + loadedCheerio(el).find('a.cell-img').attr('href'); + const novelUrl = loadedCheerio(el).find('h3 > a').attr('href'); + + return { + name: novelName, + cover: novelCover, + path: + novelUrl?.replace(this.site + '/', '')?.replace(/\/$/, '') || '', + }; + }) + .toArray(); + } + + resolveUrl = (path: string, isNovel?: boolean) => `${this.site}/${path}/`; +} + +export default new Illusia(); diff --git a/public/static/src/pt-br/illusia/icon.png b/public/static/src/pt-br/illusia/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f4a9b08e82b443487223d242e039847b4a70bd54 GIT binary patch literal 2569 zcmV+k3ikDhP)ll}kwVL_7p{Qms<{15{=T|$jXGI|#aIATJXVMUK)NS7N6 zMGFKcC>B%u`}QLjNgfqCX<>W#_4wrC=*Gj&Gap`+keqmPi&Q&+J}7CWps>--)jl*| z+u7kOB1KwLaj>kpQAugLx5S&5r|#?VYi5CheULOPPhwknNIzqXg`9J2fOj+^-v9s! zD@jB_RA}DqnR$Dvuo8z+qOxdI&;X)TRsqYR-~Yqi-vqR_N4uP3|G3Y+d0Gr)m^Ycp zHbI#W)y3c!lQL8WznYZ(|MvikfudXp$_?si`AZ8_RT&QFirOX0t53+|uHzu1D8qSQ z@Vxv5ppM5>d!=i@O-uege+A6vLxmDGaX#-+PssQ7AA>gR2(pce^0fTb!QkN{A(!8$ zpFx;;Kx?AN=dK%!i;1>XM>EZucfE?W4G0|T=?*Lu;Ex;Hv(XRrO-v(f+ z`}_qB>0TL4Y1)Uvdw{`%8eQIf2pFgXO`A^Nm3|MP$=WTK>T7;SqG)>mlf}7TAyPb6nkyR%V z({$?gB4TliAk%A8?*L%M<>F;VuZlPb{f);c8OP{Q*3Xx-sG&lq*ND9nTk?fG# z>ACPa`x^SFQ^7nxKtV;#*wwbv6r1k(1c^x*FRE%=ypr|K=4|;9Fka5M%Y4@{Wko0J zHT-MnItU|N$)m?Z=GaZ(m@P>#or+O9Aqm4+Pf#P@<#v8xI{?NXoGbxe%H4>nZb+>% zk&4NLRFHE5hi6pBzKoRj36-Gkw5c3>^8jGIdSY)(AlE6JOsEYbqID{Wv4N4+WIA1H zB=!YBPu6SpY9@~WuD9AB*K1t@$h_1T)M`Y{pxR1=zLv((b>yjaJz%0w))NpWUjRI& zOPjDm!B~M_;T;b2tgouH%S7+=7Y8=FnDNqzHFG-F*P$|^kxg{ zHl2Rou|sBj02mj*eIEdTrV^kuh0k14(ONuWUDSe_W)}FKxc2} z`#L*tUb4V)d^sxQkkA3|rzsW{q`JG+zXNdBB9=aYi$fYyp+x}h_;n<6CBR^<4IhM-B>~aVvRjWG9lH9dP3e`LZGK?K zjN3Nnb;7!Fc?H}bXsdWFooTSjF?Uyh&sqVVmkB3gEl-0oNJAD1$7RRVsW6lC?5BXj z;kSUmT49I>Ttr@N#jyL)n`Q1&as}IU=pX%=udY zzW`u;y$8TNm9T2^LBmem2^p@!|0^GG9$o;>fq5Rj`^d!j6hb~)Gm>V#1+3{2znYVM@&?`{z z6CaR~p(-yhAZm*?7!1w(qIrgT*9^^|O zjO1qd=$CH06^H>$OVqJjMyx!`%@$08Iiq6O`erc%&}B_7;^ri`zlQgviD-MZ%TwD- zv8_Ix%JLL6)xkeTuH28oG$SmNZjl6Wn!i?M7>4EPj2Ez<4QW651;E&pt!dYR)a*E7MZke3(Gu}SY4n^;GMl@GtMU^9l z;0OaDVp`k$gg1|6?C?)a)KdK6pJUD`a71vqpY27-HbI+4*X_Fe-Q>l_@LRE!VeEd3 zunn%p48n>wc?cL`=TNqeNs-nJ(VVKOi@g=#VU{*kS@_B3=y}KECh>Q+T^Fq2TM9an zWZ(;i>%(8CM*wWT5lxD1{4yO*bY>w0q7VFQGi6{&GwPc)$YSZt@`=~P3+ zg!or7NcxeJvs22240f05P{E7pV5m+(uh1^RJI+n_1Li|e2fdB3Q#?w8V z3d3uN$;VwBs>q|B?qtD%uM^)d3QTv!28*x^><5}W&lzh2O&+}T2t=9hj`!sm2006Q zWzawF4#5;^!B`#-Ac8L5>=UJkRPns4`0(0>MThA1ybr|bd`VH&YxZ*bBCDY-Ubeys zF-xs!fFB$i!5YfBO)p^oNU21&|N=-|mChJAm>Gfco}z>?1&)$UO<5 z^q=39x3gBI`+oVzOg{s>KRNDio(P~%G0N4?%BA*i=eoZESSX+Oq#ppt{_8N^vuG+f6WXTc00000NkvXXu0mjfd1Lb6 literal 0 HcmV?d00001