diff --git a/scripts/multisrc/ranobes/template.ts b/scripts/multisrc/ranobes/template.ts index 238f9ce..aeb8b75 100644 --- a/scripts/multisrc/ranobes/template.ts +++ b/scripts/multisrc/ranobes/template.ts @@ -28,10 +28,32 @@ class RanobesPlugin implements Plugin.PluginBase { this.name = metadata.sourceName; this.icon = 'multisrc/ranobes/ranobes/icon.png'; this.site = metadata.sourceSite; - this.version = '2.0.1'; + this.version = '2.0.2'; this.options = metadata.options as RanobesOptions; } + async safeFecth(url: string, init: any = {}): Promise { + const r = await fetchApi(url, init); + if (!r.ok) + throw new Error( + 'Could not reach site (' + r.status + ') try to open in webview.', + ); + const data = await r.text(); + const title = data.match(/(.*?)<\/title>/)?.[1]?.trim(); + + if ( + title && + (title == 'Bot Verification' || + title == 'You are being redirected...' || + title == 'Un instant...' || + title == 'Just a moment...' || + title == 'Redirecting...') + ) + throw new Error('Captcha error, please open in webview'); + + return data; + } + parseNovels(html: string) { const novels: Plugin.NovelItem[] = []; let tempNovel = {} as Plugin.NovelItem; @@ -154,7 +176,7 @@ class RanobesPlugin implements Plugin.PluginBase { async popularNovels(page: number): Promise<Plugin.NovelItem[]> { const link = `${this.site}/${this.options.path}/page/${page}/`; - const body = await fetchApi(link).then(r => r.text()); + const body = await this.safeFecth(link); return this.parseNovels(body); } @@ -162,8 +184,7 @@ class RanobesPlugin implements Plugin.PluginBase { novelPath: string, ): Promise<Plugin.SourceNovel & { totalPages: number }> { const baseUrl = this.site; - const result = await fetchApi(baseUrl + novelPath); - const html = await result.text(); + const html = await this.safeFecth(baseUrl + novelPath); const novel: Plugin.SourceNovel & { totalPages: number } = { path: novelPath, name: '', @@ -330,9 +351,7 @@ class RanobesPlugin implements Plugin.PluginBase { : '/' + novelPath.split('-').slice(1).join('-').split('.')[0]; const firstUrl = this.site + '/chapters' + pagePath.replace(this.options.path + '/', ''); - const pageBody = await fetchApi(firstUrl + '/page/' + page).then(r => - r.text(), - ); + const pageBody = await this.safeFecth(firstUrl + '/page/' + page); const baseUrl = this.site; let isScript = false; @@ -408,8 +427,7 @@ class RanobesPlugin implements Plugin.PluginBase { } async parseChapter(chapterPath: string): Promise<string> { - const result = await fetchApi(this.site + chapterPath); - const html = await result.text(); + const html = await this.safeFecth(this.site + chapterPath); const indexA = html.indexOf('<div class="text" id="arrticle">'); const indexB = html.indexOf('<div class="category grey ellipses">', indexA); @@ -424,7 +442,7 @@ class RanobesPlugin implements Plugin.PluginBase { ): Promise<Plugin.NovelItem[]> { let html; if (this.id === 'ranobes-ru') { - html = await fetchApi(this.site + '/index.php?do=search', { + html = await this.safeFecth(this.site + '/index.php?do=search', { headers: { 'Content-Type': 'application/x-www-form-urlencoded', Referer: this.site + '/', @@ -436,10 +454,10 @@ class RanobesPlugin implements Plugin.PluginBase { search_start: page.toString(), story: searchTerm, }).toString(), - }).then(res => res.text()); + }); } else { const link = `${this.site}/search/${searchTerm}/page/${page}`; - html = await fetchApi(link).then(r => r.text()); + html = await this.safeFecth(link); } return this.parseNovels(html); } diff --git a/src/plugins/english/foxteller.ts b/src/plugins/english/foxteller.ts index fefd360..907d5a7 100644 --- a/src/plugins/english/foxteller.ts +++ b/src/plugins/english/foxteller.ts @@ -9,9 +9,31 @@ class Foxteller implements Plugin.PluginBase { id = 'foxteller'; name = 'Foxteller'; site = 'https://www.foxteller.com'; - version = '1.0.1'; + version = '1.0.2'; icon = 'src/en/foxteller/icon.png'; + async safeFecth(url: string, init: any = {}): Promise<string> { + const r = await fetchApi(url, init); + if (!r.ok) + throw new Error( + 'Could not reach site (' + r.status + ') try to open in webview.', + ); + const data = await r.text(); + const title = data.match(/<title>(.*?)<\/title>/)?.[1]?.trim(); + + if ( + title && + (title == 'Bot Verification' || + title == 'You are being redirected...' || + title == 'Un instant...' || + title == 'Just a moment...' || + title == 'Redirecting...') + ) + throw new Error('Captcha error, please open in webview'); + + return data; + } + async popularNovels( pageNo: number, { filters }: Plugin.PopularNovelsOptions<typeof this.filters>, @@ -19,7 +41,7 @@ class Foxteller implements Plugin.PluginBase { const url = this.site + '/library?sort=' + filters?.order?.value || 'popularity'; - const body = await fetchApi(url).then(res => res.text()); + const body = await this.safeFecth(url); const novels: Plugin.NovelItem[] = []; const div = body.match(/<div class="col-md-6">([\s\S]*?)<\/div>/g) || []; @@ -44,9 +66,7 @@ class Foxteller implements Plugin.PluginBase { } async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> { - const body = await fetchApi(this.resolveUrl(novelPath)).then(res => - res.text(), - ); + const body = await this.safeFecth(this.resolveUrl(novelPath)); const novel: Plugin.SourceNovel = { path: novelPath, name: '', @@ -189,10 +209,9 @@ class Foxteller implements Plugin.PluginBase { } async parseChapter(chapterPath: string): Promise<string> { - const res = await fetchApi(this.resolveUrl(chapterPath)); - const body = await res.text(); + const res = await this.safeFecth(this.resolveUrl(chapterPath)); const novelID = chapterPath.split('/')[0]; - const chapterID = body.match(/'chapter_id': '([\d]+)'/)?.[1]; + const chapterID = res.match(/'chapter_id': '([\d]+)'/)?.[1]; if (!chapterID) throw new Error('No chapter found'); @@ -203,7 +222,7 @@ class Foxteller implements Plugin.PluginBase { 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', 'Content-Type': 'application/json;charset=utf-8', }, - Referer: res.url, + Referer: this.resolveUrl(chapterPath), body: JSON.stringify({ 'x1': novelID, 'x2': chapterID }), }).then(res => res.json()); @@ -234,7 +253,7 @@ class Foxteller implements Plugin.PluginBase { } async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> { - const body = await fetchApi(this.site + '/search', { + const body = await this.safeFecth(this.site + '/search', { method: 'post', headers: { Accept: 'application/json, text/plain, */*', @@ -243,7 +262,7 @@ class Foxteller implements Plugin.PluginBase { }, Referer: this.site, body: JSON.stringify({ query: searchTerm }), - }).then(res => res.text()); + }); const novels: Plugin.NovelItem[] = []; const items = body.match(/<a.*>([\s\S]*?)<\/a>/g) || []; diff --git a/src/plugins/russian/ranobelib.ts b/src/plugins/russian/ranobelib.ts index 02516ed..184e9f6 100644 --- a/src/plugins/russian/ranobelib.ts +++ b/src/plugins/russian/ranobelib.ts @@ -18,7 +18,7 @@ class RLIB implements Plugin.PluginBase { name = 'RanobeLib'; site = 'https://ranobelib.me'; apiSite = 'https://api.mangalib.me/api/manga/'; - version = '2.1.0'; + version = '2.1.1'; icon = 'src/ru/ranobelib/icon.png'; webStorageUtilized = true; @@ -126,10 +126,10 @@ class RLIB implements Plugin.PluginBase { novel.genres = genres.join(', '); } - const branch_id: Record<number, string> = {}; + const branch_name: Record<number, string> = {}; if (data.teams.length) { data.teams.forEach( - ({ name, details }) => (branch_id[details?.branch_id || '0'] = name), + ({ name, details }) => (branch_name[details?.branch_id || '0'] = name), ); } @@ -144,27 +144,39 @@ class RLIB implements Plugin.PluginBase { const chapters: Plugin.ChapterItem[] = []; chaptersJSON.data.forEach(chapter => - chapters.push({ - name: - 'Том ' + - chapter.volume + - ' Глава ' + - chapter.number + - (chapter.name ? ' ' + chapter.name : ''), - path: - novelPath + - '/' + - chapter.volume + - '/' + - chapter.number + - '/' + - (chapter.branches[0]?.branch_id || ''), - releaseTime: dayjs(chapter.branches[0].created_at).format('LLL'), - chapterNumber: chapter.index, - page: branch_id[chapter.branches[0].branch_id || '0'], - }), + chapter.branches.forEach(({ branch_id, created_at }) => + chapters.push({ + name: + 'Том ' + + chapter.volume + + ' Глава ' + + chapter.number + + (chapter.name ? ' ' + chapter.name : ''), + path: + novelPath + + '/' + + chapter.volume + + '/' + + chapter.number + + '/' + + (branch_id || ''), + releaseTime: dayjs(created_at).format('LLL'), + chapterNumber: chapter.index, + page: branch_name[branch_id || '0'], + }), + ), ); + if (chapters.length && data.teams.length > 1) { + //For whatever reason, the chapters overlap with another page. + chapters.sort((chapterA, chapterB) => { + if (chapterA.page !== chapterB.page) { + return chapterA.page.localeCompare(chapterB.page); + } + return chapterA.chapterNumber - chapterB.chapterNumber; + }); + } + novel.chapters = chapters; } @@ -509,6 +521,42 @@ function jsonToHtml(json: HTML[], images, html = '') { (element.content ? jsonToHtml(element.content, images) : '<br>') + '</p>'; break; + case 'orderedList': + html += + '<ol>' + + (element.content ? jsonToHtml(element.content, images) : '<br>') + + '</ol>'; + break; + case 'listItem': + html += + '<li>' + + (element.content ? jsonToHtml(element.content, images) : '<br>') + + '</li>'; + break; + case 'blockquote': + html += + '<blockquote>' + + (element.content ? jsonToHtml(element.content, images) : '<br>') + + '</blockquote>'; + break; + case 'italic': + html += + '<i>' + + (element.content ? jsonToHtml(element.content, images) : '<br>') + + '</i>'; + break; + case 'bold': + html += + '<b>' + + (element.content ? jsonToHtml(element.content, images) : '<br>') + + '</b>'; + break; + case 'underline': + html += + '<u>' + + (element.content ? jsonToHtml(element.content, images) : '<br>') + + '</u>'; + break; case 'heading': html += '<h2>' +