diff --git a/src/plugins/ukrainian/bakainua.ts b/src/plugins/ukrainian/bakainua.ts index 95697a8..b2f2bed 100644 --- a/src/plugins/ukrainian/bakainua.ts +++ b/src/plugins/ukrainian/bakainua.ts @@ -1,30 +1,69 @@ import { CheerioAPI, load as parseHTML } from 'cheerio'; import { fetchApi } from '@libs/fetch'; import { Plugin } from '@typings/plugin'; +import { NovelStatus } from '@libs/novelStatus'; +import { Filters, FilterTypes } from '@libs/filterInputs'; class BakaInUa implements Plugin.PluginBase { id = 'bakainua'; name = 'BakaInUA'; icon = 'src/uk/bakainua/icon.png'; site = 'https://baka.in.ua'; - version = '1.0.1'; + version = '2.0.0'; - async popularNovels(): Promise { - const novels: Plugin.NovelItem[] = []; + async popularNovels( + pageNo: number, + { + filters, + showLatestNovels, + }: Plugin.PopularNovelsOptions, + ): Promise { + const fictionIds: string[] = []; - const result = await fetchApi(this.site + '/fictions/alphabetical'); + let url: URL = new URL(this.site + '/fictions/alphabetical'); + + if (pageNo > 1) url.searchParams.append('page', pageNo.toString()); + if (showLatestNovels || (filters && filters.only_new.value)) + url.searchParams.append('only_new', '1'); + if (filters) { + if (filters.longreads.value) url.searchParams.append('longreads', '1'); + if (filters.finished.value) url.searchParams.append('finished', '1'); + if (filters.genre.value !== '') + url.searchParams.append('genre', filters.genre.value); + } + + const result = await fetchApi(url.toString()); const body = await result.text(); const $ = parseHTML(body); - $('#fiction-list > ol > li > div > div').each((index, elem) => { - novels.push({ - path: $(elem).find('a').attr('href'), - name: $(elem).find('a>h2').text(), - cover: this.site + $(elem).find('a>div>div>div>img').attr('src'), - }); + // get the ids of the popular novels + $('div#fiction-list-page > div > div > div > img').each((index, elem) => { + const fictionId = $(elem).attr('data-fiction-picker-id-param'); + if (fictionId) { + fictionIds.push(fictionId); + } }); - return novels; + + // fetch the details of the popular novels asynchronously + const requests: Promise[] = fictionIds.map(async id => { + const res = await fetchApi(`${this.site}/fictions/${id}/details`, { + headers: { + accept: 'text/vnd.turbo-stream.html', + }, + }); + const $ = parseHTML(await res.text()); + const elem = $('a').first(); + + return { + name: $('h3').text().trim(), + path: elem.attr('href') || '', + cover: this.site + $(elem).find('img').attr('src'), + }; + }); + + return await Promise.all(requests); } + async parseNovel(novelUrl: string): Promise { const result = await fetchApi(this.site + '/' + novelUrl); const body = await result.text(); @@ -32,26 +71,45 @@ class BakaInUa implements Plugin.PluginBase { const novel: Plugin.SourceNovel = { path: novelUrl, - name: $('h2.text-xl.font-bold.text-stone-800.mb-2').text(), - author: $('button#fictions-author-search').text(), - cover: - this.site + - $('div.from-stone-50.to-stone-100>div.relative>img').attr('src'), - summary: $('p.text-sm.leading-relaxed').text(), - status: $('div.bg-stone-100:nth-child(3)>p:nth-child(2)').text(), + name: $('main div > h1').text().trim(), + author: $('button#fictions-author-search').text().trim(), + cover: this.site + $('main div > img').attr('src'), + summary: $('main div > h3').first().parent().find('div').text().trim(), + genres: $('h4:contains("Жанри")') + .last() + .siblings() + .last() + .find('span') + .map((_, el) => $(el).text().trim()) + .get() + .join(', '), }; + switch ($('div:contains("Статус")').last().siblings().text().trim()) { + case 'Видаєт.': + novel.status = NovelStatus.Ongoing; + break; + case 'Заверш.': + novel.status = NovelStatus.Completed; + break; + case 'Покину.': + novel.status = NovelStatus.OnHiatus; + break; + default: + novel.status = NovelStatus.Unknown; + break; + } + let chapters: Plugin.ChapterItem[] = []; - $('li.group') - .find('a') - .each((index, elem) => { - const chapter: Plugin.ChapterItem = { - name: $(elem).find('div>div>span:nth-child(2)').text().trim(), - path: $(elem).attr('href'), - chapterNumber: index + 1, - }; - chapters.push(chapter); - }); + $('li.group a').each((index, elem) => { + const chapter: Plugin.ChapterItem = { + name: $(elem).find('span').eq(1).text().trim(), + path: $(elem).attr('href') || '', + chapterNumber: parseInt($(elem).find('span').eq(0).text().trim()), + releaseTime: $(elem).find('span').eq(2).text().trim(), + }; + chapters.push(chapter); + }); novel.chapters = chapters.reverse(); return novel; @@ -61,26 +119,80 @@ class BakaInUa implements Plugin.PluginBase { const result = await fetchApi(this.site + '/' + chapterUrl); const body = await result.text(); const $ = parseHTML(body); - return $('#user-content').html(); + return $('#user-content').html() || ''; } async searchNovels(searchTerm: string): Promise { let novels: Plugin.NovelItem[] = []; const result = await fetchApi( - this.site + '/search?search%5B%5D=' + searchTerm, + this.site + '/search?search%5B%5D=' + searchTerm + '&only_fictions=true', ); const body = await result.text(); const $ = parseHTML(body); - $('ul>section>div>div').each((index, elem) => { + $('ul > section').each((index, elem) => { novels.push({ - path: $(elem).find('div:nth-child(2)>a').attr('href'), - name: $(elem).find('div:nth-child(2)>a').text().trim(), - cover: this.site + $(elem).find('div:nth-child(1)>img').attr('src'), + path: $(elem).find('a').first().attr('href') || '', + name: $(elem).find('a > h2').first().text().trim(), + cover: this.site + $(elem).find('img').first().attr('src'), }); }); return novels; } + + filters = { + genre: { + type: FilterTypes.Picker, + label: 'Жанр', + value: '', + options: [ + { label: 'Всі жанри', value: '' }, + { label: 'BL', value: '19' }, + { label: 'GL', value: '20' }, + { label: 'Авторське', value: '32' }, + { label: 'Бойовик', value: '2' }, + { label: 'Вуся', value: '16' }, + { label: 'Гарем', value: '5' }, + { label: 'Детектив', value: '22' }, + { label: 'Драма', value: '12' }, + { label: 'Жахи', value: '10' }, + { label: 'Ісекай', value: '13' }, + { label: 'Історичне', value: '15' }, + { label: 'Комедія', value: '11' }, + { label: 'ЛГБТ', value: '3' }, + { label: 'Містика', value: '18' }, + { label: 'Омегаверс', value: '30' }, + { label: 'Повсякденність', value: '17' }, + { label: 'Пригоди', value: '7' }, + { label: 'Психологія', value: '28' }, + { label: 'Романтика', value: '1' }, + { label: 'Спорт', value: '9' }, + { label: 'Сюаньхвань', value: '27' }, + { label: 'Сянься', value: '26' }, + { label: 'Трагедія', value: '24' }, + { label: 'Трилер', value: '21' }, + { label: 'Фантастика', value: '8' }, + { label: 'Фанфік', value: '23' }, + { label: 'Фентезі', value: '4' }, + { label: 'Школа', value: '6' }, + ], + }, + only_new: { + type: FilterTypes.Switch, + label: 'Новинки', + value: false, + }, + longreads: { + type: FilterTypes.Switch, + label: 'Довгочити', + value: false, + }, + finished: { + type: FilterTypes.Switch, + label: 'Завершене', + value: false, + }, + } satisfies Filters; } export default new BakaInUa();