From 13620a190815132b570676ba7403b82c8f520a2c Mon Sep 17 00:00:00 2001 From: Maix0 <39835848+Maix0@users.noreply.github.com> Date: Wed, 29 May 2024 15:45:23 +0200 Subject: [PATCH] Feat(plugin): Add Re:Library (#936) * Created basic Re:Library support * Fixed Version of Re:Library * Fixed Id of Re:Library * Fixed genre and added very crude search * Fixed small bugs, and shorten url (exept image links) * Changed invalid chapter url/name to state which chapter number it should be * Some QOL stuff for the extension (client side search + latest page) Added: Client Side search using some FuzzySearch that is vendored in the plugin file Fixed: PopularNovels now correctly search for latest if requested (support paging) * Removed console.log * General Fixes - Throw error on broken scraping - Add novel path unconditionally to novels - Changed some '==' to '===' where they were present --- icons/src/en/relibrary/icon.png | Bin 0 -> 26486 bytes plugins/english/relibrary.ts | 391 ++++++++++++++++++++++++++++++++ 2 files changed, 391 insertions(+) create mode 100644 icons/src/en/relibrary/icon.png create mode 100644 plugins/english/relibrary.ts diff --git a/icons/src/en/relibrary/icon.png b/icons/src/en/relibrary/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0c27ad3bb0edb99f32af3631533c7df76ee3ad44 GIT binary patch literal 26486 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 hf#Lek { + private haystack: Item[]; + private options: FuzzySearchOptions; + private getItems: (item: Item) => string[]; + + constructor( + getItems: (item: Item) => string[], + options: Partial, + ) { + this.haystack = []; + this.options = Object.assign( + { + caseSensitive: false, + sort: false, + }, + options, + ); + this.getItems = getItems; + } + + getOptions(): FuzzySearchOptions { + return this.options; + } + + setOptions(options: Partial) { + this.options = Object.assign(this.options, options); + } + + setHaystack(items: Item[]) { + this.haystack = items; + } + + getHaystack(): Item[] { + return this.haystack; + } + + search(query: string): Item[] { + if (query.length === 0) { + return this.haystack; + } + const results: { item: Item; score: number }[] = []; + + for (let item of this.haystack) { + for (let value of this.getItems(item)) { + const score = this.isMatch(value, query); + if (score !== undefined) { + results.push({ item, score }); + /// We don't want to have multiple occurence of the same item, so only take one if it is found + break; + } + } + } + + if (this.options.sort) { + results.sort((a, b) => a.score - b.score); + } + + return results.map(result => result.item); + } + + isMatch(item: string, query: string): number | undefined { + if (!this.options.caseSensitive) { + item = item.toLocaleLowerCase(); + query = query.toLocaleLowerCase(); + } + + const indexes = this.nearestIndexesFor(item, query); + + if (indexes === undefined) { + return; + } + // Exact matches should be first. + if (item === query) { + return 1; + } + // If we have more than 2 letters, matches close to each other should be first. + if (indexes.length > 1) { + return 2 + (indexes[indexes.length - 1] - indexes[0]); + } + // Matches closest to the start of the string should be first. + return 2 + indexes[0]; + } + + nearestIndexesFor(item: string, query: string): number[] | undefined { + const letters = query.split(''); + let indexes: number[][] = []; + + const idxFL = this.idxFirstLetter(item, query); + + for (let idx of idxFL) { + indexes.push([idx]); + for (let i = 1; i < letters.length; i++) { + const letter = letters[i]; + let index = item.indexOf(letter, idx + 1); + if (index === -1) { + indexes.pop(); + break; + } + indexes[indexes.length - 1].push(index); + index++; + } + } + + if (indexes.length === 0) { + return; + } + + return indexes.sort((a, b) => { + if (a.length === 1) { + return a[0] - b[0]; + } + + let res_a = a[a.length - 1] - a[0]; + let res_b = b[b.length - 1] - b[0]; + + return res_a - res_b; + })[0]; + } + + idxFirstLetter(item: string, query: string): number[] { + const match = query[0]; + + return item + .split('') + .map((letter, index) => { + if (letter !== match) { + return; + } + return index; + }) + .filter(index => index !== undefined) as number[]; + } +} + +class ReLibraryPlugin implements Plugin.PluginBase { + id = 'ReLib'; + name = 'Re:Library'; + icon = 'src/en/relibrary/icon.png'; + site = 'https://re-library.com'; + version = '1.0.0'; + filters: Filters | undefined = undefined; + + private searchFunc: FuzzySearch = new FuzzySearch( + item => [item.name], + { sort: true, caseSensitive: false }, + ); + + private async popularNovelsInner(url: string): Promise { + const novels: Plugin.NovelItem[] = []; + const result = await fetch(url); + const body = await result.text(); + + const loadedCheerio = loadCheerio(body); + loadedCheerio('.entry-content > ol > li').each((_i, el) => { + let novel: Partial = {}; + novel.name = loadedCheerio(el).find('h3 > a').text(); + novel.path = loadedCheerio(el) + .find('table > tbody > tr > td > a') + .attr('href'); + if (novel.name === undefined || novel.path === undefined) return; + novel.cover = + loadedCheerio(el) + .find('table > tbody > tr > td > a > img') + .attr('src') || defaultCover; + if (novel.path.startsWith(this.site)) { + novel.path = novel.path.slice(this.site.length); + } + novels.push(novel as Plugin.NovelItem); + }); + return novels; + } + + private async lastestNovelsInner(url: string): Promise { + const novels: Plugin.NovelItem[] = []; + const result = await fetch(url); + const body = await result.text(); + + const loadedCheerio = loadCheerio(body); + loadedCheerio('article.type-page.page').each((_i, el) => { + let novel: Partial = {}; + novel.name = loadedCheerio(el).find('.entry-title').text(); + novel.path = loadedCheerio(el).find('.entry-title a').attr('href'); + if (novel.path === undefined || novel.name === undefined) return; + novel.cover = + loadedCheerio(el) + .find('.entry-content > table > tbody > tr > td > img') + .attr('src') || defaultCover; + if (novel.path.startsWith(this.site)) { + novel.path = novel.path.slice(this.site.length); + } + novels.push(novel as Plugin.NovelItem); + }); + return novels; + } + + async popularNovels( + pageNo: number, + { + showLatestNovels, + filters, + }: Plugin.PopularNovelsOptions, + ): Promise { + // The most-popular page only has a single page, so we return an empty array in case you ask for the impossible + // the lastest page do have paginated result, so we support that. + if (showLatestNovels) + return this.lastestNovelsInner( + `${this.site}/tag/translations/page/${pageNo}`, + ); + else if (pageNo === 1) + return this.popularNovelsInner(`${this.site}/translations/most-popular/`); + else return []; + } + + async parseNovel(novelPath: string): Promise { + let novel: Partial = { + path: novelPath, + }; + // title: .entry-content > header.entry-header > .entry-title + // img: .entry-content > table > tbody > tr > td > img + // tags: .entry-content > table > tbody > tr > td > p > span > a[] + // synopis: .entry-content > div.su-box > div.su-box-content + // chapters: .entry-content > div.su-accordion li.page_item[] + + const result = await fetch(`${this.site}/${novelPath}`); + const body = await result.text(); + + const loadedCheerio = loadCheerio(body); + + // If it doesn't find the name I should just throw an error (or early return) since the scraping is broken + novel.name = loadedCheerio('header.entry-header > .entry-title') + .text() + .trim(); + + if (novel.name === undefined || novel.name === '404 – Page not found') + throw new Error(`Invalid novel for url ${novelPath}`); + + // Find the cover + novel.cover = + loadedCheerio('.entry-content > table > tbody > tr > td > img').attr( + 'src', + ) || defaultCover; + + // Genres in comma separated "list" + novel.genres = (() => { + let genres: string[] = []; + loadedCheerio( + '.entry-content > table > tbody > tr > td > p > span > a', + ).each((_i, el) => { + genres.push(loadedCheerio(el).text().trim()); + }); + return genres.join(', '); + })(); + + // Handle the novel status + // Sadly some novels just state the status inside the summary... + // I don't even know if the snippet here works for *most* of the novels preset, or only for a few + loadedCheerio('.entry-content > table > tbody > tr > td > p').each( + function (_i, el) { + if ( + loadedCheerio(el) + .find('strong') + .text() + .toLowerCase() + .trim() + .startsWith('status') + ) { + loadedCheerio(el).find('strong').remove(); + novel.status = loadedCheerio(el).text(); + } + }, + ); + + novel.summary = loadedCheerio( + '.entry-content > div.su-box > div.su-box-content', + ).text(); + + let chapters: Plugin.ChapterItem[] = []; + + let chapter_idx = 0; + loadedCheerio('.entry-content > div.su-accordion').each((_i1, el) => { + loadedCheerio(el) + .find('li.page_item > a') + .each((_i2, chap_el) => { + chapter_idx += 1; + let chap_path = loadedCheerio(chap_el).attr('href')?.trim(); + if ( + loadedCheerio(chap_el).text() === undefined || + chap_path === undefined + ) + return; + if (chap_path.startsWith(this.site)) { + chap_path = chap_path.slice(this.site.length); + } + chapters.push({ + name: loadedCheerio(chap_el).text(), + path: chap_path, + chapterNumber: chapter_idx, + // we KNOW that we can't get the released time (at least without any additional fetches), so set it to null purposfully + releaseTime: null, + }); + }); + }); + + novel.chapters = chapters; + return novel as Plugin.NovelItem; + } + + async parseChapter(chapterPath: string): Promise { + // parse chapter text here + const result = await fetch(`${this.site}/${chapterPath}`); + const body = await result.text(); + + const loadedCheerio = loadCheerio(body); + let text: string[] = []; + loadedCheerio('.entry-content > p') + .slice(1) + .each((_i, el) => { + loadedCheerio(el).find('span').remove(); + let t = loadedCheerio(el).html(); + if (t === undefined) return; + text.push(`

${t}

`); + }); + if (text.length == 0) + throw new Error( + `Invalid Parsing of chapter, couldn't find any text for url "${this.site}/${chapterPath}"`, + ); + return text.join(''); + } + + async searchNovels( + searchTerm: string, + pageNo: number, + ): Promise { + // We only want to serve a single "page" since we do the search client side. + if (pageNo !== 1) return []; + + let novels: Plugin.NovelItem[] = []; + const req = await fetch(`${this.site}/translations/`); + const body = await req.text(); + + const loadedCheerio = loadCheerio(body); + + loadedCheerio('.entry-content table a').each((_i, el) => { + let e = loadedCheerio(el); + if (e && e.attr('href') && e.text()) { + let path = e.attr('href')!; + if (path.startsWith(this.site)) { + path = path.slice(this.site.length); + } + novels.push({ name: e.text(), path: path, cover: defaultCover }); + } + }); + this.searchFunc.setHaystack(novels); + return this.searchFunc.search(searchTerm); + } + + async fetchImage(url: string): Promise { + return fetchFile(url, { headers: { referer: this.site } }); + } +} + +export default new ReLibraryPlugin();