diff --git a/public/static/multisrc/madara/fenrir/icon.png b/public/static/multisrc/madara/fenrir/icon.png deleted file mode 100644 index 3d616ef..0000000 Binary files a/public/static/multisrc/madara/fenrir/icon.png and /dev/null differ diff --git a/public/static/src/en/fenrirrealm/icon.png b/public/static/src/en/fenrirrealm/icon.png new file mode 100644 index 0000000..5a1888e Binary files /dev/null and b/public/static/src/en/fenrirrealm/icon.png differ diff --git a/scripts/multisrc/madara/filters/fenrir.json b/scripts/multisrc/madara/filters/fenrir.json deleted file mode 100644 index e09b27b..0000000 --- a/scripts/multisrc/madara/filters/fenrir.json +++ /dev/null @@ -1,300 +0,0 @@ -{ - "filters": { - "genre[]": { - "type": "Checkbox", - "label": "Genre", - "value": [], - "options": [ - { - "label": "Action", - "value": "action" - }, - { - "label": "Adult", - "value": "adult" - }, - { - "label": "Adventure", - "value": "adventure" - }, - { - "label": "Anime", - "value": "anime" - }, - { - "label": "Cartoon", - "value": "cartoon" - }, - { - "label": "Comedy", - "value": "comedy" - }, - { - "label": "Comic", - "value": "comic" - }, - { - "label": "Cooking", - "value": "cooking" - }, - { - "label": "Detective", - "value": "detective" - }, - { - "label": "Doujinshi", - "value": "doujinshi" - }, - { - "label": "Drama", - "value": "drama" - }, - { - "label": "Ecchi", - "value": "ecchi" - }, - { - "label": "Fantasy", - "value": "fantasy" - }, - { - "label": "Gender Bender", - "value": "gender-bender" - }, - { - "label": "Harem", - "value": "harem" - }, - { - "label": "Historical", - "value": "historical" - }, - { - "label": "Horror", - "value": "horror" - }, - { - "label": "Josei", - "value": "josei" - }, - { - "label": "Live action", - "value": "live-action" - }, - { - "label": "Manga", - "value": "manga" - }, - { - "label": "Manhua", - "value": "manhua" - }, - { - "label": "Manhwa", - "value": "manhwa" - }, - { - "label": "Martial Arts", - "value": "martial-arts" - }, - { - "label": "Mature", - "value": "mature" - }, - { - "label": "Mecha", - "value": "mecha" - }, - { - "label": "Mystery", - "value": "mystery" - }, - { - "label": "One shot", - "value": "one-shot" - }, - { - "label": "Psychological", - "value": "psychological" - }, - { - "label": "Romance", - "value": "romance" - }, - { - "label": "School Life", - "value": "school-life" - }, - { - "label": "Sci-fi", - "value": "sci-fi" - }, - { - "label": "Seinen", - "value": "seinen" - }, - { - "label": "Shoujo", - "value": "shoujo" - }, - { - "label": "Shoujo Ai", - "value": "shoujo-ai" - }, - { - "label": "Shounen", - "value": "shounen" - }, - { - "label": "Shounen Ai", - "value": "shounen-ai" - }, - { - "label": "Slice of Life", - "value": "slice-of-life" - }, - { - "label": "Smut", - "value": "smut" - }, - { - "label": "Soft Yaoi", - "value": "soft-yaoi" - }, - { - "label": "Soft Yuri", - "value": "soft-yuri" - }, - { - "label": "Sports", - "value": "sports" - }, - { - "label": "Supernatural", - "value": "supernatural" - }, - { - "label": "Tragedy", - "value": "tragedy" - }, - { - "label": "Webtoon", - "value": "webtoon" - }, - { - "label": "Wuxia", - "value": "wuxia" - }, - { - "label": "Yaoi", - "value": "yaoi" - }, - { - "label": "Yuri", - "value": "yuri" - } - ] - }, - "op": { - "type": "Switch", - "label": "having all selected genres", - "value": false - }, - "author": { - "type": "Text", - "label": "Author", - "value": "" - }, - "artist": { - "type": "Text", - "label": "Artist", - "value": "" - }, - "release": { - "type": "Text", - "label": "Year of Released", - "value": "" - }, - "adult": { - "type": "Picker", - "label": "Adult content", - "value": "", - "options": [ - { - "label": "All", - "value": "" - }, - { - "label": "None adult content", - "value": "0" - }, - { - "label": "Only adult content", - "value": "1" - } - ] - }, - "status[]": { - "type": "Checkbox", - "label": "Status", - "value": [], - "options": [ - { - "label": "OnGoing", - "value": "on-going" - }, - { - "label": "Completed", - "value": "end" - }, - { - "label": "Canceled", - "value": "canceled" - }, - { - "label": "On Hold", - "value": "on-hold" - }, - { - "label": "Upcoming", - "value": "upcoming" - } - ] - }, - "m_orderby": { - "type": "Picker", - "label": "Order by", - "value": "", - "options": [ - { - "label": "Relevance", - "value": "" - }, - { - "label": "Latest", - "value": "latest" - }, - { - "label": "A-Z", - "value": "alphabet" - }, - { - "label": "Rating", - "value": "rating" - }, - { - "label": "Trending", - "value": "trending" - }, - { - "label": "Most Views", - "value": "views" - }, - { - "label": "New", - "value": "new-manga" - } - ] - } - } -} \ No newline at end of file diff --git a/scripts/multisrc/madara/sources.json b/scripts/multisrc/madara/sources.json index 8e46cc6..0f21ffa 100644 --- a/scripts/multisrc/madara/sources.json +++ b/scripts/multisrc/madara/sources.json @@ -398,15 +398,6 @@ "lang": "English" } }, - { - "id": "fenrir", - "sourceSite": "https://fenrirtranslations.com/", - "sourceName": "Fenrir Translations", - "options": { - "useNewChapterEndpoint": true, - "lang": "English" - } - }, { "id": "bellereservoir", "sourceSite": "https://bellereservoir.com/", diff --git a/src/plugins/english/fenrirrealm.ts b/src/plugins/english/fenrirrealm.ts new file mode 100644 index 0000000..930ab59 --- /dev/null +++ b/src/plugins/english/fenrirrealm.ts @@ -0,0 +1,233 @@ +import { fetchApi } from '@libs/fetch'; +import { Plugin } from '@typings/plugin'; +import { load as loadCheerio } from 'cheerio'; +import { Filters, FilterTypes } from '@libs/filterInputs'; + +class FenrirRealmPlugin implements Plugin.PluginBase { + id = 'fenrir'; + name = 'Fenrir Realm'; + icon = 'src/en/fenrirrealm/icon.png'; + site = 'https://fenrirealm.com'; + version = '1.0.7'; + imageRequestInit?: Plugin.ImageRequestInit | undefined = undefined; + + //flag indicates whether access to LocalStorage, SesesionStorage is required. + webStorageUtilized?: boolean; + + async popularNovels( + pageNo: number, + { + showLatestNovels, + filters, + }: Plugin.PopularNovelsOptions, + ): Promise { + // let sort = "updated"; + let sort = filters.sort.value; + if (showLatestNovels) sort = 'latest'; + const genresFilter = filters.genres.value + .map(g => '&genres%5B%5D=' + g) + .join(''); + const res = await fetchApi( + `${this.site}/api/novels/filter?page=${pageNo}&per_page=20&status=${filters.status.value}&order=${sort}${genresFilter}`, + ).then(r => r.json()); + + return res.data.map(r => this.parseNovelFromApi(r)); + } + + async parseNovel(novelPath: string): Promise { + const html = await fetchApi(`${this.site}/series/${novelPath}`, { + headers: { + 'User-Agent': '', + }, + }).then(r => r.text()); + const loadedCheerio = loadCheerio(html); + + loadedCheerio( + 'div.overflow-hidden.transition-all.max-h-\\[108px\\] > br', + ).replaceWith('%%NEWLINE%%'); + const novel: Plugin.SourceNovel = { + path: novelPath, + name: loadedCheerio('h1.my-2').text(), + summary: loadedCheerio( + 'div.overflow-hidden.transition-all.max-h-\\[108px\\]', + ) + .text() + .replaceAll('%%NEWLINE%%', '\n'), + }; + // novel.artist = ''; + novel.author = loadedCheerio( + 'div.flex-1 > div.mb-3 > a.inline-flex', + ).text(); + novel.cover = + this.site + + '/storage/' + + html.match(/,cover:"storage\/(.+?)",cover_data_url/)[1]; + novel.genres = loadedCheerio('div.flex-1 > div.flex:not(.mb-3, .mt-5) > a') + .map((i, el) => loadCheerio(el).text()) + .toArray() + .join(','); + novel.status = loadedCheerio('div.flex-1 > div.mb-3 > span.rounded-md') + .first() + .text(); + + const chapters = await fetchApi( + this.site + '/api/novels/chapter-list/' + novelPath, + ).then(r => r.json()); + + novel.chapters = chapters + .map(c => ({ + name: c.title, + path: novelPath + '/' + c.slug, + releaseTime: c.created_at, + chapterNumber: c.number, + })) + .sort((a, b) => a.chapterNumber - b.chapterNumber); + return novel; + } + + async parseChapter(chapterPath: string): Promise { + const page = await fetchApi(this.site + '/series/' + chapterPath, { + headers: { + 'User-Agent': '', + }, + }).then(r => r.text()); + return loadCheerio(page)('#reader-area').html(); + } + + async searchNovels( + searchTerm: string, + pageNo: number, + ): Promise { + return await fetchApi( + `${this.site}/api/novels/filter?page=${pageNo}&per_page=20&search=${encodeURIComponent(searchTerm)}`, + ) + .then(r => r.json()) + .then(r => r.data.map(novel => this.parseNovelFromApi(novel))); + } + + parseNovelFromApi(apiData) { + return { + name: apiData.title, + path: apiData.slug, + cover: this.site + '/' + apiData.cover, + summary: apiData.description, + status: apiData.status, + genres: apiData.genres.map(g => g.name).join(','), + }; + } + + resolveUrl = (path: string, isNovel?: boolean) => + this.site + '/series/' + path; + + filters = { + status: { + type: FilterTypes.Picker, + label: 'Status', + value: 'any', + options: [ + { label: 'All', value: 'any' }, + { label: 'Ongoing', value: 'ongoing' }, + { + label: 'Completed', + value: 'completed', + }, + ], + }, + sort: { + type: FilterTypes.Picker, + label: 'Sort', + value: 'popular', + options: [ + { label: 'Popular', value: 'popular' }, + { label: 'Latest', value: 'latest' }, + { label: 'Updated', value: 'updated' }, + ], + }, + genres: { + type: FilterTypes.CheckboxGroup, + label: 'Genres', + value: [], + options: [ + { 'label': 'Action', 'value': '1' }, + { 'label': 'Adult', 'value': '2' }, + { + 'label': 'Adventure', + 'value': '3', + }, + { 'label': 'Comedy', 'value': '4' }, + { 'label': 'Drama', 'value': '5' }, + { + 'label': 'Ecchi', + 'value': '6', + }, + { 'label': 'Fantasy', 'value': '7' }, + { 'label': 'Gender Bender', 'value': '8' }, + { + 'label': 'Harem', + 'value': '9', + }, + { 'label': 'Historical', 'value': '10' }, + { 'label': 'Horror', 'value': '11' }, + { + 'label': 'Josei', + 'value': '12', + }, + { 'label': 'Martial Arts', 'value': '13' }, + { 'label': 'Mature', 'value': '14' }, + { + 'label': 'Mecha', + 'value': '15', + }, + { 'label': 'Mystery', 'value': '16' }, + { 'label': 'Psychological', 'value': '17' }, + { + 'label': 'Romance', + 'value': '18', + }, + { 'label': 'School Life', 'value': '19' }, + { 'label': 'Sci-fi', 'value': '20' }, + { + 'label': 'Seinen', + 'value': '21', + }, + { 'label': 'Shoujo', 'value': '22' }, + { 'label': 'Shoujo Ai', 'value': '23' }, + { + 'label': 'Shounen', + 'value': '24', + }, + { 'label': 'Shounen Ai', 'value': '25' }, + { 'label': 'Slice of Life', 'value': '26' }, + { + 'label': 'Smut', + 'value': '27', + }, + { 'label': 'Sports', 'value': '28' }, + { 'label': 'Supernatural', 'value': '29' }, + { + 'label': 'Tragedy', + 'value': '30', + }, + { 'label': 'Wuxia', 'value': '31' }, + { 'label': 'Xianxia', 'value': '32' }, + { + 'label': 'Xuanhuan', + 'value': '33', + }, + { 'label': 'Yaoi', 'value': '34' }, + { 'label': 'Yuri', 'value': '35' }, + ], + }, + } satisfies Filters; +} + +export default new FenrirRealmPlugin(); + +//paste into console on site to load +async function getUpdatedGenres() { + const data = await fetch( + 'https://fenrirealm.com/api/novels/taxonomy/genres', + ).then(d => d.json()); + const genreData = data.map(g => ({ label: g.name, value: g.id.toString() })); + console.log(JSON.stringify(genreData)); +}