From d9f88a18f2db19787e19c96d5afd9b2d1464dc6c Mon Sep 17 00:00:00 2001 From: Adam Kemish <49228220+Soopyboo32@users.noreply.github.com> Date: Tue, 21 Jan 2025 00:13:48 +0800 Subject: [PATCH] Add readfrom (#1439) * Add readfrom * Fix webview button * Reduce api requests when possible * Fix cached novels not having extra info * Actually fix :skull: * oops :skull: Co-authored-by: nyagami * Remove non-novel text like adds and next chapter button --------- Co-authored-by: nyagami --- public/static/src/en/readfrom/icon.png | Bin 0 -> 8436 bytes src/plugins/english/readfrom.ts | 170 +++++++++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 public/static/src/en/readfrom/icon.png create mode 100644 src/plugins/english/readfrom.ts diff --git a/public/static/src/en/readfrom/icon.png b/public/static/src/en/readfrom/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..bc3a5941cb3edf99dca339b1e28a2a870cf36143 GIT binary patch literal 8436 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 article.box', + ) + .map((i, el) => { + let summary = loadedCheerio(el).find( + isSearch ? 'div.text5' : 'div.text3', + )[0]; + loadedCheerio(summary).find('.coll-ellipsis').remove(); + loadedCheerio(summary).find('a').remove(); + return { + name: loadedCheerio(el).find('h2.title').text().trim(), + path: loadedCheerio(el) + .find('h2.title > a') + .attr('href')! + .replace('https://readfrom.net/', '') + .replace(/^\//, ''), + cover: loadedCheerio(el).find('img').attr('src') || defaultCover, + summary: + loadedCheerio(summary).text().trim() + + loadedCheerio(summary).find('span.coll-hidden').text(), + genres: loadedCheerio(el) + .find(isSearch ? 'h5.title > a' : 'h2 > a') + .filter((i, el) => el.attribs['title']?.startsWith?.('Genre - ')) + .map((i, el) => loadedCheerio(el).text()) + .toArray() + .join(', '), + author: isSearch + ? loadedCheerio(el) + .find('h5.title > a') + .filter((i, el) => + el.attribs['title']?.startsWith?.('Book author - '), + ) + .text() + : loadedCheerio(el).find('h4 > a').text(), + }; + }) + .toArray(); + + this.loadedNovelCache.push(...ret); + while (this.loadedNovelCache.length > 100) { + this.loadedNovelCache.shift(); + } + + return ret; + } + + async popularNovels( + pageNo: number, + { + showLatestNovels, + filters, + }: Plugin.PopularNovelsOptions, + ) { + let type = showLatestNovels ? 'last_added_books' : 'allbooks'; + let res = await fetchApi( + 'https://readfrom.net/' + type + '/page/' + pageNo + '/', + ); + let text = await res.text(); + return this.parseNovels(loadCheerio(text)); + } + + async parseNovel(novelPath: string): Promise { + let data = await fetchApi('https://readfrom.net/' + novelPath); + let text = await data.text(); + let loadedCheerio = loadCheerio(text); + + const novel: Plugin.SourceNovel = { + path: novelPath, + name: 'Untitled', + }; + + novel.name = loadedCheerio('center > h2.title') + .text() + .split(', \n\n')[0] + .trim(); + novel.cover = + loadedCheerio('article.box > div > center > div > a > img').attr('src') || + defaultCover; + + novel.chapters = loadedCheerio(loadedCheerio('div.pages').get()[0]) + .find('> a') + .map((i, el) => { + return { + name: loadedCheerio(el).text().trim(), + path: loadedCheerio(el) + .attr('href')! + .replace('https://readfrom.net/', '') + .replace(/^\//, ''), + // releaseTime: '', + chapterNumber: i + 2, + }; + }) + .toArray(); + novel.chapters.unshift({ + name: '1', + path: novelPath, + chapterNumber: 1, + }); + + let moreNovelInfo = this.loadedNovelCache.find( + novel => novel.path === novelPath, + ); + if (!moreNovelInfo) + moreNovelInfo = (await this.searchNovels(novel.name, 1)).find( + novel => novel.path === novelPath, + ); + if (moreNovelInfo) { + novel.summary = moreNovelInfo.summary; + novel.genres = moreNovelInfo.genres; + novel.author = moreNovelInfo.author; + } + + return novel; + } + + async parseChapter(chapterPath: string): Promise { + let data = await fetchApi('https://readfrom.net/' + chapterPath); + let text = await data.text(); + let loadedCheerio = loadCheerio(text); + loadedCheerio('#textToRead > span:empty').remove(); + loadedCheerio('#textToRead > center').remove(); + return loadedCheerio('#textToRead').html() || ''; + } + + async searchNovels(searchTerm: string, pageNo: number) { + if (pageNo !== 1) return []; + let res = await fetchApi( + 'https://readfrom.net/build_in_search/?q=' + + encodeURIComponent(searchTerm), + ); + let text = await res.text(); + return this.parseNovels(loadCheerio(text), true); + } + + resolveUrl = (path: string, isNovel?: boolean) => this.site + '/' + path; +} + +export default new ReadFromPlugin();