From 3088f6882dbc79c735ae7b46178214754e68ed34 Mon Sep 17 00:00:00 2001 From: Rider21 <58046032+Rider21@users.noreply.github.com> Date: Wed, 21 May 2025 16:35:54 +0300 Subject: [PATCH] fix authorToday (#1690) * updates * fix * test * fix * Update src/plugins/russian/authortoday.ts Co-authored-by: K1ngfish3r --------- Co-authored-by: K1ngfish3r --- scripts/multisrc/rulate/sources.json | 2 +- scripts/multisrc/rulate/template.ts | 2 +- src/plugins/english/novelupdates.ts | 4 +- src/plugins/russian/authortoday.ts | 623 ++++++++++----------------- src/plugins/russian/neobook.ts | 5 +- src/plugins/russian/zelluloza.ts | 35 +- 6 files changed, 265 insertions(+), 406 deletions(-) diff --git a/scripts/multisrc/rulate/sources.json b/scripts/multisrc/rulate/sources.json index 15d8922..bd386b3 100644 --- a/scripts/multisrc/rulate/sources.json +++ b/scripts/multisrc/rulate/sources.json @@ -7549,7 +7549,7 @@ "type": "CheckboxGroup" } }, - "versionIncrements": 0 + "versionIncrements": 1 }, { "id": "erolate", diff --git a/scripts/multisrc/rulate/template.ts b/scripts/multisrc/rulate/template.ts index 155fbf0..e5487d0 100644 --- a/scripts/multisrc/rulate/template.ts +++ b/scripts/multisrc/rulate/template.ts @@ -102,7 +102,7 @@ class RulatePlugin implements Plugin.PluginBase { 'src', ); novel.summary = loadedCheerio( - '#Info > div:nth-child(3), .book__description', + '#Info > div:nth-child(4) > p:nth-child(1), .book__description', ) .text() .trim(); diff --git a/src/plugins/english/novelupdates.ts b/src/plugins/english/novelupdates.ts index e6ecd07..e44e743 100644 --- a/src/plugins/english/novelupdates.ts +++ b/src/plugins/english/novelupdates.ts @@ -6,7 +6,7 @@ import { Plugin } from '@typings/plugin'; class NovelUpdates implements Plugin.PluginBase { id = 'novelupdates'; name = 'Novel Updates'; - version = '0.9.0'; + version = '0.9.1'; icon = 'src/en/novelupdates/icon.png'; customCSS = 'src/en/novelupdates/customCSS.css'; site = 'https://www.novelupdates.com/'; @@ -799,7 +799,7 @@ class NovelUpdates implements Plugin.PluginBase { '.sidebar', '.swg-button-v2-light', '.wp-block-buttons', - '.wp-block-columns', + //'.wp-block-columns', '.wp-dark-mode-switcher', '.wp-next-post-navi', '#hpk', diff --git a/src/plugins/russian/authortoday.ts b/src/plugins/russian/authortoday.ts index 3831a3e..bddd090 100644 --- a/src/plugins/russian/authortoday.ts +++ b/src/plugins/russian/authortoday.ts @@ -3,8 +3,7 @@ import { FilterTypes, Filters } from '@libs/filterInputs'; import { defaultCover } from '@libs/defaultCover'; import { fetchApi } from '@libs/fetch'; import { NovelStatus } from '@libs/novelStatus'; -import { load as parseHTML } from 'cheerio'; -import { storage } from '@libs/storage'; +import { Parser } from 'htmlparser2'; import dayjs from 'dayjs'; class AuthorToday implements Plugin.PluginBase { @@ -12,129 +11,249 @@ class AuthorToday implements Plugin.PluginBase { name = 'Автор Тудей'; icon = 'src/ru/authortoday/icon.png'; site = 'https://author.today'; - apiSite = 'https://api.author.today/v1/'; - imageSite = 'https://cm.author.today/content/'; - version = '1.1.1'; + version = '1.2.0'; + + private userAgent = + 'Mozilla/5.0 (Android 15; Mobile; rv:138.0) Gecko/138.0 Firefox/138.0'; + + parseNovels(url: string) { + return fetchApi(url, { headers: { 'User-Agent': this.userAgent } }) + .then(res => res.text()) + .then(html => { + const novels: Plugin.NovelItem[] = []; + let tempNovel = {} as Plugin.NovelItem; + let isParsingNovel = false; + let isParsingName = false; + const parser = new Parser({ + onopentag(name, attribs) { + if ( + name === 'a' && + attribs['class'] === 'work-row item-link item-content' && + attribs['href'] + ) { + tempNovel.path = attribs['href'].replace(/\D/g, ''); + isParsingNovel = true; + } + if ( + isParsingNovel && + name === 'h4' && + attribs['class'] === 'work-title' + ) { + isParsingName = true; + } + if ( + isParsingNovel && + name === 'img' && + attribs['alt'] === 'Обложка' + ) { + tempNovel.cover = attribs['data-src']; + } + }, + ontext(data) { + if (isParsingName) { + tempNovel.name = data.trim(); + } + }, + onclosetag(name) { + if (name === 'h4') { + isParsingName = false; + } + if (name === 'a' && isParsingNovel) { + if (!tempNovel.cover) { + tempNovel.cover = defaultCover; + } + novels.push(tempNovel); + tempNovel = {} as Plugin.NovelItem; + isParsingNovel = false; + } + }, + }); + parser.write(html); + parser.end(); + return novels; + }); + } async popularNovels( pageNo: number, { showLatestNovels, filters }: Plugin.PopularNovelsOptions, ): Promise { - let url = this.apiSite + 'catalog/search?page=' + pageNo; - if (filters?.genre?.value) { - url += '&genre=' + filters.genre.value; - } + let url = this.site + '/work/genre/' + (filters?.genre?.value || 'all'); url += - '&sorting=' + + '?sorting=' + (showLatestNovels ? 'recent' : filters?.sort?.value || 'popular'); - url += '&form=' + (filters?.form?.value || 'any'); - url += '&state=' + (filters?.state?.value || 'any'); - url += '&series=' + (filters?.series?.value || 'any'); - url += '&access=' + (filters?.access?.value || 'any'); - url += '&promo=' + (filters?.promo?.value || 'hide'); + if (filters?.form?.value) url += '&form=' + filters.form.value; + if (filters?.state?.value) url += '&state=' + filters.state.value; + if (filters?.series?.value) url += '&series=' + filters.series.value; + if (filters?.access?.value) url += '&access=' + filters.access.value; + if (filters?.promo?.value) url += '&promo=' + filters.promo.value; + url += '&page=' + pageNo; - const result: response = await fetchApi(url, { - headers: { - Authorization: 'Bearer guest', - }, - }).then(res => res.json()); - const novels: Plugin.NovelItem[] = []; - - if (result.code === 'NotFound') { - return novels; - } - - result?.searchResults?.forEach(novel => - novels.push({ - name: novel.title, - cover: novel.coverUrl ? this.imageSite + novel.coverUrl : defaultCover, - path: novel.id.toString(), - }), - ); - - return novels; + return this.parseNovels(url); } async parseNovel(workID: string): Promise { - if (!this.user) this.user = await this.getUser(); - const book: responseBook = await fetchApi( - this.apiSite + 'work/' + workID + '/details', - { - headers: { - Authorization: 'Bearer ' + this.user?.token || 'guest', - }, - }, - ).then(res => res.json()); + const html = await fetchApi(this.resolveUrl(workID, true), { + headers: { 'User-Agent': this.userAgent }, + }).then(res => res.text()); const novel: Plugin.SourceNovel = { path: workID, - name: book.title, - cover: book.coverUrl ? book.coverUrl.split('?')[0] : defaultCover, - genres: book.tags?.join(', '), + name: '', + author: '', summary: '', - author: - book.originalAuthor || - book.authorFIO || - book.coAuthorFIO || - book.secondCoAuthorFIO || - book.translator || - '', - status: book.isFinished ? NovelStatus.Completed : NovelStatus.Ongoing, }; - - if (book.annotation) { - novel.summary += book.annotation + '\n'; - } - if (book.authorNotes) { - novel.summary += 'Примечания автора:\n' + book.authorNotes; - } - - const chaptersJSON: ChaptersEntity[] = await fetchApi( - this.apiSite + 'work/' + workID + '/content', - { - headers: { - Authorization: 'Bearer ' + this.user?.token || 'guest', - }, - }, - ).then(res => res.json()); - const chapters: Plugin.ChapterItem[] = []; - chaptersJSON.forEach((chapter, chapterIndex) => { - if (chapter.isAvailable && !chapter.isDraft) { - chapters.push({ - name: chapter.title || 'Глава ' + (chapterIndex + 1), - path: workID + '/' + chapter.id, - releaseTime: dayjs( - chapter.publishTime || chapter.lastModificationTime, - ).format('LLL'), - chapterNumber: (chapter.sortOrder || chapterIndex) + 1, - }); - } - }); + let isParsingName = false; + let isParsingAuthor = false; + let isReadingSummary = false; + let isParsingGenres = false; + let isParsingStatus = false; + let isParsingChapter = false; + let isReadingChapterName = false; + let tempChapter = {} as Plugin.ChapterItem; + const parser = new Parser({ + onopentag(name, attribs) { + if (name === 'h1' && attribs['class'] === 'card-title') { + isParsingName = true; + } + if (name === 'img' && attribs['class'] === 'cover-image') { + novel.cover = attribs['src']; + } + if ( + name === 'a' && + attribs['href'] && + attribs['href'].endsWith('/works') + ) { + isParsingAuthor = true; + } + if (name === 'div' && attribs['class'] === 'rich-content') { + isReadingSummary = true; + } + if (name === 'br' && isReadingSummary) { + novel.summary += '\n'; + } + if (name === 'noindex') { + isParsingGenres = true; + if (novel.genres) novel.genres = ''; + } + if (name === 'label' && attribs['class'].includes('label')) { + isParsingStatus = true; + } + if (name === 'li' && attribs['class'] === 'clearfix') { + isParsingChapter = true; + } + if (name === 'a' && isParsingChapter && attribs['href']) { + isReadingChapterName = true; + tempChapter.path = attribs.href.replace('/reader/', ''); + } + if (name === 'span' && attribs['data-time']) { + tempChapter.releaseTime = dayjs(attribs['data-time']).format('LLL'); + } + }, + ontext(data) { + if (isParsingName) { + novel.name = data.trim(); + isParsingName = false; + } + if (isParsingAuthor && data.trim()) { + novel.author += data.trim() + ', '; + } + if (isReadingSummary) { + novel.summary += data.trim(); + } + if (isParsingGenres) { + novel.genres += data; + } + if (isParsingStatus) { + const status = data.trim(); + switch (status) { + case 'в процессе': + novel.status = NovelStatus.Ongoing; + break; + case 'весь текст': + novel.status = NovelStatus.Completed; + break; + default: + novel.status = NovelStatus.Unknown; + break; + } + } + if (isReadingChapterName) { + tempChapter.name = data.trim(); + isReadingChapterName = false; + } + }, + onclosetag(name) { + if (name === 'div' && isParsingAuthor) { + isParsingAuthor = false; + novel.author = novel.author?.slice(0, -2); + } + if (name === 'div' && isReadingSummary) { + isReadingSummary = false; + } + if (name === 'noindex') { + isParsingGenres = false; + } + if (name === 'label') { + isParsingStatus = false; + } + if (name === 'li' && isParsingChapter) { + tempChapter.chapterNumber = chapters.length; + if (tempChapter.path) { + chapters?.push(tempChapter); + } + tempChapter = {} as Plugin.ChapterItem; + isParsingChapter = false; + } + }, + }); + parser.write(html); + parser.end(); + + if (!novel.cover) { + novel.cover = defaultCover; + } + if (!chapters.length) { + chapters.push({ + name: 'Рассказ', + path: workID, + }); + } novel.chapters = chapters; return novel; } async parseChapter(chapterPath: string): Promise { - const [workID, chapterID] = chapterPath.split('/'); - if (!this.user) this.user = await this.getUser(); - const result: encryptedСhapter = await fetchApi( - this.apiSite + `work/${workID}/chapter/${chapterID}/text`, - { - headers: { - Authorization: 'Bearer ' + this.user?.token || 'guest', - }, - }, - ).then(res => res.json()); + const html = await fetchApi(this.resolveUrl(chapterPath), { + headers: { 'User-Agent': this.userAgent }, + }).then(res => res.text()); - if (result.code) { - return result.code + '\n' + result?.message; + let [workID, chapterID] = chapterPath.split('/'); + const userRaw = html.match(/userId:(.*?),/)?.[1]?.trim(); + const userId = userRaw === 'null' ? '' : userRaw; + + if (!chapterID) { + chapterID = html.match(/chapterId:(.*?),/)?.[1]?.trim() || ''; } + if (!chapterID) throw new Error('Chapter ID not found'); - const chapterText = decrypt(result.text, result.key, this.user?.userId); + const chapter = await fetchApi( + this.site + '/reader/' + workID + '/chapter?id=' + chapterID, + { + headers: { 'User-Agent': this.userAgent }, + }, + ); + const chapterJson: Result = await chapter.json(); + const key = chapter.headers.get('reader-secret'); + + if (!key) + throw new Error('Failed to find the token\n' + chapterJson.messages); + + const chapterText = decrypt(chapterJson.data.text, key, userId); return chapterText; } @@ -144,64 +263,13 @@ class AuthorToday implements Plugin.PluginBase { ): Promise { const url = this.site + '/search?category=works&q=' + searchTerm + '&page=' + pageNo; - const result = await fetchApi(url).then(res => res.text()); - const loadedCheerio = parseHTML(result); - const novels: Plugin.NovelItem[] = []; - loadedCheerio('a.work-row').each((index, element) => { - const name = loadedCheerio(element) - .find('h4[class="work-title"]') - .text() - .trim(); - let cover = loadedCheerio(element).find('img').attr('data-src'); - const path = loadedCheerio(element).attr('href'); - - cover = cover ? cover.split('?')[0] : defaultCover; - - if (!path) return; - novels.push({ name, cover, path: path.replace('/work/', '') }); - }); - - return novels; + return this.parseNovels(url); } resolveUrl = (path: string, isNovel?: boolean) => this.site + (isNovel ? '/work/' : '/reader/') + path; - user: authorization | undefined; - getUser = async () => { - let user = storage.get('user') || { userId: '', token: 'guest' }; - if (user && user.userId && user.token) { - const currentUser: currentUser = await fetchApi( - this.apiSite + 'account/current-user', - { - headers: { - Authorization: 'Bearer ' + user.token || 'guest', - }, - }, - ).then(res => res.json()); - if (currentUser?.id && !currentUser.isDisabled) return user; - - storage.delete('user'); - user = { userId: '', token: 'guest' }; - } - - const result = await fetchApi(this.site + '/account/bearer-token'); - if (result.url.includes('Login?ReturnUrl=')) { - return user; //It looks like the user has lost the session - } - - const loginUser: authorization = await result.json(); - user = { userId: loginUser.userId, token: loginUser.token }; - - storage.set( - 'user', - user, //for some reason they're ending an hour early. - new Date(loginUser.expires).getTime() - 1 * 60 * 60 * 1000, - ); - return user; //user authorized successfully - }; - filters = { sort: { label: 'Сортировка', @@ -294,9 +362,9 @@ class AuthorToday implements Plugin.PluginBase { }, form: { label: 'Форма произведения', - value: 'any', + value: '', options: [ - { label: 'Любой', value: 'any' }, + { label: 'Любой', value: '' }, { label: 'Перевод', value: 'translation' }, { label: 'Повесть', value: 'tale' }, { label: 'Рассказ', value: 'story' }, @@ -308,9 +376,9 @@ class AuthorToday implements Plugin.PluginBase { }, state: { label: 'Статус произведения', - value: 'any', + value: '', options: [ - { label: 'Любой статус', value: 'any' }, + { label: 'Любой статус', value: '' }, { label: 'В процессе', value: 'in-progress' }, { label: 'Завершено', value: 'finished' }, ], @@ -318,9 +386,9 @@ class AuthorToday implements Plugin.PluginBase { }, series: { label: 'Статус цикла', - value: 'any', + value: '', options: [ - { label: 'Не важно', value: 'any' }, + { label: 'Не важно', value: '' }, { label: 'Вне цикла', value: 'out' }, { label: 'Цикл завершен', value: 'finished' }, { label: 'Цикл не завершен', value: 'unfinished' }, @@ -329,9 +397,9 @@ class AuthorToday implements Plugin.PluginBase { }, access: { label: 'Тип доступа', - value: 'any', + value: '', options: [ - { label: 'Любой', value: 'any' }, + { label: 'Любой', value: '' }, { label: 'Платный', value: 'paid' }, { label: 'Бесплатный', value: 'free' }, ], @@ -354,10 +422,10 @@ export default new AuthorToday(); function decrypt( encrypt: string, encryptedKey: string, - userId: number | string = '', + userId: string | number = '', ) { const key = encryptedKey.split('').reverse().join('') + '@_@' + userId; - const keyBytes = key.split('').map(char => char.charCodeAt()); + const keyBytes = key.split('').map(char => char.charCodeAt(0)); const keyLength = keyBytes.length; let text = ''; @@ -370,238 +438,13 @@ function decrypt( return text; } -type authorization = { - userId: number; - token: string; - issued: string; - expires: string; +type Result = { + isSuccessful: boolean; + isWarning: boolean; + messages: string | null; + data: Data; }; -type currentUser = { - id: number; - userName: string; - fio: string; - email: string; - avatarUrl?: null; - backgroundUrl?: null; - status?: null; - hideDislike: boolean; - hideFinished: boolean; - isBanned: boolean; - banReason?: null; - banEnd?: null; - emailConfirmed: boolean; - isDeleted: boolean; - isDisabled: boolean; -}; - -type response = { - searchResults?: SearchResultsEntity[] | null; - realTotalCount: number; - code?: string; - errorMessage?: null; - isLastPage: boolean; - format: Format; - duration: Duration; - isWorkMarkEnabled: boolean; -}; -type SearchResultsEntity = { - id: number; - title: string; - annotation: string; - authorId: number; - authorFIO: string; - authorUserName: string; - originalAuthor?: null; - coAuthorId?: number | null; - coAuthorFIO?: string | null; - coAuthorUserName?: string | null; - coAuthorConfirmed: boolean; - seriesId: number; - seriesTitle?: string | null; - publishTime: string; - lastModificationTime: string; - finishTime?: string | null; - isDraft: boolean; - formEnum: string; - genreId: number; - firstSubGenreId: number; - secondSubGenreId?: number | null; - adultOnly: boolean; - tags?: (string | null)[] | null; - coverUrl: string; - viewCount: number; - commentCount: number; - reviewCount: number; - textLength: number; - likeCount: number; - showAuthor: boolean; - price?: number | null; - discount?: null; - discountStartDate?: null; - discountEndDate?: null; - status: string; - promoFragment: boolean; - isExclusive: boolean; - format: string; - marks?: null[] | null; - isLiked: boolean; - isWorkMarkEnabled: boolean; - workInLibraryState: string; -}; -type Format = { - enumValue: string; - value: string; - title: string; - mobileTitle: string; -}; -type Duration = { - value: string; - title: string; - mobileTitle: string; -}; - -type responseBook = { - message?: string; - chapters?: ChaptersEntity[] | null; - allowDownloads: boolean; - downloadErrorCode: string; - downloadErrorMessage: string; - privacyDownloads: string; - collectionCount: number; - annotation: string; - authorNotes: string; - atRecommendation: boolean; - seriesWorkIds?: number[] | null; - seriesWorkNumber: number; - reviewCount: number; - tags?: string[] | null; - orderId?: null; - orderStatus: string; - orderStatusMessage?: null; - contests?: null[] | null; - galleryImages?: GalleryImagesEntity[] | null; - booktrailerVideoUrl?: null; - isExclusive: boolean; - freeChapterCount: number; - promoFragment: boolean; - recommendations?: RecommendationsEntity[] | null; - linkedWork?: null; - id: number; - title: string; - coverUrl: string; - lastModificationTime: string; - lastUpdateTime: string; - finishTime?: null; - isFinished: boolean; - textLength: number; - textLengthLastRead: number; - price: number; - discount?: null; - workForm: string; - status: string; - authorId: number; - authorFIO: string; - authorUserName: string; - originalAuthor?: null; - translator?: null; - reciter?: null; - coAuthorId?: null; - coAuthorFIO?: null; - coAuthorUserName?: null; - coAuthorConfirmed: boolean; - secondCoAuthorId?: null; - secondCoAuthorFIO?: null; - secondCoAuthorUserName?: null; - secondCoAuthorConfirmed: boolean; - isPurchased: boolean; - userLikeId?: null; - lastReadTime?: null; - lastChapterId?: null; - lastChapterProgress: number; - likeCount: number; - commentCount: number; - rewardCount: number; - rewardsEnabled: boolean; - inLibraryState: string; - addedToLibraryTime: string; - updateInLibraryTime: string; - privacyDisplay: string; - state: string; - isDraft: boolean; - enableRedLine: boolean; - enableTTS: boolean; - adultOnly: boolean; - seriesId: number; - seriesOrder: number; - seriesTitle: string; - afterword: string; - seriesNextWorkId?: null; - genreId: number; - firstSubGenreId: number; - secondSubGenreId: number; - format: string; - marks?: null; - purchaseTime?: null; - likeTime?: null; - markTime?: null; -}; -type ChaptersEntity = { - id: number; - workId: number; - title: string; - isDraft: boolean; - sortOrder: number; - publishTime?: string; - lastModificationTime: string; - textLength: number; - isAvailable: boolean; -}; - -type GalleryImagesEntity = { - id: string; - caption?: null; - url: string; - height: number; - width: number; -}; -type RecommendationsEntity = { - title: string; - coverUrl: string; - authorId: number; - authorFIO: string; - authorUserName: string; - originalAuthor?: null; - coAuthorId?: null; - coAuthorFIO?: null; - coAuthorUserName?: null; - coAuthorConfirmed: boolean; - secondCoAuthorId?: null; - secondCoAuthorFIO?: null; - secondCoAuthorUserName?: null; - secondCoAuthorConfirmed: boolean; - discount?: null; - price?: number | null; - lastModificationTime: string; - finishTime: string; - finished: boolean; - isExclusive: boolean; - format: string; - status: string; - state: string; - removalReason?: null; - removalReasonComment?: null; - id: number; -}; - -type encryptedСhapter = { - id: number; - title: string; - isDraft: boolean; - publishTime: string; - lastModificationTime: string; +type Data = { text: string; - key: string; - code?: string; - message?: string; }; diff --git a/src/plugins/russian/neobook.ts b/src/plugins/russian/neobook.ts index 8d9068c..1aef565 100644 --- a/src/plugins/russian/neobook.ts +++ b/src/plugins/russian/neobook.ts @@ -17,7 +17,7 @@ class Neobook implements Plugin.PluginBase { name = 'Neobook'; site = 'https://neobook.org'; apiSite = 'https://api.neobook.org/'; - version = '1.0.1'; + version = '1.0.2'; icon = 'src/ru/neobook/icon.png'; async fetchNovels( @@ -29,7 +29,7 @@ class Neobook implements Plugin.PluginBase { searchTerm?: string, ): Promise { const formData = new FormData(); - formData.append('version', '4.0'); + formData.append('version', '4.4'); formData.append('uid', '0'); formData.append('utoken', ''); formData.append('resource', 'general'); @@ -52,6 +52,7 @@ class Neobook implements Plugin.PluginBase { { method: 'post', body: formData, + referrer: this.site, }, ).then(res => res.json()); const novels: Plugin.NovelItem[] = []; diff --git a/src/plugins/russian/zelluloza.ts b/src/plugins/russian/zelluloza.ts index 4fa249f..d42e9a0 100644 --- a/src/plugins/russian/zelluloza.ts +++ b/src/plugins/russian/zelluloza.ts @@ -9,7 +9,7 @@ class Zelluloza implements Plugin.PluginBase { id = 'zelluloza'; name = 'Целлюлоза'; site = 'https://zelluloza.ru'; - version = '1.0.2'; + version = '1.0.3'; icon = 'src/ru/zelluloza/icon.png'; async popularNovels( @@ -21,17 +21,28 @@ class Zelluloza implements Plugin.PluginBase { ): Promise { const sort = showLatestNovels ? '0' : filters?.sort?.value || '3'; - const path = filters?.genres?.value - ? `/search/genres/${filters.genres.value}/?page=${pageNo}` - : `/top/freebooks/?sort_order=${sort}&page=${pageNo}`; + const genres = filters?.genres?.value || '0'; - const body = await fetchApi(this.site + path).then(res => res.text()); + const body = await fetchApi(this.site + '/ajaxcall/', { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Referer: this.site + '/search/done/#result', + Origin: this.site, + }, + method: 'POST', + body: new URLSearchParams({ + op: 'morebooks', + par1: '', + par2: `206:0:${genres}:0.${sort}.0.0.0.0.0.0.0.0.0.0.0..0..:${pageNo}`, + par4: '', + }).toString(), + }).then(res => res.text()); const loadedCheerio = parseHTML(body); const novels: Plugin.NovelItem[] = []; - loadedCheerio('section[class="book-card-item"]').each((index, element) => { + loadedCheerio('div[style="display: flex;"]').each((index, element) => { novels.push({ - name: loadedCheerio(element).find('span[itemprop="name"]').text() || '', + name: loadedCheerio(element).find('a[class="txt"]').attr('title') || '', cover: this.site + loadedCheerio(element).find('img[class="shadow"]').attr('src'), @@ -45,7 +56,7 @@ class Zelluloza implements Plugin.PluginBase { } async parseNovel(novelPath: string): Promise { - const body = await fetchApi(this.resolveUrl(novelPath, true)).then(res => + const body = await fetchApi(this.resolveUrl(novelPath)).then(res => res.text(), ); const loadedCheerio = parseHTML(body); @@ -54,7 +65,10 @@ class Zelluloza implements Plugin.PluginBase { path: novelPath, name: loadedCheerio('h2[class="bookname"]').text().trim(), cover: this.site + loadedCheerio('img[class="shadow"]').attr('src'), - genres: loadedCheerio('.gnres').text()?.split?.(': ')?.[1], + genres: loadedCheerio('.gnres span[itemprop="genre"]') + .map((index, element) => loadedCheerio(element).text()) + .get() + .join(','), summary: loadedCheerio('#bann_full').text() || loadedCheerio('#bann_short').text(), @@ -153,7 +167,7 @@ class Zelluloza implements Plugin.PluginBase { loadedCheerio('div[style="display: flex;"]').each((index, element) => { novels.push({ - name: loadedCheerio(element).find('span[itemprop="name"]').text() || '', + name: loadedCheerio(element).find('a[class="txt"]').attr('title') || '', cover: this.site + loadedCheerio(element).find('img[class="shadow"]').attr('src'), @@ -191,6 +205,7 @@ class Zelluloza implements Plugin.PluginBase { { label: 'По длительности чтения', value: '1' }, { label: 'По количеству читателей', value: '2' }, { label: 'По популярности', value: '4' }, + { label: 'Самые продаваемые', value: '5' }, ], type: FilterTypes.Picker, },