From b7909aac1f8b18a13ebfd22755167f8e0c10f3d8 Mon Sep 17 00:00:00 2001 From: 52191314 Date: Wed, 12 Aug 2026 09:44:18 +0700 Subject: [PATCH] fix(russian): fix ranobelib next-chapter navigation, Cyrillic search, and chapter errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - set scanlator instead of page for translation branches; the app's getNextChapter/getPrevChapter cast page to int, so team names made every chapter match the gt(page, 0) branch and jump to the first chapter. scanlator is the field the app uses for branch filtering. - drop the now-dead uniquePages/sort workaround - encodeURIComponent the search term so multi-word Cyrillic queries ('Я стал в...') are transmitted correctly - parseChapter: throw on invalid path, non-OK response or empty content so the app shows its error screen (Retry / refresh) instead of an empty chapter - bump to 2.2.6 --- plugins/russian/ranobelib.ts | 102 +++++++++++++++-------------------- 1 file changed, 42 insertions(+), 60 deletions(-) diff --git a/plugins/russian/ranobelib.ts b/plugins/russian/ranobelib.ts index 09f9a3cb8..8d0f085b3 100644 --- a/plugins/russian/ranobelib.ts +++ b/plugins/russian/ranobelib.ts @@ -29,7 +29,7 @@ class RLIB implements Plugin.PluginBase { name = 'RanobeLib'; site = 'https://ranobelib.me'; apiSite = 'https://api.cdnlibs.org/api/manga/'; - version = '2.2.4'; + version = '2.2.6'; icon = 'src/ru/ranobelib/icon.png'; webStorageUtilized = true; imageRequestInit = { @@ -161,46 +161,23 @@ class RLIB implements Plugin.PluginBase { ).then(res => res.json()); if (chaptersJSON.data?.length) { - let chapters: Plugin.ChapterItem[] = chaptersJSON.data.flatMap(chapter => - chapter.branches.map(({ branch_id, created_at }) => { - const bId = String(branch_id ?? '0'); - return { - name: `Том ${chapter.volume} Глава ${chapter.number}${ - chapter.name ? ' ' + chapter.name.trim() : '' - }`, - path: `${novelPath}/${chapter.volume}/${chapter.number}/${bId}`, - releaseTime: created_at ? dayjs(created_at).format('LLL') : null, - chapterNumber: chapter.index, - page: branch_name[bId] || 'Неизвестный', - }; - }), + const chapters: Plugin.ChapterItem[] = chaptersJSON.data.flatMap( + chapter => + chapter.branches.map(({ branch_id, created_at }) => { + const bId = String(branch_id ?? '0'); + return { + name: `Том ${chapter.volume} Глава ${chapter.number}${ + chapter.name ? ' ' + chapter.name.trim() : '' + }`, + path: `${novelPath}/${chapter.volume}/${chapter.number}/${bId}`, + releaseTime: created_at ? dayjs(created_at).format('LLL') : null, + chapterNumber: chapter.index, + scanlator: branch_name[bId] || 'Неизвестный', + }; + }), ); if (chapters.length) { - const uniquePages = new Set(chapters.map(c => c.page)); - - if (uniquePages.size === 1) { - // If only one unique page value, set page to undefined for all chapters - // Need more investigation one app side for single page shenanigans - chapters = chapters.map(chapter => ({ - ...chapter, - page: undefined, - })); - } else if (data.teams?.length > 1) { - // Original sorting logic for multiple pages, for reasons chapters overlap with one another - chapters.sort((chapterA, chapterB) => { - if ( - chapterA.page && - chapterB.page && - chapterA.page !== chapterB.page - ) { - return chapterA.page.localeCompare(chapterB.page); - } - return ( - (chapterA.chapterNumber || 0) - (chapterB.chapterNumber || 0) - ); - }); - } novel.chapters = chapters; } } @@ -209,31 +186,37 @@ class RLIB implements Plugin.PluginBase { async parseChapter(chapterPath: string): Promise { const [slug, volume, number, branch_id] = chapterPath.split('/'); - let chapterText = ''; - - if (slug && volume && number) { - const result: { data: DataClass } = await fetchApi( - this.apiSite + - slug + - '/chapter?' + - (branch_id ? 'branch_id=' + branch_id + '&' : '') + - 'number=' + - number + - '&volume=' + - volume, - { headers: this.getHeaders() }, - ).then(res => res.json()); - const content = result?.data?.content; - chapterText = - typeof content === 'object' && content?.type === 'doc' - ? jsonToHtml(content.content, result.data.attachments || []) - : (content as string) || ''; + if (!slug || !volume || !number) { + throw new Error(`Invalid chapter path: ${chapterPath}`); + } + const url = + this.apiSite + + slug + + '/chapter?' + + (branch_id ? 'branch_id=' + branch_id + '&' : '') + + 'number=' + + number + + '&volume=' + + volume; + const res = await fetchApi(url, { headers: this.getHeaders() }); + if (!res.ok) { + throw new Error(`Could not load chapter (HTTP ${res.status})`); + } + const result: { data: DataClass } = await res.json(); + const content = result?.data?.content; + const chapterText = + typeof content === 'object' && content?.type === 'doc' + ? jsonToHtml(content.content, result.data.attachments || []) + : (content as string) || ''; + if (!chapterText) { + throw new Error('Could not load chapter'); } return chapterText; } async searchNovels(searchTerm: string): Promise { - const url = this.apiSite + '?site_id[0]=3&q=' + searchTerm; + const url = + this.apiSite + '?site_id[0]=3&q=' + encodeURIComponent(searchTerm); const result: TopLevel = await fetchApi(url, { headers: this.getHeaders(), }).then(res => res.json()); @@ -276,8 +259,7 @@ class RLIB implements Plugin.PluginBase { getUser = () => { const user = storage.get('user') as - | { token: string; id: number } - | undefined; + { token: string; id: number } | undefined; if (user) { return { token: { Authorization: 'Bearer ' + user.token }, ui: user.id }; }