diff --git a/.github/workflows/updatejson.yml b/.github/workflows/updatejson.yml index bcec4e8..3e660af 100644 --- a/.github/workflows/updatejson.yml +++ b/.github/workflows/updatejson.yml @@ -1,57 +1,56 @@ name: Update json files on: - push: - branches: - - plugins + push: + branches: + - plugins jobs: - updatejson: - if: github.repository == 'LNReader/lnreader-sources' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: '16' - - name: Install dependencies - run: npm install - - - name: Clean up current dist - run: | - rm -rf dist - mkdir dist - cat > config.json << EOF - { - "githubUsername": "LNReader", - "githubRepository": "lnreader-sources", - "githubBranch": "plugins" - } - EOF - - - name: Update json files - id: json - run: | - node scripts/json_plugins.js - changes="$(git diff)" - if [ "$changes" ] - then - changes=1 - else - changes="" - fi - echo "json-diff=$changes" >> $GITHUB_OUTPUT + updatejson: + if: github.repository == 'LNReader/lnreader-sources' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: "16" + - name: Install dependencies + run: npm install - - name: Create pull request - if: ${{ steps.json.outputs.json-diff }} - uses: peter-evans/create-pull-request@v4 - with: - token: ${{ secrets.REPO_SCOPED_TOKEN }} - author: rajarsheechatterjee - committer: rajarsheechatterjee - commit-message: 'plugins: update json files for new plugins' - title: Update json files - labels: enhancement - body: Auto-generated PR for updating json files - branch: update-json - delete-branch: true + - name: Clean up current dist + run: | + rm -rf dist + mkdir dist + cat > config.json << EOF + { + "githubUsername": "LNReader", + "githubRepository": "lnreader-sources", + "githubBranch": "plugins" + } + EOF + - name: Update json files + id: json + run: | + npm run json + changes="$(git diff)" + if [ "$changes" ] + then + changes=1 + else + changes="" + fi + echo "json-diff=$changes" >> $GITHUB_OUTPUT + + - name: Create pull request + if: ${{ steps.json.outputs.json-diff }} + uses: peter-evans/create-pull-request@v4 + with: + token: ${{ secrets.REPO_SCOPED_TOKEN }} + author: rajarsheechatterjee + committer: rajarsheechatterjee + commit-message: "plugins: update json files for new plugins" + title: Update json files + labels: enhancement + body: Auto-generated PR for updating json files + branch: update-json + delete-branch: true diff --git a/.gitignore b/.gitignore index 572c3ce..3365c9d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,7 @@ node_modules/**/* .vscode .env config.json + +# ignore TS translation folder but keep plugins folder +js/* +!js/plugins diff --git a/README.md b/README.md index 17f1a07..c070119 100644 --- a/README.md +++ b/README.md @@ -14,16 +14,17 @@ Repository to host plugins and related issues, and requests for [LNReader](https ## Contributing -1. Please use this [template](./template.js) or [template.min](./template.min.js) +1. Please use this [template](./template.ts) or [template.min](./template.min.ts) 2. Choose your language in [./plugins](./plugins) -+ If your language doenst exist, please request us. We will add it soon. ++ If your language doenst exist or you need some other packages, please request us. We will add it soon. 3. Write your scripts -+ See example: [Hako](./plugins/vietnamese/LNHako.js) ++ See example: [Hako](./plugins/vietnamese/LNHako.ts) +Multisrc: [multisrc](./scripts/multisrc) ## Test your script @@ -38,7 +39,11 @@ npm install 2. Running ``` -nodemon index.js +npm start +``` +or +``` +nodemon ``` + Then open http://localhost:3000 @@ -51,13 +56,10 @@ nodemon index.js #### No need to reload webpage after changing your plugin script. -- One more thing: you can use [cheerio_space](./cheerio_space) to build functions without requesting to the site. - ---------- If you want to test plugin in app side, remember to config these things. -in [.env](./.env) 1. Create `config.json` file in root dir 2. Add your github infomation. Here is an example. @@ -68,7 +70,7 @@ in [.env](./.env) "githubBranch": "plugins" } ``` -3. Just commit and push it. And you will see your own json files in github folk: dist/`username`/ +3. Just commit and push it. And you will see your own json files in github fork: dist/`username`/ also in [pluginManager.ts](https://github.com/LNReader/lnreader/blob/plugins/src/plugins/pluginManager.ts) (app repo) diff --git a/api/plugins.js b/api/plugins.js deleted file mode 100644 index ea2e8a4..0000000 --- a/api/plugins.js +++ /dev/null @@ -1,53 +0,0 @@ -const fs = require('fs'); -const languages = require('@libs/languages'); -const path = require('path') -const root = path.dirname(require.main.filename); - -const all_plugins = () => { - const res = {} - for (let language in languages) { //language with English name - const langPath = path.join(root, 'plugins', language.toLowerCase()); - if (!fs.existsSync(langPath)) continue; - const plugins = fs.readdirSync(langPath); - res[languages[language]] = []; - plugins.forEach(plugin => { - if(plugin.startsWith('.')) return; - const requirePath = `@plugins/${language.toLowerCase()}/${plugin.split('.')[0]}` - const instance = require(requirePath); - const { id, name, lang, version, description } = instance; - const info = { id, name, lang, version, description } // lang: language with native name - info.requirePath = requirePath; - res[lang].push(info); - }); - } - return res; -} - -const popularNovels = async (pluginRequirePath, {showLatestNovels, filters}) => { - return await require(pluginRequirePath).popularNovels(1, {showLatestNovels, filters}); -} - -const searchNovels = async (pluginRequirePath, searchTerm) => { - return await require(pluginRequirePath).searchNovels(searchTerm); -} - -const parseNovelAndChapters = async(pluginRequirePath, novelUrl) => { - return await require(pluginRequirePath).parseNovelAndChapters(novelUrl); -} - -const parseChapter = async (pluginRequirePath, chapterUrl) => { - return await require(pluginRequirePath).parseChapter(chapterUrl); -} - -const fetchImage = async (pluginRequirePath, url) => { - return await require(pluginRequirePath).fetchImage(url); -} - -module.exports = { - all_plugins, - popularNovels, - searchNovels, - parseNovelAndChapters, - parseChapter, - fetchImage -} \ No newline at end of file diff --git a/cheerio_space/source.html b/cheerio_space/source.html deleted file mode 100644 index 5132f98..0000000 --- a/cheerio_space/source.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - Html source to test with cheerio. - Do not make too many requests, you could get banned from site. - - \ No newline at end of file diff --git a/cheerio_space/test.js b/cheerio_space/test.js deleted file mode 100644 index 925eb83..0000000 --- a/cheerio_space/test.js +++ /dev/null @@ -1,6 +0,0 @@ -const cheerio = require('cheerio'); -const fs = require('fs'); -const path = require('path'); - -const body = fs.readFileSync(path.join(__dirname, 'source.html')).toString(); -const loadedCheerio = cheerio.load(body); diff --git a/dist/K1ngfish3r/plugins.json b/dist/K1ngfish3r/plugins.json new file mode 100644 index 0000000..7775635 --- /dev/null +++ b/dist/K1ngfish3r/plugins.json @@ -0,0 +1,520 @@ +{ + "العربية": [ + { + "id": "kolnovel", + "name": "KolNovel", + "site": "https://kolnovel.com/", + "lang": "العربية", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/arabic/kolnovel.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/multisrc/wpmangastream/icons/kolnovel.png" + } + ], + "中文, 汉语, 漢語": [], + "English": [ + { + "id": "ANF.com", + "name": "AllNovelFull", + "site": "https://allnovelfull.com", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/allnovelfull.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/allnovelfull/icon.png" + }, + { + "id": "BLN.com", + "name": "BestLightNovel", + "site": "https://bestlightnovel.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/bestlightnovel.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/bestlightnovel/icon.png" + }, + { + "id": "comrademao", + "name": "Comrade Mao", + "site": "https://comrademao.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/comrademao.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/comrademao/icon.png" + }, + { + "id": "DDL.com", + "name": "Divine Dao Library", + "site": "https://www.divinedaolibrary.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/divinedaolibrary.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/divinedaolibrary/icon.png" + }, + { + "id": "earlynovel", + "name": "Early Novel", + "site": "https://earlynovel.net/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/earlynovel.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/multisrc/madara/icons/latestnovel.png" + }, + { + "id": "fastnovel", + "name": "Fast Novel", + "site": "https://fastnovel.org/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/fastnovel.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/fastnovel/icon.png" + }, + { + "id": "foxaholic", + "name": "Foxaholic", + "site": "https://www.foxaholic.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/foxaholic.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/foxaholic/icon.png" + }, + { + "id": "freenovelupdates", + "name": "Free Novel Updates (Broken)", + "site": "https://www.freenovelupdates.com", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/freenovelupdates.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/freenovelupdates/icon.png" + }, + { + "id": "FWN.com", + "name": "Web NOVEL", + "site": "https://freewebnovel.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/freewebnovel.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/freewebnovel/icon.png" + }, + { + "id": "lightnovelpub", + "name": "LightNovelPub", + "site": "https://www.lightnovelpub.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/lightnovelpub.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/lightnovelpub/icon.png" + }, + { + "id": "LightNovelUpdates", + "name": "Light Novel Updates", + "site": "https://www.lightnovelupdates.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/lightnovelupdates.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/lightnovelupdates/icon.png" + }, + { + "id": "lnmtl", + "name": "LNMTL", + "site": "https://lnmtl.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/lnmtl.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/lnmtl/icon.png" + }, + { + "id": "LNR.org", + "name": "LightNovelReader", + "site": "https://lightnovelreader.org", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/LightNovelReader.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/lightnovelreader/icon.png" + }, + { + "id": "mtlnovel", + "name": "MTL Novel", + "site": "https://www.mtlnovel.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/mtlnovel.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/mtlnovel/icon.png" + }, + { + "id": "mtlreader", + "name": "MTL Reader", + "site": "https://mtlreader.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/mtlreader.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/mtlreader/icon.png" + }, + { + "id": "NF.me", + "name": "NovelFull.me", + "site": "https://novelfull.me/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/NovelFullMe.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/novelfullme/icon.png" + }, + { + "id": "nitroscans", + "name": "Nitroscans", + "site": "https://nitroscans.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/nitroscans.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/nitroscans/icon.png" + }, + { + "id": "NO.net", + "name": "novelsOnline", + "site": "https://novelsonline.net", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/NovelOnline.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/coverNotAvailable.jpg" + }, + { + "id": "NobleMTL", + "name": "NobleMTL", + "site": "https://www.noblemtl.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/nobleMTL.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/noblemtl/icon.png" + }, + { + "id": "novelforest", + "name": "Novel Forest (Broken)", + "site": "https://novelforest.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/novelforest.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/novelforest/icon.png" + }, + { + "id": "novelfull", + "name": "NovelFull", + "site": "https://novelfull.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/novelfull.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/novelfull/icon.png" + }, + { + "id": "novelhold", + "name": "Novel Hold", + "site": "https://novelhold.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/novelhold.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/novelhold/icon.png" + }, + { + "id": "novelpub.com", + "name": "NovelPub", + "site": "https://www.novelpub.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/novelpub.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/novelpub/icon.png" + }, + { + "id": "novelupdates", + "name": "Novel Updates", + "site": "https://www.novelupdates.com/", + "lang": "English", + "version": "0.5.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/novelupdates.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/novelupdates/icon.png" + }, + { + "id": "PandaMTL", + "name": "PandaMTL", + "site": "https://www.pandamtl.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/pandaMTL.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/wordpress/icon.png" + }, + { + "id": "wuxiaworld", + "name": "Wuxia World (WIP)", + "site": "https://www.wuxiaworld.com/", + "lang": "English", + "version": "0.5.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/wuxiaworld.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/wuxiaworld/icon.png" + } + ], + "français, française": [ + { + "id": "chireads.com", + "name": "Chireads", + "site": "https://chireads.com/", + "lang": "français, française", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/french/chireads.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/fr/chireads/icon.png" + }, + { + "id": "NDG.com", + "name": "NovelDeGlace", + "site": "https://noveldeglace.com/", + "lang": "français, française", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/french/noveldeglace.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/fr/noveldeglace/icon.png" + } + ], + "Bahasa Indonesia": [ + { + "id": "IDWN.id", + "name": "IndoWebNovel", + "site": "https://indowebnovel.id/id/", + "lang": "Bahasa Indonesia", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/indonesian/indowebnovel.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/id/indowebnovel/icon.png" + }, + { + "id": "novelringan.com", + "name": "NovelRingan", + "site": "https://novelringan.com/", + "lang": "Bahasa Indonesia", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/indonesian/novelringan.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/id/novelringan/icon.png" + }, + { + "id": "sakura.id", + "name": "SakuraNovel", + "site": "https://sakuranovel.id/", + "lang": "Bahasa Indonesia", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/indonesian/sakuranovel.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/id/sakuranovel/icon.png" + } + ], + "日本語": [ + { + "id": "yomou.syosetu", + "name": "Syosetu", + "site": "https://yomou.syosetu.com/", + "lang": "日本語", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/japanese/Syosetu.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/jp/syosetu/icon.png" + } + ], + "한국어, 조선말": [ + { + "id": "agit.xyz", + "name": "Agitoon", + "site": "https://agit501.xyz/", + "lang": "한국어, 조선말", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/korean/Agitoon.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/kr/agitoon/agit.png" + } + ], + "Português": [ + { + "id": "lightnovelbrasil", + "name": "Light Novel Brasil", + "site": "https://lightnovelbrasil.com/", + "lang": "Português", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/portuguese/lightnovelbrasil.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/multisrc/wpmangastream/icons/lightnovelbrasil.png" + } + ], + "русский язык": [ + { + "id": "AT", + "name": "Автор Тудей", + "site": "https://author.today", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/russian/authortoday.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/ru/authortoday/icon.png" + }, + { + "id": "bookriver", + "name": "Bookriver", + "site": "https://bookriver.ru", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/russian/bookriver.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/ru/bookriver/icon.png" + }, + { + "id": "EROLATE", + "name": "Erolate", + "site": "https://erolate.com", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/russian/erolate.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/ru/erolate/icon.png" + }, + { + "id": "jaomix.ru", + "name": "Jaomix", + "site": "https://jaomix.ru", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/russian/jaomix.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/ru/jaomix/icon.png" + }, + { + "id": "ReN", + "name": "Renovels", + "site": "https://renovels.org", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/russian/renovels.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/ru/renovels/icon.png" + }, + { + "id": "RLIB", + "name": "RanobeLib", + "site": "https://ranobelib.me", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/russian/ranobelib.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/ru/ranobelib/icon.png" + }, + { + "id": "RNBH.org", + "name": "RanobeHub", + "site": "https://ranobehub.org/", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/russian/ranobehub.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/ru/ranobehub/icon.png" + }, + { + "id": "RNRF", + "name": "РанобэРФ", + "site": "https://ранобэ.рф", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/russian/ranoberf.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/ru/ranoberf/icon.png" + }, + { + "id": "RULATE", + "name": "Rulate", + "site": "https://tl.rulate.ru", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/russian/rulate.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/ru/rulate/icon.png" + }, + { + "id": "TL", + "name": "NovelTL", + "site": "https://novel.tl", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/russian/novelTL.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/ru/noveltl/icon.png" + } + ], + "español, castellano": [ + { + "id": "awuxia.com", + "name": "ReinoWuxia", + "site": "http://www.reinowuxia.com/", + "lang": "español, castellano", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/spanish/novelawuxia.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/es/novelawuxia/icon.png" + }, + { + "id": "HasuTL", + "name": "Hasu Translations", + "site": "https://hasutl.wordpress.com/", + "lang": "español, castellano", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/spanish/hasutl.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/es/hasutl/icon.png" + }, + { + "id": "ligera.com", + "name": "Novelas Ligera", + "site": "https://novelasligera.com/", + "lang": "español, castellano", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/spanish/novelasligera.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/es/novelasligera/icon.png" + }, + { + "id": "oasisTL.wp", + "name": "Oasis Translations", + "site": "https://oasistranslations.wordpress.com/", + "lang": "español, castellano", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/spanish/oasistranslations.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/es/oasistranslations/icon.png" + }, + { + "id": "skynovels.net", + "name": "SkyNovels", + "site": "https://www.skynovels.net/", + "lang": "español, castellano", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/spanish/skynovels.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/es/skynovels/icon.png" + }, + { + "id": "TNL.com", + "name": "TuNovelaLigera", + "site": "https://tunovelaligera.com/", + "lang": "español, castellano", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/spanish/tunovelaligera.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/es/tunovelaligera/icon.png" + }, + { + "id": "yuukitls.com", + "name": "Yuuki Tls", + "site": "https://yuukitls.com/", + "lang": "español, castellano", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/spanish/yukitls.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/es/yuukitls/icon.png" + } + ], + "Türkçe": [ + { + "id": "epiknovel.com", + "name": "EpikNovel", + "site": "https://www.epiknovel.com/", + "lang": "Türkçe", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/turkish/epiknovel.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/tr/epiknovel/icon.png" + }, + { + "id": "lshnovel", + "name": "Liebe Schnee Hiver Novel", + "site": "https://lshnovel.com/", + "lang": "Türkçe", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/turkish/lshnovel.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/multisrc/wpmangastream/icons/lshnovel.png" + } + ], + "Tiếng Việt": [ + { + "id": "ln.hako", + "name": "Hako", + "site": "https://ln.hako.vn", + "lang": "Tiếng Việt", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/vietnamese/LNHako.js", + "iconUrl": "https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/vi/hakolightnovel/icon.png" + } + ] +} \ No newline at end of file diff --git a/dist/K1ngfish3r/plugins.min.json b/dist/K1ngfish3r/plugins.min.json new file mode 100644 index 0000000..a957fc6 --- /dev/null +++ b/dist/K1ngfish3r/plugins.min.json @@ -0,0 +1 @@ +{"العربية":[{"id":"kolnovel","name":"KolNovel","site":"https://kolnovel.com/","lang":"العربية","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/arabic/kolnovel.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/multisrc/wpmangastream/icons/kolnovel.png"}],"中文, 汉语, 漢語":[],"English":[{"id":"ANF.com","name":"AllNovelFull","site":"https://allnovelfull.com","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/allnovelfull.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/allnovelfull/icon.png"},{"id":"BLN.com","name":"BestLightNovel","site":"https://bestlightnovel.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/bestlightnovel.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/bestlightnovel/icon.png"},{"id":"comrademao","name":"Comrade Mao","site":"https://comrademao.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/comrademao.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/comrademao/icon.png"},{"id":"DDL.com","name":"Divine Dao Library","site":"https://www.divinedaolibrary.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/divinedaolibrary.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/divinedaolibrary/icon.png"},{"id":"earlynovel","name":"Early Novel","site":"https://earlynovel.net/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/earlynovel.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/multisrc/madara/icons/latestnovel.png"},{"id":"fastnovel","name":"Fast Novel","site":"https://fastnovel.org/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/fastnovel.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/fastnovel/icon.png"},{"id":"foxaholic","name":"Foxaholic","site":"https://www.foxaholic.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/foxaholic.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/foxaholic/icon.png"},{"id":"freenovelupdates","name":"Free Novel Updates (Broken)","site":"https://www.freenovelupdates.com","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/freenovelupdates.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/freenovelupdates/icon.png"},{"id":"FWN.com","name":"Web NOVEL","site":"https://freewebnovel.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/freewebnovel.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/freewebnovel/icon.png"},{"id":"lightnovelpub","name":"LightNovelPub","site":"https://www.lightnovelpub.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/lightnovelpub.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/lightnovelpub/icon.png"},{"id":"LightNovelUpdates","name":"Light Novel Updates","site":"https://www.lightnovelupdates.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/lightnovelupdates.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/lightnovelupdates/icon.png"},{"id":"lnmtl","name":"LNMTL","site":"https://lnmtl.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/lnmtl.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/lnmtl/icon.png"},{"id":"LNR.org","name":"LightNovelReader","site":"https://lightnovelreader.org","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/LightNovelReader.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/lightnovelreader/icon.png"},{"id":"mtlnovel","name":"MTL Novel","site":"https://www.mtlnovel.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/mtlnovel.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/mtlnovel/icon.png"},{"id":"mtlreader","name":"MTL Reader","site":"https://mtlreader.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/mtlreader.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/mtlreader/icon.png"},{"id":"NF.me","name":"NovelFull.me","site":"https://novelfull.me/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/NovelFullMe.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/novelfullme/icon.png"},{"id":"nitroscans","name":"Nitroscans","site":"https://nitroscans.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/nitroscans.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/nitroscans/icon.png"},{"id":"NO.net","name":"novelsOnline","site":"https://novelsonline.net","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/NovelOnline.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/coverNotAvailable.jpg"},{"id":"NobleMTL","name":"NobleMTL","site":"https://www.noblemtl.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/nobleMTL.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/noblemtl/icon.png"},{"id":"novelforest","name":"Novel Forest (Broken)","site":"https://novelforest.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/novelforest.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/novelforest/icon.png"},{"id":"novelfull","name":"NovelFull","site":"https://novelfull.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/novelfull.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/novelfull/icon.png"},{"id":"novelhold","name":"Novel Hold","site":"https://novelhold.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/novelhold.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/novelhold/icon.png"},{"id":"novelpub.com","name":"NovelPub","site":"https://www.novelpub.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/novelpub.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/novelpub/icon.png"},{"id":"novelupdates","name":"Novel Updates","site":"https://www.novelupdates.com/","lang":"English","version":"0.5.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/novelupdates.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/novelupdates/icon.png"},{"id":"PandaMTL","name":"PandaMTL","site":"https://www.pandamtl.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/pandaMTL.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/wordpress/icon.png"},{"id":"wuxiaworld","name":"Wuxia World (WIP)","site":"https://www.wuxiaworld.com/","lang":"English","version":"0.5.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/english/wuxiaworld.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/en/wuxiaworld/icon.png"}],"français, française":[{"id":"chireads.com","name":"Chireads","site":"https://chireads.com/","lang":"français, française","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/french/chireads.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/fr/chireads/icon.png"},{"id":"NDG.com","name":"NovelDeGlace","site":"https://noveldeglace.com/","lang":"français, française","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/french/noveldeglace.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/fr/noveldeglace/icon.png"}],"Bahasa Indonesia":[{"id":"IDWN.id","name":"IndoWebNovel","site":"https://indowebnovel.id/id/","lang":"Bahasa Indonesia","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/indonesian/indowebnovel.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/id/indowebnovel/icon.png"},{"id":"novelringan.com","name":"NovelRingan","site":"https://novelringan.com/","lang":"Bahasa Indonesia","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/indonesian/novelringan.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/id/novelringan/icon.png"},{"id":"sakura.id","name":"SakuraNovel","site":"https://sakuranovel.id/","lang":"Bahasa Indonesia","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/indonesian/sakuranovel.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/id/sakuranovel/icon.png"}],"日本語":[{"id":"yomou.syosetu","name":"Syosetu","site":"https://yomou.syosetu.com/","lang":"日本語","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/japanese/Syosetu.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/jp/syosetu/icon.png"}],"한국어, 조선말":[{"id":"agit.xyz","name":"Agitoon","site":"https://agit501.xyz/","lang":"한국어, 조선말","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/korean/Agitoon.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/kr/agitoon/agit.png"}],"Português":[{"id":"lightnovelbrasil","name":"Light Novel Brasil","site":"https://lightnovelbrasil.com/","lang":"Português","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/portuguese/lightnovelbrasil.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/multisrc/wpmangastream/icons/lightnovelbrasil.png"}],"русский язык":[{"id":"AT","name":"Автор Тудей","site":"https://author.today","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/russian/authortoday.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/ru/authortoday/icon.png"},{"id":"bookriver","name":"Bookriver","site":"https://bookriver.ru","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/russian/bookriver.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/ru/bookriver/icon.png"},{"id":"EROLATE","name":"Erolate","site":"https://erolate.com","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/russian/erolate.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/ru/erolate/icon.png"},{"id":"jaomix.ru","name":"Jaomix","site":"https://jaomix.ru","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/russian/jaomix.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/ru/jaomix/icon.png"},{"id":"ReN","name":"Renovels","site":"https://renovels.org","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/russian/renovels.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/ru/renovels/icon.png"},{"id":"RLIB","name":"RanobeLib","site":"https://ranobelib.me","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/russian/ranobelib.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/ru/ranobelib/icon.png"},{"id":"RNBH.org","name":"RanobeHub","site":"https://ranobehub.org/","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/russian/ranobehub.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/ru/ranobehub/icon.png"},{"id":"RNRF","name":"РанобэРФ","site":"https://ранобэ.рф","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/russian/ranoberf.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/ru/ranoberf/icon.png"},{"id":"RULATE","name":"Rulate","site":"https://tl.rulate.ru","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/russian/rulate.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/ru/rulate/icon.png"},{"id":"TL","name":"NovelTL","site":"https://novel.tl","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/russian/novelTL.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/ru/noveltl/icon.png"}],"español, castellano":[{"id":"awuxia.com","name":"ReinoWuxia","site":"http://www.reinowuxia.com/","lang":"español, castellano","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/spanish/novelawuxia.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/es/novelawuxia/icon.png"},{"id":"HasuTL","name":"Hasu Translations","site":"https://hasutl.wordpress.com/","lang":"español, castellano","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/spanish/hasutl.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/es/hasutl/icon.png"},{"id":"ligera.com","name":"Novelas Ligera","site":"https://novelasligera.com/","lang":"español, castellano","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/spanish/novelasligera.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/es/novelasligera/icon.png"},{"id":"oasisTL.wp","name":"Oasis Translations","site":"https://oasistranslations.wordpress.com/","lang":"español, castellano","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/spanish/oasistranslations.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/es/oasistranslations/icon.png"},{"id":"skynovels.net","name":"SkyNovels","site":"https://www.skynovels.net/","lang":"español, castellano","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/spanish/skynovels.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/es/skynovels/icon.png"},{"id":"TNL.com","name":"TuNovelaLigera","site":"https://tunovelaligera.com/","lang":"español, castellano","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/spanish/tunovelaligera.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/es/tunovelaligera/icon.png"},{"id":"yuukitls.com","name":"Yuuki Tls","site":"https://yuukitls.com/","lang":"español, castellano","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/spanish/yukitls.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/es/yuukitls/icon.png"}],"Türkçe":[{"id":"epiknovel.com","name":"EpikNovel","site":"https://www.epiknovel.com/","lang":"Türkçe","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/turkish/epiknovel.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/tr/epiknovel/icon.png"},{"id":"lshnovel","name":"Liebe Schnee Hiver Novel","site":"https://lshnovel.com/","lang":"Türkçe","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/turkish/lshnovel.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/multisrc/wpmangastream/icons/lshnovel.png"}],"Tiếng Việt":[{"id":"ln.hako","name":"Hako","site":"https://ln.hako.vn","lang":"Tiếng Việt","version":"1.0.0","url":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/plugins/vietnamese/LNHako.js","iconUrl":"https://raw.githubusercontent.com/K1ngfish3r/plugins/reducing/icons/src/vi/hakolightnovel/icon.png"}]} \ No newline at end of file diff --git a/dist/SkillGG/plugins.json b/dist/SkillGG/plugins.json new file mode 100644 index 0000000..3d71295 --- /dev/null +++ b/dist/SkillGG/plugins.json @@ -0,0 +1,895 @@ +{ + "العربية": [ + { + "id": "ar-mtl", + "name": "ArMTL [madara]", + "site": "https://ar-mtl.club/", + "lang": "العربية", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/arabic/ArMTL[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/armtl.png" + }, + { + "id": "arnovel", + "name": "ArNovel [madara]", + "site": "https://arnovel.me/", + "lang": "العربية", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/arabic/ArNovel[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/arnovel.png" + }, + { + "id": "hizomanga", + "name": "HizoManga [madara]", + "site": "https://hizomanga.com/", + "lang": "العربية", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/arabic/HizoManga[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/hizomanga.png" + }, + { + "id": "kolnovel", + "name": "KolNovel", + "site": "https://kolnovel.com/", + "lang": "العربية", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/arabic/kolnovel.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/wpmangastream/icons/kolnovel.png" + }, + { + "id": "novel4up", + "name": "Novel4Up [madara]", + "site": "https://novel4up.com/", + "lang": "العربية", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/arabic/Novel4Up[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/novel4up.png" + }, + { + "id": "riwyat", + "name": "Riwyat [madara]", + "site": "https://riwyat.com/", + "lang": "العربية", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/arabic/Riwyat[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/riwyat.png" + }, + { + "id": "teamxnovel", + "name": "TeamXNovel [madara]", + "site": "https://teamxnovel.com/", + "lang": "العربية", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/arabic/TeamXNovel[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/teamxnovel.png" + } + ], + "English": [ + { + "id": "1stkissnovel", + "name": "FirstKissNovel [madara]", + "site": "https://1stkissnovel.org/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/FirstKissNovel[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/firstkissnovel.png" + }, + { + "id": "ANF.com", + "name": "AllNovelFull", + "site": "https://allnovelfull.com", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/allnovelfull.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/allnovelfull/icon.png" + }, + { + "id": "BLN.com", + "name": "BestLightNovel", + "site": "https://bestlightnovel.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/bestlightnovel.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/bestlightnovel/icon.png" + }, + { + "id": "boxnovel", + "name": "BoxNovel [madara]", + "site": "https://boxnovel.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/BoxNovel[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/boxnovel.png" + }, + { + "id": "clicknovel", + "name": "ClickNovel [madara]", + "site": "https://clicknovel.net/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/ClickNovel[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/clicknovel.png" + }, + { + "id": "comrademao", + "name": "Comrade Mao", + "site": "https://comrademao.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/comrademao.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/comrademao/icon.png" + }, + { + "id": "cratenovel", + "name": "CrateNovel [madara]", + "site": "https://cratenovel.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/CrateNovel[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/cratenovel.png" + }, + { + "id": "daonovel", + "name": "DaoNovel [madara]", + "site": "https://daonovel.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/DaoNovel[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/daonovel.png" + }, + { + "id": "DDL.com", + "name": "Divine Dao Library", + "site": "https://www.divinedaolibrary.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/divinedaolibrary.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/`en/divinedaolibrary/icon.png" + }, + { + "id": "earlynovel", + "name": "Early Novel", + "site": "https://earlynovel.net/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/earlynovel.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/latestnovel.png" + }, + { + "id": "fanTLS", + "name": "Fans Translations [madara]", + "site": "https://fanstranslations.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/FansTranslations[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/fanstranslations.png" + }, + { + "id": "fastnovel", + "name": "Fast Novel", + "site": "https://fastnovel.org/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/fastnovel.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/fastnovel/icon.png" + }, + { + "id": "foxaholic", + "name": "Foxaholic", + "site": "https://www.foxaholic.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/foxaholic.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/foxaholic/icon.png" + }, + { + "id": "freenovel.me", + "name": "FreeNovelMe [madara]", + "site": "https://freenovel.me/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/FreeNovelMe[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/freenovelme.png" + }, + { + "id": "freenovelupdates", + "name": "Free Novel Updates (Broken)", + "site": "https://www.freenovelupdates.com", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/freenovelupdates.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/freenovelupdates/icon.png" + }, + { + "id": "FWN.com", + "name": "Web NOVEL", + "site": "https://freewebnovel.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/freewebnovel.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/freewebnovel/icon.png" + }, + { + "id": "guavaread", + "name": "Guavaread [madara]", + "site": "https://guavaread.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/Guavaread[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/guavaread.png" + }, + { + "id": "lightnovelpub", + "name": "LightNovelPub", + "site": "https://www.lightnovelpub.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/lightnovelpub.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/lightnovelpub/icon.png" + }, + { + "id": "lnheaven", + "name": "LightNovelHeaven [madara]", + "site": "https://lightnovelheaven.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/LightNovelHeaven[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/lightnovelheaven.png" + }, + { + "id": "Ltnovel.com", + "name": "Ltnovel.com", + "site": "https://www.ltnovel.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/Ltnovelcom[readwn].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/" + }, + { + "id": "lunarletters", + "name": "LunarLetters [madara]", + "site": "https://www.lunarletters.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/LunarLetters[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/lunarletters.png" + }, + { + "id": "mostnovel", + "name": "MostNovel [madara]", + "site": "https://mostnovel.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/MostNovel[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/mostnovel.png" + }, + { + "id": "mtl-novel", + "name": "MTL-Novel [madara]", + "site": "https://mtl-novel.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/MTLNovel[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/mtl-novel.png" + }, + { + "id": "mtlnovel.club", + "name": "MTLNovel.Club [madara]", + "site": "https://mtlnovel.club/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/MTLNovelClub[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/mtlnovel.club.png" + }, + { + "id": "mtlreader", + "name": "MTL Reader", + "site": "https://mtlreader.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/mtlreader.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/mtlreader/icon.png" + }, + { + "id": "mysticalmerries", + "name": "MysticalSeries [madara]", + "site": "https://mysticalmerries.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/MysticalSeries[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/mysticalseries.png" + }, + { + "id": "neosekaiTLS", + "name": "NeoSekai Translations [madara]", + "site": "https://www.neosekaitranslations.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/NeoSekaiTranslations[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/neosekaitranslations.png" + }, + { + "id": "NF.me", + "name": "NovelFull.me", + "site": "https://novelfull.me/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/NovelFullMe.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/novelfullme/icon.png" + }, + { + "id": "nitroscans", + "name": "Nitroscans", + "site": "https://nitroscans.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/nitroscans.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/nitroscans/icon.png" + }, + { + "id": "NO.net", + "name": "novelsOnline", + "site": "https://novelsonline.net", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/NovelOnline.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/coverNotAvailable.jpg" + }, + { + "id": "NobleMTL", + "name": "NobleMTL", + "site": "https://www.noblemtl.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/nobleMTL.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/noblemtl/icon.png" + }, + { + "id": "nocturneTLS", + "name": "Nocturne Translations [madara]", + "site": "https://nocturnetls.net/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/NocturneTranslations[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/nocturnetranslations.png" + }, + { + "id": "noobchanTL", + "name": "Noobchan Translation [madara]", + "site": "https://noobchan.xyz/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/NoobchanTranslation[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/noobchantranslation.png" + }, + { + "id": "novelforest", + "name": "Novel Forest (Broken)", + "site": "https://novelforest.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/novelforest.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/novelforest/icon.png" + }, + { + "id": "novelfull", + "name": "NovelFull", + "site": "https://novelfull.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/novelfull.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/novelfull/icon.png" + }, + { + "id": "novelhold", + "name": "Novel Hold", + "site": "https://novelhold.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/novelhold.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/novelhold/icon.png" + }, + { + "id": "Novelmt.com", + "name": "Novelmt.com", + "site": "https://www.novelmt.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/Novelmtcom[readwn].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/" + }, + { + "id": "novelmultiverse", + "name": "NovelMultiverse [madara]", + "site": "https://www.novelmultiverse.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/NovelMultiverse[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/novelmultiverse.png" + }, + { + "id": "novelpub.com", + "name": "NovelPub", + "site": "https://www.novelpub.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/novelpub.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/novelpub/icon.png" + }, + { + "id": "novelr18", + "name": "NovelR18 [madara]", + "site": "https://novelr18.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/NovelR18[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/novelr18.png" + }, + { + "id": "novelroom", + "name": "Novelroom.net [madara]", + "site": "https://novelroom.net/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/Novelroomnet[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/novelroom.net.png" + }, + { + "id": "novelstic", + "name": "Novelstic [madara]", + "site": "https://novelstic.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/Novelstic[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/novelstic.png" + }, + { + "id": "novelTL", + "name": "NovelTranslate [madara]", + "site": "https://noveltranslate.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/NovelTranslate[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/noveltranslate.png" + }, + { + "id": "onlymtl", + "name": "OnlyMTL [madara]", + "site": "https://www.onlymtl.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/OnlyMTL[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/onlymtl.png" + }, + { + "id": "PandaMTL", + "name": "PandaMTL", + "site": "https://www.pandamtl.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/pandaMTL.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/wordpress/icon.png" + }, + { + "id": "readwebnovels", + "name": "ReadWebNovels [madara]", + "site": "https://readwebnovels.net/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/ReadWebNovels[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/readwebnovels.png" + }, + { + "id": "Readwn.com", + "name": "Readwn.com", + "site": "https://www.readwn.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/Readwncom[readwn].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/" + }, + { + "id": "sleeptTLS", + "name": "SleepyTranslations [madara]", + "site": "https://sleepytranslations.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/SleepyTranslations[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/sleepytranslations.png" + }, + { + "id": "sonicmtl", + "name": "SonicMTL [madara]", + "site": "https://www.sonicmtl.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/SonicMTL[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/sonicmtl.png" + }, + { + "id": "sugarbbscan", + "name": "Sugar Babies [madara]", + "site": "https://sugarbbscan.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/SugarBabies[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/sugarbabies.png" + }, + { + "id": "sweetEscapeTL", + "name": "Sweet Escape Translations [madara]", + "site": "https://sweetescapetranslations.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/SweetEscapeTranslations[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/sweetescapetranslations.png" + }, + { + "id": "webnovelover", + "name": "WebNovelLover [madara]", + "site": "https://www.webnovelover.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/WebNovelLover[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/webnovellover.png" + }, + { + "id": "wuxiaworld.site", + "name": "WuxiaWorld.Site [madara]", + "site": "https://wuxiaworld.site/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/WuxiaWorldSite[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/wuxiaworld.site.png" + }, + { + "id": "zetroTL", + "name": "Zetro Translation [madara]", + "site": "https://zetrotranslation.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/ZetroTranslation[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/zetrotranslation.png" + }, + { + "id": "zinnovel", + "name": "ZinnNovel [madara]", + "site": "https://zinnovel.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/ZinnNovel[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/zinnnovel.png" + } + ], + "français, française": [ + { + "id": "chireads.com", + "name": "Chireads", + "site": "https://chireads.com/", + "lang": "français, française", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/french/chireads.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/fr/chireads/icon.png" + } + ], + "Bahasa Indonesia": [ + { + "id": "IDWN.id", + "name": "IndoWebNovel", + "site": "https://indowebnovel.id/id/", + "lang": "Bahasa Indonesia", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/indonesian/indowebnovel.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/id/indowebnovel/icon.png" + }, + { + "id": "meionovel", + "name": "MeioNovel [madara]", + "site": "https://meionovel.id/", + "lang": "Bahasa Indonesia", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/indonesian/MeioNovel[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/meionovel.png" + }, + { + "id": "morenovel", + "name": "MoreNovel [madara]", + "site": "https://morenovel.net/", + "lang": "Bahasa Indonesia", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/indonesian/MoreNovel[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/morenovel.png" + }, + { + "id": "novelringan.com", + "name": "NovelRingan", + "site": "https://novelringan.com/", + "lang": "Bahasa Indonesia", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/indonesian/novelringan.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/id/novelringan/icon.png" + }, + { + "id": "sakura.id", + "name": "SakuraNovel", + "site": "https://sakuranovel.id/", + "lang": "Bahasa Indonesia", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/indonesian/sakuranovel.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/id/sakuranovel/icon.png" + }, + { + "id": "wbnovel", + "name": "WBNovel [madara]", + "site": "https://wbnovel.com/", + "lang": "Bahasa Indonesia", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/indonesian/WBNovel[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/wbnovel.png" + } + ], + "日本語": [ + { + "id": "yomou.syosetu", + "name": "Syosetu", + "site": "https//yomou.syosetu.com/", + "lang": "日本語", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/japanese/Syosetu.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/jp/syosetu/icon.png" + } + ], + "Português": [ + { + "id": "lightnovelbrasil", + "name": "Light Novel Brasil", + "site": "https://lightnovelbrasil.com/", + "lang": "Português", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/portuguese/lightnovelbrasil.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/wpmangastream/icons/lightnovelbrasil.png" + } + ], + "русский язык": [ + { + "id": "axel.znovel", + "name": "Аксел", + "site": "https://axel.znovel.space", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/axelznovelspace[noveltl].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/" + }, + { + "id": "bel.novel", + "name": "Белманга", + "site": "https://bel.novel.tl", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/belnoveltl[noveltl].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/" + }, + { + "id": "erolate", + "name": "Erolate", + "site": "https://erolate.com", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/Erolate[rulate].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/" + }, + { + "id": "jaomix.ru", + "name": "Jaomix", + "site": "https://jaomix.ru", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/jaomix.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/ru/jaomix/icon.png" + }, + { + "id": "japit.novel", + "name": "Japit Comics", + "site": "https://japit.novel.tl", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/japitnoveltl[noveltl].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/" + }, + { + "id": "kg.novel", + "name": "Карманная галактика", + "site": "https://kg.novel.tl", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/kgnoveltl[noveltl].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/" + }, + { + "id": "kod.novel", + "name": "K.O.D.", + "site": "https://kod.novel.tl", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/kodnoveltl[noveltl].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/" + }, + { + "id": "nightnovel", + "name": "Nightnovel", + "site": "https://nightnovel.online", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/nightnovelonline[noveltl].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/" + }, + { + "id": "original.novel", + "name": "Авторские работы", + "site": "https://original.novel.tl", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/originalnoveltl[noveltl].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/" + }, + { + "id": "red.novel", + "name": "Север и Юг", + "site": "https://red.novel.tl", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/rednoveltl[noveltl].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/" + }, + { + "id": "ruranobe", + "name": "РуРанобэ", + "site": "https://ruranobe.ru", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/ruranoberu[noveltl].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/" + }, + { + "id": "sferdrakon.novel", + "name": "Уголок дракона", + "site": "https://sferdrakon.novel.tl", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/sferdrakonnoveltl[noveltl].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/" + }, + { + "id": "si.novel", + "name": "Сказочные истории", + "site": "https://si.novel.tl", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/sinoveltl[noveltl].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/" + }, + { + "id": "snailulitka.novel", + "name": "Snailulitka", + "site": "https://snailulitka.novel.tl", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/snailulitkanoveltl[noveltl].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/" + }, + { + "id": "tl.rulate", + "name": "Rulate", + "site": "https://tl.rulate.ru", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/Rulate[rulate].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/" + }, + { + "id": "tsundoku.novel", + "name": "TsundokuTrans", + "site": "https://tsundoku.novel.tl", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/tsundokunoveltl[noveltl].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/" + }, + { + "id": "ukr.novel", + "name": "Переклади українською мовою", + "site": "https://ukr.novel.tl", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/ukrnoveltl[noveltl].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/" + } + ], + "español, castellano": [ + { + "id": "HasuTL", + "name": "Hasu Translations", + "site": "https://hasutl.wordpress.com/", + "lang": "español, castellano", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/spanish/hasutl.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/es/hasutl/icon.png" + }, + { + "id": "ligera.com", + "name": "Novelas Ligera", + "site": "https://novelasligera.com/", + "lang": "español, castellano", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/spanish/novelasligera.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/es/novelasligera/icon.png" + }, + { + "id": "TNL.com", + "name": "TuNovelaLigera", + "site": "https://tunovelaligera.com/", + "lang": "español, castellano", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/spanish/tunovelaligera.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/es/tunovelaligera/icon.png" + }, + { + "id": "yuukitls.com", + "name": "Yuuki Tls", + "site": "https://yuukitls.com/", + "lang": "español, castellano", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/spanish/yukitls.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/es/yuukitls/icon.png" + } + ], + "Türkçe": [ + { + "id": "epiknovel.com", + "name": "EpikNovel", + "site": "https://www.epiknovel.com/", + "lang": "Türkçe", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/turkish/epiknovel.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/tr/epiknovel/icon.png" + }, + { + "id": "lshnovel", + "name": "Liebe Schnee Hiver Novel", + "site": "https://lshnovel.com/", + "lang": "Türkçe", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/turkish/lshnovel.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/wpmangastream/icons/lshnovel.png" + }, + { + "id": "turkcelightnovels", + "name": "TurkceLightNovels [madara]", + "site": "https://turkcelightnovels.com/", + "lang": "Türkçe", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/turkish/TurkceLightNovels[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/turkcelightnovels.png" + }, + { + "id": "webnoveloku", + "name": "WebNovelOku [madara]", + "site": "https://www.webnoveloku.com/", + "lang": "Türkçe", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/turkish/WebNovelOku[madara].js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/webnoveloku.png" + } + ], + "Tiếng Việt": [ + { + "id": "ln.hako", + "name": "Hako", + "site": "https://ln.hako.vn", + "lang": "Tiếng Việt", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/vietnamese/LNHako.js", + "iconUrl": "https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/vi/hakolightnovel/icon.png" + } + ] +} \ No newline at end of file diff --git a/dist/SkillGG/plugins.min.json b/dist/SkillGG/plugins.min.json new file mode 100644 index 0000000..6d9b7cf --- /dev/null +++ b/dist/SkillGG/plugins.min.json @@ -0,0 +1 @@ +{"العربية":[{"id":"ar-mtl","name":"ArMTL [madara]","site":"https://ar-mtl.club/","lang":"العربية","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/arabic/ArMTL[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/armtl.png"},{"id":"arnovel","name":"ArNovel [madara]","site":"https://arnovel.me/","lang":"العربية","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/arabic/ArNovel[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/arnovel.png"},{"id":"hizomanga","name":"HizoManga [madara]","site":"https://hizomanga.com/","lang":"العربية","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/arabic/HizoManga[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/hizomanga.png"},{"id":"kolnovel","name":"KolNovel","site":"https://kolnovel.com/","lang":"العربية","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/arabic/kolnovel.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/wpmangastream/icons/kolnovel.png"},{"id":"novel4up","name":"Novel4Up [madara]","site":"https://novel4up.com/","lang":"العربية","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/arabic/Novel4Up[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/novel4up.png"},{"id":"riwyat","name":"Riwyat [madara]","site":"https://riwyat.com/","lang":"العربية","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/arabic/Riwyat[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/riwyat.png"},{"id":"teamxnovel","name":"TeamXNovel [madara]","site":"https://teamxnovel.com/","lang":"العربية","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/arabic/TeamXNovel[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/teamxnovel.png"}],"English":[{"id":"1stkissnovel","name":"FirstKissNovel [madara]","site":"https://1stkissnovel.org/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/FirstKissNovel[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/firstkissnovel.png"},{"id":"ANF.com","name":"AllNovelFull","site":"https://allnovelfull.com","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/allnovelfull.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/allnovelfull/icon.png"},{"id":"BLN.com","name":"BestLightNovel","site":"https://bestlightnovel.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/bestlightnovel.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/bestlightnovel/icon.png"},{"id":"boxnovel","name":"BoxNovel [madara]","site":"https://boxnovel.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/BoxNovel[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/boxnovel.png"},{"id":"clicknovel","name":"ClickNovel [madara]","site":"https://clicknovel.net/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/ClickNovel[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/clicknovel.png"},{"id":"comrademao","name":"Comrade Mao","site":"https://comrademao.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/comrademao.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/comrademao/icon.png"},{"id":"cratenovel","name":"CrateNovel [madara]","site":"https://cratenovel.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/CrateNovel[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/cratenovel.png"},{"id":"daonovel","name":"DaoNovel [madara]","site":"https://daonovel.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/DaoNovel[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/daonovel.png"},{"id":"DDL.com","name":"Divine Dao Library","site":"https://www.divinedaolibrary.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/divinedaolibrary.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/`en/divinedaolibrary/icon.png"},{"id":"earlynovel","name":"Early Novel","site":"https://earlynovel.net/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/earlynovel.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/latestnovel.png"},{"id":"fanTLS","name":"Fans Translations [madara]","site":"https://fanstranslations.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/FansTranslations[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/fanstranslations.png"},{"id":"fastnovel","name":"Fast Novel","site":"https://fastnovel.org/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/fastnovel.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/fastnovel/icon.png"},{"id":"foxaholic","name":"Foxaholic","site":"https://www.foxaholic.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/foxaholic.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/foxaholic/icon.png"},{"id":"freenovel.me","name":"FreeNovelMe [madara]","site":"https://freenovel.me/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/FreeNovelMe[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/freenovelme.png"},{"id":"freenovelupdates","name":"Free Novel Updates (Broken)","site":"https://www.freenovelupdates.com","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/freenovelupdates.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/freenovelupdates/icon.png"},{"id":"FWN.com","name":"Web NOVEL","site":"https://freewebnovel.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/freewebnovel.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/freewebnovel/icon.png"},{"id":"guavaread","name":"Guavaread [madara]","site":"https://guavaread.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/Guavaread[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/guavaread.png"},{"id":"lightnovelpub","name":"LightNovelPub","site":"https://www.lightnovelpub.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/lightnovelpub.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/lightnovelpub/icon.png"},{"id":"lnheaven","name":"LightNovelHeaven [madara]","site":"https://lightnovelheaven.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/LightNovelHeaven[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/lightnovelheaven.png"},{"id":"Ltnovel.com","name":"Ltnovel.com","site":"https://www.ltnovel.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/Ltnovelcom[readwn].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/"},{"id":"lunarletters","name":"LunarLetters [madara]","site":"https://www.lunarletters.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/LunarLetters[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/lunarletters.png"},{"id":"mostnovel","name":"MostNovel [madara]","site":"https://mostnovel.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/MostNovel[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/mostnovel.png"},{"id":"mtl-novel","name":"MTL-Novel [madara]","site":"https://mtl-novel.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/MTLNovel[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/mtl-novel.png"},{"id":"mtlnovel.club","name":"MTLNovel.Club [madara]","site":"https://mtlnovel.club/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/MTLNovelClub[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/mtlnovel.club.png"},{"id":"mtlreader","name":"MTL Reader","site":"https://mtlreader.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/mtlreader.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/mtlreader/icon.png"},{"id":"mysticalmerries","name":"MysticalSeries [madara]","site":"https://mysticalmerries.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/MysticalSeries[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/mysticalseries.png"},{"id":"neosekaiTLS","name":"NeoSekai Translations [madara]","site":"https://www.neosekaitranslations.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/NeoSekaiTranslations[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/neosekaitranslations.png"},{"id":"NF.me","name":"NovelFull.me","site":"https://novelfull.me/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/NovelFullMe.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/novelfullme/icon.png"},{"id":"nitroscans","name":"Nitroscans","site":"https://nitroscans.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/nitroscans.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/nitroscans/icon.png"},{"id":"NO.net","name":"novelsOnline","site":"https://novelsonline.net","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/NovelOnline.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/coverNotAvailable.jpg"},{"id":"NobleMTL","name":"NobleMTL","site":"https://www.noblemtl.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/nobleMTL.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/noblemtl/icon.png"},{"id":"nocturneTLS","name":"Nocturne Translations [madara]","site":"https://nocturnetls.net/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/NocturneTranslations[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/nocturnetranslations.png"},{"id":"noobchanTL","name":"Noobchan Translation [madara]","site":"https://noobchan.xyz/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/NoobchanTranslation[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/noobchantranslation.png"},{"id":"novelforest","name":"Novel Forest (Broken)","site":"https://novelforest.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/novelforest.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/novelforest/icon.png"},{"id":"novelfull","name":"NovelFull","site":"https://novelfull.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/novelfull.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/novelfull/icon.png"},{"id":"novelhold","name":"Novel Hold","site":"https://novelhold.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/novelhold.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/novelhold/icon.png"},{"id":"Novelmt.com","name":"Novelmt.com","site":"https://www.novelmt.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/Novelmtcom[readwn].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/"},{"id":"novelmultiverse","name":"NovelMultiverse [madara]","site":"https://www.novelmultiverse.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/NovelMultiverse[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/novelmultiverse.png"},{"id":"novelpub.com","name":"NovelPub","site":"https://www.novelpub.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/novelpub.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/novelpub/icon.png"},{"id":"novelr18","name":"NovelR18 [madara]","site":"https://novelr18.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/NovelR18[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/novelr18.png"},{"id":"novelroom","name":"Novelroom.net [madara]","site":"https://novelroom.net/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/Novelroomnet[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/novelroom.net.png"},{"id":"novelstic","name":"Novelstic [madara]","site":"https://novelstic.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/Novelstic[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/novelstic.png"},{"id":"novelTL","name":"NovelTranslate [madara]","site":"https://noveltranslate.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/NovelTranslate[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/noveltranslate.png"},{"id":"onlymtl","name":"OnlyMTL [madara]","site":"https://www.onlymtl.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/OnlyMTL[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/onlymtl.png"},{"id":"PandaMTL","name":"PandaMTL","site":"https://www.pandamtl.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/pandaMTL.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/en/wordpress/icon.png"},{"id":"readwebnovels","name":"ReadWebNovels [madara]","site":"https://readwebnovels.net/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/ReadWebNovels[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/readwebnovels.png"},{"id":"Readwn.com","name":"Readwn.com","site":"https://www.readwn.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/Readwncom[readwn].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/"},{"id":"sleeptTLS","name":"SleepyTranslations [madara]","site":"https://sleepytranslations.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/SleepyTranslations[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/sleepytranslations.png"},{"id":"sonicmtl","name":"SonicMTL [madara]","site":"https://www.sonicmtl.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/SonicMTL[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/sonicmtl.png"},{"id":"sugarbbscan","name":"Sugar Babies [madara]","site":"https://sugarbbscan.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/SugarBabies[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/sugarbabies.png"},{"id":"sweetEscapeTL","name":"Sweet Escape Translations [madara]","site":"https://sweetescapetranslations.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/SweetEscapeTranslations[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/sweetescapetranslations.png"},{"id":"webnovelover","name":"WebNovelLover [madara]","site":"https://www.webnovelover.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/WebNovelLover[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/webnovellover.png"},{"id":"wuxiaworld.site","name":"WuxiaWorld.Site [madara]","site":"https://wuxiaworld.site/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/WuxiaWorldSite[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/wuxiaworld.site.png"},{"id":"zetroTL","name":"Zetro Translation [madara]","site":"https://zetrotranslation.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/ZetroTranslation[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/zetrotranslation.png"},{"id":"zinnovel","name":"ZinnNovel [madara]","site":"https://zinnovel.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/english/ZinnNovel[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/zinnnovel.png"}],"français, française":[{"id":"chireads.com","name":"Chireads","site":"https://chireads.com/","lang":"français, française","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/french/chireads.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/fr/chireads/icon.png"}],"Bahasa Indonesia":[{"id":"IDWN.id","name":"IndoWebNovel","site":"https://indowebnovel.id/id/","lang":"Bahasa Indonesia","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/indonesian/indowebnovel.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/id/indowebnovel/icon.png"},{"id":"meionovel","name":"MeioNovel [madara]","site":"https://meionovel.id/","lang":"Bahasa Indonesia","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/indonesian/MeioNovel[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/meionovel.png"},{"id":"morenovel","name":"MoreNovel [madara]","site":"https://morenovel.net/","lang":"Bahasa Indonesia","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/indonesian/MoreNovel[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/morenovel.png"},{"id":"novelringan.com","name":"NovelRingan","site":"https://novelringan.com/","lang":"Bahasa Indonesia","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/indonesian/novelringan.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/id/novelringan/icon.png"},{"id":"sakura.id","name":"SakuraNovel","site":"https://sakuranovel.id/","lang":"Bahasa Indonesia","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/indonesian/sakuranovel.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/id/sakuranovel/icon.png"},{"id":"wbnovel","name":"WBNovel [madara]","site":"https://wbnovel.com/","lang":"Bahasa Indonesia","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/indonesian/WBNovel[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/wbnovel.png"}],"日本語":[{"id":"yomou.syosetu","name":"Syosetu","site":"https//yomou.syosetu.com/","lang":"日本語","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/japanese/Syosetu.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/jp/syosetu/icon.png"}],"Português":[{"id":"lightnovelbrasil","name":"Light Novel Brasil","site":"https://lightnovelbrasil.com/","lang":"Português","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/portuguese/lightnovelbrasil.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/wpmangastream/icons/lightnovelbrasil.png"}],"русский язык":[{"id":"axel.znovel","name":"Аксел","site":"https://axel.znovel.space","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/axelznovelspace[noveltl].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/"},{"id":"bel.novel","name":"Белманга","site":"https://bel.novel.tl","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/belnoveltl[noveltl].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/"},{"id":"erolate","name":"Erolate","site":"https://erolate.com","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/Erolate[rulate].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/"},{"id":"jaomix.ru","name":"Jaomix","site":"https://jaomix.ru","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/jaomix.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/ru/jaomix/icon.png"},{"id":"japit.novel","name":"Japit Comics","site":"https://japit.novel.tl","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/japitnoveltl[noveltl].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/"},{"id":"kg.novel","name":"Карманная галактика","site":"https://kg.novel.tl","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/kgnoveltl[noveltl].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/"},{"id":"kod.novel","name":"K.O.D.","site":"https://kod.novel.tl","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/kodnoveltl[noveltl].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/"},{"id":"nightnovel","name":"Nightnovel","site":"https://nightnovel.online","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/nightnovelonline[noveltl].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/"},{"id":"original.novel","name":"Авторские работы","site":"https://original.novel.tl","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/originalnoveltl[noveltl].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/"},{"id":"red.novel","name":"Север и Юг","site":"https://red.novel.tl","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/rednoveltl[noveltl].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/"},{"id":"ruranobe","name":"РуРанобэ","site":"https://ruranobe.ru","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/ruranoberu[noveltl].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/"},{"id":"sferdrakon.novel","name":"Уголок дракона","site":"https://sferdrakon.novel.tl","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/sferdrakonnoveltl[noveltl].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/"},{"id":"si.novel","name":"Сказочные истории","site":"https://si.novel.tl","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/sinoveltl[noveltl].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/"},{"id":"snailulitka.novel","name":"Snailulitka","site":"https://snailulitka.novel.tl","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/snailulitkanoveltl[noveltl].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/"},{"id":"tl.rulate","name":"Rulate","site":"https://tl.rulate.ru","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/Rulate[rulate].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/"},{"id":"tsundoku.novel","name":"TsundokuTrans","site":"https://tsundoku.novel.tl","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/tsundokunoveltl[noveltl].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/"},{"id":"ukr.novel","name":"Переклади українською мовою","site":"https://ukr.novel.tl","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/russian/ukrnoveltl[noveltl].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/"}],"español, castellano":[{"id":"HasuTL","name":"Hasu Translations","site":"https://hasutl.wordpress.com/","lang":"español, castellano","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/spanish/hasutl.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/es/hasutl/icon.png"},{"id":"ligera.com","name":"Novelas Ligera","site":"https://novelasligera.com/","lang":"español, castellano","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/spanish/novelasligera.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/es/novelasligera/icon.png"},{"id":"TNL.com","name":"TuNovelaLigera","site":"https://tunovelaligera.com/","lang":"español, castellano","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/spanish/tunovelaligera.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/es/tunovelaligera/icon.png"},{"id":"yuukitls.com","name":"Yuuki Tls","site":"https://yuukitls.com/","lang":"español, castellano","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/spanish/yukitls.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/es/yuukitls/icon.png"}],"Türkçe":[{"id":"epiknovel.com","name":"EpikNovel","site":"https://www.epiknovel.com/","lang":"Türkçe","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/turkish/epiknovel.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/tr/epiknovel/icon.png"},{"id":"lshnovel","name":"Liebe Schnee Hiver Novel","site":"https://lshnovel.com/","lang":"Türkçe","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/turkish/lshnovel.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/wpmangastream/icons/lshnovel.png"},{"id":"turkcelightnovels","name":"TurkceLightNovels [madara]","site":"https://turkcelightnovels.com/","lang":"Türkçe","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/turkish/TurkceLightNovels[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/turkcelightnovels.png"},{"id":"webnoveloku","name":"WebNovelOku [madara]","site":"https://www.webnoveloku.com/","lang":"Türkçe","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/turkish/WebNovelOku[madara].js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/multisrc/madara/icons/webnoveloku.png"}],"Tiếng Việt":[{"id":"ln.hako","name":"Hako","site":"https://ln.hako.vn","lang":"Tiếng Việt","version":"1.0.0","url":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/js/plugins/vietnamese/LNHako.js","iconUrl":"https://raw.githubusercontent.com/SkillGG/lnreader-plugins/ts-support/icons/src/vi/hakolightnovel/icon.png"}]} \ No newline at end of file diff --git a/dist/nyagami/plugins.json b/dist/nyagami/plugins.json new file mode 100644 index 0000000..20612fe --- /dev/null +++ b/dist/nyagami/plugins.json @@ -0,0 +1,895 @@ +{ + "العربية": [ + { + "id": "ar-mtl", + "name": "ArMTL [madara]", + "site": "https://ar-mtl.club/", + "lang": "العربية", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/arabic/ArMTL[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/armtl.png" + }, + { + "id": "arnovel", + "name": "ArNovel [madara]", + "site": "https://arnovel.me/", + "lang": "العربية", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/arabic/ArNovel[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/arnovel.png" + }, + { + "id": "hizomanga", + "name": "HizoManga [madara]", + "site": "https://hizomanga.com/", + "lang": "العربية", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/arabic/HizoManga[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/hizomanga.png" + }, + { + "id": "kolnovel", + "name": "KolNovel", + "site": "https://kolnovel.com/", + "lang": "العربية", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/arabic/kolnovel.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/wpmangastream/icons/kolnovel.png" + }, + { + "id": "novel4up", + "name": "Novel4Up [madara]", + "site": "https://novel4up.com/", + "lang": "العربية", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/arabic/Novel4Up[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/novel4up.png" + }, + { + "id": "riwyat", + "name": "Riwyat [madara]", + "site": "https://riwyat.com/", + "lang": "العربية", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/arabic/Riwyat[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/riwyat.png" + }, + { + "id": "teamxnovel", + "name": "TeamXNovel [madara]", + "site": "https://teamxnovel.com/", + "lang": "العربية", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/arabic/TeamXNovel[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/teamxnovel.png" + } + ], + "English": [ + { + "id": "1stkissnovel", + "name": "FirstKissNovel [madara]", + "site": "https://1stkissnovel.org/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/FirstKissNovel[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/firstkissnovel.png" + }, + { + "id": "ANF.com", + "name": "AllNovelFull", + "site": "https://allnovelfull.com", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/allnovelfull.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/allnovelfull/icon.png" + }, + { + "id": "BLN.com", + "name": "BestLightNovel", + "site": "https://bestlightnovel.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/bestlightnovel.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/bestlightnovel/icon.png" + }, + { + "id": "boxnovel", + "name": "BoxNovel [madara]", + "site": "https://boxnovel.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/BoxNovel[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/boxnovel.png" + }, + { + "id": "clicknovel", + "name": "ClickNovel [madara]", + "site": "https://clicknovel.net/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/ClickNovel[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/clicknovel.png" + }, + { + "id": "comrademao", + "name": "Comrade Mao", + "site": "https://comrademao.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/comrademao.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/comrademao/icon.png" + }, + { + "id": "cratenovel", + "name": "CrateNovel [madara]", + "site": "https://cratenovel.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/CrateNovel[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/cratenovel.png" + }, + { + "id": "daonovel", + "name": "DaoNovel [madara]", + "site": "https://daonovel.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/DaoNovel[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/daonovel.png" + }, + { + "id": "DDL.com", + "name": "Divine Dao Library", + "site": "https://www.divinedaolibrary.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/divinedaolibrary.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/`en/divinedaolibrary/icon.png" + }, + { + "id": "earlynovel", + "name": "Early Novel", + "site": "https://earlynovel.net/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/earlynovel.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/latestnovel.png" + }, + { + "id": "fanTLS", + "name": "Fans Translations [madara]", + "site": "https://fanstranslations.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/FansTranslations[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/fanstranslations.png" + }, + { + "id": "fastnovel", + "name": "Fast Novel", + "site": "https://fastnovel.org/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/fastnovel.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/fastnovel/icon.png" + }, + { + "id": "foxaholic", + "name": "Foxaholic", + "site": "https://www.foxaholic.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/foxaholic.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/foxaholic/icon.png" + }, + { + "id": "freenovel.me", + "name": "FreeNovelMe [madara]", + "site": "https://freenovel.me/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/FreeNovelMe[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/freenovelme.png" + }, + { + "id": "freenovelupdates", + "name": "Free Novel Updates (Broken)", + "site": "https://www.freenovelupdates.com", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/freenovelupdates.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/freenovelupdates/icon.png" + }, + { + "id": "FWN.com", + "name": "Web NOVEL", + "site": "https://freewebnovel.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/freewebnovel.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/freewebnovel/icon.png" + }, + { + "id": "guavaread", + "name": "Guavaread [madara]", + "site": "https://guavaread.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/Guavaread[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/guavaread.png" + }, + { + "id": "lightnovelpub", + "name": "LightNovelPub", + "site": "https://www.lightnovelpub.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/lightnovelpub.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/lightnovelpub/icon.png" + }, + { + "id": "lnheaven", + "name": "LightNovelHeaven [madara]", + "site": "https://lightnovelheaven.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/LightNovelHeaven[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/lightnovelheaven.png" + }, + { + "id": "Ltnovel.com", + "name": "Ltnovel.com", + "site": "https://www.ltnovel.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/Ltnovelcom[readwn].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/readwn/icon/ltnovel.png" + }, + { + "id": "lunarletters", + "name": "LunarLetters [madara]", + "site": "https://www.lunarletters.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/LunarLetters[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/lunarletters.png" + }, + { + "id": "mostnovel", + "name": "MostNovel [madara]", + "site": "https://mostnovel.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/MostNovel[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/mostnovel.png" + }, + { + "id": "mtl-novel", + "name": "MTL-Novel [madara]", + "site": "https://mtl-novel.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/MTLNovel[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/mtl-novel.png" + }, + { + "id": "mtlnovel.club", + "name": "MTLNovel.Club [madara]", + "site": "https://mtlnovel.club/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/MTLNovelClub[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/mtlnovel.club.png" + }, + { + "id": "mtlreader", + "name": "MTL Reader", + "site": "https://mtlreader.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/mtlreader.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/mtlreader/icon.png" + }, + { + "id": "mysticalmerries", + "name": "MysticalSeries [madara]", + "site": "https://mysticalmerries.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/MysticalSeries[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/mysticalseries.png" + }, + { + "id": "neosekaiTLS", + "name": "NeoSekai Translations [madara]", + "site": "https://www.neosekaitranslations.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/NeoSekaiTranslations[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/neosekaitranslations.png" + }, + { + "id": "NF.me", + "name": "NovelFull.me", + "site": "https://novelfull.me/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/NovelFullMe.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/novelfullme/icon.png" + }, + { + "id": "nitroscans", + "name": "Nitroscans", + "site": "https://nitroscans.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/nitroscans.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/nitroscans/icon.png" + }, + { + "id": "NO.net", + "name": "novelsOnline", + "site": "https://novelsonline.net", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/NovelOnline.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/coverNotAvailable.jpg" + }, + { + "id": "NobleMTL", + "name": "NobleMTL", + "site": "https://www.noblemtl.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/nobleMTL.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/noblemtl/icon.png" + }, + { + "id": "nocturneTLS", + "name": "Nocturne Translations [madara]", + "site": "https://nocturnetls.net/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/NocturneTranslations[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/nocturnetranslations.png" + }, + { + "id": "noobchanTL", + "name": "Noobchan Translation [madara]", + "site": "https://noobchan.xyz/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/NoobchanTranslation[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/noobchantranslation.png" + }, + { + "id": "novelforest", + "name": "Novel Forest (Broken)", + "site": "https://novelforest.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/novelforest.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/novelforest/icon.png" + }, + { + "id": "novelfull", + "name": "NovelFull", + "site": "https://novelfull.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/novelfull.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/novelfull/icon.png" + }, + { + "id": "novelhold", + "name": "Novel Hold", + "site": "https://novelhold.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/novelhold.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/novelhold/icon.png" + }, + { + "id": "Novelmt.com", + "name": "Novelmt.com", + "site": "https://www.novelmt.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/Novelmtcom[readwn].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/readwn/icon/novelmt.png" + }, + { + "id": "novelmultiverse", + "name": "NovelMultiverse [madara]", + "site": "https://www.novelmultiverse.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/NovelMultiverse[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/novelmultiverse.png" + }, + { + "id": "novelpub.com", + "name": "NovelPub", + "site": "https://www.novelpub.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/novelpub.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/novelpub/icon.png" + }, + { + "id": "novelr18", + "name": "NovelR18 [madara]", + "site": "https://novelr18.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/NovelR18[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/novelr18.png" + }, + { + "id": "novelroom", + "name": "Novelroom.net [madara]", + "site": "https://novelroom.net/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/Novelroomnet[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/novelroom.net.png" + }, + { + "id": "novelstic", + "name": "Novelstic [madara]", + "site": "https://novelstic.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/Novelstic[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/novelstic.png" + }, + { + "id": "novelTL", + "name": "NovelTranslate [madara]", + "site": "https://noveltranslate.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/NovelTranslate[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/noveltranslate.png" + }, + { + "id": "onlymtl", + "name": "OnlyMTL [madara]", + "site": "https://www.onlymtl.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/OnlyMTL[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/onlymtl.png" + }, + { + "id": "PandaMTL", + "name": "PandaMTL", + "site": "https://www.pandamtl.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/pandaMTL.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/wordpress/icon.png" + }, + { + "id": "readwebnovels", + "name": "ReadWebNovels [madara]", + "site": "https://readwebnovels.net/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/ReadWebNovels[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/readwebnovels.png" + }, + { + "id": "Readwn.com", + "name": "Readwn.com", + "site": "https://www.readwn.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/Readwncom[readwn].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/readwn/icon/readwn.png" + }, + { + "id": "sleeptTLS", + "name": "SleepyTranslations [madara]", + "site": "https://sleepytranslations.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/SleepyTranslations[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/sleepytranslations.png" + }, + { + "id": "sonicmtl", + "name": "SonicMTL [madara]", + "site": "https://www.sonicmtl.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/SonicMTL[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/sonicmtl.png" + }, + { + "id": "sugarbbscan", + "name": "Sugar Babies [madara]", + "site": "https://sugarbbscan.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/SugarBabies[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/sugarbabies.png" + }, + { + "id": "sweetEscapeTL", + "name": "Sweet Escape Translations [madara]", + "site": "https://sweetescapetranslations.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/SweetEscapeTranslations[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/sweetescapetranslations.png" + }, + { + "id": "webnovelover", + "name": "WebNovelLover [madara]", + "site": "https://www.webnovelover.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/WebNovelLover[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/webnovellover.png" + }, + { + "id": "wuxiaworld.site", + "name": "WuxiaWorld.Site [madara]", + "site": "https://wuxiaworld.site/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/WuxiaWorldSite[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/wuxiaworld.site.png" + }, + { + "id": "zetroTL", + "name": "Zetro Translation [madara]", + "site": "https://zetrotranslation.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/ZetroTranslation[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/zetrotranslation.png" + }, + { + "id": "zinnovel", + "name": "ZinnNovel [madara]", + "site": "https://zinnovel.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/ZinnNovel[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/zinnnovel.png" + } + ], + "français, française": [ + { + "id": "chireads.com", + "name": "Chireads", + "site": "https://chireads.com/", + "lang": "français, française", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/french/chireads.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/fr/chireads/icon.png" + } + ], + "Bahasa Indonesia": [ + { + "id": "IDWN.id", + "name": "IndoWebNovel", + "site": "https://indowebnovel.id/id/", + "lang": "Bahasa Indonesia", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/indonesian/indowebnovel.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/id/indowebnovel/icon.png" + }, + { + "id": "meionovel", + "name": "MeioNovel [madara]", + "site": "https://meionovel.id/", + "lang": "Bahasa Indonesia", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/indonesian/MeioNovel[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/meionovel.png" + }, + { + "id": "morenovel", + "name": "MoreNovel [madara]", + "site": "https://morenovel.net/", + "lang": "Bahasa Indonesia", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/indonesian/MoreNovel[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/morenovel.png" + }, + { + "id": "novelringan.com", + "name": "NovelRingan", + "site": "https://novelringan.com/", + "lang": "Bahasa Indonesia", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/indonesian/novelringan.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/id/novelringan/icon.png" + }, + { + "id": "sakura.id", + "name": "SakuraNovel", + "site": "https://sakuranovel.id/", + "lang": "Bahasa Indonesia", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/indonesian/sakuranovel.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/id/sakuranovel/icon.png" + }, + { + "id": "wbnovel", + "name": "WBNovel [madara]", + "site": "https://wbnovel.com/", + "lang": "Bahasa Indonesia", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/indonesian/WBNovel[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/wbnovel.png" + } + ], + "日本語": [ + { + "id": "yomou.syosetu", + "name": "Syosetu", + "site": "https//yomou.syosetu.com/", + "lang": "日本語", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/japanese/Syosetu.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/jp/syosetu/icon.png" + } + ], + "Português": [ + { + "id": "lightnovelbrasil", + "name": "Light Novel Brasil", + "site": "https://lightnovelbrasil.com/", + "lang": "Português", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/portuguese/lightnovelbrasil.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/wpmangastream/icons/lightnovelbrasil.png" + } + ], + "русский язык": [ + { + "id": "axel.znovel", + "name": "Аксел", + "site": "https://axel.znovel.space", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/axelznovelspace[noveltl].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/" + }, + { + "id": "bel.novel", + "name": "Белманга", + "site": "https://bel.novel.tl", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/belnoveltl[noveltl].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/" + }, + { + "id": "erolate", + "name": "Erolate", + "site": "https://erolate.com", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/Erolate[rulate].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/" + }, + { + "id": "jaomix.ru", + "name": "Jaomix", + "site": "https://jaomix.ru", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/jaomix.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/ru/jaomix/icon.png" + }, + { + "id": "japit.novel", + "name": "Japit Comics", + "site": "https://japit.novel.tl", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/japitnoveltl[noveltl].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/" + }, + { + "id": "kg.novel", + "name": "Карманная галактика", + "site": "https://kg.novel.tl", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/kgnoveltl[noveltl].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/" + }, + { + "id": "kod.novel", + "name": "K.O.D.", + "site": "https://kod.novel.tl", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/kodnoveltl[noveltl].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/" + }, + { + "id": "nightnovel", + "name": "Nightnovel", + "site": "https://nightnovel.online", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/nightnovelonline[noveltl].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/" + }, + { + "id": "original.novel", + "name": "Авторские работы", + "site": "https://original.novel.tl", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/originalnoveltl[noveltl].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/" + }, + { + "id": "red.novel", + "name": "Север и Юг", + "site": "https://red.novel.tl", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/rednoveltl[noveltl].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/" + }, + { + "id": "ruranobe", + "name": "РуРанобэ", + "site": "https://ruranobe.ru", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/ruranoberu[noveltl].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/" + }, + { + "id": "sferdrakon.novel", + "name": "Уголок дракона", + "site": "https://sferdrakon.novel.tl", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/sferdrakonnoveltl[noveltl].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/" + }, + { + "id": "si.novel", + "name": "Сказочные истории", + "site": "https://si.novel.tl", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/sinoveltl[noveltl].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/" + }, + { + "id": "snailulitka.novel", + "name": "Snailulitka", + "site": "https://snailulitka.novel.tl", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/snailulitkanoveltl[noveltl].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/" + }, + { + "id": "tl.rulate", + "name": "Rulate", + "site": "https://tl.rulate.ru", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/Rulate[rulate].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/" + }, + { + "id": "tsundoku.novel", + "name": "TsundokuTrans", + "site": "https://tsundoku.novel.tl", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/tsundokunoveltl[noveltl].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/" + }, + { + "id": "ukr.novel", + "name": "Переклади українською мовою", + "site": "https://ukr.novel.tl", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/ukrnoveltl[noveltl].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/" + } + ], + "español, castellano": [ + { + "id": "HasuTL", + "name": "Hasu Translations", + "site": "https://hasutl.wordpress.com/", + "lang": "español, castellano", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/spanish/hasutl.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/es/hasutl/icon.png" + }, + { + "id": "ligera.com", + "name": "Novelas Ligera", + "site": "https://novelasligera.com/", + "lang": "español, castellano", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/spanish/novelasligera.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/es/novelasligera/icon.png" + }, + { + "id": "TNL.com", + "name": "TuNovelaLigera", + "site": "https://tunovelaligera.com/", + "lang": "español, castellano", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/spanish/tunovelaligera.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/es/tunovelaligera/icon.png" + }, + { + "id": "yuukitls.com", + "name": "Yuuki Tls", + "site": "https://yuukitls.com/", + "lang": "español, castellano", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/spanish/yukitls.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/es/yuukitls/icon.png" + } + ], + "Türkçe": [ + { + "id": "epiknovel.com", + "name": "EpikNovel", + "site": "https://www.epiknovel.com/", + "lang": "Türkçe", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/turkish/epiknovel.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/tr/epiknovel/icon.png" + }, + { + "id": "lshnovel", + "name": "Liebe Schnee Hiver Novel", + "site": "https://lshnovel.com/", + "lang": "Türkçe", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/turkish/lshnovel.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/wpmangastream/icons/lshnovel.png" + }, + { + "id": "turkcelightnovels", + "name": "TurkceLightNovels [madara]", + "site": "https://turkcelightnovels.com/", + "lang": "Türkçe", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/turkish/TurkceLightNovels[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/turkcelightnovels.png" + }, + { + "id": "webnoveloku", + "name": "WebNovelOku [madara]", + "site": "https://www.webnoveloku.com/", + "lang": "Türkçe", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/turkish/WebNovelOku[madara].js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/webnoveloku.png" + } + ], + "Tiếng Việt": [ + { + "id": "ln.hako", + "name": "Hako", + "site": "https://ln.hako.vn", + "lang": "Tiếng Việt", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/vietnamese/LNHako.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/vi/hakolightnovel/icon.png" + } + ] +} \ No newline at end of file diff --git a/dist/nyagami/plugins.min.json b/dist/nyagami/plugins.min.json new file mode 100644 index 0000000..e59a361 --- /dev/null +++ b/dist/nyagami/plugins.min.json @@ -0,0 +1 @@ +{"العربية":[{"id":"ar-mtl","name":"ArMTL [madara]","site":"https://ar-mtl.club/","lang":"العربية","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/arabic/ArMTL[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/armtl.png"},{"id":"arnovel","name":"ArNovel [madara]","site":"https://arnovel.me/","lang":"العربية","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/arabic/ArNovel[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/arnovel.png"},{"id":"hizomanga","name":"HizoManga [madara]","site":"https://hizomanga.com/","lang":"العربية","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/arabic/HizoManga[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/hizomanga.png"},{"id":"kolnovel","name":"KolNovel","site":"https://kolnovel.com/","lang":"العربية","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/arabic/kolnovel.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/wpmangastream/icons/kolnovel.png"},{"id":"novel4up","name":"Novel4Up [madara]","site":"https://novel4up.com/","lang":"العربية","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/arabic/Novel4Up[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/novel4up.png"},{"id":"riwyat","name":"Riwyat [madara]","site":"https://riwyat.com/","lang":"العربية","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/arabic/Riwyat[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/riwyat.png"},{"id":"teamxnovel","name":"TeamXNovel [madara]","site":"https://teamxnovel.com/","lang":"العربية","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/arabic/TeamXNovel[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/teamxnovel.png"}],"English":[{"id":"1stkissnovel","name":"FirstKissNovel [madara]","site":"https://1stkissnovel.org/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/FirstKissNovel[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/firstkissnovel.png"},{"id":"ANF.com","name":"AllNovelFull","site":"https://allnovelfull.com","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/allnovelfull.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/allnovelfull/icon.png"},{"id":"BLN.com","name":"BestLightNovel","site":"https://bestlightnovel.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/bestlightnovel.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/bestlightnovel/icon.png"},{"id":"boxnovel","name":"BoxNovel [madara]","site":"https://boxnovel.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/BoxNovel[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/boxnovel.png"},{"id":"clicknovel","name":"ClickNovel [madara]","site":"https://clicknovel.net/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/ClickNovel[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/clicknovel.png"},{"id":"comrademao","name":"Comrade Mao","site":"https://comrademao.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/comrademao.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/comrademao/icon.png"},{"id":"cratenovel","name":"CrateNovel [madara]","site":"https://cratenovel.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/CrateNovel[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/cratenovel.png"},{"id":"daonovel","name":"DaoNovel [madara]","site":"https://daonovel.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/DaoNovel[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/daonovel.png"},{"id":"DDL.com","name":"Divine Dao Library","site":"https://www.divinedaolibrary.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/divinedaolibrary.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/`en/divinedaolibrary/icon.png"},{"id":"earlynovel","name":"Early Novel","site":"https://earlynovel.net/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/earlynovel.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/latestnovel.png"},{"id":"fanTLS","name":"Fans Translations [madara]","site":"https://fanstranslations.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/FansTranslations[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/fanstranslations.png"},{"id":"fastnovel","name":"Fast Novel","site":"https://fastnovel.org/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/fastnovel.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/fastnovel/icon.png"},{"id":"foxaholic","name":"Foxaholic","site":"https://www.foxaholic.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/foxaholic.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/foxaholic/icon.png"},{"id":"freenovel.me","name":"FreeNovelMe [madara]","site":"https://freenovel.me/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/FreeNovelMe[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/freenovelme.png"},{"id":"freenovelupdates","name":"Free Novel Updates (Broken)","site":"https://www.freenovelupdates.com","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/freenovelupdates.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/freenovelupdates/icon.png"},{"id":"FWN.com","name":"Web NOVEL","site":"https://freewebnovel.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/freewebnovel.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/freewebnovel/icon.png"},{"id":"guavaread","name":"Guavaread [madara]","site":"https://guavaread.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/Guavaread[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/guavaread.png"},{"id":"lightnovelpub","name":"LightNovelPub","site":"https://www.lightnovelpub.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/lightnovelpub.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/lightnovelpub/icon.png"},{"id":"lnheaven","name":"LightNovelHeaven [madara]","site":"https://lightnovelheaven.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/LightNovelHeaven[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/lightnovelheaven.png"},{"id":"Ltnovel.com","name":"Ltnovel.com","site":"https://www.ltnovel.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/Ltnovelcom[readwn].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/readwn/icon/ltnovel.png"},{"id":"lunarletters","name":"LunarLetters [madara]","site":"https://www.lunarletters.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/LunarLetters[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/lunarletters.png"},{"id":"mostnovel","name":"MostNovel [madara]","site":"https://mostnovel.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/MostNovel[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/mostnovel.png"},{"id":"mtl-novel","name":"MTL-Novel [madara]","site":"https://mtl-novel.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/MTLNovel[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/mtl-novel.png"},{"id":"mtlnovel.club","name":"MTLNovel.Club [madara]","site":"https://mtlnovel.club/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/MTLNovelClub[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/mtlnovel.club.png"},{"id":"mtlreader","name":"MTL Reader","site":"https://mtlreader.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/mtlreader.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/mtlreader/icon.png"},{"id":"mysticalmerries","name":"MysticalSeries [madara]","site":"https://mysticalmerries.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/MysticalSeries[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/mysticalseries.png"},{"id":"neosekaiTLS","name":"NeoSekai Translations [madara]","site":"https://www.neosekaitranslations.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/NeoSekaiTranslations[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/neosekaitranslations.png"},{"id":"NF.me","name":"NovelFull.me","site":"https://novelfull.me/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/NovelFullMe.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/novelfullme/icon.png"},{"id":"nitroscans","name":"Nitroscans","site":"https://nitroscans.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/nitroscans.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/nitroscans/icon.png"},{"id":"NO.net","name":"novelsOnline","site":"https://novelsonline.net","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/NovelOnline.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/coverNotAvailable.jpg"},{"id":"NobleMTL","name":"NobleMTL","site":"https://www.noblemtl.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/nobleMTL.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/noblemtl/icon.png"},{"id":"nocturneTLS","name":"Nocturne Translations [madara]","site":"https://nocturnetls.net/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/NocturneTranslations[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/nocturnetranslations.png"},{"id":"noobchanTL","name":"Noobchan Translation [madara]","site":"https://noobchan.xyz/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/NoobchanTranslation[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/noobchantranslation.png"},{"id":"novelforest","name":"Novel Forest (Broken)","site":"https://novelforest.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/novelforest.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/novelforest/icon.png"},{"id":"novelfull","name":"NovelFull","site":"https://novelfull.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/novelfull.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/novelfull/icon.png"},{"id":"novelhold","name":"Novel Hold","site":"https://novelhold.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/novelhold.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/novelhold/icon.png"},{"id":"Novelmt.com","name":"Novelmt.com","site":"https://www.novelmt.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/Novelmtcom[readwn].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/readwn/icon/novelmt.png"},{"id":"novelmultiverse","name":"NovelMultiverse [madara]","site":"https://www.novelmultiverse.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/NovelMultiverse[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/novelmultiverse.png"},{"id":"novelpub.com","name":"NovelPub","site":"https://www.novelpub.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/novelpub.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/novelpub/icon.png"},{"id":"novelr18","name":"NovelR18 [madara]","site":"https://novelr18.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/NovelR18[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/novelr18.png"},{"id":"novelroom","name":"Novelroom.net [madara]","site":"https://novelroom.net/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/Novelroomnet[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/novelroom.net.png"},{"id":"novelstic","name":"Novelstic [madara]","site":"https://novelstic.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/Novelstic[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/novelstic.png"},{"id":"novelTL","name":"NovelTranslate [madara]","site":"https://noveltranslate.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/NovelTranslate[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/noveltranslate.png"},{"id":"onlymtl","name":"OnlyMTL [madara]","site":"https://www.onlymtl.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/OnlyMTL[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/onlymtl.png"},{"id":"PandaMTL","name":"PandaMTL","site":"https://www.pandamtl.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/pandaMTL.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/en/wordpress/icon.png"},{"id":"readwebnovels","name":"ReadWebNovels [madara]","site":"https://readwebnovels.net/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/ReadWebNovels[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/readwebnovels.png"},{"id":"Readwn.com","name":"Readwn.com","site":"https://www.readwn.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/Readwncom[readwn].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/readwn/icon/readwn.png"},{"id":"sleeptTLS","name":"SleepyTranslations [madara]","site":"https://sleepytranslations.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/SleepyTranslations[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/sleepytranslations.png"},{"id":"sonicmtl","name":"SonicMTL [madara]","site":"https://www.sonicmtl.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/SonicMTL[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/sonicmtl.png"},{"id":"sugarbbscan","name":"Sugar Babies [madara]","site":"https://sugarbbscan.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/SugarBabies[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/sugarbabies.png"},{"id":"sweetEscapeTL","name":"Sweet Escape Translations [madara]","site":"https://sweetescapetranslations.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/SweetEscapeTranslations[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/sweetescapetranslations.png"},{"id":"webnovelover","name":"WebNovelLover [madara]","site":"https://www.webnovelover.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/WebNovelLover[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/webnovellover.png"},{"id":"wuxiaworld.site","name":"WuxiaWorld.Site [madara]","site":"https://wuxiaworld.site/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/WuxiaWorldSite[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/wuxiaworld.site.png"},{"id":"zetroTL","name":"Zetro Translation [madara]","site":"https://zetrotranslation.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/ZetroTranslation[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/zetrotranslation.png"},{"id":"zinnovel","name":"ZinnNovel [madara]","site":"https://zinnovel.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/english/ZinnNovel[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/zinnnovel.png"}],"français, française":[{"id":"chireads.com","name":"Chireads","site":"https://chireads.com/","lang":"français, française","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/french/chireads.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/fr/chireads/icon.png"}],"Bahasa Indonesia":[{"id":"IDWN.id","name":"IndoWebNovel","site":"https://indowebnovel.id/id/","lang":"Bahasa Indonesia","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/indonesian/indowebnovel.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/id/indowebnovel/icon.png"},{"id":"meionovel","name":"MeioNovel [madara]","site":"https://meionovel.id/","lang":"Bahasa Indonesia","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/indonesian/MeioNovel[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/meionovel.png"},{"id":"morenovel","name":"MoreNovel [madara]","site":"https://morenovel.net/","lang":"Bahasa Indonesia","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/indonesian/MoreNovel[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/morenovel.png"},{"id":"novelringan.com","name":"NovelRingan","site":"https://novelringan.com/","lang":"Bahasa Indonesia","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/indonesian/novelringan.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/id/novelringan/icon.png"},{"id":"sakura.id","name":"SakuraNovel","site":"https://sakuranovel.id/","lang":"Bahasa Indonesia","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/indonesian/sakuranovel.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/id/sakuranovel/icon.png"},{"id":"wbnovel","name":"WBNovel [madara]","site":"https://wbnovel.com/","lang":"Bahasa Indonesia","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/indonesian/WBNovel[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/wbnovel.png"}],"日本語":[{"id":"yomou.syosetu","name":"Syosetu","site":"https//yomou.syosetu.com/","lang":"日本語","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/japanese/Syosetu.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/jp/syosetu/icon.png"}],"Português":[{"id":"lightnovelbrasil","name":"Light Novel Brasil","site":"https://lightnovelbrasil.com/","lang":"Português","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/portuguese/lightnovelbrasil.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/wpmangastream/icons/lightnovelbrasil.png"}],"русский язык":[{"id":"axel.znovel","name":"Аксел","site":"https://axel.znovel.space","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/axelznovelspace[noveltl].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/"},{"id":"bel.novel","name":"Белманга","site":"https://bel.novel.tl","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/belnoveltl[noveltl].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/"},{"id":"erolate","name":"Erolate","site":"https://erolate.com","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/Erolate[rulate].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/"},{"id":"jaomix.ru","name":"Jaomix","site":"https://jaomix.ru","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/jaomix.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/ru/jaomix/icon.png"},{"id":"japit.novel","name":"Japit Comics","site":"https://japit.novel.tl","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/japitnoveltl[noveltl].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/"},{"id":"kg.novel","name":"Карманная галактика","site":"https://kg.novel.tl","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/kgnoveltl[noveltl].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/"},{"id":"kod.novel","name":"K.O.D.","site":"https://kod.novel.tl","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/kodnoveltl[noveltl].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/"},{"id":"nightnovel","name":"Nightnovel","site":"https://nightnovel.online","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/nightnovelonline[noveltl].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/"},{"id":"original.novel","name":"Авторские работы","site":"https://original.novel.tl","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/originalnoveltl[noveltl].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/"},{"id":"red.novel","name":"Север и Юг","site":"https://red.novel.tl","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/rednoveltl[noveltl].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/"},{"id":"ruranobe","name":"РуРанобэ","site":"https://ruranobe.ru","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/ruranoberu[noveltl].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/"},{"id":"sferdrakon.novel","name":"Уголок дракона","site":"https://sferdrakon.novel.tl","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/sferdrakonnoveltl[noveltl].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/"},{"id":"si.novel","name":"Сказочные истории","site":"https://si.novel.tl","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/sinoveltl[noveltl].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/"},{"id":"snailulitka.novel","name":"Snailulitka","site":"https://snailulitka.novel.tl","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/snailulitkanoveltl[noveltl].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/"},{"id":"tl.rulate","name":"Rulate","site":"https://tl.rulate.ru","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/Rulate[rulate].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/"},{"id":"tsundoku.novel","name":"TsundokuTrans","site":"https://tsundoku.novel.tl","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/tsundokunoveltl[noveltl].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/"},{"id":"ukr.novel","name":"Переклади українською мовою","site":"https://ukr.novel.tl","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/russian/ukrnoveltl[noveltl].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/"}],"español, castellano":[{"id":"HasuTL","name":"Hasu Translations","site":"https://hasutl.wordpress.com/","lang":"español, castellano","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/spanish/hasutl.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/es/hasutl/icon.png"},{"id":"ligera.com","name":"Novelas Ligera","site":"https://novelasligera.com/","lang":"español, castellano","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/spanish/novelasligera.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/es/novelasligera/icon.png"},{"id":"TNL.com","name":"TuNovelaLigera","site":"https://tunovelaligera.com/","lang":"español, castellano","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/spanish/tunovelaligera.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/es/tunovelaligera/icon.png"},{"id":"yuukitls.com","name":"Yuuki Tls","site":"https://yuukitls.com/","lang":"español, castellano","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/spanish/yukitls.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/es/yuukitls/icon.png"}],"Türkçe":[{"id":"epiknovel.com","name":"EpikNovel","site":"https://www.epiknovel.com/","lang":"Türkçe","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/turkish/epiknovel.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/tr/epiknovel/icon.png"},{"id":"lshnovel","name":"Liebe Schnee Hiver Novel","site":"https://lshnovel.com/","lang":"Türkçe","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/turkish/lshnovel.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/wpmangastream/icons/lshnovel.png"},{"id":"turkcelightnovels","name":"TurkceLightNovels [madara]","site":"https://turkcelightnovels.com/","lang":"Türkçe","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/turkish/TurkceLightNovels[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/turkcelightnovels.png"},{"id":"webnoveloku","name":"WebNovelOku [madara]","site":"https://www.webnoveloku.com/","lang":"Türkçe","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/turkish/WebNovelOku[madara].js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/multisrc/madara/icons/webnoveloku.png"}],"Tiếng Việt":[{"id":"ln.hako","name":"Hako","site":"https://ln.hako.vn","lang":"Tiếng Việt","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/js/plugins/vietnamese/LNHako.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/ts-support/icons/src/vi/hakolightnovel/icon.png"}]} \ No newline at end of file diff --git a/icons/src/en/noblemtl/icon.png b/icons/src/en/noblemtl/icon.png new file mode 100644 index 0000000..0f8ddde Binary files /dev/null and b/icons/src/en/noblemtl/icon.png differ diff --git a/icons/src/en/mylovenovel/icon.png b/icons/src/en/novelhold/icon.png similarity index 100% rename from icons/src/en/mylovenovel/icon.png rename to icons/src/en/novelhold/icon.png diff --git a/icons/src/en/wordpress/icon.png b/icons/src/en/wordpress/icon.png new file mode 100644 index 0000000..5a81017 Binary files /dev/null and b/icons/src/en/wordpress/icon.png differ diff --git a/index.js b/index.js deleted file mode 100644 index 42e991e..0000000 --- a/index.js +++ /dev/null @@ -1,65 +0,0 @@ -require('module-alias/register'); - -const path = require('path'); -const dayjs = require('dayjs') -const express = require('express'); -const bodyParser = require('body-parser'); -const pluginApi = require('@api/plugins'); -const localizedFormat = require('dayjs/plugin/localizedFormat'); - -const app = express(); -const port = 3000; -const host = 'localhost'; - -app.use(bodyParser.json()); -app.use('/static', express.static(path.join(__dirname, 'test_web', 'static'))); -app.get('/all_plugins', (req, res) => { - res.json(pluginApi.all_plugins()); -}); -app.post('/popularNovels/', async (req, res) => { - const filters = req.body['filters'] || {}; - const showLatestNovels = req.body['showLatestNovels'] || false - const novels = await pluginApi.popularNovels(req.body['pluginRequirePath'], {showLatestNovels, filters}); - res.json(novels); -}); -app.post('/searchNovels/', async(req, res) => { - const novels = await pluginApi.searchNovels(req.body['pluginRequirePath'], req.body['searchTerm']); - res.json(novels); -}) -app.post('/parseNovelAndChapters/', async(req, res) => { - const sourceNovel = await pluginApi.parseNovelAndChapters(req.body['pluginRequirePath'], req.body['novelUrl']); - res.json(sourceNovel); -}) -app.post('/parseChapter/', async(req, res) => { - const chapterText = await pluginApi.parseChapter(req.body['pluginRequirePath'], req.body['chapterUrl']); - res.json(chapterText); -}) -app.post('/fetchImage/', async(req, res) => { - const base64 = await pluginApi.fetchImage(req.body['pluginRequirePath'], req.body['url']); - res.json(base64); -}) - -app.get('/', (req, res) => { - res.sendFile(path.join(__dirname, 'test_web', 'index.html')); -}); - - -app.listen(port, host, () => { - console.log("Testing plugins web listening on http://localhost:3000"); -}); - -//Dayjs localization -const language = Intl?.DateTimeFormat()?.resolvedOptions()?.locale || "en"; - -require("dayjs/locale/ar"); -require("dayjs/locale/de"); -require("dayjs/locale/es"); -require("dayjs/locale/it"); -require("dayjs/locale/pt"); -require("dayjs/locale/ru"); -require("dayjs/locale/tr"); -require("dayjs/locale/uk"); -require("dayjs/locale/zh"); - -dayjs.locale(language); -dayjs.extend(localizedFormat); \ No newline at end of file diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..ee18d3d --- /dev/null +++ b/index.ts @@ -0,0 +1,82 @@ +require("module-alias/register"); + +import path from "path"; +import dayjs from "dayjs"; +import express from "express"; +import bodyParser from "body-parser"; +import * as pluginApi from "./test_web/api/plugins"; +import localizedFormat from "dayjs/plugin/localizedFormat"; + +const app = express(); +const port = 3000; +const host = "localhost"; + +const dirname = path.join(__dirname, ".."); + +app.use(bodyParser.json()); +app.use("/static", express.static(path.join(dirname, "test_web", "static"))); +app.get("/all_plugins", (req, res) => { + const allPlugins = pluginApi.all_plugins(); + res.json(allPlugins); +}); +app.post("/popularNovels/", async (req, res) => { + const filters = req.body["filters"] || {}; + const showLatestNovels = req.body["showLatestNovels"] || false; + const novels = await pluginApi.popularNovels( + req.body["pluginRequirePath"], + { showLatestNovels, filters } + ); + res.json(novels); +}); +app.post("/searchNovels/", async (req, res) => { + const novels = await pluginApi.searchNovels( + req.body["pluginRequirePath"], + req.body["searchTerm"] + ); + res.json(novels); +}); +app.post("/parseNovelAndChapters/", async (req, res) => { + const sourceNovel = await pluginApi.parseNovelAndChapters( + req.body["pluginRequirePath"], + req.body["novelUrl"] + ); + res.json(sourceNovel); +}); +app.post("/parseChapter/", async (req, res) => { + const chapterText = await pluginApi.parseChapter( + req.body["pluginRequirePath"], + req.body["chapterUrl"] + ); + res.json(chapterText); +}); +app.post("/fetchImage/", async (req, res) => { + const base64 = await pluginApi.fetchImage( + req.body["pluginRequirePath"], + req.body["url"] + ); + res.json(base64); +}); + +app.get("/", (req, res) => { + res.sendFile(path.join(dirname, "test_web", "index.html")); +}); + +app.listen(port, host, () => { + console.log("Testing plugins web listening on http://localhost:3000"); +}); + +//Dayjs localization +const language = Intl?.DateTimeFormat()?.resolvedOptions()?.locale || "en"; + +require("dayjs/locale/ar"); +require("dayjs/locale/de"); +require("dayjs/locale/es"); +require("dayjs/locale/it"); +require("dayjs/locale/pt"); +require("dayjs/locale/ru"); +require("dayjs/locale/tr"); +require("dayjs/locale/uk"); +require("dayjs/locale/zh"); + +dayjs.locale(language); +dayjs.extend(localizedFormat); diff --git a/js/plugins/arabic/ArMTL[madara].js b/js/plugins/arabic/ArMTL[madara].js new file mode 100644 index 0000000..4c4418f --- /dev/null +++ b/js/plugins/arabic/ArMTL[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "ar-mtl"; +exports.name = "ArMTL [madara]"; +exports.icon = "multisrc/madara/icons/armtl.png"; +exports.version = "1.0.0"; +exports.site = "https://ar-mtl.club/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novel" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (true) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/arabic/ArNovel[madara].js b/js/plugins/arabic/ArNovel[madara].js new file mode 100644 index 0000000..aafe7c0 --- /dev/null +++ b/js/plugins/arabic/ArNovel[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "arnovel"; +exports.name = "ArNovel [madara]"; +exports.icon = "multisrc/madara/icons/arnovel.png"; +exports.version = "1.0.0"; +exports.site = "https://arnovel.me/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novel" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (true) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/arabic/HizoManga[madara].js b/js/plugins/arabic/HizoManga[madara].js new file mode 100644 index 0000000..5caae27 --- /dev/null +++ b/js/plugins/arabic/HizoManga[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "hizomanga"; +exports.name = "HizoManga [madara]"; +exports.icon = "multisrc/madara/icons/hizomanga.png"; +exports.version = "1.0.0"; +exports.site = "https://hizomanga.com/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "serie" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (true) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "serie" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/arabic/Novel4Up[madara].js b/js/plugins/arabic/Novel4Up[madara].js new file mode 100644 index 0000000..6b711c2 --- /dev/null +++ b/js/plugins/arabic/Novel4Up[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "novel4up"; +exports.name = "Novel4Up [madara]"; +exports.icon = "multisrc/madara/icons/novel4up.png"; +exports.version = "1.0.0"; +exports.site = "https://novel4up.com/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novel" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (false) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/arabic/Riwyat[madara].js b/js/plugins/arabic/Riwyat[madara].js new file mode 100644 index 0000000..047c4f7 --- /dev/null +++ b/js/plugins/arabic/Riwyat[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "riwyat"; +exports.name = "Riwyat [madara]"; +exports.icon = "multisrc/madara/icons/riwyat.png"; +exports.version = "1.0.0"; +exports.site = "https://riwyat.com/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novel" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (true) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/arabic/TeamXNovel[madara].js b/js/plugins/arabic/TeamXNovel[madara].js new file mode 100644 index 0000000..5de3b1a --- /dev/null +++ b/js/plugins/arabic/TeamXNovel[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "teamxnovel"; +exports.name = "TeamXNovel [madara]"; +exports.icon = "multisrc/madara/icons/teamxnovel.png"; +exports.version = "1.0.0"; +exports.site = "https://teamxnovel.com/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novel" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (true) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/arabic/kolnovel.js b/js/plugins/arabic/kolnovel.js new file mode 100644 index 0000000..cffebe2 --- /dev/null +++ b/js/plugins/arabic/kolnovel.js @@ -0,0 +1,245 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.filters = exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.icon = exports.version = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const filterInputs_1 = require("@libs/filterInputs"); +exports.id = "kolnovel"; +exports.name = "KolNovel"; +exports.version = "1.0.0"; +exports.icon = "multisrc/wpmangastream/icons/kolnovel.png"; +exports.site = "https://kolnovel.com/"; +module.exports.protected = false; +const pluginId = exports.id; +const baseUrl = exports.site; +const popularNovels = function (page, { filters }) { + return __awaiter(this, void 0, void 0, function* () { + let link = `${baseUrl}series/?page=${page}`; + if (filters) { + if (Array.isArray(filters.genres) && filters.genres.length) { + link += filters.genres.map((i) => `&genre[]=${i}`).join(""); + } + if (Array.isArray(filters.type) && filters.type.length) + link += filters.type.map((i) => `&lang[]=${i}`).join(""); + } + link += "&status=" + ((filters === null || filters === void 0 ? void 0 : filters.status) ? filters.status : ""); + link += "&order=" + ((filters === null || filters === void 0 ? void 0 : filters.order) ? filters.order : "popular"); + const body = yield (0, fetch_1.fetchApi)(link, {}, pluginId).then((result) => result.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const novels = []; + loadedCheerio("article.maindet").each(function () { + const novelName = loadedCheerio(this).find("h2").text(); + let image = loadedCheerio(this).find("img"); + const novelCover = image.attr("data-src") || image.attr("src"); + const novelUrl = loadedCheerio(this).find("h2 a").attr("href"); + if (!novelUrl) + return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function parseNovelAndChapters(novelUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = novelUrl; + const result = yield (0, fetch_1.fetchApi)(url, {}, pluginId); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + const novel = { + url, + chapters: [], + }; + novel.name = loadedCheerio("h1.entry-title").text(); + novel.cover = + loadedCheerio("img.wp-post-image").attr("data-src") || + loadedCheerio("img.wp-post-image").attr("src"); + loadedCheerio("div.serl:nth-child(3) > span").each(function () { + const detailName = loadedCheerio(this).text().trim(); + const detail = loadedCheerio(this).next().text().trim(); + switch (detailName) { + case "الكاتب": + case "Author": + novel.author = detail; + break; + } + }); + novel.status = loadedCheerio("div.sertostat > span").attr("class"); + novel.genres = loadedCheerio(".sertogenre") + .children("a") + .map((i, el) => loadedCheerio(el).text()) + .toArray() + .join(","); + novel.summary = loadedCheerio(".sersys") + .find("br") + .replaceWith("\n") + .end() + .text(); + let chapter = []; + loadedCheerio(".eplister") + .find("li") + .each(function () { + const chapterName = loadedCheerio(this).find(".epl-num").text() + + " - " + + loadedCheerio(this).find(".epl-title").text(); + const releaseDate = loadedCheerio(this) + .find(".epl-date") + .text() + .trim(); + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + if (!chapterUrl) + return; + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + novel.chapters = chapter.reverse(); + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function parseChapter(chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)(chapterUrl, {}, pluginId); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let ignore = loadedCheerio(".epcontent > p").next().attr("class"); + loadedCheerio(`p.${ignore}`).remove(); + let chapterText = loadedCheerio(".epcontent").html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function searchNovels(searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}?s=${searchTerm}`; + const result = yield (0, fetch_1.fetchApi)(url, {}, pluginId); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + const novels = []; + loadedCheerio("article.maindet").each(function () { + const novelName = loadedCheerio(this).find("h2").text(); + let image = loadedCheerio(this).find("img"); + const novelCover = image.attr("data-src") || image.attr("src"); + const novelUrl = loadedCheerio(this).find("h2 a").attr("href"); + if (!novelUrl) + return; + novels.push({ + name: novelName, + url: novelUrl, + cover: novelCover, + }); + }); + return novels; + }); +}; +exports.searchNovels = searchNovels; +const fetchImage = function fetchImage(url) { + return __awaiter(this, void 0, void 0, function* () { + const headers = { + Referer: baseUrl, + }; + return yield (0, fetch_1.fetchFile)(url, { headers: headers }); + }); +}; +exports.fetchImage = fetchImage; +exports.filters = [ + { + key: "order", + label: "ترتيب حسب", + values: [ + { label: "الإعداد الأولي", value: "" }, + { label: "A-Z", value: "title" }, + { label: "Z-A", value: "titlereverse" }, + { label: "أخر التحديثات", value: "update" }, + { label: "أخر ما تم إضافته", value: "latest" }, + { label: "الرائجة", value: "popular" }, + ], + inputType: filterInputs_1.FilterInputs.Picker, + }, + { + key: "status", + label: "الحالة", + values: [ + { label: "All", value: "" }, + { label: "Ongoing", value: "ongoing" }, + { label: "Hiatus", value: "hiatus" }, + { label: "Completed", value: "completed" }, + ], + inputType: filterInputs_1.FilterInputs.Picker, + }, + { + key: "type", + label: "النوع", + values: [ + { label: "إنجليزية", value: "english" }, + { label: "روايةلايت", value: "light-novel" }, + { label: "روايةويب", value: "web-novel" }, + { label: "صينية", value: "chinese" }, + { label: "عربية", value: "arabic" }, + { label: "كورية", value: "korean" }, + { label: "يابانية", value: "japanese" }, + ], + inputType: filterInputs_1.FilterInputs.Checkbox, + }, + { + key: "genres", + label: "تصنيف", + values: [ + { label: "Wuxia", value: "wuxia" }, + { label: "Xianxia", value: "xianxia" }, + { label: "XUANHUAN", value: "xuanhuan" }, + { label: "أكشن", value: "action" }, + { label: "إثارة", value: "excitement" }, + { label: "إنتقالالىعالمأخر", value: "isekai" }, + { label: "إيتشي", value: "etchi" }, + { label: "الخيالالعلمي", value: "sci-fi" }, + { label: "بوليسي", value: "policy" }, + { label: "تاريخي", value: "historical" }, + { label: "تحقيقات", value: "%d8%aa%d8%ad%d9%82%d9%8a%d9%82" }, + { label: "تقمصشخصيات", value: "rpg" }, + { label: "جريمة", value: "crime" }, + { label: "جوسى", value: "josei" }, + { label: "حريم", value: "harem" }, + { label: "حياةمدرسية", value: "school-life" }, + { label: "خيالي(فانتازيا)", value: "fantasy" }, + { label: "دراما", value: "drama" }, + { label: "رعب", value: "horror" }, + { label: "رومانسي", value: "romantic" }, + { label: "سحر", value: "magic" }, + { label: "سينن", value: "senen" }, + { label: "شريحةمنالحياة", value: "slice-of-life" }, + { label: "شوجو", value: "shojo" }, + { label: "شونين", value: "shonen" }, + { label: "طبي", value: "medical" }, + { label: "ظواهرخارقةللطبيعة", value: "supernatural" }, + { label: "غموض", value: "mysteries" }, + { label: "فنونالقتال", value: "martial-arts" }, + { label: "قوىخارقة", value: "superpower" }, + { label: "كوميدي", value: "comedy" }, + { label: "مأساوي", value: "tragedy" }, + { label: "مابعدالكارثة", value: "after-the-disaster" }, + { label: "مغامرة", value: "adventure" }, + { label: "ميكا", value: "mechanical" }, + { label: "ناضج", value: "mature" }, + { label: "نفسي", value: "psychological" }, + ], + inputType: filterInputs_1.FilterInputs.Checkbox, + }, +]; diff --git a/js/plugins/english/BoxNovel[madara].js b/js/plugins/english/BoxNovel[madara].js new file mode 100644 index 0000000..95018d5 --- /dev/null +++ b/js/plugins/english/BoxNovel[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "boxnovel"; +exports.name = "BoxNovel [madara]"; +exports.icon = "multisrc/madara/icons/boxnovel.png"; +exports.version = "1.0.0"; +exports.site = "https://boxnovel.com/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novel" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (true) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/ClickNovel[madara].js b/js/plugins/english/ClickNovel[madara].js new file mode 100644 index 0000000..5d06b6b --- /dev/null +++ b/js/plugins/english/ClickNovel[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "clicknovel"; +exports.name = "ClickNovel [madara]"; +exports.icon = "multisrc/madara/icons/clicknovel.png"; +exports.version = "1.0.0"; +exports.site = "https://clicknovel.net/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novel" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (false) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/CrateNovel[madara].js b/js/plugins/english/CrateNovel[madara].js new file mode 100644 index 0000000..722f356 --- /dev/null +++ b/js/plugins/english/CrateNovel[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "cratenovel"; +exports.name = "CrateNovel [madara]"; +exports.icon = "multisrc/madara/icons/cratenovel.png"; +exports.version = "1.0.0"; +exports.site = "https://cratenovel.com/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novel" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (true) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/DaoNovel[madara].js b/js/plugins/english/DaoNovel[madara].js new file mode 100644 index 0000000..ac697c2 --- /dev/null +++ b/js/plugins/english/DaoNovel[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "daonovel"; +exports.name = "DaoNovel [madara]"; +exports.icon = "multisrc/madara/icons/daonovel.png"; +exports.version = "1.0.0"; +exports.site = "https://daonovel.com/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novels-list" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (true) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "novels" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/FansTranslations[madara].js b/js/plugins/english/FansTranslations[madara].js new file mode 100644 index 0000000..575ca2f --- /dev/null +++ b/js/plugins/english/FansTranslations[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "fanTLS"; +exports.name = "Fans Translations [madara]"; +exports.icon = "multisrc/madara/icons/fanstranslations.png"; +exports.version = "1.0.0"; +exports.site = "https://fanstranslations.com/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novel" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (true) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/FirstKissNovel[madara].js b/js/plugins/english/FirstKissNovel[madara].js new file mode 100644 index 0000000..a3499fa --- /dev/null +++ b/js/plugins/english/FirstKissNovel[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "1stkissnovel"; +exports.name = "FirstKissNovel [madara]"; +exports.icon = "multisrc/madara/icons/firstkissnovel.png"; +exports.version = "1.0.0"; +exports.site = "https://1stkissnovel.org/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novel" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (true) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/FreeNovelMe[madara].js b/js/plugins/english/FreeNovelMe[madara].js new file mode 100644 index 0000000..d08f4d6 --- /dev/null +++ b/js/plugins/english/FreeNovelMe[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "freenovel.me"; +exports.name = "FreeNovelMe [madara]"; +exports.icon = "multisrc/madara/icons/freenovelme.png"; +exports.version = "1.0.0"; +exports.site = "https://freenovel.me/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novel" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (false) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/Guavaread[madara].js b/js/plugins/english/Guavaread[madara].js new file mode 100644 index 0000000..bc1e230 --- /dev/null +++ b/js/plugins/english/Guavaread[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "guavaread"; +exports.name = "Guavaread [madara]"; +exports.icon = "multisrc/madara/icons/guavaread.png"; +exports.version = "1.0.0"; +exports.site = "https://guavaread.com/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novel" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (true) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/LightNovelHeaven[madara].js b/js/plugins/english/LightNovelHeaven[madara].js new file mode 100644 index 0000000..93fc578 --- /dev/null +++ b/js/plugins/english/LightNovelHeaven[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "lnheaven"; +exports.name = "LightNovelHeaven [madara]"; +exports.icon = "multisrc/madara/icons/lightnovelheaven.png"; +exports.version = "1.0.0"; +exports.site = "https://lightnovelheaven.com/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "series" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (true) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "series" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/Ltnovelcom[readwn].js b/js/plugins/english/Ltnovelcom[readwn].js new file mode 100644 index 0000000..0401676 --- /dev/null +++ b/js/plugins/english/Ltnovelcom[readwn].js @@ -0,0 +1,164 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +// import dayjs from 'dayjs'; +const fetch_1 = require("@libs/fetch"); +// import { parseMadaraDate } from "@libs/parseMadaraDate"; +// import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; +// import { showToast } from "@libs/showToast"; +// import { Filter, FilterInputs } from "@libs/filterInputs"; +// import { NovelStatus } from '@libs/novelStatus'; +// import { defaultCover } from "@libs/defaultCover"; +exports.id = "Ltnovel.com"; +exports.name = "Ltnovel.com"; +exports.icon = "multisrc/readwn/icon/ltnovel.png"; +exports.version = "1.0.0"; +exports.site = "https://www.ltnovel.com/"; +// export const filters: Filter[] = []; +exports["protected"] = false; +const baseUrl = exports.site; +const popularNovels = function (page, { filters, showLatestNovels }) { + return __awaiter(this, void 0, void 0, function* () { + const novels = []; + const pageNo = page - 1; + const url = baseUrl + 'list/all/all-onclick-' + pageNo + '.html'; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('li.novel-item').each(function () { + const novelName = loadedCheerio(this).find('h4').text(); + const novelUrl = baseUrl + loadedCheerio(this).find('a').attr('href'); + const coverUri = loadedCheerio(this) + .find('.novel-cover > img') + .attr('data-src'); + const novelCover = baseUrl + coverUri; + const novel = { name: novelName, cover: novelCover, url: novelUrl }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)(novelUrl); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + const novel = { + url: novelUrl, + chapters: [], + }; + novel.name = loadedCheerio('h1.novel-title').text(); + const coverUri = loadedCheerio('figure.cover > img').attr('data-src'); + novel.cover = baseUrl + coverUri; + novel.summary = loadedCheerio('.summary') + .text() + .replace('Summary', '') + .trim(); + novel.genres = ''; + loadedCheerio('div.categories > ul > li').each(function () { + novel.genres += loadedCheerio(this).text().trim() + ','; + }); + loadedCheerio('div.header-stats > span').each(function () { + if (loadedCheerio(this).find('small').text() === 'Status') { + novel.status = loadedCheerio(this).find('strong').text(); + } + }); + novel.genres = novel.genres.slice(0, -1); + novel.author = loadedCheerio('span[itemprop=author]').text(); + let novelChapters = []; + const novelId = novelUrl.replace('.html', '').replace(baseUrl, ''); + const latestChapterNo = parseInt(loadedCheerio('.header-stats') + .find('span > strong') + .first() + .text() + .trim()); + let lastChapterNo = 1; + loadedCheerio('.chapter-list li').each(function () { + var _a; + const chapterName = loadedCheerio(this) + .find('a .chapter-title') + .text() + .trim(); + const chapterUrl = baseUrl + ((_a = loadedCheerio(this).find('a').attr('href')) === null || _a === void 0 ? void 0 : _a.trim()); + const releaseDate = loadedCheerio(this) + .find('a .chapter-update') + .text() + .trim(); + lastChapterNo = parseInt(loadedCheerio(this).find('a .chapter-no').text().trim()); + const chapter = { name: chapterName, releaseTime: releaseDate, url: chapterUrl }; + novelChapters.push(chapter); + }); + // Itterate once more before loop to finish off + lastChapterNo++; + for (let i = lastChapterNo; i <= latestChapterNo; i++) { + const chapterName = 'Chapter ' + i; + const chapterUrl = baseUrl + novelId + '_' + i + '.html'; + const releaseDate = null; + const chapter = { name: chapterName, releaseTime: releaseDate, url: chapterUrl }; + novelChapters.push(chapter); + } + novel.chapters = novelChapters; + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)(chapterUrl); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.chapter-content').html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const searchUrl = baseUrl + 'e/search/index.php'; + const result = yield (0, fetch_1.fetchApi)(searchUrl, { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Referer: baseUrl + 'search.html', + Origin: baseUrl, + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36', + }, + method: 'POST', + body: JSON.stringify({ + show: 'title', + tempid: 1, + tbname: 'news', + keyboard: searchTerm, + }), + }); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('li.novel-item').each(function () { + const novelName = loadedCheerio(this).find('h4').text(); + const novelUrl = baseUrl + loadedCheerio(this).find('a').attr('href'); + const coverUri = baseUrl + loadedCheerio(this).find('img').attr('data-src'); + const novelCover = baseUrl + coverUri; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url, {}); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/LunarLetters[madara].js b/js/plugins/english/LunarLetters[madara].js new file mode 100644 index 0000000..b8ad356 --- /dev/null +++ b/js/plugins/english/LunarLetters[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "lunarletters"; +exports.name = "LunarLetters [madara]"; +exports.icon = "multisrc/madara/icons/lunarletters.png"; +exports.version = "1.0.0"; +exports.site = "https://www.lunarletters.com/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "series" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (true) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "series" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/MTLNovelClub[madara].js b/js/plugins/english/MTLNovelClub[madara].js new file mode 100644 index 0000000..940c117 --- /dev/null +++ b/js/plugins/english/MTLNovelClub[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "mtlnovel.club"; +exports.name = "MTLNovel.Club [madara]"; +exports.icon = "multisrc/madara/icons/mtlnovel.club.png"; +exports.version = "1.0.0"; +exports.site = "https://mtlnovel.club/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novel" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (true) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/MTLNovel[madara].js b/js/plugins/english/MTLNovel[madara].js new file mode 100644 index 0000000..48016ee --- /dev/null +++ b/js/plugins/english/MTLNovel[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "mtl-novel"; +exports.name = "MTL-Novel [madara]"; +exports.icon = "multisrc/madara/icons/mtl-novel.png"; +exports.version = "1.0.0"; +exports.site = "https://mtl-novel.com/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novel" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (true) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/MostNovel[madara].js b/js/plugins/english/MostNovel[madara].js new file mode 100644 index 0000000..435cced --- /dev/null +++ b/js/plugins/english/MostNovel[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "mostnovel"; +exports.name = "MostNovel [madara]"; +exports.icon = "multisrc/madara/icons/mostnovel.png"; +exports.version = "1.0.0"; +exports.site = "https://mostnovel.com/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "manga" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (false) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "manga" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/MysticalSeries[madara].js b/js/plugins/english/MysticalSeries[madara].js new file mode 100644 index 0000000..b4cbe7c --- /dev/null +++ b/js/plugins/english/MysticalSeries[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "mysticalmerries"; +exports.name = "MysticalSeries [madara]"; +exports.icon = "multisrc/madara/icons/mysticalseries.png"; +exports.version = "1.0.0"; +exports.site = "https://mysticalmerries.com/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "series" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (true) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "series" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/NeoSekaiTranslations[madara].js b/js/plugins/english/NeoSekaiTranslations[madara].js new file mode 100644 index 0000000..ab831b9 --- /dev/null +++ b/js/plugins/english/NeoSekaiTranslations[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "neosekaiTLS"; +exports.name = "NeoSekai Translations [madara]"; +exports.icon = "multisrc/madara/icons/neosekaitranslations.png"; +exports.version = "1.0.0"; +exports.site = "https://www.neosekaitranslations.com/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novel" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (false) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/NocturneTranslations[madara].js b/js/plugins/english/NocturneTranslations[madara].js new file mode 100644 index 0000000..a758c1e --- /dev/null +++ b/js/plugins/english/NocturneTranslations[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "nocturneTLS"; +exports.name = "Nocturne Translations [madara]"; +exports.icon = "multisrc/madara/icons/nocturnetranslations.png"; +exports.version = "1.0.0"; +exports.site = "https://nocturnetls.net/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novels" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (false) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "novels" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/NoobchanTranslation[madara].js b/js/plugins/english/NoobchanTranslation[madara].js new file mode 100644 index 0000000..006cd01 --- /dev/null +++ b/js/plugins/english/NoobchanTranslation[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "noobchanTL"; +exports.name = "Noobchan Translation [madara]"; +exports.icon = "multisrc/madara/icons/noobchantranslation.png"; +exports.version = "1.0.0"; +exports.site = "https://noobchan.xyz/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novel" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (true) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/NovelFullMe.js b/js/plugins/english/NovelFullMe.js new file mode 100644 index 0000000..5c2345a --- /dev/null +++ b/js/plugins/english/NovelFullMe.js @@ -0,0 +1,131 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.icon = exports.version = exports.site = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +exports.id = "NF.me"; +exports.name = "NovelFull.me"; +exports.site = "https://novelfull.me/"; +exports.version = "1.0.0"; +exports.icon = "src/en/novelfullme/icon.png"; +const baseUrl = exports.site; +const popularNovels = function (page) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}popular?page=${page}`; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".book-item").each(function () { + var _a; + const novelName = loadedCheerio(this).find(".title").text(); + const novelCover = "https:" + loadedCheerio(this).find("img").attr("data-src"); + const novelUrl = baseUrl + + ((_a = loadedCheerio(this).find(".title a").attr("href")) === null || _a === void 0 ? void 0 : _a.substring(1)); + const novel = { name: novelName, cover: novelCover, url: novelUrl }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + const url = novelUrl; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let novel = { + url, + name: "", + cover: "", + author: "", + status: "", + genres: "", + summary: "", + chapters: [], + }; + novel.name = loadedCheerio(".name h1").text().trim(); + novel.cover = + "https:" + loadedCheerio(".img-cover img").attr("data-src"); + novel.summary = loadedCheerio("body > div.layout > div.main-container.book-details > div > div.row.no-gutters > div.col-lg-8 > div.mt-1 > div.section.box.mt-1.summary > div.section-body > p.content") + .text() + .trim(); + novel.author = "Unknown"; + novel.status = (_a = loadedCheerio("body > div.layout > div.main-container.book-details > div > div.row.no-gutters > div.col-lg-8 > div.book-info > div.detail > div.meta.box.mt-1.p-10 > p:nth-child(1) > a > span") + .text()) === null || _a === void 0 ? void 0 : _a.trim(); + novel.genres = (_b = loadedCheerio("body > div.layout > div.main-container.book-details > div > div.row.no-gutters > div.col-lg-8 > div.book-info > div.detail > div.meta.box.mt-1.p-10 > p:nth-child(2)") + .text()) === null || _b === void 0 ? void 0 : _b.replace("Genres :", "").replace(/[\s\n]+/g, " ").trim(); + let chapters = []; + const chaptersUrl = novelUrl.replace(baseUrl, "https://novelfull.me/api/novels/") + + "/chapters?source=detail"; + const chaptersRequest = yield (0, fetch_1.fetchApi)(chaptersUrl); + const chaptersHtml = yield chaptersRequest.text(); + loadedCheerio = (0, cheerio_1.load)(chaptersHtml); + loadedCheerio("li").each(function () { + const chapterName = loadedCheerio(this) + .find(".chapter-title") + .text() + .trim(); + const releaseDate = loadedCheerio(this) + .find(".chapter-update") + .text() + .trim(); + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + if (!chapterUrl) + return; + chapters.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + novel.chapters = chapters.reverse(); + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = chapterUrl; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio("#listen-chapter").remove(); + loadedCheerio("#google_translate_element").remove(); + const chapterText = loadedCheerio(".chapter__content").html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}search?status=all&sort=views&q=${searchTerm}`; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".book-item").each(function () { + var _a; + const novelName = loadedCheerio(this).find(".title").text(); + const novelCover = "https:" + loadedCheerio(this).find("img").attr("data-src"); + const novelUrl = baseUrl + + ((_a = loadedCheerio(this).find(".title a").attr("href")) === null || _a === void 0 ? void 0 : _a.substring(1)); + const novel = { name: novelName, cover: novelCover, url: novelUrl }; + novels.push(novel); + }); + return novels; + }); +}; +exports.searchNovels = searchNovels; +exports.fetchImage = fetch_1.fetchFile; diff --git a/js/plugins/english/NovelMultiverse[madara].js b/js/plugins/english/NovelMultiverse[madara].js new file mode 100644 index 0000000..e3dca56 --- /dev/null +++ b/js/plugins/english/NovelMultiverse[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "novelmultiverse"; +exports.name = "NovelMultiverse [madara]"; +exports.icon = "multisrc/madara/icons/novelmultiverse.png"; +exports.version = "1.0.0"; +exports.site = "https://www.novelmultiverse.com/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novel" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (false) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/NovelOnline.js b/js/plugins/english/NovelOnline.js new file mode 100644 index 0000000..dc2d8e1 --- /dev/null +++ b/js/plugins/english/NovelOnline.js @@ -0,0 +1,104 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.parseChapter = exports.popularNovels = exports.parseNovelAndChapters = exports.searchNovels = exports.version = exports.icon = exports.site = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +exports.id = "NO.net"; +exports.name = "novelsOnline"; +exports.site = "https://novelsonline.net"; +exports.icon = "src/coverNotAvailable.jpg"; +exports.version = "1.0.0"; +const pluginId = exports.id; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)("https://novelsonline.net/sResults.php", { + headers: { + Accept: "*/*", + "Accept-Language": "pl,en-US;q=0.7,en;q=0.3", + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", + }, + method: "POST", + body: "q=" + encodeURIComponent(searchTerm), + }, pluginId).then((res) => res.text()); + let $ = (0, cheerio_1.load)(result); + const headers = $("li"); + return headers + .map((i, h) => { + const novelName = $(h).text(); + const novelUrl = $(h).find("a").attr("href"); + const novelCover = $(h).find("img").attr("src"); + if (!novelUrl) { + return null; + } + return { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + }) + .get() + .filter((sr) => sr !== null); + }); +}; +exports.searchNovels = searchNovels; +const parseNovelAndChapters = function (novelUrl) { + return __awaiter(this, void 0, void 0, function* () { + let novel = { + url: novelUrl, + chapters: [], + }; + const result = yield (0, fetch_1.fetchApi)(novelUrl).then((res) => res.text()); + let $ = (0, cheerio_1.load)(result); + novel.name = $("h1").text(); + novel.cover = $(".novel-cover").find("a > img").attr("src"); + novel.author = $("div.novel-details > div:nth-child(5) > div.novel-detail-body") + .find("li") + .map((_, el) => $(el).text()) + .get() + .join(", "); + novel.genres = $("div.novel-details > div:nth-child(2) > div.novel-detail-body") + .find("li") + .map((_, el) => $(el).text()) + .get() + .join(","); + novel.summary = $("div.novel-right > div > div:nth-child(1) > div.novel-detail-body").text(); + novel.chapters = $("ul.chapter-chs > li > a") + .map((_, el) => { + const chapterUrl = $(el).attr("href"); + const chapterName = $(el).text(); + return { + name: chapterName, + releaseTime: "", + url: chapterUrl, + }; + }) + .get(); + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const popularNovels = function (page) { + return __awaiter(this, void 0, void 0, function* () { + return []; /** TO DO */ + }); +}; +exports.popularNovels = popularNovels; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)(chapterUrl).then((res) => res.text()); + let loadedCheerio = (0, cheerio_1.load)(result); + const chapterText = loadedCheerio("#contentall").html() || ""; + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +exports.fetchImage = fetch_1.fetchFile; diff --git a/js/plugins/english/NovelR18[madara].js b/js/plugins/english/NovelR18[madara].js new file mode 100644 index 0000000..d0366ad --- /dev/null +++ b/js/plugins/english/NovelR18[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "novelr18"; +exports.name = "NovelR18 [madara]"; +exports.icon = "multisrc/madara/icons/novelr18.png"; +exports.version = "1.0.0"; +exports.site = "https://novelr18.com/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novel" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (true) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "manga" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/NovelTranslate[madara].js b/js/plugins/english/NovelTranslate[madara].js new file mode 100644 index 0000000..6c72825 --- /dev/null +++ b/js/plugins/english/NovelTranslate[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "novelTL"; +exports.name = "NovelTranslate [madara]"; +exports.icon = "multisrc/madara/icons/noveltranslate.png"; +exports.version = "1.0.0"; +exports.site = "https://noveltranslate.com/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "all-novels" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (false) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "manga" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/Novelmtcom[readwn].js b/js/plugins/english/Novelmtcom[readwn].js new file mode 100644 index 0000000..2b4e35d --- /dev/null +++ b/js/plugins/english/Novelmtcom[readwn].js @@ -0,0 +1,164 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +// import dayjs from 'dayjs'; +const fetch_1 = require("@libs/fetch"); +// import { parseMadaraDate } from "@libs/parseMadaraDate"; +// import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; +// import { showToast } from "@libs/showToast"; +// import { Filter, FilterInputs } from "@libs/filterInputs"; +// import { NovelStatus } from '@libs/novelStatus'; +// import { defaultCover } from "@libs/defaultCover"; +exports.id = "Novelmt.com"; +exports.name = "Novelmt.com"; +exports.icon = "multisrc/readwn/icon/novelmt.png"; +exports.version = "1.0.0"; +exports.site = "https://www.novelmt.com/"; +// export const filters: Filter[] = []; +exports["protected"] = false; +const baseUrl = exports.site; +const popularNovels = function (page, { filters, showLatestNovels }) { + return __awaiter(this, void 0, void 0, function* () { + const novels = []; + const pageNo = page - 1; + const url = baseUrl + 'list/all/all-onclick-' + pageNo + '.html'; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('li.novel-item').each(function () { + const novelName = loadedCheerio(this).find('h4').text(); + const novelUrl = baseUrl + loadedCheerio(this).find('a').attr('href'); + const coverUri = loadedCheerio(this) + .find('.novel-cover > img') + .attr('data-src'); + const novelCover = baseUrl + coverUri; + const novel = { name: novelName, cover: novelCover, url: novelUrl }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)(novelUrl); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + const novel = { + url: novelUrl, + chapters: [], + }; + novel.name = loadedCheerio('h1.novel-title').text(); + const coverUri = loadedCheerio('figure.cover > img').attr('data-src'); + novel.cover = baseUrl + coverUri; + novel.summary = loadedCheerio('.summary') + .text() + .replace('Summary', '') + .trim(); + novel.genres = ''; + loadedCheerio('div.categories > ul > li').each(function () { + novel.genres += loadedCheerio(this).text().trim() + ','; + }); + loadedCheerio('div.header-stats > span').each(function () { + if (loadedCheerio(this).find('small').text() === 'Status') { + novel.status = loadedCheerio(this).find('strong').text(); + } + }); + novel.genres = novel.genres.slice(0, -1); + novel.author = loadedCheerio('span[itemprop=author]').text(); + let novelChapters = []; + const novelId = novelUrl.replace('.html', '').replace(baseUrl, ''); + const latestChapterNo = parseInt(loadedCheerio('.header-stats') + .find('span > strong') + .first() + .text() + .trim()); + let lastChapterNo = 1; + loadedCheerio('.chapter-list li').each(function () { + var _a; + const chapterName = loadedCheerio(this) + .find('a .chapter-title') + .text() + .trim(); + const chapterUrl = baseUrl + ((_a = loadedCheerio(this).find('a').attr('href')) === null || _a === void 0 ? void 0 : _a.trim()); + const releaseDate = loadedCheerio(this) + .find('a .chapter-update') + .text() + .trim(); + lastChapterNo = parseInt(loadedCheerio(this).find('a .chapter-no').text().trim()); + const chapter = { name: chapterName, releaseTime: releaseDate, url: chapterUrl }; + novelChapters.push(chapter); + }); + // Itterate once more before loop to finish off + lastChapterNo++; + for (let i = lastChapterNo; i <= latestChapterNo; i++) { + const chapterName = 'Chapter ' + i; + const chapterUrl = baseUrl + novelId + '_' + i + '.html'; + const releaseDate = null; + const chapter = { name: chapterName, releaseTime: releaseDate, url: chapterUrl }; + novelChapters.push(chapter); + } + novel.chapters = novelChapters; + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)(chapterUrl); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.chapter-content').html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const searchUrl = baseUrl + 'e/search/index.php'; + const result = yield (0, fetch_1.fetchApi)(searchUrl, { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Referer: baseUrl + 'search.html', + Origin: baseUrl, + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36', + }, + method: 'POST', + body: JSON.stringify({ + show: 'title', + tempid: 1, + tbname: 'news', + keyboard: searchTerm, + }), + }); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('li.novel-item').each(function () { + const novelName = loadedCheerio(this).find('h4').text(); + const novelUrl = baseUrl + loadedCheerio(this).find('a').attr('href'); + const coverUri = baseUrl + loadedCheerio(this).find('img').attr('data-src'); + const novelCover = baseUrl + coverUri; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url, {}); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/Novelroomnet[madara].js b/js/plugins/english/Novelroomnet[madara].js new file mode 100644 index 0000000..557f576 --- /dev/null +++ b/js/plugins/english/Novelroomnet[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "novelroom"; +exports.name = "Novelroom.net [madara]"; +exports.icon = "multisrc/madara/icons/novelroom.net.png"; +exports.version = "1.0.0"; +exports.site = "https://novelroom.net/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "manga" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (false) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "manga" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/Novelstic[madara].js b/js/plugins/english/Novelstic[madara].js new file mode 100644 index 0000000..972496c --- /dev/null +++ b/js/plugins/english/Novelstic[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "novelstic"; +exports.name = "Novelstic [madara]"; +exports.icon = "multisrc/madara/icons/novelstic.png"; +exports.version = "1.0.0"; +exports.site = "https://novelstic.com/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novel" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (true) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/OnlyMTL[madara].js b/js/plugins/english/OnlyMTL[madara].js new file mode 100644 index 0000000..9f55ae4 --- /dev/null +++ b/js/plugins/english/OnlyMTL[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "onlymtl"; +exports.name = "OnlyMTL [madara]"; +exports.icon = "multisrc/madara/icons/onlymtl.png"; +exports.version = "1.0.0"; +exports.site = "https://www.onlymtl.com/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novel" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (true) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/ReadWebNovels[madara].js b/js/plugins/english/ReadWebNovels[madara].js new file mode 100644 index 0000000..df2f95d --- /dev/null +++ b/js/plugins/english/ReadWebNovels[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "readwebnovels"; +exports.name = "ReadWebNovels [madara]"; +exports.icon = "multisrc/madara/icons/readwebnovels.png"; +exports.version = "1.0.0"; +exports.site = "https://readwebnovels.net/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novel" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (false) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/Readwncom[readwn].js b/js/plugins/english/Readwncom[readwn].js new file mode 100644 index 0000000..b9bfcac --- /dev/null +++ b/js/plugins/english/Readwncom[readwn].js @@ -0,0 +1,164 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +// import dayjs from 'dayjs'; +const fetch_1 = require("@libs/fetch"); +// import { parseMadaraDate } from "@libs/parseMadaraDate"; +// import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; +// import { showToast } from "@libs/showToast"; +// import { Filter, FilterInputs } from "@libs/filterInputs"; +// import { NovelStatus } from '@libs/novelStatus'; +// import { defaultCover } from "@libs/defaultCover"; +exports.id = "Readwn.com"; +exports.name = "Readwn.com"; +exports.icon = "multisrc/readwn/icon/readwn.png"; +exports.version = "1.0.0"; +exports.site = "https://www.readwn.com/"; +// export const filters: Filter[] = []; +exports["protected"] = false; +const baseUrl = exports.site; +const popularNovels = function (page, { filters, showLatestNovels }) { + return __awaiter(this, void 0, void 0, function* () { + const novels = []; + const pageNo = page - 1; + const url = baseUrl + 'list/all/all-onclick-' + pageNo + '.html'; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('li.novel-item').each(function () { + const novelName = loadedCheerio(this).find('h4').text(); + const novelUrl = baseUrl + loadedCheerio(this).find('a').attr('href'); + const coverUri = loadedCheerio(this) + .find('.novel-cover > img') + .attr('data-src'); + const novelCover = baseUrl + coverUri; + const novel = { name: novelName, cover: novelCover, url: novelUrl }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)(novelUrl); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + const novel = { + url: novelUrl, + chapters: [], + }; + novel.name = loadedCheerio('h1.novel-title').text(); + const coverUri = loadedCheerio('figure.cover > img').attr('data-src'); + novel.cover = baseUrl + coverUri; + novel.summary = loadedCheerio('.summary') + .text() + .replace('Summary', '') + .trim(); + novel.genres = ''; + loadedCheerio('div.categories > ul > li').each(function () { + novel.genres += loadedCheerio(this).text().trim() + ','; + }); + loadedCheerio('div.header-stats > span').each(function () { + if (loadedCheerio(this).find('small').text() === 'Status') { + novel.status = loadedCheerio(this).find('strong').text(); + } + }); + novel.genres = novel.genres.slice(0, -1); + novel.author = loadedCheerio('span[itemprop=author]').text(); + let novelChapters = []; + const novelId = novelUrl.replace('.html', '').replace(baseUrl, ''); + const latestChapterNo = parseInt(loadedCheerio('.header-stats') + .find('span > strong') + .first() + .text() + .trim()); + let lastChapterNo = 1; + loadedCheerio('.chapter-list li').each(function () { + var _a; + const chapterName = loadedCheerio(this) + .find('a .chapter-title') + .text() + .trim(); + const chapterUrl = baseUrl + ((_a = loadedCheerio(this).find('a').attr('href')) === null || _a === void 0 ? void 0 : _a.trim()); + const releaseDate = loadedCheerio(this) + .find('a .chapter-update') + .text() + .trim(); + lastChapterNo = parseInt(loadedCheerio(this).find('a .chapter-no').text().trim()); + const chapter = { name: chapterName, releaseTime: releaseDate, url: chapterUrl }; + novelChapters.push(chapter); + }); + // Itterate once more before loop to finish off + lastChapterNo++; + for (let i = lastChapterNo; i <= latestChapterNo; i++) { + const chapterName = 'Chapter ' + i; + const chapterUrl = baseUrl + novelId + '_' + i + '.html'; + const releaseDate = null; + const chapter = { name: chapterName, releaseTime: releaseDate, url: chapterUrl }; + novelChapters.push(chapter); + } + novel.chapters = novelChapters; + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)(chapterUrl); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.chapter-content').html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const searchUrl = baseUrl + 'e/search/index.php'; + const result = yield (0, fetch_1.fetchApi)(searchUrl, { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Referer: baseUrl + 'search.html', + Origin: baseUrl, + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36', + }, + method: 'POST', + body: JSON.stringify({ + show: 'title', + tempid: 1, + tbname: 'news', + keyboard: searchTerm, + }), + }); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('li.novel-item').each(function () { + const novelName = loadedCheerio(this).find('h4').text(); + const novelUrl = baseUrl + loadedCheerio(this).find('a').attr('href'); + const coverUri = baseUrl + loadedCheerio(this).find('img').attr('data-src'); + const novelCover = baseUrl + coverUri; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url, {}); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/SleepyTranslations[madara].js b/js/plugins/english/SleepyTranslations[madara].js new file mode 100644 index 0000000..ccf3084 --- /dev/null +++ b/js/plugins/english/SleepyTranslations[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "sleeptTLS"; +exports.name = "SleepyTranslations [madara]"; +exports.icon = "multisrc/madara/icons/sleepytranslations.png"; +exports.version = "1.0.0"; +exports.site = "https://sleepytranslations.com/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "series" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (true) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "series" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/SonicMTL[madara].js b/js/plugins/english/SonicMTL[madara].js new file mode 100644 index 0000000..06a35be --- /dev/null +++ b/js/plugins/english/SonicMTL[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "sonicmtl"; +exports.name = "SonicMTL [madara]"; +exports.icon = "multisrc/madara/icons/sonicmtl.png"; +exports.version = "1.0.0"; +exports.site = "https://www.sonicmtl.com/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novel" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (true) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/SugarBabies[madara].js b/js/plugins/english/SugarBabies[madara].js new file mode 100644 index 0000000..9450dcf --- /dev/null +++ b/js/plugins/english/SugarBabies[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "sugarbbscan"; +exports.name = "Sugar Babies [madara]"; +exports.icon = "multisrc/madara/icons/sugarbabies.png"; +exports.version = "1.0.0"; +exports.site = "https://sugarbbscan.com/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "series" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (false) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "series" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/SweetEscapeTranslations[madara].js b/js/plugins/english/SweetEscapeTranslations[madara].js new file mode 100644 index 0000000..641bb66 --- /dev/null +++ b/js/plugins/english/SweetEscapeTranslations[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "sweetEscapeTL"; +exports.name = "Sweet Escape Translations [madara]"; +exports.icon = "multisrc/madara/icons/sweetescapetranslations.png"; +exports.version = "1.0.0"; +exports.site = "https://sweetescapetranslations.com/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "manga" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (false) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "manga" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/WebNovelLover[madara].js b/js/plugins/english/WebNovelLover[madara].js new file mode 100644 index 0000000..15bac6a --- /dev/null +++ b/js/plugins/english/WebNovelLover[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "webnovelover"; +exports.name = "WebNovelLover [madara]"; +exports.icon = "multisrc/madara/icons/webnovellover.png"; +exports.version = "1.0.0"; +exports.site = "https://www.webnovelover.com/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novel" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (false) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/WuxiaWorldSite[madara].js b/js/plugins/english/WuxiaWorldSite[madara].js new file mode 100644 index 0000000..9f6d96c --- /dev/null +++ b/js/plugins/english/WuxiaWorldSite[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "wuxiaworld.site"; +exports.name = "WuxiaWorld.Site [madara]"; +exports.icon = "multisrc/madara/icons/wuxiaworld.site.png"; +exports.version = "1.0.0"; +exports.site = "https://wuxiaworld.site/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novel" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (true) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/ZetroTranslation[madara].js b/js/plugins/english/ZetroTranslation[madara].js new file mode 100644 index 0000000..e8e3032 --- /dev/null +++ b/js/plugins/english/ZetroTranslation[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "zetroTL"; +exports.name = "Zetro Translation [madara]"; +exports.icon = "multisrc/madara/icons/zetrotranslation.png"; +exports.version = "1.0.0"; +exports.site = "https://zetrotranslation.com/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novel" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (false) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/ZinnNovel[madara].js b/js/plugins/english/ZinnNovel[madara].js new file mode 100644 index 0000000..817ea81 --- /dev/null +++ b/js/plugins/english/ZinnNovel[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "zinnovel"; +exports.name = "ZinnNovel [madara]"; +exports.icon = "multisrc/madara/icons/zinnnovel.png"; +exports.version = "1.0.0"; +exports.site = "https://zinnovel.com/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "manga" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (false) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "manga" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/allnovelfull.js b/js/plugins/english/allnovelfull.js new file mode 100644 index 0000000..edcc598 --- /dev/null +++ b/js/plugins/english/allnovelfull.js @@ -0,0 +1,178 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.icon = exports.version = exports.site = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +exports.id = "ANF.com"; +exports.name = "AllNovelFull"; +exports.site = "https://allnovelfull.com"; +exports.version = "1.0.0"; +exports.icon = "src/en/allnovelfull/icon.png"; +const popularNovels = function (page) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${exports.site}/most-popular?page=${page}`; + const result = yield (0, fetch_1.fetchApi)(url, {}); + if (!result.ok) { + console.error("Cloudflare error"); + // console.error(await result.text()); + // TODO: Cloudflare protection or other error + return []; + } + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + const novels = []; + loadedCheerio(".col-truyen-main .list-truyen .row").each(function () { + const novelName = loadedCheerio(this) + .find("h3.truyen-title > a") + .text(); + const novelCover = exports.site + loadedCheerio(this).find("img.cover").attr("src"); + const novelUrl = exports.site + loadedCheerio(this).find("h3.truyen-title > a").attr("href"); + const novel = { + url: novelUrl, + name: novelName, + cover: novelCover, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = novelUrl; + const result = yield (0, fetch_1.fetchApi)(url); + if (!result.ok) { + console.error("Cloudflare error"); + // console.error(await result.text()); + // TODO: Cloudflare protection or other error + return { url, chapters: [] }; + } + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let novel = { + url: novelUrl, + }; + novel.name = loadedCheerio("div.book > img").attr("alt"); + novel.cover = exports.site + loadedCheerio("div.book > img").attr("src"); + novel.summary = loadedCheerio("div.desc-text").text().trim(); + novel.author = loadedCheerio("div.info > div > h3") + .filter(function () { + return loadedCheerio(this).text().trim() === "Author:"; + }) + .siblings() + .text(); + novel.genres = loadedCheerio("div.info > div") + .filter(function () { + return (loadedCheerio(this).find("h3").text().trim() === "Genre:"); + }) + .text() + .replace("Genre:", ""); + novel.status = loadedCheerio("div.info > div > h3") + .filter(function () { + return loadedCheerio(this).text().trim() === "Status:"; + }) + .next() + .text(); + const novelId = loadedCheerio("#rating").attr("data-novel-id"); + function getChapters(id) { + return __awaiter(this, void 0, void 0, function* () { + const chapterListUrl = exports.site + "/ajax/chapter-option?novelId=" + id; + const data = yield (0, fetch_1.fetchApi)(chapterListUrl); + if (!data.ok) { + console.error("Cloudflare error"); + // console.error(await result.text()); + // TODO: Cloudflare protection or other error + return []; + } + const chapters = yield data.text(); + loadedCheerio = (0, cheerio_1.load)(chapters); + const novelChapters = []; + loadedCheerio("select > option").each(function () { + var _a; + let chapterName = loadedCheerio(this).text(); + let releaseDate = null; + let chapterUrl = (_a = loadedCheerio(this) + .attr("value")) === null || _a === void 0 ? void 0 : _a.replace(`/${novelUrl}`, ""); + if (chapterUrl) { + novelChapters.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + } + }); + return novelChapters; + }); + } + if (novelId) { + novel.chapters = yield getChapters(novelId); + } + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = exports.site + chapterUrl; + const result = yield (0, fetch_1.fetchApi)(url); + if (!result.ok) { + console.error("Cloudflare error"); + // console.error(await result.text()); + // TODO: Cloudflare protection or other error + return "Cloudflare protected site!"; + } + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio("#chapter-content").html() || ""; + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${exports.site}/search?keyword=${searchTerm}`; + const result = yield (0, fetch_1.fetchApi)(url); + if (!result.ok) { + console.error(yield result.text()); + // TODO: Cloudflare protection or other error + return []; + } + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio("div.col-truyen-main > div.list-truyen > .row").each(function () { + const novelUrl = exports.site + + loadedCheerio(this).find("h3.truyen-title > a").attr("href"); + const novelName = loadedCheerio(this) + .find("h3.truyen-title > a") + .text(); + const novelCover = exports.site + loadedCheerio(this).find("img").attr("src"); + if (novelUrl) { + novels.push({ + url: novelUrl, + name: novelName, + cover: novelCover, + }); + } + }); + return novels; + }); +}; +exports.searchNovels = searchNovels; +const fetchImage = function (url, init) { + return __awaiter(this, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url, init); + }); +}; +exports.fetchImage = fetchImage; +exports.protected = false; diff --git a/js/plugins/english/bestlightnovel.js b/js/plugins/english/bestlightnovel.js new file mode 100644 index 0000000..2596d0b --- /dev/null +++ b/js/plugins/english/bestlightnovel.js @@ -0,0 +1,150 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.icon = exports.version = exports.site = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const defaultCover_1 = require("@libs/defaultCover"); +const fetch_1 = require("@libs/fetch"); +const novelStatus_1 = require("@libs/novelStatus"); +exports.id = "BLN.com"; +exports.name = "BestLightNovel"; +exports.site = "https://bestlightnovel.com/"; +exports.version = "1.0.0"; +exports.icon = "src/en/bestlightnovel/icon.png"; +const popularNovels = function (page) { + return __awaiter(this, void 0, void 0, function* () { + const url = exports.site + "novel_list?type=topview&category=all&state=all&page=1" + page; + const result = yield (0, fetch_1.fetchApi)(url); + if (!result.ok) { + console.error(yield result.text()); + // TODO: Cloudflare protection or other error + return []; + } + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".update_item.list_category").each(function () { + const novelName = loadedCheerio(this).find("h3 > a").text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this).find("h3 > a").attr("href"); + if (!novelUrl) { + // TODO: Handle error + console.error("No novel url!"); + return; + } + const novel = { + name: novelName, + url: novelUrl, + cover: novelCover, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + var _a, _b, _c; + return __awaiter(this, void 0, void 0, function* () { + const url = novelUrl; + const result = yield (0, fetch_1.fetchApi)(url); + if (!result.ok) { + console.error(yield result.text()); + // TODO: Cloudflare protection + return { url, chapters: [] }; + } + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let novel = { + url, + name: "", + cover: "", + author: "", + status: novelStatus_1.NovelStatus.Unknown, + genres: "", + summary: "", + chapters: [], + }; + novel.name = loadedCheerio(".truyen_info_right h1").text().trim(); + novel.cover = + loadedCheerio(".info_image img").attr("src") || defaultCover_1.defaultCover; + novel.summary = (_a = loadedCheerio("#noidungm").text()) === null || _a === void 0 ? void 0 : _a.trim(); + novel.author = (_b = loadedCheerio("#main_body > div.cotgiua > div.truyen_info_wrapper > div.truyen_info > div.entry-header > div.truyen_if_wrap > ul > li:nth-child(2) > a") + .text()) === null || _b === void 0 ? void 0 : _b.trim(); + let status = (_c = loadedCheerio("#main_body > div.cotgiua > div.truyen_info_wrapper > div.truyen_info > div.entry-header > div.truyen_if_wrap > ul > li:nth-child(4) > a") + .text()) === null || _c === void 0 ? void 0 : _c.trim(); + novel.status = + status === "ONGOING" + ? novelStatus_1.NovelStatus.Ongoing + : status === "COMPLETED" + ? novelStatus_1.NovelStatus.Completed + : novelStatus_1.NovelStatus.Unknown; + let novelChapters = []; + loadedCheerio(".chapter-list div.row").each(function () { + const chapterName = loadedCheerio(this).find("a").text().trim(); + const releaseDate = loadedCheerio(this) + .find("span:nth-child(2)") + .text() + .trim(); + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + if (!chapterUrl) { + // TODO: Handle error + console.error("No chapter url!"); + return; + } + novelChapters.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + novel.chapters = novelChapters.reverse(); + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = chapterUrl; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio("#vung_doc").html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${exports.site}search_novels/${searchTerm}`; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".update_item.list_category").each(function () { + const novelName = loadedCheerio(this).find("h3 > a").text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this).find("h3 > a").attr("href"); + if (!novelUrl) { + // TODO: Handle error + console.error("No novel url!"); + return; + } + const novel = { name: novelName, cover: novelCover, url: novelUrl }; + novels.push(novel); + }); + return novels; + }); +}; +exports.searchNovels = searchNovels; +const fetchImage = (...args) => (0, fetch_1.fetchFile)(...args); +exports.fetchImage = fetchImage; +exports.protected = false; diff --git a/js/plugins/english/comrademao.js b/js/plugins/english/comrademao.js new file mode 100644 index 0000000..dd3f888 --- /dev/null +++ b/js/plugins/english/comrademao.js @@ -0,0 +1,147 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.icon = exports.version = exports.site = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "comrademao"; +exports.name = "Comrade Mao"; +exports.site = "https://comrademao.com/"; +exports.version = "1.0.0"; +exports.icon = "src/en/comrademao/icon.png"; +const popularNovels = function (page) { + return __awaiter(this, void 0, void 0, function* () { + let url = exports.site + "page/" + page + "/?post_type=novel"; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".listupd") + .find("div.bs") + .each(function () { + const novelName = loadedCheerio(this).find(".tt").text().trim(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this).find("a").attr("href"); + if (!novelUrl) { + // TODO: Handle error + console.error("No novel url!"); + return; + } + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = novelUrl; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let novel = { url }; + novel.name = loadedCheerio(".entry-title").text().trim(); + novel.cover = loadedCheerio("div.thumbook > div > img").attr("src"); + novel.summary = loadedCheerio("div.infox > div:nth-child(6) > span > p") + .text() + .trim(); + novel.genres = loadedCheerio("div.infox > div:nth-child(4) > span") + .text() + .replace(/\s/g, ""); + novel.status = loadedCheerio("div.infox > div:nth-child(3) > span") + .text() + .trim(); + novel.author = loadedCheerio("div.infox > div:nth-child(2) > span") + .text() + .trim(); + let novelChapters = []; + loadedCheerio("#chapterlist") + .find("li") + .each(function () { + const releaseDate = (0, dayjs_1.default)(loadedCheerio(this).find(".chapterdate").text()).format("LL"); + const chapterName = loadedCheerio(this) + .find(".chapternum") + .text(); + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + if (!chapterUrl) { + // TODO: Handle error + console.error("No chapter url!"); + return; + } + novelChapters.push({ + name: chapterName, + url: chapterUrl, + releaseTime: releaseDate, + }); + }); + novel.chapters = novelChapters.reverse(); + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = chapterUrl; + const result = yield (0, fetch_1.fetchApi)(url); + if (!result.ok) { + const err = yield result.text(); + console.error(err); + // TODO: Cloudflare protection or other error + return "Error!" + err; + } + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let chapterText = loadedCheerio("#chaptercontent").html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + const url = exports.site + "?s=" + searchTerm + "&post_type=novel"; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".listupd") + .find("div.bs") + .each(function () { + const novelName = loadedCheerio(this).find(".tt").text().trim(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this).find("a").attr("href"); + if (!novelUrl) { + // TODO: Handle error + console.error("No novel url!"); + return; + } + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.searchNovels = searchNovels; +const fetchImage = (...args) => (0, fetch_1.fetchFile)(...args); +exports.fetchImage = fetchImage; +exports.protected = false; diff --git a/js/plugins/english/divinedaolibrary.js b/js/plugins/english/divinedaolibrary.js new file mode 100644 index 0000000..1d97635 --- /dev/null +++ b/js/plugins/english/divinedaolibrary.js @@ -0,0 +1,134 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.icon = exports.version = exports.site = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +exports.id = "DDL.com"; +exports.name = "Divine Dao Library"; +exports.site = "https://www.divinedaolibrary.com/"; +exports.version = "1.0.0"; +exports.icon = "src/`en/divinedaolibrary/icon.png"; +const baseUrl = exports.site; +const popularNovels = function (page) { + return __awaiter(this, void 0, void 0, function* () { + let url = baseUrl + "novels"; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio("#main") + .find("li") + .each(function () { + const novelName = loadedCheerio(this).find("a").text(); + const novelCover = defaultCover_1.defaultCover; + const novelUrl = loadedCheerio(this).find(" a").attr("href"); + if (!novelUrl) + return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = novelUrl; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let novel = { url }; + novel.name = loadedCheerio("h1.entry-title").text().trim(); + novel.cover = + loadedCheerio(".entry-content").find("img").attr("data-ezsrc") || + defaultCover_1.defaultCover; + novel.summary = loadedCheerio("#main > article > div > p:nth-child(6)") + .text() + .trim(); + novel.author = loadedCheerio("#main > article > div > h3:nth-child(2)") + .text() + .replace(/Author:/g, "") + .trim(); + let novelChapters = []; + loadedCheerio("#main") + .find("li > span > a") + .each(function () { + const chapterName = loadedCheerio(this).text().trim(); + const releaseDate = null; + const chapterUrl = loadedCheerio(this).attr("href"); + if (!chapterUrl) + return; + novelChapters.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + novel.chapters = novelChapters; + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = chapterUrl; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let chapterName = loadedCheerio(".entry-title").text().trim(); + let chapterText = loadedCheerio(".entry-content").html(); + if (!chapterText) { + chapterText = loadedCheerio(".page-header").html(); + } + chapterText = `

${chapterName}

` + chapterText; + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + let url = baseUrl + "novels"; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio("#main") + .find("li") + .each(function () { + const novelName = loadedCheerio(this).find("a").text(); + const novelCover = defaultCover_1.defaultCover; + const novelUrl = loadedCheerio(this).find(" a").attr("href"); + if (!novelUrl) + return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + novels = novels.filter((novel) => novel.name.toLowerCase().includes(searchTerm.toLowerCase())); + return novels; + }); +}; +exports.searchNovels = searchNovels; +const fetchImage = function (url, init) { + return __awaiter(this, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url, init); + }); +}; +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/earlynovel.js b/js/plugins/english/earlynovel.js new file mode 100644 index 0000000..f9bbe88 --- /dev/null +++ b/js/plugins/english/earlynovel.js @@ -0,0 +1,175 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.icon = exports.version = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const showToast_1 = require("@libs/showToast"); +const defaultCover_1 = require("@libs/defaultCover"); +exports.id = "earlynovel"; +exports.name = "Early Novel"; +exports.version = "1.0.0"; +exports.icon = "multisrc/madara/icons/latestnovel.png"; +exports.site = "https://earlynovel.net/"; +module.exports.protected = false; +const pluginId = exports.id; +const baseUrl = exports.site; +const popularNovels = function (page) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}most-popular?page=${page}`; + const body = yield (0, fetch_1.fetchApi)(url, {}, pluginId).then((r) => r.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".col-truyen-main > .list-truyen > .row").each(function () { + var _a; + const novelName = loadedCheerio(this) + .find("h3.truyen-title > a") + .attr("title"); + const novelCover = loadedCheerio(this).find(".lazyimg").attr("data-desk-image") || + loadedCheerio(this).find("img.cover").attr("src"); + const novelUrl = baseUrl + + ((_a = loadedCheerio(this) + .find("h3.truyen-title > a") + .attr("href")) === null || _a === void 0 ? void 0 : _a.slice(1)); + if (!novelUrl || !novelName) + return; + const novel = { + name: novelName, + cover: novelCover || defaultCover_1.defaultCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + (0, showToast_1.showToast)("Early Novel may take 20-30 seconds"); + const url = novelUrl; + const body = yield (0, fetch_1.fetchApi)(url, {}, pluginId).then((r) => r.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + const name = loadedCheerio(".book > img").attr("alt"); + const cover = loadedCheerio(".book > img").attr("src") || defaultCover_1.defaultCover; + const summary = loadedCheerio(".desc-text").text().trim(); + const novel = { + url, + chapters: [], + name, + summary, + cover, + }; + loadedCheerio(".info > div > h3").each(function () { + let detailName = loadedCheerio(this).text(); + let detail = loadedCheerio(this) + .siblings() + .map((i, el) => loadedCheerio(el).text()) + .toArray() + .join(","); + switch (detailName) { + case "Author:": + novel.author = detail; + break; + case "Status:": + novel.status = detail; + break; + case "Genre:": + novel.genres = detail; + break; + } + }); + //! Doesn't work since there are multiple pages and can't find source API + //# Since cannot find sourceAPI i try similar function to lightnovelpub + const delay = (ms) => new Promise((res) => setTimeout(res, ms)); + const lastPageStr = (_a = loadedCheerio('a:contains("Last ")') + .attr("title")) === null || _a === void 0 ? void 0 : _a.match(/(\d+)/g); + const lastPage = Number((lastPageStr === null || lastPageStr === void 0 ? void 0 : lastPageStr[1]) || "0"); + function getChapters() { + return __awaiter(this, void 0, void 0, function* () { + let chapter = []; + for (let i = 1; i <= lastPage; i++) { + const chaptersUrl = `${novelUrl}?page=${i}`; + const chaptersHtml = yield (0, fetch_1.fetchApi)(chaptersUrl, {}, pluginId).then((r) => r.text()); + loadedCheerio = (0, cheerio_1.load)(chaptersHtml); + loadedCheerio("ul.list-chapter > li").each(function () { + var _a; + const chapterName = loadedCheerio(this) + .find(".chapter-text") + .text() + .trim(); + const releaseDate = null; + const chapterHref = (_a = loadedCheerio(this) + .find("a") + .attr("href")) === null || _a === void 0 ? void 0 : _a.slice(1); + if (!chapterHref) + return; + const chapterUrl = baseUrl + chapterHref; + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + yield delay(1000); + } + return chapter; + }); + } + novel.chapters = yield getChapters(); + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const body = yield (0, fetch_1.fetchApi)(chapterUrl, {}, pluginId).then((r) => r.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio("#chapter-c").html() || ""; + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}search?keyword=${searchTerm}`; + const body = yield (0, fetch_1.fetchApi)(url, {}, pluginId).then((r) => r.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio("div.col-truyen-main > div.list-truyen > .row").each(function () { + const novelUrl = baseUrl + + loadedCheerio(this).find("h3.truyen-title > a").attr("href"); + const novelName = loadedCheerio(this) + .find("h3.truyen-title > a") + .text(); + const novelCover = baseUrl + loadedCheerio(this).find("img").attr("src"); + if (novelUrl) { + novels.push({ + url: novelUrl, + name: novelName, + cover: novelCover, + }); + } + }); + return novels; + }); +}; +exports.searchNovels = searchNovels; +const fetchImage = function fetchImage(url) { + return __awaiter(this, void 0, void 0, function* () { + const headers = { + Referer: baseUrl, + }; + return yield (0, fetch_1.fetchFile)(url, { headers: headers }); + }); +}; +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/fastnovel.js b/js/plugins/english/fastnovel.js new file mode 100644 index 0000000..ae689e5 --- /dev/null +++ b/js/plugins/english/fastnovel.js @@ -0,0 +1,153 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.icon = exports.version = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +exports.id = "fastnovel"; +exports.name = "Fast Novel"; +exports.version = "1.0.0"; +exports.icon = "src/en/fastnovel/icon.png"; +exports.site = "https://fastnovel.org/"; +exports.protected = false; +const pluginId = exports.id; +const baseUrl = exports.site; +const popularNovels = function (page) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}sort/p/?page=${page}`; + const body = yield (0, fetch_1.fetchApi)(url, {}, pluginId).then((r) => r.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".col-novel-main .list-novel .row").each(function () { + const novelName = loadedCheerio(this).find("h3.novel-title > a").text(); + const novelCover = loadedCheerio(this).find("img.cover").attr("src"); + const novelUrl = loadedCheerio(this) + .find("h3.novel-title > a") + .attr("href"); + if (!novelUrl) + return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = novelUrl; + const body = yield (0, fetch_1.fetchApi)(url, {}, pluginId).then((r) => r.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + const novel = { + url, + chapters: [], + }; + novel.name = loadedCheerio("div.book > img").attr("alt"); + novel.cover = loadedCheerio("div.book > img").attr("src"); + novel.summary = loadedCheerio("div.desc-text").text().trim(); + loadedCheerio("ul.info > li > h3").each(function () { + let detailName = loadedCheerio(this).text(); + let detail = loadedCheerio(this) + .siblings() + .map((i, el) => loadedCheerio(el).text()) + .toArray() + .join(","); + switch (detailName) { + case "Author:": + novel.author = detail; + break; + case "Status:": + novel.status = detail; + break; + case "Genre:": + novel.genres = detail; + break; + } + }); + const novelId = loadedCheerio("#rating").attr("data-novel-id"); + function getChapters(id) { + return __awaiter(this, void 0, void 0, function* () { + const chapterListUrl = baseUrl + "ajax/chapter-archive?novelId=" + id; + const data = yield (0, fetch_1.fetchApi)(chapterListUrl); + const chapterdata = yield data.text(); + loadedCheerio = (0, cheerio_1.load)(chapterdata); + let chapter = []; + loadedCheerio("ul.list-chapter > li").each(function () { + const chapterName = loadedCheerio(this).find("a").attr("title"); + const releaseDate = null; + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + if (!chapterName || !chapterUrl) + return; + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + return chapter; + }); + } + if (novelId) { + novel.chapters = yield getChapters(novelId); + } + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const body = yield (0, fetch_1.fetchApi)(chapterUrl, {}, pluginId).then((r) => r.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('#chr-content > div,h6,p[style="display: none;"]').remove(); + let chapterText = loadedCheerio("#chr-content").html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}search/?keyword=${searchTerm}`; + const body = yield (0, fetch_1.fetchApi)(url, {}, pluginId).then((r) => r.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio("div.col-novel-main > div.list-novel > .row").each(function () { + const novelUrl = loadedCheerio(this) + .find("h3.novel-title > a") + .attr("href"); + const novelName = loadedCheerio(this) + .find("h3.novel-title > a") + .text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + if (novelUrl) { + novels.push({ + url: novelUrl, + name: novelName, + cover: novelCover, + }); + } + }); + return novels; + }); +}; +exports.searchNovels = searchNovels; +const fetchImage = function (url) { + return __awaiter(this, void 0, void 0, function* () { + const headers = { + Referer: baseUrl, + }; + return yield (0, fetch_1.fetchFile)(url, { headers: headers }); + }); +}; +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/foxaholic.js b/js/plugins/english/foxaholic.js new file mode 100644 index 0000000..1aaeee9 --- /dev/null +++ b/js/plugins/english/foxaholic.js @@ -0,0 +1,155 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.icon = exports.version = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +exports.id = "foxaholic"; +exports.name = "Foxaholic"; +exports.version = "1.0.0"; +exports.icon = "src/en/foxaholic/icon.png"; +exports.site = "https://www.foxaholic.com/"; +module.exports.protected = false; +const pluginId = exports.id; +const baseUrl = exports.site; +const popularNovels = function (page) { + return __awaiter(this, void 0, void 0, function* () { + const link = `${baseUrl}novel/page/${page}/?m_orderby=rating`; + const result = yield (0, fetch_1.fetchApi)(link, {}, pluginId); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + const novels = []; + loadedCheerio(".page-item-detail").each(function () { + const novelName = loadedCheerio(this).find(".h5 > a").text(); + const novelCover = loadedCheerio(this).find("img").attr("data-src"); + const novelUrl = loadedCheerio(this).find(".h5 > a").attr("href"); + if (!novelUrl) + return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = novelUrl; + const result = yield (0, fetch_1.fetchApi)(url, {}, pluginId); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + const novel = { + url, + chapters: [], + }; + novel.name = loadedCheerio(".post-title > h1").text().trim(); + novel.cover = loadedCheerio(".summary_image > a > img").attr("data-src"); + loadedCheerio(".post-content_item").each(function () { + const detailName = loadedCheerio(this) + .find(".summary-heading > h5") + .text() + .trim(); + const detail = loadedCheerio(this).find(".summary-content").html(); + if (!detail) + return; + switch (detailName) { + case "Genre": + novel.genres = loadedCheerio(detail) + .children("a") + .map((i, el) => loadedCheerio(el).text()) + .toArray() + .join(","); + break; + case "Author": + novel.author = loadedCheerio(detail) + .children("a") + .map((i, el) => loadedCheerio(el).text()) + .toArray() + .join(", "); + break; + case "Novel": + novel.status = detail === null || detail === void 0 ? void 0 : detail.trim().replace(/G/g, "g"); + break; + } + }); + loadedCheerio(".description-summary > div.summary__content > div").remove(); + novel.summary = loadedCheerio(".description-summary > div.summary__content") + .text() + .trim(); + let chapter = []; + loadedCheerio(".wp-manga-chapter").each(function () { + const chapterName = loadedCheerio(this) + .find("a") + .text() + .replace(/[\t\n]/g, "") + .trim(); + const releaseDate = loadedCheerio(this).find("span").text().trim(); + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + if (!chapterUrl) + return; + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + novel.chapters = chapter.reverse(); + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)(chapterUrl, {}, pluginId); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio("img").removeAttr("srcset"); + let chapterText = loadedCheerio(".reading-content").html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}?s=${searchTerm}&post_type=wp-manga`; + const result = yield (0, fetch_1.fetchApi)(url, {}, pluginId); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + const novels = []; + loadedCheerio(".c-tabs-item__content").each(function () { + const novelName = loadedCheerio(this).find(".h4 > a").text(); + const novelCover = loadedCheerio(this).find("img").attr("data-src"); + const novelUrl = loadedCheerio(this).find(".h4 > a").attr("href"); + if (!novelUrl) + return; + novels.push({ + name: novelName, + url: novelUrl, + cover: novelCover, + }); + }); + return novels; + }); +}; +exports.searchNovels = searchNovels; +const fetchImage = function (url) { + return __awaiter(this, void 0, void 0, function* () { + const headers = { + Referer: baseUrl, + }; + return yield (0, fetch_1.fetchFile)(url, { headers: headers }); + }); +}; +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/freenovelupdates.js b/js/plugins/english/freenovelupdates.js new file mode 100644 index 0000000..6e139e6 --- /dev/null +++ b/js/plugins/english/freenovelupdates.js @@ -0,0 +1,119 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.icon = exports.version = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const showToast_1 = require("@libs/showToast"); +const isAbsoluteUrl_1 = require("@libs/isAbsoluteUrl"); +exports.id = "freenovelupdates"; +exports.name = "Free Novel Updates (Broken)"; +exports.version = "1.0.0"; +exports.icon = "src/en/freenovelupdates/icon.png"; +exports.site = "https://www.freenovelupdates.com"; +exports.protected = false; +const pluginId = exports.id; +const baseUrl = exports.site; +const popularNovels = function (page) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}genres/light-novel-1002`; + const body = yield (0, fetch_1.fetchApi)(url, {}, pluginId).then((r) => r.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".books-item").each(function () { + let novelUrl = loadedCheerio(this).find("a").attr("href"); + if (novelUrl && !(0, isAbsoluteUrl_1.isUrlAbsolute)(novelUrl)) { + novelUrl = baseUrl + novelUrl; + } + if (novelUrl) { + const novelName = loadedCheerio(this).find(".title").text().trim(); + let novelCover = loadedCheerio(this).find("img").attr("src"); + if (novelCover && !(0, isAbsoluteUrl_1.isUrlAbsolute)(novelCover)) { + novelCover = baseUrl + novelCover; + } + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + } + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = novelUrl; + const body = yield (0, fetch_1.fetchApi)(url, {}, pluginId).then((r) => r.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + const novel = { + url, + chapters: [], + }; + novel.name = loadedCheerio("h1").text(); + let novelCover = loadedCheerio(".img > img").attr("src"); + novel.cover = novelCover + ? (0, isAbsoluteUrl_1.isUrlAbsolute)(novelCover) + ? novelCover + : baseUrl + novelCover + : undefined; + novel.summary = loadedCheerio(".description-content").text().trim(); + novel.author = loadedCheerio(".author").text().trim(); + novel.genres = loadedCheerio(".category").text().trim(); + novel.status = loadedCheerio(".status").text().trim(); + loadedCheerio(".chapter").each(function () { + var _a; + let chapterUrl = loadedCheerio(this).find("a").attr("href"); + if (chapterUrl && !(0, isAbsoluteUrl_1.isUrlAbsolute)(chapterUrl)) { + chapterUrl = baseUrl + chapterUrl; + } + if (chapterUrl) { + const chapterName = loadedCheerio(this).find("a").text().trim(); + const releaseDate = null; + const chapter = { + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }; + (_a = novel.chapters) === null || _a === void 0 ? void 0 : _a.push(chapter); + } + }); + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const body = yield (0, fetch_1.fetchApi)(chapterUrl, {}, pluginId).then((r) => r.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio(".content").html() || ""; + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + (0, showToast_1.showToast)("Search is not available in this source"); + return []; + }); +}; +exports.searchNovels = searchNovels; +const fetchImage = function (url) { + return __awaiter(this, void 0, void 0, function* () { + const headers = { + Referer: baseUrl, + }; + return yield (0, fetch_1.fetchFile)(url, { headers: headers }); + }); +}; +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/freewebnovel.js b/js/plugins/english/freewebnovel.js new file mode 100644 index 0000000..71fee28 --- /dev/null +++ b/js/plugins/english/freewebnovel.js @@ -0,0 +1,141 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.icon = exports.version = exports.site = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +exports.id = "FWN.com"; +exports.name = "Web NOVEL"; +exports.site = "https://freewebnovel.com/"; +exports.version = "1.0.0"; +exports.icon = "src/en/freewebnovel/icon.png"; +const baseUrl = exports.site; +const popularNovels = function (page) { + return __awaiter(this, void 0, void 0, function* () { + let url = baseUrl + "completed-novel/" + page; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".li-row").each(function () { + const novelName = loadedCheerio(this).find(".tit").text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + let novelUrl = "https://freewebnovel.com" + + loadedCheerio(this).find("h3 > a").attr("href"); + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = novelUrl; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let novel = { + url, + }; + novel.name = loadedCheerio("h1.tit").text(); + novel.cover = loadedCheerio(".pic > img").attr("src"); + novel.genres = loadedCheerio("[title=Genre]") + .next() + .text() + .replace(/[\t\n]/g, ""); + novel.author = loadedCheerio("[title=Author]") + .next() + .text() + .replace(/[\t\n]/g, ""); + novel.status = loadedCheerio("[title=Status]") + .next() + .text() + .replace(/[\t\n]/g, ""); + let novelSummary = loadedCheerio(".inner").text().trim(); + novel.summary = novelSummary; + let novelChapters = []; + let latestChapter; + loadedCheerio("h3.tit").each(function (res) { + var _a; + if (loadedCheerio(this).find("a").text() === novel.name) { + latestChapter = (_a = loadedCheerio(this) + .next() + .find("span.s3") + .text() + .match(/(\d+)/)) === null || _a === void 0 ? void 0 : _a[0]; + } + }); + let prefixUrl = novelUrl.replace(".html", "/"); + for (let i = 1; i <= parseInt(latestChapter || "0", 10); i++) { + const chapterName = "Chapter " + i; + const releaseDate = null; + let chapterUrl = "chapter-" + i; + chapterUrl = `${prefixUrl}${chapterUrl}.html`; + const chapter = { + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }; + novelChapters.push(chapter); + } + novel.chapters = novelChapters; + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = chapterUrl; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let chapterText = loadedCheerio("div.txt").html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + const url = baseUrl + "search/"; + const formData = { + searchkey: searchTerm, + }; + const result = yield (0, fetch_1.fetchApi)(url, { + method: "POST", + body: JSON.stringify(formData), + }); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".li-row > .li > .con").each(function () { + const novelName = loadedCheerio(this).find(".tit").text(); + const novelCover = loadedCheerio(this) + .find(".pic > a > img") + .attr("data-cfsrc"); + let novelUrl = "https://freewebnovel.com" + + loadedCheerio(this).find("h3 > a").attr("href"); + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.searchNovels = searchNovels; +exports.fetchImage = fetch_1.fetchFile; diff --git a/js/plugins/english/lightnovelpub.js b/js/plugins/english/lightnovelpub.js new file mode 100644 index 0000000..05ccb5a --- /dev/null +++ b/js/plugins/english/lightnovelpub.js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.icon = exports.version = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +exports.id = "lightnovelpub"; +exports.name = "LightNovelPub"; +exports.version = "1.0.0"; +exports.icon = "src/en/lightnovelpub/icon.png"; +exports.site = "https://www.lightnovelpub.com/"; +exports.protected = true; +const baseUrl = exports.site; +const pluginId = exports.id; +const headers = { + Accept: "application/json", + "Content-Type": "application/json", +}; +const popularNovels = function (page) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}browse/all/popular/all/${page}`; + const body = yield (0, fetch_1.fetchApi)(url, {}, pluginId).then((r) => r.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const novels = []; + loadedCheerio(".novel-item.ads").remove(); + loadedCheerio(".novel-item").each(function () { + var _a; + const novelName = loadedCheerio(this) + .find(".novel-title") + .text() + .trim(); + const novelCover = loadedCheerio(this).find("img").attr("data-src"); + const novelUrl = baseUrl + + ((_a = loadedCheerio(this) + .find(".novel-title > a") + .attr("href")) === null || _a === void 0 ? void 0 : _a.substring(1)); + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + const url = novelUrl; + const body = yield (0, fetch_1.fetchApi)(url, {}, pluginId).then((r) => r.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + const novel = { + url, + chapters: [], + }; + novel.name = loadedCheerio("h1.novel-title").text().trim(); + novel.cover = loadedCheerio("figure.cover > img").attr("data-src"); + novel.genres = ""; + loadedCheerio(".categories > ul > li > a").each(function () { + novel.genres += loadedCheerio(this).text() + ","; + }); + novel.genres = novel.genres.slice(0, -1); + loadedCheerio("div.header-stats > span").each(function () { + if (loadedCheerio(this).find("small").text() === "Status") { + novel.status = loadedCheerio(this).find("strong").text(); + } + }); + novel.author = loadedCheerio(".author > a > span").text(); + novel.summary = loadedCheerio(".summary > .content").text().trim(); + const delay = (ms) => new Promise((res) => setTimeout(res, ms)); + let lastPage = 1; + lastPage = parseInt((_a = loadedCheerio("#novel > header > div.header-body.container > div.novel-info > div.header-stats > span:nth-child(1) > strong") + .text()) === null || _a === void 0 ? void 0 : _a.trim()); + lastPage = Math.ceil(lastPage / 100); + function getChapters() { + return __awaiter(this, void 0, void 0, function* () { + let chapter = []; + for (let i = 1; i <= lastPage; i++) { + const chaptersUrl = `${novelUrl}/chapters/page-${i}`; + const chaptersHtml = yield (0, fetch_1.fetchApi)(chaptersUrl, { headers: headers }, pluginId).then((r) => r.text()); + loadedCheerio = (0, cheerio_1.load)(chaptersHtml); + loadedCheerio(".chapter-list li").each(function () { + var _a; + const chapterName = loadedCheerio(this) + .find(".chapter-title") + .text() + .trim(); + const releaseDate = loadedCheerio(this) + .find(".chapter-update") + .text() + .trim(); + const chapterUrl = baseUrl + + ((_a = loadedCheerio(this) + .find("a") + .attr("href")) === null || _a === void 0 ? void 0 : _a.substring(1)); + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + yield delay(1000); + } + return chapter; + }); + } + novel.chapters = yield getChapters(); + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const body = yield (0, fetch_1.fetchApi)(chapterUrl, {}, pluginId).then((r) => r.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + let chapterText = loadedCheerio("#chapter-container").html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}lnsearchlive`; + const link = `${baseUrl}search`; + const response = yield (0, fetch_1.fetchApi)(link, {}, pluginId).then((r) => r.text()); + const token = (0, cheerio_1.load)(response); + let verifytoken = token("#novelSearchForm > input").attr("value"); + let formData = new FormData(); + formData.append("inputContent", searchTerm); + const body = yield (0, fetch_1.fetchApi)(url, { + method: "POST", + headers: { LNRequestVerifyToken: verifytoken }, + body: formData, + }, pluginId).then((r) => r.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + let results = JSON.parse(loadedCheerio("body").text()); + loadedCheerio = (0, cheerio_1.load)(results.resultview); + loadedCheerio(".novel-item").each(function () { + var _a; + const novelName = loadedCheerio(this) + .find("h4.novel-title") + .text() + .trim(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = baseUrl + ((_a = loadedCheerio(this).find("a").attr("href")) === null || _a === void 0 ? void 0 : _a.substring(1)); + novels.push({ + name: novelName, + url: novelUrl, + cover: novelCover, + }); + }); + return novels; + }); +}; +exports.searchNovels = searchNovels; +const fetchImage = function (url) { + return __awaiter(this, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url, { headers: headers }); + }); +}; +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/mtlreader.js b/js/plugins/english/mtlreader.js new file mode 100644 index 0000000..2dc9b1b --- /dev/null +++ b/js/plugins/english/mtlreader.js @@ -0,0 +1,122 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.icon = exports.version = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +exports.id = "mtlreader"; +exports.name = "MTL Reader"; +exports.version = "1.0.0"; +exports.icon = "src/en/mtlreader/icon.png"; +exports.site = "https://mtlreader.com/"; +exports.protected = false; +const pluginId = exports.id; +const baseUrl = exports.site; +const popularNovels = function (page) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}novels?page=${page}`; + const body = yield (0, fetch_1.fetchApi)(url, {}, pluginId).then((r) => r.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".col-md-4.col-sm-4").each(function () { + const novelName = loadedCheerio(this).find("h5").text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this).find("h5 > a").attr("href"); + if (!novelUrl) + return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = novelUrl; + const body = yield (0, fetch_1.fetchApi)(url, {}, pluginId).then((r) => r.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + const novel = { + url, + chapters: [], + }; + novel.name = loadedCheerio(".agent-title").text().trim(); + novel.cover = loadedCheerio(".agent-p-img > img").attr("src"); + novel.summary = loadedCheerio("#editdescription").text().trim(); + novel.author = loadedCheerio("i.fa-user") + .parent() + .text() + .replace("Author: ", "") + .trim(); + let chapter = []; + loadedCheerio("tr.spaceUnder").each(function () { + const chapterName = loadedCheerio(this).find("a").text().trim(); + const releaseDate = null; + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + if (!chapterUrl) + return; + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + novel.chapters = chapter; + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const body = yield (0, fetch_1.fetchApi)(chapterUrl, {}, pluginId).then((r) => r.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio(".container ins,script,p.mtlreader").remove(); + const chapterText = loadedCheerio(".container").html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}search?input=${searchTerm}`; + const body = yield (0, fetch_1.fetchApi)(url, {}, pluginId).then((r) => r.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".col-md-4.col-sm-4").each(function () { + const novelName = loadedCheerio(this).find("h5").text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this).find("h5 > a").attr("href"); + if (!novelUrl) + return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.searchNovels = searchNovels; +const fetchImage = function (url) { + return __awaiter(this, void 0, void 0, function* () { + const headers = { + Referer: baseUrl, + }; + return yield (0, fetch_1.fetchFile)(url, { headers: headers }); + }); +}; +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/nitroscans.js b/js/plugins/english/nitroscans.js new file mode 100644 index 0000000..d814ed6 --- /dev/null +++ b/js/plugins/english/nitroscans.js @@ -0,0 +1,167 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.icon = exports.version = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +exports.id = "nitroscans"; +exports.name = "Nitroscans"; +exports.version = "1.0.0"; +exports.icon = "src/en/nitroscans/icon.png"; +exports.site = "https://nitroscans.com/"; +exports.protected = false; +const baseUrl = exports.site; +const popularNovels = function (page) { + return __awaiter(this, void 0, void 0, function* () { + const url = baseUrl + "wp-admin/admin-ajax.php"; + let formData = new FormData(); + formData.append("action", "madara_load_more"); + formData.append("page", `${Number(page - 1)}`); + formData.append("template", "madara-core/content/content-archive"); + formData.append("vars[orderby]", "meta_value_num"); + formData.append("vars[post_type]", "wp-manga"); + formData.append("vars[meta_key]", "_wp_manga_views"); + formData.append("vars[wp-manga-genre]", "novels"); + const body = yield (0, fetch_1.fetchApi)(url, { + method: "POST", + body: formData, + }).then((r) => r.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".page-item-detail").each(function () { + const novelName = loadedCheerio(this).find("h3 > a").text(); + const novelCover = loadedCheerio(this).find("img").attr("data-src"); + const novelUrl = loadedCheerio(this).find("h3 > a").attr("href"); + if (!novelUrl) + return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + const url = novelUrl; + const body = yield (0, fetch_1.fetchApi)(url).then((r) => r.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + const novel = { + url, + chapters: [], + }; + loadedCheerio(".manga-title-badges.custom.novel").remove(); + novel.name = loadedCheerio(".post-title > h1").text().trim(); + novel.cover = loadedCheerio(".summary_image") + .find("img") + .attr("data-src"); + novel.summary = (_a = loadedCheerio(".summary__content").text()) === null || _a === void 0 ? void 0 : _a.trim(); + novel.genres = loadedCheerio(".genres-content") + .children("a") + .map((i, el) => loadedCheerio(el).text()) + .toArray() + .join(","); + loadedCheerio(".post-content_item").each(function () { + const detailName = loadedCheerio(this) + .find(".summary-heading") + .text() + .trim(); + const detail = loadedCheerio(this) + .find(".summary-content") + .text() + .trim(); + switch (detailName) { + case "Author(s)": + novel.author = detail; + break; + case "Status": + novel.status = detail.replace(/G/g, "g"); + break; + } + }); + let chapter = []; + let chapterlisturl = novelUrl + "ajax/chapters/"; + const data = yield (0, fetch_1.fetchApi)(chapterlisturl, { method: "POST" }); + const text = yield data.text(); + loadedCheerio = (0, cheerio_1.load)(text); + loadedCheerio(".wp-manga-chapter").each(function () { + const chapterName = loadedCheerio(this).find("a").text().trim(); + const releaseDate = loadedCheerio(this).find("span").text().trim(); + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + if (!chapterUrl) + return; + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + novel.chapters = chapter.reverse(); + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then((r) => r.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio(".text-left").html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + const url = baseUrl + "wp-admin/admin-ajax.php"; + let formData = new FormData(); + formData.append("action", "madara_load_more"); + formData.append("page", `${Number(1 - 1)}`); + formData.append("template", "madara-core/content/content-search"); + formData.append("vars[s]", searchTerm); + formData.append("vars[post_type]", "wp-manga"); + formData.append("vars[wp-manga-genre]", "novels"); + const body = yield (0, fetch_1.fetchApi)(url, { + method: "POST", + body: formData, + }).then((r) => r.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".c-tabs-item__content").each(function () { + const novelName = loadedCheerio(this).find("h3 > a").text(); + const novelCover = loadedCheerio(this).find("img").attr("data-src"); + const novelUrl = loadedCheerio(this).find("h3 > a").attr("href"); + if (!novelUrl) + return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.searchNovels = searchNovels; +const fetchImage = function (url) { + return __awaiter(this, void 0, void 0, function* () { + const headers = { + Referer: baseUrl, + }; + return yield (0, fetch_1.fetchFile)(url, { headers: headers }); + }); +}; +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/nobleMTL.js b/js/plugins/english/nobleMTL.js new file mode 100644 index 0000000..5e6469a --- /dev/null +++ b/js/plugins/english/nobleMTL.js @@ -0,0 +1,300 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.filters = exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.icon = exports.version = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const filterInputs_1 = require("@libs/filterInputs"); +exports.id = "NobleMTL"; +exports.name = "NobleMTL"; +exports.version = "1.0.0"; +exports.icon = "src/en/noblemtl/icon.png"; +exports.site = "https://www.noblemtl.com/"; +exports.protected = false; +const popularNovels = function (page, { filters }) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + let link = `${exports.site}series/?page=${page}`; + if ((_a = filters === null || filters === void 0 ? void 0 : filters.genres) === null || _a === void 0 ? void 0 : _a.length) { + if (Array.isArray(filters.genres)) + link += filters.genres.map((i) => `&genre[]=${i}`).join(""); + } + if ((_b = filters === null || filters === void 0 ? void 0 : filters.type) === null || _b === void 0 ? void 0 : _b.length) { + if (Array.isArray(filters.type)) + link += filters.type.map((i) => `&lang[]=${i}`).join(""); + } + link += "&status=" + ((filters === null || filters === void 0 ? void 0 : filters.status) ? filters === null || filters === void 0 ? void 0 : filters.status : ""); + link += "&order=" + ((filters === null || filters === void 0 ? void 0 : filters.order) ? filters === null || filters === void 0 ? void 0 : filters.order : "popular"); + const body = yield (0, fetch_1.fetchApi)(link).then((result) => result.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const novels = []; + loadedCheerio("article.bs").each(function () { + const novelName = loadedCheerio(this).find(".ntitle").text().trim(); + let image = loadedCheerio(this).find("img"); + const novelCover = image.attr("data-src") || image.attr("src"); + const novelUrl = loadedCheerio(this).find("a").attr("href"); + if (!novelUrl) + return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function parseNovelAndChapters(novelUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = novelUrl; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + const novel = { + url, + chapters: [], + }; + novel.name = loadedCheerio(".entry-title").text(); + novel.cover = + loadedCheerio("img.wp-post-image").attr("data-src") || + loadedCheerio("img.wp-post-image").attr("src"); + loadedCheerio("div.spe > span").each(function () { + const detailName = loadedCheerio(this).find("b").text().trim(); + const detail = loadedCheerio(this) + .find("b") + .remove() + .end() + .text() + .trim(); + switch (detailName) { + case "المؤلف:": + case "Yazar:": + case "Autor:": + case "Author:": + novel.author = detail; + break; + case "Status:": + case "Seviye:": + novel.status = detail; + break; + } + }); + novel.genres = loadedCheerio(".genxed") + .text() + .trim() + .replace(/\s/g, ","); + loadedCheerio('div[itemprop="description"] h3,p.a,strong').remove(); + novel.summary = loadedCheerio('div[itemprop="description"]') + .find("br") + .replaceWith("\n") + .end() + .text(); + let chapter = []; + loadedCheerio(".eplister") + .find("li") + .each(function () { + const chapterName = loadedCheerio(this).find(".epl-num").text() + + " - " + + loadedCheerio(this).find(".epl-title").text(); + const releaseDate = loadedCheerio(this) + .find(".epl-date") + .text() + .trim(); + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + if (!chapterUrl) + return; + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + novel.chapters = chapter.reverse(); + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)(chapterUrl); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let chapterText = loadedCheerio("div.epcontent").html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${exports.site}?s=${searchTerm}`; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + const novels = []; + loadedCheerio("article.bs").each(function () { + const novelName = loadedCheerio(this).find(".ntitle").text().trim(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this).find("a").attr("href"); + if (!novelUrl) + return; + novels.push({ + name: novelName, + url: novelUrl, + cover: novelCover, + }); + }); + return novels; + }); +}; +exports.searchNovels = searchNovels; +const fetchImage = function (url) { + return __awaiter(this, void 0, void 0, function* () { + const headers = { + Referer: exports.site, + }; + return yield (0, fetch_1.fetchFile)(url, { headers: headers }); + }); +}; +exports.fetchImage = fetchImage; +exports.filters = [ + { + key: "order", + label: "Sort By", + values: [ + { label: "Default", value: "" }, + { label: "A-Z", value: "title" }, + { label: "Z-A", value: "titlereverse" }, + { label: "Latest Update", value: "update" }, + { label: "Latest Added", value: "latest" }, + { label: "Popular", value: "popular" }, + ], + inputType: filterInputs_1.FilterInputs.Picker, + }, + { + key: "status", + label: "Status", + values: [ + { label: "All", value: "" }, + { label: "Ongoing", value: "ongoing" }, + { label: "Hiatus", value: "hiatus" }, + { label: "Completed", value: "completed" }, + ], + inputType: filterInputs_1.FilterInputs.Picker, + }, + { + key: "type", + label: "Type", + values: [ + { label: "Chinese novel", value: "chinese-novel" }, + { label: "habyeol", value: "habyeol" }, + { label: "korean novel", value: "korean-novel" }, + { label: "Web Novel", value: "web-novel" }, + { label: "삼심", value: "%ec%82%bc%ec%8b%ac" }, + { label: "호곡", value: "%ed%98%b8%ea%b3%a1" }, + ], + inputType: filterInputs_1.FilterInputs.Checkbox, + }, + { + key: "genres", + label: "Genres", + values: [ + { label: "A.I", value: "a.i" }, + { label: "Academy", value: "academy" }, + { label: "Action", value: "action" }, + { label: "Adult", value: "adult" }, + { label: "Adventure", value: "adventure" }, + { label: "Alternative History", value: "alternative-history" }, + { label: "Another World", value: "another-world" }, + { label: "Apocalypse", value: "apocalypse" }, + { label: "Bromance", value: "bromance" }, + { label: "Comedy", value: "comedy" }, + { label: "Dark fantasy", value: "dark-fantasy" }, + { label: "Demons", value: "demons" }, + { label: "Drama", value: "drama" }, + { label: "Dystopia", value: "dystopia" }, + { label: "Ecchi", value: "ecchi" }, + { label: "Entertainment", value: "entertainment" }, + { label: "Exhaustion", value: "exhaustion" }, + { label: "Fanfiction", value: "fanfiction" }, + { label: "fantasy", value: "fantasy" }, + { label: "finance", value: "finance" }, + { label: "Full color", value: "full-color" }, + { label: "Game", value: "game" }, + { label: "Gender Bender", value: "gender-bender" }, + { label: "Genius", value: "genius" }, + { label: "Harem", value: "harem" }, + { label: "Hero", value: "hero" }, + { label: "Historical", value: "historical" }, + { label: "Hunter", value: "hunter" }, + { label: "korean novel", value: "korean-novel" }, + { label: "Light Novel", value: "light-novel" }, + { + label: "List Adventure Manga Genres", + value: "list-adventure-manga-genres", + }, + { label: "Long Strip", value: "long-strip" }, + { label: "Love comedy", value: "love-comedy" }, + { label: "magic", value: "magic" }, + { label: "Manhua", value: "manhua" }, + { label: "Martial Arts", value: "martial-arts" }, + { label: "Mature", value: "mature" }, + { label: "Medieval", value: "medieval" }, + { label: "Misunderstanding", value: "misunderstanding" }, + { label: "Modern", value: "modern" }, + { label: "modern fantasy", value: "modern-fantasy" }, + { label: "music", value: "music" }, + { label: "Mystery", value: "mystery" }, + { label: "Necromancy", value: "necromancy" }, + { label: "No Romance", value: "no-romance" }, + { label: "NTL", value: "ntl" }, + { label: "o", value: "o" }, + { label: "Obsession", value: "obsession" }, + { label: "Politics", value: "politics" }, + { label: "Possession", value: "possession" }, + { label: "Programming", value: "programming" }, + { label: "Psychological", value: "psychological" }, + { label: "Pure Love", value: "pure-love" }, + { label: "Redemption", value: "redemption" }, + { label: "Regression", value: "regression" }, + { label: "Regret", value: "regret" }, + { label: "Reincarnation", value: "reincarnation" }, + { label: "Revenge", value: "revenge" }, + { label: "Romance", value: "romance" }, + { label: "Romance Fanrasy", value: "romance-fanrasy" }, + { label: "Salvation", value: "salvation" }, + { label: "School Life", value: "school-life" }, + { label: "Sci-fi", value: "sci-fi" }, + { label: "Science fiction", value: "science-fiction" }, + { label: "Seinen", value: "seinen" }, + { label: "Shounen", value: "shounen" }, + { label: "Slice of Life", value: "slice-of-life" }, + { label: "Soft yandere", value: "soft-yandere" }, + { label: "Sports", value: "sports" }, + { label: "Supernatural", value: "supernatural" }, + { label: "Survival", value: "survival" }, + { label: "system", value: "system" }, + { label: "Time limit", value: "time-limit" }, + { label: "Tragedy", value: "tragedy" }, + { label: "Transmigration", value: "transmigration" }, + { label: "TS", value: "ts" }, + { label: "Tsundere", value: "tsundere" }, + { label: "Unique", value: "unique" }, + { label: "Wholesome", value: "wholesome" }, + { label: "Wuxia", value: "wuxia" }, + { label: "Xuanhuan", value: "xuanhuan" }, + { label: "Yandere", value: "yandere" }, + { label: "Yuri", value: "yuri" }, + ], + inputType: filterInputs_1.FilterInputs.Checkbox, + }, +]; diff --git a/js/plugins/english/novelforest.js b/js/plugins/english/novelforest.js new file mode 100644 index 0000000..077241c --- /dev/null +++ b/js/plugins/english/novelforest.js @@ -0,0 +1,127 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.icon = exports.version = exports.name = exports.id = void 0; +const fetch_1 = require("@libs/fetch"); +const cheerio_1 = require("cheerio"); +exports.id = "novelforest"; +exports.name = "Novel Forest (Broken)"; +exports.version = "1.0.0"; +exports.icon = "src/en/novelforest/icon.png"; +exports.site = "https://novelforest.com/"; +exports.protected = false; +const pluginId = exports.id; +const baseUrl = exports.site; +const popularNovels = function (page) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}popular?page=${page}`; + const body = yield (0, fetch_1.fetchApi)(url, {}, pluginId).then((r) => r.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".book-item").each(function () { + var _a; + const novelName = loadedCheerio(this).find(".title").text(); + const novelCover = "https:" + loadedCheerio(this).find("img").attr("data-src"); + const novelUrl = baseUrl + + ((_a = loadedCheerio(this).find(".title a").attr("href")) === null || _a === void 0 ? void 0 : _a.substring(1)); + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + var _a, _b, _c; + const url = novelUrl; + const body = yield (0, fetch_1.fetchApi)(url, {}, pluginId).then((r) => r.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + const novel = { + url, + chapters: [], + }; + novel.name = loadedCheerio(".name h1").text().trim(); + novel.cover = "https:" + loadedCheerio(".img-cover img").attr("data-src"); + novel.summary = loadedCheerio("body > div.layout > div.main-container.book-details > div > div.row.no-gutters > div.col-lg-8 > div.mt-1 > div.section.box.mt-1.summary > div.section-body > p.content") + .text() + .trim(); + novel.author = (_a = loadedCheerio("body > div.layout > div.main-container.book-details > div > div.row.no-gutters > div.col-lg-8 > div.book-info > div.detail > div.meta.box.mt-1.p-10 > p:nth-child(1) > a > span") + .text()) === null || _a === void 0 ? void 0 : _a.trim(); + novel.status = (_b = loadedCheerio("body > div.layout > div.main-container.book-details > div > div.row.no-gutters > div.col-lg-8 > div.book-info > div.detail > div.meta.box.mt-1.p-10 > p:nth-child(2) > a > span") + .text()) === null || _b === void 0 ? void 0 : _b.trim(); + novel.genres = (_c = loadedCheerio("body > div.layout > div.main-container.book-details > div > div.row.no-gutters > div.col-lg-8 > div.book-info > div.detail > div.meta.box.mt-1.p-10 > p:nth-child(3)") + .text()) === null || _c === void 0 ? void 0 : _c.replace("Genres :", "").replace(/[\s\n]+/g, " ").trim(); + let chapter = []; + const chaptersUrl = novelUrl.replace(baseUrl, "https://novelforest.com/api/novels/") + + "/chapters?source=detail"; + const chaptersRequest = yield fetch(chaptersUrl); + const chaptersHtml = yield chaptersRequest.text(); + loadedCheerio = (0, cheerio_1.load)(chaptersHtml); + loadedCheerio("li").each(function () { + var _a; + const chapterName = loadedCheerio(this) + .find(".chapter-title") + .text() + .trim(); + const releaseDate = loadedCheerio(this) + .find(".chapter-update") + .text() + .trim(); + const chapterUrl = baseUrl + ((_a = loadedCheerio(this).find("a").attr("href")) === null || _a === void 0 ? void 0 : _a.substring(1)); + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + novel.chapters = chapter; + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const body = yield (0, fetch_1.fetchApi)(chapterUrl, {}, pluginId).then((r) => r.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio("#listen-chapter").remove(); + loadedCheerio("#google_translate_element").remove(); + const chapterText = loadedCheerio(".chapter__content").html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}search?q=${searchTerm}`; + const body = yield (0, fetch_1.fetchApi)(url, {}, pluginId).then((r) => r.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".book-item").each(function () { + var _a; + const novelName = loadedCheerio(this).find(".title").text(); + const novelCover = "https:" + loadedCheerio(this).find("img").attr("data-src"); + const novelUrl = baseUrl + + ((_a = loadedCheerio(this).find(".title a").attr("href")) === null || _a === void 0 ? void 0 : _a.substring(1)); + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.searchNovels = searchNovels; +exports.fetchImage = fetch_1.fetchFile; diff --git a/js/plugins/english/novelfull.js b/js/plugins/english/novelfull.js new file mode 100644 index 0000000..e14bc2a --- /dev/null +++ b/js/plugins/english/novelfull.js @@ -0,0 +1,148 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.icon = exports.version = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +exports.id = "novelfull"; +exports.name = "NovelFull"; +exports.version = "1.0.0"; +exports.icon = "src/en/novelfull/icon.png"; +exports.site = "https://novelfull.com/"; +exports.protected = false; +const baseUrl = exports.site; +const popularNovels = function (page) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}most-popular?page=${page}`; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".col-truyen-main .list-truyen .row").each(function () { + var _a, _b; + const novelName = loadedCheerio(this) + .find("h3.truyen-title > a") + .text(); + const novelCover = baseUrl + ((_a = loadedCheerio(this).find("img").attr("src")) === null || _a === void 0 ? void 0 : _a.slice(1)); + const novelUrl = baseUrl + + ((_b = loadedCheerio(this) + .find("h3.truyen-title > a") + .attr("href")) === null || _b === void 0 ? void 0 : _b.slice(1)); + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = novelUrl; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + const novel = { + url, + chapters: [], + }; + novel.name = loadedCheerio("div.book > img").attr("alt"); + novel.cover = baseUrl + loadedCheerio("div.book > img").attr("src"); + novel.summary = loadedCheerio("div.desc-text").text().trim(); + novel.author = loadedCheerio('h3:contains("Author")') + .parent() + .contents() + .text() + .replace("Author:", ""); + novel.genres = loadedCheerio('h3:contains("Genre")') + .siblings() + .map((i, el) => loadedCheerio(el).text()) + .toArray() + .join(","); + novel.status = loadedCheerio('h3:contains("Status")').next().text(); + const novelId = loadedCheerio("#rating").attr("data-novel-id"); + function getChapters(id) { + return __awaiter(this, void 0, void 0, function* () { + const chapterListUrl = baseUrl + "ajax/chapter-option?novelId=" + id; + const data = yield (0, fetch_1.fetchApi)(chapterListUrl); + const chapterlist = yield data.text(); + loadedCheerio = (0, cheerio_1.load)(chapterlist); + let chapter = []; + loadedCheerio("select > option").each(function () { + var _a; + const chapterName = loadedCheerio(this).text(); + const releaseDate = null; + const chapterUrl = baseUrl + ((_a = loadedCheerio(this).attr("value")) === null || _a === void 0 ? void 0 : _a.slice(1)); + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + return chapter; + }); + } + novel.chapters = yield getChapters(novelId); + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)(chapterUrl); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio("#chapter-content div.ads").remove(); + let chapterText = loadedCheerio("#chapter-content").html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + const searchUrl = `${baseUrl}search?keyword=${searchTerm}`; + const result = yield (0, fetch_1.fetchApi)(searchUrl); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".col-truyen-main .list-truyen .row").each(function () { + var _a, _b; + const novelName = loadedCheerio(this) + .find("h3.truyen-title > a") + .text(); + const novelCover = baseUrl + ((_a = loadedCheerio(this).find("img").attr("src")) === null || _a === void 0 ? void 0 : _a.slice(1)); + const novelUrl = baseUrl + + ((_b = loadedCheerio(this) + .find("h3.truyen-title > a") + .attr("href")) === null || _b === void 0 ? void 0 : _b.slice(1)); + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.searchNovels = searchNovels; +const fetchImage = function (url) { + return __awaiter(this, void 0, void 0, function* () { + const headers = { + Referer: baseUrl, + }; + return yield (0, fetch_1.fetchFile)(url, { headers: headers }); + }); +}; +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/novelhold.js b/js/plugins/english/novelhold.js new file mode 100644 index 0000000..3875a0f --- /dev/null +++ b/js/plugins/english/novelhold.js @@ -0,0 +1,133 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.icon = exports.version = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +exports.id = "novelhold"; +exports.name = "Novel Hold"; +exports.version = "1.0.0"; +exports.icon = "src/en/novelhold/icon.png"; +exports.site = "https://novelhold.com/"; +exports.protected = false; +const baseUrl = exports.site; +const popularNovels = function (page) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}all-${page}.html`; + const body = yield (0, fetch_1.fetchApi)(url).then((r) => r.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio("#article_list_content > li").each(function () { + var _a; + const novelName = loadedCheerio(this) + .find("h3") + .text() + .replace(/\t+/g, "") + .replace(/\n/g, " "); + const novelCover = loadedCheerio(this).find("img").attr("data-src"); + const novelUrl = baseUrl + ((_a = loadedCheerio(this).find("a").attr("href")) === null || _a === void 0 ? void 0 : _a.slice(1)); + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + const url = novelUrl; + const body = yield (0, fetch_1.fetchApi)(url).then((r) => r.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + const novel = { + url, + chapters: [], + }; + novel.name = loadedCheerio(".booknav2 > h1").text(); + novel.cover = loadedCheerio('meta[property="og:image"]').attr("content"); + novel.summary = loadedCheerio(".navtxt").text().trim(); + novel.author = loadedCheerio('p:contains("Author")') + .text() + .replace("Author:", "") + .trim(); + novel.status = loadedCheerio('p:contains("Status")') + .text() + .replace("Status:", "") + .replace("Active", "Ongoing") + .trim(); + novel.genres = (_a = loadedCheerio('p:contains("Genre")') + .text()) === null || _a === void 0 ? void 0 : _a.replace("Genre:", "").trim(); + let chapter = []; + loadedCheerio("ul.chapterlist > li").each(function () { + var _a; + const chapterName = loadedCheerio(this).find("a").text().trim(); + const releaseDate = null; + const chapterUrl = baseUrl + ((_a = loadedCheerio(this).find("a").attr("href")) === null || _a === void 0 ? void 0 : _a.slice(1)); + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + novel.chapters = chapter; + console.log(novel); + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then((r) => r.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio(".content").html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}index.php?s=so&module=book&keyword=${searchTerm}`; + const body = yield (0, fetch_1.fetchApi)(url).then((r) => r.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio("#article_list_content > li").each(function () { + var _a; + const novelName = loadedCheerio(this) + .find("h3") + .text() + .replace(/\t+/g, "") + .replace(/\n/g, " "); + const novelCover = loadedCheerio(this).find("img").attr("data-src"); + const novelUrl = baseUrl + ((_a = loadedCheerio(this).find("a").attr("href")) === null || _a === void 0 ? void 0 : _a.slice(1)); + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.searchNovels = searchNovels; +const fetchImage = function (url) { + return __awaiter(this, void 0, void 0, function* () { + const headers = { + Referer: baseUrl, + }; + return yield (0, fetch_1.fetchFile)(url, { headers: headers }); + }); +}; +exports.fetchImage = fetchImage; diff --git a/js/plugins/english/novelpub.js b/js/plugins/english/novelpub.js new file mode 100644 index 0000000..8266f3b --- /dev/null +++ b/js/plugins/english/novelpub.js @@ -0,0 +1,152 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.icon = exports.version = exports.site = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +exports.id = "novelpub.com"; +exports.name = "NovelPub"; +exports.site = "https://www.novelpub.com/"; +exports.version = "1.0.0"; +exports.icon = "src/en/novelpub/icon.png"; +const baseUrl = exports.site; +const headers = { + Accept: "application/json", + "Content-Type": "application/json", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36", +}; +const popularNovels = function (page) { + return __awaiter(this, void 0, void 0, function* () { + let url = baseUrl + "browse/all/popular/all/" + page; + const result = yield (0, fetch_1.fetchApi)(url, { method: "GET", headers }); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".novel-item.ads").remove(); + loadedCheerio(".novel-item").each(function () { + var _a; + const novelName = loadedCheerio(this) + .find(".novel-title") + .text() + .trim(); + const novelCover = loadedCheerio(this).find("img").attr("data-src"); + const novelUrl = baseUrl + + ((_a = loadedCheerio(this) + .find(".novel-title > a") + .attr("href")) === null || _a === void 0 ? void 0 : _a.substring(1)); + const novel = { name: novelName, cover: novelCover, url: novelUrl }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + const url = novelUrl; + const result = yield (0, fetch_1.fetchApi)(url, { method: "GET", headers }); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let novel = { url, genres: "" }; + novel.name = loadedCheerio("h1.novel-title").text().trim(); + novel.cover = loadedCheerio("figure.cover > img").attr("data-src"); + loadedCheerio("div.categories > ul > li").each(function () { + novel.genres += + loadedCheerio(this) + .text() + .replace(/[\t\n]/g, "") + ","; + }); + loadedCheerio("div.header-stats > span").each(function () { + if (loadedCheerio(this).find("small").text() === "Status") { + novel.status = loadedCheerio(this).find("strong").text(); + } + }); + novel.genres = (_a = novel.genres) === null || _a === void 0 ? void 0 : _a.slice(0, -1); + novel.author = loadedCheerio(".author > a > span").text(); + novel.summary = loadedCheerio(".summary > .content").text().trim(); + const delay = (ms) => new Promise((res) => setTimeout(res, ms)); + let lastPage = 1; + lastPage = parseInt((_b = loadedCheerio("#novel > header > div.header-body.container > div.novel-info > div.header-stats > span:nth-child(1) > strong") + .text()) === null || _b === void 0 ? void 0 : _b.trim()); + lastPage = Math.ceil(lastPage / 100); + const getChapters = () => __awaiter(this, void 0, void 0, function* () { + let novelChapters = []; + for (let i = 1; i <= lastPage; i++) { + const chaptersUrl = `${novelUrl}/chapters/page-${i}`; + const chaptersRequest = yield (0, fetch_1.fetchApi)(chaptersUrl, { + headers, + }); + const chaptersHtml = yield chaptersRequest.text(); + loadedCheerio = (0, cheerio_1.load)(chaptersHtml); + loadedCheerio(".chapter-list li").each(function () { + var _a; + const chapterName = loadedCheerio(this) + .find(".chapter-title") + .text() + .trim(); + const releaseDate = loadedCheerio(this) + .find(".chapter-update") + .text() + .trim(); + const chapterUrl = baseUrl + + ((_a = loadedCheerio(this) + .find("a") + .attr("href")) === null || _a === void 0 ? void 0 : _a.substring(1)); + novelChapters.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + yield delay(1000); + } + return novelChapters; + }); + novel.chapters = yield getChapters(); + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = chapterUrl; + const result = yield (0, fetch_1.fetchApi)(url, { method: "GET", headers }); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio("#chapter-container").html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +// They have API to search, pls update it, im quite lazy xD +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}lnwsearchlive?inputContent=${searchTerm}`; + const result = yield (0, fetch_1.fetchApi)(url, { method: "GET", headers }); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + // let results = JSON.parse(loadedCheerio('body').text()); + // loadedCheerio = load(results.resultview); + // loadedCheerio('.novel-item').each(function () { + // const novelName = loadedCheerio(this).find('.novel-title').text().trim(); + // const novelCover = loadedCheerio(this).find('img').attr('src'); + // const novelUrl = + // baseUrl + loadedCheerio(this).find('a').attr('href').substring(1); + // const novel = { name: novelName, cover: novelCover, url: novelUrl }; + // novels.push(novel); + // }); + return novels; + }); +}; +exports.searchNovels = searchNovels; +exports.fetchImage = fetch_1.fetchFile; diff --git a/js/plugins/english/pandaMTL.js b/js/plugins/english/pandaMTL.js new file mode 100644 index 0000000..334a3aa --- /dev/null +++ b/js/plugins/english/pandaMTL.js @@ -0,0 +1,221 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.filters = exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.icon = exports.version = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const filterInputs_1 = require("@libs/filterInputs"); +exports.id = "PandaMTL"; +exports.name = "PandaMTL"; +exports.version = "1.0.0"; +exports.icon = "src/en/wordpress/icon.png"; +exports.site = "https://www.pandamtl.com/"; +exports.protected = false; +const pluginId = exports.id; +const baseUrl = exports.site; +const popularNovels = function (page, { filters }) { + return __awaiter(this, void 0, void 0, function* () { + let link = `${baseUrl}series/?page=${page}`; + if (filters) { + if (Array.isArray(filters.genres) && filters.genres.length) { + link += filters.genres.map((i) => `&genre[]=${i}`).join(""); + } + if (Array.isArray(filters.type) && filters.type.length) { + link += filters.type.map((i) => `&lang[]=${i}`).join(""); + } + } + link += "&status=" + ((filters === null || filters === void 0 ? void 0 : filters.status) ? filters === null || filters === void 0 ? void 0 : filters.status : ""); + link += "&order=" + ((filters === null || filters === void 0 ? void 0 : filters.order) ? filters === null || filters === void 0 ? void 0 : filters.order : "popular"); + const body = yield (0, fetch_1.fetchApi)(link, {}, pluginId).then((result) => result.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const novels = []; + loadedCheerio("article.maindet").each(function () { + const novelName = loadedCheerio(this).find("h2").text(); + let image = loadedCheerio(this).find("img"); + const novelCover = image.attr("data-src") || image.attr("src"); + const novelUrl = loadedCheerio(this).find("h2 a").attr("href"); + if (!novelUrl) + return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = novelUrl; + const result = yield (0, fetch_1.fetchApi)(url, {}, pluginId); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + const novel = { + url, + chapters: [], + }; + novel.name = loadedCheerio("h1.entry-title").text(); + novel.cover = + loadedCheerio("img.wp-post-image").attr("data-src") || + loadedCheerio("img.wp-post-image").attr("src"); + loadedCheerio("div.serl:nth-child(3) > span").each(function () { + const detailName = loadedCheerio(this).text().trim(); + const detail = loadedCheerio(this).next().text().trim(); + switch (detailName) { + case "الكاتب": + case "Author": + novel.author = detail; + break; + } + }); + novel.status = loadedCheerio("div.sertostat > span").attr("class"); + novel.genres = loadedCheerio(".sertogenre") + .children("a") + .map((i, el) => loadedCheerio(el).text()) + .toArray() + .join(","); + novel.summary = loadedCheerio(".sersys") + .find("br") + .replaceWith("\n") + .end() + .text(); + let chapter = []; + loadedCheerio(".eplister") + .find("li") + .each(function () { + const chapterName = loadedCheerio(this).find(".epl-num").text() + + " - " + + loadedCheerio(this).find(".epl-title").text(); + const releaseDate = loadedCheerio(this) + .find(".epl-date") + .text() + .trim(); + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + if (!chapterUrl) + return; + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + novel.chapters = chapter.reverse(); + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)(chapterUrl, {}, pluginId); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let chapterText = loadedCheerio(".epcontent").html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}?s=${searchTerm}`; + const result = yield (0, fetch_1.fetchApi)(url, {}, pluginId); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + const novels = []; + loadedCheerio("article.maindet").each(function () { + const novelName = loadedCheerio(this).find("h2").text(); + let image = loadedCheerio(this).find("img"); + const novelCover = image.attr("data-src") || image.attr("src"); + const novelUrl = loadedCheerio(this).find("h2 a").attr("href"); + if (!novelUrl) + return; + novels.push({ + name: novelName, + url: novelUrl, + cover: novelCover, + }); + }); + return novels; + }); +}; +exports.searchNovels = searchNovels; +const fetchImage = function (url) { + return __awaiter(this, void 0, void 0, function* () { + const headers = { + Referer: baseUrl, + }; + return yield (0, fetch_1.fetchFile)(url, { headers: headers }); + }); +}; +exports.fetchImage = fetchImage; +exports.filters = [ + { + key: "order", + label: "Sort By", + values: [ + { label: "Default", value: "" }, + { label: "A-Z", value: "title" }, + { label: "Z-A", value: "titlereverse" }, + { label: "Latest Update", value: "update" }, + { label: "Latest Added", value: "latest" }, + { label: "Popular", value: "popular" }, + ], + inputType: filterInputs_1.FilterInputs.Picker, + }, + { + key: "status", + label: "Status", + values: [ + { label: "All", value: "" }, + { label: "Ongoing", value: "ongoing" }, + { label: "Hiatus", value: "hiatus" }, + { label: "Completed", value: "completed" }, + ], + inputType: filterInputs_1.FilterInputs.Picker, + }, + { + key: "type", + label: "Type", + values: [ + { label: "Light Novel (KR)", value: "light-novel-kr" }, + { label: "Web Novel", value: "web-novel" }, + ], + inputType: filterInputs_1.FilterInputs.Checkbox, + }, + { + key: "genres", + label: "Genres", + values: [ + { label: "Action", value: "action" }, + { label: "Adult", value: "adult" }, + { label: "Adventure", value: "adventure" }, + { label: "Comedy", value: "comedy" }, + { label: "Ecchi", value: "ecchi" }, + { label: "Fantasy", value: "fantasy" }, + { label: "Harem", value: "harem" }, + { label: "Josei", value: "josei" }, + { label: "Martial Arts", value: "martial-arts" }, + { label: "Mature", value: "mature" }, + { label: "Romance", value: "romance" }, + { label: "School Life", value: "school-life" }, + { label: "Sci-fi", value: "sci-fi" }, + { label: "Seinen", value: "seinen" }, + { label: "Slice of Life", value: "slice-of-life" }, + { label: "Smut", value: "smut" }, + { label: "Sports", value: "sports" }, + { label: "Supernatural", value: "supernatural" }, + { label: "Tragedy", value: "tragedy" }, + ], + inputType: filterInputs_1.FilterInputs.Checkbox, + }, +]; diff --git a/js/plugins/french/chireads.js b/js/plugins/french/chireads.js new file mode 100644 index 0000000..9f2e74b --- /dev/null +++ b/js/plugins/french/chireads.js @@ -0,0 +1,113 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.icon = exports.version = exports.site = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +exports.id = "chireads.com"; +exports.name = "Chireads"; +exports.site = "https://chireads.com/"; +exports.version = "1.0.0"; +exports.icon = "src/fr/chireads/icon.png"; +const baseUrl = exports.site; +const popularNovels = function (page) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}category/translatedtales/page/${page}/`; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio("#content li").each(function () { + const novelName = loadedCheerio(this).find(".news-list-tit h5").text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this) + .find(".news-list-tit h5 a") + .attr("href"); + if (!novelUrl) + return; + const novel = { name: novelName, cover: novelCover, url: novelUrl }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = novelUrl; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let novel = { + url, + name: "", + cover: "", + author: "", + status: "", + genres: "", + summary: "", + chapters: [], + }; + novel.name = loadedCheerio(".inform-title").text().trim(); + novel.cover = loadedCheerio(".inform-product img").attr("src"); + novel.summary = loadedCheerio(".inform-inform-txt").text().trim(); + let chapters = []; + loadedCheerio(".chapitre-table a").each(function () { + const chapterName = loadedCheerio(this).text().trim(); + const releaseDate = null; + const chapterUrl = loadedCheerio(this).attr("href"); + if (!chapterUrl) + return; + chapters.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + novel.chapters = chapters; + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = chapterUrl; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio("#content").html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}search?x=0&y=0&name=${searchTerm}`; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio("#content li").each(function () { + const novelName = loadedCheerio(this).find(".news-list-tit h5").text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this) + .find(".news-list-tit h5 a") + .attr("href"); + if (!novelUrl) + return; + const novel = { name: novelName, cover: novelCover, url: novelUrl }; + novels.push(novel); + }); + return novels; + }); +}; +exports.searchNovels = searchNovels; +exports.fetchImage = fetch_1.fetchFile; diff --git a/js/plugins/indonesian/MeioNovel[madara].js b/js/plugins/indonesian/MeioNovel[madara].js new file mode 100644 index 0000000..bd6a3bf --- /dev/null +++ b/js/plugins/indonesian/MeioNovel[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "meionovel"; +exports.name = "MeioNovel [madara]"; +exports.icon = "multisrc/madara/icons/meionovel.png"; +exports.version = "1.0.0"; +exports.site = "https://meionovel.id/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novel" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (true) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/indonesian/MoreNovel[madara].js b/js/plugins/indonesian/MoreNovel[madara].js new file mode 100644 index 0000000..6bbcd86 --- /dev/null +++ b/js/plugins/indonesian/MoreNovel[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "morenovel"; +exports.name = "MoreNovel [madara]"; +exports.icon = "multisrc/madara/icons/morenovel.png"; +exports.version = "1.0.0"; +exports.site = "https://morenovel.net/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novel" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (true) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/indonesian/WBNovel[madara].js b/js/plugins/indonesian/WBNovel[madara].js new file mode 100644 index 0000000..3e305c9 --- /dev/null +++ b/js/plugins/indonesian/WBNovel[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "wbnovel"; +exports.name = "WBNovel [madara]"; +exports.icon = "multisrc/madara/icons/wbnovel.png"; +exports.version = "1.0.0"; +exports.site = "https://wbnovel.com/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novel" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (false) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/indonesian/indowebnovel.js b/js/plugins/indonesian/indowebnovel.js new file mode 100644 index 0000000..956c892 --- /dev/null +++ b/js/plugins/indonesian/indowebnovel.js @@ -0,0 +1,126 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.version = exports.icon = exports.site = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +exports.id = "IDWN.id"; +exports.name = "IndoWebNovel"; +exports.site = "https://indowebnovel.id/id/"; +exports.icon = "src/id/indowebnovel/icon.png"; +exports.version = "1.0.0"; +const baseUrl = exports.site; +const popularNovels = function (page) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}daftar-novel/`; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".novellist-blc li").each(function () { + const novelName = loadedCheerio(this).find("a").text(); + const novelCover = defaultCover_1.defaultCover; + const novelUrl = loadedCheerio(this).find("a").attr("href"); + if (!novelUrl || !novelName) + return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = novelUrl; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let novel = { + url, + name: "", + cover: "", + author: "", + status: "", + genres: "", + summary: "", + chapters: [], + }; + novel.name = loadedCheerio(".series-title").text().trim(); + novel.cover = loadedCheerio(".series-thumb > img").attr("src"); + novel.summary = loadedCheerio(".series-synops").text().trim(); + novel.status = loadedCheerio(".status").text().trim(); + const genres = []; + loadedCheerio(".series-genres").each(function () { + genres.push(loadedCheerio(this).find("a").text().trim()); + }); + novel.genres = genres.join(","); + let chapters = []; + loadedCheerio(".series-chapterlist li").each(function () { + const chapterName = loadedCheerio(this).find("a").text().trim(); + const releaseDate = null; + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + if (!chapterUrl) + return; + chapters.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + novel.chapters = chapters.reverse(); + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = chapterUrl; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio(".reader").html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}daftar-novel/`; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".novellist-blc li").each(function () { + const novelName = loadedCheerio(this).find("a").text(); + const novelCover = defaultCover_1.defaultCover; + const novelUrl = loadedCheerio(this).find("a").attr("href"); + if (!novelUrl) + return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + if (novelName.toLowerCase().includes(searchTerm.toLowerCase())) { + novels.push(novel); + } + }); + return novels; + }); +}; +exports.searchNovels = searchNovels; +exports.fetchImage = fetch_1.fetchFile; diff --git a/js/plugins/indonesian/novelringan.js b/js/plugins/indonesian/novelringan.js new file mode 100644 index 0000000..4b84f3a --- /dev/null +++ b/js/plugins/indonesian/novelringan.js @@ -0,0 +1,135 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.version = exports.icon = exports.site = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const novelStatus_1 = require("@libs/novelStatus"); +exports.id = "novelringan.com"; +exports.name = "NovelRingan"; +exports.site = "https://novelringan.com/"; +exports.icon = "src/id/novelringan/icon.png"; +exports.version = "1.0.0"; +const baseUrl = exports.site; +const coverUriPrefix = "https://i0.wp.com/novelringan.com/wp-content/uploads/"; +const popularNovels = function (page) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}/top-novel/page/${page}`; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + const novels = []; + loadedCheerio("article.post").each(function () { + var _a; + const novelName = (_a = loadedCheerio(this) + .find(".entry-title") + .text()) === null || _a === void 0 ? void 0 : _a.trim(); + const novelCover = coverUriPrefix + loadedCheerio(this).find("img").attr("data-sxrx"); + const novelUrl = loadedCheerio(this).find("h2 > a").attr("href"); + if (!novelUrl) + return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + const url = novelUrl; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let novel = { + url, + name: "", + cover: "", + genres: "", + author: "", + status: novelStatus_1.NovelStatus.Unknown, + summary: "", + chapters: [], + }; + novel.name = (_a = loadedCheerio(".entry-title").text()) === null || _a === void 0 ? void 0 : _a.trim(); + novel.cover = + coverUriPrefix + + loadedCheerio("img.ts-post-image").attr("data-sxrx"); + novel.summary = loadedCheerio("body > div.site-container > div > main > article > div > div.maininfo > span > p").text(); + let genreSelector = loadedCheerio("body > div.site-container > div > main > article > div > div.maininfo > span > ul > li:nth-child(4)").text(); + novel.genres = genreSelector.includes("Genre") + ? genreSelector.replace("Genre:", "").trim() + : ""; + let statusSelector = loadedCheerio("body > div.site-container > div > main > article > div > div.maininfo > span > ul > li:nth-child(3)").text(); + novel.status = statusSelector.includes("Status") + ? statusSelector.replace("Status:", "").trim() + : novelStatus_1.NovelStatus.Unknown; + let chapters = []; + loadedCheerio(".bxcl > ul > li").each(function () { + const chapterName = loadedCheerio(this).find("a").text(); + const releaseDate = null; + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + if (!chapterUrl) + return; + const chapter = { + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }; + chapters.push(chapter); + }); + novel.chapters = chapters.reverse(); + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = chapterUrl; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.entry-content div[style="display:none"]').remove(); + const chapterText = loadedCheerio(".entry-content").html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + const url = baseUrl + "?s=" + searchTerm; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio("article.post").each(function () { + const novelName = loadedCheerio(this).find(".entry-title").text(); + const novelCover = coverUriPrefix + loadedCheerio(this).find("img").attr("data-sxrx"); + const novelUrl = loadedCheerio(this).find("h2 > a").attr("href"); + if (!novelUrl) + return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.searchNovels = searchNovels; +exports.fetchImage = fetch_1.fetchFile; diff --git a/js/plugins/indonesian/sakuranovel.js b/js/plugins/indonesian/sakuranovel.js new file mode 100644 index 0000000..7913db4 --- /dev/null +++ b/js/plugins/indonesian/sakuranovel.js @@ -0,0 +1,140 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.version = exports.icon = exports.site = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +exports.id = "sakura.id"; +exports.name = "SakuraNovel"; +exports.site = "https://sakuranovel.id/"; +exports.icon = "src/id/sakuranovel/icon.png"; +exports.version = "1.0.0"; +const pluginId = exports.id; +const sourceName = exports.name; +const baseUrl = exports.site; +const popularNovels = function (page) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}advanced-search/page/${page}/?title&author&yearx&status&type&order=rating&country%5B0%5D=china&country%5B1%5D=jepang&country%5B2%5D=unknown`; + const result = yield (0, fetch_1.fetchApi)(url, {}, pluginId); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".flexbox2-item").each(function () { + const novelName = loadedCheerio(this) + .find(".flexbox2-title span") + .first() + .text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this) + .find(".flexbox2-content > a") + .attr("href"); + if (!novelUrl) + return; + const novel = { name: novelName, cover: novelCover, url: novelUrl }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)(novelUrl, {}, pluginId); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let novel = { + name: sourceName, + url: novelUrl, + }; + novel.name = loadedCheerio(".series-title h2").text().trim(); + novel.cover = loadedCheerio(".series-thumb img").attr("src"); + loadedCheerio(".series-infolist > li").each(function () { + const detailName = loadedCheerio(this).find("b").text().trim(); + const detail = loadedCheerio(this).find("b").next().text().trim(); + switch (detailName) { + case "Author": + novel.author = detail; + break; + } + }); + novel.status = loadedCheerio(".status").text().trim(); + novel.genres = loadedCheerio(".series-genres") + .children("a") + .map((i, el) => loadedCheerio(el).text()) + .toArray() + .join(","); + loadedCheerio(".series-synops div").remove(); + novel.summary = loadedCheerio(".series-synops").text().trim(); + let chapters = []; + loadedCheerio(".series-chapterlist li").each(function () { + const chapterName = loadedCheerio(this) + .find("a span") + .first() + .text() + .replace(/.*?(Chapter.|[0-9])/g, "$1") + .replace(/Bahasa Indonesia/g, "") + .replace(/\s+/g, " ") + .trim(); + const releaseDate = loadedCheerio(this) + .find("a span") + .first() + .next() + .text(); + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + if (!chapterUrl) + return; + chapters.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + novel.chapters = chapters.reverse(); + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)(chapterUrl, {}, pluginId); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio(".readers").html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}?s=${searchTerm}`; + const result = yield (0, fetch_1.fetchApi)(url, {}, pluginId); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".flexbox2-item").each(function () { + const novelName = loadedCheerio(this) + .find(".flexbox2-title span") + .first() + .text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this) + .find(".flexbox2-content > a") + .attr("href"); + if (!novelUrl) + return; + const novel = { name: novelName, cover: novelCover, url: novelUrl }; + novels.push(novel); + }); + return novels; + }); +}; +exports.searchNovels = searchNovels; +exports.fetchImage = fetch_1.fetchFile; diff --git a/js/plugins/japanese/Syosetu.js b/js/plugins/japanese/Syosetu.js new file mode 100644 index 0000000..215f777 --- /dev/null +++ b/js/plugins/japanese/Syosetu.js @@ -0,0 +1,202 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +// const novelStatus = require('@libs/novelStatus'); +// const isUrlAbsolute = require('@libs/isAbsoluteUrl'); +// const parseDate = require('@libs/parseDate'); +const pluginId = "yomou.syosetu"; +exports.id = pluginId; +exports.name = "Syosetu"; +exports.icon = "src/jp/syosetu/icon.png"; +exports.version = "1.0.0"; +exports.site = "https//yomou.syosetu.com/"; +exports.protected = false; +const searchUrl = (pagenum, order) => { + return `https://yomou.syosetu.com/search.php?order=${order || "hyoka"}${pagenum !== undefined + ? `&p=${pagenum <= 1 || pagenum > 100 ? "1" : pagenum}` // check if pagenum is between 1 and 100 + : "" // if isn't don't set ?p + }`; +}; +const popularNovels = function (pageNo) { + return __awaiter(this, void 0, void 0, function* () { + function getNovelsFromPage(pagenumber) { + return __awaiter(this, void 0, void 0, function* () { + // load page + const result = yield (0, fetch_1.fetchApi)(searchUrl(pagenumber)); + const body = yield result.text(); + // Cheerio it! + const cheerioQuery = (0, cheerio_1.load)(body, { decodeEntities: false }); + let pageNovels = []; + // find class=searchkekka_box + cheerioQuery(".searchkekka_box").each(function (i, e) { + // get div with link and name + const novelDIV = cheerioQuery(this).find(".novel_h"); + // get link element + const novelA = novelDIV.children()[0]; + // add new novel to array + pageNovels.push({ + name: novelDIV.text(), + url: novelA.attribs.href, + cover: defaultCover_1.defaultCover, + }); + }); + // return all novels from this page + return pageNovels; + }); + } + const novels = yield getNovelsFromPage(pageNo); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + return __awaiter(this, void 0, void 0, function* () { + let chapters = []; + const result = yield (0, fetch_1.fetchApi)(novelUrl); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body, { decodeEntities: false }); + // create novel object + let novel = { + url: novelUrl, + name: loadedCheerio(".novel_title").text(), + author: loadedCheerio(".novel_writername") + .text() + .replace("作者:", ""), + cover: defaultCover_1.defaultCover, + }; + // Get all the chapters + const cqGetChapters = loadedCheerio(".novel_sublist2"); + if (cqGetChapters.length !== 0) { + // has more than 1 chapter + novel.summary = loadedCheerio("#novel_ex") + .text() + .replace(/<\s*br.*?>/g, "\n"); + cqGetChapters.each(function (i, e) { + const chapterA = loadedCheerio(this).find("a"); + const [chapterName, releaseDate, chapterUrl] = [ + // set the variables + chapterA.text(), + loadedCheerio(this) + .find("dt") // get title + .text() // get text + .replace(/(.)/g, "") // remove "(edited)" mark + .trim(), + "https://ncode.syosetu.com" + chapterA.attr("href"), + ]; + chapters.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + } + else { + /** + * Because there are oneshots on the site, they have to be treated with special care + * that's what pisses me off in Shosetsu app. They have this extension, + * but every oneshot is set as "there are no chapters" and all contents are thrown into the description!! + */ + // get summary for oneshot chapter + const nameResult = yield (0, fetch_1.fetchApi)(searchUrl() + `&word=${novel.name}`); + const nameBody = yield nameResult.text(); + const summaryQuery = (0, cheerio_1.load)(nameBody, { + decodeEntities: false, + }); + const foundText = summaryQuery(".searchkekka_box") + .first() + .find(".ex") + .text() + .replace(/\s{2,}/g, "\n"); + novel.summary = foundText; + // add single chapter + chapters.push({ + name: "Oneshot", + releaseTime: loadedCheerio("head") + .find("meta[name='WWWC']") + .attr("content"), + url: novelUrl, // set chapterUrl to oneshot so that chapterScraper knows it's a one-shot, + }); + } + novel.chapters = chapters; + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)(chapterUrl); + const body = yield result.text(); + // create cheerioQuery + const cheerioQuery = (0, cheerio_1.load)(body, { + decodeEntities: false, + }); + let chapterText = cheerioQuery("#novel_honbun") // get chapter text + .html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + let novels = []; + // returns list of novels from given page + function getNovelsFromPage(pagenumber) { + return __awaiter(this, void 0, void 0, function* () { + // load page + const result = yield (0, fetch_1.fetchApi)(searchUrl(pagenumber) + `&word=${searchTerm}`); + const body = yield result.text(); + // Cheerio it! + const cheerioQuery = (0, cheerio_1.load)(body, { decodeEntities: false }); + let pageNovels = []; + // find class=searchkekka_box + cheerioQuery(".searchkekka_box").each(function (i, e) { + // get div with link and name + const novelDIV = cheerioQuery(this).find(".novel_h"); + // get link element + const novelA = novelDIV.children()[0]; + // add new novel to array + pageNovels.push({ + name: novelDIV.text(), + url: novelA.attribs.href, + cover: defaultCover_1.defaultCover, + }); + }); + // return all novels from this page + return pageNovels; + }); + } + // counter of loaded pages + // let pagesLoaded = 0; + // do { + // // always load first one + // novels.push(...(await getNovelsFromPage(pagesLoaded + 1))); + // pagesLoaded++; + // } while (pagesLoaded < maxPageLoad && isNext); // check if we should load more + novels = yield getNovelsFromPage(1); + /** Use + * novels.push(...(await getNovelsFromPage(pageNumber))) + * if you want to load more + */ + // respond with novels! + return novels; + }); +}; +exports.searchNovels = searchNovels; +const fetchImage = function (url) { + return __awaiter(this, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); + }); +}; +exports.fetchImage = fetchImage; diff --git a/js/plugins/portuguese/lightnovelbrasil.js b/js/plugins/portuguese/lightnovelbrasil.js new file mode 100644 index 0000000..dfc7512 --- /dev/null +++ b/js/plugins/portuguese/lightnovelbrasil.js @@ -0,0 +1,301 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.filters = exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.icon = exports.version = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const filterInputs_1 = require("@libs/filterInputs"); +exports.id = "lightnovelbrasil"; +exports.name = "Light Novel Brasil"; +exports.version = "1.0.0"; +exports.icon = "multisrc/wpmangastream/icons/lightnovelbrasil.png"; +exports.site = "https://lightnovelbrasil.com/"; +exports.protected = false; +const pluginId = exports.id; +const baseUrl = exports.site; +const popularNovels = function (page, { filters }) { + return __awaiter(this, void 0, void 0, function* () { + let link = `${baseUrl}series/?page=${page}`; + if (filters) { + if (Array.isArray(filters.genres) && filters.genres.length) { + link += filters.genres.map((i) => `&genre[]=${i}`).join(""); + } + if (Array.isArray(filters.type) && filters.type.length) { + link += filters.type.map((i) => `&lang[]=${i}`).join(""); + } + } + link += "&status=" + ((filters === null || filters === void 0 ? void 0 : filters.status) ? filters === null || filters === void 0 ? void 0 : filters.status : ""); + link += "&order=" + ((filters === null || filters === void 0 ? void 0 : filters.order) ? filters === null || filters === void 0 ? void 0 : filters.order : "popular"); + const body = yield (0, fetch_1.fetchApi)(link, {}, pluginId).then((result) => result.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const novels = []; + loadedCheerio("article.bs").each(function () { + const novelName = loadedCheerio(this).find(".ntitle").text().trim(); + let image = loadedCheerio(this).find("img"); + const novelCover = image.attr("data-src") || image.attr("src"); + const novelUrl = loadedCheerio(this).find("a").attr("href"); + if (!novelUrl) + return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = novelUrl; + const result = yield (0, fetch_1.fetchApi)(url, {}, pluginId); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + const novel = { + url, + chapters: [], + }; + novel.name = loadedCheerio(".entry-title").text(); + novel.cover = + loadedCheerio("img.wp-post-image").attr("data-src") || + loadedCheerio("img.wp-post-image").attr("src"); + loadedCheerio("div.spe > span").each(function () { + const detailName = loadedCheerio(this).find("b").text().trim(); + const detail = loadedCheerio(this) + .find("b") + .remove() + .end() + .text() + .trim(); + switch (detailName) { + case "المؤلف:": + case "Yazar:": + case "Autor:": + case "Author:": + novel.author = detail; + break; + case "Status:": + case "Seviye:": + novel.status = detail; + break; + } + }); + novel.genres = loadedCheerio(".genxed") + .text() + .trim() + .replace(/\s/g, ","); + loadedCheerio('div[itemprop="description"] h3,p.a,strong').remove(); + novel.summary = loadedCheerio('div[itemprop="description"]') + .find("br") + .replaceWith("\n") + .end() + .text(); + let chapter = []; + loadedCheerio(".eplister") + .find("li") + .each(function () { + const chapterName = loadedCheerio(this).find(".epl-num").text() + + " - " + + loadedCheerio(this).find(".epl-title").text(); + const releaseDate = loadedCheerio(this) + .find(".epl-date") + .text() + .trim(); + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + if (!chapterUrl) + return; + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + novel.chapters = chapter.reverse(); + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)(chapterUrl, {}, pluginId); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let chapterText = loadedCheerio("div.epcontent").html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}?s=${searchTerm}`; + const result = yield (0, fetch_1.fetchApi)(url, {}, pluginId); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + const novels = []; + loadedCheerio("article.bs").each(function () { + const novelName = loadedCheerio(this).find(".ntitle").text().trim(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this).find("a").attr("href"); + if (!novelUrl) + return; + novels.push({ + name: novelName, + url: novelUrl, + cover: novelCover, + }); + }); + return novels; + }); +}; +exports.searchNovels = searchNovels; +const fetchImage = function (url) { + return __awaiter(this, void 0, void 0, function* () { + const headers = { + Referer: baseUrl, + }; + return yield (0, fetch_1.fetchFile)(url, { headers: headers }); + }); +}; +exports.fetchImage = fetchImage; +exports.filters = [ + { + key: "order", + label: "Sort By", + values: [ + { label: "Default", value: "" }, + { label: "A-Z", value: "title" }, + { label: "Z-A", value: "titlereverse" }, + { label: "Latest Update", value: "update" }, + { label: "Latest Added", value: "latest" }, + { label: "Popular", value: "popular" }, + ], + inputType: filterInputs_1.FilterInputs.Picker, + }, + { + key: "status", + label: "Status", + values: [ + { label: "All", value: "" }, + { label: "Ongoing", value: "ongoing" }, + { label: "Hiatus", value: "hiatus" }, + { label: "Completed", value: "completed" }, + ], + inputType: filterInputs_1.FilterInputs.Picker, + }, + { + key: "type", + label: "Type", + values: [ + { label: "Chinese novel", value: "chinese-novel" }, + { label: "habyeol", value: "habyeol" }, + { label: "korean novel", value: "korean-novel" }, + { label: "Web Novel", value: "web-novel" }, + { label: "삼심", value: "%ec%82%bc%ec%8b%ac" }, + { label: "호곡", value: "%ed%98%b8%ea%b3%a1" }, + ], + inputType: filterInputs_1.FilterInputs.Checkbox, + }, + { + key: "genres", + label: "Genres", + values: [ + { label: "A.I", value: "a.i" }, + { label: "Academy", value: "academy" }, + { label: "Action", value: "action" }, + { label: "Adult", value: "adult" }, + { label: "Adventure", value: "adventure" }, + { label: "Alternative History", value: "alternative-history" }, + { label: "Another World", value: "another-world" }, + { label: "Apocalypse", value: "apocalypse" }, + { label: "Bromance", value: "bromance" }, + { label: "Comedy", value: "comedy" }, + { label: "Dark fantasy", value: "dark-fantasy" }, + { label: "Demons", value: "demons" }, + { label: "Drama", value: "drama" }, + { label: "Dystopia", value: "dystopia" }, + { label: "Ecchi", value: "ecchi" }, + { label: "Entertainment", value: "entertainment" }, + { label: "Exhaustion", value: "exhaustion" }, + { label: "Fanfiction", value: "fanfiction" }, + { label: "fantasy", value: "fantasy" }, + { label: "finance", value: "finance" }, + { label: "Full color", value: "full-color" }, + { label: "Game", value: "game" }, + { label: "Gender Bender", value: "gender-bender" }, + { label: "Genius", value: "genius" }, + { label: "Harem", value: "harem" }, + { label: "Hero", value: "hero" }, + { label: "Historical", value: "historical" }, + { label: "Hunter", value: "hunter" }, + { label: "korean novel", value: "korean-novel" }, + { label: "Light Novel", value: "light-novel" }, + { + label: "List Adventure Manga Genres", + value: "list-adventure-manga-genres", + }, + { label: "Long Strip", value: "long-strip" }, + { label: "Love comedy", value: "love-comedy" }, + { label: "magic", value: "magic" }, + { label: "Manhua", value: "manhua" }, + { label: "Martial Arts", value: "martial-arts" }, + { label: "Mature", value: "mature" }, + { label: "Medieval", value: "medieval" }, + { label: "Misunderstanding", value: "misunderstanding" }, + { label: "Modern", value: "modern" }, + { label: "modern fantasy", value: "modern-fantasy" }, + { label: "music", value: "music" }, + { label: "Mystery", value: "mystery" }, + { label: "Necromancy", value: "necromancy" }, + { label: "No Romance", value: "no-romance" }, + { label: "NTL", value: "ntl" }, + { label: "o", value: "o" }, + { label: "Obsession", value: "obsession" }, + { label: "Politics", value: "politics" }, + { label: "Possession", value: "possession" }, + { label: "Programming", value: "programming" }, + { label: "Psychological", value: "psychological" }, + { label: "Pure Love", value: "pure-love" }, + { label: "Redemption", value: "redemption" }, + { label: "Regression", value: "regression" }, + { label: "Regret", value: "regret" }, + { label: "Reincarnation", value: "reincarnation" }, + { label: "Revenge", value: "revenge" }, + { label: "Romance", value: "romance" }, + { label: "Romance Fanrasy", value: "romance-fanrasy" }, + { label: "Salvation", value: "salvation" }, + { label: "School Life", value: "school-life" }, + { label: "Sci-fi", value: "sci-fi" }, + { label: "Science fiction", value: "science-fiction" }, + { label: "Seinen", value: "seinen" }, + { label: "Shounen", value: "shounen" }, + { label: "Slice of Life", value: "slice-of-life" }, + { label: "Soft yandere", value: "soft-yandere" }, + { label: "Sports", value: "sports" }, + { label: "Supernatural", value: "supernatural" }, + { label: "Survival", value: "survival" }, + { label: "system", value: "system" }, + { label: "Time limit", value: "time-limit" }, + { label: "Tragedy", value: "tragedy" }, + { label: "Transmigration", value: "transmigration" }, + { label: "TS", value: "ts" }, + { label: "Tsundere", value: "tsundere" }, + { label: "Unique", value: "unique" }, + { label: "Wholesome", value: "wholesome" }, + { label: "Wuxia", value: "wuxia" }, + { label: "Xuanhuan", value: "xuanhuan" }, + { label: "Yandere", value: "yandere" }, + { label: "Yuri", value: "yuri" }, + ], + inputType: filterInputs_1.FilterInputs.Checkbox, + }, +]; diff --git a/js/plugins/russian/Erolate[rulate].js b/js/plugins/russian/Erolate[rulate].js new file mode 100644 index 0000000..101c366 --- /dev/null +++ b/js/plugins/russian/Erolate[rulate].js @@ -0,0 +1,244 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.filters = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +// import dayjs from 'dayjs'; +const fetch_1 = require("@libs/fetch"); +// import { parseMadaraDate } from "@libs/parseMadaraDate"; +// import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; +// import { showToast } from "@libs/showToast"; +const filterInputs_1 = require("@libs/filterInputs"); +const novelStatus_1 = require("@libs/novelStatus"); +// import { defaultCover } from "@libs/defaultCover"; +exports.id = "erolate"; +exports.name = "Erolate"; +exports.icon = ""; +exports.version = "1.0.0"; +exports.site = "https://erolate.com"; +exports["protected"] = false; +const baseUrl = exports.site; +const customFilters = [{ "key": "genres", "label": "Жанры", "values": [{ "label": "Анал", "value": "2" }, { "label": "Бдсм", "value": "3" }, { "label": "Большая грудь", "value": "5" }, { "label": "Большая попка", "value": "6" }, { "label": "Большой член", "value": "7" }, { "label": "Бондаж", "value": "8" }, { "label": "В первый раз", "value": "9" }, { "label": "В цвете", "value": "10" }, { "label": "Гарем", "value": "11" }, { "label": "Гендарная интрига", "value": "12" }, { "label": "Групповой секс", "value": "13" }, { "label": "Детектив", "value": "39" }, { "label": "Драма", "value": "14" }, { "label": "Зрелые женщины (milf)", "value": "15" }, { "label": "Измена", "value": "16" }, { "label": "Изнасилование", "value": "17" }, { "label": "Инцест", "value": "18" }, { "label": "Исторический", "value": "19" }, { "label": "Комедия", "value": "20" }, { "label": "Контроль над разумом", "value": "35" }, { "label": "Маленькая грудь", "value": "21" }, { "label": "Мистика", "value": "40" }, { "label": "Научная фантастика", "value": "22" }, { "label": "Нетораре", "value": "23" }, { "label": "Оральный секс", "value": "24" }, { "label": "Повседневность", "value": "41" }, { "label": "Приключения", "value": "38" }, { "label": "Публичный секс", "value": "33" }, { "label": "Романтика", "value": "25" }, { "label": "С изображениями ", "value": "36" }, { "label": "Сверхъестественное", "value": "34" }, { "label": "Смат", "value": "37" }, { "label": "Тентакли", "value": "26" }, { "label": "Трагедия", "value": "27" }, { "label": "Ужасы", "value": "28" }, { "label": "Фантастика", "value": "32" }, { "label": "Фэнтези", "value": "29" }, { "label": "Чикан", "value": "30" }, { "label": "Этти", "value": "31" }, { "label": "Ahegao", "value": "1" }], "inputType": filterInputs_1.FilterInputs.Checkbox }, { "key": "tags", "label": "Тэги", "values": [{ "label": "+18", "value": "141" }, { "label": "18+", "value": "200" }, { "label": "21+", "value": "334" }, { "label": "Адекватные главные герои", "value": "253" }, { "label": "Азартные игры", "value": "210" }, { "label": "Айдолы", "value": "364" }, { "label": "Акула", "value": "53" }, { "label": "Альтернативное развитие событий", "value": "338" }, { "label": "Аморальный главный герой", "value": "219" }, { "label": "Анал", "value": "7" }, { "label": "Анальный секс", "value": "67" }, { "label": "Ангелы", "value": "114" }, { "label": "Антигерой", "value": "226" }, { "label": "Аристократия", "value": "260" }, { "label": "Артефакт", "value": "379" }, { "label": "Афродизиак", "value": "276" }, { "label": "Ахегао", "value": "240" }, { "label": "Бабушка беременна от внука", "value": "393" }, { "label": "Бабушка и внук", "value": "380" }, { "label": "Бдсм", "value": "131" }, { "label": "Беременность", "value": "148" }, { "label": "Бесплатно", "value": "79" }, { "label": "Бесстрашные персонажи", "value": "339" }, { "label": "Библиотека", "value": "265" }, { "label": "Бистиалити", "value": "391" }, { "label": "Близнецы", "value": "198" }, { "label": "Блондинка", "value": "91" }, { "label": "Богатые персонажи", "value": "248" }, { "label": "Боги", "value": "115" }, { "label": "Боевик", "value": "252" }, { "label": "Большая грудь", "value": "33" }, { "label": "Большая попка", "value": "328" }, { "label": "Большой член", "value": "76" }, { "label": "Большой член сына", "value": "396" }, { "label": "Босс и подчиненный", "value": "330" }, { "label": "Брак", "value": "182" }, { "label": "Брак по расчёту", "value": "346" }, { "label": "Брат", "value": "29" }, { "label": "Брат и сестра", "value": "30" }, { "label": "Брюнетка", "value": "28" }, { "label": "Бэтмен", "value": "369" }, { "label": "Бэтмен", "value": "170" }, { "label": "В первый раз", "value": "179" }, { "label": "Вагинальный секс", "value": "57" }, { "label": "Вагинальный секс", "value": "171" }, { "label": "Вайурист", "value": "9" }, { "label": "Вампиры", "value": "133" }, { "label": "Ван-пис", "value": "335" }, { "label": "Веб камера", "value": "289" }, { "label": "Ведьмы", "value": "232" }, { "label": "Викинги", "value": "307" }, { "label": "Виртуальная реальность", "value": "382" }, { "label": "Вирус", "value": "203" }, { "label": "Внучка", "value": "189" }, { "label": "Война", "value": "387" }, { "label": "Время", "value": "81" }, { "label": "Второй шанс", "value": "244" }, { "label": "Вуайеризм", "value": "62" }, { "label": "Выживание", "value": "135" }, { "label": "Гарем", "value": "107" }, { "label": "Гарри поттер", "value": "97" }, { "label": "Гг имба", "value": "87" }, { "label": "Генетические модификации", "value": "314" }, { "label": "Гипноз", "value": "143" }, { "label": "Главная героиня девушка", "value": "147" }, { "label": "Главный герой женщина", "value": "213" }, { "label": "Главный герой извращенец", "value": "116" }, { "label": "Главный герой мужчина", "value": "117" }, { "label": "Глубокое горло", "value": "94" }, { "label": "Гоблины", "value": "230" }, { "label": "Город", "value": "139" }, { "label": "Городское фэнтези", "value": "297" }, { "label": "Грудное молоко", "value": "225" }, { "label": "Групповой секс", "value": "72" }, { "label": "Гэнг-бэнг", "value": "235" }, { "label": "Гяру", "value": "222" }, { "label": "Двойное проникновение", "value": "42" }, { "label": "Двойной анал", "value": "47" }, { "label": "Дворяне", "value": "246" }, { "label": "Деамоны старший школы", "value": "291" }, { "label": "Девственница", "value": "119" }, { "label": "Девушки-монстры", "value": "223" }, { "label": "Демоны", "value": "98" }, { "label": "Детектив", "value": "249" }, { "label": "Дзёсэй", "value": "259" }, { "label": "Доктор", "value": "326" }, { "label": "Дом", "value": "138" }, { "label": "Доминирование", "value": "18" }, { "label": "Дочь", "value": "153" }, { "label": "Дочь беременна от отца", "value": "348" }, { "label": "Древние времена", "value": "383" }, { "label": "Древний китай", "value": "261" }, { "label": "Другой мир", "value": "365" }, { "label": "Дружба", "value": "325" }, { "label": "Друзья детства", "value": "183" }, { "label": "Дядя", "value": "160" }, { "label": "Жена", "value": "70" }, { "label": "Жена и муж", "value": "99" }, { "label": "Жена шлюха", "value": "236" }, { "label": "Женское доминирование", "value": "100" }, { "label": "Жесткий секс", "value": "283" }, { "label": "Жёсткий секс", "value": "101" }, { "label": "Жестокие персонажи", "value": "254" }, { "label": "Жестокий мир", "value": "336" }, { "label": "Жестокость", "value": "123" }, { "label": "Заботливый главный герой", "value": "340" }, { "label": "Зависимость", "value": "370" }, { "label": "Заключение", "value": "196" }, { "label": "Заключённые", "value": "384" }, { "label": "Замок", "value": "140" }, { "label": "Запретная любовь", "value": "273" }, { "label": "Заражение", "value": "206" }, { "label": "Звёздные войны", "value": "120" }, { "label": "Зверодевочки", "value": "158" }, { "label": "Зло", "value": "343" }, { "label": "Золовка", "value": "77" }, { "label": "Золотой дождь", "value": "146" }, { "label": "Зомби апокалипсис", "value": "241" }, { "label": "Зоофилия", "value": "144" }, { "label": "Игровые элементы", "value": "385" }, { "label": "Извращения", "value": "192" }, { "label": "Измена", "value": "71" }, { "label": "Изменения внешности", "value": "315" }, { "label": "Изменения личности", "value": "316" }, { "label": "Изнасилование", "value": "275" }, { "label": "Изуку мидория", "value": "337" }, { "label": "Ино", "value": "127" }, { "label": "Инопланетяне", "value": "304" }, { "label": "Интересный сюжет", "value": "349" }, { "label": "Интимные сцены", "value": "322" }, { "label": "Интриги и заговоры", "value": "262" }, { "label": "Интроверт", "value": "38" }, { "label": "Инфекция", "value": "207" }, { "label": "Инцест", "value": "35" }, { "label": "Камера", "value": "92" }, { "label": "Колледж", "value": "82" }, { "label": "Комикс", "value": "286" }, { "label": "Контроль", "value": "19" }, { "label": "Контроль над разумом", "value": "181" }, { "label": "Контроль разума", "value": "65" }, { "label": "Кончил внутрь", "value": "221" }, { "label": "Королевская семья", "value": "358" }, { "label": "Коррупция", "value": "49" }, { "label": "Космос", "value": "204" }, { "label": "Красивая главная героиня", "value": "255" }, { "label": "Красивые женщины", "value": "321" }, { "label": "Красивый главный герой", "value": "266" }, { "label": "Ксенофилия", "value": "386" }, { "label": "Кулинария", "value": "362" }, { "label": "Культвация", "value": "86" }, { "label": "Кунилингус", "value": "152" }, { "label": "Лактация", "value": "11" }, { "label": "Лишение девственности", "value": "212" }, { "label": "Лоли", "value": "218" }, { "label": "Лунатизм", "value": "279" }, { "label": "Любовь", "value": "89" }, { "label": "Магический мир", "value": "357" }, { "label": "Магия", "value": "80" }, { "label": "Мама", "value": "3" }, { "label": "Мама беременна от сына", "value": "274" }, { "label": "Мама и дочь", "value": "163" }, { "label": "Мама и сын", "value": "1" }, { "label": "Мама супермена", "value": "39" }, { "label": "Манипуляция временем", "value": "292" }, { "label": "Марвел", "value": "290" }, { "label": "Марвел", "value": "173" }, { "label": "Марти стью", "value": "102" }, { "label": "Массаж", "value": "224" }, { "label": "Мастурбация", "value": "63" }, { "label": "Мать", "value": "239" }, { "label": "Мать и дочь", "value": "381" }, { "label": "Мать и сын", "value": "237" }, { "label": "Мафия", "value": "256" }, { "label": "Мачеха", "value": "197" }, { "label": "Мачеха беременна от сына", "value": "395" }, { "label": "Мачеха и сын", "value": "394" }, { "label": "Медсестра", "value": "341" }, { "label": "Межрассовый", "value": "54" }, { "label": "Месть", "value": "306" }, { "label": "Меха", "value": "268" }, { "label": "Ми", "value": "243" }, { "label": "Милф", "value": "34" }, { "label": "Милф", "value": "180" }, { "label": "Минет", "value": "52" }, { "label": "Мистика", "value": "250" }, { "label": "Младшая сестра", "value": "154" }, { "label": "Модель", "value": "324" }, { "label": "Монстры", "value": "333" }, { "label": "Мошеничество", "value": "44" }, { "label": "Мужская беременность", "value": "363" }, { "label": "Мужчина протагонист", "value": "108" }, { "label": "Музыка", "value": "331" }, { "label": "Мэри съюха", "value": "309" }, { "label": "Наруто", "value": "46" }, { "label": "Насилие", "value": "128" }, { "label": "Насилие и жестокость", "value": "193" }, { "label": "Научный эксперимент", "value": "313" }, { "label": "Нежить", "value": "373" }, { "label": "Некромантия", "value": "298" }, { "label": "Ненормативная лексика", "value": "124" }, { "label": "Нетораре", "value": "69" }, { "label": "Нетори", "value": "284" }, { "label": "Нижнее бельё", "value": "392" }, { "label": "Ниндзя", "value": "296" }, { "label": "Ношеные трусики", "value": "166" }, { "label": "Нудизм", "value": "156" }, { "label": "Няня", "value": "88" }, { "label": "Обмен женами", "value": "78" }, { "label": "Оборотни", "value": "136" }, { "label": "Обратный гарем", "value": "214" }, { "label": "Одержимость", "value": "345" }, { "label": "Омегаверс", "value": "368" }, { "label": "Оплодотворение", "value": "157" }, { "label": "Оральный секс", "value": "10" }, { "label": "Оргия", "value": "356" }, { "label": "Орки", "value": "388" }, { "label": "От слабого к сильному", "value": "351" }, { "label": "Отец", "value": "2" }, { "label": "Отец делится с сыном", "value": "6" }, { "label": "Отец и дочь", "value": "205" }, { "label": "Отец куколд", "value": "272" }, { "label": "Офис", "value": "234" }, { "label": "Падчерица", "value": "271" }, { "label": "Пайзури", "value": "195" }, { "label": "Папа и дочь", "value": "360" }, { "label": "Паразиты", "value": "208" }, { "label": "Параллельный мир", "value": "320" }, { "label": "Пародия", "value": "371" }, { "label": "Первый раз", "value": "159" }, { "label": "Перемещение в другой мир", "value": "347" }, { "label": "Перемещение во времени", "value": "245" }, { "label": "Перерождение", "value": "109" }, { "label": "Перерождение в злодея", "value": "202" }, { "label": "Пирсинг", "value": "145" }, { "label": "Писатель", "value": "267" }, { "label": "Планомерное развитие событий", "value": "350" }, { "label": "Повседневность", "value": "247" }, { "label": "Подглядывание", "value": "280" }, { "label": "Подчинение", "value": "106" }, { "label": "Подчинение и унижение", "value": "211" }, { "label": "Поедание киски", "value": "26" }, { "label": "Покемоны", "value": "132" }, { "label": "Покорная", "value": "8" }, { "label": "Полигамия", "value": "352" }, { "label": "Половое воспитание", "value": "37" }, { "label": "Полулюди", "value": "231" }, { "label": "Попаданец", "value": "103" }, { "label": "Попадание в книгу", "value": "361" }, { "label": "Порка", "value": "20" }, { "label": "Порно", "value": "278" }, { "label": "Постапокалипсис", "value": "332" }, { "label": "Потеря девственности", "value": "142" }, { "label": "Похищение", "value": "161" }, { "label": "Приключения", "value": "134" }, { "label": "Принуждение", "value": "66" }, { "label": "Психология", "value": "238" }, { "label": "Публично", "value": "85" }, { "label": "Публичный секс", "value": "233" }, { "label": "Раб", "value": "353" }, { "label": "Рабы", "value": "308" }, { "label": "Разват", "value": "58" }, { "label": "Разврат", "value": "64" }, { "label": "Райзен", "value": "95" }, { "label": "Раса инопланетных космических лесбиянок", "value": "294" }, { "label": "Рестленг", "value": "84" }, { "label": "Риас гремори", "value": "389" }, { "label": "Романтика", "value": "178" }, { "label": "Рыжий", "value": "90" }, { "label": "Сакура", "value": "126" }, { "label": "Самолет", "value": "93" }, { "label": "Санса старк", "value": "277" }, { "label": "Санта", "value": "372" }, { "label": "Свадьба", "value": "73" }, { "label": "Сверхсила", "value": "228" }, { "label": "Сверхъестественное", "value": "319" }, { "label": "Свингеры", "value": "201" }, { "label": "Свободные отношения", "value": "186" }, { "label": "Связывание", "value": "137" }, { "label": "Сёдзе", "value": "264" }, { "label": "Секс", "value": "21" }, { "label": "Секс без проникновения", "value": "68" }, { "label": "Секс втроем", "value": "43" }, { "label": "Секс игрушки", "value": "59" }, { "label": "Секс рабыня", "value": "55" }, { "label": "Секс с монстрами", "value": "56" }, { "label": "Секс с учителем", "value": "184" }, { "label": "Секса будет много", "value": "151" }, { "label": "Сексуальное желание", "value": "209" }, { "label": "Селфцест", "value": "397" }, { "label": "Семья", "value": "5" }, { "label": "Сестра", "value": "31" }, { "label": "Сестра беременна от брата", "value": "355" }, { "label": "Сильный с самого начала", "value": "112" }, { "label": "Симбиоз", "value": "50" }, { "label": "Сирена", "value": "51" }, { "label": "Система", "value": "104" }, { "label": "Сиськи", "value": "285" }, { "label": "Сквирт", "value": "164" }, { "label": "Скрытый секс", "value": "287" }, { "label": "Служанка", "value": "215" }, { "label": "Соб", "value": "281" }, { "label": "Соблазнение", "value": "162" }, { "label": "Современность", "value": "217" }, { "label": "Соколиный Глаз", "value": "176" }, { "label": "Соперничество", "value": "216" }, { "label": "Соседи", "value": "61" }, { "label": "Соседка", "value": "312" }, { "label": "Сперма", "value": "229" }, { "label": "Сперма на лицо", "value": "199" }, { "label": "Спящие", "value": "187" }, { "label": "Старшая сестра", "value": "311" }, { "label": "Стеб", "value": "111" }, { "label": "Студенты", "value": "177" }, { "label": "Суккуб", "value": "390" }, { "label": "Супергерои", "value": "305" }, { "label": "Суперспособности", "value": "300" }, { "label": "Сын", "value": "4" }, { "label": "Твинцест", "value": "188" }, { "label": "Темное фэнтези", "value": "293" }, { "label": "Тентакли", "value": "303" }, { "label": "Тетя", "value": "113" }, { "label": "Тетя беременна от племянника", "value": "376" }, { "label": "Тетя и племянник", "value": "377" }, { "label": "Техника", "value": "302" }, { "label": "Технологии", "value": "301" }, { "label": "Тёща", "value": "270" }, { "label": "Тиран", "value": "310" }, { "label": "Трагическое прошлое", "value": "251" }, { "label": "Трансмиграция", "value": "269" }, { "label": "Трансформация", "value": "317" }, { "label": "Триллер", "value": "344" }, { "label": "Трусики", "value": "165" }, { "label": "Убийства", "value": "258" }, { "label": "Удача", "value": "299" }, { "label": "Ужасы", "value": "375" }, { "label": "Улучшение тела", "value": "288" }, { "label": "Умная главная героиня", "value": "242" }, { "label": "Умные персонажи", "value": "257" }, { "label": "Умный главный герой", "value": "354" }, { "label": "Университет", "value": "323" }, { "label": "Ученик", "value": "83" }, { "label": "Ф", "value": "149" }, { "label": "Фанатичная любовь", "value": "366" }, { "label": "Фанаты", "value": "367" }, { "label": "Фантастика", "value": "295" }, { "label": "Фанфик", "value": "40" }, { "label": "Фелляция", "value": "22" }, { "label": "Ферма", "value": "191" }, { "label": "Фетиш", "value": "60" }, { "label": "Флэш", "value": "121" }, { "label": "Футанария", "value": "150" }, { "label": "Фэнтези", "value": "359" }, { "label": "Хентай", "value": "75" }, { "label": "Хината", "value": "110" }, { "label": "Холодная главная героиня", "value": "263" }, { "label": "Черная вдова", "value": "220" }, { "label": "Черная Вдова", "value": "175" }, { "label": "Черный юмор", "value": "374" }, { "label": "Читерство", "value": "45" }, { "label": "Чтение мыслей", "value": "227" }, { "label": "Чудовища", "value": "378" }, { "label": "Шантаж", "value": "129" }, { "label": "Школа", "value": "118" }, { "label": "Школьная жизнь", "value": "318" }, { "label": "Шлюха", "value": "32" }, { "label": "Шоу-бизнес", "value": "329" }, { "label": "Шоу-бизнес", "value": "172" }, { "label": "Эксгибиционизм", "value": "74" }, { "label": "Элементы бдсм", "value": "185" }, { "label": "Эмма уотсон", "value": "342" }, { "label": "Эмма Уотсон", "value": "169" }, { "label": "Эротика", "value": "36" }, { "label": "Этти", "value": "327" }, { "label": "Юмор", "value": "105" }, { "label": "Яндере", "value": "155" }, { "label": "Bl", "value": "125" }, { "label": "Gl", "value": "96" }, { "label": "Harry potter", "value": "190" }, { "label": "Harry potter", "value": "168" }, { "label": "Hentai", "value": "282" }, { "label": "Marvel", "value": "194" }, { "label": "Marvel", "value": "174" }, { "label": "Milf", "value": "167" }], "inputType": filterInputs_1.FilterInputs.Checkbox }]; +exports.filters = [ + { + key: 'sort', + label: 'Сортировка', + values: [ + { label: 'По рейтингу', value: '6' }, + { label: 'По степени готовности', value: '0' }, + { label: 'По названию на языке оригинала', value: '1' }, + { label: 'По названию на языке перевода', value: '2' }, + { label: 'По дате создания', value: '3' }, + { label: 'По дате последней активности', value: '4' }, + { label: 'По просмотрам', value: '5' }, + { label: 'По кол-ву переведённых глав', value: '7' }, + { label: 'По кол-ву лайков', value: '8' }, + { label: 'По кол-ву страниц', value: '10' }, + { label: 'По кол-ву бесплатных глав', value: '11' }, + { label: 'По кол-ву рецензий', value: '12' }, + { label: 'По кол-ву в закладках', value: '13' }, + { label: 'По кол-ву в избранном', value: '14' }, + ], + inputType: filterInputs_1.FilterInputs.Picker, + }, + { + key: 'type', + label: 'Тип', + values: [ + { label: 'Неважно', value: '0' }, + { label: 'Только переводы', value: '1' }, + { label: 'Только авторские', value: '2' }, + ], + inputType: filterInputs_1.FilterInputs.Picker, + }, + { + key: 'atmosphere', + label: 'Атмосфера', + values: [ + { label: 'Неважно', value: '0' }, + { label: 'Позитивная', value: '1' }, + { label: 'Dark', value: '2' }, + ], + inputType: filterInputs_1.FilterInputs.Picker, + }, + { + key: 'trash', + label: 'Другое', + values: [ + { label: 'Готовые на 100%', value: 'ready' }, + { label: 'Доступные для перевода', value: 'tr' }, + { label: 'Доступные для скачивания', value: 'gen' }, + { label: 'Завершённые проекты', value: 'wealth' }, + { label: 'Распродажа', value: 'discount' }, + { label: 'Только онгоинги', value: 'ongoings' }, + { label: 'Убрать машинный', value: 'remove_machinelate' }, + ], + inputType: filterInputs_1.FilterInputs.Checkbox, + }, + ...customFilters, + { + key: 'adult', + label: 'Возрастные ограничения', + values: [ + { label: 'Все', value: '0' }, + { label: 'Убрать 18+', value: '1' }, + { label: 'Только 18+', value: '2' }, + ], + inputType: filterInputs_1.FilterInputs.Picker, + }, +]; +const popularNovels = function (page, { filters, showLatestNovels }) { + return __awaiter(this, void 0, void 0, function* () { + let url = baseUrl + '/search?t=&cat=2'; + url += '&sort=' + showLatestNovels ? '4' : (filters === null || filters === void 0 ? void 0 : filters.sort) || '6'; + url += '&type=' + (filters === null || filters === void 0 ? void 0 : filters.type) || '0'; + url += '&atmosphere=' + (filters === null || filters === void 0 ? void 0 : filters.atmosphere) || '0'; + url += '&adult=' + (filters === null || filters === void 0 ? void 0 : filters.adult) || '0'; + if ((filters === null || filters === void 0 ? void 0 : filters.genres) instanceof Array) { + url += filters.genres.map(i => '&genres[]=' + i).join(''); + } + if ((filters === null || filters === void 0 ? void 0 : filters.tags) instanceof Array) { + url += filters.tags.map(i => '&tags[]=' + i).join(''); + } + if ((filters === null || filters === void 0 ? void 0 : filters.trash) instanceof Array) { + url += filters.trash.map(i => '&' + i + '=1'); + } + url += '&Book_page=' + page; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + const novels = []; + loadedCheerio('ul[class="search-results"] > li:not([class="ad_type_catalog"])').each(function () { + novels.push({ + name: loadedCheerio(this).find('p > a').text(), + cover: baseUrl + loadedCheerio(this).find('img').attr('src'), + url: baseUrl + loadedCheerio(this).find('p > a').attr('href') || '', + }); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + return __awaiter(this, void 0, void 0, function* () { + const novel = { + url: novelUrl, + chapters: [], + }; + let result = yield (0, fetch_1.fetchApi)(novelUrl); + if (result.url.includes('mature?path=')) { + const formData = new FormData(); + formData.append('path', novelUrl); + formData.append('ok', 'Да'); + yield fetch(result.url, { + method: 'POST', + body: formData, + }); + result = yield (0, fetch_1.fetchApi)(novelUrl); + } + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + novel.name = loadedCheerio('div[class="container"] > div > div > h1') + .text() + .trim(); + novel.cover = + baseUrl + loadedCheerio('div[class="images"] > div img').attr('src'); + novel.summary = loadedCheerio('#Info > div:nth-child(3)').text(); + let genres = []; + loadedCheerio('div[class="span5"] > p').each(function () { + switch (loadedCheerio(this).find('strong').text()) { + case 'Автор:': + novel.author = loadedCheerio(this).find('em > a').text().trim(); + break; + case 'Выпуск:': + novel.status = + loadedCheerio(this).find('em').text().trim() === 'продолжается' + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + case 'Тэги:': + loadedCheerio(this) + .find('em > a') + .each(function () { + genres.push(loadedCheerio(this).text()); + }); + break; + case 'Жанры:': + loadedCheerio(this) + .find('em > a') + .each(function () { + genres.push(loadedCheerio(this).text()); + }); + break; + } + }); + if (genres.length > 0) { + novel.genres = genres.reverse().join(','); + } + const chapters = []; + loadedCheerio('table > tbody > tr.chapter_row').each(function () { + var _a; + const chapterName = loadedCheerio(this) + .find('td[class="t"] > a') + .text() + .trim(); + const releaseDate = (_a = loadedCheerio(this) + .find('td > span') + .attr('title')) === null || _a === void 0 ? void 0 : _a.trim(); + const chapterUrl = baseUrl + loadedCheerio(this) + .find('td[class="t"] > a') + .attr('href'); + if (loadedCheerio(this).find('td > span[class="disabled"]').length < 1 && + releaseDate) { + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + } + }); + novel.chapters = chapters; + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + let result = yield (0, fetch_1.fetchApi)(chapterUrl); + if (result.url.includes('mature?path=')) { + const formData = new FormData(); + formData.append('ok', 'Да'); + yield fetch(result.url, { + method: 'POST', + body: formData, + }); + result = yield (0, fetch_1.fetchApi)(chapterUrl); + } + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.content-text').html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const result = yield (0, fetch_1.fetchApi)(baseUrl + '/search/autocomplete?query=' + searchTerm); + let json = yield result.json(); + json.forEach((item) => { + const novelName = item.title_one + ' / ' + item.title_two; + const novelCover = baseUrl + item.img; + const novelUrl = baseUrl + item.url; + novels.push({ name: novelName, cover: novelCover, url: novelUrl }); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url, {}); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/russian/Rulate[rulate].js b/js/plugins/russian/Rulate[rulate].js new file mode 100644 index 0000000..c7d4b69 --- /dev/null +++ b/js/plugins/russian/Rulate[rulate].js @@ -0,0 +1,244 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.filters = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +// import dayjs from 'dayjs'; +const fetch_1 = require("@libs/fetch"); +// import { parseMadaraDate } from "@libs/parseMadaraDate"; +// import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; +// import { showToast } from "@libs/showToast"; +const filterInputs_1 = require("@libs/filterInputs"); +const novelStatus_1 = require("@libs/novelStatus"); +// import { defaultCover } from "@libs/defaultCover"; +exports.id = "tl.rulate"; +exports.name = "Rulate"; +exports.icon = ""; +exports.version = "1.0.0"; +exports.site = "https://tl.rulate.ru"; +exports["protected"] = false; +const baseUrl = exports.site; +const customFilters = [{ "key": "genres", "label": "Жанры", "values": [{ "label": "18+", "value": "52" }, { "label": "Арт", "value": "1" }, { "label": "Боевик", "value": "2" }, { "label": "Боевые искусства", "value": "3" }, { "label": "Гарем", "value": "5" }, { "label": "Героическое фэнтези", "value": "7" }, { "label": "Детектив", "value": "8" }, { "label": "Дзёсэй", "value": "9" }, { "label": "Додзинси", "value": "10" }, { "label": "Драма", "value": "11" }, { "label": "История", "value": "13" }, { "label": "Киберпанк", "value": "46" }, { "label": "Кодомо", "value": "14" }, { "label": "Комедия", "value": "15" }, { "label": "Литрпг", "value": "48" }, { "label": "Махо-сёдзё", "value": "16" }, { "label": "Мелодрама", "value": "49" }, { "label": "Меха", "value": "17" }, { "label": "Мистика", "value": "18" }, { "label": "Научная фантастика", "value": "19" }, { "label": "Повседневность", "value": "20" }, { "label": "Постапокалиптика", "value": "21" }, { "label": "Приключения", "value": "22" }, { "label": "Психология", "value": "23" }, { "label": "Романтика", "value": "24" }, { "label": "Самурайский боевик", "value": "25" }, { "label": "Сверхъестественное", "value": "26" }, { "label": "Сёдзё", "value": "27" }, { "label": "Сёнэн", "value": "29" }, { "label": "Смат", "value": "45" }, { "label": "Спорт", "value": "31" }, { "label": "Сэйнэн", "value": "32" }, { "label": "Сюаньхуа", "value": "44" }, { "label": "Сюаньхуань", "value": "47" }, { "label": "Сянься (XianXia)", "value": "42" }, { "label": "Трагедия", "value": "33" }, { "label": "Триллер", "value": "34" }, { "label": "Ужасы", "value": "35" }, { "label": "Уся (wuxia)", "value": "41" }, { "label": "Фантастика", "value": "36" }, { "label": "Фанфик", "value": "50" }, { "label": "Фэнтези", "value": "37" }, { "label": "Школа", "value": "38" }, { "label": "Этти", "value": "39" }, { "label": "Bl", "value": "43" }, { "label": "Gl", "value": "40" }], "inputType": filterInputs_1.FilterInputs.Checkbox }, { "key": "tags", "label": "Тэги", "values": [{ "label": "12+", "value": "3316" }, { "label": "16+", "value": "83" }, { "label": "18+", "value": "1749" }, { "label": "21+", "value": "3191" }, { "label": "Абсурд", "value": "7973" }, { "label": "Авантюристы", "value": "222" }, { "label": "Автомат", "value": "5272" }, { "label": "Авторский мир", "value": "1062" }, { "label": "Агрессивные персонажи", "value": "3249" }, { "label": "Ад", "value": "2623" }, { "label": "Ад и рай", "value": "817" }, { "label": "Адам дженсен", "value": "1649" }, { "label": "Адаптация произведения", "value": "192" }, { "label": "Адвокат", "value": "6340" }, { "label": "Адекватные главные герои", "value": "1953" }, { "label": "Азартные игры", "value": "5301" }, { "label": "Азгард", "value": "3494" }, { "label": "Азула", "value": "5550" }, { "label": "Аид", "value": "7641" }, { "label": "Айдолы", "value": "4081" }, { "label": "Академия", "value": "316" }, { "label": "Акамэ", "value": "5059" }, { "label": "Аквакинез", "value": "5952" }, { "label": "Акира", "value": "2639" }, { "label": "Акнология", "value": "4634" }, { "label": "Актерское мастерство", "value": "4798" }, { "label": "Актёры", "value": "7506" }, { "label": "Активные герои", "value": "6376" }, { "label": "Акутагава рюноске", "value": "4113" }, { "label": "Аладдин", "value": "6489" }, { "label": "Алая ведьма", "value": "4767" }, { "label": "Алиса", "value": "4761" }, { "label": "Алкоголь", "value": "5508" }, { "label": "Аллегория", "value": "271" }, { "label": "Алукард", "value": "4914" }, { "label": "Алхимики", "value": "1164" }, { "label": "Алхимия", "value": "1473" }, { "label": "Алчность", "value": "6897" }, { "label": "Альбус дамблдор", "value": "5784" }, { "label": "Альтернативное развитие событий", "value": "5330" }, { "label": "Альянсы", "value": "328" }, { "label": "Амбициозный главный герой", "value": "3751" }, { "label": "Америка", "value": "4082" }, { "label": "Амнезия", "value": "2207" }, { "label": "Аморальный герой", "value": "18" }, { "label": "Анальный секс", "value": "1942" }, { "label": "Анбу", "value": "8129" }, { "label": "Ангелы", "value": "857" }, { "label": "Ангелы и демоны", "value": "1327" }, { "label": "Ангст", "value": "4638" }, { "label": "Андроиды", "value": "1993" }, { "label": "Анимализм", "value": "5249" }, { "label": "Аниме", "value": "1294" }, { "label": "Анко митараши", "value": "5768" }, { "label": "Антагонист", "value": "2144" }, { "label": "Антигерой", "value": "7725" }, { "label": "Антиквариат", "value": "3552" }, { "label": "Антимаг", "value": "1348" }, { "label": "Антиутопия", "value": "662" }, { "label": "Апокалипсис", "value": "1498" }, { "label": "Аристократия", "value": "371" }, { "label": "Аристократы", "value": "7338" }, { "label": "Армия", "value": "1723" }, { "label": "Артас", "value": "6700" }, { "label": "Артефакты", "value": "2296" }, { "label": "Археология", "value": "6523" }, { "label": "Асгард", "value": "3635" }, { "label": "Ассасин", "value": "1415" }, { "label": "Асуна", "value": "5044" }, { "label": "Аугментации", "value": "7751" }, { "label": "Ацуши накаджима", "value": "4109" }, { "label": "Аянокоджи киетака", "value": "6862" }, { "label": "Бандиты", "value": "2188" }, { "label": "Басейн", "value": "6847" }, { "label": "Баскетбол", "value": "7933" }, { "label": "Бастарды", "value": "76" }, { "label": "Бдсм", "value": "444" }, { "label": "Бедность", "value": "1207" }, { "label": "Без культивации", "value": "668" }, { "label": "Без любовной линий", "value": "2225" }, { "label": "Без перерождений", "value": "262" }, { "label": "Без попаданца", "value": "8065" }, { "label": "Без системы", "value": "747" }, { "label": "Без цензуры", "value": "7525" }, { "label": "Без юмора", "value": "763" }, { "label": "Бездна", "value": "1026" }, { "label": "Безжалостное домашнее животное", "value": "1245" }, { "label": "Безжалостные персонажи", "value": "2986" }, { "label": "Беззаботные персонажи ", "value": "3123" }, { "label": "Безответная любовь", "value": "7815" }, { "label": "Безработный", "value": "2202" }, { "label": "Безумие", "value": "37" }, { "label": "Безумные персонажи", "value": "2049" }, { "label": "Бейсбол", "value": "7623" }, { "label": "Беременность", "value": "2122" }, { "label": "Бесконечный поток", "value": "7556" }, { "label": "Беспечные персонажи", "value": "742" }, { "label": "Бесплатно", "value": "8403" }, { "label": "Бессмертные персонажи", "value": "566" }, { "label": "Бесстрашные персонажи", "value": "6921" }, { "label": "Бесстыдные персонажи", "value": "1257" }, { "label": "Бесстыдный главный герой", "value": "8190" }, { "label": "Библиотека", "value": "4210" }, { "label": "Библия", "value": "488" }, { "label": "Бизнес", "value": "704" }, { "label": "Биология", "value": "2220" }, { "label": "Биомехи", "value": "1968" }, { "label": "Биседзе", "value": "777" }, { "label": "Битва за веру", "value": "721" }, { "label": "Битва за трон", "value": "7211" }, { "label": "Битва королевств", "value": "1151" }, { "label": "Битва рас", "value": "6989" }, { "label": "Битвы", "value": "1740" }, { "label": "Благородные персонажи", "value": "2201" }, { "label": "Близнецы", "value": "2931" }, { "label": "Богатство", "value": "6522" }, { "label": "Богатые персонажи", "value": "520" }, { "label": "Боги", "value": "173" }, { "label": "Богини", "value": "30" }, { "label": "Богиня", "value": "8324" }, { "label": "Богоубийца", "value": "1253" }, { "label": "Богохульство", "value": "95" }, { "label": "Боевая академия", "value": "2108" }, { "label": "Боевик", "value": "529" }, { "label": "Боевые искусства", "value": "2090" }, { "label": "Большая грудь", "value": "4717" }, { "label": "Большой член", "value": "5299" }, { "label": "Борос", "value": "6533" }, { "label": "Борьба", "value": "1925" }, { "label": "Борьба за власть", "value": "8072" }, { "label": "Босс и подчиненный", "value": "7157" }, { "label": "Бояръ", "value": "2307" }, { "label": "Брак", "value": "1558" }, { "label": "Брак по расчету", "value": "1389" }, { "label": "Брат и сестра", "value": "615" }, { "label": "Братский комплекс", "value": "2321" }, { "label": "Броманс", "value": "1103" }, { "label": "Брошенные дети", "value": "2846" }, { "label": "Буддизм", "value": "1871" }, { "label": "Будущее", "value": "1820" }, { "label": "Бывший муж", "value": "8080" }, { "label": "Быстрое развитие", "value": "1443" }, { "label": "Бэтгерл", "value": "2584" }, { "label": "Бэтмен", "value": "4668" }, { "label": "В первый раз", "value": "6212" }, { "label": "В этот же мир", "value": "49" }, { "label": "Вагинальный секс", "value": "4742" }, { "label": "Ваканда", "value": "8331" }, { "label": "Валькирии", "value": "4047" }, { "label": "Вампиры", "value": "2206" }, { "label": "Веб камера", "value": "5664" }, { "label": "Веб новелла", "value": "2349" }, { "label": "Ведьмак", "value": "843" }, { "label": "Ведьмы", "value": "643" }, { "label": "Везучие персонажи", "value": "1879" }, { "label": "Веном", "value": "3994" }, { "label": "Вестерн", "value": "2131" }, { "label": "Вестерос", "value": "8171" }, { "label": "Взросление", "value": "624" }, { "label": "Взрослый главный герой", "value": "2416" }, { "label": "Видео игры", "value": "2630" }, { "label": "Викинги", "value": "267" }, { "label": "Вино", "value": "6972" }, { "label": "Вирт", "value": "6213" }, { "label": "Виртуальная реальность", "value": "325" }, { "label": "Вирусы", "value": "3327" }, { "label": "Владыка демонов", "value": "555" }, { "label": "Власть", "value": "756" }, { "label": "Влюбленность", "value": "6132" }, { "label": "Внешний вид отличается от личности", "value": "1938" }, { "label": "Внучка", "value": "8399" }, { "label": "Военные", "value": "1096" }, { "label": "Возрождение", "value": "1160" }, { "label": "Воины", "value": "287" }, { "label": "Война", "value": "443" }, { "label": "Войны", "value": "2431" }, { "label": "Воландеморт", "value": "4278" }, { "label": "Волейбол", "value": "6415" }, { "label": "Волки", "value": "1166" }, { "label": "Волшебники", "value": "122" }, { "label": "Волшебные существа", "value": "1689" }, { "label": "Волшебство", "value": "719" }, { "label": "Вор", "value": "1399" }, { "label": "Воровка", "value": "6948" }, { "label": "Ворон", "value": "848" }, { "label": "Воскрешение", "value": "6915" }, { "label": "Воспоминания из другого мира", "value": "1366" }, { "label": "Воспоминания из прошлого", "value": "1372" }, { "label": "Воссоединие семьи", "value": "8130" }, { "label": "Восхождение", "value": "380" }, { "label": "Враги становятся любовниками", "value": "978" }, { "label": "Врата", "value": "40" }, { "label": "Врата в другой мир", "value": "1957" }, { "label": "Врач", "value": "1243" }, { "label": "Временная петля", "value": "2852" }, { "label": "Врмморпг", "value": "2043" }, { "label": "Все дозволено", "value": "96" }, { "label": "Всемогущий главный герой", "value": "1414" }, { "label": "Второй шанс", "value": "2372" }, { "label": "Вуайеризм", "value": "2498" }, { "label": "Выживание", "value": "501" }, { "label": "Вымышленные существа", "value": "3466" }, { "label": "Вынужденный брак", "value": "7705" }, { "label": "Высокие технологии", "value": "653" }, { "label": "Высокомерные персонажи", "value": "3250" }, { "label": "Галустян", "value": "5199" }, { "label": "Гангстеры", "value": "7704" }, { "label": "Гарем", "value": "440" }, { "label": "Гарри поттер", "value": "226" }, { "label": "Геймеры", "value": "2130" }, { "label": "Генетические модификации", "value": "1750" }, { "label": "Гениальный главный герой", "value": "1500" }, { "label": "Гений", "value": "1650" }, { "label": "Гермиона грейнджер", "value": "4280" }, { "label": "Герои", "value": "185" }, { "label": "Героическое фэнтези", "value": "1148" }, { "label": "Гигаполис", "value": "8393" }, { "label": "Гильгамеш", "value": "6031" }, { "label": "Гильдии", "value": "900" }, { "label": "Гильдия авантюристов", "value": "1858" }, { "label": "Гинтама", "value": "4544" }, { "label": "Гипноз", "value": "358" }, { "label": "Главная героиня влюбляется первой", "value": "973" }, { "label": "Главный герой бог", "value": "164" }, { "label": "Главный герой влюбляется первым", "value": "3629" }, { "label": "Главный герой выживальщик", "value": "120" }, { "label": "Главный герой гонг", "value": "7877" }, { "label": "Главный герой девушка", "value": "257" }, { "label": "Главный герой демон", "value": "136" }, { "label": "Главный герой женщина", "value": "7329" }, { "label": "Главный герой жестокий", "value": "1553" }, { "label": "Главный герой извращенец", "value": "94" }, { "label": "Главный герой имба", "value": "2421" }, { "label": "Главный герой киборг", "value": "7780" }, { "label": "Главный герой монстр", "value": "869" }, { "label": "Главный герой мужчина", "value": "1997" }, { "label": "Главный герой на стороне сил зла", "value": "1514" }, { "label": "Главный герой не адекватный", "value": "5" }, { "label": "Главный герой не психопат", "value": "7804" }, { "label": "Главный герой не человек", "value": "2172" }, { "label": "Главный герой не эмоционален", "value": "1150" }, { "label": "Главный герой подросток", "value": "2215" }, { "label": "Главный герой позитивный", "value": "7787" }, { "label": "Главный герой психопат", "value": "7803" }, { "label": "Главный герой ребенок", "value": "2347" }, { "label": "Главный герой русский", "value": "2417" }, { "label": "Главный герой сильный с самого начала", "value": "88" }, { "label": "Главный герой скрывает свою силу", "value": "1059" }, { "label": "Главный герой эгоист", "value": "8326" }, { "label": "Глотание спермы", "value": "4491" }, { "label": "Гномы", "value": "1870" }, { "label": "Гоблины", "value": "2029" }, { "label": "Годзилла", "value": "6990" }, { "label": "Големы", "value": "1690" }, { "label": "Голливуд", "value": "7754" }, { "label": "Головоломка", "value": "1697" }, { "label": "Горничные", "value": "8091" }, { "label": "Гробница", "value": "388" }, { "label": "Групповой секс", "value": "1314" }, { "label": "Дазай осаму", "value": "4112" }, { "label": "Даньмэй", "value": "3411" }, { "label": "Даосизм", "value": "258" }, { "label": "Дарк соулс", "value": "6057" }, { "label": "Дарт вейдер", "value": "6826" }, { "label": "Дафна гринграсс", "value": "5192" }, { "label": "Дворяне", "value": "7204" }, { "label": "Девушки", "value": "7668" }, { "label": "Девушки монстры", "value": "2231" }, { "label": "Дедушка и внучка", "value": "8400" }, { "label": "Дейви джонс", "value": "8061" }, { "label": "Действие", "value": "1425" }, { "label": "Демон лорд", "value": "153" }, { "label": "Демонология", "value": "2024" }, { "label": "Демоны", "value": "1406" }, { "label": "Детектив", "value": "1265" }, { "label": "Дети", "value": "580" }, { "label": "Джарвис", "value": "933" }, { "label": "Джедай", "value": "4442" }, { "label": "Джин", "value": "4024" }, { "label": "Джокер", "value": "3351" }, { "label": "Джон сноу", "value": "7412" }, { "label": "Джоффри баратеон", "value": "3936" }, { "label": "Дзесэй", "value": "678" }, { "label": "Дин винчестер", "value": "6953" }, { "label": "Династии", "value": "2982" }, { "label": "Динозавры", "value": "3666" }, { "label": "Директор", "value": "1417" }, { "label": "Для девушек", "value": "1441" }, { "label": "Дневник", "value": "298" }, { "label": "Добби", "value": "6762" }, { "label": "Добрый мир", "value": "1685" }, { "label": "Договоры с богами", "value": "2152" }, { "label": "Договоры с демонами", "value": "1922" }, { "label": "Доктор", "value": "1991" }, { "label": "Доктор стрэндж", "value": "7634" }, { "label": "Доминирование", "value": "2054" }, { "label": "Дота", "value": "6965" }, { "label": "Доудзюцу", "value": "7133" }, { "label": "Дочь", "value": "3291" }, { "label": "Драко малфой", "value": "46" }, { "label": "Драконы", "value": "282" }, { "label": "Драконы оборотни", "value": "8082" }, { "label": "Драма", "value": "611" }, { "label": "Древние времена", "value": "7781" }, { "label": "Древний египет", "value": "7614" }, { "label": "Древний китай", "value": "473" }, { "label": "Древний мир", "value": "4" }, { "label": "Дрочка", "value": "4565" }, { "label": "Другая вселенная", "value": "39" }, { "label": "Другие планеты", "value": "2371" }, { "label": "Другой мир", "value": "2248" }, { "label": "Дружба", "value": "1903" }, { "label": "Друзья детства", "value": "1153" }, { "label": "Духи", "value": "114" }, { "label": "Духовные силы", "value": "1955" }, { "label": "Души", "value": "1212" }, { "label": "Дьявол", "value": "1094" }, { "label": "Дьявольские фрукты", "value": "1564" }, { "label": "Дэдпул", "value": "4399" }, { "label": "Евнух", "value": "6293" }, { "label": "Европейская атмосфера", "value": "5837" }, { "label": "Египет", "value": "528" }, { "label": "Еда", "value": "2056" }, { "label": "Единороги", "value": "437" }, { "label": "Екаи", "value": "655" }, { "label": "Жалкий главный герой", "value": "97" }, { "label": "Жена", "value": "5729" }, { "label": "Женское доминирование", "value": "4622" }, { "label": "Женщина в теле мужчины", "value": "2748" }, { "label": "Женщина кошка", "value": "7513" }, { "label": "Жесткий секс", "value": "2238" }, { "label": "Жестокие персонажи ", "value": "3153" }, { "label": "Жестокий мир", "value": "1532" }, { "label": "Жестокое обращение с детьми", "value": "84" }, { "label": "Жестокость", "value": "7880" }, { "label": "Животные", "value": "7488" }, { "label": "Животные компаньоны", "value": "2061" }, { "label": "Жизнь и смерть", "value": "2014" }, { "label": "Жнец", "value": "6288" }, { "label": "Жойен рид", "value": "3926" }, { "label": "Заботливый главный герой", "value": "338" }, { "label": "Закалка тела", "value": "148" }, { "label": "Заклинания", "value": "558" }, { "label": "Заключенные", "value": "3052" }, { "label": "Занпакто", "value": "1102" }, { "label": "Запретная любовь", "value": "8033" }, { "label": "Запутанный сюжет", "value": "1041" }, { "label": "Захват мира", "value": "315" }, { "label": "Зачарованные", "value": "4169" }, { "label": "Звери", "value": "1638" }, { "label": "Зверодевочки", "value": "2006" }, { "label": "Зверолюди", "value": "1018" }, { "label": "Земля", "value": "82" }, { "label": "Злая организация", "value": "7768" }, { "label": "Зло", "value": "1512" }, { "label": "Злодей", "value": "1060" }, { "label": "Злодейка", "value": "2376" }, { "label": "Злой главный герой", "value": "616" }, { "label": "Злые боги", "value": "7109" }, { "label": "Злые персонажи", "value": "3243" }, { "label": "Змей", "value": "5721" }, { "label": "Знаменитости", "value": "1341" }, { "label": "Знание медицины", "value": "1411" }, { "label": "Золотой дождь", "value": "1828" }, { "label": "Золушка", "value": "2980" }, { "label": "Зомби", "value": "1743" }, { "label": "Зомби апокалипсис", "value": "912" }, { "label": "Зона 51", "value": "3051" }, { "label": "Зооморфы", "value": "375" }, { "label": "Зоофилия", "value": "1165" }, { "label": "Зорро", "value": "6736" }, { "label": "Зрелые женщины", "value": "1458" }, { "label": "Игра", "value": "663" }, { "label": "Игра на выживание", "value": "2182" }, { "label": "Игровая система", "value": "384" }, { "label": "Игровые элементы", "value": "720" }, { "label": "Извращения", "value": "651" }, { "label": "Измена", "value": "1874" }, { "label": "Изменение характера", "value": "1113" }, { "label": "Изменения внешности", "value": "259" }, { "label": "Изменения личности", "value": "1080" }, { "label": "Изнасилование", "value": "1283" }, { "label": "Изобретения", "value": "126" }, { "label": "Изуку мидория", "value": "6967" }, { "label": "Император", "value": "867" }, { "label": "Империи", "value": "712" }, { "label": "Империя", "value": "1716" }, { "label": "Инвалидность", "value": "2073" }, { "label": "Инквизитор", "value": "143" }, { "label": "Инкуб", "value": "3264" }, { "label": "Инопланетное вторжение", "value": "209" }, { "label": "Инопланетяне", "value": "866" }, { "label": "Интерактив с читателями", "value": "472" }, { "label": "Интересный сюжет", "value": "7374" }, { "label": "Интернет", "value": "739" }, { "label": "Интимные сцены", "value": "3450" }, { "label": "Интрига", "value": "317" }, { "label": "Интриги и заговоры", "value": "1009" }, { "label": "Инцест", "value": "1934" }, { "label": "Иные миры", "value": "35" }, { "label": "Исекай", "value": "2979" }, { "label": "Искусственный интеллект", "value": "1900" }, { "label": "Исследование", "value": "1626" }, { "label": "Исторический роман", "value": "573" }, { "label": "Итачи учиха", "value": "4556" }, { "label": "Ичиго", "value": "7254" }, { "label": "Йода", "value": "6829" }, { "label": "Йоруичи", "value": "6755" }, { "label": "Калипсо", "value": "8062" }, { "label": "Каннибализм", "value": "2249" }, { "label": "Капитан америка", "value": "6301" }, { "label": "Каракура", "value": "7473" }, { "label": "Карточная игра", "value": "1557" }, { "label": "Кастиэль", "value": "6954" }, { "label": "Кафе", "value": "3934" }, { "label": "Кацуки бакуго", "value": "7696" }, { "label": "Квесты", "value": "1707" }, { "label": "Киберпанк", "value": "1614" }, { "label": "Киберспорт", "value": "1984" }, { "label": "Кино", "value": "7126" }, { "label": "Китай", "value": "3762" }, { "label": "Кицунэ", "value": "241" }, { "label": "Кланы", "value": "834" }, { "label": "Клоны", "value": "1624" }, { "label": "Книги", "value": "1068" }, { "label": "Книжный червь", "value": "521" }, { "label": "Колдовство", "value": "2681" }, { "label": "Колледж", "value": "5058" }, { "label": "Коллекционер", "value": "2588" }, { "label": "Кольцо", "value": "1628" }, { "label": "Комедия", "value": "1430" }, { "label": "Коммунисты", "value": "1042" }, { "label": "Коноха", "value": "2827" }, { "label": "Контракт", "value": "4304" }, { "label": "Контроль разума", "value": "485" }, { "label": "Конфеты", "value": "341" }, { "label": "Кончил внутрь", "value": "4589" }, { "label": "Копрофилия", "value": "2568" }, { "label": "Корейцы", "value": "6535" }, { "label": "Корея", "value": "2443" }, { "label": "Королевская власть", "value": "55" }, { "label": "Королевская семья", "value": "276" }, { "label": "Королевство", "value": "1695" }, { "label": "Короткий рассказ", "value": "239" }, { "label": "Коррупция", "value": "831" }, { "label": "Космическая опера", "value": "2245" }, { "label": "Космические рейнджеры", "value": "5860" }, { "label": "Космос", "value": "329" }, { "label": "Кот", "value": "5171" }, { "label": "Кража навыков", "value": "1159" }, { "label": "Красивая главная героиня", "value": "186" }, { "label": "Красивые женщины", "value": "66" }, { "label": "Красивые персонажи ", "value": "5787" }, { "label": "Красивый главный герой", "value": "895" }, { "label": "Крафт", "value": "1599" }, { "label": "Кровь и расчлененка", "value": "7752" }, { "label": "Кроссовер", "value": "75" }, { "label": "Ксенофилия", "value": "2265" }, { "label": "Ктулху", "value": "920" }, { "label": "Кузнец", "value": "853" }, { "label": "Кукла", "value": "3085" }, { "label": "Кулинария", "value": "2230" }, { "label": "Культивация", "value": "1901" }, { "label": "Культивирование", "value": "1860" }, { "label": "Кунилингус", "value": "4651" }, { "label": "Курама", "value": "5709" }, { "label": "Курилин", "value": "6134" }, { "label": "Кушина узумаки", "value": "5222" }, { "label": "Лавкрафт", "value": "6063" }, { "label": "Лара крофт", "value": "6435" }, { "label": "Легенда о зельде", "value": "5707" }, { "label": "Ленивый главный герой", "value": "1516" }, { "label": "Леон золдик", "value": "7469" }, { "label": "Лес", "value": "6645" }, { "label": "Лечебная магия", "value": "562" }, { "label": "Литрпг", "value": "505" }, { "label": "Локи", "value": "1725" }, { "label": "Лоли", "value": "1576" }, { "label": "Лопата", "value": "4989" }, { "label": "Лорд", "value": "8404" }, { "label": "Лориен", "value": "7397" }, { "label": "Лошади", "value": "6110" }, { "label": "Лунатизм", "value": "3520" }, { "label": "Луффи", "value": "274" }, { "label": "Лучник", "value": "1038" }, { "label": "Любовный интерес влюбляется первым", "value": "7753" }, { "label": "Любовный треугольник", "value": "2109" }, { "label": "Любовь", "value": "2297" }, { "label": "Любовь с первого взгляда", "value": "7748" }, { "label": "Любящие родители", "value": "2699" }, { "label": "Люди", "value": "3572" }, { "label": "Люцифер", "value": "5883" }, { "label": "Маг", "value": "780" }, { "label": "Магазин", "value": "4762" }, { "label": "Маги", "value": "58" }, { "label": "Магическая академия", "value": "6788" }, { "label": "Магические звери", "value": "684" }, { "label": "Магические предметы", "value": "1648" }, { "label": "Магический мир", "value": "578" }, { "label": "Магия", "value": "150" }, { "label": "Магия воды", "value": "6450" }, { "label": "Магия воздуха", "value": "7627" }, { "label": "Магия земли", "value": "7626" }, { "label": "Магия и технология", "value": "107" }, { "label": "Магия крови", "value": "7976" }, { "label": "Магия льда", "value": "1379" }, { "label": "Магия огня", "value": "7625" }, { "label": "Магия призыва", "value": "885" }, { "label": "Магия природы", "value": "6226" }, { "label": "Магия стихий", "value": "7673" }, { "label": "Магия тьмы", "value": "3596" }, { "label": "Мадара", "value": "2929" }, { "label": "Мазохизм", "value": "1961" }, { "label": "Мама", "value": "12" }, { "label": "Мама и дочь", "value": "7894" }, { "label": "Мама и сын", "value": "7895" }, { "label": "Манга", "value": "2464" }, { "label": "Манипуляция временем", "value": "932" }, { "label": "Маньяк", "value": "2026" }, { "label": "Маргери тирелл", "value": "3935" }, { "label": "Марионетки", "value": "3175" }, { "label": "Марс", "value": "4013" }, { "label": "Марти стью", "value": "594" }, { "label": "Мастер на все руки", "value": "3189" }, { "label": "Мастурбация", "value": "3078" }, { "label": "Матриархат", "value": "7197" }, { "label": "Мать и сестры", "value": "7946" }, { "label": "Мафия", "value": "353" }, { "label": "Мачеха", "value": "4664" }, { "label": "Мегаполис", "value": "3322" }, { "label": "Медицина", "value": "441" }, { "label": "Медленная романтика", "value": "8383" }, { "label": "Медленное развитие истории", "value": "7462" }, { "label": "Межрасовый секс", "value": "3489" }, { "label": "Мелиодас", "value": "7224" }, { "label": "Мелодрама", "value": "1507" }, { "label": "Менеджмент", "value": "368" }, { "label": "Мерлин", "value": "3977" }, { "label": "Месть", "value": "1218" }, { "label": "Меха", "value": "2303" }, { "label": "Меч", "value": "48" }, { "label": "Мечники", "value": "969" }, { "label": "Мечта", "value": "527" }, { "label": "Мизуки", "value": "4269" }, { "label": "Микото", "value": "3862" }, { "label": "Милая главная героиня", "value": "134" }, { "label": "Милф", "value": "1987" }, { "label": "Милый главный герой", "value": "2678" }, { "label": "Мимик", "value": "5706" }, { "label": "Мина ашидо", "value": "7695" }, { "label": "Минато намикадзе", "value": "5223" }, { "label": "Минет", "value": "3289" }, { "label": "Мир будущего", "value": "1770" }, { "label": "Мир земли", "value": "8394" }, { "label": "Мир меча и магии", "value": "1271" }, { "label": "Мистер пропер", "value": "565" }, { "label": "Мистика", "value": "510" }, { "label": "Мифические существа", "value": "1878" }, { "label": "Мифы и легенды", "value": "4463" }, { "label": "Младшая сестра", "value": "1523" }, { "label": "Мморпг", "value": "620" }, { "label": "Много главных героев", "value": "1191" }, { "label": "Много крови", "value": "5318" }, { "label": "Много персонажей", "value": "1539" }, { "label": "Множество королевств", "value": "32" }, { "label": "Модерн", "value": "5457" }, { "label": "Мокутон", "value": "4438" }, { "label": "Молоко", "value": "5469" }, { "label": "Монархия", "value": "3590" }, { "label": "Монстры", "value": "307" }, { "label": "Море", "value": "922" }, { "label": "Моритика ринносукэ", "value": "1142" }, { "label": "Мортал комбат", "value": "5861" }, { "label": "Мошенник", "value": "959" }, { "label": "Мрачный мир", "value": "1" }, { "label": "Мужчина протагонист", "value": "31" }, { "label": "Музыка", "value": "1183" }, { "label": "Мультивселенная", "value": "7410" }, { "label": "Мурим", "value": "7342" }, { "label": "Мутанты", "value": "2392" }, { "label": "Мутация", "value": "29" }, { "label": "Мясо", "value": "1503" }, { "label": "На реальных событиях", "value": "26" }, { "label": "Навыки", "value": "415" }, { "label": "Нагато узумаки", "value": "5221" }, { "label": "Наемники", "value": "1668" }, { "label": "Назарик", "value": "5369" }, { "label": "Наивный главный герой", "value": "159" }, { "label": "Наложница", "value": "4867" }, { "label": "Наноботы", "value": "2332" }, { "label": "Наркотики", "value": "1436" }, { "label": "Наруто", "value": "7177" }, { "label": "Наруто и хината", "value": "5766" }, { "label": "Насилие и жестокость", "value": "1440" }, { "label": "Наука", "value": "2150" }, { "label": "Научная фантастика", "value": "2244" }, { "label": "Национализм", "value": "5556" }, { "label": "Нацу", "value": "5826" }, { "label": "Нацуки субару", "value": "6656" }, { "label": "Начальная школа", "value": "3920" }, { "label": "Наши дни", "value": "1814" }, { "label": "Не всесильный главный герой", "value": "42" }, { "label": "Не по канону", "value": "1611" }, { "label": "Неверность", "value": "5735" }, { "label": "Невеста", "value": "6011" }, { "label": "Неджи хьюга", "value": "3099" }, { "label": "Недооценённый главный герой", "value": "43" }, { "label": "Нежить", "value": "1949" }, { "label": "Некромаг", "value": "7441" }, { "label": "Некромант", "value": "1914" }, { "label": "Некромантия", "value": "754" }, { "label": "Некрофилия", "value": "1285" }, { "label": "Ненормативная лексика", "value": "160" }, { "label": "Необычные герои", "value": "67" }, { "label": "Нет людей", "value": "7486" }, { "label": "Нетораре", "value": "403" }, { "label": "Нетори", "value": "5393" }, { "label": "Нефилимы", "value": "7764" }, { "label": "Нецензурная лексика", "value": "4912" }, { "label": "Нечисть", "value": "4559" }, { "label": "Ник фьюри", "value": "6304" }, { "label": "Ниндзюцу", "value": "2354" }, { "label": "Ниндзя", "value": "1330" }, { "label": "Новелла", "value": "2105" }, { "label": "Нудизм", "value": "2233" }, { "label": "Нэко", "value": "147" }, { "label": "Оби ван кеноби", "value": "6830" }, { "label": "Обито учиха", "value": "2814" }, { "label": "Оборотни", "value": "53" }, { "label": "Обратный гарем", "value": "3303" }, { "label": "Овощ", "value": "6098" }, { "label": "Огнестрельное оружие", "value": "3155" }, { "label": "Одержимость", "value": "708" }, { "label": "Один в поле воин", "value": "6498" }, { "label": "Онлайн игра", "value": "1565" }, { "label": "Ооками теппей", "value": "849" }, { "label": "Оральный секс", "value": "605" }, { "label": "Оргия", "value": "974" }, { "label": "Оригинальные персонажи", "value": "7741" }, { "label": "Орки", "value": "1597" }, { "label": "Оружие", "value": "125" }, { "label": "Основано на видео игре", "value": "602" }, { "label": "Особые способности", "value": "2904" }, { "label": "От бедности к богатству", "value": "901" }, { "label": "От слабого до сильного", "value": "118" }, { "label": "Отель", "value": "2879" }, { "label": "Отец", "value": "3290" }, { "label": "Отношения", "value": "398" }, { "label": "Отношения человек/нечеловек", "value": "7406" }, { "label": "Офис", "value": "3460" }, { "label": "Охотники", "value": "2263" }, { "label": "Оцуцуки", "value": "5101" }, { "label": "Ошейник", "value": "2362" }, { "label": "Ояш", "value": "8048" }, { "label": "Падчерица", "value": "7023" }, { "label": "Падший ангел", "value": "184" }, { "label": "Пайзури", "value": "3560" }, { "label": "Палач", "value": "852" }, { "label": "Палочка", "value": "2682" }, { "label": "Пандорум", "value": "4360" }, { "label": "Папа и дочь", "value": "7647" }, { "label": "Паразит", "value": "1213" }, { "label": "Параллельный мир", "value": "2092" }, { "label": "Парень", "value": "619" }, { "label": "Паркур", "value": "3391" }, { "label": "Пародия", "value": "898" }, { "label": "Пасхалки", "value": "455" }, { "label": "Паук", "value": "2209" }, { "label": "Пекарь", "value": "5073" }, { "label": "Первая книга", "value": "269" }, { "label": "Первая любовь", "value": "640" }, { "label": "Первый раз", "value": "1465" }, { "label": "Переводы", "value": "24" }, { "label": "Перевоплощение", "value": "1596" }, { "label": "Перевоплощение в игровом мире", "value": "92" }, { "label": "Перемещение в другой мир", "value": "1288" }, { "label": "Перемещение во времени", "value": "146" }, { "label": "Перерождение", "value": "894" }, { "label": "Перерождение в демона", "value": "1111" }, { "label": "Перерождение в дракона", "value": "6867" }, { "label": "Перерождение в злодейку", "value": "5619" }, { "label": "Перерождение в злодея", "value": "749" }, { "label": "Перерождение в ином мире", "value": "109" }, { "label": "Перерождение в монстра", "value": "917" }, { "label": "Перерождений", "value": "335" }, { "label": "Переселение", "value": "813" }, { "label": "Переселение души", "value": "1864" }, { "label": "Перси джексон", "value": "6329" }, { "label": "Пилот", "value": "5521" }, { "label": "Пилюли", "value": "1531" }, { "label": "Пират", "value": "1579" }, { "label": "Пирог", "value": "80" }, { "label": "Писатель", "value": "734" }, { "label": "Пистолеты", "value": "4916" }, { "label": "Письмо", "value": "840" }, { "label": "Питомцы", "value": "3122" }, { "label": "Планомерное развитие событий", "value": "62" }, { "label": "Племянник", "value": "8303" }, { "label": "Пленница", "value": "4897" }, { "label": "Плети", "value": "4839" }, { "label": "Пляж", "value": "4243" }, { "label": "Повар", "value": "658" }, { "label": "Повествование от первого лица", "value": "1054" }, { "label": "Повествование от разных лиц", "value": "600" }, { "label": "Повествование от третьего лица", "value": "2116" }, { "label": "Повседневность", "value": "802" }, { "label": "Подавитель", "value": "497" }, { "label": "Подземелье", "value": "798" }, { "label": "Подростки", "value": "636" }, { "label": "Подруга", "value": "5804" }, { "label": "Подчинение", "value": "8343" }, { "label": "Подчинение и унижение", "value": "4500" }, { "label": "Покемоны", "value": "2840" }, { "label": "Полиамория", "value": "1300" }, { "label": "Полигамия", "value": "1336" }, { "label": "Политика", "value": "1313" }, { "label": "Полицейский", "value": "1907" }, { "label": "Полубог", "value": "7170" }, { "label": "Полукровка", "value": "999" }, { "label": "Полулюди", "value": "2273" }, { "label": "Поношенные трусики", "value": "1204" }, { "label": "Попаданец", "value": "2396" }, { "label": "Попаданец в другой мир", "value": "685" }, { "label": "Попаданец в игру", "value": "6466" }, { "label": "Попадание в книгу", "value": "6957" }, { "label": "Порно", "value": "4023" }, { "label": "Постапокалипсис", "value": "1079" }, { "label": "Потеря девственности", "value": "5879" }, { "label": "Похищение", "value": "7452" }, { "label": "Поэзия", "value": "1120" }, { "label": "Правитель", "value": "2261" }, { "label": "Превращения в животных", "value": "3410" }, { "label": "Преданный любовный интерес", "value": "2055" }, { "label": "Предательство", "value": "70" }, { "label": "Президент", "value": "4334" }, { "label": "Преступник", "value": "4049" }, { "label": "Преступный мир", "value": "965" }, { "label": "Призрак", "value": "511" }, { "label": "Призыв", "value": "332" }, { "label": "Призыв в другой мир", "value": "781" }, { "label": "Призыв существ", "value": "412" }, { "label": "Приключения", "value": "964" }, { "label": "Приложение на телефоне", "value": "5345" }, { "label": "Принудительный брак", "value": "2954" }, { "label": "Принуждение", "value": "1442" }, { "label": "Принц", "value": "362" }, { "label": "Принцесса", "value": "2403" }, { "label": "Приручение мостров", "value": "2338" }, { "label": "Прислуга", "value": "5185" }, { "label": "Пришельцы", "value": "273" }, { "label": "Проза", "value": "2012" }, { "label": "Прокачка", "value": "1070" }, { "label": "Проклятия", "value": "1347" }, { "label": "Проработанный мир", "value": "1711" }, { "label": "Пророчество", "value": "7762" }, { "label": "Псайкеры", "value": "3659" }, { "label": "Псионика", "value": "5216" }, { "label": "Психбольница", "value": "6969" }, { "label": "Психические расстройства", "value": "2445" }, { "label": "Психологические травмы", "value": "3418" }, { "label": "Психология", "value": "17" }, { "label": "Публичный дом", "value": "7860" }, { "label": "Публичный секс", "value": "6647" }, { "label": "Путешествие в другой мир", "value": "10" }, { "label": "Пух", "value": "4286" }, { "label": "Пушечное мясо", "value": "7234" }, { "label": "Пытки", "value": "1756" }, { "label": "Пьяный мастер", "value": "1655" }, { "label": "Пэнси паркинсон", "value": "7411" }, { "label": "Раб", "value": "977" }, { "label": "Рабы", "value": "1240" }, { "label": "Разведение животных", "value": "3990" }, { "label": "Развитие", "value": "208" }, { "label": "Развитие персонажа", "value": "1780" }, { "label": "Развитие поселения", "value": "7203" }, { "label": "Развитие технологий", "value": "1875" }, { "label": "Разврат", "value": "1924" }, { "label": "Раздвоение личности", "value": "2062" }, { "label": "Разные расы", "value": "1188" }, { "label": "Разорванная помолвка", "value": "2057" }, { "label": "Разработчик", "value": "2333" }, { "label": "Ранги", "value": "1568" }, { "label": "Ранобэ", "value": "1764" }, { "label": "Расизм", "value": "3554" }, { "label": "Растение", "value": "407" }, { "label": "Расчленение", "value": "7125" }, { "label": "Реализм", "value": "1180" }, { "label": "Ребенок", "value": "2881" }, { "label": "Ревность", "value": "293" }, { "label": "Революция", "value": "3996" }, { "label": "Реинкарнация", "value": "1302" }, { "label": "Реинкарнация в другом мире", "value": "2180" }, { "label": "Реинкарнация в игровой мир", "value": "304" }, { "label": "Рейджер", "value": "5147" }, { "label": "Религия", "value": "2184" }, { "label": "Рем", "value": "6655" }, { "label": "Ремесленник", "value": "1483" }, { "label": "Ремонт", "value": "5703" }, { "label": "Репликаторы", "value": "3495" }, { "label": "Ресторан", "value": "7519" }, { "label": "Риас гремори", "value": "6950" }, { "label": "Рим", "value": "612" }, { "label": "Риннеган", "value": "4230" }, { "label": "Ритуал", "value": "1233" }, { "label": "Робот", "value": "4011" }, { "label": "Роботы", "value": "467" }, { "label": "Рок ли", "value": "7002" }, { "label": "Рокси", "value": "7380" }, { "label": "Романтика", "value": "1025" }, { "label": "Рон уизли", "value": "4279" }, { "label": "Ророноа зоро", "value": "1590" }, { "label": "Росомаха", "value": "1035" }, { "label": "Российская империя", "value": "2146" }, { "label": "Россия", "value": "4246" }, { "label": "Рудеус", "value": "7379" }, { "label": "Руны", "value": "2393" }, { "label": "Русалки", "value": "3127" }, { "label": "Русь", "value": "2016" }, { "label": "Рыцари", "value": "1902" }, { "label": "Рэо хатаке", "value": "2823" }, { "label": "Сабли", "value": "6136" }, { "label": "Сайян", "value": "6802" }, { "label": "Сакура", "value": "2515" }, { "label": "Самец", "value": "1592" }, { "label": "Самоубийство", "value": "4312" }, { "label": "Самураи", "value": "576" }, { "label": "Санса старк", "value": "3925" }, { "label": "Сарказм", "value": "4658" }, { "label": "Саске", "value": "7237" }, { "label": "Сатира", "value": "215" }, { "label": "Сборник", "value": "7367" }, { "label": "Свадьба", "value": "1524" }, { "label": "Сверхсила", "value": "1055" }, { "label": "Сверхъестественное", "value": "1074" }, { "label": "Свингеры", "value": "514" }, { "label": "Свинцовые книги", "value": "5322" }, { "label": "Связывание", "value": "4506" }, { "label": "Священик", "value": "61" }, { "label": "Северус снейп", "value": "5783" }, { "label": "Сёдзе", "value": "2219" }, { "label": "Секс", "value": "2472" }, { "label": "Секс без проникновения", "value": "151" }, { "label": "Секс игрушки", "value": "1124" }, { "label": "Секс по принуждению", "value": "8335" }, { "label": "Секс по согласию", "value": "7307" }, { "label": "Секс рабыня", "value": "7174" }, { "label": "Секс с близнецами", "value": "7457" }, { "label": "Секс с монстрами", "value": "6073" }, { "label": "Секс с учителем", "value": "2570" }, { "label": "Секса будет много", "value": "7265" }, { "label": "Сексуальные и психологические отклонения", "value": "888" }, { "label": "Секты", "value": "1234" }, { "label": "Селфцест", "value": "7498" }, { "label": "Сельское хозяйство", "value": "787" }, { "label": "Семейный конфликт", "value": "935" }, { "label": "Семья", "value": "728" }, { "label": "Сенджу", "value": "6069" }, { "label": "Сёнэн", "value": "2058" }, { "label": "Серийный убийца", "value": "261" }, { "label": "Сестра", "value": "2342" }, { "label": "Сила глаза", "value": "1019" }, { "label": "Сильная героиня", "value": "7292" }, { "label": "Сильная главная героиня", "value": "1994" }, { "label": "Сильный главный герой", "value": "570" }, { "label": "Сильфиетта", "value": "7381" }, { "label": "Симбиот", "value": "2648" }, { "label": "Сириус блэк", "value": "7655" }, { "label": "Сирота", "value": "297" }, { "label": "Система", "value": "251" }, { "label": "Система уровней", "value": "2174" }, { "label": "Сиськи", "value": "16" }, { "label": "Ситх", "value": "1618" }, { "label": "Сказка", "value": "3570" }, { "label": "Скайрим", "value": "4032" }, { "label": "Скайуокер", "value": "6828" }, { "label": "Сквиб", "value": "4101" }, { "label": "Сквирт", "value": "4526" }, { "label": "Скелет", "value": "1315" }, { "label": "Скрытые способности", "value": "3417" }, { "label": "Скульптор", "value": "1178" }, { "label": "Слабая главная героиня", "value": "5805" }, { "label": "Слабый главный герой", "value": "731" }, { "label": "Славяне", "value": "2168" }, { "label": "Слизерин", "value": "6979" }, { "label": "Слизь", "value": "1095" }, { "label": "Сломанная игрушка", "value": "8396" }, { "label": "Служанка", "value": "3486" }, { "label": "Смартфон", "value": "457" }, { "label": "Смена сущности", "value": "8391" }, { "label": "Смерть", "value": "1779" }, { "label": "Смерть основного персонажа", "value": "1530" }, { "label": "Собаки", "value": "6109" }, { "label": "Соблазн", "value": "7994" }, { "label": "Соблазнение", "value": "2290" }, { "label": "Современность", "value": "568" }, { "label": "Современные знания", "value": "988" }, { "label": "Сожительство", "value": "3026" }, { "label": "Создание армии", "value": "1992" }, { "label": "Создание королевства", "value": "1497" }, { "label": "Создание пилюль", "value": "816" }, { "label": "Создатель", "value": "1045" }, { "label": "Сокровища", "value": "6521" }, { "label": "Сокрытие истинной личности", "value": "2894" }, { "label": "Сома и эрина", "value": "5737" }, { "label": "Соперничество", "value": "7699" }, { "label": "Соседи", "value": "4075" }, { "label": "Сотрудник", "value": "6339" }, { "label": "Социология", "value": "7618" }, { "label": "Спокойная главная героиня", "value": "236" }, { "label": "Спокойный главный герой", "value": "1656" }, { "label": "Спор", "value": "3484" }, { "label": "Спорт", "value": "1387" }, { "label": "Спортивное тело", "value": "8317" }, { "label": "Способность кражи", "value": "7784" }, { "label": "Сражения", "value": "23" }, { "label": "Средневековье", "value": "1022" }, { "label": "Сталкер", "value": "1932" }, { "label": "Становление государства", "value": "111" }, { "label": "Старшая школа", "value": "1658" }, { "label": "Стёб", "value": "454" }, { "label": "Стекло", "value": "7453" }, { "label": "Стеснительный персонаж", "value": "152" }, { "label": "Стимпанк", "value": "1894" }, { "label": "Стихи", "value": "1309" }, { "label": "Стратегия", "value": "569" }, { "label": "Стриптиз", "value": "6914" }, { "label": "Строительство", "value": "921" }, { "label": "Студент", "value": "1464" }, { "label": "Студентка", "value": "7518" }, { "label": "Суккубы", "value": "2662" }, { "label": "Супер зрение", "value": "6574" }, { "label": "Супер удача", "value": "489" }, { "label": "Супергерои", "value": "1481" }, { "label": "Суперзлодеи", "value": "2125" }, { "label": "Супермен", "value": "6997" }, { "label": "Суперсилы", "value": "2276" }, { "label": "Существа", "value": "6033" }, { "label": "Сферы", "value": "1077" }, { "label": "Схемы и заговоры", "value": "7116" }, { "label": "Сценарий", "value": "7489" }, { "label": "Счастливый конец", "value": "3293" }, { "label": "Сын", "value": "3297" }, { "label": "Сэйнэн", "value": "345" }, { "label": "Сэм винчестер", "value": "7461" }, { "label": "Сюаньхуа", "value": "2560" }, { "label": "Сюжетные повороты", "value": "7202" }, { "label": "Сянься", "value": "1126" }, { "label": "Таблетки для развития", "value": "589" }, { "label": "Таинственное прошлое", "value": "769" }, { "label": "Тайная личность", "value": "8096" }, { "label": "Тайная любовь", "value": "113" }, { "label": "Тайны", "value": "7481" }, { "label": "Тайные отношения", "value": "7450" }, { "label": "Тайцы", "value": "6413" }, { "label": "Танки", "value": "1787" }, { "label": "Твинцест", "value": "2450" }, { "label": "Творчество", "value": "20" }, { "label": "Тейлор эберт", "value": "3526" }, { "label": "Телекинез", "value": "2203" }, { "label": "Телепортация", "value": "914" }, { "label": "Телохранитель", "value": "723" }, { "label": "Темари", "value": "4810" }, { "label": "Темная магия", "value": "4788" }, { "label": "Темное фэнтези", "value": "1675" }, { "label": "Темные эльфы", "value": "7198" }, { "label": "Тенсейган", "value": "4367" }, { "label": "Тентакли", "value": "3236" }, { "label": "Тетя", "value": "4194" }, { "label": "Техномагия", "value": "508" }, { "label": "Тиран", "value": "1174" }, { "label": "Титаны", "value": "3308" }, { "label": "Тодороки шото", "value": "5105" }, { "label": "Токио", "value": "5791" }, { "label": "Том реддл", "value": "6652" }, { "label": "Тони старк", "value": "1338" }, { "label": "Тор", "value": "1800" }, { "label": "Тор и локи", "value": "2645" }, { "label": "Торговля", "value": "1637" }, { "label": "Травничество", "value": "221" }, { "label": "Трагедия", "value": "470" }, { "label": "Трагическое прошлое", "value": "644" }, { "label": "Трактир", "value": "1918" }, { "label": "Трансмиграция", "value": "1071" }, { "label": "Трансформация", "value": "156" }, { "label": "Трансформеры", "value": "4616" }, { "label": "Тревор филипс", "value": "5427" }, { "label": "Триллер", "value": "1676" }, { "label": "Тройняшки", "value": "7169" }, { "label": "Троли", "value": "4250" }, { "label": "Трудолюбивый главный герой", "value": "248" }, { "label": "Тупой главный герой", "value": "3710" }, { "label": "Тьма", "value": "1919" }, { "label": "Тюрьма", "value": "5597" }, { "label": "Тяжёлое детство", "value": "2253" }, { "label": "Убийства", "value": "378" }, { "label": "Убийцы", "value": "1766" }, { "label": "Убийцы драконов", "value": "1484" }, { "label": "Увечья", "value": "3266" }, { "label": "Удача", "value": "6575" }, { "label": "Ужасы", "value": "1747" }, { "label": "Узумаки", "value": "7579" }, { "label": "Укротитель", "value": "367" }, { "label": "Умения", "value": "531" }, { "label": "Умная главная героиня", "value": "1381" }, { "label": "Умные персонажи", "value": "2756" }, { "label": "Умный главный герой", "value": "2093" }, { "label": "Университет", "value": "2102" }, { "label": "Унижение", "value": "8342" }, { "label": "Уничтожение мира", "value": "81" }, { "label": "Управление временем", "value": "5648" }, { "label": "Уровни", "value": "8" }, { "label": "Усиление", "value": "3549" }, { "label": "Усопп", "value": "6737" }, { "label": "Устроенный брак", "value": "2223" }, { "label": "Усыновление", "value": "395" }, { "label": "Уся", "value": "1172" }, { "label": "Утопия", "value": "4677" }, { "label": "Уход за детьми", "value": "7609" }, { "label": "Учеба в университете", "value": "2797" }, { "label": "Учебное заведение", "value": "1817" }, { "label": "Ученые", "value": "1843" }, { "label": "Учитель", "value": "845" }, { "label": "Учиха", "value": "5348" }, { "label": "Фамильяры", "value": "1471" }, { "label": "Фанатичная любовь", "value": "6721" }, { "label": "Фанаты", "value": "7739" }, { "label": "Фантастика", "value": "286" }, { "label": "Фантастический мир", "value": "2414" }, { "label": "Фанфик", "value": "767" }, { "label": "Фармацевт", "value": "1706" }, { "label": "Фашисты", "value": "3118" }, { "label": "Феи", "value": "7185" }, { "label": "Фелляция", "value": "5880" }, { "label": "Феминизм", "value": "6527" }, { "label": "Феникс", "value": "2413" }, { "label": "Фенрир", "value": "2740" }, { "label": "Феодализм", "value": "280" }, { "label": "Ферма", "value": "285" }, { "label": "Фетиш", "value": "2398" }, { "label": "Фехтовальщик", "value": "4685" }, { "label": "Филиппины", "value": "128" }, { "label": "Философия", "value": "1859" }, { "label": "Фистинг", "value": "4206" }, { "label": "Флафф", "value": "5048" }, { "label": "Флер делакур", "value": "5191" }, { "label": "Флэш", "value": "4667" }, { "label": "Фокусник", "value": "7602" }, { "label": "Фольклор", "value": "4086" }, { "label": "Фугаку", "value": "6161" }, { "label": "Фурри", "value": "6631" }, { "label": "Футбол", "value": "1121" }, { "label": "Фэнтези", "value": "174" }, { "label": "Фэнтезийный мир", "value": "1556" }, { "label": "Хакер", "value": "475" }, { "label": "Халк", "value": "1196" }, { "label": "Хан нуньен синг", "value": "4817" }, { "label": "Ханма юджиро", "value": "4369" }, { "label": "Хаос", "value": "3117" }, { "label": "Харизматичный главный герой", "value": "2350" }, { "label": "Хатаке какаши", "value": "2820" }, { "label": "Хентай", "value": "1682" }, { "label": "Хиддлстон", "value": "5903" }, { "label": "Химавари", "value": "2583" }, { "label": "Хината", "value": "2513" }, { "label": "Хитрый главный герой", "value": "2036" }, { "label": "Хоббит", "value": "6698" }, { "label": "Хогвартс", "value": "5954" }, { "label": "Холодная главная героиня", "value": "6507" }, { "label": "Холодное оружие", "value": "4889" }, { "label": "Холодный главный герой", "value": "2686" }, { "label": "Храм", "value": "2135" }, { "label": "Христианство", "value": "877" }, { "label": "Художественная литература", "value": "2380" }, { "label": "Художница", "value": "518" }, { "label": "Хулиганы", "value": "854" }, { "label": "Цветок", "value": "902" }, { "label": "Цезарь", "value": "1705" }, { "label": "Целители", "value": "4523" }, { "label": "Церковь", "value": "1980" }, { "label": "Цундере", "value": "1078" }, { "label": "Чакра", "value": "1393" }, { "label": "Черный юмор", "value": "2868" }, { "label": "Честная главная героиня", "value": "5855" }, { "label": "Честный главный герой", "value": "129" }, { "label": "Читы", "value": "451" }, { "label": "Шантаж", "value": "7175" }, { "label": "Шаринган", "value": "5165" }, { "label": "Шинигами", "value": "4661" }, { "label": "Шиноби", "value": "1227" }, { "label": "Школьная жизнь", "value": "284" }, { "label": "Школьники", "value": "218" }, { "label": "Школьницы", "value": "5946" }, { "label": "Шлюха", "value": "5822" }, { "label": "Шоу-бизнес", "value": "7119" }, { "label": "Шпионы", "value": "4262" }, { "label": "Эволюция", "value": "2040" }, { "label": "Экзорцизм", "value": "676" }, { "label": "Экономика", "value": "121" }, { "label": "Эксгибиционизм", "value": "1643" }, { "label": "Экспансия", "value": "110" }, { "label": "Эксперименты над людьми", "value": "7408" }, { "label": "Экшен", "value": "2859" }, { "label": "Элементальная магия", "value": "1264" }, { "label": "Элементы бдсм", "value": "1323" }, { "label": "Эльфы", "value": "1362" }, { "label": "Эротика", "value": "2053" }, { "label": "Этти", "value": "1317" }, { "label": "Юмор", "value": "7360" }, { "label": "Яды", "value": "201" }, { "label": "Якудза", "value": "4599" }, { "label": "Яндере", "value": "7111" }, { "label": "Bl", "value": "8282" }, { "label": "Gl", "value": "8284" }, { "label": "Rpg", "value": "90" }, { "label": "Star wars", "value": "1985" }, { "label": "Time skip", "value": "824" }, { "label": "Warrior of light", "value": "5289" }], "inputType": filterInputs_1.FilterInputs.Checkbox }]; +exports.filters = [ + { + key: 'sort', + label: 'Сортировка', + values: [ + { label: 'По рейтингу', value: '6' }, + { label: 'По степени готовности', value: '0' }, + { label: 'По названию на языке оригинала', value: '1' }, + { label: 'По названию на языке перевода', value: '2' }, + { label: 'По дате создания', value: '3' }, + { label: 'По дате последней активности', value: '4' }, + { label: 'По просмотрам', value: '5' }, + { label: 'По кол-ву переведённых глав', value: '7' }, + { label: 'По кол-ву лайков', value: '8' }, + { label: 'По кол-ву страниц', value: '10' }, + { label: 'По кол-ву бесплатных глав', value: '11' }, + { label: 'По кол-ву рецензий', value: '12' }, + { label: 'По кол-ву в закладках', value: '13' }, + { label: 'По кол-ву в избранном', value: '14' }, + ], + inputType: filterInputs_1.FilterInputs.Picker, + }, + { + key: 'type', + label: 'Тип', + values: [ + { label: 'Неважно', value: '0' }, + { label: 'Только переводы', value: '1' }, + { label: 'Только авторские', value: '2' }, + ], + inputType: filterInputs_1.FilterInputs.Picker, + }, + { + key: 'atmosphere', + label: 'Атмосфера', + values: [ + { label: 'Неважно', value: '0' }, + { label: 'Позитивная', value: '1' }, + { label: 'Dark', value: '2' }, + ], + inputType: filterInputs_1.FilterInputs.Picker, + }, + { + key: 'trash', + label: 'Другое', + values: [ + { label: 'Готовые на 100%', value: 'ready' }, + { label: 'Доступные для перевода', value: 'tr' }, + { label: 'Доступные для скачивания', value: 'gen' }, + { label: 'Завершённые проекты', value: 'wealth' }, + { label: 'Распродажа', value: 'discount' }, + { label: 'Только онгоинги', value: 'ongoings' }, + { label: 'Убрать машинный', value: 'remove_machinelate' }, + ], + inputType: filterInputs_1.FilterInputs.Checkbox, + }, + ...customFilters, + { + key: 'adult', + label: 'Возрастные ограничения', + values: [ + { label: 'Все', value: '0' }, + { label: 'Убрать 18+', value: '1' }, + { label: 'Только 18+', value: '2' }, + ], + inputType: filterInputs_1.FilterInputs.Picker, + }, +]; +const popularNovels = function (page, { filters, showLatestNovels }) { + return __awaiter(this, void 0, void 0, function* () { + let url = baseUrl + '/search?t=&cat=2'; + url += '&sort=' + showLatestNovels ? '4' : (filters === null || filters === void 0 ? void 0 : filters.sort) || '6'; + url += '&type=' + (filters === null || filters === void 0 ? void 0 : filters.type) || '0'; + url += '&atmosphere=' + (filters === null || filters === void 0 ? void 0 : filters.atmosphere) || '0'; + url += '&adult=' + (filters === null || filters === void 0 ? void 0 : filters.adult) || '0'; + if ((filters === null || filters === void 0 ? void 0 : filters.genres) instanceof Array) { + url += filters.genres.map(i => '&genres[]=' + i).join(''); + } + if ((filters === null || filters === void 0 ? void 0 : filters.tags) instanceof Array) { + url += filters.tags.map(i => '&tags[]=' + i).join(''); + } + if ((filters === null || filters === void 0 ? void 0 : filters.trash) instanceof Array) { + url += filters.trash.map(i => '&' + i + '=1'); + } + url += '&Book_page=' + page; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + const novels = []; + loadedCheerio('ul[class="search-results"] > li:not([class="ad_type_catalog"])').each(function () { + novels.push({ + name: loadedCheerio(this).find('p > a').text(), + cover: baseUrl + loadedCheerio(this).find('img').attr('src'), + url: baseUrl + loadedCheerio(this).find('p > a').attr('href') || '', + }); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + return __awaiter(this, void 0, void 0, function* () { + const novel = { + url: novelUrl, + chapters: [], + }; + let result = yield (0, fetch_1.fetchApi)(novelUrl); + if (result.url.includes('mature?path=')) { + const formData = new FormData(); + formData.append('path', novelUrl); + formData.append('ok', 'Да'); + yield fetch(result.url, { + method: 'POST', + body: formData, + }); + result = yield (0, fetch_1.fetchApi)(novelUrl); + } + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + novel.name = loadedCheerio('div[class="container"] > div > div > h1') + .text() + .trim(); + novel.cover = + baseUrl + loadedCheerio('div[class="images"] > div img').attr('src'); + novel.summary = loadedCheerio('#Info > div:nth-child(3)').text(); + let genres = []; + loadedCheerio('div[class="span5"] > p').each(function () { + switch (loadedCheerio(this).find('strong').text()) { + case 'Автор:': + novel.author = loadedCheerio(this).find('em > a').text().trim(); + break; + case 'Выпуск:': + novel.status = + loadedCheerio(this).find('em').text().trim() === 'продолжается' + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + case 'Тэги:': + loadedCheerio(this) + .find('em > a') + .each(function () { + genres.push(loadedCheerio(this).text()); + }); + break; + case 'Жанры:': + loadedCheerio(this) + .find('em > a') + .each(function () { + genres.push(loadedCheerio(this).text()); + }); + break; + } + }); + if (genres.length > 0) { + novel.genres = genres.reverse().join(','); + } + const chapters = []; + loadedCheerio('table > tbody > tr.chapter_row').each(function () { + var _a; + const chapterName = loadedCheerio(this) + .find('td[class="t"] > a') + .text() + .trim(); + const releaseDate = (_a = loadedCheerio(this) + .find('td > span') + .attr('title')) === null || _a === void 0 ? void 0 : _a.trim(); + const chapterUrl = baseUrl + loadedCheerio(this) + .find('td[class="t"] > a') + .attr('href'); + if (loadedCheerio(this).find('td > span[class="disabled"]').length < 1 && + releaseDate) { + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + } + }); + novel.chapters = chapters; + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + let result = yield (0, fetch_1.fetchApi)(chapterUrl); + if (result.url.includes('mature?path=')) { + const formData = new FormData(); + formData.append('ok', 'Да'); + yield fetch(result.url, { + method: 'POST', + body: formData, + }); + result = yield (0, fetch_1.fetchApi)(chapterUrl); + } + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.content-text').html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const result = yield (0, fetch_1.fetchApi)(baseUrl + '/search/autocomplete?query=' + searchTerm); + let json = yield result.json(); + json.forEach((item) => { + const novelName = item.title_one + ' / ' + item.title_two; + const novelCover = baseUrl + item.img; + const novelUrl = baseUrl + item.url; + novels.push({ name: novelName, cover: novelCover, url: novelUrl }); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url, {}); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/russian/axelznovelspace[noveltl].js b/js/plugins/russian/axelznovelspace[noveltl].js new file mode 100644 index 0000000..476dab5 --- /dev/null +++ b/js/plugins/russian/axelznovelspace[noveltl].js @@ -0,0 +1,213 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.filters = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const dayjs_1 = __importDefault(require("dayjs")); +const fetch_1 = require("@libs/fetch"); +// import { parseMadaraDate } from "@libs/parseMadaraDate"; +// import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; +// import { showToast } from "@libs/showToast"; +const filterInputs_1 = require("@libs/filterInputs"); +const novelStatus_1 = require("@libs/novelStatus"); +const defaultCover_1 = require("@libs/defaultCover"); +exports.id = "axel.znovel"; +exports.name = "Аксел"; +exports.icon = ""; +exports.version = "1.0.0"; +exports.site = "https://axel.znovel.space"; +exports.filters = [{ "key": "tags", "label": "Тэги", "values": [{ "label": "Богини", "value": 316 }, { "label": "Везучий главный герой", "value": 411 }, { "label": "Виртуальная реальность", "value": 744 }, { "label": "Высокомерный герой", "value": 56 }, { "label": "Герой-извращенец", "value": 521 }, { "label": "Главная героиня — женщина", "value": 277 }, { "label": "Главный герой — мужчина", "value": 419 }, { "label": "Дворяне", "value": 487 }, { "label": "Девочки-волшебницы", "value": 415 }, { "label": "Демоны", "value": 193 }, { "label": "Дружба", "value": 293 }, { "label": "Есть аниме-адаптация", "value": 10 }, { "label": "Есть видеоигра", "value": 13 }, { "label": "Есть манга-адаптация", "value": 14 }, { "label": "Игра на выживание", "value": 696 }, { "label": "Из грязи в князи", "value": 542 }, { "label": "Королевская особа", "value": 596 }, { "label": "Ленивый главный герой", "value": 389 }, { "label": "Маги", "value": 757 }, { "label": "Магические технологии", "value": 417 }], "inputType": filterInputs_1.FilterInputs.Checkbox }]; +exports["protected"] = false; +const baseUrl = exports.site; +const domain = baseUrl.split('//')[1]; +const popularNovels = function (page, { filters }) { + var _a, _b, _c, _d, _e; + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: ((_b = (_a = filters === null || filters === void 0 ? void 0 : filters.tags) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0 > 0) ? { tags: filters === null || filters === void 0 ? void 0 : filters.tags } : {}, + hostname: domain, + limit: 40, + page, + }, + }), + }); + const json = yield result.json(); + const novels = (_e = (_d = (_c = json.data) === null || _c === void 0 ? void 0 : _c.projects) === null || _d === void 0 ? void 0 : _d.content) === null || _e === void 0 ? void 0 : _e.map((novel) => { + var _a; + return ({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: ((_a = novel.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + novel.covers[0].url + : defaultCover_1.defaultCover, + }); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Book', + query: 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}', + variables: { + hostname: domain, + project: novelUrl, + url: novelUrl, + }, + }), + }); + const json = yield result.json(); + const novel = { + url: novelUrl, + name: json.data.project.title, + cover: ((_a = json.data.project.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + json.data.project.covers[0].url + : defaultCover_1.defaultCover, + summary: (0, cheerio_1.load)((_b = json.data.project.annotation) === null || _b === void 0 ? void 0 : _b.text).text(), + author: json.data.project.persons[0].name.firstName + + ' ' + + json.data.project.persons[0].name.lastName, + status: json.data.project.translationStatus === 'active' + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed, + }; + let genres = [] + .concat(json.data.project.tags, json.data.project.genres) + .map((item) => (item === null || item === void 0 ? void 0 : item.nameRu) || (item === null || item === void 0 ? void 0 : item.nameEng)) + .filter(item => item); + if (genres.length > 0) { + novel.genres = genres.join(','); + } + let novelChapters = []; + json.data.project.subprojects.content.forEach((work) => work.volumes.content.forEach((volume) => volume.chapters.forEach((chapter) => (chapter === null || chapter === void 0 ? void 0 : chapter.published) && + novelChapters.push({ + name: volume.shortName + ' ' + chapter.title, + url: novelUrl + '/' + volume.url + '/' + chapter.url, + releaseTime: (0, dayjs_1.default)(chapter.publishDate).format('LLL'), + })))); + novel.chapters = novelChapters; + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'EReaderData', + query: 'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}', + variables: { + chapterSelector: { + fullUrl: chapterUrl, + }, + url: chapterUrl, + }, + }), + }); + const json = yield result.json(); + return (0, cheerio_1.load)(json.data.chapter.text.text).html(); + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + var _a, _b, _c; + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: { + query: searchTerm, + }, + hostname: domain, + limit: 40, + page: 1, + }, + }), + }); + const json = yield result.json(); + const novels = []; + (_c = (_b = (_a = json === null || json === void 0 ? void 0 : json.data) === null || _a === void 0 ? void 0 : _a.projects) === null || _b === void 0 ? void 0 : _b.content) === null || _c === void 0 ? void 0 : _c.forEach((novel) => { + var _a; + return novels.push({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: ((_a = novel.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + novel.covers[0].url + : defaultCover_1.defaultCover, + }); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url, {}); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/russian/belnoveltl[noveltl].js b/js/plugins/russian/belnoveltl[noveltl].js new file mode 100644 index 0000000..d0b33cf --- /dev/null +++ b/js/plugins/russian/belnoveltl[noveltl].js @@ -0,0 +1,209 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.filters = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const dayjs_1 = __importDefault(require("dayjs")); +const fetch_1 = require("@libs/fetch"); +const novelStatus_1 = require("@libs/novelStatus"); +const defaultCover_1 = require("@libs/defaultCover"); +exports.id = "bel.novel"; +exports.name = "Белманга"; +exports.icon = ""; +exports.version = "1.0.0"; +exports.site = "https://bel.novel.tl"; +exports.filters = []; +exports["protected"] = false; +const baseUrl = exports.site; +const domain = baseUrl.split('//')[1]; +const popularNovels = function (page, { filters }) { + var _a, _b, _c, _d, _e; + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: ((_b = (_a = filters === null || filters === void 0 ? void 0 : filters.tags) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0 > 0) ? { tags: filters === null || filters === void 0 ? void 0 : filters.tags } : {}, + hostname: domain, + limit: 40, + page, + }, + }), + }); + const json = yield result.json(); + const novels = (_e = (_d = (_c = json.data) === null || _c === void 0 ? void 0 : _c.projects) === null || _d === void 0 ? void 0 : _d.content) === null || _e === void 0 ? void 0 : _e.map((novel) => { + var _a; + return ({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: ((_a = novel.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + novel.covers[0].url + : defaultCover_1.defaultCover, + }); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Book', + query: 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}', + variables: { + hostname: domain, + project: novelUrl, + url: novelUrl, + }, + }), + }); + const json = yield result.json(); + const novel = { + url: novelUrl, + name: json.data.project.title, + cover: ((_a = json.data.project.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + json.data.project.covers[0].url + : defaultCover_1.defaultCover, + summary: (0, cheerio_1.load)((_b = json.data.project.annotation) === null || _b === void 0 ? void 0 : _b.text).text(), + author: json.data.project.persons[0].name.firstName + + ' ' + + json.data.project.persons[0].name.lastName, + status: json.data.project.translationStatus === 'active' + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed, + }; + let genres = [] + .concat(json.data.project.tags, json.data.project.genres) + .map((item) => (item === null || item === void 0 ? void 0 : item.nameRu) || (item === null || item === void 0 ? void 0 : item.nameEng)) + .filter(item => item); + if (genres.length > 0) { + novel.genres = genres.join(','); + } + let novelChapters = []; + json.data.project.subprojects.content.forEach((work) => work.volumes.content.forEach((volume) => volume.chapters.forEach((chapter) => (chapter === null || chapter === void 0 ? void 0 : chapter.published) && + novelChapters.push({ + name: volume.shortName + ' ' + chapter.title, + url: novelUrl + '/' + volume.url + '/' + chapter.url, + releaseTime: (0, dayjs_1.default)(chapter.publishDate).format('LLL'), + })))); + novel.chapters = novelChapters; + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'EReaderData', + query: 'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}', + variables: { + chapterSelector: { + fullUrl: chapterUrl, + }, + url: chapterUrl, + }, + }), + }); + const json = yield result.json(); + return (0, cheerio_1.load)(json.data.chapter.text.text).html(); + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + var _a, _b, _c; + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: { + query: searchTerm, + }, + hostname: domain, + limit: 40, + page: 1, + }, + }), + }); + const json = yield result.json(); + const novels = []; + (_c = (_b = (_a = json === null || json === void 0 ? void 0 : json.data) === null || _a === void 0 ? void 0 : _a.projects) === null || _b === void 0 ? void 0 : _b.content) === null || _c === void 0 ? void 0 : _c.forEach((novel) => { + var _a; + return novels.push({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: ((_a = novel.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + novel.covers[0].url + : defaultCover_1.defaultCover, + }); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url, {}); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/russian/jaomix.js b/js/plugins/russian/jaomix.js new file mode 100644 index 0000000..7877587 --- /dev/null +++ b/js/plugins/russian/jaomix.js @@ -0,0 +1,210 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.filters = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.icon = exports.version = exports.site = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const novelStatus_1 = require("@libs/novelStatus"); +const filterInputs_1 = require("@libs/filterInputs"); +exports.id = "jaomix.ru"; +exports.name = "Jaomix"; +exports.site = "https://jaomix.ru"; +exports.version = "1.0.0"; +exports.icon = "src/ru/jaomix/icon.png"; +const baseUrl = exports.site; +const popularNovels = function (page, { showLatestNovels, filters }) { + return __awaiter(this, void 0, void 0, function* () { + let url = baseUrl + "/?searchrn&sortby="; + url += showLatestNovels ? "upd" : (filters === null || filters === void 0 ? void 0 : filters.sort) || "count"; + if (filters) { + if (Array.isArray(filters.type) && filters.type.length) { + url += filters.type.map((i) => `&lang[]=${i}`).join(""); + } + if (Array.isArray(filters.genres) && filters.genres.length) { + url += filters.genres.map((i) => `&genre[]=${i}`).join(""); + } + } + url += `&page=${page}`; + const result = yield (0, fetch_1.fetchApi)(url); + let body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio('div[class="block-home"] > div[class="one"]').each(function () { + var _a; + const name = loadedCheerio(this) + .find('div[class="img-home"] > a') + .attr("title"); + const cover = (_a = loadedCheerio(this) + .find('div[class="img-home"] > a > img') + .attr("src")) === null || _a === void 0 ? void 0 : _a.replace("-150x150", ""); + const url = loadedCheerio(this) + .find('div[class="img-home"] > a') + .attr("href"); + if (!name || !url) + return; + novels.push({ name, cover, url }); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)(novelUrl); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let novel = { + url: novelUrl, + }; + novel.name = loadedCheerio('div[class="desc-book"] > h1').text().trim(); + novel.cover = loadedCheerio('div[class="img-book"] > img').attr("src"); + novel.summary = loadedCheerio('div[id="desc-tab"]').text().trim(); + loadedCheerio("#info-book > p").each(function () { + let text = loadedCheerio(this).text().replace(/,/g, "").split(" "); + if (text[0] === "Автор:") { + novel.author = text.splice(1).join(" "); + } + else if (text[0] === "Жанры:") { + novel.genres = text.splice(1).join(","); + } + else if (text[0] === "Статус:") { + novel.status = text.includes("продолжается") + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + } + }); + const chapters = []; + loadedCheerio(".download-chapter div.title").each(function () { + chapters.push({ + name: loadedCheerio(this).find("a").attr("title"), + releaseTime: loadedCheerio(this).find("time").text(), + url: loadedCheerio(this).find("a").attr("href"), + }); + }); + novel.chapters = chapters.reverse(); + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)(chapterUrl); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('div[class="adblock-service"]').remove(); + const chapterText = loadedCheerio('div[class="entry-content"]').html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}/?searchrn=${searchTerm}&but=Поиск по названию&sortby=upd`; + const result = yield (0, fetch_1.fetchApi)(url); + let body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio('div[class="block-home"] > div[class="one"]').each(function () { + var _a; + const name = loadedCheerio(this) + .find('div[class="img-home"] > a') + .attr("title"); + const cover = (_a = loadedCheerio(this) + .find('div[class="img-home"] > a > img') + .attr("src")) === null || _a === void 0 ? void 0 : _a.replace("-150x150", ""); + const url = loadedCheerio(this) + .find('div[class="img-home"] > a') + .attr("href"); + if (!name || !url) + return; + novels.push({ name, cover, url }); + }); + return novels; + }); +}; +exports.searchNovels = searchNovels; +exports.filters = [ + { + key: "sort", + label: "Сортировка", + values: [ + { label: "Имя", value: "alphabet" }, + { label: "Просмотры", value: "count" }, + { label: "Дате добавления", value: "new" }, + { label: "Дате обновления", value: "upd" }, + ], + inputType: filterInputs_1.FilterInputs.Picker, + }, + { + key: "type", + label: "Тип", + values: [ + { label: "Английский", value: "Английский" }, + { label: "Китайский", value: "Китайский" }, + { label: "Корейский", value: "Корейский" }, + { label: "Японский", value: "Японский" }, + ], + inputType: filterInputs_1.FilterInputs.Checkbox, + }, + { + key: "genres", + label: "Жанры", + values: [ + { label: "Боевые Искусства", value: "Боевые Искусства" }, + { label: "Виртуальный Мир", value: "Виртуальный Мир" }, + { label: "Гарем", value: "Гарем" }, + { label: "Детектив", value: "Детектив" }, + { label: "Драма", value: "Драма" }, + { label: "Игра", value: "Игра" }, + { label: "Истории из жизни", value: "Истории из жизни" }, + { label: "Исторический", value: "Исторический" }, + { label: "История", value: "История" }, + { label: "Исэкай", value: "Исэкай" }, + { label: "Комедия", value: "Комедия" }, + { label: "Меха", value: "Меха" }, + { label: "Мистика", value: "Мистика" }, + { label: "Научная Фантастика", value: "Научная Фантастика" }, + { label: "Повседневность", value: "Повседневность" }, + { label: "Постапокалипсис", value: "Постапокалипсис" }, + { label: "Приключения", value: "Приключения" }, + { label: "Психология", value: "Психология" }, + { label: "Романтика", value: "Романтика" }, + { label: "Сверхъестественное", value: "Сверхъестественное" }, + { label: "Сёнэн", value: "Сёнэн" }, + { label: "Сёнэн-ай", value: "Сёнэн-ай" }, + { label: "Спорт", value: "Спорт" }, + { label: "Сэйнэн", value: "Сэйнэн" }, + { label: "Сюаньхуа", value: "Сюаньхуа" }, + { label: "Трагедия", value: "Трагедия" }, + { label: "Триллер", value: "Триллер" }, + { label: "Фантастика", value: "Фантастика" }, + { label: "Фэнтези", value: "Фэнтези" }, + { label: "Хоррор", value: "Хоррор" }, + { label: "Школьная жизнь", value: "Школьная жизнь" }, + { label: "Шоунен", value: "Шоунен" }, + { label: "Экшн", value: "Экшн" }, + { label: "Этти", value: "Этти" }, + { label: "Юри", value: "Юри" }, + { label: "Adult", value: "Adult" }, + { label: "Ecchi", value: "Ecchi" }, + { label: "Josei", value: "Josei" }, + { label: "Lolicon", value: "Lolicon" }, + { label: "Mature", value: "Mature" }, + { label: "Shoujo", value: "Shoujo" }, + { label: "Wuxia", value: "Wuxia" }, + { label: "Xianxia", value: "Xianxia" }, + { label: "Xuanhuan", value: "Xuanhuan" }, + { label: "Yaoi", value: "Yaoi" }, + ], + inputType: filterInputs_1.FilterInputs.Checkbox, + }, +]; +exports.fetchImage = fetch_1.fetchFile; diff --git a/js/plugins/russian/japitnoveltl[noveltl].js b/js/plugins/russian/japitnoveltl[noveltl].js new file mode 100644 index 0000000..90aa0c9 --- /dev/null +++ b/js/plugins/russian/japitnoveltl[noveltl].js @@ -0,0 +1,213 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.filters = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const dayjs_1 = __importDefault(require("dayjs")); +const fetch_1 = require("@libs/fetch"); +// import { parseMadaraDate } from "@libs/parseMadaraDate"; +// import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; +// import { showToast } from "@libs/showToast"; +const filterInputs_1 = require("@libs/filterInputs"); +const novelStatus_1 = require("@libs/novelStatus"); +const defaultCover_1 = require("@libs/defaultCover"); +exports.id = "japit.novel"; +exports.name = "Japit Comics"; +exports.icon = ""; +exports.version = "1.0.0"; +exports.site = "https://japit.novel.tl"; +exports.filters = [{ "key": "tags", "label": "Тэги", "values": [{ "label": "Академия", "value": 5 }, { "label": "Андроиды", "value": 37 }, { "label": "Беззаботный герой", "value": 115 }, { "label": "Боги", "value": 318 }, { "label": "Богини", "value": 316 }, { "label": "Вампиры", "value": 742 }, { "label": "Везучий главный герой", "value": 411 }, { "label": "Влияние прошлого", "value": 517 }, { "label": "Внезапное обогащение", "value": 691 }, { "label": "Военные хроники", "value": 749 }, { "label": "Война", "value": 750 }, { "label": "Воспоминания прошлого", "value": 283 }, { "label": "Герои", "value": 341 }, { "label": "Героиня красавица", "value": 81 }, { "label": "Герой влюбляется первым", "value": 561 }, { "label": "Герой красавец", "value": 329 }, { "label": "Герой-бесстыдник", "value": 632 }, { "label": "Герой-извращенец", "value": 521 }, { "label": "Герой-трудяга", "value": 330 }, { "label": "R-15", "value": 570 }], "inputType": filterInputs_1.FilterInputs.Checkbox }]; +exports["protected"] = false; +const baseUrl = exports.site; +const domain = baseUrl.split('//')[1]; +const popularNovels = function (page, { filters }) { + var _a, _b, _c, _d, _e; + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: ((_b = (_a = filters === null || filters === void 0 ? void 0 : filters.tags) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0 > 0) ? { tags: filters === null || filters === void 0 ? void 0 : filters.tags } : {}, + hostname: domain, + limit: 40, + page, + }, + }), + }); + const json = yield result.json(); + const novels = (_e = (_d = (_c = json.data) === null || _c === void 0 ? void 0 : _c.projects) === null || _d === void 0 ? void 0 : _d.content) === null || _e === void 0 ? void 0 : _e.map((novel) => { + var _a; + return ({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: ((_a = novel.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + novel.covers[0].url + : defaultCover_1.defaultCover, + }); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Book', + query: 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}', + variables: { + hostname: domain, + project: novelUrl, + url: novelUrl, + }, + }), + }); + const json = yield result.json(); + const novel = { + url: novelUrl, + name: json.data.project.title, + cover: ((_a = json.data.project.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + json.data.project.covers[0].url + : defaultCover_1.defaultCover, + summary: (0, cheerio_1.load)((_b = json.data.project.annotation) === null || _b === void 0 ? void 0 : _b.text).text(), + author: json.data.project.persons[0].name.firstName + + ' ' + + json.data.project.persons[0].name.lastName, + status: json.data.project.translationStatus === 'active' + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed, + }; + let genres = [] + .concat(json.data.project.tags, json.data.project.genres) + .map((item) => (item === null || item === void 0 ? void 0 : item.nameRu) || (item === null || item === void 0 ? void 0 : item.nameEng)) + .filter(item => item); + if (genres.length > 0) { + novel.genres = genres.join(','); + } + let novelChapters = []; + json.data.project.subprojects.content.forEach((work) => work.volumes.content.forEach((volume) => volume.chapters.forEach((chapter) => (chapter === null || chapter === void 0 ? void 0 : chapter.published) && + novelChapters.push({ + name: volume.shortName + ' ' + chapter.title, + url: novelUrl + '/' + volume.url + '/' + chapter.url, + releaseTime: (0, dayjs_1.default)(chapter.publishDate).format('LLL'), + })))); + novel.chapters = novelChapters; + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'EReaderData', + query: 'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}', + variables: { + chapterSelector: { + fullUrl: chapterUrl, + }, + url: chapterUrl, + }, + }), + }); + const json = yield result.json(); + return (0, cheerio_1.load)(json.data.chapter.text.text).html(); + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + var _a, _b, _c; + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: { + query: searchTerm, + }, + hostname: domain, + limit: 40, + page: 1, + }, + }), + }); + const json = yield result.json(); + const novels = []; + (_c = (_b = (_a = json === null || json === void 0 ? void 0 : json.data) === null || _a === void 0 ? void 0 : _a.projects) === null || _b === void 0 ? void 0 : _b.content) === null || _c === void 0 ? void 0 : _c.forEach((novel) => { + var _a; + return novels.push({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: ((_a = novel.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + novel.covers[0].url + : defaultCover_1.defaultCover, + }); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url, {}); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/russian/kgnoveltl[noveltl].js b/js/plugins/russian/kgnoveltl[noveltl].js new file mode 100644 index 0000000..ed871ac --- /dev/null +++ b/js/plugins/russian/kgnoveltl[noveltl].js @@ -0,0 +1,213 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.filters = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const dayjs_1 = __importDefault(require("dayjs")); +const fetch_1 = require("@libs/fetch"); +// import { parseMadaraDate } from "@libs/parseMadaraDate"; +// import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; +// import { showToast } from "@libs/showToast"; +const filterInputs_1 = require("@libs/filterInputs"); +const novelStatus_1 = require("@libs/novelStatus"); +const defaultCover_1 = require("@libs/defaultCover"); +exports.id = "kg.novel"; +exports.name = "Карманная галактика"; +exports.icon = ""; +exports.version = "1.0.0"; +exports.site = "https://kg.novel.tl"; +exports.filters = [{ "key": "tags", "label": "Тэги", "values": [{ "label": "Алхимия", "value": 27 }, { "label": "Андроиды", "value": 37 }, { "label": "Анти-рост персонажа", "value": 25 }, { "label": "Антигерой", "value": 43 }, { "label": "Апатичный главный герой", "value": 46 }, { "label": "Аристократия", "value": 51 }, { "label": "Асоциальный главный герой", "value": 42 }, { "label": "Ассасины", "value": 61 }, { "label": "Беззаботный герой", "value": 115 }, { "label": "Безработные", "value": 377 }, { "label": "Бессмертные", "value": 359 }, { "label": "Библиотека", "value": 393 }, { "label": "Боги", "value": 318 }, { "label": "Боевая академия", "value": 75 }, { "label": "Бывший герой", "value": 290 }, { "label": "Ведьмы", "value": 756 }, { "label": "Везучий главный герой", "value": 411 }, { "label": "Верные подданные", "value": 410 }, { "label": "Взросление персонажа", "value": 24 }, { "label": "R-15", "value": 570 }], "inputType": filterInputs_1.FilterInputs.Checkbox }]; +exports["protected"] = false; +const baseUrl = exports.site; +const domain = baseUrl.split('//')[1]; +const popularNovels = function (page, { filters }) { + var _a, _b, _c, _d, _e; + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: ((_b = (_a = filters === null || filters === void 0 ? void 0 : filters.tags) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0 > 0) ? { tags: filters === null || filters === void 0 ? void 0 : filters.tags } : {}, + hostname: domain, + limit: 40, + page, + }, + }), + }); + const json = yield result.json(); + const novels = (_e = (_d = (_c = json.data) === null || _c === void 0 ? void 0 : _c.projects) === null || _d === void 0 ? void 0 : _d.content) === null || _e === void 0 ? void 0 : _e.map((novel) => { + var _a; + return ({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: ((_a = novel.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + novel.covers[0].url + : defaultCover_1.defaultCover, + }); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Book', + query: 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}', + variables: { + hostname: domain, + project: novelUrl, + url: novelUrl, + }, + }), + }); + const json = yield result.json(); + const novel = { + url: novelUrl, + name: json.data.project.title, + cover: ((_a = json.data.project.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + json.data.project.covers[0].url + : defaultCover_1.defaultCover, + summary: (0, cheerio_1.load)((_b = json.data.project.annotation) === null || _b === void 0 ? void 0 : _b.text).text(), + author: json.data.project.persons[0].name.firstName + + ' ' + + json.data.project.persons[0].name.lastName, + status: json.data.project.translationStatus === 'active' + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed, + }; + let genres = [] + .concat(json.data.project.tags, json.data.project.genres) + .map((item) => (item === null || item === void 0 ? void 0 : item.nameRu) || (item === null || item === void 0 ? void 0 : item.nameEng)) + .filter(item => item); + if (genres.length > 0) { + novel.genres = genres.join(','); + } + let novelChapters = []; + json.data.project.subprojects.content.forEach((work) => work.volumes.content.forEach((volume) => volume.chapters.forEach((chapter) => (chapter === null || chapter === void 0 ? void 0 : chapter.published) && + novelChapters.push({ + name: volume.shortName + ' ' + chapter.title, + url: novelUrl + '/' + volume.url + '/' + chapter.url, + releaseTime: (0, dayjs_1.default)(chapter.publishDate).format('LLL'), + })))); + novel.chapters = novelChapters; + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'EReaderData', + query: 'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}', + variables: { + chapterSelector: { + fullUrl: chapterUrl, + }, + url: chapterUrl, + }, + }), + }); + const json = yield result.json(); + return (0, cheerio_1.load)(json.data.chapter.text.text).html(); + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + var _a, _b, _c; + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: { + query: searchTerm, + }, + hostname: domain, + limit: 40, + page: 1, + }, + }), + }); + const json = yield result.json(); + const novels = []; + (_c = (_b = (_a = json === null || json === void 0 ? void 0 : json.data) === null || _a === void 0 ? void 0 : _a.projects) === null || _b === void 0 ? void 0 : _b.content) === null || _c === void 0 ? void 0 : _c.forEach((novel) => { + var _a; + return novels.push({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: ((_a = novel.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + novel.covers[0].url + : defaultCover_1.defaultCover, + }); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url, {}); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/russian/kodnoveltl[noveltl].js b/js/plugins/russian/kodnoveltl[noveltl].js new file mode 100644 index 0000000..f41a0cd --- /dev/null +++ b/js/plugins/russian/kodnoveltl[noveltl].js @@ -0,0 +1,213 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.filters = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const dayjs_1 = __importDefault(require("dayjs")); +const fetch_1 = require("@libs/fetch"); +// import { parseMadaraDate } from "@libs/parseMadaraDate"; +// import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; +// import { showToast } from "@libs/showToast"; +const filterInputs_1 = require("@libs/filterInputs"); +const novelStatus_1 = require("@libs/novelStatus"); +const defaultCover_1 = require("@libs/defaultCover"); +exports.id = "kod.novel"; +exports.name = "K.O.D."; +exports.icon = ""; +exports.version = "1.0.0"; +exports.site = "https://kod.novel.tl"; +exports.filters = [{ "key": "tags", "label": "Тэги", "values": [{ "label": "Актёрское искусство", "value": 7 }, { "label": "Забота о детях", "value": 127 }, { "label": "Управление бизнесом", "value": 109 }, { "label": "Шоу-бизнес", "value": 642 }], "inputType": filterInputs_1.FilterInputs.Checkbox }]; +exports["protected"] = false; +const baseUrl = exports.site; +const domain = baseUrl.split('//')[1]; +const popularNovels = function (page, { filters }) { + var _a, _b, _c, _d, _e; + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: ((_b = (_a = filters === null || filters === void 0 ? void 0 : filters.tags) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0 > 0) ? { tags: filters === null || filters === void 0 ? void 0 : filters.tags } : {}, + hostname: domain, + limit: 40, + page, + }, + }), + }); + const json = yield result.json(); + const novels = (_e = (_d = (_c = json.data) === null || _c === void 0 ? void 0 : _c.projects) === null || _d === void 0 ? void 0 : _d.content) === null || _e === void 0 ? void 0 : _e.map((novel) => { + var _a; + return ({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: ((_a = novel.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + novel.covers[0].url + : defaultCover_1.defaultCover, + }); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Book', + query: 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}', + variables: { + hostname: domain, + project: novelUrl, + url: novelUrl, + }, + }), + }); + const json = yield result.json(); + const novel = { + url: novelUrl, + name: json.data.project.title, + cover: ((_a = json.data.project.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + json.data.project.covers[0].url + : defaultCover_1.defaultCover, + summary: (0, cheerio_1.load)((_b = json.data.project.annotation) === null || _b === void 0 ? void 0 : _b.text).text(), + author: json.data.project.persons[0].name.firstName + + ' ' + + json.data.project.persons[0].name.lastName, + status: json.data.project.translationStatus === 'active' + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed, + }; + let genres = [] + .concat(json.data.project.tags, json.data.project.genres) + .map((item) => (item === null || item === void 0 ? void 0 : item.nameRu) || (item === null || item === void 0 ? void 0 : item.nameEng)) + .filter(item => item); + if (genres.length > 0) { + novel.genres = genres.join(','); + } + let novelChapters = []; + json.data.project.subprojects.content.forEach((work) => work.volumes.content.forEach((volume) => volume.chapters.forEach((chapter) => (chapter === null || chapter === void 0 ? void 0 : chapter.published) && + novelChapters.push({ + name: volume.shortName + ' ' + chapter.title, + url: novelUrl + '/' + volume.url + '/' + chapter.url, + releaseTime: (0, dayjs_1.default)(chapter.publishDate).format('LLL'), + })))); + novel.chapters = novelChapters; + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'EReaderData', + query: 'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}', + variables: { + chapterSelector: { + fullUrl: chapterUrl, + }, + url: chapterUrl, + }, + }), + }); + const json = yield result.json(); + return (0, cheerio_1.load)(json.data.chapter.text.text).html(); + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + var _a, _b, _c; + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: { + query: searchTerm, + }, + hostname: domain, + limit: 40, + page: 1, + }, + }), + }); + const json = yield result.json(); + const novels = []; + (_c = (_b = (_a = json === null || json === void 0 ? void 0 : json.data) === null || _a === void 0 ? void 0 : _a.projects) === null || _b === void 0 ? void 0 : _b.content) === null || _c === void 0 ? void 0 : _c.forEach((novel) => { + var _a; + return novels.push({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: ((_a = novel.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + novel.covers[0].url + : defaultCover_1.defaultCover, + }); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url, {}); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/russian/nightnovelonline[noveltl].js b/js/plugins/russian/nightnovelonline[noveltl].js new file mode 100644 index 0000000..a259263 --- /dev/null +++ b/js/plugins/russian/nightnovelonline[noveltl].js @@ -0,0 +1,213 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.filters = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const dayjs_1 = __importDefault(require("dayjs")); +const fetch_1 = require("@libs/fetch"); +// import { parseMadaraDate } from "@libs/parseMadaraDate"; +// import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; +// import { showToast } from "@libs/showToast"; +const filterInputs_1 = require("@libs/filterInputs"); +const novelStatus_1 = require("@libs/novelStatus"); +const defaultCover_1 = require("@libs/defaultCover"); +exports.id = "nightnovel"; +exports.name = "Nightnovel"; +exports.icon = ""; +exports.version = "1.0.0"; +exports.site = "https://nightnovel.online"; +exports.filters = [{ "key": "tags", "label": "Тэги", "values": [{ "label": "Альтернативная реальность", "value": 30 }, { "label": "Амнезия", "value": 31 }, { "label": "Анал", "value": 33 }, { "label": "Ангелы", "value": 38 }, { "label": "Аристократия", "value": 51 }, { "label": "Артефакты", "value": 58 }, { "label": "Ассасины", "value": 61 }, { "label": "Банды", "value": 302 }, { "label": "БДСМ", "value": 77 }, { "label": "Беременность", "value": 551 }, { "label": "Боги", "value": 318 }, { "label": "Богини", "value": 316 }, { "label": "Боевая академия", "value": 75 }, { "label": "Божественная сила", "value": 317 }, { "label": "Влияние прошлого", "value": 517 }, { "label": "Враги становятся союзниками", "value": 238 }, { "label": "Вынужденные условия проживания", "value": 287 }, { "label": "Герои", "value": 341 }, { "label": "Героиня красавица", "value": 81 }, { "label": "R-18", "value": 571 }], "inputType": filterInputs_1.FilterInputs.Checkbox }]; +exports["protected"] = false; +const baseUrl = exports.site; +const domain = baseUrl.split('//')[1]; +const popularNovels = function (page, { filters }) { + var _a, _b, _c, _d, _e; + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: ((_b = (_a = filters === null || filters === void 0 ? void 0 : filters.tags) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0 > 0) ? { tags: filters === null || filters === void 0 ? void 0 : filters.tags } : {}, + hostname: domain, + limit: 40, + page, + }, + }), + }); + const json = yield result.json(); + const novels = (_e = (_d = (_c = json.data) === null || _c === void 0 ? void 0 : _c.projects) === null || _d === void 0 ? void 0 : _d.content) === null || _e === void 0 ? void 0 : _e.map((novel) => { + var _a; + return ({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: ((_a = novel.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + novel.covers[0].url + : defaultCover_1.defaultCover, + }); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Book', + query: 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}', + variables: { + hostname: domain, + project: novelUrl, + url: novelUrl, + }, + }), + }); + const json = yield result.json(); + const novel = { + url: novelUrl, + name: json.data.project.title, + cover: ((_a = json.data.project.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + json.data.project.covers[0].url + : defaultCover_1.defaultCover, + summary: (0, cheerio_1.load)((_b = json.data.project.annotation) === null || _b === void 0 ? void 0 : _b.text).text(), + author: json.data.project.persons[0].name.firstName + + ' ' + + json.data.project.persons[0].name.lastName, + status: json.data.project.translationStatus === 'active' + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed, + }; + let genres = [] + .concat(json.data.project.tags, json.data.project.genres) + .map((item) => (item === null || item === void 0 ? void 0 : item.nameRu) || (item === null || item === void 0 ? void 0 : item.nameEng)) + .filter(item => item); + if (genres.length > 0) { + novel.genres = genres.join(','); + } + let novelChapters = []; + json.data.project.subprojects.content.forEach((work) => work.volumes.content.forEach((volume) => volume.chapters.forEach((chapter) => (chapter === null || chapter === void 0 ? void 0 : chapter.published) && + novelChapters.push({ + name: volume.shortName + ' ' + chapter.title, + url: novelUrl + '/' + volume.url + '/' + chapter.url, + releaseTime: (0, dayjs_1.default)(chapter.publishDate).format('LLL'), + })))); + novel.chapters = novelChapters; + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'EReaderData', + query: 'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}', + variables: { + chapterSelector: { + fullUrl: chapterUrl, + }, + url: chapterUrl, + }, + }), + }); + const json = yield result.json(); + return (0, cheerio_1.load)(json.data.chapter.text.text).html(); + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + var _a, _b, _c; + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: { + query: searchTerm, + }, + hostname: domain, + limit: 40, + page: 1, + }, + }), + }); + const json = yield result.json(); + const novels = []; + (_c = (_b = (_a = json === null || json === void 0 ? void 0 : json.data) === null || _a === void 0 ? void 0 : _a.projects) === null || _b === void 0 ? void 0 : _b.content) === null || _c === void 0 ? void 0 : _c.forEach((novel) => { + var _a; + return novels.push({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: ((_a = novel.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + novel.covers[0].url + : defaultCover_1.defaultCover, + }); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url, {}); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/russian/originalnoveltl[noveltl].js b/js/plugins/russian/originalnoveltl[noveltl].js new file mode 100644 index 0000000..3ba3181 --- /dev/null +++ b/js/plugins/russian/originalnoveltl[noveltl].js @@ -0,0 +1,213 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.filters = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const dayjs_1 = __importDefault(require("dayjs")); +const fetch_1 = require("@libs/fetch"); +// import { parseMadaraDate } from "@libs/parseMadaraDate"; +// import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; +// import { showToast } from "@libs/showToast"; +const filterInputs_1 = require("@libs/filterInputs"); +const novelStatus_1 = require("@libs/novelStatus"); +const defaultCover_1 = require("@libs/defaultCover"); +exports.id = "original.novel"; +exports.name = "Авторские работы"; +exports.icon = ""; +exports.version = "1.0.0"; +exports.site = "https://original.novel.tl"; +exports.filters = [{ "key": "tags", "label": "Тэги", "values": [{ "label": "Агрессивные персонажи", "value": 26 }, { "label": "Академия", "value": 5 }, { "label": "Андроиды", "value": 37 }, { "label": "Антимагия", "value": 41 }, { "label": "Аристократия", "value": 51 }, { "label": "Артефакты", "value": 58 }, { "label": "Брошенные дети", "value": 1 }, { "label": "Вероломные герои/плетение интриг", "value": 424 }, { "label": "Взросление персонажа", "value": 24 }, { "label": "Владелец уникального оружия", "value": 736 }, { "label": "Герой-извращенец", "value": 521 }, { "label": "Главный герой не человек", "value": 488 }, { "label": "Государственные интриги", "value": 212 }, { "label": "Душещипательная история", "value": 335 }, { "label": "Жестокие персонажи", "value": 4 }, { "label": "Зверолюди", "value": 39 }, { "label": "Знания из прошлой жизни", "value": 553 }, { "label": "Искусственный интеллект", "value": 59 }, { "label": "Конфликт в семье", "value": 261 }, { "label": "Королевства", "value": 382 }], "inputType": filterInputs_1.FilterInputs.Checkbox }]; +exports["protected"] = false; +const baseUrl = exports.site; +const domain = baseUrl.split('//')[1]; +const popularNovels = function (page, { filters }) { + var _a, _b, _c, _d, _e; + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: ((_b = (_a = filters === null || filters === void 0 ? void 0 : filters.tags) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0 > 0) ? { tags: filters === null || filters === void 0 ? void 0 : filters.tags } : {}, + hostname: domain, + limit: 40, + page, + }, + }), + }); + const json = yield result.json(); + const novels = (_e = (_d = (_c = json.data) === null || _c === void 0 ? void 0 : _c.projects) === null || _d === void 0 ? void 0 : _d.content) === null || _e === void 0 ? void 0 : _e.map((novel) => { + var _a; + return ({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: ((_a = novel.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + novel.covers[0].url + : defaultCover_1.defaultCover, + }); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Book', + query: 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}', + variables: { + hostname: domain, + project: novelUrl, + url: novelUrl, + }, + }), + }); + const json = yield result.json(); + const novel = { + url: novelUrl, + name: json.data.project.title, + cover: ((_a = json.data.project.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + json.data.project.covers[0].url + : defaultCover_1.defaultCover, + summary: (0, cheerio_1.load)((_b = json.data.project.annotation) === null || _b === void 0 ? void 0 : _b.text).text(), + author: json.data.project.persons[0].name.firstName + + ' ' + + json.data.project.persons[0].name.lastName, + status: json.data.project.translationStatus === 'active' + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed, + }; + let genres = [] + .concat(json.data.project.tags, json.data.project.genres) + .map((item) => (item === null || item === void 0 ? void 0 : item.nameRu) || (item === null || item === void 0 ? void 0 : item.nameEng)) + .filter(item => item); + if (genres.length > 0) { + novel.genres = genres.join(','); + } + let novelChapters = []; + json.data.project.subprojects.content.forEach((work) => work.volumes.content.forEach((volume) => volume.chapters.forEach((chapter) => (chapter === null || chapter === void 0 ? void 0 : chapter.published) && + novelChapters.push({ + name: volume.shortName + ' ' + chapter.title, + url: novelUrl + '/' + volume.url + '/' + chapter.url, + releaseTime: (0, dayjs_1.default)(chapter.publishDate).format('LLL'), + })))); + novel.chapters = novelChapters; + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'EReaderData', + query: 'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}', + variables: { + chapterSelector: { + fullUrl: chapterUrl, + }, + url: chapterUrl, + }, + }), + }); + const json = yield result.json(); + return (0, cheerio_1.load)(json.data.chapter.text.text).html(); + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + var _a, _b, _c; + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: { + query: searchTerm, + }, + hostname: domain, + limit: 40, + page: 1, + }, + }), + }); + const json = yield result.json(); + const novels = []; + (_c = (_b = (_a = json === null || json === void 0 ? void 0 : json.data) === null || _a === void 0 ? void 0 : _a.projects) === null || _b === void 0 ? void 0 : _b.content) === null || _c === void 0 ? void 0 : _c.forEach((novel) => { + var _a; + return novels.push({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: ((_a = novel.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + novel.covers[0].url + : defaultCover_1.defaultCover, + }); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url, {}); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/russian/rednoveltl[noveltl].js b/js/plugins/russian/rednoveltl[noveltl].js new file mode 100644 index 0000000..f97268c --- /dev/null +++ b/js/plugins/russian/rednoveltl[noveltl].js @@ -0,0 +1,213 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.filters = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const dayjs_1 = __importDefault(require("dayjs")); +const fetch_1 = require("@libs/fetch"); +// import { parseMadaraDate } from "@libs/parseMadaraDate"; +// import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; +// import { showToast } from "@libs/showToast"; +const filterInputs_1 = require("@libs/filterInputs"); +const novelStatus_1 = require("@libs/novelStatus"); +const defaultCover_1 = require("@libs/defaultCover"); +exports.id = "red.novel"; +exports.name = "Север и Юг"; +exports.icon = ""; +exports.version = "1.0.0"; +exports.site = "https://red.novel.tl"; +exports.filters = [{ "key": "tags", "label": "Тэги", "values": [{ "label": "Героиня красавица", "value": 81 }, { "label": "Герой влюбляется первым", "value": 561 }, { "label": "Главный герой — мужчина", "value": 419 }, { "label": "Дружба", "value": 293 }, { "label": "Жизнь в одиночку", "value": 396 }, { "label": "Сложные семейные отношения", "value": 149 }], "inputType": filterInputs_1.FilterInputs.Checkbox }]; +exports["protected"] = false; +const baseUrl = exports.site; +const domain = baseUrl.split('//')[1]; +const popularNovels = function (page, { filters }) { + var _a, _b, _c, _d, _e; + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: ((_b = (_a = filters === null || filters === void 0 ? void 0 : filters.tags) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0 > 0) ? { tags: filters === null || filters === void 0 ? void 0 : filters.tags } : {}, + hostname: domain, + limit: 40, + page, + }, + }), + }); + const json = yield result.json(); + const novels = (_e = (_d = (_c = json.data) === null || _c === void 0 ? void 0 : _c.projects) === null || _d === void 0 ? void 0 : _d.content) === null || _e === void 0 ? void 0 : _e.map((novel) => { + var _a; + return ({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: ((_a = novel.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + novel.covers[0].url + : defaultCover_1.defaultCover, + }); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Book', + query: 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}', + variables: { + hostname: domain, + project: novelUrl, + url: novelUrl, + }, + }), + }); + const json = yield result.json(); + const novel = { + url: novelUrl, + name: json.data.project.title, + cover: ((_a = json.data.project.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + json.data.project.covers[0].url + : defaultCover_1.defaultCover, + summary: (0, cheerio_1.load)((_b = json.data.project.annotation) === null || _b === void 0 ? void 0 : _b.text).text(), + author: json.data.project.persons[0].name.firstName + + ' ' + + json.data.project.persons[0].name.lastName, + status: json.data.project.translationStatus === 'active' + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed, + }; + let genres = [] + .concat(json.data.project.tags, json.data.project.genres) + .map((item) => (item === null || item === void 0 ? void 0 : item.nameRu) || (item === null || item === void 0 ? void 0 : item.nameEng)) + .filter(item => item); + if (genres.length > 0) { + novel.genres = genres.join(','); + } + let novelChapters = []; + json.data.project.subprojects.content.forEach((work) => work.volumes.content.forEach((volume) => volume.chapters.forEach((chapter) => (chapter === null || chapter === void 0 ? void 0 : chapter.published) && + novelChapters.push({ + name: volume.shortName + ' ' + chapter.title, + url: novelUrl + '/' + volume.url + '/' + chapter.url, + releaseTime: (0, dayjs_1.default)(chapter.publishDate).format('LLL'), + })))); + novel.chapters = novelChapters; + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'EReaderData', + query: 'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}', + variables: { + chapterSelector: { + fullUrl: chapterUrl, + }, + url: chapterUrl, + }, + }), + }); + const json = yield result.json(); + return (0, cheerio_1.load)(json.data.chapter.text.text).html(); + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + var _a, _b, _c; + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: { + query: searchTerm, + }, + hostname: domain, + limit: 40, + page: 1, + }, + }), + }); + const json = yield result.json(); + const novels = []; + (_c = (_b = (_a = json === null || json === void 0 ? void 0 : json.data) === null || _a === void 0 ? void 0 : _a.projects) === null || _b === void 0 ? void 0 : _b.content) === null || _c === void 0 ? void 0 : _c.forEach((novel) => { + var _a; + return novels.push({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: ((_a = novel.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + novel.covers[0].url + : defaultCover_1.defaultCover, + }); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url, {}); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/russian/ruranoberu[noveltl].js b/js/plugins/russian/ruranoberu[noveltl].js new file mode 100644 index 0000000..39ee442 --- /dev/null +++ b/js/plugins/russian/ruranoberu[noveltl].js @@ -0,0 +1,213 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.filters = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const dayjs_1 = __importDefault(require("dayjs")); +const fetch_1 = require("@libs/fetch"); +// import { parseMadaraDate } from "@libs/parseMadaraDate"; +// import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; +// import { showToast } from "@libs/showToast"; +const filterInputs_1 = require("@libs/filterInputs"); +const novelStatus_1 = require("@libs/novelStatus"); +const defaultCover_1 = require("@libs/defaultCover"); +exports.id = "ruranobe"; +exports.name = "РуРанобэ"; +exports.icon = ""; +exports.version = "1.0.0"; +exports.site = "https://ruranobe.ru"; +exports.filters = [{ "key": "tags", "label": "Тэги", "values": [{ "label": "Автоматы", "value": 64 }, { "label": "Агрессивные персонажи", "value": 26 }, { "label": "Ад", "value": 338 }, { "label": "Адаптация манги", "value": 8 }, { "label": "Академия", "value": 5 }, { "label": "Алхимия", "value": 27 }, { "label": "Альтернативная реальность", "value": 30 }, { "label": "Амнезия", "value": 31 }, { "label": "Анабиоз", "value": 170 }, { "label": "Ангелы", "value": 38 }, { "label": "Андроиды", "value": 37 }, { "label": "Антигерой", "value": 43 }, { "label": "Антимагия", "value": 41 }, { "label": "Апатичный главный герой", "value": 46 }, { "label": "Апокалипсис", "value": 47 }, { "label": "Аристократия", "value": 51 }, { "label": "Артефакты", "value": 58 }, { "label": "Асоциальный главный герой", "value": 42 }, { "label": "R-15", "value": 570 }, { "label": "R-18", "value": 571 }], "inputType": filterInputs_1.FilterInputs.Checkbox }]; +exports["protected"] = false; +const baseUrl = exports.site; +const domain = baseUrl.split('//')[1]; +const popularNovels = function (page, { filters }) { + var _a, _b, _c, _d, _e; + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: ((_b = (_a = filters === null || filters === void 0 ? void 0 : filters.tags) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0 > 0) ? { tags: filters === null || filters === void 0 ? void 0 : filters.tags } : {}, + hostname: domain, + limit: 40, + page, + }, + }), + }); + const json = yield result.json(); + const novels = (_e = (_d = (_c = json.data) === null || _c === void 0 ? void 0 : _c.projects) === null || _d === void 0 ? void 0 : _d.content) === null || _e === void 0 ? void 0 : _e.map((novel) => { + var _a; + return ({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: ((_a = novel.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + novel.covers[0].url + : defaultCover_1.defaultCover, + }); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Book', + query: 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}', + variables: { + hostname: domain, + project: novelUrl, + url: novelUrl, + }, + }), + }); + const json = yield result.json(); + const novel = { + url: novelUrl, + name: json.data.project.title, + cover: ((_a = json.data.project.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + json.data.project.covers[0].url + : defaultCover_1.defaultCover, + summary: (0, cheerio_1.load)((_b = json.data.project.annotation) === null || _b === void 0 ? void 0 : _b.text).text(), + author: json.data.project.persons[0].name.firstName + + ' ' + + json.data.project.persons[0].name.lastName, + status: json.data.project.translationStatus === 'active' + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed, + }; + let genres = [] + .concat(json.data.project.tags, json.data.project.genres) + .map((item) => (item === null || item === void 0 ? void 0 : item.nameRu) || (item === null || item === void 0 ? void 0 : item.nameEng)) + .filter(item => item); + if (genres.length > 0) { + novel.genres = genres.join(','); + } + let novelChapters = []; + json.data.project.subprojects.content.forEach((work) => work.volumes.content.forEach((volume) => volume.chapters.forEach((chapter) => (chapter === null || chapter === void 0 ? void 0 : chapter.published) && + novelChapters.push({ + name: volume.shortName + ' ' + chapter.title, + url: novelUrl + '/' + volume.url + '/' + chapter.url, + releaseTime: (0, dayjs_1.default)(chapter.publishDate).format('LLL'), + })))); + novel.chapters = novelChapters; + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'EReaderData', + query: 'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}', + variables: { + chapterSelector: { + fullUrl: chapterUrl, + }, + url: chapterUrl, + }, + }), + }); + const json = yield result.json(); + return (0, cheerio_1.load)(json.data.chapter.text.text).html(); + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + var _a, _b, _c; + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: { + query: searchTerm, + }, + hostname: domain, + limit: 40, + page: 1, + }, + }), + }); + const json = yield result.json(); + const novels = []; + (_c = (_b = (_a = json === null || json === void 0 ? void 0 : json.data) === null || _a === void 0 ? void 0 : _a.projects) === null || _b === void 0 ? void 0 : _b.content) === null || _c === void 0 ? void 0 : _c.forEach((novel) => { + var _a; + return novels.push({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: ((_a = novel.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + novel.covers[0].url + : defaultCover_1.defaultCover, + }); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url, {}); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/russian/sferdrakonnoveltl[noveltl].js b/js/plugins/russian/sferdrakonnoveltl[noveltl].js new file mode 100644 index 0000000..f64f4c4 --- /dev/null +++ b/js/plugins/russian/sferdrakonnoveltl[noveltl].js @@ -0,0 +1,213 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.filters = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const dayjs_1 = __importDefault(require("dayjs")); +const fetch_1 = require("@libs/fetch"); +// import { parseMadaraDate } from "@libs/parseMadaraDate"; +// import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; +// import { showToast } from "@libs/showToast"; +const filterInputs_1 = require("@libs/filterInputs"); +const novelStatus_1 = require("@libs/novelStatus"); +const defaultCover_1 = require("@libs/defaultCover"); +exports.id = "sferdrakon.novel"; +exports.name = "Уголок дракона"; +exports.icon = ""; +exports.version = "1.0.0"; +exports.site = "https://sferdrakon.novel.tl"; +exports.filters = [{ "key": "tags", "label": "Тэги", "values": [{ "label": "Главный герой — мужчина", "value": 419 }, { "label": "Демоны", "value": 193 }, { "label": "Драконы", "value": 218 }, { "label": "Есть аниме-адаптация", "value": 10 }, { "label": "Есть видеоигра", "value": 13 }, { "label": "Есть манга-адаптация", "value": 14 }, { "label": "Звероподобные", "value": 79 }, { "label": "Изнасилование", "value": 574 }, { "label": "Королевская особа", "value": 596 }, { "label": "Магия", "value": 412 }, { "label": "Магия призыва", "value": 694 }, { "label": "Наездники на драконах", "value": 216 }, { "label": "Рыцари", "value": 383 }, { "label": "Цундере", "value": 728 }, { "label": "Эльфы", "value": 235 }], "inputType": filterInputs_1.FilterInputs.Checkbox }]; +exports["protected"] = false; +const baseUrl = exports.site; +const domain = baseUrl.split('//')[1]; +const popularNovels = function (page, { filters }) { + var _a, _b, _c, _d, _e; + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: ((_b = (_a = filters === null || filters === void 0 ? void 0 : filters.tags) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0 > 0) ? { tags: filters === null || filters === void 0 ? void 0 : filters.tags } : {}, + hostname: domain, + limit: 40, + page, + }, + }), + }); + const json = yield result.json(); + const novels = (_e = (_d = (_c = json.data) === null || _c === void 0 ? void 0 : _c.projects) === null || _d === void 0 ? void 0 : _d.content) === null || _e === void 0 ? void 0 : _e.map((novel) => { + var _a; + return ({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: ((_a = novel.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + novel.covers[0].url + : defaultCover_1.defaultCover, + }); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Book', + query: 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}', + variables: { + hostname: domain, + project: novelUrl, + url: novelUrl, + }, + }), + }); + const json = yield result.json(); + const novel = { + url: novelUrl, + name: json.data.project.title, + cover: ((_a = json.data.project.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + json.data.project.covers[0].url + : defaultCover_1.defaultCover, + summary: (0, cheerio_1.load)((_b = json.data.project.annotation) === null || _b === void 0 ? void 0 : _b.text).text(), + author: json.data.project.persons[0].name.firstName + + ' ' + + json.data.project.persons[0].name.lastName, + status: json.data.project.translationStatus === 'active' + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed, + }; + let genres = [] + .concat(json.data.project.tags, json.data.project.genres) + .map((item) => (item === null || item === void 0 ? void 0 : item.nameRu) || (item === null || item === void 0 ? void 0 : item.nameEng)) + .filter(item => item); + if (genres.length > 0) { + novel.genres = genres.join(','); + } + let novelChapters = []; + json.data.project.subprojects.content.forEach((work) => work.volumes.content.forEach((volume) => volume.chapters.forEach((chapter) => (chapter === null || chapter === void 0 ? void 0 : chapter.published) && + novelChapters.push({ + name: volume.shortName + ' ' + chapter.title, + url: novelUrl + '/' + volume.url + '/' + chapter.url, + releaseTime: (0, dayjs_1.default)(chapter.publishDate).format('LLL'), + })))); + novel.chapters = novelChapters; + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'EReaderData', + query: 'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}', + variables: { + chapterSelector: { + fullUrl: chapterUrl, + }, + url: chapterUrl, + }, + }), + }); + const json = yield result.json(); + return (0, cheerio_1.load)(json.data.chapter.text.text).html(); + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + var _a, _b, _c; + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: { + query: searchTerm, + }, + hostname: domain, + limit: 40, + page: 1, + }, + }), + }); + const json = yield result.json(); + const novels = []; + (_c = (_b = (_a = json === null || json === void 0 ? void 0 : json.data) === null || _a === void 0 ? void 0 : _a.projects) === null || _b === void 0 ? void 0 : _b.content) === null || _c === void 0 ? void 0 : _c.forEach((novel) => { + var _a; + return novels.push({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: ((_a = novel.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + novel.covers[0].url + : defaultCover_1.defaultCover, + }); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url, {}); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/russian/sinoveltl[noveltl].js b/js/plugins/russian/sinoveltl[noveltl].js new file mode 100644 index 0000000..b5b09b4 --- /dev/null +++ b/js/plugins/russian/sinoveltl[noveltl].js @@ -0,0 +1,213 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.filters = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const dayjs_1 = __importDefault(require("dayjs")); +const fetch_1 = require("@libs/fetch"); +// import { parseMadaraDate } from "@libs/parseMadaraDate"; +// import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; +// import { showToast } from "@libs/showToast"; +const filterInputs_1 = require("@libs/filterInputs"); +const novelStatus_1 = require("@libs/novelStatus"); +const defaultCover_1 = require("@libs/defaultCover"); +exports.id = "si.novel"; +exports.name = "Сказочные истории"; +exports.icon = ""; +exports.version = "1.0.0"; +exports.site = "https://si.novel.tl"; +exports.filters = [{ "key": "tags", "label": "Тэги", "values": [{ "label": "Аристократия", "value": 51 }, { "label": "Война", "value": 750 }, { "label": "Главная героиня — женщина", "value": 277 }, { "label": "Драконы", "value": 218 }, { "label": "Древний Китай", "value": 34 }, { "label": "Жестокие персонажи", "value": 4 }, { "label": "Злобные благородные девы", "value": 743 }, { "label": "Комедийный подтекст", "value": 147 }, { "label": "Культивация", "value": 171 }, { "label": "Магия", "value": 412 }, { "label": "Отомэ-игры", "value": 504 }, { "label": "Перерождение в другом мире", "value": 580 }, { "label": "Перерождение в другом мире", "value": 720 }, { "label": "Повелитель демонов", "value": 191 }, { "label": "Попаданец в другой мир", "value": 723 }, { "label": "Смерть", "value": 185 }, { "label": "Средневековье", "value": 436 }, { "label": "Фэнтези", "value": 267 }, { "label": "Харизматичный протагонист", "value": 120 }, { "label": "Хитрый главный герой", "value": 173 }], "inputType": filterInputs_1.FilterInputs.Checkbox }]; +exports["protected"] = false; +const baseUrl = exports.site; +const domain = baseUrl.split('//')[1]; +const popularNovels = function (page, { filters }) { + var _a, _b, _c, _d, _e; + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: ((_b = (_a = filters === null || filters === void 0 ? void 0 : filters.tags) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0 > 0) ? { tags: filters === null || filters === void 0 ? void 0 : filters.tags } : {}, + hostname: domain, + limit: 40, + page, + }, + }), + }); + const json = yield result.json(); + const novels = (_e = (_d = (_c = json.data) === null || _c === void 0 ? void 0 : _c.projects) === null || _d === void 0 ? void 0 : _d.content) === null || _e === void 0 ? void 0 : _e.map((novel) => { + var _a; + return ({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: ((_a = novel.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + novel.covers[0].url + : defaultCover_1.defaultCover, + }); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Book', + query: 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}', + variables: { + hostname: domain, + project: novelUrl, + url: novelUrl, + }, + }), + }); + const json = yield result.json(); + const novel = { + url: novelUrl, + name: json.data.project.title, + cover: ((_a = json.data.project.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + json.data.project.covers[0].url + : defaultCover_1.defaultCover, + summary: (0, cheerio_1.load)((_b = json.data.project.annotation) === null || _b === void 0 ? void 0 : _b.text).text(), + author: json.data.project.persons[0].name.firstName + + ' ' + + json.data.project.persons[0].name.lastName, + status: json.data.project.translationStatus === 'active' + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed, + }; + let genres = [] + .concat(json.data.project.tags, json.data.project.genres) + .map((item) => (item === null || item === void 0 ? void 0 : item.nameRu) || (item === null || item === void 0 ? void 0 : item.nameEng)) + .filter(item => item); + if (genres.length > 0) { + novel.genres = genres.join(','); + } + let novelChapters = []; + json.data.project.subprojects.content.forEach((work) => work.volumes.content.forEach((volume) => volume.chapters.forEach((chapter) => (chapter === null || chapter === void 0 ? void 0 : chapter.published) && + novelChapters.push({ + name: volume.shortName + ' ' + chapter.title, + url: novelUrl + '/' + volume.url + '/' + chapter.url, + releaseTime: (0, dayjs_1.default)(chapter.publishDate).format('LLL'), + })))); + novel.chapters = novelChapters; + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'EReaderData', + query: 'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}', + variables: { + chapterSelector: { + fullUrl: chapterUrl, + }, + url: chapterUrl, + }, + }), + }); + const json = yield result.json(); + return (0, cheerio_1.load)(json.data.chapter.text.text).html(); + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + var _a, _b, _c; + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: { + query: searchTerm, + }, + hostname: domain, + limit: 40, + page: 1, + }, + }), + }); + const json = yield result.json(); + const novels = []; + (_c = (_b = (_a = json === null || json === void 0 ? void 0 : json.data) === null || _a === void 0 ? void 0 : _a.projects) === null || _b === void 0 ? void 0 : _b.content) === null || _c === void 0 ? void 0 : _c.forEach((novel) => { + var _a; + return novels.push({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: ((_a = novel.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + novel.covers[0].url + : defaultCover_1.defaultCover, + }); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url, {}); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/russian/snailulitkanoveltl[noveltl].js b/js/plugins/russian/snailulitkanoveltl[noveltl].js new file mode 100644 index 0000000..c57b727 --- /dev/null +++ b/js/plugins/russian/snailulitkanoveltl[noveltl].js @@ -0,0 +1,213 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.filters = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const dayjs_1 = __importDefault(require("dayjs")); +const fetch_1 = require("@libs/fetch"); +// import { parseMadaraDate } from "@libs/parseMadaraDate"; +// import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; +// import { showToast } from "@libs/showToast"; +const filterInputs_1 = require("@libs/filterInputs"); +const novelStatus_1 = require("@libs/novelStatus"); +const defaultCover_1 = require("@libs/defaultCover"); +exports.id = "snailulitka.novel"; +exports.name = "Snailulitka"; +exports.icon = ""; +exports.version = "1.0.0"; +exports.site = "https://snailulitka.novel.tl"; +exports.filters = [{ "key": "tags", "label": "Тэги", "values": [{ "label": "Алхимия", "value": 27 }, { "label": "Ассасины", "value": 61 }, { "label": "Бедный главный герой", "value": 541 }, { "label": "Богачи", "value": 753 }, { "label": "Виртуальная реальность", "value": 744 }, { "label": "Война", "value": 750 }, { "label": "Геймеры", "value": 301 }, { "label": "Героиня красавица", "value": 81 }, { "label": "Герой красавец", "value": 329 }, { "label": "Гильдии", "value": 324 }, { "label": "Главный герой - гений", "value": 308 }, { "label": "Главный герой — знаменитость", "value": 263 }, { "label": "Главный герой — мужчина", "value": 419 }, { "label": "Дворфы", "value": 224 }, { "label": "Драконы", "value": 218 }, { "label": "Есть маньхуа-адаптация", "value": 15 }, { "label": "Игровая рейтинговая система", "value": 300 }, { "label": "Книги навыков", "value": 654 }, { "label": "Лекари", "value": 334 }, { "label": "Месть", "value": 588 }], "inputType": filterInputs_1.FilterInputs.Checkbox }]; +exports["protected"] = false; +const baseUrl = exports.site; +const domain = baseUrl.split('//')[1]; +const popularNovels = function (page, { filters }) { + var _a, _b, _c, _d, _e; + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: ((_b = (_a = filters === null || filters === void 0 ? void 0 : filters.tags) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0 > 0) ? { tags: filters === null || filters === void 0 ? void 0 : filters.tags } : {}, + hostname: domain, + limit: 40, + page, + }, + }), + }); + const json = yield result.json(); + const novels = (_e = (_d = (_c = json.data) === null || _c === void 0 ? void 0 : _c.projects) === null || _d === void 0 ? void 0 : _d.content) === null || _e === void 0 ? void 0 : _e.map((novel) => { + var _a; + return ({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: ((_a = novel.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + novel.covers[0].url + : defaultCover_1.defaultCover, + }); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Book', + query: 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}', + variables: { + hostname: domain, + project: novelUrl, + url: novelUrl, + }, + }), + }); + const json = yield result.json(); + const novel = { + url: novelUrl, + name: json.data.project.title, + cover: ((_a = json.data.project.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + json.data.project.covers[0].url + : defaultCover_1.defaultCover, + summary: (0, cheerio_1.load)((_b = json.data.project.annotation) === null || _b === void 0 ? void 0 : _b.text).text(), + author: json.data.project.persons[0].name.firstName + + ' ' + + json.data.project.persons[0].name.lastName, + status: json.data.project.translationStatus === 'active' + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed, + }; + let genres = [] + .concat(json.data.project.tags, json.data.project.genres) + .map((item) => (item === null || item === void 0 ? void 0 : item.nameRu) || (item === null || item === void 0 ? void 0 : item.nameEng)) + .filter(item => item); + if (genres.length > 0) { + novel.genres = genres.join(','); + } + let novelChapters = []; + json.data.project.subprojects.content.forEach((work) => work.volumes.content.forEach((volume) => volume.chapters.forEach((chapter) => (chapter === null || chapter === void 0 ? void 0 : chapter.published) && + novelChapters.push({ + name: volume.shortName + ' ' + chapter.title, + url: novelUrl + '/' + volume.url + '/' + chapter.url, + releaseTime: (0, dayjs_1.default)(chapter.publishDate).format('LLL'), + })))); + novel.chapters = novelChapters; + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'EReaderData', + query: 'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}', + variables: { + chapterSelector: { + fullUrl: chapterUrl, + }, + url: chapterUrl, + }, + }), + }); + const json = yield result.json(); + return (0, cheerio_1.load)(json.data.chapter.text.text).html(); + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + var _a, _b, _c; + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: { + query: searchTerm, + }, + hostname: domain, + limit: 40, + page: 1, + }, + }), + }); + const json = yield result.json(); + const novels = []; + (_c = (_b = (_a = json === null || json === void 0 ? void 0 : json.data) === null || _a === void 0 ? void 0 : _a.projects) === null || _b === void 0 ? void 0 : _b.content) === null || _c === void 0 ? void 0 : _c.forEach((novel) => { + var _a; + return novels.push({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: ((_a = novel.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + novel.covers[0].url + : defaultCover_1.defaultCover, + }); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url, {}); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/russian/tsundokunoveltl[noveltl].js b/js/plugins/russian/tsundokunoveltl[noveltl].js new file mode 100644 index 0000000..c12b867 --- /dev/null +++ b/js/plugins/russian/tsundokunoveltl[noveltl].js @@ -0,0 +1,213 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.filters = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const dayjs_1 = __importDefault(require("dayjs")); +const fetch_1 = require("@libs/fetch"); +// import { parseMadaraDate } from "@libs/parseMadaraDate"; +// import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; +// import { showToast } from "@libs/showToast"; +const filterInputs_1 = require("@libs/filterInputs"); +const novelStatus_1 = require("@libs/novelStatus"); +const defaultCover_1 = require("@libs/defaultCover"); +exports.id = "tsundoku.novel"; +exports.name = "TsundokuTrans"; +exports.icon = ""; +exports.version = "1.0.0"; +exports.site = "https://tsundoku.novel.tl"; +exports.filters = [{ "key": "tags", "label": "Тэги", "values": [{ "label": "Боги", "value": 318 }, { "label": "Боевая академия", "value": 75 }, { "label": "Вампиры", "value": 742 }, { "label": "Влияние прошлого", "value": 517 }, { "label": "Война", "value": 750 }, { "label": "Главная героиня — женщина", "value": 277 }, { "label": "Главный герой — мужчина", "value": 419 }, { "label": "Демоны", "value": 193 }, { "label": "Драконы", "value": 218 }, { "label": "Королевская особа", "value": 596 }, { "label": "Коррупция", "value": 157 }, { "label": "Магия", "value": 412 }, { "label": "Мастер подземелий", "value": 222 }, { "label": "Мечи и магия", "value": 697 }, { "label": "Мечники", "value": 698 }, { "label": "Монстры", "value": 454 }, { "label": "Недооцененный главный герой", "value": 734 }, { "label": "От слабого к сильному", "value": 752 }, { "label": "R-15", "value": 570 }, { "label": "R-18", "value": 571 }], "inputType": filterInputs_1.FilterInputs.Checkbox }]; +exports["protected"] = false; +const baseUrl = exports.site; +const domain = baseUrl.split('//')[1]; +const popularNovels = function (page, { filters }) { + var _a, _b, _c, _d, _e; + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: ((_b = (_a = filters === null || filters === void 0 ? void 0 : filters.tags) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0 > 0) ? { tags: filters === null || filters === void 0 ? void 0 : filters.tags } : {}, + hostname: domain, + limit: 40, + page, + }, + }), + }); + const json = yield result.json(); + const novels = (_e = (_d = (_c = json.data) === null || _c === void 0 ? void 0 : _c.projects) === null || _d === void 0 ? void 0 : _d.content) === null || _e === void 0 ? void 0 : _e.map((novel) => { + var _a; + return ({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: ((_a = novel.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + novel.covers[0].url + : defaultCover_1.defaultCover, + }); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Book', + query: 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}', + variables: { + hostname: domain, + project: novelUrl, + url: novelUrl, + }, + }), + }); + const json = yield result.json(); + const novel = { + url: novelUrl, + name: json.data.project.title, + cover: ((_a = json.data.project.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + json.data.project.covers[0].url + : defaultCover_1.defaultCover, + summary: (0, cheerio_1.load)((_b = json.data.project.annotation) === null || _b === void 0 ? void 0 : _b.text).text(), + author: json.data.project.persons[0].name.firstName + + ' ' + + json.data.project.persons[0].name.lastName, + status: json.data.project.translationStatus === 'active' + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed, + }; + let genres = [] + .concat(json.data.project.tags, json.data.project.genres) + .map((item) => (item === null || item === void 0 ? void 0 : item.nameRu) || (item === null || item === void 0 ? void 0 : item.nameEng)) + .filter(item => item); + if (genres.length > 0) { + novel.genres = genres.join(','); + } + let novelChapters = []; + json.data.project.subprojects.content.forEach((work) => work.volumes.content.forEach((volume) => volume.chapters.forEach((chapter) => (chapter === null || chapter === void 0 ? void 0 : chapter.published) && + novelChapters.push({ + name: volume.shortName + ' ' + chapter.title, + url: novelUrl + '/' + volume.url + '/' + chapter.url, + releaseTime: (0, dayjs_1.default)(chapter.publishDate).format('LLL'), + })))); + novel.chapters = novelChapters; + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'EReaderData', + query: 'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}', + variables: { + chapterSelector: { + fullUrl: chapterUrl, + }, + url: chapterUrl, + }, + }), + }); + const json = yield result.json(); + return (0, cheerio_1.load)(json.data.chapter.text.text).html(); + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + var _a, _b, _c; + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: { + query: searchTerm, + }, + hostname: domain, + limit: 40, + page: 1, + }, + }), + }); + const json = yield result.json(); + const novels = []; + (_c = (_b = (_a = json === null || json === void 0 ? void 0 : json.data) === null || _a === void 0 ? void 0 : _a.projects) === null || _b === void 0 ? void 0 : _b.content) === null || _c === void 0 ? void 0 : _c.forEach((novel) => { + var _a; + return novels.push({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: ((_a = novel.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + novel.covers[0].url + : defaultCover_1.defaultCover, + }); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url, {}); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/russian/ukrnoveltl[noveltl].js b/js/plugins/russian/ukrnoveltl[noveltl].js new file mode 100644 index 0000000..b62a3e6 --- /dev/null +++ b/js/plugins/russian/ukrnoveltl[noveltl].js @@ -0,0 +1,213 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.filters = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const dayjs_1 = __importDefault(require("dayjs")); +const fetch_1 = require("@libs/fetch"); +// import { parseMadaraDate } from "@libs/parseMadaraDate"; +// import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; +// import { showToast } from "@libs/showToast"; +const filterInputs_1 = require("@libs/filterInputs"); +const novelStatus_1 = require("@libs/novelStatus"); +const defaultCover_1 = require("@libs/defaultCover"); +exports.id = "ukr.novel"; +exports.name = "Переклади українською мовою"; +exports.icon = ""; +exports.version = "1.0.0"; +exports.site = "https://ukr.novel.tl"; +exports.filters = [{ "key": "tags", "label": "Тэги", "values": [{ "label": "Альтернативная реальность", "value": 30 }, { "label": "Апатичный главный герой", "value": 46 }, { "label": "Асоциальный главный герой", "value": 42 }, { "label": "Владелец уникального оружия", "value": 736 }, { "label": "Гарем рабов", "value": 656 }, { "label": "Герои", "value": 341 }, { "label": "Героиня красавица", "value": 81 }, { "label": "Герой использует щит", "value": 636 }, { "label": "Герой-трудяга", "value": 330 }, { "label": "Главный герой — мужчина", "value": 419 }, { "label": "Глухой к любви герой", "value": 194 }, { "label": "Демоны", "value": 193 }, { "label": "Друзья детства", "value": 128 }, { "label": "Есть аниме-адаптация", "value": 10 }, { "label": "Есть манга-адаптация", "value": 14 }, { "label": "Есть CD дорама-адаптация", "value": 12 }, { "label": "Зверолюди", "value": 39 }, { "label": "Магия", "value": 412 }, { "label": "Магия призыва", "value": 694 }, { "label": "От слабого к сильному", "value": 752 }], "inputType": filterInputs_1.FilterInputs.Checkbox }]; +exports["protected"] = false; +const baseUrl = exports.site; +const domain = baseUrl.split('//')[1]; +const popularNovels = function (page, { filters }) { + var _a, _b, _c, _d, _e; + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: ((_b = (_a = filters === null || filters === void 0 ? void 0 : filters.tags) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0 > 0) ? { tags: filters === null || filters === void 0 ? void 0 : filters.tags } : {}, + hostname: domain, + limit: 40, + page, + }, + }), + }); + const json = yield result.json(); + const novels = (_e = (_d = (_c = json.data) === null || _c === void 0 ? void 0 : _c.projects) === null || _d === void 0 ? void 0 : _d.content) === null || _e === void 0 ? void 0 : _e.map((novel) => { + var _a; + return ({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: ((_a = novel.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + novel.covers[0].url + : defaultCover_1.defaultCover, + }); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Book', + query: 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}', + variables: { + hostname: domain, + project: novelUrl, + url: novelUrl, + }, + }), + }); + const json = yield result.json(); + const novel = { + url: novelUrl, + name: json.data.project.title, + cover: ((_a = json.data.project.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + json.data.project.covers[0].url + : defaultCover_1.defaultCover, + summary: (0, cheerio_1.load)((_b = json.data.project.annotation) === null || _b === void 0 ? void 0 : _b.text).text(), + author: json.data.project.persons[0].name.firstName + + ' ' + + json.data.project.persons[0].name.lastName, + status: json.data.project.translationStatus === 'active' + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed, + }; + let genres = [] + .concat(json.data.project.tags, json.data.project.genres) + .map((item) => (item === null || item === void 0 ? void 0 : item.nameRu) || (item === null || item === void 0 ? void 0 : item.nameEng)) + .filter(item => item); + if (genres.length > 0) { + novel.genres = genres.join(','); + } + let novelChapters = []; + json.data.project.subprojects.content.forEach((work) => work.volumes.content.forEach((volume) => volume.chapters.forEach((chapter) => (chapter === null || chapter === void 0 ? void 0 : chapter.published) && + novelChapters.push({ + name: volume.shortName + ' ' + chapter.title, + url: novelUrl + '/' + volume.url + '/' + chapter.url, + releaseTime: (0, dayjs_1.default)(chapter.publishDate).format('LLL'), + })))); + novel.chapters = novelChapters; + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'EReaderData', + query: 'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}', + variables: { + chapterSelector: { + fullUrl: chapterUrl, + }, + url: chapterUrl, + }, + }), + }); + const json = yield result.json(); + return (0, cheerio_1.load)(json.data.chapter.text.text).html(); + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + var _a, _b, _c; + const result = yield (0, fetch_1.fetchApi)('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: { + query: searchTerm, + }, + hostname: domain, + limit: 40, + page: 1, + }, + }), + }); + const json = yield result.json(); + const novels = []; + (_c = (_b = (_a = json === null || json === void 0 ? void 0 : json.data) === null || _a === void 0 ? void 0 : _a.projects) === null || _b === void 0 ? void 0 : _b.content) === null || _c === void 0 ? void 0 : _c.forEach((novel) => { + var _a; + return novels.push({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: ((_a = novel.covers[0]) === null || _a === void 0 ? void 0 : _a.url) + ? baseUrl + novel.covers[0].url + : defaultCover_1.defaultCover, + }); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url, {}); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/spanish/hasutl.js b/js/plugins/spanish/hasutl.js new file mode 100644 index 0000000..8a31f92 --- /dev/null +++ b/js/plugins/spanish/hasutl.js @@ -0,0 +1,121 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.icon = exports.version = exports.site = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +exports.id = "HasuTL"; +exports.name = "Hasu Translations"; +exports.site = "https://hasutl.wordpress.com/"; +exports.version = "1.0.0"; +exports.icon = "src/es/hasutl/icon.png"; +const baseUrl = exports.site; +const popularNovels = function (page) { + return __awaiter(this, void 0, void 0, function* () { + let url = baseUrl + "light-novels-activas/"; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio("div.wp-block-columns").each(function () { + const novelName = loadedCheerio(this).find(".wp-block-button").text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + let novelUrl = loadedCheerio(this) + .find(".wp-block-button > a") + .attr("href"); + if (!novelUrl) + return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = novelUrl; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let novel = { + url, + }; + novel.url = novelUrl; + novel.name = loadedCheerio(".post-header").text(); + novel.cover = loadedCheerio(".featured-media > img").attr("src"); + let novelSummary = loadedCheerio(".post-content").find("p").html(); + novel.summary = novelSummary; + let novelChapters = []; + loadedCheerio(".wp-block-media-text__content") + .find("a") + .each(function () { + const chapterName = loadedCheerio(this).text().trim(); + const releaseDate = null; + let chapterUrl = loadedCheerio(this).attr("href"); + if (!chapterUrl) + return; + const chapter = { + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }; + novelChapters.push(chapter); + }); + novel.chapters = novelChapters; + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = chapterUrl; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let chapterText = loadedCheerio(".post-content").html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}?s=${searchTerm}&post_type=wp-manga`; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".post-container ").each(function () { + const novelName = loadedCheerio(this).find(".post-header").text(); + if (!novelName.includes("Cap") && + !novelName.includes("Vol") && + !novelName.includes("Light Novels")) { + const novelCover = loadedCheerio(this).find("img").attr("src"); + let novelUrl = loadedCheerio(this).find("a").attr("href"); + if (!novelUrl) + return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + } + }); + return novels; + }); +}; +exports.searchNovels = searchNovels; +exports.fetchImage = fetch_1.fetchFile; diff --git a/js/plugins/spanish/novelasligera.js b/js/plugins/spanish/novelasligera.js new file mode 100644 index 0000000..40ab405 --- /dev/null +++ b/js/plugins/spanish/novelasligera.js @@ -0,0 +1,157 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.icon = exports.version = exports.site = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +exports.id = "ligera.com"; +exports.name = "Novelas Ligera"; +exports.site = "https://novelasligera.com/"; +exports.version = "1.0.0"; +exports.icon = "src/es/novelasligera/icon.png"; +const baseUrl = exports.site; +const popularNovels = function (page) { + return __awaiter(this, void 0, void 0, function* () { + let url = baseUrl; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".elementor-column").each(function () { + const novelName = loadedCheerio(this) + .find(".widget-image-caption.wp-caption-text") + .text(); + if (novelName) { + const novelCover = loadedCheerio(this) + .find("a > img") + .attr("data-lazy-src"); + let novelUrl = loadedCheerio(this).find("a").attr("href"); + if (!novelUrl) + return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + } + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = baseUrl + "novela/" + novelUrl; + // console.log(url); + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let novel = { url }; + novel.name = loadedCheerio("h1").text(); + novel.cover = loadedCheerio(".elementor-widget-container") + .find("img") + .attr("src"); + loadedCheerio(".elementor-row") + .find("p") + .each(function () { + if (loadedCheerio(this).text().includes("Autor:")) { + novel.author = loadedCheerio(this) + .text() + .replace("Autor:", "") + .trim(); + } + if (loadedCheerio(this).text().includes("Estado:")) { + novel.status = loadedCheerio(this) + .text() + .replace("Estado: ", "") + .trim(); + } + if (loadedCheerio(this).text().includes("Género:")) { + loadedCheerio(this).find("span").remove(); + novel.genres = loadedCheerio(this) + .text() + .replace(/,\s/g, ","); + } + }); + novel.summary = loadedCheerio(".elementor-text-editor.elementor-clearfix").text(); + let novelChapters = []; + loadedCheerio(".elementor-accordion-item").remove(); + loadedCheerio(".elementor-tab-content") + .find("li") + .each(function () { + const chapterName = loadedCheerio(this).text(); + const releaseDate = null; + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + if (!chapterUrl) + return; + const chapter = { + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }; + novelChapters.push(chapter); + }); + novel.chapters = novelChapters; + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = chapterUrl; + // console.log(url); + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio(".osny-nightmode.osny-nightmode--left").remove(); + loadedCheerio(".code-block.code-block-1").remove(); + loadedCheerio(".adsb30").remove(); + loadedCheerio(".saboxplugin-wrap").remove(); + loadedCheerio(".wp-post-navigation").remove(); + let chapterText = loadedCheerio(".entry-content").html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + // console.log(url); + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".inside-article").each(function () { + const novelCover = loadedCheerio(this).find("img").attr("src"); + let novelUrl = loadedCheerio(this).find("a").attr("href"); + let novelName; + if (novelUrl) { + novelName = novelUrl.replace(/-/g, " ").replace(/^./, function (x) { + return x.toUpperCase(); + }); + } + novelUrl += "/"; + if (!novelName || !novelUrl) + return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + novels = [Object.assign({}, novels[1])]; + return novels; + }); +}; +exports.searchNovels = searchNovels; +exports.fetchImage = fetch_1.fetchFile; diff --git a/js/plugins/spanish/tunovelaligera.js b/js/plugins/spanish/tunovelaligera.js new file mode 100644 index 0000000..d279fe8 --- /dev/null +++ b/js/plugins/spanish/tunovelaligera.js @@ -0,0 +1,146 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.icon = exports.version = exports.site = exports.name = exports.id = void 0; +const fetch_1 = require("@libs/fetch"); +const cheerio_1 = require("cheerio"); +const novelStatus_1 = require("@libs/novelStatus"); +const defaultCover_1 = require("@libs/defaultCover"); +exports.id = "TNL.com"; +exports.name = "TuNovelaLigera"; +exports.site = "https://tunovelaligera.com/"; +exports.version = "1.0.0"; +exports.icon = "src/es/tunovelaligera/icon.png"; +const baseUrl = exports.site; +const popularNovels = function (page) { + return __awaiter(this, void 0, void 0, function* () { + let url = baseUrl + "novelas/page/" + page + "/?m_orderby=rating"; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".page-item-detail").each(function () { + const novelName = loadedCheerio(this).find(".h5 > a").text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + let novelUrl = loadedCheerio(this).find(".h5 > a").attr("href"); + if (!novelUrl) + return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = novelUrl; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let novel = { + url, + }; + loadedCheerio(".manga-title-badges").remove(); + novel.name = loadedCheerio(".post-title > h1").text().trim(); + let novelCover = loadedCheerio(".summary_image > a > img"); + novel.cover = + novelCover.attr("data-src") || + novelCover.attr("src") || + defaultCover_1.defaultCover; + loadedCheerio(".post-content_item").each(function () { + const detailName = loadedCheerio(this) + .find(".summary-heading > h5") + .text() + .trim(); + const detail = loadedCheerio(this) + .find(".summary-content") + .text() + .trim(); + switch (detailName) { + case "Generos": + novel.genres = detail.replace(/, /g, ","); + break; + case "Autores": + novel.author = detail; + break; + case "Estado": + novel.status = + detail.includes("OnGoing") || + detail.includes("Updating") + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + novel.summary = loadedCheerio("div.summary__content > p").text().trim(); + let novelChapters = []; + loadedCheerio(".lcp_catlist li").each(function () { + const chapterName = loadedCheerio(this) + .find("a") + .text() + .replace(/[\t\n]/g, "") + .trim(); + const releaseDate = loadedCheerio(this).find("span").text().trim(); + let chapterUrl = loadedCheerio(this).find("a").attr("href"); + if (!chapterUrl) + return; + novelChapters.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + novel.chapters = novelChapters.reverse(); + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = chapterUrl; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let chapterText = loadedCheerio("#hola_siguiente").next().text(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}?s=${searchTerm}&post_type=wp-manga`; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".c-tabs-item__content").each(function () { + const novelName = loadedCheerio(this).find(".h4 > a").text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + let novelUrl = loadedCheerio(this).find(".h4 > a").attr("href"); + if (!novelUrl) + return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.searchNovels = searchNovels; +exports.fetchImage = fetch_1.fetchFile; diff --git a/js/plugins/spanish/yukitls.js b/js/plugins/spanish/yukitls.js new file mode 100644 index 0000000..6b7547e --- /dev/null +++ b/js/plugins/spanish/yukitls.js @@ -0,0 +1,160 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.icon = exports.version = exports.site = exports.name = exports.id = void 0; +const fetch_1 = require("@libs/fetch"); +const cheerio_1 = require("cheerio"); +exports.id = "yuukitls.com"; +exports.name = "Yuuki Tls"; +exports.site = "https://yuukitls.com/"; +exports.version = "1.0.0"; +exports.icon = "src/es/yuukitls/icon.png"; +const baseUrl = exports.site; +const popularNovels = function (page) { + return __awaiter(this, void 0, void 0, function* () { + let url = baseUrl; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".quadmenu-navbar-collapse ul li:nth-child(2)") + .find("li") + .each(function () { + const novelName = loadedCheerio(this) + .text() + .replace(/[\s\n]+/g, " "); + const novelCover = loadedCheerio(this).find("img").attr("src"); + let novelUrl = loadedCheerio(this).find("a").attr("href"); + if (!novelUrl) + return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = novelUrl; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let novel = { + url, + }; + novel.name = loadedCheerio("h1.entry-title") + .text() + .replace(/[\t\n]/g, "") + .trim(); + novel.cover = loadedCheerio('img[loading="lazy"]').attr("src"); + loadedCheerio(".entry-content") + .find("div") + .each(function () { + if (loadedCheerio(this).text().includes("Escritor:")) { + novel.author = loadedCheerio(this) + .text() + .replace("Escritor: ", "") + .trim(); + } + if (loadedCheerio(this).text().includes("Género:")) { + novel.genres = loadedCheerio(this) + .text() + .replace(/Género: |\s/g, ""); + } + if (loadedCheerio(this).text().includes("Sinopsis:")) { + novel.summary = loadedCheerio(this).next().text(); + } + }); + let novelChapters = []; + if (loadedCheerio(".entry-content").find("li").length) { + loadedCheerio(".entry-content") + .find("li") + .each(function () { + let chapterUrl = loadedCheerio(this).find("a").attr("href"); + if (chapterUrl && chapterUrl.includes(baseUrl)) { + const chapterName = loadedCheerio(this).text(); + const releaseDate = null; + const chapter = { + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }; + novelChapters.push(chapter); + } + }); + } + else { + loadedCheerio(".entry-content") + .find("p") + .each(function () { + let chapterUrl = loadedCheerio(this).find("a").attr("href"); + if (chapterUrl && chapterUrl.includes(baseUrl)) { + const chapterName = loadedCheerio(this).text(); + const releaseDate = null; + const chapter = { + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }; + novelChapters.push(chapter); + } + }); + } + novel.chapters = novelChapters; + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = chapterUrl; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let chapterText = loadedCheerio(".entry-content").html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + searchTerm = searchTerm.toLowerCase(); + let url = baseUrl; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio(".menu-item-2869") + .find(".menu-item.menu-item-type-post_type.menu-item-object-post") + .each(function () { + const novelName = loadedCheerio(this).text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + let novelUrl = loadedCheerio(this).find("a").attr("href"); + if (!novelUrl) + return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + novels = novels.filter((novel) => novel.name.toLowerCase().includes(searchTerm)); + return novels; + }); +}; +exports.searchNovels = searchNovels; +exports.fetchImage = fetch_1.fetchFile; diff --git a/js/plugins/turkish/TurkceLightNovels[madara].js b/js/plugins/turkish/TurkceLightNovels[madara].js new file mode 100644 index 0000000..6e39521 --- /dev/null +++ b/js/plugins/turkish/TurkceLightNovels[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "turkcelightnovels"; +exports.name = "TurkceLightNovels [madara]"; +exports.icon = "multisrc/madara/icons/turkcelightnovels.png"; +exports.version = "1.0.0"; +exports.site = "https://turkcelightnovels.com/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "light-novel" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (false) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "light-novel" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/turkish/WebNovelOku[madara].js b/js/plugins/turkish/WebNovelOku[madara].js new file mode 100644 index 0000000..a527249 --- /dev/null +++ b/js/plugins/turkish/WebNovelOku[madara].js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const defaultCover_1 = require("@libs/defaultCover"); +const novelStatus_1 = require("@libs/novelStatus"); +const parseMadaraDate_1 = require("@libs/parseMadaraDate"); +const dayjs_1 = __importDefault(require("dayjs")); +exports.id = "webnoveloku"; +exports.name = "WebNovelOku [madara]"; +exports.icon = "multisrc/madara/icons/webnoveloku.png"; +exports.version = "1.0.0"; +exports.site = "https://www.webnoveloku.com/"; +const baseUrl = exports.site; +const popularNovels = (pageNo, { showLatestNovels }) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + let url = exports.site + "novel" + '/page/' + pageNo + sortOrder; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const body = yield (0, fetch_1.fetchApi)(novelUrl).then(res => res.text()); + let loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.manga-title-badges').remove(); + novel.name = loadedCheerio('.post-title h1').text().trim(); + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover_1.defaultCover; + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? novelStatus_1.NovelStatus.Ongoing + : novelStatus_1.NovelStatus.Completed; + break; + } + }); + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + let html; + if (false) { + const novelId = loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + const body = { + action: "manga_get_chapters", + manga: novelId, + }; + html = yield (0, fetch_1.fetchApi)(baseUrl + 'wp-admin/admin-ajax.php', { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } + else { + html = yield (0, fetch_1.fetchApi)(baseUrl + 'ajax/chapters/', { method: 'POST' }) + .then(res => res.text()); + } + if (html !== '0') { + loadedCheerio = (0, cheerio_1.load)(html); + } + const chapters = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + if (releaseDate) { + releaseDate = (0, parseMadaraDate_1.parseMadaraDate)(releaseDate); + } + else { + /** + * Insert current date + */ + releaseDate = (0, dayjs_1.default)().format('LL'); + } + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + novel.chapters = chapters.reverse(); + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + const body = yield (0, fetch_1.fetchApi)(chapterUrl).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + const body = yield (0, fetch_1.fetchApi)(url).then(res => res.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + return yield (0, fetch_1.fetchFile)(url); +}); +exports.fetchImage = fetchImage; diff --git a/js/plugins/turkish/epiknovel.js b/js/plugins/turkish/epiknovel.js new file mode 100644 index 0000000..a80159c --- /dev/null +++ b/js/plugins/turkish/epiknovel.js @@ -0,0 +1,142 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.icon = exports.version = exports.site = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +exports.id = "epiknovel.com"; +exports.name = "EpikNovel"; +exports.site = "https://www.epiknovel.com/"; +exports.version = "1.0.0"; +exports.icon = "src/tr/epiknovel/icon.png"; +const baseUrl = exports.site; +const popularNovels = function (page) { + return __awaiter(this, void 0, void 0, function* () { + let url = baseUrl + "seri-listesi?Sayfa=" + page; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio("div.col-lg-12.col-md-12").each(function () { + const novelName = loadedCheerio(this).find("h3").text(); + const novelCover = loadedCheerio(this).find("img").attr("data-src"); + const novelUrl = loadedCheerio(this).find("h3 > a").attr("href"); + if (!novelUrl) + return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + // console.log(novels); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = novelUrl; + // console.log(url); + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let novel = { url }; + novel.name = loadedCheerio("h1#tables").text().trim(); + novel.cover = loadedCheerio("img.manga-cover").attr("src"); + novel.summary = loadedCheerio("#wrapper > div.row > div.col-md-9 > div:nth-child(6) > p:nth-child(3)") + .text() + .trim(); + novel.status = loadedCheerio("#wrapper > div.row > div.col-md-9 > div.row > div.col-md-9 > h4:nth-child(3) > a") + .text() + .trim(); + novel.author = loadedCheerio("#NovelInfo > p:nth-child(4)") + .text() + .replace(/Publisher:|\s/g, "") + .trim(); + let novelChapters = []; + loadedCheerio("table").find("tr").first().remove(); + loadedCheerio("table") + .find("tr") + .each(function () { + const releaseDate = loadedCheerio(this) + .find("td:nth-child(3)") + .text(); + let chapterName = loadedCheerio(this) + .find("td:nth-child(1) > a") + .text(); + if (loadedCheerio(this).find("td:nth-child(1) > span").length > + 0) { + chapterName = "🔒 " + chapterName; + } + const chapterUrl = loadedCheerio(this) + .find(" td:nth-child(1) > a") + .attr("href"); + if (!chapterUrl) + return; + novelChapters.push({ + name: chapterName, + url: chapterUrl, + releaseTime: releaseDate, + }); + }); + novel.chapters = novelChapters; + // console.log(novel); + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = chapterUrl; + // console.log(url); + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let chapterName, chapterText; + if (result.url === "https://www.epiknovel.com/login") { + chapterName = ""; + chapterText = "Premium Chapter"; + } + else { + chapterName = loadedCheerio("#icerik > center > h4 > b").text(); + chapterText = loadedCheerio("div#icerik").html(); + } + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + const url = baseUrl + "seri-listesi?q=" + searchTerm; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + let novels = []; + loadedCheerio("div.col-lg-12.col-md-12").each(function () { + const novelName = loadedCheerio(this).find("h3").text(); + const novelCover = loadedCheerio(this).find("img").attr("data-src"); + const novelUrl = loadedCheerio(this).find("h3 > a").attr("href"); + if (!novelUrl) + return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.searchNovels = searchNovels; +exports.fetchImage = fetch_1.fetchFile; diff --git a/js/plugins/turkish/lshnovel.js b/js/plugins/turkish/lshnovel.js new file mode 100644 index 0000000..13c7b99 --- /dev/null +++ b/js/plugins/turkish/lshnovel.js @@ -0,0 +1,248 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.filters = exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.icon = exports.version = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +const filterInputs_1 = require("@libs/filterInputs"); +exports.id = "lshnovel"; +exports.name = "Liebe Schnee Hiver Novel"; +exports.version = "1.0.0"; +exports.icon = "multisrc/wpmangastream/icons/lshnovel.png"; +exports.site = "https://lshnovel.com/"; +exports.protected = false; +const pluginId = exports.id; +const baseUrl = exports.site; +const popularNovels = function (page, { filters }) { + return __awaiter(this, void 0, void 0, function* () { + let link = `${baseUrl}series/?page=${page}`; + if (filters) { + if (Array.isArray(filters.genres) && filters.genres.length) { + link += filters.genres.map((i) => `&genre[]=${i}`).join(""); + } + if (Array.isArray(filters.type) && filters.type.length) { + link += filters.type.map((i) => `&lang[]=${i}`).join(""); + } + } + link += "&status=" + ((filters === null || filters === void 0 ? void 0 : filters.status) ? filters === null || filters === void 0 ? void 0 : filters.status : ""); + link += "&order=" + ((filters === null || filters === void 0 ? void 0 : filters.order) ? filters === null || filters === void 0 ? void 0 : filters.order : "popular"); + const body = yield (0, fetch_1.fetchApi)(link, {}, pluginId).then((result) => result.text()); + const loadedCheerio = (0, cheerio_1.load)(body); + const novels = []; + loadedCheerio("article.bs").each(function () { + const novelName = loadedCheerio(this).find(".ntitle").text().trim(); + let image = loadedCheerio(this).find("img"); + const novelCover = image.attr("data-src") || image.attr("src"); + const novelUrl = loadedCheerio(this).find("a").attr("href"); + if (!novelUrl) + return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + novels.push(novel); + }); + return novels; + }); +}; +exports.popularNovels = popularNovels; +const parseNovelAndChapters = function (novelUrl) { + return __awaiter(this, void 0, void 0, function* () { + const url = novelUrl; + const result = yield (0, fetch_1.fetchApi)(url, {}, pluginId); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + const novel = { + url, + chapters: [], + }; + novel.name = loadedCheerio(".entry-title").text(); + novel.cover = + loadedCheerio("img.wp-post-image").attr("data-src") || + loadedCheerio("img.wp-post-image").attr("src"); + loadedCheerio("div.spe > span").each(function () { + const detailName = loadedCheerio(this).find("b").text().trim(); + const detail = loadedCheerio(this) + .find("b") + .remove() + .end() + .text() + .trim(); + switch (detailName) { + case "المؤلف:": + case "Yazar:": + case "Autor:": + case "Author:": + novel.author = detail; + break; + case "Status:": + case "Seviye:": + novel.status = detail; + break; + } + }); + novel.genres = loadedCheerio(".genxed") + .text() + .trim() + .replace(/\s/g, ","); + loadedCheerio('div[itemprop="description"] h3,p.a,strong').remove(); + novel.summary = loadedCheerio('div[itemprop="description"]') + .find("br") + .replaceWith("\n") + .end() + .text(); + let chapter = []; + loadedCheerio(".eplister") + .find("li") + .each(function () { + const chapterName = loadedCheerio(this).find(".epl-num").text() + + " - " + + loadedCheerio(this).find(".epl-title").text(); + const releaseDate = loadedCheerio(this) + .find(".epl-date") + .text() + .trim(); + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + if (!chapterUrl) + return; + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + novel.chapters = chapter.reverse(); + return novel; + }); +}; +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = function (chapterUrl) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)(chapterUrl, {}, pluginId); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + let chapterText = loadedCheerio("div.epcontent").html(); + return chapterText; + }); +}; +exports.parseChapter = parseChapter; +const searchNovels = function (searchTerm) { + return __awaiter(this, void 0, void 0, function* () { + const url = `${baseUrl}?s=${searchTerm}`; + const result = yield (0, fetch_1.fetchApi)(url, {}, pluginId); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + const novels = []; + loadedCheerio("article.bs").each(function () { + const novelName = loadedCheerio(this).find(".ntitle").text().trim(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this).find("a").attr("href"); + if (!novelUrl) + return; + novels.push({ + name: novelName, + url: novelUrl, + cover: novelCover, + }); + }); + return novels; + }); +}; +exports.searchNovels = searchNovels; +const fetchImage = function (url) { + return __awaiter(this, void 0, void 0, function* () { + const headers = { + Referer: baseUrl, + }; + return yield (0, fetch_1.fetchFile)(url, { headers: headers }); + }); +}; +exports.fetchImage = fetchImage; +exports.filters = [ + { + key: "order", + label: "Önerilen", + values: [ + { label: "Varsayılan", value: "" }, + { label: "A-Z", value: "title" }, + { label: "Z-A", value: "titlereverse" }, + { label: "Son Yüklemeler", value: "update" }, + { label: "Son Eklenenler", value: "latest" }, + { label: "Bestimiz", value: "popular" }, + ], + inputType: filterInputs_1.FilterInputs.Picker, + }, + { + key: "status", + label: "Statü", + values: [ + { label: "Tümü", value: "" }, + { label: "Ongoing", value: "ongoing" }, + { label: "Hiatus", value: "hiatus" }, + { label: "Completed", value: "completed" }, + ], + inputType: filterInputs_1.FilterInputs.Picker, + }, + { + key: "type", + label: "Tür", + values: [ + { label: "Çeviri Novel", value: "ceviri-novel" }, + { label: "Liz-Chan", value: "liz-chan" }, + { label: "Manhwa", value: "manhwa" }, + { label: "Orijinal Novel", value: "orijinal-novel" }, + { label: "Web Novel", value: "web-novel" }, + ], + inputType: filterInputs_1.FilterInputs.Checkbox, + }, + { + key: "genres", + label: "Kategori", + values: [ + { label: "+18", value: "18" }, + { label: "Action", value: "action" }, + { label: "Adult", value: "adult" }, + { label: "Aksiyon", value: "aksiyon" }, + { label: "BL", value: "bl" }, + { label: "Comedy", value: "comedy" }, + { label: "Doğaüstü", value: "dogaustu" }, + { label: "Dram", value: "dram" }, + { label: "Drama", value: "drama" }, + { label: "Ecchi", value: "ecchi" }, + { label: "Fantastik", value: "fantastik" }, + { label: "Fantasy", value: "fantasy" }, + { label: "Gizem", value: "gizem" }, + { label: "Harem", value: "harem" }, + { label: "Historical", value: "historical" }, + { label: "Josei", value: "josei" }, + { label: "Macera", value: "macera" }, + { label: "Manhwa", value: "manhwa" }, + { label: "Martial Arts", value: "martial-arts" }, + { label: "Mature", value: "mature" }, + { label: "Novel", value: "novel" }, + { label: "Okul", value: "okul" }, + { label: "Psikolojik", value: "psikolojik" }, + { label: "Psychological", value: "psychological" }, + { label: "Reverse Harem", value: "reverse-harem" }, + { label: "Romance", value: "romance" }, + { label: "Romantik", value: "romantik" }, + { label: "Shoujo", value: "shoujo" }, + { label: "Slice Of Life", value: "slice-of-life" }, + { label: "Smut", value: "smut" }, + { label: "Supernatural", value: "supernatural" }, + { label: "Tarihi", value: "tarihi" }, + { label: "Tragedy", value: "tragedy" }, + { label: "Yaoi", value: "yaoi" }, + ], + inputType: filterInputs_1.FilterInputs.Checkbox, + }, +]; diff --git a/js/plugins/vietnamese/LNHako.js b/js/plugins/vietnamese/LNHako.js new file mode 100644 index 0000000..9b2fe79 --- /dev/null +++ b/js/plugins/vietnamese/LNHako.js @@ -0,0 +1,163 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchImage = exports.searchNovels = exports.parseChapter = exports.parseNovelAndChapters = exports.popularNovels = exports.site = exports.version = exports.icon = exports.name = exports.id = void 0; +const cheerio_1 = require("cheerio"); +const fetch_1 = require("@libs/fetch"); +// import dayjs from "dayjs"; +// import { FilterInputs } from "@libs/filterInputs"; +// import { NovelStatus } from "@libs/novelStatus"; +const isAbsoluteUrl_1 = require("@libs/isAbsoluteUrl"); +// import { parseMadaraDate } from "@libs/parseMadaraDate"; +const pluginId = "ln.hako"; +const baseUrl = "https://ln.hako.vn"; +exports.id = pluginId; // string and must be unique +exports.name = "Hako"; +exports.icon = "src/vi/hakolightnovel/icon.png"; // The relative path to the icon without @icons . For example: 'src/vi/hakolightnovel/icon.png' +exports.version = "1.0.0"; // xx.xx.xx +exports.site = baseUrl; // the link to the site +exports["protected"] = true; +const popularNovels = (pageNo) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const link = baseUrl + "/danh-sach?truyendich=1&sapxep=topthang&page=" + pageNo; + const result = yield (0, fetch_1.fetchApi)(link); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio("main.row > .thumb-item-flow").each(function () { + let url = loadedCheerio(this) + .find("div.thumb_attr.series-title > a") + .attr("href"); + if (url && !(0, isAbsoluteUrl_1.isUrlAbsolute)(url)) { + url = baseUrl + url; + } + if (url) { + const name = loadedCheerio(this) + .find(".series-title") + .text() + .trim(); + let cover = loadedCheerio(this) + .find(".img-in-ratio") + .attr("data-bg"); + if (cover && !(0, isAbsoluteUrl_1.isUrlAbsolute)(cover)) { + cover = baseUrl + cover; + } + const novel = { + name, + url, + cover, + }; + novels.push(novel); + } + }); + return novels; +}); +exports.popularNovels = popularNovels; +const parseNovelAndChapters = (novelUrl) => __awaiter(void 0, void 0, void 0, function* () { + const novel = { + url: novelUrl, + }; + const result = yield (0, fetch_1.fetchApi)(novelUrl); + const body = yield result.text(); + let loadedCheerio = (0, cheerio_1.load)(body); + novel.name = loadedCheerio(".series-name").text(); + const background = loadedCheerio(".series-cover > .a6-ratio > div").attr("style") || ""; + const novelCover = background.substring(background.indexOf("http"), background.length - 2); + novel.cover = novelCover + ? (0, isAbsoluteUrl_1.isUrlAbsolute)(novelCover) + ? novelCover + : baseUrl + novelCover + : ""; + novel.summary = loadedCheerio(".summary-content").text().trim(); + novel.author = loadedCheerio("#mainpart > div:nth-child(2) > div > div:nth-child(1) > section > main > div.top-part > div > div.col-12.col-md-9 > div.series-information > div:nth-child(2) > span.info-value > a") + .text() + .trim(); + novel.genres = loadedCheerio(".series-gernes") + .text() + .trim() + .replace(/ +/g, " ") + .split("\n") + .filter((e) => e.trim()) + .join(", "); + novel.status = loadedCheerio("#mainpart > div:nth-child(2) > div > div:nth-child(1) > section > main > div.top-part > div > div.col-12.col-md-9 > div.series-information > div:nth-child(4) > span.info-value > a") + .text() + .trim(); + const chapters = []; + loadedCheerio(".list-chapters li").each(function () { + let chapterUrl = loadedCheerio(this).find("a").attr("href"); + if (chapterUrl && !(0, isAbsoluteUrl_1.isUrlAbsolute)(chapterUrl)) { + chapterUrl = baseUrl + chapterUrl; + } + if (chapterUrl) { + const chapterName = loadedCheerio(this) + .find(".chapter-name") + .text() + .trim(); + const releaseTime = loadedCheerio(this) + .find(".chapter-time") + .text(); + const chapter = { + name: chapterName, + releaseTime: releaseTime, + url: chapterUrl, + }; + chapters.push(chapter); + } + }); + novel.chapters = chapters; + return novel; +}); +exports.parseNovelAndChapters = parseNovelAndChapters; +const parseChapter = (chapterUrl) => __awaiter(void 0, void 0, void 0, function* () { + const result = yield (0, fetch_1.fetchApi)(chapterUrl); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + const chapterText = loadedCheerio("#chapter-content").html() || ""; + return chapterText; +}); +exports.parseChapter = parseChapter; +const searchNovels = (searchTerm) => __awaiter(void 0, void 0, void 0, function* () { + const novels = []; + const url = baseUrl + "/tim-kiem?keywords=" + searchTerm; + const result = yield (0, fetch_1.fetchApi)(url); + const body = yield result.text(); + const loadedCheerio = (0, cheerio_1.load)(body); + loadedCheerio("div.row > .thumb-item-flow").each(function () { + let novelUrl = loadedCheerio(this) + .find("div.thumb_attr.series-title > a") + .attr("href"); + if (novelUrl && !(0, isAbsoluteUrl_1.isUrlAbsolute)(novelUrl)) { + novelUrl = baseUrl + novelUrl; + } + if (novelUrl) { + const novelName = loadedCheerio(this).find(".series-title").text(); + let novelCover = loadedCheerio(this) + .find(".img-in-ratio") + .attr("data-bg"); + if (novelCover && !(0, isAbsoluteUrl_1.isUrlAbsolute)(novelCover)) { + novelCover = baseUrl + novelCover; + } + novels.push({ + name: novelName, + url: novelUrl, + cover: novelCover, + }); + } + }); + return novels; +}); +exports.searchNovels = searchNovels; +const fetchImage = (url) => __awaiter(void 0, void 0, void 0, function* () { + const headers = { + Referer: "https://ln.hako.vn", + }; + return yield (0, fetch_1.fetchFile)(url, { headers: headers }); +}); +exports.fetchImage = fetchImage; diff --git a/jsconfig.json b/jsconfig.json index a6b1ab6..7ab345c 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -2,9 +2,6 @@ "compilerOptions": { "baseUrl": ".", "paths": { - "@api/*": [ - "./api/*", - ], "@libs/*": [ "./libs/*", ], diff --git a/libs/defaultCover.ts b/libs/defaultCover.ts new file mode 100644 index 0000000..2fe0459 --- /dev/null +++ b/libs/defaultCover.ts @@ -0,0 +1,2 @@ +export const defaultCover = + "https://github.com/LNReader/lnreader-sources/blob/main/icons/src/coverNotAvailable.jpg?raw=true"; diff --git a/libs/fetch.ts b/libs/fetch.ts new file mode 100644 index 0000000..39282ce --- /dev/null +++ b/libs/fetch.ts @@ -0,0 +1,33 @@ +import { Plugin } from "@typings/plugin"; +import { NodeFetchParams, getNodeFetch } from "./nodeFetch"; + +const fetch = async (...args: NodeFetchParams) => + (await getNodeFetch())(...args); + +const defaultUserAgentString = + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36"; + +export async function fetchApi( + url: string, + init?: NodeFetchParams[1], + pluginId?: string +) { + const headers = { + "User-Agent": defaultUserAgentString, + ...init?.headers, + }; + if (pluginId) console.log("Enable CloudFlare cookie for", pluginId); + return await fetch(url, { ...init, headers }); +} + +export const fetchFile: Plugin.fetchImage = async function (url, init) { + if (!init) init = {}; + try { + const res = await fetch(url, init); + if (!res.ok) return undefined; + const arrayBuffer = await res.arrayBuffer(); + return Buffer.from(arrayBuffer).toString("base64"); + } catch (e) { + return undefined; + } +}; diff --git a/libs/fetchApi.js b/libs/fetchApi.js deleted file mode 100644 index 78b564e..0000000 --- a/libs/fetchApi.js +++ /dev/null @@ -1,14 +0,0 @@ -const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args)); - -const defaultUserAgentString = - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36'; - -async function fetchApi (url, init, pluginId){ - const headers = { - ...init?.headers, - 'User-Agent': defaultUserAgentString, - }; - return await fetch(url, { ...init, headers }); -} - -module.exports = fetchApi \ No newline at end of file diff --git a/libs/fetchFile.js b/libs/fetchFile.js deleted file mode 100644 index 86c9d3f..0000000 --- a/libs/fetchFile.js +++ /dev/null @@ -1,15 +0,0 @@ -const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args)); - -async function fetchFile(url, init){ - if(!init) init = {}; - try{ - const res = await fetch(url, init); - if(!res.ok) return undefined; - const arrayBuffer = await res.arrayBuffer(); - return Buffer.from(arrayBuffer).toString('base64'); - }catch(e){ - return undefined; - } -} - -module.exports = fetchFile; \ No newline at end of file diff --git a/libs/filterInputs.js b/libs/filterInputs.js deleted file mode 100644 index 06b6427..0000000 --- a/libs/filterInputs.js +++ /dev/null @@ -1,7 +0,0 @@ -const FilterInputs = { - TextInput: 0, - Picker: 1, - Checkbox: 2, -} - -module.exports = FilterInputs diff --git a/libs/filterInputs.ts b/libs/filterInputs.ts new file mode 100644 index 0000000..2327a77 --- /dev/null +++ b/libs/filterInputs.ts @@ -0,0 +1,15 @@ +export interface FilterValue { + label: string; + value: string | number; +} +export enum FilterInputs { + TextInput, + Picker, + Checkbox, +} +export interface Filter { + key: string; + label: string; + values: FilterValue[]; + inputType: FilterInputs; +} diff --git a/libs/isAbsoluteUrl.js b/libs/isAbsoluteUrl.js deleted file mode 100644 index 11d6435..0000000 --- a/libs/isAbsoluteUrl.js +++ /dev/null @@ -1,25 +0,0 @@ -const isUrlAbsolute = (url) => { - if (url) { - if (url.indexOf('//') === 0) { - return true; - } // URL is protocol-relative (= absolute) - if (url.indexOf('://') === -1) { - return false; - } // URL has no protocol (= relative) - if (url.indexOf('.') === -1) { - return false; - } // URL does not contain a dot, i.e. no TLD (= relative, possibly REST) - if (url.indexOf('/') === -1) { - return false; - } // URL does not contain a single slash (= relative) - if (url.indexOf(':') > url.indexOf('/')) { - return false; - } // The first colon comes after the first slash (= relative) - if (url.indexOf('://') < url.indexOf('.')) { - return true; - } // Protocol is defined before first dot (= absolute) - } - return false; // Anything else must be relative - }; - -module.exports = isUrlAbsolute; \ No newline at end of file diff --git a/libs/isAbsoluteUrl.ts b/libs/isAbsoluteUrl.ts new file mode 100644 index 0000000..d082f6e --- /dev/null +++ b/libs/isAbsoluteUrl.ts @@ -0,0 +1,23 @@ +export const isUrlAbsolute = (url: string) => { + if (url) { + if (url.indexOf("//") === 0) { + return true; + } // URL is protocol-relative (= absolute) + if (url.indexOf("://") === -1) { + return false; + } // URL has no protocol (= relative) + if (url.indexOf(".") === -1) { + return false; + } // URL does not contain a dot, i.e. no TLD (= relative, possibly REST) + if (url.indexOf("/") === -1) { + return false; + } // URL does not contain a single slash (= relative) + if (url.indexOf(":") > url.indexOf("/")) { + return false; + } // The first colon comes after the first slash (= relative) + if (url.indexOf("://") < url.indexOf(".")) { + return true; + } // Protocol is defined before first dot (= absolute) + } + return false; // Anything else must be relative +}; diff --git a/libs/languages.js b/libs/languages.js deleted file mode 100644 index 1e91d33..0000000 --- a/libs/languages.js +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = { - Arabic: 'العربية', - Chinese: '中文, 汉语, 漢語', - English: 'English', - French: 'français, française', - Indonesian: 'Bahasa Indonesia', - Japanese: '日本語', - Korean: '한국어, 조선말', - Portuguese: 'Português', - Russian: 'русский язык', - Spanish: 'español, castellano', - Turkish: 'Türkçe', - Vietnamese: 'Tiếng Việt', -} \ No newline at end of file diff --git a/libs/languages.ts b/libs/languages.ts new file mode 100644 index 0000000..7a6da82 --- /dev/null +++ b/libs/languages.ts @@ -0,0 +1,14 @@ +export const languages = { + Arabic: "العربية", + Chinese: "中文, 汉语, 漢語", + English: "English", + French: "français, française", + Indonesian: "Bahasa Indonesia", + Japanese: "日本語", + Korean: "한국어, 조선말", + Portuguese: "Português", + Russian: "русский язык", + Spanish: "español, castellano", + Turkish: "Türkçe", + Vietnamese: "Tiếng Việt", +} as const; diff --git a/libs/nodeFetch.ts b/libs/nodeFetch.ts new file mode 100644 index 0000000..d815177 --- /dev/null +++ b/libs/nodeFetch.ts @@ -0,0 +1,9 @@ +const nodeFetch = import("node-fetch"); + +export type NodeFetchFunction = Awaited["default"]; + +export type NodeFetchParams = Parameters; + +export const getNodeFetch = async (): Promise => { + return (await nodeFetch).default; +}; diff --git a/libs/novelStatus.js b/libs/novelStatus.js deleted file mode 100644 index 69a9a03..0000000 --- a/libs/novelStatus.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - Unknown: 'Unknown', - Ongoing: 'Ongoing', - Completed: 'Completed', - Licensed: 'Licensed', - PublishingFinished: 'Publishing Finished', - Cancelled: 'Cancelled', - OnHiatus: 'On Hiatus', -}; \ No newline at end of file diff --git a/libs/novelStatus.ts b/libs/novelStatus.ts new file mode 100644 index 0000000..280a647 --- /dev/null +++ b/libs/novelStatus.ts @@ -0,0 +1,9 @@ +export const NovelStatus = { + Unknown: "Unknown", + Ongoing: "Ongoing", + Completed: "Completed", + Licensed: "Licensed", + PublishingFinished: "Publishing Finished", + Cancelled: "Cancelled", + OnHiatus: "On Hiatus", +} as const; \ No newline at end of file diff --git a/libs/parseDate.js b/libs/parseDate.js deleted file mode 100644 index 5f481a3..0000000 --- a/libs/parseDate.js +++ /dev/null @@ -1,30 +0,0 @@ -const dayjs = require('dayjs'); - -const parseMadaraDate = date => { - let releaseDate = date; - - if (date.includes('ago')) { - let timeAgo = date; - releaseDate = new Date(); - - timeAgo = timeAgo.match(/\d+/)[0]; - - if (timeAgo.includes('hours ago') || timeAgo.includes('hour ago')) { - releaseDate.setHours(releaseDate.getHours() - timeAgo); - } - - if (timeAgo.includes('days ago') || timeAgo.includes('day ago')) { - releaseDate.setDate(releaseDate.getDate() - timeAgo); - } - - if (timeAgo.includes('months ago') || timeAgo.includes('month ago')) { - releaseDate.setMonth(releaseDate.getMonth() - timeAgo); - } - - releaseDate = dayjs(releaseDate).format('LL'); - } - - return releaseDate; -}; - -module.exports = parseMadaraDate; \ No newline at end of file diff --git a/libs/parseMadaraDate.ts b/libs/parseMadaraDate.ts new file mode 100644 index 0000000..da5d4a7 --- /dev/null +++ b/libs/parseMadaraDate.ts @@ -0,0 +1,26 @@ +import dayjs from "dayjs"; + +export const parseMadaraDate = (date: string) => { + if (date.includes("ago")) { + const dayJSDate = dayjs(new Date()); // today + const timeAgo = date.match(/\d+/)?.[0] || ""; + const timeAgoInt = parseInt(timeAgo, 10); + + if (!timeAgo) return date; // there is no number! + + if (date.includes("hours ago") || date.includes("hour ago")) { + dayJSDate.subtract(timeAgoInt, "hours"); // go back N hours + } + + if (date.includes("days ago") || date.includes("day ago")) { + dayJSDate.subtract(timeAgoInt, "days"); // go back N days + } + + if (date.includes("months ago") || date.includes("month ago")) { + dayJSDate.subtract(timeAgoInt, "months"); // go back N months + } + + return dayJSDate.format("LL"); + } + return date; // there is no "ago" so don't mess with the date +}; diff --git a/libs/showToast.ts b/libs/showToast.ts new file mode 100644 index 0000000..e6b12e5 --- /dev/null +++ b/libs/showToast.ts @@ -0,0 +1,3 @@ +export function showToast(...message: Parameters<(typeof console)["log"]>) { + console.log(...message); +} diff --git a/nodemon.json b/nodemon.json index d7f117e..879d47a 100644 --- a/nodemon.json +++ b/nodemon.json @@ -1,3 +1,5 @@ { - "ignore": ["plugins/plugins.json", "plugins/plugins.min.json", "cheerio_space/*"] + "ext": "js,mjs,json,ts", + "ignore": ["js/**"], + "exec": "tsc && node ./js/index.js" } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 13b1c74..3343b8f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,2883 +1,3701 @@ { - "name": "lnreader-plugins", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "lnreader-plugins", - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "body-parser": "^1.20.2", - "cheerio": "^1.0.0-rc.10", - "cli-color": "^2.0.3", - "dayjs": "^1.11.4", - "express": "^4.18.2", - "module-alias": "^2.2.2", - "node-fetch": "^3.3.1" - }, - "devDependencies": { - "nodemon": "^2.0.22", - "pre-commit": "^1.2.2" - } - }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/cheerio": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz", - "integrity": "sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==", - "dependencies": { - "cheerio-select": "^1.5.0", - "dom-serializer": "^1.3.2", - "domhandler": "^4.2.0", - "htmlparser2": "^6.1.0", - "parse5": "^6.0.1", - "parse5-htmlparser2-tree-adapter": "^6.0.1", - "tslib": "^2.2.0" - }, - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" - } - }, - "node_modules/cheerio-select": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.6.0.tgz", - "integrity": "sha512-eq0GdBvxVFbqWgmCm7M3XGs1I8oLy/nExUnh6oLqmBditPO9AqQJrkslDpMun/hZ0yyTs8L0m85OHp4ho6Qm9g==", - "dependencies": { - "css-select": "^4.3.0", - "css-what": "^6.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.3.1", - "domutils": "^2.8.0" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/cli-color": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.3.tgz", - "integrity": "sha512-OkoZnxyC4ERN3zLzZaY9Emb7f/MhBOIpePv0Ycok0fJYT+Ouo00UBEIwsVsr0yoow++n5YWlSUgST9GKhNHiRQ==", - "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.61", - "es6-iterator": "^2.0.3", - "memoizee": "^0.4.15", - "timers-ext": "^0.1.7" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "engines": [ - "node >= 0.8" - ], - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true - }, - "node_modules/cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", - "dev": true, - "dependencies": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "node_modules/css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dependencies": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "engines": { - "node": ">= 12" - } - }, - "node_modules/dayjs": { - "version": "1.11.4", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.4.tgz", - "integrity": "sha512-Zj/lPM5hOvQ1Bf7uAvewDaUcsJoI6JmNqmHhHl3nyumwe0XHwt8sWdOVAPACJzCebL8gQCi+K49w7iKWnGwX9g==" - }, - "node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", - "hasInstallScript": true, - "dependencies": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "next-tick": "^1.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "dependencies": { - "d": "^1.0.1", - "ext": "^1.1.2" - } - }, - "node_modules/es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", - "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/express/node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/express/node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/ext": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", - "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "dependencies": { - "type": "^2.7.2" - } - }, - "node_modules/ext/node_modules/type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" - }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" + "name": "lnreader-plugins", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "lnreader-plugins", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "body-parser": "^1.20.2", + "cheerio": "^1.0.0-rc.10", + "cli-color": "^2.0.3", + "dayjs": "^1.11.4", + "express": "^4.18.2", + "module-alias": "^2.2.2", + "node-fetch": "^3", + "ts-node": "^10.9.1", + "tsc-alias": "^1.8.6", + "typescript": "^5.1.3" + }, + "devDependencies": { + "@types/express": "^4.17.17", + "@types/node": "^20.3.1", + "nodemon": "^2.0.22", + "pre-commit": "^1.2.2" + } }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "node_modules/get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-promise": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "dependencies": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "node_modules/lru-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", - "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==", - "dependencies": { - "es5-ext": "~0.10.2" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memoizee": { - "version": "0.4.15", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz", - "integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==", - "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.53", - "es6-weak-map": "^2.0.3", - "event-emitter": "^0.3.5", - "is-promise": "^2.2.2", - "lru-queue": "^0.1.0", - "next-tick": "^1.1.0", - "timers-ext": "^0.1.7" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/module-alias": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz", - "integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==" - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz", - "integrity": "sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/nodemon": { - "version": "2.0.22", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", - "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==", - "dev": true, - "dependencies": { - "chokidar": "^3.5.2", - "debug": "^3.2.7", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^5.7.1", - "simple-update-notifier": "^1.0.7", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=8.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" - } - }, - "node_modules/nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", - "dev": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "*" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/os-shim": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz", - "integrity": "sha512-jd0cvB8qQ5uVt0lvCIexBaROw1KyKm5sbulg2fWOHjETisuCzWyt+eTZKEMs8v6HwzoGs8xik26jg7eCM6pS+A==", - "dev": true, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", - "dependencies": { - "parse5": "^6.0.1" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pre-commit": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/pre-commit/-/pre-commit-1.2.2.tgz", - "integrity": "sha512-qokTiqxD6GjODy5ETAIgzsRgnBWWQHQH2ghy86PU7mIn/wuWeTwF3otyNQZxWBwVn8XNr8Tdzj/QfUXpH+gRZA==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "cross-spawn": "^5.0.1", - "spawn-sync": "^1.0.15", - "which": "1.2.x" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", - "dev": true - }, - "node_modules/pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true - }, - "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "engines": { + "node": ">=6.0.0" + } }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dev": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/simple-update-notifier": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", - "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", - "dev": true, - "dependencies": { - "semver": "~7.0.0" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/simple-update-notifier/node_modules/semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/spawn-sync": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz", - "integrity": "sha512-9DWBgrgYZzNghseho0JOuh+5fg9u6QWhAWa51QC7+U5rCheZ/j1DrEZnyE0RBBRqZ9uEXGPgSSM0nky6burpVw==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "concat-stream": "^1.4.7", - "os-shim": "^0.1.2" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/timers-ext": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", - "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", - "dependencies": { - "es5-ext": "~0.10.46", - "next-tick": "1" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/touch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", - "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", - "dev": true, - "dependencies": { - "nopt": "~1.0.10" - }, - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, - "node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" - }, - "node_modules/type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "dev": true - }, - "node_modules/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/web-streams-polyfill": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", - "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/which": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", - "integrity": "sha512-16uPglFkRPzgiUXYMi1Jf8Z5EzN1iB4V0ZtMXcHZnwsBtQhhHeCqoWw7tsUY42hJGNDWtUsVLTjakIa5BgAxCw==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", - "dev": true - } - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, - "anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" + }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", + "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.35", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz", + "integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.3.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.1.tgz", + "integrity": "sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==" + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", + "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz", + "integrity": "sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==", + "dev": true, + "dependencies": { + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", + "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cli-color": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.3.tgz", + "integrity": "sha512-OkoZnxyC4ERN3zLzZaY9Emb7f/MhBOIpePv0Ycok0fJYT+Ouo00UBEIwsVsr0yoow++n5YWlSUgST9GKhNHiRQ==", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.61", + "es6-iterator": "^2.0.3", + "memoizee": "^0.4.15", + "timers-ext": "^0.1.7" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + }, + "node_modules/cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "dev": true, + "dependencies": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dependencies": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/dayjs": { + "version": "1.11.4", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.4.tgz", + "integrity": "sha512-Zj/lPM5hOvQ1Bf7uAvewDaUcsJoI6JmNqmHhHl3nyumwe0XHwt8sWdOVAPACJzCebL8gQCi+K49w7iKWnGwX9g==" + }, + "node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es5-ext": { + "version": "0.10.62", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", + "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "hasInstallScript": true, + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dependencies": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "node_modules/es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/express/node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "dependencies": { + "type": "^2.7.2" + } + }, + "node_modules/ext/node_modules/type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==", + "dependencies": { + "es5-ext": "~0.10.2" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memoizee": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz", + "integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.53", + "es6-weak-map": "^2.0.3", + "event-emitter": "^0.3.5", + "is-promise": "^2.2.2", + "lru-queue": "^0.1.0", + "next-tick": "^1.1.0", + "timers-ext": "^0.1.7" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/module-alias": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz", + "integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/mylas": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/mylas/-/mylas-2.1.13.tgz", + "integrity": "sha512-+MrqnJRtxdF+xngFfUUkIMQrUUL0KsxbADUkn23Z/4ibGg192Q+z+CQyiYwvWTsYjJygmMR8+w3ZDa98Zh6ESg==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/raouldeheer" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz", + "integrity": "sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/nodemon": { + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", + "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/os-shim": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz", + "integrity": "sha512-jd0cvB8qQ5uVt0lvCIexBaROw1KyKm5sbulg2fWOHjETisuCzWyt+eTZKEMs8v6HwzoGs8xik26jg7eCM6pS+A==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "dependencies": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/plimit-lit": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.5.0.tgz", + "integrity": "sha512-Eb/MqCb1Iv/ok4m1FqIXqvUKPISufcjZ605hl3KM/n8GaX8zfhtgdLwZU3vKjuHGh2O9Rjog/bHTq8ofIShdng==", + "dependencies": { + "queue-lit": "^1.5.0" + } + }, + "node_modules/pre-commit": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/pre-commit/-/pre-commit-1.2.2.tgz", + "integrity": "sha512-qokTiqxD6GjODy5ETAIgzsRgnBWWQHQH2ghy86PU7mIn/wuWeTwF3otyNQZxWBwVn8XNr8Tdzj/QfUXpH+gRZA==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "cross-spawn": "^5.0.1", + "spawn-sync": "^1.0.15", + "which": "1.2.x" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-lit": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.0.tgz", + "integrity": "sha512-IslToJ4eiCEE9xwMzq3viOO5nH8sUWUCwoElrhNMozzr9IIt2qqvB4I+uHu/zJTQVqc9R5DFwok4ijNK1pU3fA==" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-update-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "dev": true, + "dependencies": { + "semver": "~7.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/spawn-sync": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz", + "integrity": "sha512-9DWBgrgYZzNghseho0JOuh+5fg9u6QWhAWa51QC7+U5rCheZ/j1DrEZnyE0RBBRqZ9uEXGPgSSM0nky6burpVw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "concat-stream": "^1.4.7", + "os-shim": "^0.1.2" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/timers-ext": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", + "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "dependencies": { + "es5-ext": "~0.10.46", + "next-tick": "1" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "dependencies": { + "nopt": "~1.0.10" + }, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tsc-alias": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.6.tgz", + "integrity": "sha512-vq+i6VpE83IeMsSJVcFN03ZBofADhr8/gIJXjxpbnTRfN/MFXy0+SBaKG2o7p95QqXBGkeG98HYz3IkOOveFbg==", + "dependencies": { + "chokidar": "^3.5.3", + "commander": "^9.0.0", + "globby": "^11.0.4", + "mylas": "^2.1.9", + "normalize-path": "^3.0.0", + "plimit-lit": "^1.2.6" + }, + "bin": { + "tsc-alias": "dist/bin/index.js" + } + }, + "node_modules/type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "dev": true + }, + "node_modules/typescript": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", + "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/which": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", + "integrity": "sha512-16uPglFkRPzgiUXYMi1Jf8Z5EzN1iB4V0ZtMXcHZnwsBtQhhHeCqoWw7tsUY42hJGNDWtUsVLTjakIa5BgAxCw==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "engines": { + "node": ">=6" + } } - } }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "cheerio": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz", - "integrity": "sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==", - "requires": { - "cheerio-select": "^1.5.0", - "dom-serializer": "^1.3.2", - "domhandler": "^4.2.0", - "htmlparser2": "^6.1.0", - "parse5": "^6.0.1", - "parse5-htmlparser2-tree-adapter": "^6.0.1", - "tslib": "^2.2.0" - } - }, - "cheerio-select": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.6.0.tgz", - "integrity": "sha512-eq0GdBvxVFbqWgmCm7M3XGs1I8oLy/nExUnh6oLqmBditPO9AqQJrkslDpMun/hZ0yyTs8L0m85OHp4ho6Qm9g==", - "requires": { - "css-select": "^4.3.0", - "css-what": "^6.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.3.1", - "domutils": "^2.8.0" - } - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, - "cli-color": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.3.tgz", - "integrity": "sha512-OkoZnxyC4ERN3zLzZaY9Emb7f/MhBOIpePv0Ycok0fJYT+Ouo00UBEIwsVsr0yoow++n5YWlSUgST9GKhNHiRQ==", - "requires": { - "d": "^1.0.1", - "es5-ext": "^0.10.61", - "es6-iterator": "^2.0.3", - "memoizee": "^0.4.15", - "timers-ext": "^0.1.7" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "requires": { - "safe-buffer": "5.2.1" - } - }, - "content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" - }, - "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true - }, - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "requires": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - } - }, - "css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==" - }, - "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==" - }, - "dayjs": { - "version": "1.11.4", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.4.tgz", - "integrity": "sha512-Zj/lPM5hOvQ1Bf7uAvewDaUcsJoI6JmNqmHhHl3nyumwe0XHwt8sWdOVAPACJzCebL8gQCi+K49w7iKWnGwX9g==" - }, - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - }, - "destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" - }, - "dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - } - }, - "domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" - }, - "domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "requires": { - "domelementtype": "^2.2.0" - } - }, - "domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "requires": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" - }, - "entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==" - }, - "es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", - "requires": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "next-tick": "^1.1.0" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "requires": { - "d": "^1.0.1", - "ext": "^1.1.2" - } - }, - "es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", - "requires": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" - } - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" - }, - "event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", - "requires": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { + "dependencies": { + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==" + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" + }, + "@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" + }, + "@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/express": { + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", + "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.35", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz", + "integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "dev": true + }, + "@types/node": { + "version": "20.3.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.1.tgz", + "integrity": "sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==" + }, + "@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, + "@types/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", + "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", + "dev": true, + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "@types/serve-static": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz", + "integrity": "sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==", + "dev": true, + "requires": { + "@types/mime": "*", + "@types/node": "*" + } + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "acorn": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", + "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==" + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==" + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" + }, "body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - } + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "requires": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + } + }, + "cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "requires": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "cli-color": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.3.tgz", + "integrity": "sha512-OkoZnxyC4ERN3zLzZaY9Emb7f/MhBOIpePv0Ycok0fJYT+Ouo00UBEIwsVsr0yoow++n5YWlSUgST9GKhNHiRQ==", + "requires": { + "d": "^1.0.1", + "es5-ext": "^0.10.61", + "es6-iterator": "^2.0.3", + "memoizee": "^0.4.15", + "timers-ext": "^0.1.7" + } + }, + "commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "requires": { + "safe-buffer": "5.2.1" + } + }, + "content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" + }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + } + }, + "css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==" + }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==" + }, + "dayjs": { + "version": "1.11.4", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.4.tgz", + "integrity": "sha512-Zj/lPM5hOvQ1Bf7uAvewDaUcsJoI6JmNqmHhHl3nyumwe0XHwt8sWdOVAPACJzCebL8gQCi+K49w7iKWnGwX9g==" }, "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "requires": { + "path-type": "^4.0.0" + } + }, + "dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" + }, + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "requires": { + "domelementtype": "^2.3.0" + } + }, + "domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" + }, + "es5-ext": { + "version": "0.10.62", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", + "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "requires": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "next-tick": "^1.1.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "requires": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + } + } + }, + "ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "requires": { + "type": "^2.7.2" + }, + "dependencies": { + "type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" + } + } + }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "requires": { + "reusify": "^1.0.4" + } + }, + "fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "requires": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "requires": { + "fetch-blob": "^3.1.2" + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "requires": { + "is-glob": "^4.0.1" + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==" + }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==", + "requires": { + "es5-ext": "~0.10.2" + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" + }, + "memoizee": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz", + "integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==", + "requires": { + "d": "^1.0.1", + "es5-ext": "^0.10.53", + "es6-weak-map": "^2.0.3", + "event-emitter": "^0.3.5", + "is-promise": "^2.2.2", + "lru-queue": "^0.1.0", + "next-tick": "^1.1.0", + "timers-ext": "^0.1.7" + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "module-alias": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz", + "integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==" }, "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "mylas": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/mylas/-/mylas-2.1.13.tgz", + "integrity": "sha512-+MrqnJRtxdF+xngFfUUkIMQrUUL0KsxbADUkn23Z/4ibGg192Q+z+CQyiYwvWTsYjJygmMR8+w3ZDa98Zh6ESg==" + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + }, + "node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==" + }, + "node-fetch": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz", + "integrity": "sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==", + "requires": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + } + }, + "nodemon": { + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", + "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==", + "dev": true, + "requires": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + } + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "requires": { + "boolbase": "^1.0.0" + } + }, + "object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "requires": { + "ee-first": "1.1.1" + } + }, + "os-shim": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz", + "integrity": "sha512-jd0cvB8qQ5uVt0lvCIexBaROw1KyKm5sbulg2fWOHjETisuCzWyt+eTZKEMs8v6HwzoGs8xik26jg7eCM6pS+A==", + "dev": true + }, + "parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "requires": { + "entities": "^4.4.0" + } + }, + "parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "requires": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + }, + "plimit-lit": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.5.0.tgz", + "integrity": "sha512-Eb/MqCb1Iv/ok4m1FqIXqvUKPISufcjZ605hl3KM/n8GaX8zfhtgdLwZU3vKjuHGh2O9Rjog/bHTq8ofIShdng==", + "requires": { + "queue-lit": "^1.5.0" + } + }, + "pre-commit": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/pre-commit/-/pre-commit-1.2.2.tgz", + "integrity": "sha512-qokTiqxD6GjODy5ETAIgzsRgnBWWQHQH2ghy86PU7mIn/wuWeTwF3otyNQZxWBwVn8XNr8Tdzj/QfUXpH+gRZA==", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "spawn-sync": "^1.0.15", + "which": "1.2.x" + } + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true + }, + "pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "queue-lit": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.0.tgz", + "integrity": "sha512-IslToJ4eiCEE9xwMzq3viOO5nH8sUWUCwoElrhNMozzr9IIt2qqvB4I+uHu/zJTQVqc9R5DFwok4ijNK1pU3fA==" + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - } - } - }, - "ext": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", - "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "requires": { - "type": "^2.7.2" - }, - "dependencies": { - "type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" - } - } - }, - "fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "requires": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "requires": { - "fetch-blob": "^3.1.2" - } - }, - "forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" - }, - "htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-promise": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "lru-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", - "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==", - "requires": { - "es5-ext": "~0.10.2" - } - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" - }, - "memoizee": { - "version": "0.4.15", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz", - "integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==", - "requires": { - "d": "^1.0.1", - "es5-ext": "^0.10.53", - "es6-weak-map": "^2.0.3", - "event-emitter": "^0.3.5", - "is-promise": "^2.2.2", - "lru-queue": "^0.1.0", - "next-tick": "^1.1.0", - "timers-ext": "^0.1.7" - } - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "module-alias": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz", - "integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==" - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" - }, - "next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" - }, - "node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==" - }, - "node-fetch": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz", - "integrity": "sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==", - "requires": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - } - }, - "nodemon": { - "version": "2.0.22", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", - "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==", - "dev": true, - "requires": { - "chokidar": "^3.5.2", - "debug": "^3.2.7", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^5.7.1", - "simple-update-notifier": "^1.0.7", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - } - }, - "nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", - "dev": true, - "requires": { - "abbrev": "1" - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "requires": { - "boolbase": "^1.0.0" - } - }, - "object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "requires": { - "ee-first": "1.1.1" - } - }, - "os-shim": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz", - "integrity": "sha512-jd0cvB8qQ5uVt0lvCIexBaROw1KyKm5sbulg2fWOHjETisuCzWyt+eTZKEMs8v6HwzoGs8xik26jg7eCM6pS+A==", - "dev": true - }, - "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" - }, - "parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", - "requires": { - "parse5": "^6.0.1" - } - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pre-commit": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/pre-commit/-/pre-commit-1.2.2.tgz", - "integrity": "sha512-qokTiqxD6GjODy5ETAIgzsRgnBWWQHQH2ghy86PU7mIn/wuWeTwF3otyNQZxWBwVn8XNr8Tdzj/QfUXpH+gRZA==", - "dev": true, - "requires": { - "cross-spawn": "^5.0.1", - "spawn-sync": "^1.0.15", - "which": "1.2.x" - } - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "requires": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - } - }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", - "dev": true - }, - "pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true - }, - "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "requires": { - "side-channel": "^1.0.4" - } - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" - }, - "raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" } - } - } - } - }, - "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - } - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "simple-update-notifier": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", - "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", - "dev": true, - "requires": { - "semver": "~7.0.0" - }, - "dependencies": { - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true - } - } - }, - "spawn-sync": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz", - "integrity": "sha512-9DWBgrgYZzNghseho0JOuh+5fg9u6QWhAWa51QC7+U5rCheZ/j1DrEZnyE0RBBRqZ9uEXGPgSSM0nky6burpVw==", - "dev": true, - "requires": { - "concat-stream": "^1.4.7", - "os-shim": "^0.1.2" - } - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - }, - "dependencies": { + }, + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "requires": { + "picomatch": "^2.2.1" + } + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "requires": { + "queue-microtask": "^1.2.2" + } + }, "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + } + } + }, + "serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "simple-update-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "dev": true, + "requires": { + "semver": "~7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true + } + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" + }, + "spawn-sync": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz", + "integrity": "sha512-9DWBgrgYZzNghseho0JOuh+5fg9u6QWhAWa51QC7+U5rCheZ/j1DrEZnyE0RBBRqZ9uEXGPgSSM0nky6burpVw==", + "dev": true, + "requires": { + "concat-stream": "^1.4.7", + "os-shim": "^0.1.2" + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "timers-ext": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", + "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "requires": { + "es5-ext": "~0.10.46", + "next-tick": "1" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "requires": { + "nopt": "~1.0.10" + } + }, + "ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + } + }, + "tsc-alias": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.6.tgz", + "integrity": "sha512-vq+i6VpE83IeMsSJVcFN03ZBofADhr8/gIJXjxpbnTRfN/MFXy0+SBaKG2o7p95QqXBGkeG98HYz3IkOOveFbg==", + "requires": { + "chokidar": "^3.5.3", + "commander": "^9.0.0", + "globby": "^11.0.4", + "mylas": "^2.1.9", + "normalize-path": "^3.0.0", + "plimit-lit": "^1.2.6" + } + }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "dev": true + }, + "typescript": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", + "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==" + }, + "undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" + }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + }, + "web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==" + }, + "which": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", + "integrity": "sha512-16uPglFkRPzgiUXYMi1Jf8Z5EzN1iB4V0ZtMXcHZnwsBtQhhHeCqoWw7tsUY42hJGNDWtUsVLTjakIa5BgAxCw==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" } - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "timers-ext": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", - "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", - "requires": { - "es5-ext": "~0.10.46", - "next-tick": "1" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" - }, - "touch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", - "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", - "dev": true, - "requires": { - "nopt": "~1.0.10" - } - }, - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" - }, - "type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "dev": true - }, - "undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" - }, - "web-streams-polyfill": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", - "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==" - }, - "which": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", - "integrity": "sha512-16uPglFkRPzgiUXYMi1Jf8Z5EzN1iB4V0ZtMXcHZnwsBtQhhHeCqoWw7tsUY42hJGNDWtUsVLTjakIa5BgAxCw==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", - "dev": true } - } } diff --git a/package.json b/package.json index d945661..50110ef 100644 --- a/package.json +++ b/package.json @@ -1,36 +1,42 @@ { - "name": "lnreader-plugins", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "start": "node ./index.js", - "test": "exit 0", - "json": "node ./scripts/json_plugins.js && git add dist" - }, - "author": "", - "license": "MIT", - "dependencies": { - "body-parser": "^1.20.2", - "cheerio": "^1.0.0-rc.10", - "cli-color": "^2.0.3", - "dayjs": "^1.11.4", - "express": "^4.18.2", - "module-alias": "^2.2.2", - "node-fetch": "^3.3.1" - }, - "_moduleAliases": { - "@api": "./api", - "@libs": "./libs", - "@icons": "./icons", - "@plugins": "./plugins", - "@scripts": "./scripts" - }, - "devDependencies": { - "nodemon": "^2.0.22", - "pre-commit": "^1.2.2" - }, - "pre-commit": [ - "json" - ] + "name": "lnreader-plugins", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "start": "nodemon", + "test": "exit 0", + "json": "tsc && node ./js/scripts/json_plugins.js && git add plugins && git add dist && git add js/plugins", + "generate": "ts-node ./scripts/multisrc/generate.ts" + }, + "author": "", + "license": "MIT", + "dependencies": { + "body-parser": "^1.20.2", + "cheerio": "^1.0.0-rc.10", + "cli-color": "^2.0.3", + "dayjs": "^1.11.4", + "express": "^4.18.2", + "module-alias": "^2.2.2", + "node-fetch": "^3", + "ts-node": "^10.9.1", + "tsc-alias": "^1.8.6", + "typescript": "^5.1.3" + }, + "_moduleAliases": { + "@libs": "./js/libs", + "@icons": "./js/icons", + "@plugins": "./js/plugins", + "@scripts": "./js/scripts", + "@typings": "./js/types" + }, + "devDependencies": { + "@types/express": "^4.17.17", + "@types/node": "^20.3.1", + "nodemon": "^2.0.22", + "pre-commit": "^1.2.2" + }, + "pre-commit": [ + "json" + ] } diff --git a/plugins/arabic/ArMTL[madara].ts b/plugins/arabic/ArMTL[madara].ts new file mode 100644 index 0000000..e52c67c --- /dev/null +++ b/plugins/arabic/ArMTL[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "ar-mtl"; +export const name = "ArMTL [madara]"; +export const icon = "multisrc/madara/icons/armtl.png"; +export const version = "1.0.0"; +export const site = "https://ar-mtl.club/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novel" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (true) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/arabic/ArNovel[madara].ts b/plugins/arabic/ArNovel[madara].ts new file mode 100644 index 0000000..582c1ba --- /dev/null +++ b/plugins/arabic/ArNovel[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "arnovel"; +export const name = "ArNovel [madara]"; +export const icon = "multisrc/madara/icons/arnovel.png"; +export const version = "1.0.0"; +export const site = "https://arnovel.me/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novel" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (true) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/arabic/HizoManga[madara].ts b/plugins/arabic/HizoManga[madara].ts new file mode 100644 index 0000000..d5c9ac6 --- /dev/null +++ b/plugins/arabic/HizoManga[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "hizomanga"; +export const name = "HizoManga [madara]"; +export const icon = "multisrc/madara/icons/hizomanga.png"; +export const version = "1.0.0"; +export const site = "https://hizomanga.com/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "serie" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (true) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "serie" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/arabic/Novel4Up[madara].ts b/plugins/arabic/Novel4Up[madara].ts new file mode 100644 index 0000000..6c1743d --- /dev/null +++ b/plugins/arabic/Novel4Up[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "novel4up"; +export const name = "Novel4Up [madara]"; +export const icon = "multisrc/madara/icons/novel4up.png"; +export const version = "1.0.0"; +export const site = "https://novel4up.com/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novel" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (false) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/arabic/Riwyat[madara].ts b/plugins/arabic/Riwyat[madara].ts new file mode 100644 index 0000000..f67e012 --- /dev/null +++ b/plugins/arabic/Riwyat[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "riwyat"; +export const name = "Riwyat [madara]"; +export const icon = "multisrc/madara/icons/riwyat.png"; +export const version = "1.0.0"; +export const site = "https://riwyat.com/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novel" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (true) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/arabic/TeamXNovel[madara].ts b/plugins/arabic/TeamXNovel[madara].ts new file mode 100644 index 0000000..3957279 --- /dev/null +++ b/plugins/arabic/TeamXNovel[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "teamxnovel"; +export const name = "TeamXNovel [madara]"; +export const icon = "multisrc/madara/icons/teamxnovel.png"; +export const version = "1.0.0"; +export const site = "https://teamxnovel.com/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novel" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (true) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/arabic/kolnovel.ts b/plugins/arabic/kolnovel.ts new file mode 100644 index 0000000..66c0684 --- /dev/null +++ b/plugins/arabic/kolnovel.ts @@ -0,0 +1,323 @@ +import { load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { FilterInputs } from "@libs/filterInputs"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "kolnovel"; +export const name = "KolNovel"; +export const version = "1.0.0"; +export const icon = "multisrc/wpmangastream/icons/kolnovel.png"; +export const site = "https://kolnovel.com/"; +module.exports.protected = false; + +const pluginId = id; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function ( + page, + { filters } +) { + let link = `${baseUrl}series/?page=${page}`; + + if (filters) { + if (Array.isArray(filters.genres) && filters.genres.length) { + link += filters.genres.map((i) => `&genre[]=${i}`).join(""); + } + + if (Array.isArray(filters.type) && filters.type.length) + link += filters.type.map((i) => `&lang[]=${i}`).join(""); + } + link += "&status=" + (filters?.status ? filters.status : ""); + + link += "&order=" + (filters?.order ? filters.order : "popular"); + + const body = await fetchApi(link, {}, pluginId).then((result) => + result.text() + ); + + const loadedCheerio = parseHTML(body); + + const novels: Novel.Item[] = []; + + loadedCheerio("article.maindet").each(function () { + const novelName = loadedCheerio(this).find("h2").text(); + let image = loadedCheerio(this).find("img"); + const novelCover = image.attr("data-src") || image.attr("src"); + const novelUrl = loadedCheerio(this).find("h2 a").attr("href"); + + if (!novelUrl) return; + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function parseNovelAndChapters(novelUrl) { + const url = novelUrl; + + const result = await fetchApi(url, {}, pluginId); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + const novel: Novel.instance = { + url, + chapters: [], + }; + + novel.name = loadedCheerio("h1.entry-title").text(); + + novel.cover = + loadedCheerio("img.wp-post-image").attr("data-src") || + loadedCheerio("img.wp-post-image").attr("src"); + + loadedCheerio("div.serl:nth-child(3) > span").each(function () { + const detailName = loadedCheerio(this).text().trim(); + const detail = loadedCheerio(this).next().text().trim(); + + switch (detailName) { + case "الكاتب": + case "Author": + novel.author = detail; + break; + } + }); + + novel.status = loadedCheerio("div.sertostat > span").attr("class"); + + novel.genres = loadedCheerio(".sertogenre") + .children("a") + .map((i, el) => loadedCheerio(el).text()) + .toArray() + .join(","); + + novel.summary = loadedCheerio(".sersys") + .find("br") + .replaceWith("\n") + .end() + .text(); + + let chapter: Chapter.Item[] = []; + + loadedCheerio(".eplister") + .find("li") + .each(function () { + const chapterName = + loadedCheerio(this).find(".epl-num").text() + + " - " + + loadedCheerio(this).find(".epl-title").text(); + + const releaseDate = loadedCheerio(this) + .find(".epl-date") + .text() + .trim(); + + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + + if (!chapterUrl) return; + + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + + novel.chapters = chapter.reverse(); + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function parseChapter( + chapterUrl +) { + const result = await fetchApi(chapterUrl, {}, pluginId); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + let ignore = loadedCheerio(".epcontent > p").next().attr("class"); + loadedCheerio(`p.${ignore}`).remove(); + let chapterText = loadedCheerio(".epcontent").html(); + + return chapterText; +}; +export const searchNovels: Plugin.searchNovels = async function searchNovels( + searchTerm +) { + const url = `${baseUrl}?s=${searchTerm}`; + + const result = await fetchApi(url, {}, pluginId); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + const novels: Novel.Item[] = []; + + loadedCheerio("article.maindet").each(function () { + const novelName = loadedCheerio(this).find("h2").text(); + let image = loadedCheerio(this).find("img"); + const novelCover = image.attr("data-src") || image.attr("src"); + const novelUrl = loadedCheerio(this).find("h2 a").attr("href"); + + if (!novelUrl) return; + + novels.push({ + name: novelName, + url: novelUrl, + cover: novelCover, + }); + }); + + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async function fetchImage(url) { + const headers = { + Referer: baseUrl, + }; + return await fetchFile(url, { headers: headers }); +}; + +export const filters = [ + { + key: "order", + label: "ترتيب حسب", + values: [ + { label: "الإعداد الأولي", value: "" }, + + { label: "A-Z", value: "title" }, + + { label: "Z-A", value: "titlereverse" }, + + { label: "أخر التحديثات", value: "update" }, + + { label: "أخر ما تم إضافته", value: "latest" }, + + { label: "الرائجة", value: "popular" }, + ], + inputType: FilterInputs.Picker, + }, + { + key: "status", + label: "الحالة", + values: [ + { label: "All", value: "" }, + + { label: "Ongoing", value: "ongoing" }, + + { label: "Hiatus", value: "hiatus" }, + + { label: "Completed", value: "completed" }, + ], + inputType: FilterInputs.Picker, + }, + { + key: "type", + label: "النوع", + values: [ + { label: "إنجليزية", value: "english" }, + + { label: "روايةلايت", value: "light-novel" }, + + { label: "روايةويب", value: "web-novel" }, + + { label: "صينية", value: "chinese" }, + + { label: "عربية", value: "arabic" }, + + { label: "كورية", value: "korean" }, + + { label: "يابانية", value: "japanese" }, + ], + inputType: FilterInputs.Checkbox, + }, + { + key: "genres", + label: "تصنيف", + values: [ + { label: "Wuxia", value: "wuxia" }, + + { label: "Xianxia", value: "xianxia" }, + + { label: "XUANHUAN", value: "xuanhuan" }, + + { label: "أكشن", value: "action" }, + + { label: "إثارة", value: "excitement" }, + + { label: "إنتقالالىعالمأخر", value: "isekai" }, + + { label: "إيتشي", value: "etchi" }, + + { label: "الخيالالعلمي", value: "sci-fi" }, + + { label: "بوليسي", value: "policy" }, + + { label: "تاريخي", value: "historical" }, + + { label: "تحقيقات", value: "%d8%aa%d8%ad%d9%82%d9%8a%d9%82" }, + + { label: "تقمصشخصيات", value: "rpg" }, + + { label: "جريمة", value: "crime" }, + + { label: "جوسى", value: "josei" }, + + { label: "حريم", value: "harem" }, + + { label: "حياةمدرسية", value: "school-life" }, + + { label: "خيالي(فانتازيا)", value: "fantasy" }, + + { label: "دراما", value: "drama" }, + + { label: "رعب", value: "horror" }, + + { label: "رومانسي", value: "romantic" }, + + { label: "سحر", value: "magic" }, + + { label: "سينن", value: "senen" }, + + { label: "شريحةمنالحياة", value: "slice-of-life" }, + + { label: "شوجو", value: "shojo" }, + + { label: "شونين", value: "shonen" }, + + { label: "طبي", value: "medical" }, + + { label: "ظواهرخارقةللطبيعة", value: "supernatural" }, + + { label: "غموض", value: "mysteries" }, + + { label: "فنونالقتال", value: "martial-arts" }, + + { label: "قوىخارقة", value: "superpower" }, + + { label: "كوميدي", value: "comedy" }, + + { label: "مأساوي", value: "tragedy" }, + + { label: "مابعدالكارثة", value: "after-the-disaster" }, + + { label: "مغامرة", value: "adventure" }, + + { label: "ميكا", value: "mechanical" }, + + { label: "ناضج", value: "mature" }, + + { label: "نفسي", value: "psychological" }, + ], + inputType: FilterInputs.Checkbox, + }, +]; diff --git a/plugins/english/BoxNovel[madara].ts b/plugins/english/BoxNovel[madara].ts new file mode 100644 index 0000000..e7c4435 --- /dev/null +++ b/plugins/english/BoxNovel[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "boxnovel"; +export const name = "BoxNovel [madara]"; +export const icon = "multisrc/madara/icons/boxnovel.png"; +export const version = "1.0.0"; +export const site = "https://boxnovel.com/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novel" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (true) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/ClickNovel[madara].ts b/plugins/english/ClickNovel[madara].ts new file mode 100644 index 0000000..6a6bf50 --- /dev/null +++ b/plugins/english/ClickNovel[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "clicknovel"; +export const name = "ClickNovel [madara]"; +export const icon = "multisrc/madara/icons/clicknovel.png"; +export const version = "1.0.0"; +export const site = "https://clicknovel.net/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novel" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (false) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/CrateNovel[madara].ts b/plugins/english/CrateNovel[madara].ts new file mode 100644 index 0000000..2719393 --- /dev/null +++ b/plugins/english/CrateNovel[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "cratenovel"; +export const name = "CrateNovel [madara]"; +export const icon = "multisrc/madara/icons/cratenovel.png"; +export const version = "1.0.0"; +export const site = "https://cratenovel.com/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novel" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (true) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/DaoNovel[madara].ts b/plugins/english/DaoNovel[madara].ts new file mode 100644 index 0000000..d11d00a --- /dev/null +++ b/plugins/english/DaoNovel[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "daonovel"; +export const name = "DaoNovel [madara]"; +export const icon = "multisrc/madara/icons/daonovel.png"; +export const version = "1.0.0"; +export const site = "https://daonovel.com/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novels-list" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (true) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "novels" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/FansTranslations[madara].ts b/plugins/english/FansTranslations[madara].ts new file mode 100644 index 0000000..177f7f4 --- /dev/null +++ b/plugins/english/FansTranslations[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "fanTLS"; +export const name = "Fans Translations [madara]"; +export const icon = "multisrc/madara/icons/fanstranslations.png"; +export const version = "1.0.0"; +export const site = "https://fanstranslations.com/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novel" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (true) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/FirstKissNovel[madara].ts b/plugins/english/FirstKissNovel[madara].ts new file mode 100644 index 0000000..5b2aedb --- /dev/null +++ b/plugins/english/FirstKissNovel[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "1stkissnovel"; +export const name = "FirstKissNovel [madara]"; +export const icon = "multisrc/madara/icons/firstkissnovel.png"; +export const version = "1.0.0"; +export const site = "https://1stkissnovel.org/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novel" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (true) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/FreeNovelMe[madara].ts b/plugins/english/FreeNovelMe[madara].ts new file mode 100644 index 0000000..9c8a51d --- /dev/null +++ b/plugins/english/FreeNovelMe[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "freenovel.me"; +export const name = "FreeNovelMe [madara]"; +export const icon = "multisrc/madara/icons/freenovelme.png"; +export const version = "1.0.0"; +export const site = "https://freenovel.me/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novel" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (false) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/Guavaread[madara].ts b/plugins/english/Guavaread[madara].ts new file mode 100644 index 0000000..13d0000 --- /dev/null +++ b/plugins/english/Guavaread[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "guavaread"; +export const name = "Guavaread [madara]"; +export const icon = "multisrc/madara/icons/guavaread.png"; +export const version = "1.0.0"; +export const site = "https://guavaread.com/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novel" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (true) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/LightNovelHeaven[madara].ts b/plugins/english/LightNovelHeaven[madara].ts new file mode 100644 index 0000000..2f79a4c --- /dev/null +++ b/plugins/english/LightNovelHeaven[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "lnheaven"; +export const name = "LightNovelHeaven [madara]"; +export const icon = "multisrc/madara/icons/lightnovelheaven.png"; +export const version = "1.0.0"; +export const site = "https://lightnovelheaven.com/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "series" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (true) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "series" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/LightNovelReader.broken.ts b/plugins/english/LightNovelReader.broken.ts new file mode 100644 index 0000000..4b10b95 --- /dev/null +++ b/plugins/english/LightNovelReader.broken.ts @@ -0,0 +1,168 @@ +import { load as parseHTML } from "cheerio"; +import { isUrlAbsolute } from "@libs/isAbsoluteUrl"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "LNR.org"; +export const name = "LightNovelReader"; +export const icon = "src/en/lightnovelreader/icon.png"; +export const version = "1.0.0"; +export const site = "https://lightnovelreader.org"; +exports.protected = false; + +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function (page) { + const url = baseUrl + "/ranking/top-rated/" + page; + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + const novels: Novel.Item[] = []; + + loadedCheerio(".category-items.ranking-category.cm-list > ul > li").each( + function () { + let novelUrl = loadedCheerio(this).find("a").attr("href"); + + if (novelUrl && !isUrlAbsolute(novelUrl)) { + novelUrl = baseUrl + novelUrl; + } + + if (novelUrl) { + const novelName = loadedCheerio(this) + .find(".category-name a") + .text() + .trim(); + let novelCover = loadedCheerio(this) + .find(".category-img img") + .attr("src"); + + if (novelCover && !isUrlAbsolute(novelCover)) { + novelCover = baseUrl + novelCover; + } + + const novel = { + url: novelUrl, + name: novelName, + cover: novelCover, + }; + + novels.push(novel); + } + } + ); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + const novel: Novel.instance = { + url: novelUrl, + chapters: [], + }; + + novel.name = loadedCheerio(".section-header-title > h2").text(); + + let novelCover = loadedCheerio(".novels-detail img").attr("src"); + + novel.cover = novelCover + ? isUrlAbsolute(novelCover) + ? novelCover + : baseUrl + novelCover + : undefined; + + novel.summary = loadedCheerio( + "div.container > div > div.col-12.col-xl-9 > div > div:nth-child(5) > div" + ) + .text() + .trim(); + + novel.author = loadedCheerio( + "div.novels-detail-right > ul > li:nth-child(6) > .novels-detail-right-in-right > a" + ) + .text() + .trim(); + + novel.genres = loadedCheerio( + "body > section:nth-child(4) > div > div > div.col-12.col-xl-9 > div > div:nth-child(2) > div > div.novels-detail-right > ul > li:nth-child(3) > div.novels-detail-right-in-right" + ) + .text() + .trim(); + + novel.status = loadedCheerio( + "div.novels-detail-right > ul > li:nth-child(2) > .novels-detail-right-in-right" + ) + .text() + .trim(); + + loadedCheerio(".cm-tabs-content > ul > li").each(function () { + let chapterUrl = loadedCheerio(this).find("a").attr("href"); + + if (chapterUrl && !isUrlAbsolute(chapterUrl)) { + chapterUrl = baseUrl + chapterUrl; + } + + if (chapterUrl) { + const chapterName = loadedCheerio(this).find("a").text().trim(); + const releaseDate = null; + + const chapter: Chapter.Item = { + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }; + + novel.chapters?.push(chapter); + } + }); + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const url = chapterUrl; + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + const chapterText = loadedCheerio("#chapterText").html() || ""; + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = + baseUrl + `/search/autocomplete?dataType=json&query=${searchTerm}`; + const result = await fetchApi(url); + const body = await result.json(); + const data = body?.results || []; + + const novels: Novel.Item[] = []; + + data.forEach((item) => { + let novelUrl = item.link; + let novelName = item.original_title; + let novelCover = item.image; + + novels.push({ + url: novelUrl, + name: novelName, + cover: novelCover, + }); + }); + + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async function (url) { + return await fetchFile(url, {}); +}; diff --git a/plugins/english/LightNovelReader.js b/plugins/english/LightNovelReader.js deleted file mode 100644 index 91e4126..0000000 --- a/plugins/english/LightNovelReader.js +++ /dev/null @@ -1,178 +0,0 @@ -const cheerio = require('cheerio'); -const languages = require('@libs/languages'); -const isUrlAbsolute = require('@libs/isAbsoluteUrl'); -const fetchApi = require('@libs/fetchApi'); -const fetchFile = require('@libs/fetchFile'); - -const pluginId = 'LNR.org'; -const baseUrl = 'https://lightnovelreader.org'; - -async function popularNovels(page) { - const url = baseUrl + '/ranking/top-rated/' + page; - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - const novels = []; - - loadedCheerio('.category-items.ranking-category.cm-list > ul > li').each( - function () { - let novelUrl = loadedCheerio(this).find('a').attr('href'); - - if (novelUrl && !isUrlAbsolute(novelUrl)) { - novelUrl = baseUrl + novelUrl; - } - - if (novelUrl) { - const novelName = loadedCheerio(this) - .find('.category-name a') - .text() - .trim(); - let novelCover = loadedCheerio(this) - .find('.category-img img') - .attr('src'); - - if (novelCover && !isUrlAbsolute(novelCover)) { - novelCover = baseUrl + novelCover; - } - - const novel = { - url: novelUrl, - name: novelName, - cover: novelCover, - }; - - novels.push(novel); - } - }, - ); - - return novels; -}; - -async function parseNovelAndChapters (novelUrl) { - const url = novelUrl; - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - const novel = { - url: novelUrl, - chapters: [], - }; - - novel.name = loadedCheerio('.section-header-title > h2').text(); - - let novelCover = loadedCheerio('.novels-detail img').attr('src'); - - novel.cover = novelCover - ? isUrlAbsolute(novelCover) - ? novelCover - : baseUrl + novelCover - : undefined; - - novel.summary = loadedCheerio( - 'div.container > div > div.col-12.col-xl-9 > div > div:nth-child(5) > div', - ) - .text() - .trim(); - - novel.author = loadedCheerio( - 'div.novels-detail-right > ul > li:nth-child(6) > .novels-detail-right-in-right > a', - ) - .text() - .trim(); - - novel.genres = loadedCheerio( - 'body > section:nth-child(4) > div > div > div.col-12.col-xl-9 > div > div:nth-child(2) > div > div.novels-detail-right > ul > li:nth-child(3) > div.novels-detail-right-in-right', - ) - .text() - .trim() - - novel.status = loadedCheerio( - 'div.novels-detail-right > ul > li:nth-child(2) > .novels-detail-right-in-right', - ) - .text() - .trim(); - - loadedCheerio('.cm-tabs-content > ul > li').each(function () { - let chapterUrl = loadedCheerio(this).find('a').attr('href'); - - if (chapterUrl && !isUrlAbsolute(chapterUrl)) { - chapterUrl = baseUrl + chapterUrl; - } - - if (chapterUrl) { - const chapterName = loadedCheerio(this).find('a').text().trim(); - const releaseDate = null; - - const chapter = { - name: chapterName, - releaseTime: releaseDate, - url: chapterUrl, - }; - - novel.chapters.push(chapter); - } - }); - - return novel; -}; - -async function parseChapter(chapterUrl) { - const url = chapterUrl; - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - const chapterText = loadedCheerio('#chapterText').html() || ''; - - return chapterText; -}; - - -async function searchNovels (searchTerm) { - const url = baseUrl + `/search/autocomplete?dataType=json&query=${searchTerm}`; - const result = await fetchApi(url); - const body = await result.json(); - const data = body.results || []; - - const novels = []; - - data.forEach(item => { - let novelUrl = item.link; - let novelName = item.original_title - let novelCover = item.image; - - novels.push({ - url: novelUrl, - name: novelName, - cover: novelCover, - }); - }); - - return novels; -}; - -async function fetchImage (url) { - return await fetchFile(url, {}); -} - -module.exports = { - id: pluginId, - name: 'LightNovelReader', - icon: 'src/en/lightnovelreader/icon.png', - version: '1.0.0', - site: baseUrl, - lang: languages.English, - description: 'This is description for plugin', - protected: false, - fetchImage, - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, -} \ No newline at end of file diff --git a/plugins/english/Ltnovelcom[readwn].ts b/plugins/english/Ltnovelcom[readwn].ts new file mode 100644 index 0000000..2eff0d7 --- /dev/null +++ b/plugins/english/Ltnovelcom[readwn].ts @@ -0,0 +1,198 @@ + + import { load as parseHTML } from "cheerio"; + // import dayjs from 'dayjs'; + import { fetchFile, fetchApi } from "@libs/fetch"; + import { Novel, Plugin, Chapter } from "@typings/plugin"; + // import { parseMadaraDate } from "@libs/parseMadaraDate"; + // import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; + // import { showToast } from "@libs/showToast"; + // import { Filter, FilterInputs } from "@libs/filterInputs"; + // import { NovelStatus } from '@libs/novelStatus'; + // import { defaultCover } from "@libs/defaultCover"; + + + export const id = "Ltnovel.com"; + export const name = "Ltnovel.com"; + export const icon = "multisrc/readwn/icon/ltnovel.png"; + export const version = "1.0.0"; + export const site = "https://www.ltnovel.com/"; + // export const filters: Filter[] = []; + exports["protected"] = false; + + const baseUrl = site; + + export const popularNovels: Plugin.popularNovels = async function ( + page, + { filters, showLatestNovels } + ) { + const novels: Novel.Item[] = []; + const pageNo = page - 1; + + const url = baseUrl + 'list/all/all-onclick-' + pageNo +'.html'; + + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('li.novel-item').each(function () { + const novelName = loadedCheerio(this).find('h4').text(); + const novelUrl = baseUrl + loadedCheerio(this).find('a').attr('href'); + + const coverUri = loadedCheerio(this) + .find('.novel-cover > img') + .attr('data-src'); + + const novelCover = baseUrl + coverUri; + + const novel = { name: novelName, cover: novelCover, url: novelUrl }; + + novels.push(novel); + }); + + return novels; + }; + + + export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const result = await fetchApi(novelUrl); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + const novel: Novel.instance = { + url: novelUrl, + chapters: [], + }; + + novel.name = loadedCheerio('h1.novel-title').text(); + + const coverUri = loadedCheerio('figure.cover > img').attr('data-src'); + novel.cover = baseUrl + coverUri; + + novel.summary = loadedCheerio('.summary') + .text() + .replace('Summary', '') + .trim(); + + novel.genres = ''; + + loadedCheerio('div.categories > ul > li').each(function () { + novel.genres += loadedCheerio(this).text().trim() + ','; + }); + + loadedCheerio('div.header-stats > span').each(function () { + if (loadedCheerio(this).find('small').text() === 'Status') { + novel.status = loadedCheerio(this).find('strong').text(); + } + }); + + novel.genres = novel.genres.slice(0, -1); + + novel.author = loadedCheerio('span[itemprop=author]').text(); + + let novelChapters: Chapter.Item[] = []; + + const novelId = novelUrl.replace('.html', '').replace(baseUrl, ''); + + const latestChapterNo = parseInt(loadedCheerio('.header-stats') + .find('span > strong') + .first() + .text() + .trim()); + + let lastChapterNo = 1; + loadedCheerio('.chapter-list li').each(function () { + const chapterName = loadedCheerio(this) + .find('a .chapter-title') + .text() + .trim(); + + const chapterUrl = baseUrl + loadedCheerio(this).find('a').attr('href')?.trim(); + + const releaseDate = loadedCheerio(this) + .find('a .chapter-update') + .text() + .trim(); + + lastChapterNo = parseInt(loadedCheerio(this).find('a .chapter-no').text().trim()); + + const chapter = { name: chapterName, releaseTime: releaseDate, url: chapterUrl }; + + novelChapters.push(chapter); + }); + + // Itterate once more before loop to finish off + lastChapterNo++; + for (let i = lastChapterNo; i <= latestChapterNo; i++) { + const chapterName = 'Chapter ' + i; + const chapterUrl = baseUrl + novelId+'_'+ i +'.html'; + const releaseDate = null; + + const chapter = { name: chapterName, releaseTime: releaseDate,url: chapterUrl }; + + novelChapters.push(chapter); + } + + novel.chapters = novelChapters; + return novel; + }; + + export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi(chapterUrl); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + const chapterText = loadedCheerio('.chapter-content').html(); + return chapterText; + }; + + export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const searchUrl = baseUrl + 'e/search/index.php'; + + const result = await fetchApi(searchUrl, { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Referer: baseUrl + 'search.html', + Origin: baseUrl, + 'user-agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36', + }, + method: 'POST', + body: JSON.stringify({ + show: 'title', + tempid: 1, + tbname: 'news', + keyboard: searchTerm, + }), + }); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + loadedCheerio('li.novel-item').each(function () { + const novelName = loadedCheerio(this).find('h4').text(); + const novelUrl = baseUrl + loadedCheerio(this).find('a').attr('href'); + + const coverUri = baseUrl + loadedCheerio(this).find('img').attr('data-src'); + const novelCover = baseUrl + coverUri; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; + }; + + export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url, {}); + }; + + \ No newline at end of file diff --git a/plugins/english/LunarLetters[madara].ts b/plugins/english/LunarLetters[madara].ts new file mode 100644 index 0000000..653b358 --- /dev/null +++ b/plugins/english/LunarLetters[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "lunarletters"; +export const name = "LunarLetters [madara]"; +export const icon = "multisrc/madara/icons/lunarletters.png"; +export const version = "1.0.0"; +export const site = "https://www.lunarletters.com/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "series" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (true) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "series" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/MTLNovelClub[madara].ts b/plugins/english/MTLNovelClub[madara].ts new file mode 100644 index 0000000..bd23795 --- /dev/null +++ b/plugins/english/MTLNovelClub[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "mtlnovel.club"; +export const name = "MTLNovel.Club [madara]"; +export const icon = "multisrc/madara/icons/mtlnovel.club.png"; +export const version = "1.0.0"; +export const site = "https://mtlnovel.club/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novel" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (true) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/MTLNovel[madara].ts b/plugins/english/MTLNovel[madara].ts new file mode 100644 index 0000000..15e24cd --- /dev/null +++ b/plugins/english/MTLNovel[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "mtl-novel"; +export const name = "MTL-Novel [madara]"; +export const icon = "multisrc/madara/icons/mtl-novel.png"; +export const version = "1.0.0"; +export const site = "https://mtl-novel.com/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novel" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (true) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/MostNovel[madara].ts b/plugins/english/MostNovel[madara].ts new file mode 100644 index 0000000..d8bd956 --- /dev/null +++ b/plugins/english/MostNovel[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "mostnovel"; +export const name = "MostNovel [madara]"; +export const icon = "multisrc/madara/icons/mostnovel.png"; +export const version = "1.0.0"; +export const site = "https://mostnovel.com/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "manga" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (false) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "manga" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/MysticalSeries[madara].ts b/plugins/english/MysticalSeries[madara].ts new file mode 100644 index 0000000..4f30a57 --- /dev/null +++ b/plugins/english/MysticalSeries[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "mysticalmerries"; +export const name = "MysticalSeries [madara]"; +export const icon = "multisrc/madara/icons/mysticalseries.png"; +export const version = "1.0.0"; +export const site = "https://mysticalmerries.com/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "series" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (true) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "series" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/NeoSekaiTranslations[madara].ts b/plugins/english/NeoSekaiTranslations[madara].ts new file mode 100644 index 0000000..9460e6e --- /dev/null +++ b/plugins/english/NeoSekaiTranslations[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "neosekaiTLS"; +export const name = "NeoSekai Translations [madara]"; +export const icon = "multisrc/madara/icons/neosekaitranslations.png"; +export const version = "1.0.0"; +export const site = "https://www.neosekaitranslations.com/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novel" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (false) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/NocturneTranslations[madara].ts b/plugins/english/NocturneTranslations[madara].ts new file mode 100644 index 0000000..ef6ed3a --- /dev/null +++ b/plugins/english/NocturneTranslations[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "nocturneTLS"; +export const name = "Nocturne Translations [madara]"; +export const icon = "multisrc/madara/icons/nocturnetranslations.png"; +export const version = "1.0.0"; +export const site = "https://nocturnetls.net/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novels" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (false) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "novels" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/NoobchanTranslation[madara].ts b/plugins/english/NoobchanTranslation[madara].ts new file mode 100644 index 0000000..45b04b0 --- /dev/null +++ b/plugins/english/NoobchanTranslation[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "noobchanTL"; +export const name = "Noobchan Translation [madara]"; +export const icon = "multisrc/madara/icons/noobchantranslation.png"; +export const version = "1.0.0"; +export const site = "https://noobchan.xyz/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novel" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (true) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/NovelFullMe.js b/plugins/english/NovelFullMe.js deleted file mode 100644 index 018a10e..0000000 --- a/plugins/english/NovelFullMe.js +++ /dev/null @@ -1,167 +0,0 @@ -const cheerio = require('cheerio'); -const fetchApi = require('@libs/fetchApi'); -const languages = require('@libs/languages'); -const fetchFile = require('@libs/fetchFile'); - -const pluginId = 'NF.me'; -const sourceName = 'NovelFull.me'; - -const baseUrl = 'https://novelfull.me/'; - -async function popularNovels (page) { - const url = `${baseUrl}popular?page=${page}`; - - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('.book-item').each(function () { - const novelName = loadedCheerio(this).find('.title').text(); - const novelCover = - 'https:' + loadedCheerio(this).find('img').attr('data-src'); - const novelUrl = - baseUrl + loadedCheerio(this).find('.title a').attr('href').substring(1); - - const novel = { name: novelName, cover: novelCover, url: novelUrl }; - - novels.push(novel); - }); - - return novels; -}; - -async function parseNovelAndChapters (novelUrl) { - const url = novelUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novel = { - url, - name: '', - cover: '', - author: '', - status: '', - genres: '', - summary: '', - chapters: [], - }; - - novel.name = loadedCheerio('.name h1').text().trim(); - - novel.cover = - 'https:' + loadedCheerio('.img-cover img').attr('data-src'); - - novel.summary = loadedCheerio( - 'body > div.layout > div.main-container.book-details > div > div.row.no-gutters > div.col-lg-8 > div.mt-1 > div.section.box.mt-1.summary > div.section-body > p.content', - ) - .text() - .trim(); - - novel.author = 'Unknown'; - - novel.status = loadedCheerio( - 'body > div.layout > div.main-container.book-details > div > div.row.no-gutters > div.col-lg-8 > div.book-info > div.detail > div.meta.box.mt-1.p-10 > p:nth-child(1) > a > span', - ) - .text() - ?.trim(); - - novel.genres = loadedCheerio( - 'body > div.layout > div.main-container.book-details > div > div.row.no-gutters > div.col-lg-8 > div.book-info > div.detail > div.meta.box.mt-1.p-10 > p:nth-child(2)', - ) - .text() - ?.replace('Genres :', '') - .replace(/[\s\n]+/g, ' ') - .trim(); - - let chapters = []; - - const chaptersUrl = - novelUrl.replace(baseUrl, 'https://novelfull.me/api/novels/') + - '/chapters?source=detail'; - - const chaptersRequest = await fetchApi(chaptersUrl); - const chaptersHtml = await chaptersRequest.text(); - - loadedCheerio = cheerio.load(chaptersHtml); - - loadedCheerio('li').each(function () { - const chapterName = loadedCheerio(this) - .find('.chapter-title') - .text() - .trim(); - - const releaseDate = loadedCheerio(this) - .find('.chapter-update') - .text() - .trim(); - - const chapterUrl = loadedCheerio(this).find('a').attr('href'); - - chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); - }); - - novel.chapters = chapters.reverse(); - - return novel; -}; - -async function parseChapter (chapterUrl) { - const url = chapterUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - loadedCheerio('#listen-chapter').remove(); - loadedCheerio('#google_translate_element').remove(); - - const chapterText = loadedCheerio('.chapter__content').html(); - - return chapterText; -}; - -async function searchNovels (searchTerm) { - const url = `${baseUrl}search?status=all&sort=views&q=${searchTerm}`; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('.book-item').each(function () { - const novelName = loadedCheerio(this).find('.title').text(); - const novelCover = - 'https:' + loadedCheerio(this).find('img').attr('data-src'); - const novelUrl = - baseUrl + loadedCheerio(this).find('.title a').attr('href').substring(1); - - const novel = {name: novelName, cover: novelCover, url: novelUrl }; - - novels.push(novel); - }); - - return novels; -}; - -module.exports = { - id: pluginId, - name: sourceName, - site: baseUrl, - lang: languages.English, - version: '1.0.0', - icon: 'src/en/novelfullme/icon.png', - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; \ No newline at end of file diff --git a/plugins/english/NovelFullMe.ts b/plugins/english/NovelFullMe.ts new file mode 100644 index 0000000..a0025d0 --- /dev/null +++ b/plugins/english/NovelFullMe.ts @@ -0,0 +1,165 @@ +import { load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "NF.me"; +export const name = "NovelFull.me"; +export const site = "https://novelfull.me/"; +export const version = "1.0.0"; +export const icon = "src/en/novelfullme/icon.png"; + +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function (page) { + const url = `${baseUrl}popular?page=${page}`; + + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".book-item").each(function () { + const novelName = loadedCheerio(this).find(".title").text(); + const novelCover = + "https:" + loadedCheerio(this).find("img").attr("data-src"); + const novelUrl = + baseUrl + + loadedCheerio(this).find(".title a").attr("href")?.substring(1); + + const novel = { name: novelName, cover: novelCover, url: novelUrl }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + let novel: Novel.instance = { + url, + name: "", + cover: "", + author: "", + status: "", + genres: "", + summary: "", + chapters: [], + }; + + novel.name = loadedCheerio(".name h1").text().trim(); + + novel.cover = + "https:" + loadedCheerio(".img-cover img").attr("data-src"); + + novel.summary = loadedCheerio( + "body > div.layout > div.main-container.book-details > div > div.row.no-gutters > div.col-lg-8 > div.mt-1 > div.section.box.mt-1.summary > div.section-body > p.content" + ) + .text() + .trim(); + + novel.author = "Unknown"; + + novel.status = loadedCheerio( + "body > div.layout > div.main-container.book-details > div > div.row.no-gutters > div.col-lg-8 > div.book-info > div.detail > div.meta.box.mt-1.p-10 > p:nth-child(1) > a > span" + ) + .text() + ?.trim(); + + novel.genres = loadedCheerio( + "body > div.layout > div.main-container.book-details > div > div.row.no-gutters > div.col-lg-8 > div.book-info > div.detail > div.meta.box.mt-1.p-10 > p:nth-child(2)" + ) + .text() + ?.replace("Genres :", "") + .replace(/[\s\n]+/g, " ") + .trim(); + + let chapters: Chapter.Item[] = []; + + const chaptersUrl = + novelUrl.replace(baseUrl, "https://novelfull.me/api/novels/") + + "/chapters?source=detail"; + + const chaptersRequest = await fetchApi(chaptersUrl); + const chaptersHtml = await chaptersRequest.text(); + + loadedCheerio = parseHTML(chaptersHtml); + + loadedCheerio("li").each(function () { + const chapterName = loadedCheerio(this) + .find(".chapter-title") + .text() + .trim(); + + const releaseDate = loadedCheerio(this) + .find(".chapter-update") + .text() + .trim(); + + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + + if (!chapterUrl) return; + + chapters.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + + novel.chapters = chapters.reverse(); + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const url = chapterUrl; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + loadedCheerio("#listen-chapter").remove(); + loadedCheerio("#google_translate_element").remove(); + + const chapterText = loadedCheerio(".chapter__content").html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = `${baseUrl}search?status=all&sort=views&q=${searchTerm}`; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".book-item").each(function () { + const novelName = loadedCheerio(this).find(".title").text(); + const novelCover = + "https:" + loadedCheerio(this).find("img").attr("data-src"); + const novelUrl = + baseUrl + + loadedCheerio(this).find(".title a").attr("href")?.substring(1); + + const novel = { name: novelName, cover: novelCover, url: novelUrl }; + + novels.push(novel); + }); + + return novels; +}; +export const fetchImage: Plugin.fetchImage = fetchFile; diff --git a/plugins/english/NovelMultiverse[madara].ts b/plugins/english/NovelMultiverse[madara].ts new file mode 100644 index 0000000..237ca2e --- /dev/null +++ b/plugins/english/NovelMultiverse[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "novelmultiverse"; +export const name = "NovelMultiverse [madara]"; +export const icon = "multisrc/madara/icons/novelmultiverse.png"; +export const version = "1.0.0"; +export const site = "https://www.novelmultiverse.com/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novel" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (false) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/NovelOnline.js b/plugins/english/NovelOnline.js deleted file mode 100644 index 698ea01..0000000 --- a/plugins/english/NovelOnline.js +++ /dev/null @@ -1,120 +0,0 @@ -const cheerio = require('cheerio'); -const fetchApi = require('@libs/fetchApi'); -const fetchFile = require('@libs/fetchFile'); -const languages = require('@libs/languages'); - -const pluginId = 'NO.net'; -const sourceName = 'novelsOnline'; - -const baseUrl = 'https://novelsonline.net'; - -async function searchNovels (searchTerm) { - const result = await fetchApi( - 'https://novelsonline.net/sResults.php', - { - headers: { - 'Accept': '*/*', - 'Accept-Language': 'pl,en-US;q=0.7,en;q=0.3', - 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', - }, - method: 'POST', - body: 'q=' + encodeURIComponent(searchTerm), - }, - pluginId - ).then(res => res.text()); - let $ = cheerio.load(result); - - const headers = $('li'); - return headers - .map((i, h) => { - const novelName = $(h).text(); - const novelUrl = $(h).find('a').attr('href'); - const novelCover = $(h).find('img').attr('src'); - - if (!novelUrl) { - return null; - } - - return { - name: novelName, - cover: novelCover, - url: novelUrl, - }; - }) - .get() - .filter(sr => sr !== null); -}; - -async function parseNovelAndChapters (novelUrl){ - let novel = { - url: novelUrl, - chapters: [], - }; - - const result = await fetchApi(novelUrl).then(res => res.text()); - let $ = cheerio.load(result); - - novel.name = $('h1').text(); - novel.cover = $('.novel-cover').find('a > img').attr('src'); - novel.author = $( - 'div.novel-details > div:nth-child(5) > div.novel-detail-body', - ) - .find('li') - .map((_, el) => $(el).text()) - .get() - .join(', '); - - novel.genres = $( - 'div.novel-details > div:nth-child(2) > div.novel-detail-body', - ) - .find('li') - .map((_, el) => $(el).text()) - .get() - .join(','); - - novel.summary = $( - 'div.novel-right > div > div:nth-child(1) > div.novel-detail-body', - ).text(); - - novel.chapters = $('ul.chapter-chs > li > a') - .map((_, el) => { - const chapterUrl = $(el).attr('href'); - const chapterName = $(el).text(); - - return { - name: chapterName, - releaseTime: '', - url: chapterUrl, - }; - }) - .get(); - - return novel; -}; - -async function popularNovels (page) { - return { novels: [] }; /** TO DO */ -}; - -async function parseChapter (chapterUrl) { - const result = await fetchApi(chapterUrl).then(res => res.text()); - let loadedCheerio = cheerio.load(result); - - const chapterText = loadedCheerio('#contentall').html() || ''; - - return chapterText; -}; - -module.exports = { - id: pluginId, - name: sourceName, - site: baseUrl, - lang: languages.English, - icon: 'src/coverNotAvailable.jpg', - version: '1.0.0', - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; \ No newline at end of file diff --git a/plugins/english/NovelOnline.ts b/plugins/english/NovelOnline.ts new file mode 100644 index 0000000..e7a0f25 --- /dev/null +++ b/plugins/english/NovelOnline.ts @@ -0,0 +1,111 @@ +import { load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "NO.net"; +export const name = "novelsOnline"; +export const site = "https://novelsonline.net"; +export const icon = "src/coverNotAvailable.jpg"; +export const version = "1.0.0"; + +const pluginId = id; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const result = await fetchApi( + "https://novelsonline.net/sResults.php", + { + headers: { + Accept: "*/*", + "Accept-Language": "pl,en-US;q=0.7,en;q=0.3", + "Content-Type": + "application/x-www-form-urlencoded; charset=UTF-8", + }, + method: "POST", + body: "q=" + encodeURIComponent(searchTerm), + }, + pluginId + ).then((res) => res.text()); + let $ = parseHTML(result); + + const headers = $("li"); + return headers + .map((i, h) => { + const novelName = $(h).text(); + const novelUrl = $(h).find("a").attr("href"); + const novelCover = $(h).find("img").attr("src"); + + if (!novelUrl) { + return null; + } + + return { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + }) + .get() + .filter((sr) => sr !== null); +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + let novel: Novel.instance = { + url: novelUrl, + chapters: [], + }; + + const result = await fetchApi(novelUrl).then((res) => res.text()); + let $ = parseHTML(result); + + novel.name = $("h1").text(); + novel.cover = $(".novel-cover").find("a > img").attr("src"); + novel.author = $( + "div.novel-details > div:nth-child(5) > div.novel-detail-body" + ) + .find("li") + .map((_, el) => $(el).text()) + .get() + .join(", "); + + novel.genres = $( + "div.novel-details > div:nth-child(2) > div.novel-detail-body" + ) + .find("li") + .map((_, el) => $(el).text()) + .get() + .join(","); + + novel.summary = $( + "div.novel-right > div > div:nth-child(1) > div.novel-detail-body" + ).text(); + + novel.chapters = $("ul.chapter-chs > li > a") + .map((_, el) => { + const chapterUrl = $(el).attr("href"); + const chapterName = $(el).text(); + + return { + name: chapterName, + releaseTime: "", + url: chapterUrl, + } as Chapter.Item; + }) + .get(); + + return novel; + }; + +export const popularNovels: Plugin.popularNovels = async function (page) { + return []; /** TO DO */ +}; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi(chapterUrl).then((res) => res.text()); + let loadedCheerio = parseHTML(result); + + const chapterText = loadedCheerio("#contentall").html() || ""; + + return chapterText; +}; +export const fetchImage: Plugin.fetchImage = fetchFile; diff --git a/plugins/english/NovelR18[madara].ts b/plugins/english/NovelR18[madara].ts new file mode 100644 index 0000000..039aea7 --- /dev/null +++ b/plugins/english/NovelR18[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "novelr18"; +export const name = "NovelR18 [madara]"; +export const icon = "multisrc/madara/icons/novelr18.png"; +export const version = "1.0.0"; +export const site = "https://novelr18.com/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novel" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (true) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "manga" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/NovelTranslate[madara].ts b/plugins/english/NovelTranslate[madara].ts new file mode 100644 index 0000000..5a794ea --- /dev/null +++ b/plugins/english/NovelTranslate[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "novelTL"; +export const name = "NovelTranslate [madara]"; +export const icon = "multisrc/madara/icons/noveltranslate.png"; +export const version = "1.0.0"; +export const site = "https://noveltranslate.com/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "all-novels" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (false) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "manga" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/Novelmtcom[readwn].ts b/plugins/english/Novelmtcom[readwn].ts new file mode 100644 index 0000000..23b6b4f --- /dev/null +++ b/plugins/english/Novelmtcom[readwn].ts @@ -0,0 +1,198 @@ + + import { load as parseHTML } from "cheerio"; + // import dayjs from 'dayjs'; + import { fetchFile, fetchApi } from "@libs/fetch"; + import { Novel, Plugin, Chapter } from "@typings/plugin"; + // import { parseMadaraDate } from "@libs/parseMadaraDate"; + // import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; + // import { showToast } from "@libs/showToast"; + // import { Filter, FilterInputs } from "@libs/filterInputs"; + // import { NovelStatus } from '@libs/novelStatus'; + // import { defaultCover } from "@libs/defaultCover"; + + + export const id = "Novelmt.com"; + export const name = "Novelmt.com"; + export const icon = "multisrc/readwn/icon/novelmt.png"; + export const version = "1.0.0"; + export const site = "https://www.novelmt.com/"; + // export const filters: Filter[] = []; + exports["protected"] = false; + + const baseUrl = site; + + export const popularNovels: Plugin.popularNovels = async function ( + page, + { filters, showLatestNovels } + ) { + const novels: Novel.Item[] = []; + const pageNo = page - 1; + + const url = baseUrl + 'list/all/all-onclick-' + pageNo +'.html'; + + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('li.novel-item').each(function () { + const novelName = loadedCheerio(this).find('h4').text(); + const novelUrl = baseUrl + loadedCheerio(this).find('a').attr('href'); + + const coverUri = loadedCheerio(this) + .find('.novel-cover > img') + .attr('data-src'); + + const novelCover = baseUrl + coverUri; + + const novel = { name: novelName, cover: novelCover, url: novelUrl }; + + novels.push(novel); + }); + + return novels; + }; + + + export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const result = await fetchApi(novelUrl); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + const novel: Novel.instance = { + url: novelUrl, + chapters: [], + }; + + novel.name = loadedCheerio('h1.novel-title').text(); + + const coverUri = loadedCheerio('figure.cover > img').attr('data-src'); + novel.cover = baseUrl + coverUri; + + novel.summary = loadedCheerio('.summary') + .text() + .replace('Summary', '') + .trim(); + + novel.genres = ''; + + loadedCheerio('div.categories > ul > li').each(function () { + novel.genres += loadedCheerio(this).text().trim() + ','; + }); + + loadedCheerio('div.header-stats > span').each(function () { + if (loadedCheerio(this).find('small').text() === 'Status') { + novel.status = loadedCheerio(this).find('strong').text(); + } + }); + + novel.genres = novel.genres.slice(0, -1); + + novel.author = loadedCheerio('span[itemprop=author]').text(); + + let novelChapters: Chapter.Item[] = []; + + const novelId = novelUrl.replace('.html', '').replace(baseUrl, ''); + + const latestChapterNo = parseInt(loadedCheerio('.header-stats') + .find('span > strong') + .first() + .text() + .trim()); + + let lastChapterNo = 1; + loadedCheerio('.chapter-list li').each(function () { + const chapterName = loadedCheerio(this) + .find('a .chapter-title') + .text() + .trim(); + + const chapterUrl = baseUrl + loadedCheerio(this).find('a').attr('href')?.trim(); + + const releaseDate = loadedCheerio(this) + .find('a .chapter-update') + .text() + .trim(); + + lastChapterNo = parseInt(loadedCheerio(this).find('a .chapter-no').text().trim()); + + const chapter = { name: chapterName, releaseTime: releaseDate, url: chapterUrl }; + + novelChapters.push(chapter); + }); + + // Itterate once more before loop to finish off + lastChapterNo++; + for (let i = lastChapterNo; i <= latestChapterNo; i++) { + const chapterName = 'Chapter ' + i; + const chapterUrl = baseUrl + novelId+'_'+ i +'.html'; + const releaseDate = null; + + const chapter = { name: chapterName, releaseTime: releaseDate,url: chapterUrl }; + + novelChapters.push(chapter); + } + + novel.chapters = novelChapters; + return novel; + }; + + export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi(chapterUrl); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + const chapterText = loadedCheerio('.chapter-content').html(); + return chapterText; + }; + + export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const searchUrl = baseUrl + 'e/search/index.php'; + + const result = await fetchApi(searchUrl, { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Referer: baseUrl + 'search.html', + Origin: baseUrl, + 'user-agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36', + }, + method: 'POST', + body: JSON.stringify({ + show: 'title', + tempid: 1, + tbname: 'news', + keyboard: searchTerm, + }), + }); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + loadedCheerio('li.novel-item').each(function () { + const novelName = loadedCheerio(this).find('h4').text(); + const novelUrl = baseUrl + loadedCheerio(this).find('a').attr('href'); + + const coverUri = baseUrl + loadedCheerio(this).find('img').attr('data-src'); + const novelCover = baseUrl + coverUri; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; + }; + + export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url, {}); + }; + + \ No newline at end of file diff --git a/plugins/english/Novelroomnet[madara].ts b/plugins/english/Novelroomnet[madara].ts new file mode 100644 index 0000000..06c7a7c --- /dev/null +++ b/plugins/english/Novelroomnet[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "novelroom"; +export const name = "Novelroom.net [madara]"; +export const icon = "multisrc/madara/icons/novelroom.net.png"; +export const version = "1.0.0"; +export const site = "https://novelroom.net/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "manga" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (false) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "manga" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/Novelstic[madara].ts b/plugins/english/Novelstic[madara].ts new file mode 100644 index 0000000..c6c8c90 --- /dev/null +++ b/plugins/english/Novelstic[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "novelstic"; +export const name = "Novelstic [madara]"; +export const icon = "multisrc/madara/icons/novelstic.png"; +export const version = "1.0.0"; +export const site = "https://novelstic.com/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novel" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (true) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/OnlyMTL[madara].ts b/plugins/english/OnlyMTL[madara].ts new file mode 100644 index 0000000..78b8426 --- /dev/null +++ b/plugins/english/OnlyMTL[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "onlymtl"; +export const name = "OnlyMTL [madara]"; +export const icon = "multisrc/madara/icons/onlymtl.png"; +export const version = "1.0.0"; +export const site = "https://www.onlymtl.com/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novel" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (true) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/ReadWebNovels[madara].ts b/plugins/english/ReadWebNovels[madara].ts new file mode 100644 index 0000000..7f9978b --- /dev/null +++ b/plugins/english/ReadWebNovels[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "readwebnovels"; +export const name = "ReadWebNovels [madara]"; +export const icon = "multisrc/madara/icons/readwebnovels.png"; +export const version = "1.0.0"; +export const site = "https://readwebnovels.net/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novel" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (false) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/Readwncom[readwn].ts b/plugins/english/Readwncom[readwn].ts new file mode 100644 index 0000000..c440105 --- /dev/null +++ b/plugins/english/Readwncom[readwn].ts @@ -0,0 +1,198 @@ + + import { load as parseHTML } from "cheerio"; + // import dayjs from 'dayjs'; + import { fetchFile, fetchApi } from "@libs/fetch"; + import { Novel, Plugin, Chapter } from "@typings/plugin"; + // import { parseMadaraDate } from "@libs/parseMadaraDate"; + // import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; + // import { showToast } from "@libs/showToast"; + // import { Filter, FilterInputs } from "@libs/filterInputs"; + // import { NovelStatus } from '@libs/novelStatus'; + // import { defaultCover } from "@libs/defaultCover"; + + + export const id = "Readwn.com"; + export const name = "Readwn.com"; + export const icon = "multisrc/readwn/icon/readwn.png"; + export const version = "1.0.0"; + export const site = "https://www.readwn.com/"; + // export const filters: Filter[] = []; + exports["protected"] = false; + + const baseUrl = site; + + export const popularNovels: Plugin.popularNovels = async function ( + page, + { filters, showLatestNovels } + ) { + const novels: Novel.Item[] = []; + const pageNo = page - 1; + + const url = baseUrl + 'list/all/all-onclick-' + pageNo +'.html'; + + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('li.novel-item').each(function () { + const novelName = loadedCheerio(this).find('h4').text(); + const novelUrl = baseUrl + loadedCheerio(this).find('a').attr('href'); + + const coverUri = loadedCheerio(this) + .find('.novel-cover > img') + .attr('data-src'); + + const novelCover = baseUrl + coverUri; + + const novel = { name: novelName, cover: novelCover, url: novelUrl }; + + novels.push(novel); + }); + + return novels; + }; + + + export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const result = await fetchApi(novelUrl); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + const novel: Novel.instance = { + url: novelUrl, + chapters: [], + }; + + novel.name = loadedCheerio('h1.novel-title').text(); + + const coverUri = loadedCheerio('figure.cover > img').attr('data-src'); + novel.cover = baseUrl + coverUri; + + novel.summary = loadedCheerio('.summary') + .text() + .replace('Summary', '') + .trim(); + + novel.genres = ''; + + loadedCheerio('div.categories > ul > li').each(function () { + novel.genres += loadedCheerio(this).text().trim() + ','; + }); + + loadedCheerio('div.header-stats > span').each(function () { + if (loadedCheerio(this).find('small').text() === 'Status') { + novel.status = loadedCheerio(this).find('strong').text(); + } + }); + + novel.genres = novel.genres.slice(0, -1); + + novel.author = loadedCheerio('span[itemprop=author]').text(); + + let novelChapters: Chapter.Item[] = []; + + const novelId = novelUrl.replace('.html', '').replace(baseUrl, ''); + + const latestChapterNo = parseInt(loadedCheerio('.header-stats') + .find('span > strong') + .first() + .text() + .trim()); + + let lastChapterNo = 1; + loadedCheerio('.chapter-list li').each(function () { + const chapterName = loadedCheerio(this) + .find('a .chapter-title') + .text() + .trim(); + + const chapterUrl = baseUrl + loadedCheerio(this).find('a').attr('href')?.trim(); + + const releaseDate = loadedCheerio(this) + .find('a .chapter-update') + .text() + .trim(); + + lastChapterNo = parseInt(loadedCheerio(this).find('a .chapter-no').text().trim()); + + const chapter = { name: chapterName, releaseTime: releaseDate, url: chapterUrl }; + + novelChapters.push(chapter); + }); + + // Itterate once more before loop to finish off + lastChapterNo++; + for (let i = lastChapterNo; i <= latestChapterNo; i++) { + const chapterName = 'Chapter ' + i; + const chapterUrl = baseUrl + novelId+'_'+ i +'.html'; + const releaseDate = null; + + const chapter = { name: chapterName, releaseTime: releaseDate,url: chapterUrl }; + + novelChapters.push(chapter); + } + + novel.chapters = novelChapters; + return novel; + }; + + export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi(chapterUrl); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + const chapterText = loadedCheerio('.chapter-content').html(); + return chapterText; + }; + + export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const searchUrl = baseUrl + 'e/search/index.php'; + + const result = await fetchApi(searchUrl, { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Referer: baseUrl + 'search.html', + Origin: baseUrl, + 'user-agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36', + }, + method: 'POST', + body: JSON.stringify({ + show: 'title', + tempid: 1, + tbname: 'news', + keyboard: searchTerm, + }), + }); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + loadedCheerio('li.novel-item').each(function () { + const novelName = loadedCheerio(this).find('h4').text(); + const novelUrl = baseUrl + loadedCheerio(this).find('a').attr('href'); + + const coverUri = baseUrl + loadedCheerio(this).find('img').attr('data-src'); + const novelCover = baseUrl + coverUri; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; + }; + + export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url, {}); + }; + + \ No newline at end of file diff --git a/plugins/english/SleepyTranslations[madara].ts b/plugins/english/SleepyTranslations[madara].ts new file mode 100644 index 0000000..802830a --- /dev/null +++ b/plugins/english/SleepyTranslations[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "sleeptTLS"; +export const name = "SleepyTranslations [madara]"; +export const icon = "multisrc/madara/icons/sleepytranslations.png"; +export const version = "1.0.0"; +export const site = "https://sleepytranslations.com/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "series" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (true) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "series" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/SonicMTL[madara].ts b/plugins/english/SonicMTL[madara].ts new file mode 100644 index 0000000..c596184 --- /dev/null +++ b/plugins/english/SonicMTL[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "sonicmtl"; +export const name = "SonicMTL [madara]"; +export const icon = "multisrc/madara/icons/sonicmtl.png"; +export const version = "1.0.0"; +export const site = "https://www.sonicmtl.com/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novel" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (true) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/SugarBabies[madara].ts b/plugins/english/SugarBabies[madara].ts new file mode 100644 index 0000000..c26d0d8 --- /dev/null +++ b/plugins/english/SugarBabies[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "sugarbbscan"; +export const name = "Sugar Babies [madara]"; +export const icon = "multisrc/madara/icons/sugarbabies.png"; +export const version = "1.0.0"; +export const site = "https://sugarbbscan.com/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "series" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (false) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "series" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/SweetEscapeTranslations[madara].ts b/plugins/english/SweetEscapeTranslations[madara].ts new file mode 100644 index 0000000..f16d921 --- /dev/null +++ b/plugins/english/SweetEscapeTranslations[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "sweetEscapeTL"; +export const name = "Sweet Escape Translations [madara]"; +export const icon = "multisrc/madara/icons/sweetescapetranslations.png"; +export const version = "1.0.0"; +export const site = "https://sweetescapetranslations.com/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "manga" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (false) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "manga" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/WebNovelLover[madara].ts b/plugins/english/WebNovelLover[madara].ts new file mode 100644 index 0000000..bbe0428 --- /dev/null +++ b/plugins/english/WebNovelLover[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "webnovelover"; +export const name = "WebNovelLover [madara]"; +export const icon = "multisrc/madara/icons/webnovellover.png"; +export const version = "1.0.0"; +export const site = "https://www.webnovelover.com/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novel" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (false) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/WuxiaWorldSite[madara].ts b/plugins/english/WuxiaWorldSite[madara].ts new file mode 100644 index 0000000..3836a9f --- /dev/null +++ b/plugins/english/WuxiaWorldSite[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "wuxiaworld.site"; +export const name = "WuxiaWorld.Site [madara]"; +export const icon = "multisrc/madara/icons/wuxiaworld.site.png"; +export const version = "1.0.0"; +export const site = "https://wuxiaworld.site/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novel" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (true) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/ZetroTranslation[madara].ts b/plugins/english/ZetroTranslation[madara].ts new file mode 100644 index 0000000..763c332 --- /dev/null +++ b/plugins/english/ZetroTranslation[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "zetroTL"; +export const name = "Zetro Translation [madara]"; +export const icon = "multisrc/madara/icons/zetrotranslation.png"; +export const version = "1.0.0"; +export const site = "https://zetrotranslation.com/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novel" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (false) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/ZinnNovel[madara].ts b/plugins/english/ZinnNovel[madara].ts new file mode 100644 index 0000000..92dc019 --- /dev/null +++ b/plugins/english/ZinnNovel[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "zinnovel"; +export const name = "ZinnNovel [madara]"; +export const icon = "multisrc/madara/icons/zinnnovel.png"; +export const version = "1.0.0"; +export const site = "https://zinnovel.com/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "manga" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (false) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "manga" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/english/allnovelfull.js b/plugins/english/allnovelfull.js deleted file mode 100644 index b1240a5..0000000 --- a/plugins/english/allnovelfull.js +++ /dev/null @@ -1,171 +0,0 @@ -const cheerio = require('cheerio'); -const fetchApi = require('@libs/fetchApi'); -const fetchFile = require('@libs/fetchFile'); -const languages = require('@libs/languages'); - -const pluginId = 'ANF.com'; -const sourceName = 'AllNovelFull'; -const baseUrl = 'https://allnovelfull.com'; - -async function popularNovels (page) { - const url = `${baseUrl}/most-popular?page=${page}`; - - const result = await fetchApi(url); - console.log(result.ok); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - const novels = []; - loadedCheerio('.col-truyen-main .list-truyen .row').each(function () { - const novelName = loadedCheerio(this).find('h3.truyen-title > a').text(); - const novelCover = - baseUrl + loadedCheerio(this).find('img.cover').attr('src'); - const novelUrl = - baseUrl + loadedCheerio(this).find('h3.truyen-title > a').attr('href'); - - const novel = { - url: novelUrl, - name: novelName, - cover: novelCover, - }; - console.log(novel); - novels.push(novel); - }); - return novels; -}; - -async function parseNovelAndChapters (novelUrl) { - const url = novelUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novel = { - url: novelUrl, - }; - - novel.name = loadedCheerio('div.book > img').attr('alt'); - - novel.cover = baseUrl + loadedCheerio('div.book > img').attr('src'); - - novel.summary = loadedCheerio('div.desc-text').text().trim(); - - novel.author = loadedCheerio('div.info > div > h3') - .filter(function () { - return loadedCheerio(this).text().trim() === 'Author:'; - }) - .siblings() - .text(); - - novel.genres = loadedCheerio('div.info > div') - .filter(function () { - return loadedCheerio(this).find('h3').text().trim() === 'Genre:'; - }) - .text() - .replace('Genre:', ''); - - novel.status = loadedCheerio('div.info > div > h3') - .filter(function () { - return loadedCheerio(this).text().trim() === 'Status:'; - }) - .next() - .text(); - - const novelId = loadedCheerio('#rating').attr('data-novel-id'); - - async function getChapters (id) { - const chapterListUrl = baseUrl + '/ajax/chapter-option?novelId=' + id; - - const data = await fetchApi(chapterListUrl); - const chapters = await data.text(); - - loadedCheerio = cheerio.load(chapters); - - let novelChapters = []; - - loadedCheerio('select > option').each(function () { - let chapterName = loadedCheerio(this).text(); - let releaseDate = null; - let chapterUrl = loadedCheerio(this) - .attr('value') - ?.replace(`/${novelUrl}`, ''); - - if (chapterUrl) { - novelChapters.push({ - name: chapterName, - releaseTime: releaseDate, - url: chapterUrl, - }); - } - }); - - return novelChapters; - }; - - if (novelId) { - novel.chapters = await getChapters(novelId); - } - - return novel; -}; - -async function parseChapter (chapterUrl) { - const url = baseUrl + chapterUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - const chapterText = loadedCheerio('#chapter-content').html() || ''; - - return chapterText; -}; - -async function searchNovels (searchTerm){ - const url = `${baseUrl}/search?keyword=${searchTerm}`; - - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('div.col-truyen-main > div.list-truyen > .row').each( - function () { - const novelUrl = - baseUrl + loadedCheerio(this).find('h3.truyen-title > a').attr('href'); - - const novelName = loadedCheerio(this).find('h3.truyen-title > a').text(); - const novelCover = baseUrl + loadedCheerio(this).find('img').attr('src'); - - if (novelUrl) { - novels.push({ - url: novelUrl, - name: novelName, - cover: novelCover, - }); - } - }, - ); - - return novels; -}; - -module.exports = { - id: pluginId, - name: sourceName, - site: baseUrl, - version: '1.0.0', - lang: languages.English, - icon: 'src/en/allnovelfull/icon.png', - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; diff --git a/plugins/english/allnovelfull.ts b/plugins/english/allnovelfull.ts new file mode 100644 index 0000000..c0dda5e --- /dev/null +++ b/plugins/english/allnovelfull.ts @@ -0,0 +1,200 @@ +import { load as cheerioload } from "cheerio"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; +import { fetchApi, fetchFile } from "@libs/fetch"; + +export const id = "ANF.com"; +export const name = "AllNovelFull"; +export const site = "https://allnovelfull.com"; +export const version = "1.0.0"; +export const icon = "src/en/allnovelfull/icon.png"; + +export const popularNovels: Plugin.popularNovels = async function (page) { + const url = `${site}/most-popular?page=${page}`; + + const result = await fetchApi(url, {}); + if (!result.ok) { + console.error("Cloudflare error"); + // console.error(await result.text()); + // TODO: Cloudflare protection or other error + return []; + } + const body = await result.text(); + + const loadedCheerio = cheerioload(body); + + const novels: Novel.Item[] = []; + loadedCheerio(".col-truyen-main .list-truyen .row").each(function () { + const novelName = loadedCheerio(this) + .find("h3.truyen-title > a") + .text(); + const novelCover = + site + loadedCheerio(this).find("img.cover").attr("src"); + const novelUrl = + site + loadedCheerio(this).find("h3.truyen-title > a").attr("href"); + + const novel = { + url: novelUrl, + name: novelName, + cover: novelCover, + }; + novels.push(novel); + }); + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + + const result = await fetchApi(url); + if (!result.ok) { + console.error("Cloudflare error"); + // console.error(await result.text()); + // TODO: Cloudflare protection or other error + return { url, chapters: [] }; + } + const body = await result.text(); + + let loadedCheerio = cheerioload(body); + + let novel: Novel.instance = { + url: novelUrl, + }; + + novel.name = loadedCheerio("div.book > img").attr("alt"); + + novel.cover = site + loadedCheerio("div.book > img").attr("src"); + + novel.summary = loadedCheerio("div.desc-text").text().trim(); + + novel.author = loadedCheerio("div.info > div > h3") + .filter(function () { + return loadedCheerio(this).text().trim() === "Author:"; + }) + .siblings() + .text(); + + novel.genres = loadedCheerio("div.info > div") + .filter(function () { + return ( + loadedCheerio(this).find("h3").text().trim() === "Genre:" + ); + }) + .text() + .replace("Genre:", ""); + + novel.status = loadedCheerio("div.info > div > h3") + .filter(function () { + return loadedCheerio(this).text().trim() === "Status:"; + }) + .next() + .text(); + + const novelId = loadedCheerio("#rating").attr("data-novel-id"); + + async function getChapters(id: string) { + const chapterListUrl = site + "/ajax/chapter-option?novelId=" + id; + + const data = await fetchApi(chapterListUrl); + if (!data.ok) { + console.error("Cloudflare error"); + // console.error(await result.text()); + // TODO: Cloudflare protection or other error + return []; + } + const chapters = await data.text(); + + loadedCheerio = cheerioload(chapters); + + const novelChapters: Chapter.Item[] = []; + + loadedCheerio("select > option").each(function () { + let chapterName = loadedCheerio(this).text(); + let releaseDate = null; + let chapterUrl = loadedCheerio(this) + .attr("value") + ?.replace(`/${novelUrl}`, ""); + + if (chapterUrl) { + novelChapters.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + } + }); + + return novelChapters; + } + + if (novelId) { + novel.chapters = await getChapters(novelId); + } + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const url = site + chapterUrl; + + const result = await fetchApi(url); + if (!result.ok) { + console.error("Cloudflare error"); + // console.error(await result.text()); + // TODO: Cloudflare protection or other error + return "Cloudflare protected site!"; + } + const body = await result.text(); + + const loadedCheerio = cheerioload(body); + + const chapterText = loadedCheerio("#chapter-content").html() || ""; + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = `${site}/search?keyword=${searchTerm}`; + + const result = await fetchApi(url); + if (!result.ok) { + console.error(await result.text()); + // TODO: Cloudflare protection or other error + return []; + } + const body = await result.text(); + + const loadedCheerio = cheerioload(body); + + let novels: Novel.Item[] = []; + + loadedCheerio("div.col-truyen-main > div.list-truyen > .row").each( + function () { + const novelUrl = + site + + loadedCheerio(this).find("h3.truyen-title > a").attr("href"); + + const novelName = loadedCheerio(this) + .find("h3.truyen-title > a") + .text(); + const novelCover = + site + loadedCheerio(this).find("img").attr("src"); + + if (novelUrl) { + novels.push({ + url: novelUrl, + name: novelName, + cover: novelCover, + }); + } + } + ); + + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async function (url, init) { + return await fetchFile(url, init); +}; + +exports.protected = false; diff --git a/plugins/english/bestlightnovel.js b/plugins/english/bestlightnovel.js deleted file mode 100644 index c88e0cd..0000000 --- a/plugins/english/bestlightnovel.js +++ /dev/null @@ -1,143 +0,0 @@ -const cheerio = require('cheerio'); -const fetchApi = require('@libs/fetchApi'); -const fetchFile = require('@libs/fetchFile'); -const languages = require('@libs/languages'); -const Status = require('@libs/novelStatus'); - -const pluginId = "BLN.com"; -const sourceName = 'BestLightNovel'; - -const baseUrl = 'https://bestlightnovel.com/'; - -async function popularNovels (page) { - const url = - baseUrl + 'novel_list?type=topview&category=all&state=all&page=1' + page; - - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('.update_item.list_category').each(function () { - const novelName = loadedCheerio(this).find('h3 > a').text(); - const novelCover = loadedCheerio(this).find('img').attr('src'); - const novelUrl = loadedCheerio(this).find('h3 > a').attr('href'); - - const novel = { name: novelName, cover: novelCover, url: novelUrl }; - - novels.push(novel); - }); - - return novels; -}; - -async function parseNovelAndChapters (novelUrl) { - const url = novelUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novel = { - url, - name: '', - cover: '', - author: '', - status: Status.Unknown, - genres: '', - summary: '', - chapters: [], - }; - - novel.name = loadedCheerio('.truyen_info_right h1').text().trim(); - novel.cover = loadedCheerio('.info_image img').attr('src'); - novel.summary = loadedCheerio('#noidungm').text()?.trim(); - novel.author = loadedCheerio( - '#main_body > div.cotgiua > div.truyen_info_wrapper > div.truyen_info > div.entry-header > div.truyen_if_wrap > ul > li:nth-child(2) > a', - ) - .text() - ?.trim(); - - let status = loadedCheerio( - '#main_body > div.cotgiua > div.truyen_info_wrapper > div.truyen_info > div.entry-header > div.truyen_if_wrap > ul > li:nth-child(4) > a', - ) - .text() - ?.trim(); - - novel.status = - status === 'ONGOING' - ? Status.Ongoing - : status === 'COMPLETED' - ? Status.Completed - : Status.Unknown; - - let novelChapters = []; - - loadedCheerio('.chapter-list div.row').each(function () { - const chapterName = loadedCheerio(this).find('a').text().trim(); - const releaseDate = loadedCheerio(this) - .find('span:nth-child(2)') - .text() - .trim(); - const chapterUrl = loadedCheerio(this).find('a').attr('href'); - - novelChapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); - }); - - novel.chapters = novelChapters.reverse(); - - return novel; -}; - -async function parseChapter ( chapterUrl) { - const url = chapterUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - const chapterText = loadedCheerio('#vung_doc').html(); - - return chapterText; -}; - -async function searchNovels (searchTerm) { - const url = `${baseUrl}search_novels/${searchTerm}`; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('.update_item.list_category').each(function () { - const novelName = loadedCheerio(this).find('h3 > a').text(); - const novelCover = loadedCheerio(this).find('img').attr('src'); - const novelUrl = loadedCheerio(this).find('h3 > a').attr('href'); - - const novel = { name: novelName, cover: novelCover, url: novelUrl }; - - novels.push(novel); - }); - - return novels; -}; - -module.exports = { - id: pluginId, - name: sourceName, - site: baseUrl, - version: '1.0.0', - lang: languages.English, - icon: 'src/en/bestlightnovel/icon.png', - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; \ No newline at end of file diff --git a/plugins/english/bestlightnovel.ts b/plugins/english/bestlightnovel.ts new file mode 100644 index 0000000..fab4623 --- /dev/null +++ b/plugins/english/bestlightnovel.ts @@ -0,0 +1,172 @@ +import { load as cheerioload } from "cheerio"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { NovelStatus } from "@libs/novelStatus"; + +export const id = "BLN.com"; +export const name = "BestLightNovel"; +export const site = "https://bestlightnovel.com/"; +export const version = "1.0.0"; +export const icon = "src/en/bestlightnovel/icon.png"; + +export const popularNovels: Plugin.popularNovels = async function (page) { + const url = + site + "novel_list?type=topview&category=all&state=all&page=1" + page; + + const result = await fetchApi(url); + if (!result.ok) { + console.error(await result.text()); + // TODO: Cloudflare protection or other error + return []; + } + const body = await result.text(); + + const loadedCheerio = cheerioload(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".update_item.list_category").each(function () { + const novelName = loadedCheerio(this).find("h3 > a").text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this).find("h3 > a").attr("href"); + + if (!novelUrl) { + // TODO: Handle error + console.error("No novel url!"); + return; + } + + const novel = { + name: novelName, + url: novelUrl, + cover: novelCover, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + + const result = await fetchApi(url); + if (!result.ok) { + console.error(await result.text()); + // TODO: Cloudflare protection + return { url, chapters: [] }; + } + const body = await result.text(); + + let loadedCheerio = cheerioload(body); + + let novel: Novel.instance = { + url, + name: "", + cover: "", + author: "", + status: NovelStatus.Unknown, + genres: "", + summary: "", + chapters: [], + }; + + novel.name = loadedCheerio(".truyen_info_right h1").text().trim(); + novel.cover = + loadedCheerio(".info_image img").attr("src") || defaultCover; + novel.summary = loadedCheerio("#noidungm").text()?.trim(); + novel.author = loadedCheerio( + "#main_body > div.cotgiua > div.truyen_info_wrapper > div.truyen_info > div.entry-header > div.truyen_if_wrap > ul > li:nth-child(2) > a" + ) + .text() + ?.trim(); + + let status = loadedCheerio( + "#main_body > div.cotgiua > div.truyen_info_wrapper > div.truyen_info > div.entry-header > div.truyen_if_wrap > ul > li:nth-child(4) > a" + ) + .text() + ?.trim(); + + novel.status = + status === "ONGOING" + ? NovelStatus.Ongoing + : status === "COMPLETED" + ? NovelStatus.Completed + : NovelStatus.Unknown; + + let novelChapters: Chapter.Item[] = []; + + loadedCheerio(".chapter-list div.row").each(function () { + const chapterName = loadedCheerio(this).find("a").text().trim(); + const releaseDate = loadedCheerio(this) + .find("span:nth-child(2)") + .text() + .trim(); + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + + if (!chapterUrl) { + // TODO: Handle error + console.error("No chapter url!"); + return; + } + + novelChapters.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + + novel.chapters = novelChapters.reverse(); + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const url = chapterUrl; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = cheerioload(body); + + const chapterText = loadedCheerio("#vung_doc").html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = `${site}search_novels/${searchTerm}`; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = cheerioload(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".update_item.list_category").each(function () { + const novelName = loadedCheerio(this).find("h3 > a").text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this).find("h3 > a").attr("href"); + + if (!novelUrl) { + // TODO: Handle error + console.error("No novel url!"); + return; + } + + const novel = { name: novelName, cover: novelCover, url: novelUrl }; + + novels.push(novel); + }); + + return novels; +}; + +export const fetchImage: Plugin.fetchImage = (...args) => fetchFile(...args); + +exports.protected = false; diff --git a/plugins/english/comrademao.js b/plugins/english/comrademao.js deleted file mode 100644 index 0d323e1..0000000 --- a/plugins/english/comrademao.js +++ /dev/null @@ -1,149 +0,0 @@ -const cheerio = require('cheerio'); -const fetchApi = require('@libs/fetchApi'); -const fetchFile = require('@libs/fetchFile'); -const languages = require('@libs/languages'); -const dayjs = require('dayjs'); - -const pluginId = 'comrademao'; - -const sourceName = 'Comrade Mao'; - -const baseUrl = 'https://comrademao.com/'; - -async function popularNovels (page) { - let url = baseUrl + 'page/' + page + '/?post_type=novel'; - - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('.listupd') - .find('div.bs') - .each(function () { - const novelName = loadedCheerio(this).find('.tt').text().trim(); - const novelCover = loadedCheerio(this).find('img').attr('src'); - const novelUrl = loadedCheerio(this).find('a').attr('href'); - - const novel = { - name: novelName, - cover: novelCover, - url: novelUrl, - }; - - novels.push(novel); - }); - - return novels; -}; - -async function parseNovelAndChapters (novelUrl) { - const url = novelUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - let novel = { url }; - - novel.name = loadedCheerio('.entry-title').text().trim(); - - novel.cover = loadedCheerio('div.thumbook > div > img').attr('src'); - - novel.summary = loadedCheerio('div.infox > div:nth-child(6) > span > p') - .text() - .trim(); - - novel.genres = loadedCheerio('div.infox > div:nth-child(4) > span') - .text() - .replace(/\s/g, ''); - - novel.status = loadedCheerio('div.infox > div:nth-child(3) > span') - .text() - .trim(); - - novel.author = loadedCheerio('div.infox > div:nth-child(2) > span') - .text() - .trim(); - - let novelChapters = []; - - loadedCheerio('#chapterlist') - .find('li') - .each(function () { - const releaseDate = dayjs( - loadedCheerio(this).find('.chapterdate').text(), - ).format('LL'); - const chapterName = loadedCheerio(this).find('.chapternum').text(); - const chapterUrl = loadedCheerio(this).find('a').attr('href'); - - novelChapters.push({ - name: chapterName, - url: chapterUrl, - releaseTime: releaseDate, - }); - }); - - novel.chapters = novelChapters.reverse(); - - return novel; -}; - -async function parseChapter (chapterUrl) { - const url = chapterUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - let chapterText = loadedCheerio('#chaptercontent').html(); - - return chapterText; -}; - -async function searchNovels (searchTerm) { - const url = baseUrl + '?s=' + searchTerm + '&post_type=novel'; - - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('.listupd') - .find('div.bs') - .each(function () { - const novelName = loadedCheerio(this).find('.tt').text().trim(); - const novelCover = loadedCheerio(this).find('img').attr('src'); - const novelUrl = loadedCheerio(this).find('a').attr('href'); - - const novel = { - name: novelName, - cover: novelCover, - url: novelUrl, - }; - - novels.push(novel); - }); - - return novels; -}; - -module.exports = { - id: pluginId, - name: sourceName, - site: baseUrl, - version: '1.0.0', - lang: languages.English, - icon: 'src/en/comrademao/icon.png', - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; diff --git a/plugins/english/comrademao.ts b/plugins/english/comrademao.ts new file mode 100644 index 0000000..5bc0060 --- /dev/null +++ b/plugins/english/comrademao.ts @@ -0,0 +1,164 @@ +import { load as cheerioload } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import dayjs from "dayjs"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "comrademao"; +export const name = "Comrade Mao"; +export const site = "https://comrademao.com/"; +export const version = "1.0.0"; +export const icon = "src/en/comrademao/icon.png"; + +export const popularNovels: Plugin.popularNovels = async function (page) { + let url = site + "page/" + page + "/?post_type=novel"; + + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = cheerioload(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".listupd") + .find("div.bs") + .each(function () { + const novelName = loadedCheerio(this).find(".tt").text().trim(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this).find("a").attr("href"); + + if (!novelUrl) { + // TODO: Handle error + console.error("No novel url!"); + return; + } + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = cheerioload(body); + + let novel: Novel.instance = { url }; + + novel.name = loadedCheerio(".entry-title").text().trim(); + + novel.cover = loadedCheerio("div.thumbook > div > img").attr("src"); + + novel.summary = loadedCheerio("div.infox > div:nth-child(6) > span > p") + .text() + .trim(); + + novel.genres = loadedCheerio("div.infox > div:nth-child(4) > span") + .text() + .replace(/\s/g, ""); + + novel.status = loadedCheerio("div.infox > div:nth-child(3) > span") + .text() + .trim(); + + novel.author = loadedCheerio("div.infox > div:nth-child(2) > span") + .text() + .trim(); + + let novelChapters: Chapter.Item[] = []; + + loadedCheerio("#chapterlist") + .find("li") + .each(function () { + const releaseDate = dayjs( + loadedCheerio(this).find(".chapterdate").text() + ).format("LL"); + const chapterName = loadedCheerio(this) + .find(".chapternum") + .text(); + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + + if (!chapterUrl) { + // TODO: Handle error + console.error("No chapter url!"); + return; + } + + novelChapters.push({ + name: chapterName, + url: chapterUrl, + releaseTime: releaseDate, + }); + }); + + novel.chapters = novelChapters.reverse(); + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const url = chapterUrl; + + const result = await fetchApi(url); + if (!result.ok) { + const err = await result.text(); + console.error(err); + // TODO: Cloudflare protection or other error + return "Error!" + err; + } + const body = await result.text(); + + const loadedCheerio = cheerioload(body); + + let chapterText = loadedCheerio("#chaptercontent").html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = site + "?s=" + searchTerm + "&post_type=novel"; + + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = cheerioload(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".listupd") + .find("div.bs") + .each(function () { + const novelName = loadedCheerio(this).find(".tt").text().trim(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this).find("a").attr("href"); + + if (!novelUrl) { + // TODO: Handle error + console.error("No novel url!"); + return; + } + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; +export const fetchImage: Plugin.fetchImage = (...args) => fetchFile(...args); + +exports.protected = false; diff --git a/plugins/english/divinedaolibrary.js b/plugins/english/divinedaolibrary.js deleted file mode 100644 index 4f6870d..0000000 --- a/plugins/english/divinedaolibrary.js +++ /dev/null @@ -1,155 +0,0 @@ -const cheerio = require('cheerio'); -const fetchApi = require('@libs/fetchApi'); -const fetchFile = require('@libs/fetchFile'); -const languages = require('@libs/languages'); - -const defaultCoverUri = - 'https://github.com/LNReader/lnreader-sources/blob/main/icons/src/coverNotAvailable.jpg?raw=true'; - -const pluginId = 'DDL.com'; - -const sourceName = 'Divine Dao Library'; - -const baseUrl = 'https://www.divinedaolibrary.com/'; - -async function popularNovels (page) { - let url = baseUrl + 'novels'; - - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('#main') - .find('li') - .each(function () { - const novelName = loadedCheerio(this).find('a').text(); - const novelCover = defaultCoverUri; - const novelUrl = loadedCheerio(this).find(' a').attr('href'); - - const novel = { - name: novelName, - cover: novelCover, - url: novelUrl, - }; - - novels.push(novel); - }); - - return novels; -}; - -async function parseNovelAndChapters (novelUrl) { - const url = novelUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - let novel = {url }; - - novel.name = loadedCheerio('h1.entry-title').text().trim(); - - novel.cover = - loadedCheerio('.entry-content').find('img').attr('data-ezsrc') || defaultCoverUri; - - novel.summary = loadedCheerio('#main > article > div > p:nth-child(6)') - .text() - .trim(); - - novel.genres = null; - - novel.status = null; - - novel.author = loadedCheerio('#main > article > div > h3:nth-child(2)') - .text() - .replace(/Author:/g, '') - .trim(); - - let novelChapters = []; - - loadedCheerio('#main') - .find('li > span > a') - .each(function () { - const chapterName = loadedCheerio(this).text().trim(); - const releaseDate = null; - const chapterUrl = loadedCheerio(this).attr('href'); - - novelChapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); - }); - - novel.chapters = novelChapters; - - return novel; -}; - -async function parseChapter (chapterUrl) { - const url = chapterUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - let chapterName = loadedCheerio('.entry-title').text().trim(); - - let chapterText = loadedCheerio('.entry-content').html(); - - if (!chapterText) { - chapterText = loadedCheerio('.page-header').html(); - } - - chapterText = `

${chapterName}

` + chapterText; - - return chapterText; -}; - -async function searchNovels (searchTerm) { - let url = baseUrl + 'novels'; - - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('#main') - .find('li') - .each(function () { - const novelName = loadedCheerio(this).find('a').text(); - const novelCover = defaultCoverUri; - const novelUrl = loadedCheerio(this).find(' a').attr('href'); - - const novel = { - name: novelName, - cover: novelCover, - url: novelUrl, - }; - - novels.push(novel); - }); - - novels = novels.filter(novel => - novel.name.toLowerCase().includes(searchTerm.toLowerCase()), - ); - - return novels; -}; - -module.exports = { - id: pluginId, - name: sourceName, - site: baseUrl, - version: '1.0.0', - lang: languages.English, - icon: 'src/en/divinedaolibrary/icon.png', - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; diff --git a/plugins/english/divinedaolibrary.ts b/plugins/english/divinedaolibrary.ts new file mode 100644 index 0000000..c4b6386 --- /dev/null +++ b/plugins/english/divinedaolibrary.ts @@ -0,0 +1,151 @@ +import { load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; + +export const id = "DDL.com"; +export const name = "Divine Dao Library"; +export const site = "https://www.divinedaolibrary.com/"; +export const version = "1.0.0"; +export const icon = "src/`en/divinedaolibrary/icon.png"; + +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function (page) { + let url = baseUrl + "novels"; + + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio("#main") + .find("li") + .each(function () { + const novelName = loadedCheerio(this).find("a").text(); + const novelCover = defaultCover; + const novelUrl = loadedCheerio(this).find(" a").attr("href"); + + if (!novelUrl) return; + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + let novel: Novel.instance = { url }; + + novel.name = loadedCheerio("h1.entry-title").text().trim(); + + novel.cover = + loadedCheerio(".entry-content").find("img").attr("data-ezsrc") || + defaultCover; + + novel.summary = loadedCheerio("#main > article > div > p:nth-child(6)") + .text() + .trim(); + + novel.author = loadedCheerio("#main > article > div > h3:nth-child(2)") + .text() + .replace(/Author:/g, "") + .trim(); + + let novelChapters: Chapter.Item[] = []; + + loadedCheerio("#main") + .find("li > span > a") + .each(function () { + const chapterName = loadedCheerio(this).text().trim(); + const releaseDate = null; + const chapterUrl = loadedCheerio(this).attr("href"); + + if (!chapterUrl) return; + + novelChapters.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + + novel.chapters = novelChapters; + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const url = chapterUrl; + + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + let chapterName = loadedCheerio(".entry-title").text().trim(); + + let chapterText = loadedCheerio(".entry-content").html(); + + if (!chapterText) { + chapterText = loadedCheerio(".page-header").html(); + } + + chapterText = `

${chapterName}

` + chapterText; + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + let url = baseUrl + "novels"; + + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio("#main") + .find("li") + .each(function () { + const novelName = loadedCheerio(this).find("a").text(); + const novelCover = defaultCover; + const novelUrl = loadedCheerio(this).find(" a").attr("href"); + + if (!novelUrl) return; + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + novels = novels.filter((novel) => + novel.name.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + return novels; +}; +export const fetchImage: Plugin.fetchImage = async function (url, init) { + return await fetchFile(url, init); +}; diff --git a/plugins/english/earlynovel.ts b/plugins/english/earlynovel.ts new file mode 100644 index 0000000..620826b --- /dev/null +++ b/plugins/english/earlynovel.ts @@ -0,0 +1,196 @@ +import { load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { showToast } from "@libs/showToast"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; + +export const id = "earlynovel"; +export const name = "Early Novel"; +export const version = "1.0.0"; +export const icon = "multisrc/madara/icons/latestnovel.png"; +export const site = "https://earlynovel.net/"; +module.exports.protected = false; + +const pluginId = id; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function (page) { + const url = `${baseUrl}most-popular?page=${page}`; + + const body = await fetchApi(url, {}, pluginId).then((r) => r.text()); + + const loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".col-truyen-main > .list-truyen > .row").each(function () { + const novelName = loadedCheerio(this) + .find("h3.truyen-title > a") + .attr("title"); + const novelCover = + loadedCheerio(this).find(".lazyimg").attr("data-desk-image") || + loadedCheerio(this).find("img.cover").attr("src"); + const novelUrl = + baseUrl + + loadedCheerio(this) + .find("h3.truyen-title > a") + .attr("href") + ?.slice(1); + + if (!novelUrl || !novelName) return; + + const novel = { + name: novelName, + cover: novelCover || defaultCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + showToast("Early Novel may take 20-30 seconds"); + const url = novelUrl; + + const body = await fetchApi(url, {}, pluginId).then((r) => r.text()); + + let loadedCheerio = parseHTML(body); + + const name = loadedCheerio(".book > img").attr("alt"); + const cover = loadedCheerio(".book > img").attr("src") || defaultCover; + const summary = loadedCheerio(".desc-text").text().trim(); + const novel: Novel.instance = { + url, + chapters: [], + name, + summary, + cover, + }; + + loadedCheerio(".info > div > h3").each(function () { + let detailName = loadedCheerio(this).text(); + let detail = loadedCheerio(this) + .siblings() + .map((i, el) => loadedCheerio(el).text()) + .toArray() + .join(","); + + switch (detailName) { + case "Author:": + novel.author = detail; + break; + case "Status:": + novel.status = detail; + break; + case "Genre:": + novel.genres = detail; + break; + } + }); + + //! Doesn't work since there are multiple pages and can't find source API + //# Since cannot find sourceAPI i try similar function to lightnovelpub + const delay = (ms: number) => new Promise((res) => setTimeout(res, ms)); + + const lastPageStr = loadedCheerio('a:contains("Last ")') + .attr("title") + ?.match(/(\d+)/g); + const lastPage = Number(lastPageStr?.[1] || "0"); + + async function getChapters() { + let chapter: Chapter.Item[] = []; + for (let i = 1; i <= lastPage; i++) { + const chaptersUrl = `${novelUrl}?page=${i}`; + + const chaptersHtml = await fetchApi( + chaptersUrl, + {}, + pluginId + ).then((r) => r.text()); + + loadedCheerio = parseHTML(chaptersHtml); + + loadedCheerio("ul.list-chapter > li").each(function () { + const chapterName = loadedCheerio(this) + .find(".chapter-text") + .text() + .trim(); + const releaseDate = null; + const chapterHref = loadedCheerio(this) + .find("a") + .attr("href") + ?.slice(1); + if (!chapterHref) return; + const chapterUrl = baseUrl + chapterHref; + + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + + await delay(1000); + } + + return chapter; + } + + novel.chapters = await getChapters(); + + return novel; + }; +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const body = await fetchApi(chapterUrl, {}, pluginId).then((r) => r.text()); + + const loadedCheerio = parseHTML(body); + + const chapterText = loadedCheerio("#chapter-c").html() || ""; + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = `${baseUrl}search?keyword=${searchTerm}`; + + const body = await fetchApi(url, {}, pluginId).then((r) => r.text()); + + const loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio("div.col-truyen-main > div.list-truyen > .row").each( + function () { + const novelUrl = + baseUrl + + loadedCheerio(this).find("h3.truyen-title > a").attr("href"); + + const novelName = loadedCheerio(this) + .find("h3.truyen-title > a") + .text(); + const novelCover = + baseUrl + loadedCheerio(this).find("img").attr("src"); + + if (novelUrl) { + novels.push({ + url: novelUrl, + name: novelName, + cover: novelCover, + }); + } + } + ); + + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async function fetchImage(url) { + const headers = { + Referer: baseUrl, + }; + return await fetchFile(url, { headers: headers }); +}; diff --git a/plugins/english/fastnovel.ts b/plugins/english/fastnovel.ts new file mode 100644 index 0000000..45a37c8 --- /dev/null +++ b/plugins/english/fastnovel.ts @@ -0,0 +1,168 @@ +import { load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "fastnovel"; +export const name = "Fast Novel"; +export const version = "1.0.0"; +export const icon = "src/en/fastnovel/icon.png"; +export const site = "https://fastnovel.org/"; +exports.protected = false; + +const pluginId = id; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function (page) { + const url = `${baseUrl}sort/p/?page=${page}`; + + const body = await fetchApi(url, {}, pluginId).then((r) => r.text()); + + const loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".col-novel-main .list-novel .row").each(function () { + const novelName = loadedCheerio(this).find("h3.novel-title > a").text(); + const novelCover = loadedCheerio(this).find("img.cover").attr("src"); + const novelUrl = loadedCheerio(this) + .find("h3.novel-title > a") + .attr("href"); + + if (!novelUrl) return; + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + const body = await fetchApi(url, {}, pluginId).then((r) => r.text()); + + let loadedCheerio = parseHTML(body); + + const novel: Novel.instance = { + url, + chapters: [], + }; + + novel.name = loadedCheerio("div.book > img").attr("alt"); + + novel.cover = loadedCheerio("div.book > img").attr("src"); + + novel.summary = loadedCheerio("div.desc-text").text().trim(); + + loadedCheerio("ul.info > li > h3").each(function () { + let detailName = loadedCheerio(this).text(); + let detail = loadedCheerio(this) + .siblings() + .map((i, el) => loadedCheerio(el).text()) + .toArray() + .join(","); + + switch (detailName) { + case "Author:": + novel.author = detail; + break; + case "Status:": + novel.status = detail; + break; + case "Genre:": + novel.genres = detail; + break; + } + }); + + const novelId = loadedCheerio("#rating").attr("data-novel-id"); + + async function getChapters(id: string) { + const chapterListUrl = + baseUrl + "ajax/chapter-archive?novelId=" + id; + + const data = await fetchApi(chapterListUrl); + const chapterdata = await data.text(); + + loadedCheerio = parseHTML(chapterdata); + + let chapter: Chapter.Item[] = []; + + loadedCheerio("ul.list-chapter > li").each(function () { + const chapterName = loadedCheerio(this).find("a").attr("title"); + const releaseDate = null; + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + + if (!chapterName || !chapterUrl) return; + + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + return chapter; + } + + if (novelId) { + novel.chapters = await getChapters(novelId); + } + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const body = await fetchApi(chapterUrl, {}, pluginId).then((r) => r.text()); + + const loadedCheerio = parseHTML(body); + + loadedCheerio('#chr-content > div,h6,p[style="display: none;"]').remove(); + let chapterText = loadedCheerio("#chr-content").html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = `${baseUrl}search/?keyword=${searchTerm}`; + const body = await fetchApi(url, {}, pluginId).then((r) => r.text()); + + const loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio("div.col-novel-main > div.list-novel > .row").each( + function () { + const novelUrl = loadedCheerio(this) + .find("h3.novel-title > a") + .attr("href"); + + const novelName = loadedCheerio(this) + .find("h3.novel-title > a") + .text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + + if (novelUrl) { + novels.push({ + url: novelUrl, + name: novelName, + cover: novelCover, + }); + } + } + ); + + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async function (url) { + const headers = { + Referer: baseUrl, + }; + return await fetchFile(url, { headers: headers }); +}; diff --git a/plugins/english/foxaholic.js b/plugins/english/foxaholic.js deleted file mode 100644 index 6ad359c..0000000 --- a/plugins/english/foxaholic.js +++ /dev/null @@ -1,162 +0,0 @@ -const cheerio = require('cheerio'); -const languages = require('@libs/languages'); -const fetchApi = require('@libs/fetchApi'); -const fetchFile = require('@libs/fetchFile'); - -const pluginId = 'foxaholic'; -const baseUrl = 'https://www.foxaholic.com/'; - -async function popularNovels (page){ - const link = `${baseUrl}novel/page/${page}/?m_orderby=rating`; - - const result = await fetchApi(link, {}, pluginId); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - const novels = []; - - loadedCheerio('.page-item-detail').each(function () { - const name = loadedCheerio(this).find('.h5 > a').text(); - const cover = loadedCheerio(this).find('img').attr('data-src'); - const url = loadedCheerio(this).find('.h5 > a').attr('href'); - - const novel = { - name, - url, - cover, - }; - - novels.push(novel); - }); - - return novels; -}; - -async function parseNovelAndChapters (novelUrl){ - const url = novelUrl; - const result = await fetchApi(url, {}, pluginId); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - const novel = { - url, - chapters: [], - }; - - novel.name = loadedCheerio('.post-title > h1') - .text() - .replace(/[\t\n]/g, '') - .trim(); - - novel.cover = loadedCheerio('.summary_image > a > img').attr('data-src'); - - loadedCheerio('.post-content_item').each(function () { - const detailName = loadedCheerio(this) - .find('.summary-heading > h5') - .text() - .replace(/[\t\n]/g, '') - .trim(); - const detail = loadedCheerio(this) - .find('.summary-content') - .text() - .replace(/[\t\n]/g, '') - .trim(); - - switch (detailName) { - case 'Genre': - novel.genre = detail.trim().replace(/[\t\n]/g, ','); - break; - case 'Author': - novel.author = detail.trim(); - break; - case 'Novel': - novel.status = detail.trim(); - break; - } - }); - - loadedCheerio('.description-summary > div.summary__content') - .find('em') - .remove(); - - novel.summary = loadedCheerio('.description-summary > div.summary__content') - .text() - .trim(); - - let chapter = []; - loadedCheerio('.wp-manga-chapter').each(function () { - const name = loadedCheerio(this) - .find('a') - .text() - .replace(/[\t\n]/g, '') - .trim(); - const releaseTime = loadedCheerio(this).find('span').text().trim(); - const url = loadedCheerio(this).find('a').attr('href'); - - chapter.push({ name, releaseTime, url }); - }); - - novel.chapters = chapter.reverse(); - - return novel; -}; - -async function parseChapter (chapterUrl){ - const result = await fetchApi(chapterUrl, {}, pluginId); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - let chapterText = loadedCheerio('.reading-content').html(); - - return chapterText; -}; - -async function searchNovels (searchTerm) { - const url = `${baseUrl}?s=${searchTerm}&post_type=wp-manga`; - const result = await fetchApi(url, {}, pluginId); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - const novels = []; - - loadedCheerio('.c-tabs-item__content').each(function () { - const novelName = loadedCheerio(this).find('.h4 > a').text(); - const novelCover = loadedCheerio(this).find('img').attr('data-src'); - const novelUrl = loadedCheerio(this).find('.h4 > a').attr('href'); - - novels.push({ - name: novelName, - url: novelUrl, - cover: novelCover, - }); - }); - - return novels; -}; - -async function fetchImage (url){ - const headers = { - Referer: baseUrl, - } - return await fetchFile(url, {headers: headers}); -}; - -module.exports = { - id: pluginId, - name: 'Foxaholic', - version: '1.0.0', - icon: 'src/en/foxaholic/icon.png', - site: baseUrl, - lang: languages.English, - description: 'Digging Pits', - protected: false, - fetchImage, - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, -}; \ No newline at end of file diff --git a/plugins/english/foxaholic.ts b/plugins/english/foxaholic.ts new file mode 100644 index 0000000..d9f4b88 --- /dev/null +++ b/plugins/english/foxaholic.ts @@ -0,0 +1,170 @@ +import { load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "foxaholic"; +export const name = "Foxaholic"; +export const version = "1.0.0"; +export const icon = "src/en/foxaholic/icon.png"; +export const site = "https://www.foxaholic.com/"; +module.exports.protected = false; + +const pluginId = id; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function (page) { + const link = `${baseUrl}novel/page/${page}/?m_orderby=rating`; + + const result = await fetchApi(link, {}, pluginId); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + const novels: Novel.Item[] = []; + + loadedCheerio(".page-item-detail").each(function () { + const novelName = loadedCheerio(this).find(".h5 > a").text(); + const novelCover = loadedCheerio(this).find("img").attr("data-src"); + const novelUrl = loadedCheerio(this).find(".h5 > a").attr("href"); + + if (!novelUrl) return; + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + const result = await fetchApi(url, {}, pluginId); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + const novel: Novel.instance = { + url, + chapters: [], + }; + + novel.name = loadedCheerio(".post-title > h1").text().trim(); + + novel.cover = loadedCheerio(".summary_image > a > img").attr( + "data-src" + ); + + loadedCheerio(".post-content_item").each(function () { + const detailName = loadedCheerio(this) + .find(".summary-heading > h5") + .text() + .trim(); + const detail = loadedCheerio(this).find(".summary-content").html(); + + if (!detail) return; + + switch (detailName) { + case "Genre": + novel.genres = loadedCheerio(detail) + .children("a") + .map((i, el) => loadedCheerio(el).text()) + .toArray() + .join(","); + break; + case "Author": + novel.author = loadedCheerio(detail) + .children("a") + .map((i, el) => loadedCheerio(el).text()) + .toArray() + .join(", "); + break; + case "Novel": + novel.status = detail?.trim().replace(/G/g, "g"); + break; + } + }); + + loadedCheerio( + ".description-summary > div.summary__content > div" + ).remove(); + + novel.summary = loadedCheerio( + ".description-summary > div.summary__content" + ) + .text() + .trim(); + + let chapter: Chapter.Item[] = []; + loadedCheerio(".wp-manga-chapter").each(function () { + const chapterName = loadedCheerio(this) + .find("a") + .text() + .replace(/[\t\n]/g, "") + .trim(); + const releaseDate = loadedCheerio(this).find("span").text().trim(); + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + + if (!chapterUrl) return; + + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + + novel.chapters = chapter.reverse(); + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi(chapterUrl, {}, pluginId); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + loadedCheerio("img").removeAttr("srcset"); + let chapterText = loadedCheerio(".reading-content").html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = `${baseUrl}?s=${searchTerm}&post_type=wp-manga`; + const result = await fetchApi(url, {}, pluginId); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + const novels: Novel.Item[] = []; + + loadedCheerio(".c-tabs-item__content").each(function () { + const novelName = loadedCheerio(this).find(".h4 > a").text(); + const novelCover = loadedCheerio(this).find("img").attr("data-src"); + const novelUrl = loadedCheerio(this).find(".h4 > a").attr("href"); + + if (!novelUrl) return; + + novels.push({ + name: novelName, + url: novelUrl, + cover: novelCover, + }); + }); + + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async function (url) { + const headers = { + Referer: baseUrl, + }; + return await fetchFile(url, { headers: headers }); +}; diff --git a/plugins/english/freenovelupdates.ts b/plugins/english/freenovelupdates.ts new file mode 100644 index 0000000..3313d94 --- /dev/null +++ b/plugins/english/freenovelupdates.ts @@ -0,0 +1,130 @@ +import { load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { showToast } from "@libs/showToast"; +import { isUrlAbsolute } from "@libs/isAbsoluteUrl"; +import { Novel, Plugin } from "@typings/plugin"; + +export const id = "freenovelupdates"; +export const name = "Free Novel Updates (Broken)"; +export const version = "1.0.0"; +export const icon = "src/en/freenovelupdates/icon.png"; +export const site = "https://www.freenovelupdates.com"; +exports.protected = false; + +const pluginId = id; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function (page) { + const url = `${baseUrl}genres/light-novel-1002`; + + const body = await fetchApi(url, {}, pluginId).then((r) => r.text()); + + const loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".books-item").each(function () { + let novelUrl = loadedCheerio(this).find("a").attr("href"); + + if (novelUrl && !isUrlAbsolute(novelUrl)) { + novelUrl = baseUrl + novelUrl; + } + + if (novelUrl) { + const novelName = loadedCheerio(this).find(".title").text().trim(); + let novelCover = loadedCheerio(this).find("img").attr("src"); + + if (novelCover && !isUrlAbsolute(novelCover)) { + novelCover = baseUrl + novelCover; + } + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + } + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + + const body = await fetchApi(url, {}, pluginId).then((r) => r.text()); + + let loadedCheerio = parseHTML(body); + + const novel: Novel.instance = { + url, + chapters: [], + }; + + novel.name = loadedCheerio("h1").text(); + + let novelCover = loadedCheerio(".img > img").attr("src"); + + novel.cover = novelCover + ? isUrlAbsolute(novelCover) + ? novelCover + : baseUrl + novelCover + : undefined; + + novel.summary = loadedCheerio(".description-content").text().trim(); + + novel.author = loadedCheerio(".author").text().trim(); + + novel.genres = loadedCheerio(".category").text().trim(); + + novel.status = loadedCheerio(".status").text().trim(); + + loadedCheerio(".chapter").each(function () { + let chapterUrl = loadedCheerio(this).find("a").attr("href"); + + if (chapterUrl && !isUrlAbsolute(chapterUrl)) { + chapterUrl = baseUrl + chapterUrl; + } + + if (chapterUrl) { + const chapterName = loadedCheerio(this).find("a").text().trim(); + const releaseDate = null; + + const chapter = { + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }; + + novel.chapters?.push(chapter); + } + }); + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const body = await fetchApi(chapterUrl, {}, pluginId).then((r) => r.text()); + + const loadedCheerio = parseHTML(body); + + const chapterText = loadedCheerio(".content").html() || ""; + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + showToast("Search is not available in this source"); + + return []; +}; + +export const fetchImage: Plugin.fetchImage = async function (url) { + const headers = { + Referer: baseUrl, + }; + return await fetchFile(url, { headers: headers }); +}; diff --git a/plugins/english/freewebnovel.js b/plugins/english/freewebnovel.js deleted file mode 100644 index f93cd68..0000000 --- a/plugins/english/freewebnovel.js +++ /dev/null @@ -1,173 +0,0 @@ -const cheerio = require('cheerio'); -const fetchApi = require('@libs/fetchApi'); -const fetchFile = require('@libs/fetchFile'); -const languages = require('@libs/languages'); - -const pluginId = 'FWN.com'; -const sourceName = "Web NOVEL"; -const baseUrl = 'https://freewebnovel.com/'; - -async function popularNovels (page) { - let url = baseUrl + 'completed-novel/' + page; - - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('.li-row').each(function () { - const novelName = loadedCheerio(this).find('.tit').text(); - const novelCover = loadedCheerio(this).find('img').attr('src'); - - let novelUrl = 'https://freewebnovel.com' + loadedCheerio(this) - .find('h3 > a') - .attr('href') - - const novel = { - name: novelName, - cover: novelCover, - url: novelUrl, - }; - - novels.push(novel); - }); - - return novels; -}; - -async function parseNovelAndChapters (novelUrl) { - const url = novelUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - let novel = {}; - - novel.url = url; - - novel.name = loadedCheerio('h1.tit').text(); - - novel.cover = loadedCheerio('.pic > img').attr('src'); - - novel.genres = loadedCheerio('[title=Genre]') - .next() - .text() - .replace(/[\t\n]/g, ''); - - novel.author = loadedCheerio('[title=Author]') - .next() - .text() - .replace(/[\t\n]/g, ''); - - novel.artist = null; - - novel.status = loadedCheerio('[title=Status]') - .next() - .text() - .replace(/[\t\n]/g, ''); - - let novelSummary = loadedCheerio('.inner').text().trim(); - novel.summary = novelSummary; - - let novelChapters = []; - - let latestChapter; - - loadedCheerio('h3.tit').each(function (res) { - if (loadedCheerio(this).find('a').text() === novel.name) { - latestChapter = loadedCheerio(this) - .next() - .find('span.s3') - .text() - .match(/\d+/); - } - }); - - latestChapter = latestChapter[0]; - let prefixUrl = novelUrl.replace('.html', '/'); - - for (let i = 1; i <= parseInt(latestChapter, 10); i++) { - const chapterName = 'Chapter ' + i; - - const releaseDate = null; - - let chapterUrl = 'chapter-' + i; - chapterUrl = `${prefixUrl}${chapterUrl}.html` - const chapter = { name: chapterName, releaseTime: releaseDate, url: chapterUrl }; - - novelChapters.push(chapter); - } - - novel.chapters = novelChapters; - - return novel; -}; - -async function parseChapter (chapterUrl) { - const url = chapterUrl; - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - let chapterText = loadedCheerio('div.txt').html(); - - return chapterText; -}; - -async function searchNovels (searchTerm) { - const url = baseUrl + 'search/'; - - const formData = { - searchkey: searchTerm, - }; - - const result = await fetchApi(url, { - method: 'POST', - body: formData, - }); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('.li-row > .li > .con').each(function () { - const novelName = loadedCheerio(this).find('.tit').text(); - const novelCover = loadedCheerio(this) - .find('.pic > a > img') - .attr('data-cfsrc'); - - let novelUrl = "https://freewebnovel.com" + loadedCheerio(this) - .find('h3 > a') - .attr('href') - - const novel = { - name: novelName, - cover: novelCover, - url: novelUrl, - }; - - novels.push(novel); - }); - - return novels; -}; - -module.exports = { - id: pluginId, - name: sourceName, - site: baseUrl, - version: '1.0.0', - lang: languages.English, - icon: 'src/en/freewebnovel/icon.png', - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; diff --git a/plugins/english/freewebnovel.ts b/plugins/english/freewebnovel.ts new file mode 100644 index 0000000..4505f5c --- /dev/null +++ b/plugins/english/freewebnovel.ts @@ -0,0 +1,166 @@ +import { load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Novel, Plugin } from "@typings/plugin"; + +export const id = "FWN.com"; +export const name = "Web NOVEL"; +export const site = "https://freewebnovel.com/"; +export const version = "1.0.0"; +export const icon = "src/en/freewebnovel/icon.png"; + +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function (page) { + let url = baseUrl + "completed-novel/" + page; + + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".li-row").each(function () { + const novelName = loadedCheerio(this).find(".tit").text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + + let novelUrl = + "https://freewebnovel.com" + + loadedCheerio(this).find("h3 > a").attr("href"); + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + let novel: Novel.instance = { + url, + }; + + novel.name = loadedCheerio("h1.tit").text(); + + novel.cover = loadedCheerio(".pic > img").attr("src"); + + novel.genres = loadedCheerio("[title=Genre]") + .next() + .text() + .replace(/[\t\n]/g, ""); + + novel.author = loadedCheerio("[title=Author]") + .next() + .text() + .replace(/[\t\n]/g, ""); + + novel.status = loadedCheerio("[title=Status]") + .next() + .text() + .replace(/[\t\n]/g, ""); + + let novelSummary = loadedCheerio(".inner").text().trim(); + novel.summary = novelSummary; + + let novelChapters = []; + + let latestChapter: string | undefined; + + loadedCheerio("h3.tit").each(function (res) { + if (loadedCheerio(this).find("a").text() === novel.name) { + latestChapter = loadedCheerio(this) + .next() + .find("span.s3") + .text() + .match(/(\d+)/)?.[0]; + } + }); + + let prefixUrl = novelUrl.replace(".html", "/"); + + for (let i = 1; i <= parseInt(latestChapter || "0", 10); i++) { + const chapterName = "Chapter " + i; + + const releaseDate = null; + + let chapterUrl = "chapter-" + i; + chapterUrl = `${prefixUrl}${chapterUrl}.html`; + const chapter = { + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }; + + novelChapters.push(chapter); + } + + novel.chapters = novelChapters; + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const url = chapterUrl; + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + let chapterText = loadedCheerio("div.txt").html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = baseUrl + "search/"; + + const formData = { + searchkey: searchTerm, + }; + + const result = await fetchApi(url, { + method: "POST", + body: JSON.stringify(formData), + }); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".li-row > .li > .con").each(function () { + const novelName = loadedCheerio(this).find(".tit").text(); + const novelCover = loadedCheerio(this) + .find(".pic > a > img") + .attr("data-cfsrc"); + + let novelUrl = + "https://freewebnovel.com" + + loadedCheerio(this).find("h3 > a").attr("href"); + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const fetchImage: Plugin.fetchImage = fetchFile; diff --git a/plugins/english/lightnovelpub.ts b/plugins/english/lightnovelpub.ts new file mode 100644 index 0000000..cbc3f31 --- /dev/null +++ b/plugins/english/lightnovelpub.ts @@ -0,0 +1,213 @@ +import { load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "lightnovelpub"; +export const name = "LightNovelPub"; +export const version = "1.0.0"; +export const icon = "src/en/lightnovelpub/icon.png"; +export const site = "https://www.lightnovelpub.com/"; +exports.protected = true; + +const baseUrl = site; +const pluginId = id; + +const headers = { + Accept: "application/json", + "Content-Type": "application/json", +}; + +export const popularNovels: Plugin.popularNovels = async function (page) { + const url = `${baseUrl}browse/all/popular/all/${page}`; + + const body = await fetchApi(url, {}, pluginId).then((r) => r.text()); + + const loadedCheerio = parseHTML(body); + + const novels: Novel.Item[] = []; + + loadedCheerio(".novel-item.ads").remove(); + + loadedCheerio(".novel-item").each(function () { + const novelName = loadedCheerio(this) + .find(".novel-title") + .text() + .trim(); + const novelCover = loadedCheerio(this).find("img").attr("data-src"); + const novelUrl = + baseUrl + + loadedCheerio(this) + .find(".novel-title > a") + .attr("href") + ?.substring(1); + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + const body = await fetchApi(url, {}, pluginId).then((r) => r.text()); + + let loadedCheerio = parseHTML(body); + + const novel: Novel.instance = { + url, + chapters: [], + }; + + novel.name = loadedCheerio("h1.novel-title").text().trim(); + + novel.cover = loadedCheerio("figure.cover > img").attr("data-src"); + + novel.genres = ""; + + loadedCheerio(".categories > ul > li > a").each(function () { + novel.genres += loadedCheerio(this).text() + ","; + }); + + novel.genres = novel.genres.slice(0, -1); + + loadedCheerio("div.header-stats > span").each(function () { + if (loadedCheerio(this).find("small").text() === "Status") { + novel.status = loadedCheerio(this).find("strong").text(); + } + }); + + novel.author = loadedCheerio(".author > a > span").text(); + + novel.summary = loadedCheerio(".summary > .content").text().trim(); + + const delay = (ms: number) => new Promise((res) => setTimeout(res, ms)); + + let lastPage = 1; + + lastPage = parseInt( + loadedCheerio( + "#novel > header > div.header-body.container > div.novel-info > div.header-stats > span:nth-child(1) > strong" + ) + .text() + ?.trim() + ); + + lastPage = Math.ceil(lastPage / 100); + + async function getChapters() { + let chapter: Chapter.Item[] = []; + + for (let i = 1; i <= lastPage; i++) { + const chaptersUrl = `${novelUrl}/chapters/page-${i}`; + + const chaptersHtml = await fetchApi( + chaptersUrl, + { headers: headers }, + pluginId + ).then((r) => r.text()); + + loadedCheerio = parseHTML(chaptersHtml); + + loadedCheerio(".chapter-list li").each(function () { + const chapterName = loadedCheerio(this) + .find(".chapter-title") + .text() + .trim(); + + const releaseDate = loadedCheerio(this) + .find(".chapter-update") + .text() + .trim(); + + const chapterUrl = + baseUrl + + loadedCheerio(this) + .find("a") + .attr("href") + ?.substring(1); + + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + + await delay(1000); + } + + return chapter; + } + + novel.chapters = await getChapters(); + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const body = await fetchApi(chapterUrl, {}, pluginId).then((r) => r.text()); + + const loadedCheerio = parseHTML(body); + + let chapterText = loadedCheerio("#chapter-container").html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = `${baseUrl}lnsearchlive`; + const link = `${baseUrl}search`; + const response = await fetchApi(link, {}, pluginId).then((r) => r.text()); + const token = parseHTML(response); + let verifytoken = token("#novelSearchForm > input").attr("value"); + + let formData = new FormData(); + formData.append("inputContent", searchTerm); + + const body = await fetchApi( + url, + { + method: "POST", + headers: { LNRequestVerifyToken: verifytoken! }, + body: formData, + }, + pluginId + ).then((r) => r.text()); + + let loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + let results = JSON.parse(loadedCheerio("body").text()); + + loadedCheerio = parseHTML(results.resultview); + + loadedCheerio(".novel-item").each(function () { + const novelName = loadedCheerio(this) + .find("h4.novel-title") + .text() + .trim(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = + baseUrl + loadedCheerio(this).find("a").attr("href")?.substring(1); + + novels.push({ + name: novelName, + url: novelUrl, + cover: novelCover, + }); + }); + + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async function (url) { + return await fetchFile(url, { headers: headers }); +}; diff --git a/plugins/english/lightnovelupdates.broken.ts b/plugins/english/lightnovelupdates.broken.ts new file mode 100644 index 0000000..aab029b --- /dev/null +++ b/plugins/english/lightnovelupdates.broken.ts @@ -0,0 +1,161 @@ +import { load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "LightNovelUpdates"; +export const name = "Light Novel Updates"; +export const version = "1.0.0"; +export const icon = "src/en/lightnovelupdates/icon.png"; +export const site = "https://www.lightnovelupdates.com/"; +exports.protected = false; + +const pluginId = id; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function (page) { + let url = `${baseUrl}novel/page/${page}/?m_orderby=rating`; + + const body = await fetchApi(url, {}, pluginId).then((r) => r.text()); + + const loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".page-item-detail").each(function () { + const novelName = loadedCheerio(this).find(".h5 > a").text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this).find(".h5 > a").attr("href"); + + if (!novelUrl) return; + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + + const body = await fetchApi(url, {}, pluginId).then((r) => r.text()); + + let loadedCheerio = parseHTML(body); + + const novel: Novel.instance = { + url, + chapters: [], + }; + + novel.name = loadedCheerio(".post-title > h1").text().trim(); + novel.cover = + loadedCheerio(".summary_image > a > img").attr("src") || undefined; + + loadedCheerio(".post-content_item").each(function () { + const detailName = loadedCheerio(this) + .find(".summary-heading > h5") + .text() + .trim(); + const detail = loadedCheerio(this) + .find(".summary-content") + .text() + .trim(); + + switch (detailName) { + case "Genre(s)": + novel.genres = detail.trim().replace(/[\t\n]/g, ","); + break; + case "Author(s)": + novel.author = detail.trim(); + break; + case "Status": + novel.status = detail.replace(/G/g, "g"); + break; + } + }); + + novel.summary = loadedCheerio("div.summary__content").text(); + + const chapter: Chapter.Item[] = []; + + const novelId = loadedCheerio(".rating-post-id").attr("value")!; + + let formData = new FormData(); + formData.append("action", "manga_get_chapters"); + formData.append("manga", novelId); + + const data = await fetchApi( + "https://www.lightnovelupdates.com/wp-admin/admin-ajax.php", + { + method: "POST", + body: formData, + }, + pluginId + ); + const text = await data.text(); + + loadedCheerio = parseHTML(text); + + loadedCheerio(".wp-manga-chapter").each(function () { + const chapterName = loadedCheerio(this).find("a").text().trim(); + const releaseDate = loadedCheerio(this).find("span").text().trim(); + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + + if (!chapterUrl) return; + + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + + novel.chapters = chapter.reverse(); + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const chapterText = (await NovelUpdatesScraper.parseChapter(chapterUrl)) + .chapterText; + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = `${baseUrl}?s=${searchTerm}&post_type=wp-manga&m_orderby=rating`; + + const body = await fetchApi(url, {}, pluginId).then((r) => r.text()); + + const loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".c-tabs-item__content").each(function () { + const novelName = loadedCheerio(this).find(".h4 > a").text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this).find(".h4 > a").attr("href"); + + if (!novelUrl) return; + + novels.push({ + url: novelUrl, + name: novelName, + cover: novelCover, + }); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async function (url) { + const headers = { + Referer: baseUrl, + }; + return await fetchFile(url, { headers: headers }); +}; diff --git a/plugins/english/lnmtl.broken.ts b/plugins/english/lnmtl.broken.ts new file mode 100644 index 0000000..b7755d0 --- /dev/null +++ b/plugins/english/lnmtl.broken.ts @@ -0,0 +1,183 @@ +import { load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { showToast } from "@libs/showToast"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +const pluginId = "lnmtl"; +const baseUrl = "https://lnmtl.com/"; + +export const popularNovels: Plugin.popularNovels = async function (page) { + let url = baseUrl + "novel?page=" + page; + + const body = await fetchApi(url, {}, pluginId).then((r) => r.text()); + + const loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".media").each(function () { + const novelName = loadedCheerio(this).find("h4").text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this).find("h4 > a").attr("href"); + + if (!novelUrl) return; + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + showToast("LNMTL might take around 20-30 seconds."); + + const url = novelUrl; + + const body = await fetchApi(url, {}, pluginId).then((r) => r.text()); + + const loadedCheerio = parseHTML(body); + + const novel: Novel.instance = { + url, + chapters: [], + }; + + novel.name = loadedCheerio(".novel-name").text(); + + novel.cover = loadedCheerio("div.novel").find("img").attr("src"); + + novel.summary = loadedCheerio("div.description").text().trim(); + + loadedCheerio(".panel-body > dl").each(function () { + let detailName = loadedCheerio(this).find("dt").text().trim(); + let detail = loadedCheerio(this).find("dd").text().trim(); + + switch (detailName) { + case "Authors": + novel.author = detail; + break; + case "Current status": + novel.status = detail; + break; + } + }); + + novel.genres = loadedCheerio('.panel-heading:contains(" Genres ")') + .next() + .text() + .trim() + .replace(/\s\s/g, ","); + + let volumes = JSON.parse( + loadedCheerio("main") + .next() + .html() + .match(/lnmtl.volumes = \[(.*?)\]/).[0] + .replace("lnmtl.volumes = ", "") + ); + + let chapters: Chapter.Item[] = []; + + volumes = volumes.map((volume) => volume.id); + + for (const volume of volumes) { + let volumeData = await fetchApi( + `https://lnmtl.com/chapter?page=1&volumeId=${volume}` + ); + volumeData = await volumeData.json(); + + // volumeData = volumeData.data.map((volume) => volume.slug); + + for (let i = 1; i <= volumeData.last_page; i++) { + let chapterData = await fetchApi( + `https://lnmtl.com/chapter?page=${i}&volumeId=${volume}` + ); + chapterData = await chapterData.json(); + + chapterData = chapterData.data.map((chapter) => ({ + name: `#${chapter.number} ${chapter.title}`, + url: `${baseUrl}chapter/${chapter.slug}`, + releaseTime: chapter.created_at, + })); + + chapters.push(...chapterData); + } + } + + novel.chapters = chapters; + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const body = await fetchApi(chapterUrl, {}, pluginId).then((r) => r.text()); + + const loadedCheerio = parseHTML(body); + + loadedCheerio(".original").replaceWith("

"); + + let chapterText = loadedCheerio(".chapter-body").html(); + + if (!chapterText) { + chapterText = loadedCheerio(".alert.alert-warning").text(); + } + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = "https://lnmtl.com/term"; + + const body = await fetchApi(url, {}, pluginId).then((r) => r.text()); + + const loadedCheerio = parseHTML(body); + + let novels:Novel.Item[] = loadedCheerio("footer") + .next() + .next() + .html() + .match(/local: \[(.*?)\]/)[0] + .replace("local: ", ""); + + novels = JSON.parse(novels); + + novels = novels.filter((novel) => + novel.name.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + novels = novels.map((novel) => ({ + name: novel.name, + url: novel.slug, + cover: novel.image, + })); + + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async function (url) { + const headers = { + Referer: baseUrl, + }; + return await fetchFile(url, { headers: headers }); +}; + +module.exports = { + id: pluginId, + name: "LNMTL", + version: "1.0.0", + icon: "src/en/lnmtl/icon.png", + site: baseUrl, + protected: true, + fetchImage, + popularNovels, + parseNovelAndChapters, + parseChapter, + searchNovels, +}; diff --git a/plugins/english/mtlnovel.broken.ts b/plugins/english/mtlnovel.broken.ts new file mode 100644 index 0000000..89fa757 --- /dev/null +++ b/plugins/english/mtlnovel.broken.ts @@ -0,0 +1,180 @@ +import { load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +const pluginId = "mtlnovel"; +const baseUrl = "https://www.mtlnovel.com/"; + +export const popularNovels: Plugin.popularNovels = async function (page) { + const url = `${baseUrl}alltime-rank/page/${page}`; + + const headers = { + Referer: `${baseUrl}`, + "User-Agent": + "'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36", + }; + + const body = await fetchApi( + url, + { + method: "GET", + headers: headers, + }, + pluginId + ).then((result) => result.text()); + + const loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio("div.box.wide").each(function () { + const novelName = loadedCheerio(this) + .find("a.list-title") + .text() + .slice(4); + const novelCover = loadedCheerio(this).find("amp-img").attr("src"); + const novelUrl = loadedCheerio(this).find("a.list-title").attr("href"); + + if (!novelUrl) return; + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + + let headers = { + Referer: `${baseUrl}alltime-rank/`, + "User-Agent": + "'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36", + }; + + const body = await fetchApi( + url, + { + method: "GET", + headers: headers, + }, + pluginId + ).then((result) => result.text()); + + let loadedCheerio = parseHTML(body); + + const novel: Novel.instance = { + url, + chapters: [], + }; + + novel.name = loadedCheerio("h1.entry-title").text(); + + novel.cover = loadedCheerio(".nov-head > amp-img").attr("src"); + + novel.summary = loadedCheerio("div.desc > h2").next().text().trim(); + + novel.author = loadedCheerio("#author").text(); + + novel.status = loadedCheerio("#status").text(); + + novel.genres = loadedCheerio("#genre").text().replace(/\s+/g, ""); + + const chapterListUrl = url + "chapter-list/"; + + async function getChapters() { + const listResult = await fetch(chapterListUrl); + const listBody = await listResult.text(); + + loadedCheerio = parseHTML(listBody); + + let chapter: Chapter.Item[] = []; + + loadedCheerio("div.ch-list") + .find("a.ch-link") + .each(function () { + const chapterName = loadedCheerio(this) + .text() + .replace("~ ", ""); + const releaseDate = null; + const chapterUrl = loadedCheerio(this).attr("href"); + + chapter.push({ + url: chapterUrl, + name: chapterName, + releaseTime: releaseDate, + }); + }); + return chapter.reverse(); + } + + novel.chapters = await getChapters(); + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const body = await fetchApi(chapterUrl, {}, pluginId).then((r) => r.text()); + + const loadedCheerio = parseHTML(body); + + const chapterText = loadedCheerio("div.par").html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const searchUrl = + baseUrl + + "wp-admin/admin-ajax.php?action=autosuggest&q=" + + searchTerm + + "&__amp_source_origin=https%3A%2F%2Fwww.mtlnovel.com"; + + const res = await fetch(searchUrl); + const result = await res.json(); + + let novels: Novel.Item[] = []; + + result.items[0].results.map((item) => { + const novelName = item.title.replace(/<\/?strong>/g, ""); + const novelCover = item.thumbnail; + const novelUrl = item.permalink.replace( + "https://www.mtlnovel.com/", + "" + ); + + const novel = { name: novelName, cover: novelCover, url: novelUrl }; + + novels.push(novel); + }); + + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async function (url) { + const headers = { + Referer: baseUrl, + }; + return await fetchFile(url, { headers: headers }); +}; + +module.exports = { + id: pluginId, + name: "MTL Novel", + version: "1.0.0", + icon: "src/en/mtlnovel/icon.png", + site: baseUrl, + protected: false, + fetchImage, + popularNovels, + parseNovelAndChapters, + parseChapter, + searchNovels, +}; diff --git a/plugins/english/mtlreader.ts b/plugins/english/mtlreader.ts new file mode 100644 index 0000000..ef9f8b3 --- /dev/null +++ b/plugins/english/mtlreader.ts @@ -0,0 +1,133 @@ +import { load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "mtlreader"; +export const name = "MTL Reader"; +export const version = "1.0.0"; +export const icon = "src/en/mtlreader/icon.png"; +export const site = "https://mtlreader.com/"; +exports.protected = false; + +const pluginId = id; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function (page) { + const url = `${baseUrl}novels?page=${page}`; + + const body = await fetchApi(url, {}, pluginId).then((r) => r.text()); + + const loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".col-md-4.col-sm-4").each(function () { + const novelName = loadedCheerio(this).find("h5").text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this).find("h5 > a").attr("href"); + + if (!novelUrl) return; + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + + const body = await fetchApi(url, {}, pluginId).then((r) => r.text()); + + let loadedCheerio = parseHTML(body); + + const novel: Novel.instance = { + url, + chapters: [], + }; + + novel.name = loadedCheerio(".agent-title").text().trim(); + + novel.cover = loadedCheerio(".agent-p-img > img").attr("src"); + + novel.summary = loadedCheerio("#editdescription").text().trim(); + + novel.author = loadedCheerio("i.fa-user") + .parent() + .text() + .replace("Author: ", "") + .trim(); + + let chapter: Chapter.Item[] = []; + + loadedCheerio("tr.spaceUnder").each(function () { + const chapterName = loadedCheerio(this).find("a").text().trim(); + const releaseDate = null; + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + + if (!chapterUrl) return; + + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + + novel.chapters = chapter; + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const body = await fetchApi(chapterUrl, {}, pluginId).then((r) => r.text()); + + const loadedCheerio = parseHTML(body); + + loadedCheerio(".container ins,script,p.mtlreader").remove(); + const chapterText = loadedCheerio(".container").html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = `${baseUrl}search?input=${searchTerm}`; + + const body = await fetchApi(url, {}, pluginId).then((r) => r.text()); + + let loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".col-md-4.col-sm-4").each(function () { + const novelName = loadedCheerio(this).find("h5").text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this).find("h5 > a").attr("href"); + + if (!novelUrl) return; + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async function (url) { + const headers = { + Referer: baseUrl, + }; + return await fetchFile(url, { headers: headers }); +}; diff --git a/plugins/english/nitroscans.ts b/plugins/english/nitroscans.ts new file mode 100644 index 0000000..cfccd0b --- /dev/null +++ b/plugins/english/nitroscans.ts @@ -0,0 +1,182 @@ +import { load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "nitroscans"; +export const name = "Nitroscans"; +export const version = "1.0.0"; +export const icon = "src/en/nitroscans/icon.png"; +export const site = "https://nitroscans.com/"; +exports.protected = false; + +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function (page) { + const url = baseUrl + "wp-admin/admin-ajax.php"; + + let formData = new FormData(); + formData.append("action", "madara_load_more"); + formData.append("page", `${Number(page - 1)}`); + formData.append("template", "madara-core/content/content-archive"); + formData.append("vars[orderby]", "meta_value_num"); + formData.append("vars[post_type]", "wp-manga"); + formData.append("vars[meta_key]", "_wp_manga_views"); + formData.append("vars[wp-manga-genre]", "novels"); + + const body = await fetchApi(url, { + method: "POST", + body: formData, + }).then((r) => r.text()); + + const loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".page-item-detail").each(function () { + const novelName = loadedCheerio(this).find("h3 > a").text(); + const novelCover = loadedCheerio(this).find("img").attr("data-src"); + const novelUrl = loadedCheerio(this).find("h3 > a").attr("href"); + + if (!novelUrl) return; + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + + const body = await fetchApi(url).then((r) => r.text()); + + let loadedCheerio = parseHTML(body); + + const novel: Novel.instance = { + url, + chapters: [], + }; + + loadedCheerio(".manga-title-badges.custom.novel").remove(); + + novel.name = loadedCheerio(".post-title > h1").text().trim(); + novel.cover = loadedCheerio(".summary_image") + .find("img") + .attr("data-src"); + + novel.summary = loadedCheerio(".summary__content").text()?.trim(); + + novel.genres = loadedCheerio(".genres-content") + .children("a") + .map((i, el) => loadedCheerio(el).text()) + .toArray() + .join(","); + + loadedCheerio(".post-content_item").each(function () { + const detailName = loadedCheerio(this) + .find(".summary-heading") + .text() + .trim(); + const detail = loadedCheerio(this) + .find(".summary-content") + .text() + .trim(); + switch (detailName) { + case "Author(s)": + novel.author = detail; + break; + case "Status": + novel.status = detail.replace(/G/g, "g"); + break; + } + }); + let chapter: Chapter.Item[] = []; + + let chapterlisturl = novelUrl + "ajax/chapters/"; + + const data = await fetchApi(chapterlisturl, { method: "POST" }); + const text = await data.text(); + + loadedCheerio = parseHTML(text); + + loadedCheerio(".wp-manga-chapter").each(function () { + const chapterName = loadedCheerio(this).find("a").text().trim(); + const releaseDate = loadedCheerio(this).find("span").text().trim(); + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + + if (!chapterUrl) return; + + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + + novel.chapters = chapter.reverse(); + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const body = await fetchApi(chapterUrl).then((r) => r.text()); + + let loadedCheerio = parseHTML(body); + + const chapterText = loadedCheerio(".text-left").html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = baseUrl + "wp-admin/admin-ajax.php"; + + let formData = new FormData(); + formData.append("action", "madara_load_more"); + formData.append("page", `${Number(1 - 1)}`); + formData.append("template", "madara-core/content/content-search"); + formData.append("vars[s]", searchTerm); + formData.append("vars[post_type]", "wp-manga"); + formData.append("vars[wp-manga-genre]", "novels"); + + const body = await fetchApi(url, { + method: "POST", + body: formData, + }).then((r) => r.text()); + + let loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".c-tabs-item__content").each(function () { + const novelName = loadedCheerio(this).find("h3 > a").text(); + const novelCover = loadedCheerio(this).find("img").attr("data-src"); + const novelUrl = loadedCheerio(this).find("h3 > a").attr("href"); + + if (!novelUrl) return; + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async function (url) { + const headers = { + Referer: baseUrl, + }; + return await fetchFile(url, { headers: headers }); +}; diff --git a/plugins/english/nobleMTL.ts b/plugins/english/nobleMTL.ts new file mode 100644 index 0000000..c427bf7 --- /dev/null +++ b/plugins/english/nobleMTL.ts @@ -0,0 +1,416 @@ +import { load as cheerioload } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { Filter, FilterInputs } from "@libs/filterInputs"; + +export const id = "NobleMTL"; +export const name = "NobleMTL"; +export const version = "1.0.0"; +export const icon = "src/en/noblemtl/icon.png"; +export const site = "https://www.noblemtl.com/"; +exports.protected = false; + +export const popularNovels: Plugin.popularNovels = async function ( + page, + { filters } +) { + let link = `${site}series/?page=${page}`; + + if (filters?.genres?.length) { + if (Array.isArray(filters.genres)) + link += filters.genres.map((i) => `&genre[]=${i}`).join(""); + } + + if (filters?.type?.length) { + if (Array.isArray(filters.type)) + link += filters.type.map((i) => `&lang[]=${i}`).join(""); + } + + link += "&status=" + (filters?.status ? filters?.status : ""); + + link += "&order=" + (filters?.order ? filters?.order : "popular"); + + const body = await fetchApi(link).then((result) => result.text()); + + const loadedCheerio = cheerioload(body); + + const novels: Novel.Item[] = []; + + loadedCheerio("article.bs").each(function () { + const novelName = loadedCheerio(this).find(".ntitle").text().trim(); + let image = loadedCheerio(this).find("img"); + const novelCover = image.attr("data-src") || image.attr("src"); + + const novelUrl = loadedCheerio(this).find("a").attr("href"); + + if (!novelUrl) return; + + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function parseNovelAndChapters(novelUrl) { + const url = novelUrl; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = cheerioload(body); + + const novel: Novel.instance = { + url, + chapters: [], + }; + + novel.name = loadedCheerio(".entry-title").text(); + + novel.cover = + loadedCheerio("img.wp-post-image").attr("data-src") || + loadedCheerio("img.wp-post-image").attr("src"); + + loadedCheerio("div.spe > span").each(function () { + const detailName = loadedCheerio(this).find("b").text().trim(); + const detail = loadedCheerio(this) + .find("b") + .remove() + .end() + .text() + .trim(); + + switch (detailName) { + case "المؤلف:": + case "Yazar:": + case "Autor:": + case "Author:": + novel.author = detail; + break; + case "Status:": + case "Seviye:": + novel.status = detail; + break; + } + }); + + novel.genres = loadedCheerio(".genxed") + .text() + .trim() + .replace(/\s/g, ","); + + loadedCheerio('div[itemprop="description"] h3,p.a,strong').remove(); + novel.summary = loadedCheerio('div[itemprop="description"]') + .find("br") + .replaceWith("\n") + .end() + .text(); + + let chapter: Chapter.Item[] = []; + + loadedCheerio(".eplister") + .find("li") + .each(function () { + const chapterName = + loadedCheerio(this).find(".epl-num").text() + + " - " + + loadedCheerio(this).find(".epl-title").text(); + + const releaseDate = loadedCheerio(this) + .find(".epl-date") + .text() + .trim(); + + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + + if (!chapterUrl) return; + + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + + novel.chapters = chapter.reverse(); + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi(chapterUrl); + const body = await result.text(); + + const loadedCheerio = cheerioload(body); + + let chapterText = loadedCheerio("div.epcontent").html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = `${site}?s=${searchTerm}`; + + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = cheerioload(body); + + const novels: Novel.Item[] = []; + + loadedCheerio("article.bs").each(function () { + const novelName = loadedCheerio(this).find(".ntitle").text().trim(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this).find("a").attr("href"); + if (!novelUrl) return; + + novels.push({ + name: novelName, + url: novelUrl, + cover: novelCover, + }); + }); + + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async function (url) { + const headers = { + Referer: site, + }; + return await fetchFile(url, { headers: headers }); +}; + +export const filters: Filter[] = [ + { + key: "order", + label: "Sort By", + values: [ + { label: "Default", value: "" }, + + { label: "A-Z", value: "title" }, + + { label: "Z-A", value: "titlereverse" }, + + { label: "Latest Update", value: "update" }, + + { label: "Latest Added", value: "latest" }, + + { label: "Popular", value: "popular" }, + ], + inputType: FilterInputs.Picker, + }, + { + key: "status", + label: "Status", + values: [ + { label: "All", value: "" }, + + { label: "Ongoing", value: "ongoing" }, + + { label: "Hiatus", value: "hiatus" }, + + { label: "Completed", value: "completed" }, + ], + inputType: FilterInputs.Picker, + }, + { + key: "type", + label: "Type", + values: [ + { label: "Chinese novel", value: "chinese-novel" }, + + { label: "habyeol", value: "habyeol" }, + + { label: "korean novel", value: "korean-novel" }, + + { label: "Web Novel", value: "web-novel" }, + + { label: "삼심", value: "%ec%82%bc%ec%8b%ac" }, + + { label: "호곡", value: "%ed%98%b8%ea%b3%a1" }, + ], + inputType: FilterInputs.Checkbox, + }, + { + key: "genres", + label: "Genres", + values: [ + { label: "A.I", value: "a.i" }, + + { label: "Academy", value: "academy" }, + + { label: "Action", value: "action" }, + + { label: "Adult", value: "adult" }, + + { label: "Adventure", value: "adventure" }, + + { label: "Alternative History", value: "alternative-history" }, + + { label: "Another World", value: "another-world" }, + + { label: "Apocalypse", value: "apocalypse" }, + + { label: "Bromance", value: "bromance" }, + + { label: "Comedy", value: "comedy" }, + + { label: "Dark fantasy", value: "dark-fantasy" }, + + { label: "Demons", value: "demons" }, + + { label: "Drama", value: "drama" }, + + { label: "Dystopia", value: "dystopia" }, + + { label: "Ecchi", value: "ecchi" }, + + { label: "Entertainment", value: "entertainment" }, + + { label: "Exhaustion", value: "exhaustion" }, + + { label: "Fanfiction", value: "fanfiction" }, + + { label: "fantasy", value: "fantasy" }, + + { label: "finance", value: "finance" }, + + { label: "Full color", value: "full-color" }, + + { label: "Game", value: "game" }, + + { label: "Gender Bender", value: "gender-bender" }, + + { label: "Genius", value: "genius" }, + + { label: "Harem", value: "harem" }, + + { label: "Hero", value: "hero" }, + + { label: "Historical", value: "historical" }, + + { label: "Hunter", value: "hunter" }, + + { label: "korean novel", value: "korean-novel" }, + + { label: "Light Novel", value: "light-novel" }, + + { + label: "List Adventure Manga Genres", + value: "list-adventure-manga-genres", + }, + + { label: "Long Strip", value: "long-strip" }, + + { label: "Love comedy", value: "love-comedy" }, + + { label: "magic", value: "magic" }, + + { label: "Manhua", value: "manhua" }, + + { label: "Martial Arts", value: "martial-arts" }, + + { label: "Mature", value: "mature" }, + + { label: "Medieval", value: "medieval" }, + + { label: "Misunderstanding", value: "misunderstanding" }, + + { label: "Modern", value: "modern" }, + + { label: "modern fantasy", value: "modern-fantasy" }, + + { label: "music", value: "music" }, + + { label: "Mystery", value: "mystery" }, + + { label: "Necromancy", value: "necromancy" }, + + { label: "No Romance", value: "no-romance" }, + + { label: "NTL", value: "ntl" }, + + { label: "o", value: "o" }, + + { label: "Obsession", value: "obsession" }, + + { label: "Politics", value: "politics" }, + + { label: "Possession", value: "possession" }, + + { label: "Programming", value: "programming" }, + + { label: "Psychological", value: "psychological" }, + + { label: "Pure Love", value: "pure-love" }, + + { label: "Redemption", value: "redemption" }, + + { label: "Regression", value: "regression" }, + + { label: "Regret", value: "regret" }, + + { label: "Reincarnation", value: "reincarnation" }, + + { label: "Revenge", value: "revenge" }, + + { label: "Romance", value: "romance" }, + + { label: "Romance Fanrasy", value: "romance-fanrasy" }, + + { label: "Salvation", value: "salvation" }, + + { label: "School Life", value: "school-life" }, + + { label: "Sci-fi", value: "sci-fi" }, + + { label: "Science fiction", value: "science-fiction" }, + + { label: "Seinen", value: "seinen" }, + + { label: "Shounen", value: "shounen" }, + + { label: "Slice of Life", value: "slice-of-life" }, + + { label: "Soft yandere", value: "soft-yandere" }, + + { label: "Sports", value: "sports" }, + + { label: "Supernatural", value: "supernatural" }, + + { label: "Survival", value: "survival" }, + + { label: "system", value: "system" }, + + { label: "Time limit", value: "time-limit" }, + + { label: "Tragedy", value: "tragedy" }, + + { label: "Transmigration", value: "transmigration" }, + + { label: "TS", value: "ts" }, + + { label: "Tsundere", value: "tsundere" }, + + { label: "Unique", value: "unique" }, + + { label: "Wholesome", value: "wholesome" }, + + { label: "Wuxia", value: "wuxia" }, + + { label: "Xuanhuan", value: "xuanhuan" }, + + { label: "Yandere", value: "yandere" }, + + { label: "Yuri", value: "yuri" }, + ], + inputType: FilterInputs.Checkbox, + }, +]; diff --git a/plugins/english/novelforest.ts b/plugins/english/novelforest.ts new file mode 100644 index 0000000..ef2bf11 --- /dev/null +++ b/plugins/english/novelforest.ts @@ -0,0 +1,167 @@ +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; +import { load as parseHTML } from "cheerio"; + +export const id = "novelforest"; +export const name = "Novel Forest (Broken)"; +export const version = "1.0.0"; +export const icon = "src/en/novelforest/icon.png"; +export const site = "https://novelforest.com/"; +exports.protected = false; + +const pluginId = id; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function (page) { + const url = `${baseUrl}popular?page=${page}`; + + const body = await fetchApi(url, {}, pluginId).then((r) => r.text()); + + const loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".book-item").each(function () { + const novelName = loadedCheerio(this).find(".title").text(); + const novelCover = + "https:" + loadedCheerio(this).find("img").attr("data-src"); + const novelUrl = + baseUrl + + loadedCheerio(this).find(".title a").attr("href")?.substring(1); + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const url = novelUrl; + + const body = await fetchApi(url, {}, pluginId).then((r) => r.text()); + + let loadedCheerio = parseHTML(body); + + const novel: Novel.instance = { + url, + chapters: [], + }; + + novel.name = loadedCheerio(".name h1").text().trim(); + + novel.cover = "https:" + loadedCheerio(".img-cover img").attr("data-src"); + + novel.summary = loadedCheerio( + "body > div.layout > div.main-container.book-details > div > div.row.no-gutters > div.col-lg-8 > div.mt-1 > div.section.box.mt-1.summary > div.section-body > p.content" + ) + .text() + .trim(); + + novel.author = loadedCheerio( + "body > div.layout > div.main-container.book-details > div > div.row.no-gutters > div.col-lg-8 > div.book-info > div.detail > div.meta.box.mt-1.p-10 > p:nth-child(1) > a > span" + ) + .text() + ?.trim(); + + novel.status = loadedCheerio( + "body > div.layout > div.main-container.book-details > div > div.row.no-gutters > div.col-lg-8 > div.book-info > div.detail > div.meta.box.mt-1.p-10 > p:nth-child(2) > a > span" + ) + .text() + ?.trim(); + + novel.genres = loadedCheerio( + "body > div.layout > div.main-container.book-details > div > div.row.no-gutters > div.col-lg-8 > div.book-info > div.detail > div.meta.box.mt-1.p-10 > p:nth-child(3)" + ) + .text() + ?.replace("Genres :", "") + .replace(/[\s\n]+/g, " ") + .trim(); + + let chapter: Chapter.Item[] = []; + + const chaptersUrl = + novelUrl.replace(baseUrl, "https://novelforest.com/api/novels/") + + "/chapters?source=detail"; + + const chaptersRequest = await fetch(chaptersUrl); + const chaptersHtml = await chaptersRequest.text(); + + loadedCheerio = parseHTML(chaptersHtml); + + loadedCheerio("li").each(function () { + const chapterName = loadedCheerio(this) + .find(".chapter-title") + .text() + .trim(); + + const releaseDate = loadedCheerio(this) + .find(".chapter-update") + .text() + .trim(); + + const chapterUrl = + baseUrl + loadedCheerio(this).find("a").attr("href")?.substring(1); + + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + + novel.chapters = chapter; + + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const body = await fetchApi(chapterUrl, {}, pluginId).then((r) => r.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio("#listen-chapter").remove(); + loadedCheerio("#google_translate_element").remove(); + + const chapterText = loadedCheerio(".chapter__content").html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = `${baseUrl}search?q=${searchTerm}`; + + const body = await fetchApi(url, {}, pluginId).then((r) => r.text()); + + let loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".book-item").each(function () { + const novelName = loadedCheerio(this).find(".title").text(); + const novelCover = + "https:" + loadedCheerio(this).find("img").attr("data-src"); + const novelUrl = + baseUrl + + loadedCheerio(this).find(".title a").attr("href")?.substring(1); + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const fetchImage: Plugin.fetchImage = fetchFile; diff --git a/plugins/english/novelfull.ts b/plugins/english/novelfull.ts new file mode 100644 index 0000000..7175abf --- /dev/null +++ b/plugins/english/novelfull.ts @@ -0,0 +1,172 @@ +import { load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "novelfull"; +export const name = "NovelFull"; +export const version = "1.0.0"; +export const icon = "src/en/novelfull/icon.png"; +export const site = "https://novelfull.com/"; +exports.protected = false; + +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function (page) { + const url = `${baseUrl}most-popular?page=${page}`; + + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".col-truyen-main .list-truyen .row").each(function () { + const novelName = loadedCheerio(this) + .find("h3.truyen-title > a") + .text(); + + const novelCover = + baseUrl + loadedCheerio(this).find("img").attr("src")?.slice(1); + + const novelUrl = + baseUrl + + loadedCheerio(this) + .find("h3.truyen-title > a") + .attr("href") + ?.slice(1); + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + const novel: Novel.instance = { + url, + chapters: [], + }; + + novel.name = loadedCheerio("div.book > img").attr("alt"); + + novel.cover = baseUrl + loadedCheerio("div.book > img").attr("src"); + + novel.summary = loadedCheerio("div.desc-text").text().trim(); + + novel.author = loadedCheerio('h3:contains("Author")') + .parent() + .contents() + .text() + .replace("Author:", ""); + + novel.genres = loadedCheerio('h3:contains("Genre")') + .siblings() + .map((i, el) => loadedCheerio(el).text()) + .toArray() + .join(","); + + novel.status = loadedCheerio('h3:contains("Status")').next().text(); + + const novelId = loadedCheerio("#rating").attr("data-novel-id")!; + + async function getChapters(id: string) { + const chapterListUrl = + baseUrl + "ajax/chapter-option?novelId=" + id; + + const data = await fetchApi(chapterListUrl); + const chapterlist = await data.text(); + + loadedCheerio = parseHTML(chapterlist); + + let chapter: Chapter.Item[] = []; + + loadedCheerio("select > option").each(function () { + const chapterName = loadedCheerio(this).text(); + const releaseDate = null; + const chapterUrl = + baseUrl + loadedCheerio(this).attr("value")?.slice(1); + + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + return chapter; + } + + novel.chapters = await getChapters(novelId); + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi(chapterUrl); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + loadedCheerio("#chapter-content div.ads").remove(); + let chapterText = loadedCheerio("#chapter-content").html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const searchUrl = `${baseUrl}search?keyword=${searchTerm}`; + + const result = await fetchApi(searchUrl); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".col-truyen-main .list-truyen .row").each(function () { + const novelName = loadedCheerio(this) + .find("h3.truyen-title > a") + .text(); + + const novelCover = + baseUrl + loadedCheerio(this).find("img").attr("src")?.slice(1); + + const novelUrl = + baseUrl + + loadedCheerio(this) + .find("h3.truyen-title > a") + .attr("href") + ?.slice(1); + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async function (url) { + const headers = { + Referer: baseUrl, + }; + return await fetchFile(url, { headers: headers }); +}; diff --git a/plugins/english/novelhold.ts b/plugins/english/novelhold.ts new file mode 100644 index 0000000..604db23 --- /dev/null +++ b/plugins/english/novelhold.ts @@ -0,0 +1,148 @@ +import { load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "novelhold"; +export const name = "Novel Hold"; +export const version = "1.0.0"; +export const icon = "src/en/novelhold/icon.png"; +export const site = "https://novelhold.com/"; +exports.protected = false; + +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function (page) { + const url = `${baseUrl}all-${page}.html`; + + const body = await fetchApi(url).then((r) => r.text()); + + const loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio("#article_list_content > li").each(function () { + const novelName = loadedCheerio(this) + .find("h3") + .text() + .replace(/\t+/g, "") + .replace(/\n/g, " "); + const novelCover = loadedCheerio(this).find("img").attr("data-src"); + const novelUrl = + baseUrl + loadedCheerio(this).find("a").attr("href")?.slice(1); + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + + const body = await fetchApi(url).then((r) => r.text()); + + let loadedCheerio = parseHTML(body); + + const novel: Novel.instance = { + url, + chapters: [], + }; + + novel.name = loadedCheerio(".booknav2 > h1").text(); + + novel.cover = loadedCheerio('meta[property="og:image"]').attr( + "content" + ); + + novel.summary = loadedCheerio(".navtxt").text().trim(); + + novel.author = loadedCheerio('p:contains("Author")') + .text() + .replace("Author:", "") + .trim(); + + novel.status = loadedCheerio('p:contains("Status")') + .text() + .replace("Status:", "") + .replace("Active", "Ongoing") + .trim(); + + novel.genres = loadedCheerio('p:contains("Genre")') + .text() + ?.replace("Genre:", "") + .trim(); + + let chapter: Chapter.Item[] = []; + + loadedCheerio("ul.chapterlist > li").each(function () { + const chapterName = loadedCheerio(this).find("a").text().trim(); + const releaseDate = null; + const chapterUrl = + baseUrl + loadedCheerio(this).find("a").attr("href")?.slice(1); + + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + + novel.chapters = chapter; + console.log(novel); + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const body = await fetchApi(chapterUrl).then((r) => r.text()); + + let loadedCheerio = parseHTML(body); + + const chapterText = loadedCheerio(".content").html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = `${baseUrl}index.php?s=so&module=book&keyword=${searchTerm}`; + + const body = await fetchApi(url).then((r) => r.text()); + + let loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio("#article_list_content > li").each(function () { + const novelName = loadedCheerio(this) + .find("h3") + .text() + .replace(/\t+/g, "") + .replace(/\n/g, " "); + const novelCover = loadedCheerio(this).find("img").attr("data-src"); + const novelUrl = + baseUrl + loadedCheerio(this).find("a").attr("href")?.slice(1); + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async function (url) { + const headers = { + Referer: baseUrl, + }; + return await fetchFile(url, { headers: headers }); +}; diff --git a/plugins/english/novelpub.js b/plugins/english/novelpub.js deleted file mode 100644 index 6e6c9e0..0000000 --- a/plugins/english/novelpub.js +++ /dev/null @@ -1,182 +0,0 @@ -const cheerio = require('cheerio'); -const fetchApi = require('@libs/fetchApi'); -const fetchFile = require('@libs/fetchFile'); -const languages = require('@libs/languages'); - -const baseUrl = 'https://www.novelpub.com/'; - -const sourceName = 'NovelPub'; -const pluginId = 'novelpub.com'; - -const headers = { - Accept: 'application/json', - 'Content-Type': 'application/json', - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36', -}; - -async function popularNovels (page) { - let url = baseUrl + 'browse/all/popular/all/' + page; - - const result = await fetchApi(url, { method: 'GET', headers }); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('.novel-item.ads').remove(); - - loadedCheerio('.novel-item').each(function () { - const novelName = loadedCheerio(this).find('.novel-title').text().trim(); - const novelCover = loadedCheerio(this).find('img').attr('data-src'); - const novelUrl = - baseUrl + - loadedCheerio(this).find('.novel-title > a').attr('href').substring(1); - - const novel = { name: novelName, cover: novelCover, url: novelUrl }; - - novels.push(novel); - }); - - return novels; -}; - -async function parseNovelAndChapters (novelUrl) { - const url = novelUrl; - - const result = await fetchApi(url, { method: 'GET', headers }); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novel = { url, genres: '' }; - - novel.name = loadedCheerio('h1.novel-title').text().trim(); - - novel.cover = loadedCheerio('figure.cover > img').attr('data-src'); - - loadedCheerio('div.categories > ul > li').each(function () { - novel.genres += - loadedCheerio(this) - .text() - .replace(/[\t\n]/g, '') + ','; - }); - - loadedCheerio('div.header-stats > span').each(function () { - if (loadedCheerio(this).find('small').text() === 'Status') { - novel.status = loadedCheerio(this).find('strong').text(); - } - }); - - novel.genres = novel.genres.slice(0, -1); - - novel.author = loadedCheerio('.author > a > span').text(); - - novel.summary = loadedCheerio('.summary > .content').text().trim(); - - const delay = ms => new Promise(res => setTimeout(res, ms)); - - let lastPage = 1; - - lastPage = loadedCheerio( - '#novel > header > div.header-body.container > div.novel-info > div.header-stats > span:nth-child(1) > strong', - ) - .text() - ?.trim(); - - lastPage = Math.ceil(lastPage / 100); - - const getChapters = async () => { - let novelChapters = []; - - for (let i = 1; i <= lastPage; i++) { - const chaptersUrl = `${novelUrl}/chapters/page-${i}`; - - const chaptersRequest = await fetchApi(chaptersUrl, { headers }); - const chaptersHtml = await chaptersRequest.text(); - - loadedCheerio = cheerio.load(chaptersHtml); - - loadedCheerio('.chapter-list li').each(function () { - const chapterName = loadedCheerio(this) - .find('.chapter-title') - .text() - .trim(); - - const releaseDate = loadedCheerio(this) - .find('.chapter-update') - .text() - .trim(); - - const chapterUrl = - baseUrl + loadedCheerio(this).find('a').attr('href').substring(1); - - novelChapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); - }); - - await delay(1000); - } - - return novelChapters; - }; - - novel.chapters = await getChapters(); - - return novel; -}; - -async function parseChapter (chapterUrl) { - const url = chapterUrl; - - const result = await fetchApi(url, { method: 'GET', headers }); - - const body = await result.text(); - const loadedCheerio = cheerio.load(body); - - const chapterText = loadedCheerio('#chapter-container').html(); - return chapterText; -}; - -// They have API to search, pls update it, im quite lazy xD -async function searchNovels (searchTerm) { - const url = `${baseUrl}lnwsearchlive?inputContent=${searchTerm}`; - - const result = await fetchApi(url, { method: 'GET', headers }); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novels = []; - -// let results = JSON.parse(loadedCheerio('body').text()); - -// loadedCheerio = cheerio.load(results.resultview); - -// loadedCheerio('.novel-item').each(function () { -// const novelName = loadedCheerio(this).find('.novel-title').text().trim(); -// const novelCover = loadedCheerio(this).find('img').attr('src'); -// const novelUrl = -// baseUrl + loadedCheerio(this).find('a').attr('href').substring(1); - -// const novel = { name: novelName, cover: novelCover, url: novelUrl }; - -// novels.push(novel); -// }); - - return novels; -}; - -module.exports = { - id: pluginId, - name: sourceName, - site: baseUrl, - version: '1.0.0', - lang: languages.English, - icon: 'src/en/novelpub/icon.png', - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; \ No newline at end of file diff --git a/plugins/english/novelpub.ts b/plugins/english/novelpub.ts new file mode 100644 index 0000000..0aef4ae --- /dev/null +++ b/plugins/english/novelpub.ts @@ -0,0 +1,189 @@ +import { load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "novelpub.com"; +export const name = "NovelPub"; +export const site = "https://www.novelpub.com/"; +export const version = "1.0.0"; +export const icon = "src/en/novelpub/icon.png"; +const baseUrl = site; + +const headers = { + Accept: "application/json", + "Content-Type": "application/json", + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36", +}; + +export const popularNovels: Plugin.popularNovels = async function (page) { + let url = baseUrl + "browse/all/popular/all/" + page; + + const result = await fetchApi(url, { method: "GET", headers }); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".novel-item.ads").remove(); + + loadedCheerio(".novel-item").each(function () { + const novelName = loadedCheerio(this) + .find(".novel-title") + .text() + .trim(); + const novelCover = loadedCheerio(this).find("img").attr("data-src"); + const novelUrl = + baseUrl + + loadedCheerio(this) + .find(".novel-title > a") + .attr("href") + ?.substring(1); + + const novel = { name: novelName, cover: novelCover, url: novelUrl }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + + const result = await fetchApi(url, { method: "GET", headers }); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + let novel: Novel.instance = { url, genres: "" }; + + novel.name = loadedCheerio("h1.novel-title").text().trim(); + + novel.cover = loadedCheerio("figure.cover > img").attr("data-src"); + + loadedCheerio("div.categories > ul > li").each(function () { + novel.genres += + loadedCheerio(this) + .text() + .replace(/[\t\n]/g, "") + ","; + }); + + loadedCheerio("div.header-stats > span").each(function () { + if (loadedCheerio(this).find("small").text() === "Status") { + novel.status = loadedCheerio(this).find("strong").text(); + } + }); + + novel.genres = novel.genres?.slice(0, -1); + + novel.author = loadedCheerio(".author > a > span").text(); + + novel.summary = loadedCheerio(".summary > .content").text().trim(); + + const delay = (ms: number) => new Promise((res) => setTimeout(res, ms)); + + let lastPage = 1; + + lastPage = parseInt( + loadedCheerio( + "#novel > header > div.header-body.container > div.novel-info > div.header-stats > span:nth-child(1) > strong" + ) + .text() + ?.trim() + ); + + lastPage = Math.ceil(lastPage / 100); + + const getChapters = async () => { + let novelChapters: Chapter.Item[] = []; + + for (let i = 1; i <= lastPage; i++) { + const chaptersUrl = `${novelUrl}/chapters/page-${i}`; + + const chaptersRequest = await fetchApi(chaptersUrl, { + headers, + }); + const chaptersHtml = await chaptersRequest.text(); + + loadedCheerio = parseHTML(chaptersHtml); + + loadedCheerio(".chapter-list li").each(function () { + const chapterName = loadedCheerio(this) + .find(".chapter-title") + .text() + .trim(); + + const releaseDate = loadedCheerio(this) + .find(".chapter-update") + .text() + .trim(); + + const chapterUrl = + baseUrl + + loadedCheerio(this) + .find("a") + .attr("href") + ?.substring(1); + + novelChapters.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + + await delay(1000); + } + + return novelChapters; + }; + + novel.chapters = await getChapters(); + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const url = chapterUrl; + + const result = await fetchApi(url, { method: "GET", headers }); + + const body = await result.text(); + const loadedCheerio = parseHTML(body); + + const chapterText = loadedCheerio("#chapter-container").html(); + return chapterText; +}; + +// They have API to search, pls update it, im quite lazy xD +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = `${baseUrl}lnwsearchlive?inputContent=${searchTerm}`; + + const result = await fetchApi(url, { method: "GET", headers }); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + // let results = JSON.parse(loadedCheerio('body').text()); + + // loadedCheerio = load(results.resultview); + + // loadedCheerio('.novel-item').each(function () { + // const novelName = loadedCheerio(this).find('.novel-title').text().trim(); + // const novelCover = loadedCheerio(this).find('img').attr('src'); + // const novelUrl = + // baseUrl + loadedCheerio(this).find('a').attr('href').substring(1); + + // const novel = { name: novelName, cover: novelCover, url: novelUrl }; + + // novels.push(novel); + // }); + + return novels; +}; +export const fetchImage: Plugin.fetchImage = fetchFile; diff --git a/plugins/english/novelupdates.broken.ts b/plugins/english/novelupdates.broken.ts new file mode 100644 index 0000000..fccbe8d --- /dev/null +++ b/plugins/english/novelupdates.broken.ts @@ -0,0 +1,492 @@ +import cheerio from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { FilterInputs } from "@libs/filterInputs"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +const pluginId = "novelupdates"; +const baseUrl = "https://www.novelupdates.com/"; + +const headers = { + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36", +}; + +function getPopularNovelsUrl( + page: number, + { showLatestNovels, filters }: Plugin.Options +) { + let url = `${baseUrl}${ + filters + ? "series-finder" + : showLatestNovels + ? "latest-series" + : "series-ranking" + }/`; + + if (!filters) { + url += "?rank=week"; + } else { + url += "?sf=1"; + } + + if (filters) { + if (Array.isArray(filters.novelType) && filters.novelType?.length) { + url += "&nt=" + filters?.novelType.join(","); + } + + if (Array.isArray(filters.genres) && filters.genres?.length) { + url += "&gi=" + filters?.genres.join(",") + "&mgi=and"; + } + + if (Array.isArray(filters.language) && filters.language?.length) { + url += "&org=" + filters?.language.join(","); + } + + if (filters.storyStatus) { + url += "&ss=" + filters?.storyStatus; + } + } + + if (!filters?.sort) { + url += "&sort=" + "sdate"; + } else { + url += "&sort=" + filters?.sort; + } + + if (!filters?.order) { + url += "&order=" + "desc"; + } else { + url += "&order=" + filters?.order; + } + + return url; +} + +export const popularNovels: Plugin.popularNovels = async function ( + page, + { showLatestNovels, filters } +) { + const url = getPopularNovelsUrl(page, { showLatestNovels, filters }); + + const result = await fetchApi(url, { headers: headers }, pluginId); + const body = await result.text(); + + const loadedCheerio = cheerio.load(body); + + const novels: Novel.Item[] = []; + + loadedCheerio("div.search_main_box_nu").each(function (res) { + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelName = loadedCheerio(this).find(".search_title > a").text(); + const novelUrl = loadedCheerio(this) + .find(".search_title > a") + .attr("href"); + + if (!novelUrl) return; + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + + const result = await fetchApi(url, { headers: headers }, pluginId); + const body = await result.text(); + + let loadedCheerio = cheerio.load(body); + + const novel: Novel.instance = { + url, + chapters: [], + }; + + novel.name = loadedCheerio(".seriestitlenu").text(); + + novel.cover = loadedCheerio(".seriesimg > img").attr("src"); + + novel.author = loadedCheerio("#showauthors").text().trim(); + + novel.genres = loadedCheerio("#seriesgenre") + .children("a") + .map((i, el) => loadedCheerio(el).text()) + .toArray() + .join(","); + novel.status = loadedCheerio("#editstatus").text().includes("Ongoing") + ? "Ongoing" + : "Completed"; + + let type = loadedCheerio("#showtype").text().trim(); + + let summary = loadedCheerio("#editdescription").text().trim(); + + novel.summary = summary + `\n\nType: ${type}`; + + let chapter: Chapter.Item[] = []; + + const novelId = loadedCheerio("input#mypostid").attr("value")!; + + let formData = new FormData(); + formData.append("action", "nd_getchapters"); + formData.append("mygrr", "0"); + formData.append("mypostid", novelId); + + let link = "https://www.novelupdates.com/wp-admin/admin-ajax.php"; + + const text = await fetchApi( + link, + { + method: "POST", + body: formData, + headers: headers, + }, + pluginId + ).then((data) => data.text()); + + loadedCheerio = cheerio.load(text); + + loadedCheerio("li.sp_li_chp").each(function () { + const chapterName = loadedCheerio(this).text().trim(); + + const releaseDate = null; + + const chapterUrl = + "https:" + + loadedCheerio(this).find("a").first().next().attr("href"); + + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + + novel.chapters = chapter.reverse(); + + return novel; + }; + +function getLocation(href: string) { + var match = href.match( + /^(https?:)\/\/(([^:/?#]*)(?::([0-9]+))?)([/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/ + ); + return match && `${match[1]}//${match[3]}`; +} + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + // console.log('Original URL: ', chapterUrl); + + let chapterText = ""; + + const result = await fetchApi( + chapterUrl, + { + method: "GET", + headers: headers, + }, + pluginId + ); + const body = await result.text(); + + // console.log(result.chapterUrl); + + // console.log('Redirected URL: ', result.chapterUrl); + + const loadedCheerio = cheerio.load(body); + + let isWuxiaWorld = result.chapterUrl.toLowerCase().includes("wuxiaworld"); + + let isBlogspot = result.chapterUrl.toLowerCase().includes("blogspot"); + + let isTumblr = result.chapterUrl.toLowerCase().includes("tumblr"); + + let isWattpad = result.chapterUrl.toLowerCase().includes("wattpad"); + + let isLightNovelsTls = result.chapterUrl + .toLowerCase() + .includes("lightnovelstranslations"); + + let isiNovelTranslation = result.chapterUrl + .toLowerCase() + .includes("inoveltranslation"); + + let isTravisTranslation = result.chapterUrl + .toLowerCase() + .includes("travistranslations"); + + /** + * Checks if its a wordpress site + */ + let isWordPressStr = + loadedCheerio('meta[name="generator"]').attr("content") || + loadedCheerio("footer").text(); + + let isWordPress = false; + + if (isWordPressStr) { + isWordPress = + isWordPressStr.toLowerCase().includes("wordpress") || + isWordPressStr.includes("Site Kit by Google") || + loadedCheerio(".powered-by") + .text() + .toLowerCase() + .includes("wordpress"); + } + + let isRainOfSnow = result.chapterUrl.toLowerCase().includes("rainofsnow"); + + let isWebNovel = result.chapterUrl.toLowerCase().includes("webnovel"); + + let isHostedNovel = result.chapterUrl.toLowerCase().includes("hostednovel"); + + let isScribbleHub = result.chapterUrl.toLowerCase().includes("scribblehub"); + + if (isWuxiaWorld) { + chapterText = loadedCheerio("#chapter-content").html()!; + } else if (isRainOfSnow) { + chapterText = loadedCheerio("div.content").html()!; + } else if (isTumblr) { + chapterText = loadedCheerio(".post").html()!; + } else if (isBlogspot) { + loadedCheerio(".post-share-buttons").remove(); + chapterText = loadedCheerio(".entry-content").html()!; + } else if (isHostedNovel) { + chapterText = loadedCheerio(".chapter").html()!; + } else if (isScribbleHub) { + chapterText = loadedCheerio("div.chp_raw").html()!; + } else if (isWattpad) { + chapterText = loadedCheerio(".container pre").html()!; + } else if (isTravisTranslation) { + chapterText = loadedCheerio(".reader-content").html()!; + } else if (isLightNovelsTls) { + chapterText = loadedCheerio(".text_story").html()!; + } else if (isiNovelTranslation) { + chapterText = loadedCheerio(".chakra-skeleton").html()!; + } else if (isWordPress) { + /** + * Remove wordpress bloat tags + */ + + const bloatClasses = [ + ".c-ads", + "#madara-comments", + "#comments", + ".content-comments", + ".sharedaddy", + ".wp-dark-mode-switcher", + ".wp-next-post-navi", + ".wp-block-buttons", + ".wp-block-columns", + ".post-cats", + ".sidebar", + ".author-avatar", + ".ezoic-ad", + ]; + + bloatClasses.map((tag) => loadedCheerio(tag).remove()); + + chapterText = + loadedCheerio(".entry-content").html() || + loadedCheerio(".single_post").html() || + loadedCheerio(".post-entry").html() || + loadedCheerio(".main-content").html() || + loadedCheerio("article.post").html() || + loadedCheerio(".content").html() || + loadedCheerio("#content").html() || + loadedCheerio(".page-body").html() || + loadedCheerio(".td-page-content").html()!; + } else if (isWebNovel) { + chapterText = loadedCheerio(".cha-words").html()!; + + if (!chapterText) { + chapterText = loadedCheerio("._content").html()!; + } + } else { + /** + * Remove unnecessary tags + */ + const tags = ["nav", "header", "footer", ".hidden"]; + + tags.map((tag) => loadedCheerio(tag).remove()); + + chapterText = loadedCheerio("body").html()!; + } + + if (chapterText) { + /** + * Convert relative urls to absolute + */ + chapterText = chapterText.replace( + /href="\//g, + `href="${getLocation(result.chapterUrl)}/` + ); + } + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = + "https://www.novelupdates.com/?s=" + + searchTerm + + "&post_type=seriesplans"; + + const result = await fetchApi(url, { headers: headers }, pluginId); + const body = await result.text(); + + const loadedCheerio = cheerio.load(body); + + let novels: Novel.Item[] = []; + + loadedCheerio("div.search_main_box_nu").each(function () { + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelName = loadedCheerio(this).find(".search_title > a").text(); + const novelUrl = loadedCheerio(this) + .find(".search_title > a") + .attr("href") + ?.split("/")[4]; + + if (!novelUrl) return; + + novels.push({ + name: novelName, + url: novelUrl, + cover: novelCover, + }); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async function (url) { + return await fetchFile(url, { headers: headers }); +}; + +const filters = [ + { + key: "sort", + label: "Sort Results By", + values: [ + { label: "Last Updated", value: "sdate" }, + { label: "Rating", value: "srate" }, + { label: "Rank", value: "srank" }, + { label: "Reviews", value: "sreview" }, + { label: "Chapters", value: "srel" }, + { label: "Title", value: "abc" }, + { label: "Readers", value: "sread" }, + { label: "Frequency", value: "sfrel" }, + ], + inputType: FilterInputs.Picker, + }, + { + key: "order", + label: "Order", + values: [ + { label: "Descending", value: "desc" }, + { label: "Ascending", value: "asc" }, + ], + inputType: FilterInputs.Picker, + }, + { + key: "storyStatus", + label: "Story Status (Translation)", + values: [ + { label: "All", value: "" }, + { label: "Completed", value: "2" }, + { label: "Ongoing", value: "3" }, + { label: "Hiatus", value: "4" }, + ], + inputType: FilterInputs.Picker, + }, + { + key: "language", + label: "Language", + values: [ + { label: "Chinese", value: 495 }, + { label: "Filipino", value: 9181 }, + { label: "Indonesian", value: 9179 }, + { label: "Japanese", value: 496 }, + { label: "Khmer", value: 18657 }, + { label: "Korean", value: 497 }, + { label: "Malaysian", value: 9183 }, + { label: "Thai", value: 9954 }, + { label: "Vietnamese", value: 9177 }, + ], + inputType: FilterInputs.Checkbox, + }, + { + key: "novelType", + label: "Novel Type", + values: [ + { label: "Light Novel", value: "2443" }, + { label: "Published Novel", value: "26874" }, + { label: "Web Novel", value: "2444" }, + ], + inputType: FilterInputs.Checkbox, + }, + { + key: "genres", + label: "Genres", + values: [ + { label: "Action", value: 8 }, + { label: "Adult", value: 280 }, + { label: "Adventure", value: 13 }, + { label: "Comedy", value: 17 }, + { label: "Drama", value: 9 }, + { label: "Ecchi", value: 292 }, + { label: "Fantasy", value: 5 }, + { label: "Gender Bender", value: 168 }, + { label: "Harem", value: 3 }, + { label: "Historical", value: 330 }, + { label: "Horror", value: 343 }, + { label: "Josei", value: 324 }, + { label: "Martial Arts", value: 14 }, + { label: "Mature", value: 4 }, + { label: "Mecha", value: 10 }, + { label: "Mystery", value: 245 }, + { label: "Psychoical", value: 486 }, + { label: "Romance", value: 15 }, + { label: "School Life", value: 6 }, + { label: "Sci-fi", value: 11 }, + { label: "Seinen", value: 18 }, + { label: "Shoujo", value: 157 }, + { label: "Shoujo Ai", value: 851 }, + { label: "Shounen", value: 12 }, + { label: "Shounen Ai", value: 1692 }, + { label: "Slice of Life", value: 7 }, + { label: "Smut", value: 281 }, + { label: "Sports", value: 1357 }, + { label: "Supernatural", value: 16 }, + { label: "Tragedy", value: 132 }, + { label: "Wuxia", value: 479 }, + { label: "Xianxia", value: 480 }, + { label: "Xuanhuan", value: 3954 }, + { label: "Yaoi", value: 560 }, + { label: "Yuri", value: 922 }, + ], + inputType: FilterInputs.Checkbox, + }, +]; + +module.exports = { + id: pluginId, + name: "Novel Updates", + version: "0.5.0", + icon: "src/en/novelupdates/icon.png", + site: baseUrl, + protected: false, + fetchImage, + popularNovels, + parseNovelAndChapters, + parseChapter, + searchNovels, + filters, +}; diff --git a/plugins/english/pandaMTL.ts b/plugins/english/pandaMTL.ts new file mode 100644 index 0000000..85b44a0 --- /dev/null +++ b/plugins/english/pandaMTL.ts @@ -0,0 +1,274 @@ +import { load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { FilterInputs } from "@libs/filterInputs"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "PandaMTL"; +export const name = "PandaMTL"; +export const version = "1.0.0"; +export const icon = "src/en/wordpress/icon.png"; +export const site = "https://www.pandamtl.com/"; +exports.protected = false; + +const pluginId = id; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function ( + page, + { filters } +) { + let link = `${baseUrl}series/?page=${page}`; + + if (filters) { + if (Array.isArray(filters.genres) && filters.genres.length) { + link += filters.genres.map((i) => `&genre[]=${i}`).join(""); + } + + if (Array.isArray(filters.type) && filters.type.length) { + link += filters.type.map((i) => `&lang[]=${i}`).join(""); + } + } + + link += "&status=" + (filters?.status ? filters?.status : ""); + + link += "&order=" + (filters?.order ? filters?.order : "popular"); + + const body = await fetchApi(link, {}, pluginId).then((result) => + result.text() + ); + + const loadedCheerio = parseHTML(body); + + const novels: Novel.Item[] = []; + + loadedCheerio("article.maindet").each(function () { + const novelName = loadedCheerio(this).find("h2").text(); + let image = loadedCheerio(this).find("img"); + const novelCover = image.attr("data-src") || image.attr("src"); + const novelUrl = loadedCheerio(this).find("h2 a").attr("href"); + + if (!novelUrl) return; + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + + const result = await fetchApi(url, {}, pluginId); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + const novel: Novel.instance = { + url, + chapters: [], + }; + + novel.name = loadedCheerio("h1.entry-title").text(); + + novel.cover = + loadedCheerio("img.wp-post-image").attr("data-src") || + loadedCheerio("img.wp-post-image").attr("src"); + + loadedCheerio("div.serl:nth-child(3) > span").each(function () { + const detailName = loadedCheerio(this).text().trim(); + const detail = loadedCheerio(this).next().text().trim(); + + switch (detailName) { + case "الكاتب": + case "Author": + novel.author = detail; + break; + } + }); + + novel.status = loadedCheerio("div.sertostat > span").attr("class"); + + novel.genres = loadedCheerio(".sertogenre") + .children("a") + .map((i, el) => loadedCheerio(el).text()) + .toArray() + .join(","); + + novel.summary = loadedCheerio(".sersys") + .find("br") + .replaceWith("\n") + .end() + .text(); + + let chapter: Chapter.Item[] = []; + + loadedCheerio(".eplister") + .find("li") + .each(function () { + const chapterName = + loadedCheerio(this).find(".epl-num").text() + + " - " + + loadedCheerio(this).find(".epl-title").text(); + + const releaseDate = loadedCheerio(this) + .find(".epl-date") + .text() + .trim(); + + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + + if (!chapterUrl) return; + + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + + novel.chapters = chapter.reverse(); + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi(chapterUrl, {}, pluginId); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + let chapterText = loadedCheerio(".epcontent").html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = `${baseUrl}?s=${searchTerm}`; + + const result = await fetchApi(url, {}, pluginId); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + const novels: Novel.Item[] = []; + + loadedCheerio("article.maindet").each(function () { + const novelName = loadedCheerio(this).find("h2").text(); + let image = loadedCheerio(this).find("img"); + const novelCover = image.attr("data-src") || image.attr("src"); + const novelUrl = loadedCheerio(this).find("h2 a").attr("href"); + + if (!novelUrl) return; + + novels.push({ + name: novelName, + url: novelUrl, + cover: novelCover, + }); + }); + + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async function (url) { + const headers = { + Referer: baseUrl, + }; + return await fetchFile(url, { headers: headers }); +}; + +export const filters = [ + { + key: "order", + label: "Sort By", + values: [ + { label: "Default", value: "" }, + + { label: "A-Z", value: "title" }, + + { label: "Z-A", value: "titlereverse" }, + + { label: "Latest Update", value: "update" }, + + { label: "Latest Added", value: "latest" }, + + { label: "Popular", value: "popular" }, + ], + inputType: FilterInputs.Picker, + }, + { + key: "status", + label: "Status", + values: [ + { label: "All", value: "" }, + + { label: "Ongoing", value: "ongoing" }, + + { label: "Hiatus", value: "hiatus" }, + + { label: "Completed", value: "completed" }, + ], + inputType: FilterInputs.Picker, + }, + { + key: "type", + label: "Type", + values: [ + { label: "Light Novel (KR)", value: "light-novel-kr" }, + + { label: "Web Novel", value: "web-novel" }, + ], + inputType: FilterInputs.Checkbox, + }, + { + key: "genres", + label: "Genres", + values: [ + { label: "Action", value: "action" }, + + { label: "Adult", value: "adult" }, + + { label: "Adventure", value: "adventure" }, + + { label: "Comedy", value: "comedy" }, + + { label: "Ecchi", value: "ecchi" }, + + { label: "Fantasy", value: "fantasy" }, + + { label: "Harem", value: "harem" }, + + { label: "Josei", value: "josei" }, + + { label: "Martial Arts", value: "martial-arts" }, + + { label: "Mature", value: "mature" }, + + { label: "Romance", value: "romance" }, + + { label: "School Life", value: "school-life" }, + + { label: "Sci-fi", value: "sci-fi" }, + + { label: "Seinen", value: "seinen" }, + + { label: "Slice of Life", value: "slice-of-life" }, + + { label: "Smut", value: "smut" }, + + { label: "Sports", value: "sports" }, + + { label: "Supernatural", value: "supernatural" }, + + { label: "Tragedy", value: "tragedy" }, + ], + inputType: FilterInputs.Checkbox, + }, +]; diff --git a/plugins/english/wuxiaworld.broken.ts b/plugins/english/wuxiaworld.broken.ts new file mode 100644 index 0000000..f06fdf0 --- /dev/null +++ b/plugins/english/wuxiaworld.broken.ts @@ -0,0 +1,146 @@ +import cheerio from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "wuxiaworld"; +export const name = "Wuxia World (WIP)"; +export const version = "0.5.0"; +export const icon = "src/en/wuxiaworld/icon.png"; +export const site = "https://www.wuxiaworld.com/"; +exports.protected = true; + +const pluginId = id; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function (page) { + const link = `${baseUrl}api/novels`; + + const result = await fetchApi(link, {}, pluginId); + const data = await result.json(); + + let novels: Novel.Item[] = []; + + data.items.map((novel) => { + let name = novel.name; + let cover = novel.coverUrl; + let url = baseUrl + "novel/" + novel.slug + "/"; + + novels.push({ + name, + cover, + url, + }); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + console.log(url); + const result = await fetchApi(url, {}, pluginId); + const body = await result.text(); + + const loadedCheerio = cheerio.load(body); + + const novel: Novel.instance = { + url, + chapters: [], + }; + + novel.name = loadedCheerio("h1.line-clamp-2").text(); + + novel.cover = loadedCheerio("img.absolute").attr("src"); + + novel.summary = loadedCheerio( + "div.flex-col:nth-child(4) > div > div > span > span" + ) + .text() + .trim(); + + novel.author = loadedCheerio("div.MuiGrid-container > div > div > div") + .filter(function () { + return loadedCheerio(this).text().trim() === "Author:"; + }) + .next() + .text(); + + let genres: string[] = []; + + loadedCheerio("a.MuiLink-underlineNone").each(function () { + genres.push(loadedCheerio(this).find("div > div").text()); + }); + + novel.genres = genres.join(","); + + novel.status = loadedCheerio("div.font-set-b10") + .text() + .includes("Complete"); + + let chapter: Chapter.Item[] = []; + + loadedCheerio("div.border-b").each(function () { + const name = loadedCheerio(this) + .find("a > div > div > div > span") + .text(); + const releaseTime = loadedCheerio(this) + .find("a > div > div > div > div > span") + .text(); + let url = loadedCheerio(this).find("a").attr("href")?.slice(1); + url = `${baseUrl}${url}`; + + chapter.push({ name, releaseTime, url }); + }); + + novel.chapters = chapter; + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi(chapterUrl, {}, pluginId); + const body = await result.text(); + + const loadedCheerio = cheerio.load(body); + + loadedCheerio(".chapter-nav").remove(); + + loadedCheerio("#chapter-content > script").remove(); + + let chapterText = loadedCheerio("#chapter-content").html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const searchUrl = "https://www.wuxiaworld.com/api/novels/search?query="; + + const url = `${searchUrl}${searchTerm}`; + + const result = await fetchApi(url); + const data = await result.json(); + + const novels: Novel.Item[] = []; + + data.items.map((novel) => { + let name = novel.name; + let cover = novel.cover; + let url = baseUrl + "novel/" + novel.slug + "/"; + + novels.push({ + name, + url, + cover, + }); + }); + + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async function (url) { + const headers = { + Referer: baseUrl, + }; + return await fetchFile(url, { headers: headers }); +}; diff --git a/plugins/french/chireads.js b/plugins/french/chireads.js deleted file mode 100644 index 42d012b..0000000 --- a/plugins/french/chireads.js +++ /dev/null @@ -1,126 +0,0 @@ -const cheerio = require('cheerio'); -const fetchApi = require('@libs/fetchApi'); -const fetchFile = require('@libs/fetchFile'); -const languages = require('@libs/languages'); - -const pluginId = 'chireads.com'; -const sourceName = 'Chireads'; - -const baseUrl = 'https://chireads.com/'; - -async function popularNovels (page) { - const url = `${baseUrl}category/translatedtales/page/${page}/`; - - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('#content li').each(function () { - const novelName = loadedCheerio(this).find('.news-list-tit h5').text(); - const novelCover = loadedCheerio(this).find('img').attr('src'); - const novelUrl = loadedCheerio(this) - .find('.news-list-tit h5 a') - .attr('href'); - - const novel = { name: novelName, cover: novelCover, url :novelUrl }; - - novels.push(novel); - }); - - return novels; -}; - -async function parseNovelAndChapters (novelUrl) { - const url = novelUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novel = { - url, - name: '', - cover: '', - author: '', - artist: '', - status: '', - genres: '', - summary: '', - chapters: [], - }; - - novel.name = loadedCheerio('.inform-title').text().trim(); - - novel.cover = loadedCheerio('.inform-product img').attr('src'); - - novel.summary = loadedCheerio('.inform-inform-txt').text().trim(); - - let chapters = []; - - loadedCheerio('.chapitre-table a').each(function () { - const chapterName = loadedCheerio(this).text().trim(); - const releaseDate = null; - const chapterUrl = loadedCheerio(this).attr('href'); - - chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); - }); - - novel.chapters = chapters; - - return novel; -}; - -async function parseChapter (chapterUrl) { - const url = chapterUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - const chapterText = loadedCheerio('#content').html(); - - return chapterText; -}; - -async function searchNovels (searchTerm) { - const url = `${baseUrl}search?x=0&y=0&name=${searchTerm}`; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('#content li').each(function () { - const novelName = loadedCheerio(this).find('.news-list-tit h5').text(); - const novelCover = loadedCheerio(this).find('img').attr('src'); - const novelUrl = loadedCheerio(this) - .find('.news-list-tit h5 a') - .attr('href'); - - const novel = { name: novelName, cover: novelCover, url: novelUrl }; - - novels.push(novel); - }); - return novels; -}; - -module.exports = { - id: pluginId, - name: sourceName, - site: baseUrl, - version: '1.0.0', - icon: 'src/fr/chireads/icon.png', - lang: languages.French, - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; \ No newline at end of file diff --git a/plugins/french/chireads.ts b/plugins/french/chireads.ts new file mode 100644 index 0000000..9ba2698 --- /dev/null +++ b/plugins/french/chireads.ts @@ -0,0 +1,125 @@ +import { load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "chireads.com"; +export const name = "Chireads"; +export const site = "https://chireads.com/"; +export const version = "1.0.0"; +export const icon = "src/fr/chireads/icon.png"; + +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function (page) { + const url = `${baseUrl}category/translatedtales/page/${page}/`; + + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio("#content li").each(function () { + const novelName = loadedCheerio(this).find(".news-list-tit h5").text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this) + .find(".news-list-tit h5 a") + .attr("href"); + + if (!novelUrl) return; + + const novel = { name: novelName, cover: novelCover, url: novelUrl }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + let novel: Novel.instance = { + url, + name: "", + cover: "", + author: "", + status: "", + genres: "", + summary: "", + chapters: [], + }; + + novel.name = loadedCheerio(".inform-title").text().trim(); + + novel.cover = loadedCheerio(".inform-product img").attr("src"); + + novel.summary = loadedCheerio(".inform-inform-txt").text().trim(); + + let chapters: Chapter.Item[] = []; + + loadedCheerio(".chapitre-table a").each(function () { + const chapterName = loadedCheerio(this).text().trim(); + const releaseDate = null; + const chapterUrl = loadedCheerio(this).attr("href"); + + if (!chapterUrl) return; + + chapters.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + + novel.chapters = chapters; + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const url = chapterUrl; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + const chapterText = loadedCheerio("#content").html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = `${baseUrl}search?x=0&y=0&name=${searchTerm}`; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio("#content li").each(function () { + const novelName = loadedCheerio(this).find(".news-list-tit h5").text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this) + .find(".news-list-tit h5 a") + .attr("href"); + + if (!novelUrl) return; + + const novel = { name: novelName, cover: novelCover, url: novelUrl }; + + novels.push(novel); + }); + return novels; +}; +export const fetchImage: Plugin.fetchImage = fetchFile; diff --git a/plugins/french/noveldeglace.broken.ts b/plugins/french/noveldeglace.broken.ts new file mode 100644 index 0000000..96fae82 --- /dev/null +++ b/plugins/french/noveldeglace.broken.ts @@ -0,0 +1,140 @@ +import cheerio from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "NDG.com"; +export const name = "NovelDeGlace"; +export const site = "https://noveldeglace.com/"; +export const version = "1.0.0"; +export const icon = "src/fr/noveldeglace/icon.png"; + +const pluginId = id; + +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function (page) { + let url = baseUrl + "roman"; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = cheerio.load(body); + + let novels: Novel.Item[] = []; + + loadedCheerio("article").each(function () { + const novelName = loadedCheerio(this).find("h2").text().trim(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this).find("h2 > a").attr("href"); + + if (!novelUrl) return; + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + + const result = await fetchApi(url, {}, pluginId); + const body = await result.text(); + + let loadedCheerio = cheerio.load(body); + + let novel: Novel.instance = { url }; + + novel.name = loadedCheerio( + "div.entry-content > div > strong" + )[0].nextSibling?.nodeType.trim(); + + novel.cover = loadedCheerio(".su-row > div > div > img").attr("src"); + + novel.summary = loadedCheerio("div[data-title=Synopsis]").text(); + + const author = loadedCheerio( + "div.romans > div.project-large > div.su-row > div.su-column.su-column-size-3-4 > div > div:nth-child(3) > strong" + )[0]; + + novel.author = author ? author.nextSibling.nodeValue.trim() : null; + + novel.genres = loadedCheerio(".genre") + .text() + .replace("Genre : ", "") + .replace(/, /g, ","); + + let novelChapters: Chapter.Item[] = []; + + loadedCheerio(".chpt").each(function () { + const chapterName = loadedCheerio(this).find("a").text().trim(); + const releaseDate = null; + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + + if (!chapterUrl) return; + + const chapter = { + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }; + + novelChapters.push(chapter); + }); + + novel.chapters = novelChapters; + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const url = chapterUrl; + + const result = await fetchApi(url, {}, pluginId); + const body = await result.text(); + + let loadedCheerio = cheerio.load(body); + + let chapterText = loadedCheerio(".chapter-content").html(); + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + let url = baseUrl + "roman"; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = cheerio.load(body); + + let novels: Novel.Item[] = []; + + loadedCheerio("article").each(function () { + const novelName = loadedCheerio(this).find("h2").text().trim(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this).find("h2 > a").attr("href"); + + if (!novelUrl) return; + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + novels = novels.filter((novel) => + novel.name.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + return novels; +}; diff --git a/plugins/french/noveldeglace.js b/plugins/french/noveldeglace.js deleted file mode 100644 index 562aaf8..0000000 --- a/plugins/french/noveldeglace.js +++ /dev/null @@ -1,147 +0,0 @@ -const cheerio = require('cheerio'); -const fetchApi = require('@libs/fetchApi'); -const fetchFile = require('@libs/fetchFile'); -const languages = require('@libs/languages'); - -const sourceName = 'NovelDeGlace'; -const pluginId = 'NDG.com'; - -const baseUrl = 'https://noveldeglace.com/'; - -async function popularNovels (page) { - let url = baseUrl + 'roman'; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('article').each(function () { - const novelName = loadedCheerio(this).find('h2').text().trim(); - const novelCover = loadedCheerio(this).find('img').attr('src'); - const novelUrl = loadedCheerio(this) - .find('h2 > a') - .attr('href') - - const novel = { - name: novelName, - cover: novelCover, - url: novelUrl, - }; - - novels.push(novel); - }); - - return novels; -}; - -async function parseNovelAndChapters (novelUrl) { - const url = novelUrl; - - const result = await fetchApi(url, {}, pluginId); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novel = {url}; - - novel.name = loadedCheerio( - 'div.entry-content > div > strong', - )[0].nextSibling.nodeValue.trim(); - - novel.cover = loadedCheerio('.su-row > div > div > img').attr('src'); - - novel.summary = loadedCheerio('div[data-title=Synopsis]').text(); - - const author = loadedCheerio( - 'div.romans > div.project-large > div.su-row > div.su-column.su-column-size-3-4 > div > div:nth-child(3) > strong', - )[0]; - - novel.author = author ? author.nextSibling.nodeValue.trim() : null; - - novel.genres = loadedCheerio('.genre') - .text() - .replace('Genre : ', '') - .replace(/, /g, ','); - - let novelChapters = []; - - loadedCheerio('.chpt').each(function () { - const chapterName = loadedCheerio(this).find('a').text().trim(); - const releaseDate = null; - const chapterUrl = loadedCheerio(this).find('a').attr('href'); - - const chapter = { - name: chapterName, - releaseTime: releaseDate, - url: chapterUrl, - }; - - novelChapters.push(chapter); - }); - - novel.chapters = novelChapters; - - return novel; -}; - -async function parseChapter ( chapterUrl) { - const url = chapterUrl; - - const result = await fetchApi(url, {}, pluginId); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let chapterText = loadedCheerio('.chapter-content').html(); - return chapterText; -}; - -async function searchNovels (searchTerm) { - let url = baseUrl + 'roman'; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('article').each(function () { - const novelName = loadedCheerio(this).find('h2').text().trim(); - const novelCover = loadedCheerio(this).find('img').attr('src'); - const novelUrl = loadedCheerio(this) - .find('h2 > a') - .attr('href') - - const novel = { - name: novelName, - cover: novelCover, - url: novelUrl, - }; - - novels.push(novel); - }); - - novels = novels.filter(novel => - novel.name.toLowerCase().includes(searchTerm.toLowerCase()), - ); - - return novels; -}; - -module.exports = { - id: pluginId, - name: sourceName, - site: baseUrl, - version: '1.0.0', - icon: 'src/fr/noveldeglace/icon.png', - lang: languages.French, - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; \ No newline at end of file diff --git a/plugins/indonesian/MeioNovel[madara].ts b/plugins/indonesian/MeioNovel[madara].ts new file mode 100644 index 0000000..71586a9 --- /dev/null +++ b/plugins/indonesian/MeioNovel[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "meionovel"; +export const name = "MeioNovel [madara]"; +export const icon = "multisrc/madara/icons/meionovel.png"; +export const version = "1.0.0"; +export const site = "https://meionovel.id/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novel" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (true) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/indonesian/MoreNovel[madara].ts b/plugins/indonesian/MoreNovel[madara].ts new file mode 100644 index 0000000..be1b02c --- /dev/null +++ b/plugins/indonesian/MoreNovel[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "morenovel"; +export const name = "MoreNovel [madara]"; +export const icon = "multisrc/madara/icons/morenovel.png"; +export const version = "1.0.0"; +export const site = "https://morenovel.net/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novel" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (true) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/indonesian/WBNovel[madara].ts b/plugins/indonesian/WBNovel[madara].ts new file mode 100644 index 0000000..9ad6604 --- /dev/null +++ b/plugins/indonesian/WBNovel[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "wbnovel"; +export const name = "WBNovel [madara]"; +export const icon = "multisrc/madara/icons/wbnovel.png"; +export const version = "1.0.0"; +export const site = "https://wbnovel.com/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novel" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (false) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/indonesian/indowebnovel.js b/plugins/indonesian/indowebnovel.js deleted file mode 100644 index 053de72..0000000 --- a/plugins/indonesian/indowebnovel.js +++ /dev/null @@ -1,142 +0,0 @@ -const cheerio = require('cheerio') -const fetchApi = require('@libs/fetchApi'); -const fetchFile = require('@libs/fetchFile'); -const languages = require('@libs/languages'); - -const pluginId = "IDWN.id"; -const sourceName = 'IndoWebNovel'; - -const baseUrl = 'https://indowebnovel.id/id/'; - -async function popularNovels (page) { - const url = `${baseUrl}daftar-novel/`; - - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('.novellist-blc li').each(function () { - const novelName = loadedCheerio(this).find('a').text(); - const novelCover = null; - const novelUrl = loadedCheerio(this).find('a').attr('href'); - - const novel = { - name: novelName, - cover: novelCover, - url: novelUrl, - }; - - novels.push(novel); - }); - - return novels; -}; - -async function parseNovelAndChapters (novelUrl) { - const url = novelUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novel = { - url, - name: '', - cover: '', - author: '', - status: '', - genres: '', - summary: '', - chapters: [], - }; - - novel.name = loadedCheerio('.series-title').text().trim(); - - novel.cover = loadedCheerio('.series-thumb > img').attr('src'); - - novel.summary = loadedCheerio('.series-synops').text().trim(); - - novel.status = loadedCheerio('.status').text().trim(); - - novel.genres = []; - - loadedCheerio('.series-genres').each(function () { - novel.genres.push(loadedCheerio(this).find('a').text().trim()); - }); - - novel.genres = novel.genres.toString(); - - let chapters = []; - - loadedCheerio('.series-chapterlist li').each(function () { - const chapterName = loadedCheerio(this).find('a').text().trim(); - const releaseDate = null; - const chapterUrl = loadedCheerio(this).find('a').attr('href'); - - chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); - }); - - novel.chapters = chapters.reverse(); - - return novel; -}; - -async function parseChapter ( chapterUrl) { - const url = chapterUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - const chapterText = loadedCheerio('.reader').html(); - - return chapterText; -}; - -async function searchNovels (searchTerm) { - const url = `${baseUrl}daftar-novel/`; - - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('.novellist-blc li').each(function () { - const novelName = loadedCheerio(this).find('a').text(); - const novelCover = null; - const novelUrl = loadedCheerio(this).find('a').attr('href'); - - const novel = { - name: novelName, - cover: novelCover, - url: novelUrl, - }; - - if (novelName.toLowerCase().includes(searchTerm.toLowerCase())) { - novels.push(novel); - } - }); - - return novels; -}; - -module.exports = { - id: pluginId, - name: sourceName, - site: baseUrl, - icon: 'src/id/indowebnovel/icon.png', - version: '1.0.0', - lang: languages.Indonesian, - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; \ No newline at end of file diff --git a/plugins/indonesian/indowebnovel.ts b/plugins/indonesian/indowebnovel.ts new file mode 100644 index 0000000..84abde2 --- /dev/null +++ b/plugins/indonesian/indowebnovel.ts @@ -0,0 +1,143 @@ +import { load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; + +export const id = "IDWN.id"; +export const name = "IndoWebNovel"; +export const site = "https://indowebnovel.id/id/"; +export const icon = "src/id/indowebnovel/icon.png"; +export const version = "1.0.0"; + +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function (page) { + const url = `${baseUrl}daftar-novel/`; + + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".novellist-blc li").each(function () { + const novelName = loadedCheerio(this).find("a").text(); + const novelCover = defaultCover; + const novelUrl = loadedCheerio(this).find("a").attr("href"); + + if (!novelUrl || !novelName) return; + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + let novel: Novel.instance = { + url, + name: "", + cover: "", + author: "", + status: "", + genres: "", + summary: "", + chapters: [], + }; + + novel.name = loadedCheerio(".series-title").text().trim(); + + novel.cover = loadedCheerio(".series-thumb > img").attr("src"); + + novel.summary = loadedCheerio(".series-synops").text().trim(); + + novel.status = loadedCheerio(".status").text().trim(); + + const genres: string[] = []; + + loadedCheerio(".series-genres").each(function () { + genres.push(loadedCheerio(this).find("a").text().trim()); + }); + + novel.genres = genres.join(","); + + let chapters: Chapter.Item[] = []; + + loadedCheerio(".series-chapterlist li").each(function () { + const chapterName = loadedCheerio(this).find("a").text().trim(); + const releaseDate = null; + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + + if (!chapterUrl) return; + + chapters.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + + novel.chapters = chapters.reverse(); + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const url = chapterUrl; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + const chapterText = loadedCheerio(".reader").html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = `${baseUrl}daftar-novel/`; + + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".novellist-blc li").each(function () { + const novelName = loadedCheerio(this).find("a").text(); + const novelCover = defaultCover; + const novelUrl = loadedCheerio(this).find("a").attr("href"); + + if (!novelUrl) return; + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + if (novelName.toLowerCase().includes(searchTerm.toLowerCase())) { + novels.push(novel); + } + }); + + return novels; +}; +export const fetchImage: Plugin.fetchImage = fetchFile; diff --git a/plugins/indonesian/novelringan.js b/plugins/indonesian/novelringan.js deleted file mode 100644 index bcc7183..0000000 --- a/plugins/indonesian/novelringan.js +++ /dev/null @@ -1,162 +0,0 @@ -const cheerio = require('cheerio') -const fetchApi = require('@libs/fetchApi'); -const fetchFile = require('@libs/fetchFile'); -const languages = require('@libs/languages'); -const Status = require('@libs/novelStatus'); - -const pluginId = "novelringan.com"; - -const sourceName = 'NovelRingan'; - -const baseUrl = 'https://novelringan.com/'; -const coverUriPrefix = 'https://i0.wp.com/novelringan.com/wp-content/uploads/'; - -async function popularNovels (page) { - const url = `${baseUrl}/top-novel/page/${page}`; - - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - const novels = []; - - loadedCheerio('article.post').each(function () { - const novelName = loadedCheerio(this).find('.entry-title').text()?.trim(); - const novelCover = - coverUriPrefix + loadedCheerio(this).find('img').attr('data-sxrx'); - const novelUrl = loadedCheerio(this).find('h2 > a').attr('href'); - - const novel = { - name: novelName, - cover: novelCover, - url: novelUrl, - }; - - novels.push(novel); - }); - - return novels ; -}; - -async function parseNovelAndChapters (novelUrl) { - const url = novelUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - let novel = { - url, - name: '', - cover: '', - genres: '', - author: '', - status: Status.Unknown, - artist: '', - summary: '', - chapters: [], - }; - - novel.name = loadedCheerio('.entry-title').text()?.trim(); - novel.cover = - coverUriPrefix + loadedCheerio('img.ts-post-image').attr('data-sxrx'); - novel.summary = loadedCheerio( - 'body > div.site-container > div > main > article > div > div.maininfo > span > p', - ).text(); - - let genreSelector = loadedCheerio( - 'body > div.site-container > div > main > article > div > div.maininfo > span > ul > li:nth-child(4)', - ).text(); - - novel.genres = genreSelector.includes('Genre') - ? genreSelector.replace('Genre:', '').trim() - : ''; - - let statusSelector = loadedCheerio( - 'body > div.site-container > div > main > article > div > div.maininfo > span > ul > li:nth-child(3)', - ).text(); - - novel.status = statusSelector.includes('Status') - ? statusSelector.replace('Status:', '').trim() - : Status.Unknown; - - let chapters = []; - - loadedCheerio('.bxcl > ul > li').each(function () { - const chapterName = loadedCheerio(this).find('a').text(); - const releaseDate = null; - const chapterUrl = loadedCheerio(this).find('a').attr('href'); - - const chapter = { - name: chapterName, - releaseTime: releaseDate, - url: chapterUrl, - }; - - chapters.push(chapter); - }); - - novel.chapters = chapters.reverse(); - - return novel; -}; - -async function parseChapter ( chapterUrl) { - const url = chapterUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - loadedCheerio('.entry-content div[style="display:none"]').remove(); - - const chapterText = loadedCheerio('.entry-content').html(); - - return chapterText; -}; - -async function searchNovels (searchTerm) { - const url = baseUrl + '?s=' + searchTerm; - - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('article.post').each(function () { - const novelName = loadedCheerio(this).find('.entry-title').text(); - const novelCover = - coverUriPrefix + loadedCheerio(this).find('img').attr('data-sxrx'); - - const novelUrl = loadedCheerio(this).find('h2 > a').attr('href'); - - const novel = { - name: novelName, - cover: novelCover, - url: novelUrl, - }; - - novels.push(novel); - }); - return novels; -}; - - -module.exports = { - id: pluginId, - name: sourceName, - site: baseUrl, - icon: 'src/id/novelringan/icon.png', - version: '1.0.0', - lang: languages.Indonesian, - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; \ No newline at end of file diff --git a/plugins/indonesian/novelringan.ts b/plugins/indonesian/novelringan.ts new file mode 100644 index 0000000..0e15f9d --- /dev/null +++ b/plugins/indonesian/novelringan.ts @@ -0,0 +1,159 @@ +import { load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { NovelStatus } from "@libs/novelStatus"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "novelringan.com"; +export const name = "NovelRingan"; +export const site = "https://novelringan.com/"; +export const icon = "src/id/novelringan/icon.png"; +export const version = "1.0.0"; + +const baseUrl = site; +const coverUriPrefix = "https://i0.wp.com/novelringan.com/wp-content/uploads/"; + +export const popularNovels: Plugin.popularNovels = async function (page) { + const url = `${baseUrl}/top-novel/page/${page}`; + + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + const novels: Novel.Item[] = []; + + loadedCheerio("article.post").each(function () { + const novelName = loadedCheerio(this) + .find(".entry-title") + .text() + ?.trim(); + const novelCover = + coverUriPrefix + loadedCheerio(this).find("img").attr("data-sxrx"); + const novelUrl = loadedCheerio(this).find("h2 > a").attr("href"); + + if (!novelUrl) return; + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + let novel: Novel.instance = { + url, + name: "", + cover: "", + genres: "", + author: "", + status: NovelStatus.Unknown, + summary: "", + chapters: [], + }; + + novel.name = loadedCheerio(".entry-title").text()?.trim(); + novel.cover = + coverUriPrefix + + loadedCheerio("img.ts-post-image").attr("data-sxrx"); + novel.summary = loadedCheerio( + "body > div.site-container > div > main > article > div > div.maininfo > span > p" + ).text(); + + let genreSelector = loadedCheerio( + "body > div.site-container > div > main > article > div > div.maininfo > span > ul > li:nth-child(4)" + ).text(); + + novel.genres = genreSelector.includes("Genre") + ? genreSelector.replace("Genre:", "").trim() + : ""; + + let statusSelector = loadedCheerio( + "body > div.site-container > div > main > article > div > div.maininfo > span > ul > li:nth-child(3)" + ).text(); + + novel.status = statusSelector.includes("Status") + ? statusSelector.replace("Status:", "").trim() + : NovelStatus.Unknown; + + let chapters: Chapter.Item[] = []; + + loadedCheerio(".bxcl > ul > li").each(function () { + const chapterName = loadedCheerio(this).find("a").text(); + const releaseDate = null; + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + + if (!chapterUrl) return; + + const chapter = { + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }; + + chapters.push(chapter); + }); + + novel.chapters = chapters.reverse(); + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const url = chapterUrl; + + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + loadedCheerio('.entry-content div[style="display:none"]').remove(); + + const chapterText = loadedCheerio(".entry-content").html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = baseUrl + "?s=" + searchTerm; + + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio("article.post").each(function () { + const novelName = loadedCheerio(this).find(".entry-title").text(); + const novelCover = + coverUriPrefix + loadedCheerio(this).find("img").attr("data-sxrx"); + + const novelUrl = loadedCheerio(this).find("h2 > a").attr("href"); + + if (!novelUrl) return; + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; +export const fetchImage: Plugin.fetchImage = fetchFile; diff --git a/plugins/indonesian/sakuranovel.js b/plugins/indonesian/sakuranovel.js deleted file mode 100644 index f0a6365..0000000 --- a/plugins/indonesian/sakuranovel.js +++ /dev/null @@ -1,151 +0,0 @@ -const cheerio = require('cheerio') -const fetchApi = require('@libs/fetchApi'); -const fetchFile = require('@libs/fetchFile'); -const languages = require('@libs/languages'); - -const pluginId = "sakura.id"; -const sourceName = 'SakuraNovel'; - -const baseUrl = 'https://sakuranovel.id/'; - -async function popularNovels (page) { - const url = `${baseUrl}advanced-search/page/${page}/?title&author&yearx&status&type&order=rating&country%5B0%5D=china&country%5B1%5D=jepang&country%5B2%5D=unknown`; - - const result = await fetchApi(url, {}, pluginId); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('.flexbox2-item').each(function () { - const novelName = loadedCheerio(this) - .find('.flexbox2-title span') - .first() - .text(); - const novelCover = loadedCheerio(this).find('img').attr('src'); - const novelUrl = loadedCheerio(this) - .find('.flexbox2-content > a') - .attr('href'); - - const novel = {name: novelName, cover: novelCover, url: novelUrl }; - - novels.push(novel); - }); - - return novels; -}; - -async function parseNovelAndChapters (novelUrl) { - const result = await fetchApi(novelUrl, {}, pluginId); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novel = { - name: sourceName, - url: novelUrl, - }; - - novel.name = loadedCheerio('.series-title h2').text().trim(); - - novel.cover = loadedCheerio('.series-thumb img').attr('src'); - - loadedCheerio('.series-infolist > li').each(function () { - const detailName = loadedCheerio(this).find('b').text().trim(); - const detail = loadedCheerio(this).find('b').next().text().trim(); - - switch (detailName) { - case 'Author': - novel.author = detail; - break; - } - }); - - novel.status = loadedCheerio('.status').text().trim(); - - novel.genres = loadedCheerio('.series-genres') - .prop('innerHTML') - .replace(/<.*?>(.*?)<.*?>/g, '$1,') - .slice(0, -1); - - novel.summary = loadedCheerio('.series-synops').text().trim(); - - let chapters = []; - - loadedCheerio('.series-chapterlist li').each(function () { - const chapterName = loadedCheerio(this) - .find('a span') - .first() - .text() - .replace(`${novel.novelName} – `, '') - .replace('Bahasa Indonesia', '') - .replace(/\s+/g, ' ') - .trim(); - - const releaseDate = loadedCheerio(this) - .find('a span') - .first() - .next() - .text(); - const chapterUrl = loadedCheerio(this).find('a').attr('href'); - - chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); - }); - - novel.chapters = chapters.reverse(); - - return novel; -}; - -async function parseChapter (chapterUrl) { - const result = await fetchApi( chapterUrl, {}, pluginId); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - const chapterText = loadedCheerio('.reader').html(); - - return chapterText -}; - -async function searchNovels (searchTerm) { - const url = `${baseUrl}?s=${searchTerm}`; - const result = await fetchApi(url, {}, pluginId); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('.flexbox2-item').each(function () { - const novelName = loadedCheerio(this) - .find('.flexbox2-title span') - .first() - .text(); - const novelCover = loadedCheerio(this).find('img').attr('src'); - const novelUrl = loadedCheerio(this) - .find('.flexbox2-content > a') - .attr('href'); - - const novel = { name: novelName, cover: novelCover, url: novelUrl }; - - novels.push(novel); - }); - - return novels; -}; - -module.exports = { - id: pluginId, - name: sourceName, - site: baseUrl, - icon: 'src/id/sakuranovel/icon.png', - version: '1.0.0', - lang: languages.Indonesian, - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; \ No newline at end of file diff --git a/plugins/indonesian/sakuranovel.ts b/plugins/indonesian/sakuranovel.ts new file mode 100644 index 0000000..48dd452 --- /dev/null +++ b/plugins/indonesian/sakuranovel.ts @@ -0,0 +1,156 @@ +import { load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "sakura.id"; +export const name = "SakuraNovel"; +export const site = "https://sakuranovel.id/"; +export const icon = "src/id/sakuranovel/icon.png"; +export const version = "1.0.0"; + +const pluginId = id; +const sourceName = name; + +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function (page) { + const url = `${baseUrl}advanced-search/page/${page}/?title&author&yearx&status&type&order=rating&country%5B0%5D=china&country%5B1%5D=jepang&country%5B2%5D=unknown`; + + const result = await fetchApi(url, {}, pluginId); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".flexbox2-item").each(function () { + const novelName = loadedCheerio(this) + .find(".flexbox2-title span") + .first() + .text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this) + .find(".flexbox2-content > a") + .attr("href"); + + if (!novelUrl) return; + + const novel = { name: novelName, cover: novelCover, url: novelUrl }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const result = await fetchApi(novelUrl, {}, pluginId); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + let novel: Novel.instance = { + name: sourceName, + url: novelUrl, + }; + + novel.name = loadedCheerio(".series-title h2").text().trim(); + + novel.cover = loadedCheerio(".series-thumb img").attr("src"); + + loadedCheerio(".series-infolist > li").each(function () { + const detailName = loadedCheerio(this).find("b").text().trim(); + const detail = loadedCheerio(this).find("b").next().text().trim(); + + switch (detailName) { + case "Author": + novel.author = detail; + break; + } + }); + + novel.status = loadedCheerio(".status").text().trim(); + + novel.genres = loadedCheerio(".series-genres") + .children("a") + .map((i, el) => loadedCheerio(el).text()) + .toArray() + .join(","); + + loadedCheerio(".series-synops div").remove(); + novel.summary = loadedCheerio(".series-synops").text().trim(); + + let chapters: Chapter.Item[] = []; + + loadedCheerio(".series-chapterlist li").each(function () { + const chapterName = loadedCheerio(this) + .find("a span") + .first() + .text() + .replace(/.*?(Chapter.|[0-9])/g, "$1") + .replace(/Bahasa Indonesia/g, "") + .replace(/\s+/g, " ") + .trim(); + + const releaseDate = loadedCheerio(this) + .find("a span") + .first() + .next() + .text(); + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + + if (!chapterUrl) return; + + chapters.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + + novel.chapters = chapters.reverse(); + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi(chapterUrl, {}, pluginId); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + const chapterText = loadedCheerio(".readers").html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = `${baseUrl}?s=${searchTerm}`; + const result = await fetchApi(url, {}, pluginId); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".flexbox2-item").each(function () { + const novelName = loadedCheerio(this) + .find(".flexbox2-title span") + .first() + .text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this) + .find(".flexbox2-content > a") + .attr("href"); + + if (!novelUrl) return; + + const novel = { name: novelName, cover: novelCover, url: novelUrl }; + + novels.push(novel); + }); + + return novels; +}; +export const fetchImage: Plugin.fetchImage = fetchFile; diff --git a/plugins/japanese/Syosetu.js b/plugins/japanese/Syosetu.js deleted file mode 100644 index 4825255..0000000 --- a/plugins/japanese/Syosetu.js +++ /dev/null @@ -1,205 +0,0 @@ -const cheerio = require('cheerio'); -const languages = require('@libs/languages'); -const fetchApi = require('@libs/fetchApi'); -const fetchFile = require('@libs/fetchFile'); -// const novelStatus = require('@libs/novelStatus'); -// const isUrlAbsolute = require('@libs/isAbsoluteUrl'); -// const parseDate = require('@libs/parseDate'); - -const pluginId = 'yomou.syosetu'; - -const defaultCoverUri = - 'https://github.com/LNReader/lnreader-sources/blob/main/icons/src/coverNotAvailable.jpg?raw=true'; - -const searchUrl = (pagenum, order) => { - return `https://yomou.syosetu.com/search.php?order=${order || 'hyoka'}${!isNaN((pagenum = parseInt(pagenum, 10))) // check if pagenum is a number - ? `&p=${pagenum <= 1 || pagenum > 100 ? '1' : pagenum}` // check if pagenum is between 1 and 100 - : '' // if isn't don't set ?p - }`; -}; - -const novelCover = defaultCoverUri; - -async function popularNovels(pageNo) { - let novels = []; - - async function getNovelsFromPage(pagenumber) { - // load page - const result = await fetchApi(searchUrl(pagenumber || null)); - const body = await result.text(); - // Cheerio it! - const cheerioQuery = cheerio.load(body, { decodeEntities: false }); - let pageNovels = []; - // find class=searchkekka_box - cheerioQuery('.searchkekka_box').each(function (i, e) { - // get div with link and name - const novelDIV = cheerioQuery(this).find('.novel_h'); - // get link element - const novelA = novelDIV.children()[0]; - // add new novel to array - pageNovels.push({ - name: novelDIV.text(), // get the name - url: novelA.attribs.href, // get last part of the link - cover: novelCover, // TODO: IDK what to do about covers... On Syo they don't have them - }); - }); - // return all novels from this page - return pageNovels; - }; - - novels = await getNovelsFromPage(pageNo); - - return novels; -}; - -async function parseNovelAndChapters(novelUrl) { - let chapters = []; - const result = await fetchApi(novelUrl); - const body = await result.text(); - const cheerioQuery = cheerio.load(body, { decodeEntities: false }); - - // create novel object - let novel = { - url: novelUrl, - name: cheerioQuery('.novel_title').text(), - author: cheerioQuery('.novel_writername').text().replace('作者:', ''), - cover: novelCover, - }; - - // Get all the chapters - const cqGetChapters = cheerioQuery('.novel_sublist2'); - if (cqGetChapters.length !== 0) { - // has more than 1 chapter - novel.summary = cheerioQuery('#novel_ex') - .text() - .replace(/<\s*br.*?>/g, '\n'); - cqGetChapters.each(function (i, e) { - const chapterA = cheerioQuery(this).find('a'); - const [chapterName, releaseDate, chapterUrl] = [ - // set the variables - chapterA.text(), - cheerioQuery(this) - .find('dt') // get title - .text() // get text - .replace(/(.)/g, '') // remove "(edited)" mark - .trim(), // trim spaces - 'https://ncode.syosetu.com' + chapterA.attr('href'), - ]; - chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); - }); - } else { - /** - * Because there are oneshots on the site, they have to be treated with special care - * that's what pisses me off in Shosetsu app. They have this extension, - * but every oneshot is set as "there are no chapters" and all contents are thrown into the description!! - */ - // get summary for oneshot chapter - - const nameResult = await fetchApi(searchUrl() + `&word=${novel.name}`); - const nameBody = await nameResult.text(); - const summaryQuery = cheerio.load(nameBody, { decodeEntities: false }); - const foundText = summaryQuery('.searchkekka_box') - .first() - .find('.ex') - .text() - .replace(/\s{2,}/g, '\n'); - novel.summary = foundText; - - // add single chapter - chapters.push({ - name: 'Oneshot', - releaseTime: cheerioQuery('head') - .find("meta[name='WWWC']") - .attr('content'), // get date from metadata - url: novelUrl, // set chapterUrl to oneshot so that chapterScraper knows it's a one-shot - }); - } - novel.chapters = chapters; - return novel; -}; - -async function parseChapter(chapterUrl) { - const result = await fetchApi(chapterUrl); - const body = await result.text(); - - // create cheerioQuery - const cheerioQuery = cheerio.load(body, { - decodeEntities: false, - }); - - let chapterText = cheerioQuery('#novel_honbun') // get chapter text - .html(); - return chapterText; -}; - - -async function searchNovels(searchTerm, pageNo) { - let novels = []; - - // returns list of novels from given page - async function getNovelsFromPage (pagenumber) { - // load page - const result = await fetchApi( - searchUrl(pagenumber || null, null) + `&word=${searchTerm}` - ); - const body = await result.text(); - // Cheerio it! - const cheerioQuery = cheerio.load(body, { decodeEntities: false }); - - let pageNovels = []; - // find class=searchkekka_box - cheerioQuery('.searchkekka_box').each(function (i, e) { - // get div with link and name - const novelDIV = cheerioQuery(this).find('.novel_h'); - // get link element - const novelA = novelDIV.children()[0]; - // add new novel to array - pageNovels.push({ - name: novelDIV.text(), // get the name - url: novelA.attribs.href, // get last part of the link - cover: novelCover, // TODO: IDK what to do about covers... On Syo they don't have them - }); - }); - // return all novels from this page - return pageNovels; - }; - - // counter of loaded pages - // let pagesLoaded = 0; - // do { - // // always load first one - // novels.push(...(await getNovelsFromPage(pagesLoaded + 1))); - // pagesLoaded++; - // } while (pagesLoaded < maxPageLoad && isNext); // check if we should load more - - novels = await getNovelsFromPage(1); - - /** Use - * novels.push(...(await getNovelsFromPage(pageNumber))) - * if you want to load more - */ - - // respond with novels! - return novels; -}; - -async function fetchImage(url) { - - return await fetchFile(url); -}; - -module.exports = { - id: pluginId, - name: 'Syosetu', - icon: 'src/jp/syosetu/icon.png', - version: '1.0.0', - site: 'https://yomou.syosetu.com/', - lang: languages.Japanese, - description: '小説を読もう!は「小説家になろう」に投稿された', - protected: false, - fetchImage, - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, -} \ No newline at end of file diff --git a/plugins/japanese/Syosetu.ts b/plugins/japanese/Syosetu.ts new file mode 100644 index 0000000..f170a70 --- /dev/null +++ b/plugins/japanese/Syosetu.ts @@ -0,0 +1,197 @@ +import { load as loadCheerio } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +// const novelStatus = require('@libs/novelStatus'); +// const isUrlAbsolute = require('@libs/isAbsoluteUrl'); +// const parseDate = require('@libs/parseDate'); + +const pluginId = "yomou.syosetu"; + +export const id = pluginId; +export const name = "Syosetu"; +export const icon = "src/jp/syosetu/icon.png"; +export const version = "1.0.0"; +export const site = "https//yomou.syosetu.com/"; +exports.protected = false; + +const searchUrl = (pagenum?: number, order?: string) => { + return `https://yomou.syosetu.com/search.php?order=${order || "hyoka"}${ + pagenum !== undefined + ? `&p=${pagenum <= 1 || pagenum > 100 ? "1" : pagenum}` // check if pagenum is between 1 and 100 + : "" // if isn't don't set ?p + }`; +}; + +export const popularNovels: Plugin.popularNovels = async function (pageNo) { + async function getNovelsFromPage(pagenumber: number) { + // load page + const result = await fetchApi(searchUrl(pagenumber)); + const body = await result.text(); + // Cheerio it! + const cheerioQuery = loadCheerio(body, { decodeEntities: false }); + let pageNovels: Novel.Item[] = []; + // find class=searchkekka_box + cheerioQuery(".searchkekka_box").each(function (i, e) { + // get div with link and name + const novelDIV = cheerioQuery(this).find(".novel_h"); + // get link element + const novelA = novelDIV.children()[0]; + // add new novel to array + pageNovels.push({ + name: novelDIV.text(), // get the name + url: novelA.attribs.href, // get last part of the link + cover: defaultCover, + }); + }); + // return all novels from this page + return pageNovels; + } + const novels = await getNovelsFromPage(pageNo); + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + let chapters: Chapter.Item[] = []; + const result = await fetchApi(novelUrl); + const body = await result.text(); + const loadedCheerio = loadCheerio(body, { decodeEntities: false }); + + // create novel object + let novel: Novel.instance = { + url: novelUrl, + name: loadedCheerio(".novel_title").text(), + author: loadedCheerio(".novel_writername") + .text() + .replace("作者:", ""), + cover: defaultCover, + }; + + // Get all the chapters + const cqGetChapters = loadedCheerio(".novel_sublist2"); + if (cqGetChapters.length !== 0) { + // has more than 1 chapter + novel.summary = loadedCheerio("#novel_ex") + .text() + .replace(/<\s*br.*?>/g, "\n"); + cqGetChapters.each(function (i, e) { + const chapterA = loadedCheerio(this).find("a"); + const [chapterName, releaseDate, chapterUrl] = [ + // set the variables + chapterA.text(), + loadedCheerio(this) + .find("dt") // get title + .text() // get text + .replace(/(.)/g, "") // remove "(edited)" mark + .trim(), // trim spaces + "https://ncode.syosetu.com" + chapterA.attr("href"), + ]; + chapters.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + } else { + /** + * Because there are oneshots on the site, they have to be treated with special care + * that's what pisses me off in Shosetsu app. They have this extension, + * but every oneshot is set as "there are no chapters" and all contents are thrown into the description!! + */ + // get summary for oneshot chapter + + const nameResult = await fetchApi( + searchUrl() + `&word=${novel.name}` + ); + const nameBody = await nameResult.text(); + const summaryQuery = loadCheerio(nameBody, { + decodeEntities: false, + }); + const foundText = summaryQuery(".searchkekka_box") + .first() + .find(".ex") + .text() + .replace(/\s{2,}/g, "\n"); + novel.summary = foundText; + + // add single chapter + chapters.push({ + name: "Oneshot", + releaseTime: loadedCheerio("head") + .find("meta[name='WWWC']") + .attr("content"), // get date from metadata + url: novelUrl, // set chapterUrl to oneshot so that chapterScraper knows it's a one-shot, + }); + } + novel.chapters = chapters; + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi(chapterUrl); + const body = await result.text(); + + // create cheerioQuery + const cheerioQuery = loadCheerio(body, { + decodeEntities: false, + }); + + let chapterText = cheerioQuery("#novel_honbun") // get chapter text + .html(); + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + let novels = []; + + // returns list of novels from given page + async function getNovelsFromPage(pagenumber: number) { + // load page + const result = await fetchApi( + searchUrl(pagenumber) + `&word=${searchTerm}` + ); + const body = await result.text(); + // Cheerio it! + const cheerioQuery = loadCheerio(body, { decodeEntities: false }); + + let pageNovels: Novel.Item[] = []; + // find class=searchkekka_box + cheerioQuery(".searchkekka_box").each(function (i, e) { + // get div with link and name + const novelDIV = cheerioQuery(this).find(".novel_h"); + // get link element + const novelA = novelDIV.children()[0]; + // add new novel to array + pageNovels.push({ + name: novelDIV.text(), // get the name + url: novelA.attribs.href, // get last part of the link + cover: defaultCover, + }); + }); + // return all novels from this page + return pageNovels; + } + + // counter of loaded pages + // let pagesLoaded = 0; + // do { + // // always load first one + // novels.push(...(await getNovelsFromPage(pagesLoaded + 1))); + // pagesLoaded++; + // } while (pagesLoaded < maxPageLoad && isNext); // check if we should load more + + novels = await getNovelsFromPage(1); + + /** Use + * novels.push(...(await getNovelsFromPage(pageNumber))) + * if you want to load more + */ + + // respond with novels! + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async function (url) { + return await fetchFile(url); +}; diff --git a/plugins/korean/Agitoon.broken.ts b/plugins/korean/Agitoon.broken.ts new file mode 100644 index 0000000..16c50d1 --- /dev/null +++ b/plugins/korean/Agitoon.broken.ts @@ -0,0 +1,147 @@ +import cheerio from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "agit.xyz"; +export const name = "Agitoon"; +export const site = "https://agit501.xyz/"; +export const version = "1.0.0"; +export const icon = "src/kr/agitoon/agit.png"; + +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function (page) { + const list_limit = 20 * (page - 1); + const day = new Date().getDay(); + + const res = await fetchApi(baseUrl + "novel/index.update.php", { + headers: { + "content-type": "application/x-www-form-urlencoded; charset=UTF-8", + }, + body: `mode=get_data_novel_list_p&novel_menu=3&np_day=${day}&np_rank=1&np_distributor=0&np_genre=00&np_order=1&np_genre_ex_1=00&np_genre_ex_2=00&list_limit=${list_limit}&is_query_first=true`, + method: "POST", + }); + + const resJson = await res.json(); + + const novels: Novel.Item[] = resJson.list.map((novel) => { + return { + url: baseUrl + "novel/list/" + novel.wr_id, + name: novel.wr_subject, + cover: baseUrl + novel.np_dir + "/thumbnail/" + novel.np_thumbnail, + }; + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const novelId = novelUrl.split("/").reverse()[0]; + + // cheerio + const result = await fetchApi(novelUrl); + const body = await result.text(); + + const loadedCheerio = cheerio.load(body, { decodeEntities: false }); + const name = loadedCheerio("h5.pt-2").text(); + const summary = loadedCheerio(".pt-1.mt-1.pb-1.mb-1").text(); + const author = loadedCheerio(".post-item-list-cate-v") + .first() + .text() + .split(" : ") + .reverse()[0]; + const cover = + baseUrl.slice(0, baseUrl.length - 1) + + loadedCheerio("div.col-5.pr-0.pl-0 img").attr("src"); + const genresTag = loadedCheerio(".col-7 > .post-item-list-cate > span"); + let genres = ""; + + genresTag.each((_, element) => { + genres += loadedCheerio(element).text(); + genres += ", "; + }); + genres = genres.slice(0, genres.length - 2); + + // normal REST HTTP requests + let chapters: Chapter.Item[] = []; + + const res = await fetchApi(baseUrl + "novel/list.update.php", { + headers: { + "content-type": + "application/x-www-form-urlencoded; charset=UTF-8", + }, + body: `mode=get_data_novel_list_c&wr_id_p=${novelId}&page_no=1&cnt_list=10000&order_type=Asc`, + method: "POST", + }); + + const resJson = await res.json(); + + chapters = resJson.list.map((chapter) => { + return { + name: chapter.wr_subject, + url: baseUrl + `novel/view/${chapter.wr_id}/2`, + releaseTime: chapter.wr_datetime, + }; + }); + + const novel: Novel.instance = { + url: novelUrl, + name, + cover, + summary, + author, + status: "", + genres: genres, + chapters, + }; + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi(chapterUrl); + const body = await result.text(); + + const loadedCheerio = cheerio.load(body); + + const contentTag = loadedCheerio("#id_wr_content > p"); + + let content = ""; + contentTag.each((_, element) => { + content += loadedCheerio(element).text(); + content += "
"; + }); + + // gets rid of the popup thingy + content = content.replace( + "팝업메뉴는 빈공간을 더치하거나 스크룰시 사라집니다", + "" + ); + + return content; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const rawResults = await fetchApi("https://agit501.xyz/novel/search.php", { + headers: { + "content-type": "application/x-www-form-urlencoded; charset=UTF-8", + }, + body: `mode=get_data_novel_list_p_sch&search_novel=${searchTerm}&list_limit=0`, + method: "POST", + }); + + const results = await rawResults.json(); + if (results.list_count === 0) { + return []; + } + + const novels: Novel.Item[] = results.list.map((result) => { + return { + url: baseUrl + "novel/list/" + result.wr_id, + name: result.wr_subject, + cover: + baseUrl + result.np_dir + "/thumbnail/" + result.np_thumbnail, + }; + }); + return novels; +}; diff --git a/plugins/korean/Agitoon.js b/plugins/korean/Agitoon.js deleted file mode 100644 index d099189..0000000 --- a/plugins/korean/Agitoon.js +++ /dev/null @@ -1,155 +0,0 @@ -const cheerio = require('cheerio'); -const fetchApi = require('@libs/fetchApi'); -const fetchFile = require('@libs/fetchFile'); -const languages = require('@libs/languages'); - -const pluginId = 'agit.xyz'; -const sourceName = 'Agitoon'; -const baseUrl = 'https://agit501.xyz/'; - -async function popularNovels (page) { - const list_limit = 20 * (page - 1); - const day = new Date().getDay(); - - const res = await fetchApi(baseUrl + 'novel/index.update.php', { - headers: { - 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', - }, - body: `mode=get_data_novel_list_p&novel_menu=3&np_day=${day}&np_rank=1&np_distributor=0&np_genre=00&np_order=1&np_genre_ex_1=00&np_genre_ex_2=00&list_limit=${list_limit}&is_query_first=true`, - method: 'POST', - }); - - const resJson = await res.json(); - - const novels = resJson.list.map(novel => { - return { - url: baseUrl + 'novel/list/' + novel.wr_id, - name: novel.wr_subject, - cover: baseUrl + novel.np_dir + '/thumbnail/' + novel.np_thumbnail, - }; - }); - - return novels ; -}; - -async function parseNovelAndChapters (novelUrl) { - const novelId = novelUrl.split('/').reverse()[0]; - - // cheerio - const result = await fetchApi(novelUrl); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body, { decodeEntities: false }); - const name = loadedCheerio('h5.pt-2').text(); - const summary = loadedCheerio('.pt-1.mt-1.pb-1.mb-1').text(); - const author = loadedCheerio('.post-item-list-cate-v') - .first() - .text() - .split(' : ') - .reverse()[0]; - const cover = - baseUrl.slice(0, baseUrl.length - 1) + - loadedCheerio('div.col-5.pr-0.pl-0 img').attr('src'); - const genresTag = loadedCheerio('.col-7 > .post-item-list-cate > span'); - let genres = ''; - - genresTag.each((_, element) => { - genres += loadedCheerio(element).text(); - genres += ', '; - }); - genres = genres.slice(0, genres.length - 2); - - // normal REST HTTP requests - let chapters = []; - - const res = await fetchApi(baseUrl + 'novel/list.update.php', { - headers: { - 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', - }, - body: `mode=get_data_novel_list_c&wr_id_p=${novelId}&page_no=1&cnt_list=10000&order_type=Asc`, - method: 'POST', - }); - - const resJson = await res.json(); - - chapters = resJson.list.map(chapter => { - return { - name: chapter.wr_subject, - url: baseUrl + `novel/view/${chapter.wr_id}/2`, - releaseTime: chapter.wr_datetime, - }; - }); - - const novel = { - url: novelUrl, - name, - cover, - summary, - author, - status: '', - genres: genres, - chapters, - }; - return novel; -}; - -async function parseChapter (chapterUrl) { - const result = await fetchApi(chapterUrl); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - const contentTag = loadedCheerio('#id_wr_content > p'); - - let content = ''; - contentTag.each((_, element) => { - content += loadedCheerio(element).text(); - content += '
'; - }); - - // gets rid of the popup thingy - content = content.replace( - '팝업메뉴는 빈공간을 더치하거나 스크룰시 사라집니다', - '', - ); - - return content; -}; - -async function searchNovels (searchTerm) { - const rawResults = await fetchApi('https://agit501.xyz/novel/search.php', { - headers: { - 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', - }, - body: `mode=get_data_novel_list_p_sch&search_novel=${searchTerm}&list_limit=0`, - method: 'POST', - }); - - const results = await rawResults.json(); - if (results.list_count === 0) { - return []; - } - - const novels = results.list.map(result => { - return { - url: baseUrl + 'novel/list/' + result.wr_id, - name: result.wr_subject, - cover: baseUrl + result.np_dir + '/thumbnail/' + result.np_thumbnail, - }; - }); - return novels; -}; - -module.exports = { - id: pluginId, - name: sourceName, - site: baseUrl, - version: '1.0.0', - icon: 'src/kr/agitoon/agit.png', - lang: languages.Korean, - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; \ No newline at end of file diff --git a/plugins/portuguese/lightnovelbrasil.ts b/plugins/portuguese/lightnovelbrasil.ts new file mode 100644 index 0000000..d5f4a33 --- /dev/null +++ b/plugins/portuguese/lightnovelbrasil.ts @@ -0,0 +1,421 @@ +import { load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { FilterInputs } from "@libs/filterInputs"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "lightnovelbrasil"; +export const name = "Light Novel Brasil"; +export const version = "1.0.0"; +export const icon = "multisrc/wpmangastream/icons/lightnovelbrasil.png"; +export const site = "https://lightnovelbrasil.com/"; +exports.protected = false; + +const pluginId = id; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function ( + page, + { filters } +) { + let link = `${baseUrl}series/?page=${page}`; + + if (filters) { + if (Array.isArray(filters.genres) && filters.genres.length) { + link += filters.genres.map((i) => `&genre[]=${i}`).join(""); + } + + if (Array.isArray(filters.type) && filters.type.length) { + link += filters.type.map((i) => `&lang[]=${i}`).join(""); + } + } + + link += "&status=" + (filters?.status ? filters?.status : ""); + + link += "&order=" + (filters?.order ? filters?.order : "popular"); + + const body = await fetchApi(link, {}, pluginId).then((result) => + result.text() + ); + + const loadedCheerio = parseHTML(body); + + const novels: Novel.Item[] = []; + + loadedCheerio("article.bs").each(function () { + const novelName = loadedCheerio(this).find(".ntitle").text().trim(); + let image = loadedCheerio(this).find("img"); + const novelCover = image.attr("data-src") || image.attr("src"); + + const novelUrl = loadedCheerio(this).find("a").attr("href"); + + if (!novelUrl) return; + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + + const result = await fetchApi(url, {}, pluginId); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + const novel: Novel.instance = { + url, + chapters: [], + }; + + novel.name = loadedCheerio(".entry-title").text(); + + novel.cover = + loadedCheerio("img.wp-post-image").attr("data-src") || + loadedCheerio("img.wp-post-image").attr("src"); + + loadedCheerio("div.spe > span").each(function () { + const detailName = loadedCheerio(this).find("b").text().trim(); + const detail = loadedCheerio(this) + .find("b") + .remove() + .end() + .text() + .trim(); + + switch (detailName) { + case "المؤلف:": + case "Yazar:": + case "Autor:": + case "Author:": + novel.author = detail; + break; + case "Status:": + case "Seviye:": + novel.status = detail; + break; + } + }); + + novel.genres = loadedCheerio(".genxed") + .text() + .trim() + .replace(/\s/g, ","); + + loadedCheerio('div[itemprop="description"] h3,p.a,strong').remove(); + novel.summary = loadedCheerio('div[itemprop="description"]') + .find("br") + .replaceWith("\n") + .end() + .text(); + + let chapter: Chapter.Item[] = []; + + loadedCheerio(".eplister") + .find("li") + .each(function () { + const chapterName = + loadedCheerio(this).find(".epl-num").text() + + " - " + + loadedCheerio(this).find(".epl-title").text(); + + const releaseDate = loadedCheerio(this) + .find(".epl-date") + .text() + .trim(); + + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + + if (!chapterUrl) return; + + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + + novel.chapters = chapter.reverse(); + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi(chapterUrl, {}, pluginId); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + let chapterText = loadedCheerio("div.epcontent").html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = `${baseUrl}?s=${searchTerm}`; + + const result = await fetchApi(url, {}, pluginId); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + const novels: Novel.Item[] = []; + + loadedCheerio("article.bs").each(function () { + const novelName = loadedCheerio(this).find(".ntitle").text().trim(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this).find("a").attr("href"); + + if (!novelUrl) return; + novels.push({ + name: novelName, + url: novelUrl, + cover: novelCover, + }); + }); + + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async function (url) { + const headers = { + Referer: baseUrl, + }; + return await fetchFile(url, { headers: headers }); +}; + +export const filters = [ + { + key: "order", + label: "Sort By", + values: [ + { label: "Default", value: "" }, + + { label: "A-Z", value: "title" }, + + { label: "Z-A", value: "titlereverse" }, + + { label: "Latest Update", value: "update" }, + + { label: "Latest Added", value: "latest" }, + + { label: "Popular", value: "popular" }, + ], + inputType: FilterInputs.Picker, + }, + { + key: "status", + label: "Status", + values: [ + { label: "All", value: "" }, + + { label: "Ongoing", value: "ongoing" }, + + { label: "Hiatus", value: "hiatus" }, + + { label: "Completed", value: "completed" }, + ], + inputType: FilterInputs.Picker, + }, + { + key: "type", + label: "Type", + values: [ + { label: "Chinese novel", value: "chinese-novel" }, + + { label: "habyeol", value: "habyeol" }, + + { label: "korean novel", value: "korean-novel" }, + + { label: "Web Novel", value: "web-novel" }, + + { label: "삼심", value: "%ec%82%bc%ec%8b%ac" }, + + { label: "호곡", value: "%ed%98%b8%ea%b3%a1" }, + ], + inputType: FilterInputs.Checkbox, + }, + { + key: "genres", + label: "Genres", + values: [ + { label: "A.I", value: "a.i" }, + + { label: "Academy", value: "academy" }, + + { label: "Action", value: "action" }, + + { label: "Adult", value: "adult" }, + + { label: "Adventure", value: "adventure" }, + + { label: "Alternative History", value: "alternative-history" }, + + { label: "Another World", value: "another-world" }, + + { label: "Apocalypse", value: "apocalypse" }, + + { label: "Bromance", value: "bromance" }, + + { label: "Comedy", value: "comedy" }, + + { label: "Dark fantasy", value: "dark-fantasy" }, + + { label: "Demons", value: "demons" }, + + { label: "Drama", value: "drama" }, + + { label: "Dystopia", value: "dystopia" }, + + { label: "Ecchi", value: "ecchi" }, + + { label: "Entertainment", value: "entertainment" }, + + { label: "Exhaustion", value: "exhaustion" }, + + { label: "Fanfiction", value: "fanfiction" }, + + { label: "fantasy", value: "fantasy" }, + + { label: "finance", value: "finance" }, + + { label: "Full color", value: "full-color" }, + + { label: "Game", value: "game" }, + + { label: "Gender Bender", value: "gender-bender" }, + + { label: "Genius", value: "genius" }, + + { label: "Harem", value: "harem" }, + + { label: "Hero", value: "hero" }, + + { label: "Historical", value: "historical" }, + + { label: "Hunter", value: "hunter" }, + + { label: "korean novel", value: "korean-novel" }, + + { label: "Light Novel", value: "light-novel" }, + + { + label: "List Adventure Manga Genres", + value: "list-adventure-manga-genres", + }, + + { label: "Long Strip", value: "long-strip" }, + + { label: "Love comedy", value: "love-comedy" }, + + { label: "magic", value: "magic" }, + + { label: "Manhua", value: "manhua" }, + + { label: "Martial Arts", value: "martial-arts" }, + + { label: "Mature", value: "mature" }, + + { label: "Medieval", value: "medieval" }, + + { label: "Misunderstanding", value: "misunderstanding" }, + + { label: "Modern", value: "modern" }, + + { label: "modern fantasy", value: "modern-fantasy" }, + + { label: "music", value: "music" }, + + { label: "Mystery", value: "mystery" }, + + { label: "Necromancy", value: "necromancy" }, + + { label: "No Romance", value: "no-romance" }, + + { label: "NTL", value: "ntl" }, + + { label: "o", value: "o" }, + + { label: "Obsession", value: "obsession" }, + + { label: "Politics", value: "politics" }, + + { label: "Possession", value: "possession" }, + + { label: "Programming", value: "programming" }, + + { label: "Psychological", value: "psychological" }, + + { label: "Pure Love", value: "pure-love" }, + + { label: "Redemption", value: "redemption" }, + + { label: "Regression", value: "regression" }, + + { label: "Regret", value: "regret" }, + + { label: "Reincarnation", value: "reincarnation" }, + + { label: "Revenge", value: "revenge" }, + + { label: "Romance", value: "romance" }, + + { label: "Romance Fanrasy", value: "romance-fanrasy" }, + + { label: "Salvation", value: "salvation" }, + + { label: "School Life", value: "school-life" }, + + { label: "Sci-fi", value: "sci-fi" }, + + { label: "Science fiction", value: "science-fiction" }, + + { label: "Seinen", value: "seinen" }, + + { label: "Shounen", value: "shounen" }, + + { label: "Slice of Life", value: "slice-of-life" }, + + { label: "Soft yandere", value: "soft-yandere" }, + + { label: "Sports", value: "sports" }, + + { label: "Supernatural", value: "supernatural" }, + + { label: "Survival", value: "survival" }, + + { label: "system", value: "system" }, + + { label: "Time limit", value: "time-limit" }, + + { label: "Tragedy", value: "tragedy" }, + + { label: "Transmigration", value: "transmigration" }, + + { label: "TS", value: "ts" }, + + { label: "Tsundere", value: "tsundere" }, + + { label: "Unique", value: "unique" }, + + { label: "Wholesome", value: "wholesome" }, + + { label: "Wuxia", value: "wuxia" }, + + { label: "Xuanhuan", value: "xuanhuan" }, + + { label: "Yandere", value: "yandere" }, + + { label: "Yuri", value: "yuri" }, + ], + inputType: FilterInputs.Checkbox, + }, +]; diff --git a/plugins/russian/Erolate[rulate].ts b/plugins/russian/Erolate[rulate].ts new file mode 100644 index 0000000..0711914 --- /dev/null +++ b/plugins/russian/Erolate[rulate].ts @@ -0,0 +1,271 @@ + + import { load as parseHTML } from "cheerio"; + // import dayjs from 'dayjs'; + import { fetchFile, fetchApi } from "@libs/fetch"; + import { Novel, Plugin, Chapter } from "@typings/plugin"; + // import { parseMadaraDate } from "@libs/parseMadaraDate"; + // import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; + // import { showToast } from "@libs/showToast"; + import { Filter, FilterInputs } from "@libs/filterInputs"; + import { NovelStatus } from '@libs/novelStatus'; + // import { defaultCover } from "@libs/defaultCover"; + + + export const id = "erolate"; + export const name = "Erolate"; + export const icon = ""; + export const version = "1.0.0"; + export const site = "https://erolate.com"; + exports["protected"] = false; + + const baseUrl = site; + + const customFilters: Filter[] = [{"key":"genres","label":"Жанры","values":[{"label":"Анал","value":"2"},{"label":"Бдсм","value":"3"},{"label":"Большая грудь","value":"5"},{"label":"Большая попка","value":"6"},{"label":"Большой член","value":"7"},{"label":"Бондаж","value":"8"},{"label":"В первый раз","value":"9"},{"label":"В цвете","value":"10"},{"label":"Гарем","value":"11"},{"label":"Гендарная интрига","value":"12"},{"label":"Групповой секс","value":"13"},{"label":"Детектив","value":"39"},{"label":"Драма","value":"14"},{"label":"Зрелые женщины (milf)","value":"15"},{"label":"Измена","value":"16"},{"label":"Изнасилование","value":"17"},{"label":"Инцест","value":"18"},{"label":"Исторический","value":"19"},{"label":"Комедия","value":"20"},{"label":"Контроль над разумом","value":"35"},{"label":"Маленькая грудь","value":"21"},{"label":"Мистика","value":"40"},{"label":"Научная фантастика","value":"22"},{"label":"Нетораре","value":"23"},{"label":"Оральный секс","value":"24"},{"label":"Повседневность","value":"41"},{"label":"Приключения","value":"38"},{"label":"Публичный секс","value":"33"},{"label":"Романтика","value":"25"},{"label":"С изображениями ","value":"36"},{"label":"Сверхъестественное","value":"34"},{"label":"Смат","value":"37"},{"label":"Тентакли","value":"26"},{"label":"Трагедия","value":"27"},{"label":"Ужасы","value":"28"},{"label":"Фантастика","value":"32"},{"label":"Фэнтези","value":"29"},{"label":"Чикан","value":"30"},{"label":"Этти","value":"31"},{"label":"Ahegao","value":"1"}],"inputType":FilterInputs.Checkbox},{"key":"tags","label":"Тэги","values":[{"label":"+18","value":"141"},{"label":"18+","value":"200"},{"label":"21+","value":"334"},{"label":"Адекватные главные герои","value":"253"},{"label":"Азартные игры","value":"210"},{"label":"Айдолы","value":"364"},{"label":"Акула","value":"53"},{"label":"Альтернативное развитие событий","value":"338"},{"label":"Аморальный главный герой","value":"219"},{"label":"Анал","value":"7"},{"label":"Анальный секс","value":"67"},{"label":"Ангелы","value":"114"},{"label":"Антигерой","value":"226"},{"label":"Аристократия","value":"260"},{"label":"Артефакт","value":"379"},{"label":"Афродизиак","value":"276"},{"label":"Ахегао","value":"240"},{"label":"Бабушка беременна от внука","value":"393"},{"label":"Бабушка и внук","value":"380"},{"label":"Бдсм","value":"131"},{"label":"Беременность","value":"148"},{"label":"Бесплатно","value":"79"},{"label":"Бесстрашные персонажи","value":"339"},{"label":"Библиотека","value":"265"},{"label":"Бистиалити","value":"391"},{"label":"Близнецы","value":"198"},{"label":"Блондинка","value":"91"},{"label":"Богатые персонажи","value":"248"},{"label":"Боги","value":"115"},{"label":"Боевик","value":"252"},{"label":"Большая грудь","value":"33"},{"label":"Большая попка","value":"328"},{"label":"Большой член","value":"76"},{"label":"Большой член сына","value":"396"},{"label":"Босс и подчиненный","value":"330"},{"label":"Брак","value":"182"},{"label":"Брак по расчёту","value":"346"},{"label":"Брат","value":"29"},{"label":"Брат и сестра","value":"30"},{"label":"Брюнетка","value":"28"},{"label":"Бэтмен","value":"369"},{"label":"Бэтмен","value":"170"},{"label":"В первый раз","value":"179"},{"label":"Вагинальный секс","value":"57"},{"label":"Вагинальный секс","value":"171"},{"label":"Вайурист","value":"9"},{"label":"Вампиры","value":"133"},{"label":"Ван-пис","value":"335"},{"label":"Веб камера","value":"289"},{"label":"Ведьмы","value":"232"},{"label":"Викинги","value":"307"},{"label":"Виртуальная реальность","value":"382"},{"label":"Вирус","value":"203"},{"label":"Внучка","value":"189"},{"label":"Война","value":"387"},{"label":"Время","value":"81"},{"label":"Второй шанс","value":"244"},{"label":"Вуайеризм","value":"62"},{"label":"Выживание","value":"135"},{"label":"Гарем","value":"107"},{"label":"Гарри поттер","value":"97"},{"label":"Гг имба","value":"87"},{"label":"Генетические модификации","value":"314"},{"label":"Гипноз","value":"143"},{"label":"Главная героиня девушка","value":"147"},{"label":"Главный герой женщина","value":"213"},{"label":"Главный герой извращенец","value":"116"},{"label":"Главный герой мужчина","value":"117"},{"label":"Глубокое горло","value":"94"},{"label":"Гоблины","value":"230"},{"label":"Город","value":"139"},{"label":"Городское фэнтези","value":"297"},{"label":"Грудное молоко","value":"225"},{"label":"Групповой секс","value":"72"},{"label":"Гэнг-бэнг","value":"235"},{"label":"Гяру","value":"222"},{"label":"Двойное проникновение","value":"42"},{"label":"Двойной анал","value":"47"},{"label":"Дворяне","value":"246"},{"label":"Деамоны старший школы","value":"291"},{"label":"Девственница","value":"119"},{"label":"Девушки-монстры","value":"223"},{"label":"Демоны","value":"98"},{"label":"Детектив","value":"249"},{"label":"Дзёсэй","value":"259"},{"label":"Доктор","value":"326"},{"label":"Дом","value":"138"},{"label":"Доминирование","value":"18"},{"label":"Дочь","value":"153"},{"label":"Дочь беременна от отца","value":"348"},{"label":"Древние времена","value":"383"},{"label":"Древний китай","value":"261"},{"label":"Другой мир","value":"365"},{"label":"Дружба","value":"325"},{"label":"Друзья детства","value":"183"},{"label":"Дядя","value":"160"},{"label":"Жена","value":"70"},{"label":"Жена и муж","value":"99"},{"label":"Жена шлюха","value":"236"},{"label":"Женское доминирование","value":"100"},{"label":"Жесткий секс","value":"283"},{"label":"Жёсткий секс","value":"101"},{"label":"Жестокие персонажи","value":"254"},{"label":"Жестокий мир","value":"336"},{"label":"Жестокость","value":"123"},{"label":"Заботливый главный герой","value":"340"},{"label":"Зависимость","value":"370"},{"label":"Заключение","value":"196"},{"label":"Заключённые","value":"384"},{"label":"Замок","value":"140"},{"label":"Запретная любовь","value":"273"},{"label":"Заражение","value":"206"},{"label":"Звёздные войны","value":"120"},{"label":"Зверодевочки","value":"158"},{"label":"Зло","value":"343"},{"label":"Золовка","value":"77"},{"label":"Золотой дождь","value":"146"},{"label":"Зомби апокалипсис","value":"241"},{"label":"Зоофилия","value":"144"},{"label":"Игровые элементы","value":"385"},{"label":"Извращения","value":"192"},{"label":"Измена","value":"71"},{"label":"Изменения внешности","value":"315"},{"label":"Изменения личности","value":"316"},{"label":"Изнасилование","value":"275"},{"label":"Изуку мидория","value":"337"},{"label":"Ино","value":"127"},{"label":"Инопланетяне","value":"304"},{"label":"Интересный сюжет","value":"349"},{"label":"Интимные сцены","value":"322"},{"label":"Интриги и заговоры","value":"262"},{"label":"Интроверт","value":"38"},{"label":"Инфекция","value":"207"},{"label":"Инцест","value":"35"},{"label":"Камера","value":"92"},{"label":"Колледж","value":"82"},{"label":"Комикс","value":"286"},{"label":"Контроль","value":"19"},{"label":"Контроль над разумом","value":"181"},{"label":"Контроль разума","value":"65"},{"label":"Кончил внутрь","value":"221"},{"label":"Королевская семья","value":"358"},{"label":"Коррупция","value":"49"},{"label":"Космос","value":"204"},{"label":"Красивая главная героиня","value":"255"},{"label":"Красивые женщины","value":"321"},{"label":"Красивый главный герой","value":"266"},{"label":"Ксенофилия","value":"386"},{"label":"Кулинария","value":"362"},{"label":"Культвация","value":"86"},{"label":"Кунилингус","value":"152"},{"label":"Лактация","value":"11"},{"label":"Лишение девственности","value":"212"},{"label":"Лоли","value":"218"},{"label":"Лунатизм","value":"279"},{"label":"Любовь","value":"89"},{"label":"Магический мир","value":"357"},{"label":"Магия","value":"80"},{"label":"Мама","value":"3"},{"label":"Мама беременна от сына","value":"274"},{"label":"Мама и дочь","value":"163"},{"label":"Мама и сын","value":"1"},{"label":"Мама супермена","value":"39"},{"label":"Манипуляция временем","value":"292"},{"label":"Марвел","value":"290"},{"label":"Марвел","value":"173"},{"label":"Марти стью","value":"102"},{"label":"Массаж","value":"224"},{"label":"Мастурбация","value":"63"},{"label":"Мать","value":"239"},{"label":"Мать и дочь","value":"381"},{"label":"Мать и сын","value":"237"},{"label":"Мафия","value":"256"},{"label":"Мачеха","value":"197"},{"label":"Мачеха беременна от сына","value":"395"},{"label":"Мачеха и сын","value":"394"},{"label":"Медсестра","value":"341"},{"label":"Межрассовый","value":"54"},{"label":"Месть","value":"306"},{"label":"Меха","value":"268"},{"label":"Ми","value":"243"},{"label":"Милф","value":"34"},{"label":"Милф","value":"180"},{"label":"Минет","value":"52"},{"label":"Мистика","value":"250"},{"label":"Младшая сестра","value":"154"},{"label":"Модель","value":"324"},{"label":"Монстры","value":"333"},{"label":"Мошеничество","value":"44"},{"label":"Мужская беременность","value":"363"},{"label":"Мужчина протагонист","value":"108"},{"label":"Музыка","value":"331"},{"label":"Мэри съюха","value":"309"},{"label":"Наруто","value":"46"},{"label":"Насилие","value":"128"},{"label":"Насилие и жестокость","value":"193"},{"label":"Научный эксперимент","value":"313"},{"label":"Нежить","value":"373"},{"label":"Некромантия","value":"298"},{"label":"Ненормативная лексика","value":"124"},{"label":"Нетораре","value":"69"},{"label":"Нетори","value":"284"},{"label":"Нижнее бельё","value":"392"},{"label":"Ниндзя","value":"296"},{"label":"Ношеные трусики","value":"166"},{"label":"Нудизм","value":"156"},{"label":"Няня","value":"88"},{"label":"Обмен женами","value":"78"},{"label":"Оборотни","value":"136"},{"label":"Обратный гарем","value":"214"},{"label":"Одержимость","value":"345"},{"label":"Омегаверс","value":"368"},{"label":"Оплодотворение","value":"157"},{"label":"Оральный секс","value":"10"},{"label":"Оргия","value":"356"},{"label":"Орки","value":"388"},{"label":"От слабого к сильному","value":"351"},{"label":"Отец","value":"2"},{"label":"Отец делится с сыном","value":"6"},{"label":"Отец и дочь","value":"205"},{"label":"Отец куколд","value":"272"},{"label":"Офис","value":"234"},{"label":"Падчерица","value":"271"},{"label":"Пайзури","value":"195"},{"label":"Папа и дочь","value":"360"},{"label":"Паразиты","value":"208"},{"label":"Параллельный мир","value":"320"},{"label":"Пародия","value":"371"},{"label":"Первый раз","value":"159"},{"label":"Перемещение в другой мир","value":"347"},{"label":"Перемещение во времени","value":"245"},{"label":"Перерождение","value":"109"},{"label":"Перерождение в злодея","value":"202"},{"label":"Пирсинг","value":"145"},{"label":"Писатель","value":"267"},{"label":"Планомерное развитие событий","value":"350"},{"label":"Повседневность","value":"247"},{"label":"Подглядывание","value":"280"},{"label":"Подчинение","value":"106"},{"label":"Подчинение и унижение","value":"211"},{"label":"Поедание киски","value":"26"},{"label":"Покемоны","value":"132"},{"label":"Покорная","value":"8"},{"label":"Полигамия","value":"352"},{"label":"Половое воспитание","value":"37"},{"label":"Полулюди","value":"231"},{"label":"Попаданец","value":"103"},{"label":"Попадание в книгу","value":"361"},{"label":"Порка","value":"20"},{"label":"Порно","value":"278"},{"label":"Постапокалипсис","value":"332"},{"label":"Потеря девственности","value":"142"},{"label":"Похищение","value":"161"},{"label":"Приключения","value":"134"},{"label":"Принуждение","value":"66"},{"label":"Психология","value":"238"},{"label":"Публично","value":"85"},{"label":"Публичный секс","value":"233"},{"label":"Раб","value":"353"},{"label":"Рабы","value":"308"},{"label":"Разват","value":"58"},{"label":"Разврат","value":"64"},{"label":"Райзен","value":"95"},{"label":"Раса инопланетных космических лесбиянок","value":"294"},{"label":"Рестленг","value":"84"},{"label":"Риас гремори","value":"389"},{"label":"Романтика","value":"178"},{"label":"Рыжий","value":"90"},{"label":"Сакура","value":"126"},{"label":"Самолет","value":"93"},{"label":"Санса старк","value":"277"},{"label":"Санта","value":"372"},{"label":"Свадьба","value":"73"},{"label":"Сверхсила","value":"228"},{"label":"Сверхъестественное","value":"319"},{"label":"Свингеры","value":"201"},{"label":"Свободные отношения","value":"186"},{"label":"Связывание","value":"137"},{"label":"Сёдзе","value":"264"},{"label":"Секс","value":"21"},{"label":"Секс без проникновения","value":"68"},{"label":"Секс втроем","value":"43"},{"label":"Секс игрушки","value":"59"},{"label":"Секс рабыня","value":"55"},{"label":"Секс с монстрами","value":"56"},{"label":"Секс с учителем","value":"184"},{"label":"Секса будет много","value":"151"},{"label":"Сексуальное желание","value":"209"},{"label":"Селфцест","value":"397"},{"label":"Семья","value":"5"},{"label":"Сестра","value":"31"},{"label":"Сестра беременна от брата","value":"355"},{"label":"Сильный с самого начала","value":"112"},{"label":"Симбиоз","value":"50"},{"label":"Сирена","value":"51"},{"label":"Система","value":"104"},{"label":"Сиськи","value":"285"},{"label":"Сквирт","value":"164"},{"label":"Скрытый секс","value":"287"},{"label":"Служанка","value":"215"},{"label":"Соб","value":"281"},{"label":"Соблазнение","value":"162"},{"label":"Современность","value":"217"},{"label":"Соколиный Глаз","value":"176"},{"label":"Соперничество","value":"216"},{"label":"Соседи","value":"61"},{"label":"Соседка","value":"312"},{"label":"Сперма","value":"229"},{"label":"Сперма на лицо","value":"199"},{"label":"Спящие","value":"187"},{"label":"Старшая сестра","value":"311"},{"label":"Стеб","value":"111"},{"label":"Студенты","value":"177"},{"label":"Суккуб","value":"390"},{"label":"Супергерои","value":"305"},{"label":"Суперспособности","value":"300"},{"label":"Сын","value":"4"},{"label":"Твинцест","value":"188"},{"label":"Темное фэнтези","value":"293"},{"label":"Тентакли","value":"303"},{"label":"Тетя","value":"113"},{"label":"Тетя беременна от племянника","value":"376"},{"label":"Тетя и племянник","value":"377"},{"label":"Техника","value":"302"},{"label":"Технологии","value":"301"},{"label":"Тёща","value":"270"},{"label":"Тиран","value":"310"},{"label":"Трагическое прошлое","value":"251"},{"label":"Трансмиграция","value":"269"},{"label":"Трансформация","value":"317"},{"label":"Триллер","value":"344"},{"label":"Трусики","value":"165"},{"label":"Убийства","value":"258"},{"label":"Удача","value":"299"},{"label":"Ужасы","value":"375"},{"label":"Улучшение тела","value":"288"},{"label":"Умная главная героиня","value":"242"},{"label":"Умные персонажи","value":"257"},{"label":"Умный главный герой","value":"354"},{"label":"Университет","value":"323"},{"label":"Ученик","value":"83"},{"label":"Ф","value":"149"},{"label":"Фанатичная любовь","value":"366"},{"label":"Фанаты","value":"367"},{"label":"Фантастика","value":"295"},{"label":"Фанфик","value":"40"},{"label":"Фелляция","value":"22"},{"label":"Ферма","value":"191"},{"label":"Фетиш","value":"60"},{"label":"Флэш","value":"121"},{"label":"Футанария","value":"150"},{"label":"Фэнтези","value":"359"},{"label":"Хентай","value":"75"},{"label":"Хината","value":"110"},{"label":"Холодная главная героиня","value":"263"},{"label":"Черная вдова","value":"220"},{"label":"Черная Вдова","value":"175"},{"label":"Черный юмор","value":"374"},{"label":"Читерство","value":"45"},{"label":"Чтение мыслей","value":"227"},{"label":"Чудовища","value":"378"},{"label":"Шантаж","value":"129"},{"label":"Школа","value":"118"},{"label":"Школьная жизнь","value":"318"},{"label":"Шлюха","value":"32"},{"label":"Шоу-бизнес","value":"329"},{"label":"Шоу-бизнес","value":"172"},{"label":"Эксгибиционизм","value":"74"},{"label":"Элементы бдсм","value":"185"},{"label":"Эмма уотсон","value":"342"},{"label":"Эмма Уотсон","value":"169"},{"label":"Эротика","value":"36"},{"label":"Этти","value":"327"},{"label":"Юмор","value":"105"},{"label":"Яндере","value":"155"},{"label":"Bl","value":"125"},{"label":"Gl","value":"96"},{"label":"Harry potter","value":"190"},{"label":"Harry potter","value":"168"},{"label":"Hentai","value":"282"},{"label":"Marvel","value":"194"},{"label":"Marvel","value":"174"},{"label":"Milf","value":"167"}],"inputType":FilterInputs.Checkbox}]; + + export const filters = [ + { + key: 'sort', + label: 'Сортировка', + values: [ + { label: 'По рейтингу', value: '6' }, + { label: 'По степени готовности', value: '0' }, + { label: 'По названию на языке оригинала', value: '1' }, + { label: 'По названию на языке перевода', value: '2' }, + { label: 'По дате создания', value: '3' }, + { label: 'По дате последней активности', value: '4' }, + { label: 'По просмотрам', value: '5' }, + { label: 'По кол-ву переведённых глав', value: '7' }, + { label: 'По кол-ву лайков', value: '8' }, + { label: 'По кол-ву страниц', value: '10' }, + { label: 'По кол-ву бесплатных глав', value: '11' }, + { label: 'По кол-ву рецензий', value: '12' }, + { label: 'По кол-ву в закладках', value: '13' }, + { label: 'По кол-ву в избранном', value: '14' }, + ], + inputType: FilterInputs.Picker, + }, + { + key: 'type', + label: 'Тип', + values: [ + { label: 'Неважно', value: '0' }, + { label: 'Только переводы', value: '1' }, + { label: 'Только авторские', value: '2' }, + ], + inputType: FilterInputs.Picker, + }, + { + key: 'atmosphere', + label: 'Атмосфера', + values: [ + { label: 'Неважно', value: '0' }, + { label: 'Позитивная', value: '1' }, + { label: 'Dark', value: '2' }, + ], + inputType: FilterInputs.Picker, + }, + { + key: 'trash', + label: 'Другое', + values: [ + { label: 'Готовые на 100%', value: 'ready' }, + { label: 'Доступные для перевода', value: 'tr' }, + { label: 'Доступные для скачивания', value: 'gen' }, + { label: 'Завершённые проекты', value: 'wealth' }, + { label: 'Распродажа', value: 'discount' }, + { label: 'Только онгоинги', value: 'ongoings' }, + { label: 'Убрать машинный', value: 'remove_machinelate' }, + ], + inputType: FilterInputs.Checkbox, + }, + ...customFilters, + { + key: 'adult', + label: 'Возрастные ограничения', + values: [ + { label: 'Все', value: '0' }, + { label: 'Убрать 18+', value: '1' }, + { label: 'Только 18+', value: '2' }, + ], + inputType: FilterInputs.Picker, + }, + ]; + + export const popularNovels: Plugin.popularNovels = async function ( + page, + { filters, showLatestNovels } + ) { + let url = baseUrl + '/search?t=&cat=2'; + url += '&sort=' + showLatestNovels ? '4' : filters?.sort || '6'; + url += '&type=' + filters?.type || '0'; + url += '&atmosphere=' + filters?.atmosphere || '0'; + url += '&adult=' + filters?.adult || '0'; + + if (filters?.genres instanceof Array) { + url += filters.genres.map(i => '&genres[]='+i).join(''); + } + + if (filters?.tags instanceof Array) { + url += filters.tags.map(i => '&tags[]='+i).join(''); + } + + if (filters?.trash instanceof Array) { + url += filters.trash.map(i => '&' + i + '=1'); + } + + url += '&Book_page=' + page; + + const result = await fetchApi(url); + const body = await result.text(); + const loadedCheerio = parseHTML(body); + + const novels: Novel.Item[] = []; + + loadedCheerio( + 'ul[class="search-results"] > li:not([class="ad_type_catalog"])', + ).each(function () { + novels.push({ + name: loadedCheerio(this).find('p > a').text(), + cover: baseUrl + loadedCheerio(this).find('img').attr('src'), + url: baseUrl + loadedCheerio(this).find('p > a').attr('href') || '', + }); + }); + + return novels; + }; + + + export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const novel: Novel.instance = { + url: novelUrl, + chapters: [], + }; + let result = await fetchApi(novelUrl); + if (result.url.includes('mature?path=')) { + const formData = new FormData(); + formData.append('path', novelUrl); + formData.append('ok', 'Да'); + + await fetch(result.url, { + method: 'POST', + body: formData, + }); + + result = await fetchApi(novelUrl); + } + const body = await result.text(); + const loadedCheerio = parseHTML(body); + + novel.name = loadedCheerio('div[class="container"] > div > div > h1') + .text() + .trim(); + novel.cover = + baseUrl + loadedCheerio('div[class="images"] > div img').attr('src'); + novel.summary = loadedCheerio('#Info > div:nth-child(3)').text(); + let genres: string[] = []; + + loadedCheerio('div[class="span5"] > p').each(function () { + switch (loadedCheerio(this).find('strong').text()) { + case 'Автор:': + novel.author = loadedCheerio(this).find('em > a').text().trim(); + break; + case 'Выпуск:': + novel.status = + loadedCheerio(this).find('em').text().trim() === 'продолжается' + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + case 'Тэги:': + loadedCheerio(this) + .find('em > a') + .each(function () { + genres.push(loadedCheerio(this).text()); + }); + break; + case 'Жанры:': + loadedCheerio(this) + .find('em > a') + .each(function () { + genres.push(loadedCheerio(this).text()); + }); + break; + } + }); + + if (genres.length > 0) { + novel.genres = genres.reverse().join(','); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('table > tbody > tr.chapter_row').each(function () { + const chapterName = loadedCheerio(this) + .find('td[class="t"] > a') + .text() + .trim(); + const releaseDate = loadedCheerio(this) + .find('td > span') + .attr('title') + ?.trim(); + const chapterUrl = baseUrl + loadedCheerio(this) + .find('td[class="t"] > a') + .attr('href'); + + if ( + loadedCheerio(this).find('td > span[class="disabled"]').length < 1 && + releaseDate + ) { + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + } + }); + + novel.chapters = chapters; + return novel; + }; + + export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + let result = await fetchApi(chapterUrl); + if (result.url.includes('mature?path=')) { + const formData = new FormData(); + formData.append('ok', 'Да'); + + await fetch(result.url, { + method: 'POST', + body: formData, + }); + + result = await fetchApi(chapterUrl); + } + const body = await result.text(); + const loadedCheerio = parseHTML(body); + + + const chapterText = loadedCheerio('.content-text').html(); + + + return chapterText; + }; + + export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const result = await fetchApi( + baseUrl + '/search/autocomplete?query=' + searchTerm, + ); + let json: any = await result.json(); + + json.forEach((item: any) => { + const novelName = item.title_one + ' / ' + item.title_two; + const novelCover = baseUrl + item.img; + const novelUrl = baseUrl + item.url; + + novels.push({ name: novelName, cover: novelCover, url: novelUrl }); + }); + + return novels; + }; + + export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url, {}); + }; + + \ No newline at end of file diff --git a/plugins/russian/Rulate[rulate].ts b/plugins/russian/Rulate[rulate].ts new file mode 100644 index 0000000..5d393df --- /dev/null +++ b/plugins/russian/Rulate[rulate].ts @@ -0,0 +1,271 @@ + + import { load as parseHTML } from "cheerio"; + // import dayjs from 'dayjs'; + import { fetchFile, fetchApi } from "@libs/fetch"; + import { Novel, Plugin, Chapter } from "@typings/plugin"; + // import { parseMadaraDate } from "@libs/parseMadaraDate"; + // import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; + // import { showToast } from "@libs/showToast"; + import { Filter, FilterInputs } from "@libs/filterInputs"; + import { NovelStatus } from '@libs/novelStatus'; + // import { defaultCover } from "@libs/defaultCover"; + + + export const id = "tl.rulate"; + export const name = "Rulate"; + export const icon = ""; + export const version = "1.0.0"; + export const site = "https://tl.rulate.ru"; + exports["protected"] = false; + + const baseUrl = site; + + const customFilters: Filter[] = [{"key":"genres","label":"Жанры","values":[{"label":"18+","value":"52"},{"label":"Арт","value":"1"},{"label":"Боевик","value":"2"},{"label":"Боевые искусства","value":"3"},{"label":"Гарем","value":"5"},{"label":"Героическое фэнтези","value":"7"},{"label":"Детектив","value":"8"},{"label":"Дзёсэй","value":"9"},{"label":"Додзинси","value":"10"},{"label":"Драма","value":"11"},{"label":"История","value":"13"},{"label":"Киберпанк","value":"46"},{"label":"Кодомо","value":"14"},{"label":"Комедия","value":"15"},{"label":"Литрпг","value":"48"},{"label":"Махо-сёдзё","value":"16"},{"label":"Мелодрама","value":"49"},{"label":"Меха","value":"17"},{"label":"Мистика","value":"18"},{"label":"Научная фантастика","value":"19"},{"label":"Повседневность","value":"20"},{"label":"Постапокалиптика","value":"21"},{"label":"Приключения","value":"22"},{"label":"Психология","value":"23"},{"label":"Романтика","value":"24"},{"label":"Самурайский боевик","value":"25"},{"label":"Сверхъестественное","value":"26"},{"label":"Сёдзё","value":"27"},{"label":"Сёнэн","value":"29"},{"label":"Смат","value":"45"},{"label":"Спорт","value":"31"},{"label":"Сэйнэн","value":"32"},{"label":"Сюаньхуа","value":"44"},{"label":"Сюаньхуань","value":"47"},{"label":"Сянься (XianXia)","value":"42"},{"label":"Трагедия","value":"33"},{"label":"Триллер","value":"34"},{"label":"Ужасы","value":"35"},{"label":"Уся (wuxia)","value":"41"},{"label":"Фантастика","value":"36"},{"label":"Фанфик","value":"50"},{"label":"Фэнтези","value":"37"},{"label":"Школа","value":"38"},{"label":"Этти","value":"39"},{"label":"Bl","value":"43"},{"label":"Gl","value":"40"}],"inputType":FilterInputs.Checkbox},{"key":"tags","label":"Тэги","values":[{"label":"12+","value":"3316"},{"label":"16+","value":"83"},{"label":"18+","value":"1749"},{"label":"21+","value":"3191"},{"label":"Абсурд","value":"7973"},{"label":"Авантюристы","value":"222"},{"label":"Автомат","value":"5272"},{"label":"Авторский мир","value":"1062"},{"label":"Агрессивные персонажи","value":"3249"},{"label":"Ад","value":"2623"},{"label":"Ад и рай","value":"817"},{"label":"Адам дженсен","value":"1649"},{"label":"Адаптация произведения","value":"192"},{"label":"Адвокат","value":"6340"},{"label":"Адекватные главные герои","value":"1953"},{"label":"Азартные игры","value":"5301"},{"label":"Азгард","value":"3494"},{"label":"Азула","value":"5550"},{"label":"Аид","value":"7641"},{"label":"Айдолы","value":"4081"},{"label":"Академия","value":"316"},{"label":"Акамэ","value":"5059"},{"label":"Аквакинез","value":"5952"},{"label":"Акира","value":"2639"},{"label":"Акнология","value":"4634"},{"label":"Актерское мастерство","value":"4798"},{"label":"Актёры","value":"7506"},{"label":"Активные герои","value":"6376"},{"label":"Акутагава рюноске","value":"4113"},{"label":"Аладдин","value":"6489"},{"label":"Алая ведьма","value":"4767"},{"label":"Алиса","value":"4761"},{"label":"Алкоголь","value":"5508"},{"label":"Аллегория","value":"271"},{"label":"Алукард","value":"4914"},{"label":"Алхимики","value":"1164"},{"label":"Алхимия","value":"1473"},{"label":"Алчность","value":"6897"},{"label":"Альбус дамблдор","value":"5784"},{"label":"Альтернативное развитие событий","value":"5330"},{"label":"Альянсы","value":"328"},{"label":"Амбициозный главный герой","value":"3751"},{"label":"Америка","value":"4082"},{"label":"Амнезия","value":"2207"},{"label":"Аморальный герой","value":"18"},{"label":"Анальный секс","value":"1942"},{"label":"Анбу","value":"8129"},{"label":"Ангелы","value":"857"},{"label":"Ангелы и демоны","value":"1327"},{"label":"Ангст","value":"4638"},{"label":"Андроиды","value":"1993"},{"label":"Анимализм","value":"5249"},{"label":"Аниме","value":"1294"},{"label":"Анко митараши","value":"5768"},{"label":"Антагонист","value":"2144"},{"label":"Антигерой","value":"7725"},{"label":"Антиквариат","value":"3552"},{"label":"Антимаг","value":"1348"},{"label":"Антиутопия","value":"662"},{"label":"Апокалипсис","value":"1498"},{"label":"Аристократия","value":"371"},{"label":"Аристократы","value":"7338"},{"label":"Армия","value":"1723"},{"label":"Артас","value":"6700"},{"label":"Артефакты","value":"2296"},{"label":"Археология","value":"6523"},{"label":"Асгард","value":"3635"},{"label":"Ассасин","value":"1415"},{"label":"Асуна","value":"5044"},{"label":"Аугментации","value":"7751"},{"label":"Ацуши накаджима","value":"4109"},{"label":"Аянокоджи киетака","value":"6862"},{"label":"Бандиты","value":"2188"},{"label":"Басейн","value":"6847"},{"label":"Баскетбол","value":"7933"},{"label":"Бастарды","value":"76"},{"label":"Бдсм","value":"444"},{"label":"Бедность","value":"1207"},{"label":"Без культивации","value":"668"},{"label":"Без любовной линий","value":"2225"},{"label":"Без перерождений","value":"262"},{"label":"Без попаданца","value":"8065"},{"label":"Без системы","value":"747"},{"label":"Без цензуры","value":"7525"},{"label":"Без юмора","value":"763"},{"label":"Бездна","value":"1026"},{"label":"Безжалостное домашнее животное","value":"1245"},{"label":"Безжалостные персонажи","value":"2986"},{"label":"Беззаботные персонажи ","value":"3123"},{"label":"Безответная любовь","value":"7815"},{"label":"Безработный","value":"2202"},{"label":"Безумие","value":"37"},{"label":"Безумные персонажи","value":"2049"},{"label":"Бейсбол","value":"7623"},{"label":"Беременность","value":"2122"},{"label":"Бесконечный поток","value":"7556"},{"label":"Беспечные персонажи","value":"742"},{"label":"Бесплатно","value":"8403"},{"label":"Бессмертные персонажи","value":"566"},{"label":"Бесстрашные персонажи","value":"6921"},{"label":"Бесстыдные персонажи","value":"1257"},{"label":"Бесстыдный главный герой","value":"8190"},{"label":"Библиотека","value":"4210"},{"label":"Библия","value":"488"},{"label":"Бизнес","value":"704"},{"label":"Биология","value":"2220"},{"label":"Биомехи","value":"1968"},{"label":"Биседзе","value":"777"},{"label":"Битва за веру","value":"721"},{"label":"Битва за трон","value":"7211"},{"label":"Битва королевств","value":"1151"},{"label":"Битва рас","value":"6989"},{"label":"Битвы","value":"1740"},{"label":"Благородные персонажи","value":"2201"},{"label":"Близнецы","value":"2931"},{"label":"Богатство","value":"6522"},{"label":"Богатые персонажи","value":"520"},{"label":"Боги","value":"173"},{"label":"Богини","value":"30"},{"label":"Богиня","value":"8324"},{"label":"Богоубийца","value":"1253"},{"label":"Богохульство","value":"95"},{"label":"Боевая академия","value":"2108"},{"label":"Боевик","value":"529"},{"label":"Боевые искусства","value":"2090"},{"label":"Большая грудь","value":"4717"},{"label":"Большой член","value":"5299"},{"label":"Борос","value":"6533"},{"label":"Борьба","value":"1925"},{"label":"Борьба за власть","value":"8072"},{"label":"Босс и подчиненный","value":"7157"},{"label":"Бояръ","value":"2307"},{"label":"Брак","value":"1558"},{"label":"Брак по расчету","value":"1389"},{"label":"Брат и сестра","value":"615"},{"label":"Братский комплекс","value":"2321"},{"label":"Броманс","value":"1103"},{"label":"Брошенные дети","value":"2846"},{"label":"Буддизм","value":"1871"},{"label":"Будущее","value":"1820"},{"label":"Бывший муж","value":"8080"},{"label":"Быстрое развитие","value":"1443"},{"label":"Бэтгерл","value":"2584"},{"label":"Бэтмен","value":"4668"},{"label":"В первый раз","value":"6212"},{"label":"В этот же мир","value":"49"},{"label":"Вагинальный секс","value":"4742"},{"label":"Ваканда","value":"8331"},{"label":"Валькирии","value":"4047"},{"label":"Вампиры","value":"2206"},{"label":"Веб камера","value":"5664"},{"label":"Веб новелла","value":"2349"},{"label":"Ведьмак","value":"843"},{"label":"Ведьмы","value":"643"},{"label":"Везучие персонажи","value":"1879"},{"label":"Веном","value":"3994"},{"label":"Вестерн","value":"2131"},{"label":"Вестерос","value":"8171"},{"label":"Взросление","value":"624"},{"label":"Взрослый главный герой","value":"2416"},{"label":"Видео игры","value":"2630"},{"label":"Викинги","value":"267"},{"label":"Вино","value":"6972"},{"label":"Вирт","value":"6213"},{"label":"Виртуальная реальность","value":"325"},{"label":"Вирусы","value":"3327"},{"label":"Владыка демонов","value":"555"},{"label":"Власть","value":"756"},{"label":"Влюбленность","value":"6132"},{"label":"Внешний вид отличается от личности","value":"1938"},{"label":"Внучка","value":"8399"},{"label":"Военные","value":"1096"},{"label":"Возрождение","value":"1160"},{"label":"Воины","value":"287"},{"label":"Война","value":"443"},{"label":"Войны","value":"2431"},{"label":"Воландеморт","value":"4278"},{"label":"Волейбол","value":"6415"},{"label":"Волки","value":"1166"},{"label":"Волшебники","value":"122"},{"label":"Волшебные существа","value":"1689"},{"label":"Волшебство","value":"719"},{"label":"Вор","value":"1399"},{"label":"Воровка","value":"6948"},{"label":"Ворон","value":"848"},{"label":"Воскрешение","value":"6915"},{"label":"Воспоминания из другого мира","value":"1366"},{"label":"Воспоминания из прошлого","value":"1372"},{"label":"Воссоединие семьи","value":"8130"},{"label":"Восхождение","value":"380"},{"label":"Враги становятся любовниками","value":"978"},{"label":"Врата","value":"40"},{"label":"Врата в другой мир","value":"1957"},{"label":"Врач","value":"1243"},{"label":"Временная петля","value":"2852"},{"label":"Врмморпг","value":"2043"},{"label":"Все дозволено","value":"96"},{"label":"Всемогущий главный герой","value":"1414"},{"label":"Второй шанс","value":"2372"},{"label":"Вуайеризм","value":"2498"},{"label":"Выживание","value":"501"},{"label":"Вымышленные существа","value":"3466"},{"label":"Вынужденный брак","value":"7705"},{"label":"Высокие технологии","value":"653"},{"label":"Высокомерные персонажи","value":"3250"},{"label":"Галустян","value":"5199"},{"label":"Гангстеры","value":"7704"},{"label":"Гарем","value":"440"},{"label":"Гарри поттер","value":"226"},{"label":"Геймеры","value":"2130"},{"label":"Генетические модификации","value":"1750"},{"label":"Гениальный главный герой","value":"1500"},{"label":"Гений","value":"1650"},{"label":"Гермиона грейнджер","value":"4280"},{"label":"Герои","value":"185"},{"label":"Героическое фэнтези","value":"1148"},{"label":"Гигаполис","value":"8393"},{"label":"Гильгамеш","value":"6031"},{"label":"Гильдии","value":"900"},{"label":"Гильдия авантюристов","value":"1858"},{"label":"Гинтама","value":"4544"},{"label":"Гипноз","value":"358"},{"label":"Главная героиня влюбляется первой","value":"973"},{"label":"Главный герой бог","value":"164"},{"label":"Главный герой влюбляется первым","value":"3629"},{"label":"Главный герой выживальщик","value":"120"},{"label":"Главный герой гонг","value":"7877"},{"label":"Главный герой девушка","value":"257"},{"label":"Главный герой демон","value":"136"},{"label":"Главный герой женщина","value":"7329"},{"label":"Главный герой жестокий","value":"1553"},{"label":"Главный герой извращенец","value":"94"},{"label":"Главный герой имба","value":"2421"},{"label":"Главный герой киборг","value":"7780"},{"label":"Главный герой монстр","value":"869"},{"label":"Главный герой мужчина","value":"1997"},{"label":"Главный герой на стороне сил зла","value":"1514"},{"label":"Главный герой не адекватный","value":"5"},{"label":"Главный герой не психопат","value":"7804"},{"label":"Главный герой не человек","value":"2172"},{"label":"Главный герой не эмоционален","value":"1150"},{"label":"Главный герой подросток","value":"2215"},{"label":"Главный герой позитивный","value":"7787"},{"label":"Главный герой психопат","value":"7803"},{"label":"Главный герой ребенок","value":"2347"},{"label":"Главный герой русский","value":"2417"},{"label":"Главный герой сильный с самого начала","value":"88"},{"label":"Главный герой скрывает свою силу","value":"1059"},{"label":"Главный герой эгоист","value":"8326"},{"label":"Глотание спермы","value":"4491"},{"label":"Гномы","value":"1870"},{"label":"Гоблины","value":"2029"},{"label":"Годзилла","value":"6990"},{"label":"Големы","value":"1690"},{"label":"Голливуд","value":"7754"},{"label":"Головоломка","value":"1697"},{"label":"Горничные","value":"8091"},{"label":"Гробница","value":"388"},{"label":"Групповой секс","value":"1314"},{"label":"Дазай осаму","value":"4112"},{"label":"Даньмэй","value":"3411"},{"label":"Даосизм","value":"258"},{"label":"Дарк соулс","value":"6057"},{"label":"Дарт вейдер","value":"6826"},{"label":"Дафна гринграсс","value":"5192"},{"label":"Дворяне","value":"7204"},{"label":"Девушки","value":"7668"},{"label":"Девушки монстры","value":"2231"},{"label":"Дедушка и внучка","value":"8400"},{"label":"Дейви джонс","value":"8061"},{"label":"Действие","value":"1425"},{"label":"Демон лорд","value":"153"},{"label":"Демонология","value":"2024"},{"label":"Демоны","value":"1406"},{"label":"Детектив","value":"1265"},{"label":"Дети","value":"580"},{"label":"Джарвис","value":"933"},{"label":"Джедай","value":"4442"},{"label":"Джин","value":"4024"},{"label":"Джокер","value":"3351"},{"label":"Джон сноу","value":"7412"},{"label":"Джоффри баратеон","value":"3936"},{"label":"Дзесэй","value":"678"},{"label":"Дин винчестер","value":"6953"},{"label":"Династии","value":"2982"},{"label":"Динозавры","value":"3666"},{"label":"Директор","value":"1417"},{"label":"Для девушек","value":"1441"},{"label":"Дневник","value":"298"},{"label":"Добби","value":"6762"},{"label":"Добрый мир","value":"1685"},{"label":"Договоры с богами","value":"2152"},{"label":"Договоры с демонами","value":"1922"},{"label":"Доктор","value":"1991"},{"label":"Доктор стрэндж","value":"7634"},{"label":"Доминирование","value":"2054"},{"label":"Дота","value":"6965"},{"label":"Доудзюцу","value":"7133"},{"label":"Дочь","value":"3291"},{"label":"Драко малфой","value":"46"},{"label":"Драконы","value":"282"},{"label":"Драконы оборотни","value":"8082"},{"label":"Драма","value":"611"},{"label":"Древние времена","value":"7781"},{"label":"Древний египет","value":"7614"},{"label":"Древний китай","value":"473"},{"label":"Древний мир","value":"4"},{"label":"Дрочка","value":"4565"},{"label":"Другая вселенная","value":"39"},{"label":"Другие планеты","value":"2371"},{"label":"Другой мир","value":"2248"},{"label":"Дружба","value":"1903"},{"label":"Друзья детства","value":"1153"},{"label":"Духи","value":"114"},{"label":"Духовные силы","value":"1955"},{"label":"Души","value":"1212"},{"label":"Дьявол","value":"1094"},{"label":"Дьявольские фрукты","value":"1564"},{"label":"Дэдпул","value":"4399"},{"label":"Евнух","value":"6293"},{"label":"Европейская атмосфера","value":"5837"},{"label":"Египет","value":"528"},{"label":"Еда","value":"2056"},{"label":"Единороги","value":"437"},{"label":"Екаи","value":"655"},{"label":"Жалкий главный герой","value":"97"},{"label":"Жена","value":"5729"},{"label":"Женское доминирование","value":"4622"},{"label":"Женщина в теле мужчины","value":"2748"},{"label":"Женщина кошка","value":"7513"},{"label":"Жесткий секс","value":"2238"},{"label":"Жестокие персонажи ","value":"3153"},{"label":"Жестокий мир","value":"1532"},{"label":"Жестокое обращение с детьми","value":"84"},{"label":"Жестокость","value":"7880"},{"label":"Животные","value":"7488"},{"label":"Животные компаньоны","value":"2061"},{"label":"Жизнь и смерть","value":"2014"},{"label":"Жнец","value":"6288"},{"label":"Жойен рид","value":"3926"},{"label":"Заботливый главный герой","value":"338"},{"label":"Закалка тела","value":"148"},{"label":"Заклинания","value":"558"},{"label":"Заключенные","value":"3052"},{"label":"Занпакто","value":"1102"},{"label":"Запретная любовь","value":"8033"},{"label":"Запутанный сюжет","value":"1041"},{"label":"Захват мира","value":"315"},{"label":"Зачарованные","value":"4169"},{"label":"Звери","value":"1638"},{"label":"Зверодевочки","value":"2006"},{"label":"Зверолюди","value":"1018"},{"label":"Земля","value":"82"},{"label":"Злая организация","value":"7768"},{"label":"Зло","value":"1512"},{"label":"Злодей","value":"1060"},{"label":"Злодейка","value":"2376"},{"label":"Злой главный герой","value":"616"},{"label":"Злые боги","value":"7109"},{"label":"Злые персонажи","value":"3243"},{"label":"Змей","value":"5721"},{"label":"Знаменитости","value":"1341"},{"label":"Знание медицины","value":"1411"},{"label":"Золотой дождь","value":"1828"},{"label":"Золушка","value":"2980"},{"label":"Зомби","value":"1743"},{"label":"Зомби апокалипсис","value":"912"},{"label":"Зона 51","value":"3051"},{"label":"Зооморфы","value":"375"},{"label":"Зоофилия","value":"1165"},{"label":"Зорро","value":"6736"},{"label":"Зрелые женщины","value":"1458"},{"label":"Игра","value":"663"},{"label":"Игра на выживание","value":"2182"},{"label":"Игровая система","value":"384"},{"label":"Игровые элементы","value":"720"},{"label":"Извращения","value":"651"},{"label":"Измена","value":"1874"},{"label":"Изменение характера","value":"1113"},{"label":"Изменения внешности","value":"259"},{"label":"Изменения личности","value":"1080"},{"label":"Изнасилование","value":"1283"},{"label":"Изобретения","value":"126"},{"label":"Изуку мидория","value":"6967"},{"label":"Император","value":"867"},{"label":"Империи","value":"712"},{"label":"Империя","value":"1716"},{"label":"Инвалидность","value":"2073"},{"label":"Инквизитор","value":"143"},{"label":"Инкуб","value":"3264"},{"label":"Инопланетное вторжение","value":"209"},{"label":"Инопланетяне","value":"866"},{"label":"Интерактив с читателями","value":"472"},{"label":"Интересный сюжет","value":"7374"},{"label":"Интернет","value":"739"},{"label":"Интимные сцены","value":"3450"},{"label":"Интрига","value":"317"},{"label":"Интриги и заговоры","value":"1009"},{"label":"Инцест","value":"1934"},{"label":"Иные миры","value":"35"},{"label":"Исекай","value":"2979"},{"label":"Искусственный интеллект","value":"1900"},{"label":"Исследование","value":"1626"},{"label":"Исторический роман","value":"573"},{"label":"Итачи учиха","value":"4556"},{"label":"Ичиго","value":"7254"},{"label":"Йода","value":"6829"},{"label":"Йоруичи","value":"6755"},{"label":"Калипсо","value":"8062"},{"label":"Каннибализм","value":"2249"},{"label":"Капитан америка","value":"6301"},{"label":"Каракура","value":"7473"},{"label":"Карточная игра","value":"1557"},{"label":"Кастиэль","value":"6954"},{"label":"Кафе","value":"3934"},{"label":"Кацуки бакуго","value":"7696"},{"label":"Квесты","value":"1707"},{"label":"Киберпанк","value":"1614"},{"label":"Киберспорт","value":"1984"},{"label":"Кино","value":"7126"},{"label":"Китай","value":"3762"},{"label":"Кицунэ","value":"241"},{"label":"Кланы","value":"834"},{"label":"Клоны","value":"1624"},{"label":"Книги","value":"1068"},{"label":"Книжный червь","value":"521"},{"label":"Колдовство","value":"2681"},{"label":"Колледж","value":"5058"},{"label":"Коллекционер","value":"2588"},{"label":"Кольцо","value":"1628"},{"label":"Комедия","value":"1430"},{"label":"Коммунисты","value":"1042"},{"label":"Коноха","value":"2827"},{"label":"Контракт","value":"4304"},{"label":"Контроль разума","value":"485"},{"label":"Конфеты","value":"341"},{"label":"Кончил внутрь","value":"4589"},{"label":"Копрофилия","value":"2568"},{"label":"Корейцы","value":"6535"},{"label":"Корея","value":"2443"},{"label":"Королевская власть","value":"55"},{"label":"Королевская семья","value":"276"},{"label":"Королевство","value":"1695"},{"label":"Короткий рассказ","value":"239"},{"label":"Коррупция","value":"831"},{"label":"Космическая опера","value":"2245"},{"label":"Космические рейнджеры","value":"5860"},{"label":"Космос","value":"329"},{"label":"Кот","value":"5171"},{"label":"Кража навыков","value":"1159"},{"label":"Красивая главная героиня","value":"186"},{"label":"Красивые женщины","value":"66"},{"label":"Красивые персонажи ","value":"5787"},{"label":"Красивый главный герой","value":"895"},{"label":"Крафт","value":"1599"},{"label":"Кровь и расчлененка","value":"7752"},{"label":"Кроссовер","value":"75"},{"label":"Ксенофилия","value":"2265"},{"label":"Ктулху","value":"920"},{"label":"Кузнец","value":"853"},{"label":"Кукла","value":"3085"},{"label":"Кулинария","value":"2230"},{"label":"Культивация","value":"1901"},{"label":"Культивирование","value":"1860"},{"label":"Кунилингус","value":"4651"},{"label":"Курама","value":"5709"},{"label":"Курилин","value":"6134"},{"label":"Кушина узумаки","value":"5222"},{"label":"Лавкрафт","value":"6063"},{"label":"Лара крофт","value":"6435"},{"label":"Легенда о зельде","value":"5707"},{"label":"Ленивый главный герой","value":"1516"},{"label":"Леон золдик","value":"7469"},{"label":"Лес","value":"6645"},{"label":"Лечебная магия","value":"562"},{"label":"Литрпг","value":"505"},{"label":"Локи","value":"1725"},{"label":"Лоли","value":"1576"},{"label":"Лопата","value":"4989"},{"label":"Лорд","value":"8404"},{"label":"Лориен","value":"7397"},{"label":"Лошади","value":"6110"},{"label":"Лунатизм","value":"3520"},{"label":"Луффи","value":"274"},{"label":"Лучник","value":"1038"},{"label":"Любовный интерес влюбляется первым","value":"7753"},{"label":"Любовный треугольник","value":"2109"},{"label":"Любовь","value":"2297"},{"label":"Любовь с первого взгляда","value":"7748"},{"label":"Любящие родители","value":"2699"},{"label":"Люди","value":"3572"},{"label":"Люцифер","value":"5883"},{"label":"Маг","value":"780"},{"label":"Магазин","value":"4762"},{"label":"Маги","value":"58"},{"label":"Магическая академия","value":"6788"},{"label":"Магические звери","value":"684"},{"label":"Магические предметы","value":"1648"},{"label":"Магический мир","value":"578"},{"label":"Магия","value":"150"},{"label":"Магия воды","value":"6450"},{"label":"Магия воздуха","value":"7627"},{"label":"Магия земли","value":"7626"},{"label":"Магия и технология","value":"107"},{"label":"Магия крови","value":"7976"},{"label":"Магия льда","value":"1379"},{"label":"Магия огня","value":"7625"},{"label":"Магия призыва","value":"885"},{"label":"Магия природы","value":"6226"},{"label":"Магия стихий","value":"7673"},{"label":"Магия тьмы","value":"3596"},{"label":"Мадара","value":"2929"},{"label":"Мазохизм","value":"1961"},{"label":"Мама","value":"12"},{"label":"Мама и дочь","value":"7894"},{"label":"Мама и сын","value":"7895"},{"label":"Манга","value":"2464"},{"label":"Манипуляция временем","value":"932"},{"label":"Маньяк","value":"2026"},{"label":"Маргери тирелл","value":"3935"},{"label":"Марионетки","value":"3175"},{"label":"Марс","value":"4013"},{"label":"Марти стью","value":"594"},{"label":"Мастер на все руки","value":"3189"},{"label":"Мастурбация","value":"3078"},{"label":"Матриархат","value":"7197"},{"label":"Мать и сестры","value":"7946"},{"label":"Мафия","value":"353"},{"label":"Мачеха","value":"4664"},{"label":"Мегаполис","value":"3322"},{"label":"Медицина","value":"441"},{"label":"Медленная романтика","value":"8383"},{"label":"Медленное развитие истории","value":"7462"},{"label":"Межрасовый секс","value":"3489"},{"label":"Мелиодас","value":"7224"},{"label":"Мелодрама","value":"1507"},{"label":"Менеджмент","value":"368"},{"label":"Мерлин","value":"3977"},{"label":"Месть","value":"1218"},{"label":"Меха","value":"2303"},{"label":"Меч","value":"48"},{"label":"Мечники","value":"969"},{"label":"Мечта","value":"527"},{"label":"Мизуки","value":"4269"},{"label":"Микото","value":"3862"},{"label":"Милая главная героиня","value":"134"},{"label":"Милф","value":"1987"},{"label":"Милый главный герой","value":"2678"},{"label":"Мимик","value":"5706"},{"label":"Мина ашидо","value":"7695"},{"label":"Минато намикадзе","value":"5223"},{"label":"Минет","value":"3289"},{"label":"Мир будущего","value":"1770"},{"label":"Мир земли","value":"8394"},{"label":"Мир меча и магии","value":"1271"},{"label":"Мистер пропер","value":"565"},{"label":"Мистика","value":"510"},{"label":"Мифические существа","value":"1878"},{"label":"Мифы и легенды","value":"4463"},{"label":"Младшая сестра","value":"1523"},{"label":"Мморпг","value":"620"},{"label":"Много главных героев","value":"1191"},{"label":"Много крови","value":"5318"},{"label":"Много персонажей","value":"1539"},{"label":"Множество королевств","value":"32"},{"label":"Модерн","value":"5457"},{"label":"Мокутон","value":"4438"},{"label":"Молоко","value":"5469"},{"label":"Монархия","value":"3590"},{"label":"Монстры","value":"307"},{"label":"Море","value":"922"},{"label":"Моритика ринносукэ","value":"1142"},{"label":"Мортал комбат","value":"5861"},{"label":"Мошенник","value":"959"},{"label":"Мрачный мир","value":"1"},{"label":"Мужчина протагонист","value":"31"},{"label":"Музыка","value":"1183"},{"label":"Мультивселенная","value":"7410"},{"label":"Мурим","value":"7342"},{"label":"Мутанты","value":"2392"},{"label":"Мутация","value":"29"},{"label":"Мясо","value":"1503"},{"label":"На реальных событиях","value":"26"},{"label":"Навыки","value":"415"},{"label":"Нагато узумаки","value":"5221"},{"label":"Наемники","value":"1668"},{"label":"Назарик","value":"5369"},{"label":"Наивный главный герой","value":"159"},{"label":"Наложница","value":"4867"},{"label":"Наноботы","value":"2332"},{"label":"Наркотики","value":"1436"},{"label":"Наруто","value":"7177"},{"label":"Наруто и хината","value":"5766"},{"label":"Насилие и жестокость","value":"1440"},{"label":"Наука","value":"2150"},{"label":"Научная фантастика","value":"2244"},{"label":"Национализм","value":"5556"},{"label":"Нацу","value":"5826"},{"label":"Нацуки субару","value":"6656"},{"label":"Начальная школа","value":"3920"},{"label":"Наши дни","value":"1814"},{"label":"Не всесильный главный герой","value":"42"},{"label":"Не по канону","value":"1611"},{"label":"Неверность","value":"5735"},{"label":"Невеста","value":"6011"},{"label":"Неджи хьюга","value":"3099"},{"label":"Недооценённый главный герой","value":"43"},{"label":"Нежить","value":"1949"},{"label":"Некромаг","value":"7441"},{"label":"Некромант","value":"1914"},{"label":"Некромантия","value":"754"},{"label":"Некрофилия","value":"1285"},{"label":"Ненормативная лексика","value":"160"},{"label":"Необычные герои","value":"67"},{"label":"Нет людей","value":"7486"},{"label":"Нетораре","value":"403"},{"label":"Нетори","value":"5393"},{"label":"Нефилимы","value":"7764"},{"label":"Нецензурная лексика","value":"4912"},{"label":"Нечисть","value":"4559"},{"label":"Ник фьюри","value":"6304"},{"label":"Ниндзюцу","value":"2354"},{"label":"Ниндзя","value":"1330"},{"label":"Новелла","value":"2105"},{"label":"Нудизм","value":"2233"},{"label":"Нэко","value":"147"},{"label":"Оби ван кеноби","value":"6830"},{"label":"Обито учиха","value":"2814"},{"label":"Оборотни","value":"53"},{"label":"Обратный гарем","value":"3303"},{"label":"Овощ","value":"6098"},{"label":"Огнестрельное оружие","value":"3155"},{"label":"Одержимость","value":"708"},{"label":"Один в поле воин","value":"6498"},{"label":"Онлайн игра","value":"1565"},{"label":"Ооками теппей","value":"849"},{"label":"Оральный секс","value":"605"},{"label":"Оргия","value":"974"},{"label":"Оригинальные персонажи","value":"7741"},{"label":"Орки","value":"1597"},{"label":"Оружие","value":"125"},{"label":"Основано на видео игре","value":"602"},{"label":"Особые способности","value":"2904"},{"label":"От бедности к богатству","value":"901"},{"label":"От слабого до сильного","value":"118"},{"label":"Отель","value":"2879"},{"label":"Отец","value":"3290"},{"label":"Отношения","value":"398"},{"label":"Отношения человек/нечеловек","value":"7406"},{"label":"Офис","value":"3460"},{"label":"Охотники","value":"2263"},{"label":"Оцуцуки","value":"5101"},{"label":"Ошейник","value":"2362"},{"label":"Ояш","value":"8048"},{"label":"Падчерица","value":"7023"},{"label":"Падший ангел","value":"184"},{"label":"Пайзури","value":"3560"},{"label":"Палач","value":"852"},{"label":"Палочка","value":"2682"},{"label":"Пандорум","value":"4360"},{"label":"Папа и дочь","value":"7647"},{"label":"Паразит","value":"1213"},{"label":"Параллельный мир","value":"2092"},{"label":"Парень","value":"619"},{"label":"Паркур","value":"3391"},{"label":"Пародия","value":"898"},{"label":"Пасхалки","value":"455"},{"label":"Паук","value":"2209"},{"label":"Пекарь","value":"5073"},{"label":"Первая книга","value":"269"},{"label":"Первая любовь","value":"640"},{"label":"Первый раз","value":"1465"},{"label":"Переводы","value":"24"},{"label":"Перевоплощение","value":"1596"},{"label":"Перевоплощение в игровом мире","value":"92"},{"label":"Перемещение в другой мир","value":"1288"},{"label":"Перемещение во времени","value":"146"},{"label":"Перерождение","value":"894"},{"label":"Перерождение в демона","value":"1111"},{"label":"Перерождение в дракона","value":"6867"},{"label":"Перерождение в злодейку","value":"5619"},{"label":"Перерождение в злодея","value":"749"},{"label":"Перерождение в ином мире","value":"109"},{"label":"Перерождение в монстра","value":"917"},{"label":"Перерождений","value":"335"},{"label":"Переселение","value":"813"},{"label":"Переселение души","value":"1864"},{"label":"Перси джексон","value":"6329"},{"label":"Пилот","value":"5521"},{"label":"Пилюли","value":"1531"},{"label":"Пират","value":"1579"},{"label":"Пирог","value":"80"},{"label":"Писатель","value":"734"},{"label":"Пистолеты","value":"4916"},{"label":"Письмо","value":"840"},{"label":"Питомцы","value":"3122"},{"label":"Планомерное развитие событий","value":"62"},{"label":"Племянник","value":"8303"},{"label":"Пленница","value":"4897"},{"label":"Плети","value":"4839"},{"label":"Пляж","value":"4243"},{"label":"Повар","value":"658"},{"label":"Повествование от первого лица","value":"1054"},{"label":"Повествование от разных лиц","value":"600"},{"label":"Повествование от третьего лица","value":"2116"},{"label":"Повседневность","value":"802"},{"label":"Подавитель","value":"497"},{"label":"Подземелье","value":"798"},{"label":"Подростки","value":"636"},{"label":"Подруга","value":"5804"},{"label":"Подчинение","value":"8343"},{"label":"Подчинение и унижение","value":"4500"},{"label":"Покемоны","value":"2840"},{"label":"Полиамория","value":"1300"},{"label":"Полигамия","value":"1336"},{"label":"Политика","value":"1313"},{"label":"Полицейский","value":"1907"},{"label":"Полубог","value":"7170"},{"label":"Полукровка","value":"999"},{"label":"Полулюди","value":"2273"},{"label":"Поношенные трусики","value":"1204"},{"label":"Попаданец","value":"2396"},{"label":"Попаданец в другой мир","value":"685"},{"label":"Попаданец в игру","value":"6466"},{"label":"Попадание в книгу","value":"6957"},{"label":"Порно","value":"4023"},{"label":"Постапокалипсис","value":"1079"},{"label":"Потеря девственности","value":"5879"},{"label":"Похищение","value":"7452"},{"label":"Поэзия","value":"1120"},{"label":"Правитель","value":"2261"},{"label":"Превращения в животных","value":"3410"},{"label":"Преданный любовный интерес","value":"2055"},{"label":"Предательство","value":"70"},{"label":"Президент","value":"4334"},{"label":"Преступник","value":"4049"},{"label":"Преступный мир","value":"965"},{"label":"Призрак","value":"511"},{"label":"Призыв","value":"332"},{"label":"Призыв в другой мир","value":"781"},{"label":"Призыв существ","value":"412"},{"label":"Приключения","value":"964"},{"label":"Приложение на телефоне","value":"5345"},{"label":"Принудительный брак","value":"2954"},{"label":"Принуждение","value":"1442"},{"label":"Принц","value":"362"},{"label":"Принцесса","value":"2403"},{"label":"Приручение мостров","value":"2338"},{"label":"Прислуга","value":"5185"},{"label":"Пришельцы","value":"273"},{"label":"Проза","value":"2012"},{"label":"Прокачка","value":"1070"},{"label":"Проклятия","value":"1347"},{"label":"Проработанный мир","value":"1711"},{"label":"Пророчество","value":"7762"},{"label":"Псайкеры","value":"3659"},{"label":"Псионика","value":"5216"},{"label":"Психбольница","value":"6969"},{"label":"Психические расстройства","value":"2445"},{"label":"Психологические травмы","value":"3418"},{"label":"Психология","value":"17"},{"label":"Публичный дом","value":"7860"},{"label":"Публичный секс","value":"6647"},{"label":"Путешествие в другой мир","value":"10"},{"label":"Пух","value":"4286"},{"label":"Пушечное мясо","value":"7234"},{"label":"Пытки","value":"1756"},{"label":"Пьяный мастер","value":"1655"},{"label":"Пэнси паркинсон","value":"7411"},{"label":"Раб","value":"977"},{"label":"Рабы","value":"1240"},{"label":"Разведение животных","value":"3990"},{"label":"Развитие","value":"208"},{"label":"Развитие персонажа","value":"1780"},{"label":"Развитие поселения","value":"7203"},{"label":"Развитие технологий","value":"1875"},{"label":"Разврат","value":"1924"},{"label":"Раздвоение личности","value":"2062"},{"label":"Разные расы","value":"1188"},{"label":"Разорванная помолвка","value":"2057"},{"label":"Разработчик","value":"2333"},{"label":"Ранги","value":"1568"},{"label":"Ранобэ","value":"1764"},{"label":"Расизм","value":"3554"},{"label":"Растение","value":"407"},{"label":"Расчленение","value":"7125"},{"label":"Реализм","value":"1180"},{"label":"Ребенок","value":"2881"},{"label":"Ревность","value":"293"},{"label":"Революция","value":"3996"},{"label":"Реинкарнация","value":"1302"},{"label":"Реинкарнация в другом мире","value":"2180"},{"label":"Реинкарнация в игровой мир","value":"304"},{"label":"Рейджер","value":"5147"},{"label":"Религия","value":"2184"},{"label":"Рем","value":"6655"},{"label":"Ремесленник","value":"1483"},{"label":"Ремонт","value":"5703"},{"label":"Репликаторы","value":"3495"},{"label":"Ресторан","value":"7519"},{"label":"Риас гремори","value":"6950"},{"label":"Рим","value":"612"},{"label":"Риннеган","value":"4230"},{"label":"Ритуал","value":"1233"},{"label":"Робот","value":"4011"},{"label":"Роботы","value":"467"},{"label":"Рок ли","value":"7002"},{"label":"Рокси","value":"7380"},{"label":"Романтика","value":"1025"},{"label":"Рон уизли","value":"4279"},{"label":"Ророноа зоро","value":"1590"},{"label":"Росомаха","value":"1035"},{"label":"Российская империя","value":"2146"},{"label":"Россия","value":"4246"},{"label":"Рудеус","value":"7379"},{"label":"Руны","value":"2393"},{"label":"Русалки","value":"3127"},{"label":"Русь","value":"2016"},{"label":"Рыцари","value":"1902"},{"label":"Рэо хатаке","value":"2823"},{"label":"Сабли","value":"6136"},{"label":"Сайян","value":"6802"},{"label":"Сакура","value":"2515"},{"label":"Самец","value":"1592"},{"label":"Самоубийство","value":"4312"},{"label":"Самураи","value":"576"},{"label":"Санса старк","value":"3925"},{"label":"Сарказм","value":"4658"},{"label":"Саске","value":"7237"},{"label":"Сатира","value":"215"},{"label":"Сборник","value":"7367"},{"label":"Свадьба","value":"1524"},{"label":"Сверхсила","value":"1055"},{"label":"Сверхъестественное","value":"1074"},{"label":"Свингеры","value":"514"},{"label":"Свинцовые книги","value":"5322"},{"label":"Связывание","value":"4506"},{"label":"Священик","value":"61"},{"label":"Северус снейп","value":"5783"},{"label":"Сёдзе","value":"2219"},{"label":"Секс","value":"2472"},{"label":"Секс без проникновения","value":"151"},{"label":"Секс игрушки","value":"1124"},{"label":"Секс по принуждению","value":"8335"},{"label":"Секс по согласию","value":"7307"},{"label":"Секс рабыня","value":"7174"},{"label":"Секс с близнецами","value":"7457"},{"label":"Секс с монстрами","value":"6073"},{"label":"Секс с учителем","value":"2570"},{"label":"Секса будет много","value":"7265"},{"label":"Сексуальные и психологические отклонения","value":"888"},{"label":"Секты","value":"1234"},{"label":"Селфцест","value":"7498"},{"label":"Сельское хозяйство","value":"787"},{"label":"Семейный конфликт","value":"935"},{"label":"Семья","value":"728"},{"label":"Сенджу","value":"6069"},{"label":"Сёнэн","value":"2058"},{"label":"Серийный убийца","value":"261"},{"label":"Сестра","value":"2342"},{"label":"Сила глаза","value":"1019"},{"label":"Сильная героиня","value":"7292"},{"label":"Сильная главная героиня","value":"1994"},{"label":"Сильный главный герой","value":"570"},{"label":"Сильфиетта","value":"7381"},{"label":"Симбиот","value":"2648"},{"label":"Сириус блэк","value":"7655"},{"label":"Сирота","value":"297"},{"label":"Система","value":"251"},{"label":"Система уровней","value":"2174"},{"label":"Сиськи","value":"16"},{"label":"Ситх","value":"1618"},{"label":"Сказка","value":"3570"},{"label":"Скайрим","value":"4032"},{"label":"Скайуокер","value":"6828"},{"label":"Сквиб","value":"4101"},{"label":"Сквирт","value":"4526"},{"label":"Скелет","value":"1315"},{"label":"Скрытые способности","value":"3417"},{"label":"Скульптор","value":"1178"},{"label":"Слабая главная героиня","value":"5805"},{"label":"Слабый главный герой","value":"731"},{"label":"Славяне","value":"2168"},{"label":"Слизерин","value":"6979"},{"label":"Слизь","value":"1095"},{"label":"Сломанная игрушка","value":"8396"},{"label":"Служанка","value":"3486"},{"label":"Смартфон","value":"457"},{"label":"Смена сущности","value":"8391"},{"label":"Смерть","value":"1779"},{"label":"Смерть основного персонажа","value":"1530"},{"label":"Собаки","value":"6109"},{"label":"Соблазн","value":"7994"},{"label":"Соблазнение","value":"2290"},{"label":"Современность","value":"568"},{"label":"Современные знания","value":"988"},{"label":"Сожительство","value":"3026"},{"label":"Создание армии","value":"1992"},{"label":"Создание королевства","value":"1497"},{"label":"Создание пилюль","value":"816"},{"label":"Создатель","value":"1045"},{"label":"Сокровища","value":"6521"},{"label":"Сокрытие истинной личности","value":"2894"},{"label":"Сома и эрина","value":"5737"},{"label":"Соперничество","value":"7699"},{"label":"Соседи","value":"4075"},{"label":"Сотрудник","value":"6339"},{"label":"Социология","value":"7618"},{"label":"Спокойная главная героиня","value":"236"},{"label":"Спокойный главный герой","value":"1656"},{"label":"Спор","value":"3484"},{"label":"Спорт","value":"1387"},{"label":"Спортивное тело","value":"8317"},{"label":"Способность кражи","value":"7784"},{"label":"Сражения","value":"23"},{"label":"Средневековье","value":"1022"},{"label":"Сталкер","value":"1932"},{"label":"Становление государства","value":"111"},{"label":"Старшая школа","value":"1658"},{"label":"Стёб","value":"454"},{"label":"Стекло","value":"7453"},{"label":"Стеснительный персонаж","value":"152"},{"label":"Стимпанк","value":"1894"},{"label":"Стихи","value":"1309"},{"label":"Стратегия","value":"569"},{"label":"Стриптиз","value":"6914"},{"label":"Строительство","value":"921"},{"label":"Студент","value":"1464"},{"label":"Студентка","value":"7518"},{"label":"Суккубы","value":"2662"},{"label":"Супер зрение","value":"6574"},{"label":"Супер удача","value":"489"},{"label":"Супергерои","value":"1481"},{"label":"Суперзлодеи","value":"2125"},{"label":"Супермен","value":"6997"},{"label":"Суперсилы","value":"2276"},{"label":"Существа","value":"6033"},{"label":"Сферы","value":"1077"},{"label":"Схемы и заговоры","value":"7116"},{"label":"Сценарий","value":"7489"},{"label":"Счастливый конец","value":"3293"},{"label":"Сын","value":"3297"},{"label":"Сэйнэн","value":"345"},{"label":"Сэм винчестер","value":"7461"},{"label":"Сюаньхуа","value":"2560"},{"label":"Сюжетные повороты","value":"7202"},{"label":"Сянься","value":"1126"},{"label":"Таблетки для развития","value":"589"},{"label":"Таинственное прошлое","value":"769"},{"label":"Тайная личность","value":"8096"},{"label":"Тайная любовь","value":"113"},{"label":"Тайны","value":"7481"},{"label":"Тайные отношения","value":"7450"},{"label":"Тайцы","value":"6413"},{"label":"Танки","value":"1787"},{"label":"Твинцест","value":"2450"},{"label":"Творчество","value":"20"},{"label":"Тейлор эберт","value":"3526"},{"label":"Телекинез","value":"2203"},{"label":"Телепортация","value":"914"},{"label":"Телохранитель","value":"723"},{"label":"Темари","value":"4810"},{"label":"Темная магия","value":"4788"},{"label":"Темное фэнтези","value":"1675"},{"label":"Темные эльфы","value":"7198"},{"label":"Тенсейган","value":"4367"},{"label":"Тентакли","value":"3236"},{"label":"Тетя","value":"4194"},{"label":"Техномагия","value":"508"},{"label":"Тиран","value":"1174"},{"label":"Титаны","value":"3308"},{"label":"Тодороки шото","value":"5105"},{"label":"Токио","value":"5791"},{"label":"Том реддл","value":"6652"},{"label":"Тони старк","value":"1338"},{"label":"Тор","value":"1800"},{"label":"Тор и локи","value":"2645"},{"label":"Торговля","value":"1637"},{"label":"Травничество","value":"221"},{"label":"Трагедия","value":"470"},{"label":"Трагическое прошлое","value":"644"},{"label":"Трактир","value":"1918"},{"label":"Трансмиграция","value":"1071"},{"label":"Трансформация","value":"156"},{"label":"Трансформеры","value":"4616"},{"label":"Тревор филипс","value":"5427"},{"label":"Триллер","value":"1676"},{"label":"Тройняшки","value":"7169"},{"label":"Троли","value":"4250"},{"label":"Трудолюбивый главный герой","value":"248"},{"label":"Тупой главный герой","value":"3710"},{"label":"Тьма","value":"1919"},{"label":"Тюрьма","value":"5597"},{"label":"Тяжёлое детство","value":"2253"},{"label":"Убийства","value":"378"},{"label":"Убийцы","value":"1766"},{"label":"Убийцы драконов","value":"1484"},{"label":"Увечья","value":"3266"},{"label":"Удача","value":"6575"},{"label":"Ужасы","value":"1747"},{"label":"Узумаки","value":"7579"},{"label":"Укротитель","value":"367"},{"label":"Умения","value":"531"},{"label":"Умная главная героиня","value":"1381"},{"label":"Умные персонажи","value":"2756"},{"label":"Умный главный герой","value":"2093"},{"label":"Университет","value":"2102"},{"label":"Унижение","value":"8342"},{"label":"Уничтожение мира","value":"81"},{"label":"Управление временем","value":"5648"},{"label":"Уровни","value":"8"},{"label":"Усиление","value":"3549"},{"label":"Усопп","value":"6737"},{"label":"Устроенный брак","value":"2223"},{"label":"Усыновление","value":"395"},{"label":"Уся","value":"1172"},{"label":"Утопия","value":"4677"},{"label":"Уход за детьми","value":"7609"},{"label":"Учеба в университете","value":"2797"},{"label":"Учебное заведение","value":"1817"},{"label":"Ученые","value":"1843"},{"label":"Учитель","value":"845"},{"label":"Учиха","value":"5348"},{"label":"Фамильяры","value":"1471"},{"label":"Фанатичная любовь","value":"6721"},{"label":"Фанаты","value":"7739"},{"label":"Фантастика","value":"286"},{"label":"Фантастический мир","value":"2414"},{"label":"Фанфик","value":"767"},{"label":"Фармацевт","value":"1706"},{"label":"Фашисты","value":"3118"},{"label":"Феи","value":"7185"},{"label":"Фелляция","value":"5880"},{"label":"Феминизм","value":"6527"},{"label":"Феникс","value":"2413"},{"label":"Фенрир","value":"2740"},{"label":"Феодализм","value":"280"},{"label":"Ферма","value":"285"},{"label":"Фетиш","value":"2398"},{"label":"Фехтовальщик","value":"4685"},{"label":"Филиппины","value":"128"},{"label":"Философия","value":"1859"},{"label":"Фистинг","value":"4206"},{"label":"Флафф","value":"5048"},{"label":"Флер делакур","value":"5191"},{"label":"Флэш","value":"4667"},{"label":"Фокусник","value":"7602"},{"label":"Фольклор","value":"4086"},{"label":"Фугаку","value":"6161"},{"label":"Фурри","value":"6631"},{"label":"Футбол","value":"1121"},{"label":"Фэнтези","value":"174"},{"label":"Фэнтезийный мир","value":"1556"},{"label":"Хакер","value":"475"},{"label":"Халк","value":"1196"},{"label":"Хан нуньен синг","value":"4817"},{"label":"Ханма юджиро","value":"4369"},{"label":"Хаос","value":"3117"},{"label":"Харизматичный главный герой","value":"2350"},{"label":"Хатаке какаши","value":"2820"},{"label":"Хентай","value":"1682"},{"label":"Хиддлстон","value":"5903"},{"label":"Химавари","value":"2583"},{"label":"Хината","value":"2513"},{"label":"Хитрый главный герой","value":"2036"},{"label":"Хоббит","value":"6698"},{"label":"Хогвартс","value":"5954"},{"label":"Холодная главная героиня","value":"6507"},{"label":"Холодное оружие","value":"4889"},{"label":"Холодный главный герой","value":"2686"},{"label":"Храм","value":"2135"},{"label":"Христианство","value":"877"},{"label":"Художественная литература","value":"2380"},{"label":"Художница","value":"518"},{"label":"Хулиганы","value":"854"},{"label":"Цветок","value":"902"},{"label":"Цезарь","value":"1705"},{"label":"Целители","value":"4523"},{"label":"Церковь","value":"1980"},{"label":"Цундере","value":"1078"},{"label":"Чакра","value":"1393"},{"label":"Черный юмор","value":"2868"},{"label":"Честная главная героиня","value":"5855"},{"label":"Честный главный герой","value":"129"},{"label":"Читы","value":"451"},{"label":"Шантаж","value":"7175"},{"label":"Шаринган","value":"5165"},{"label":"Шинигами","value":"4661"},{"label":"Шиноби","value":"1227"},{"label":"Школьная жизнь","value":"284"},{"label":"Школьники","value":"218"},{"label":"Школьницы","value":"5946"},{"label":"Шлюха","value":"5822"},{"label":"Шоу-бизнес","value":"7119"},{"label":"Шпионы","value":"4262"},{"label":"Эволюция","value":"2040"},{"label":"Экзорцизм","value":"676"},{"label":"Экономика","value":"121"},{"label":"Эксгибиционизм","value":"1643"},{"label":"Экспансия","value":"110"},{"label":"Эксперименты над людьми","value":"7408"},{"label":"Экшен","value":"2859"},{"label":"Элементальная магия","value":"1264"},{"label":"Элементы бдсм","value":"1323"},{"label":"Эльфы","value":"1362"},{"label":"Эротика","value":"2053"},{"label":"Этти","value":"1317"},{"label":"Юмор","value":"7360"},{"label":"Яды","value":"201"},{"label":"Якудза","value":"4599"},{"label":"Яндере","value":"7111"},{"label":"Bl","value":"8282"},{"label":"Gl","value":"8284"},{"label":"Rpg","value":"90"},{"label":"Star wars","value":"1985"},{"label":"Time skip","value":"824"},{"label":"Warrior of light","value":"5289"}],"inputType":FilterInputs.Checkbox}]; + + export const filters = [ + { + key: 'sort', + label: 'Сортировка', + values: [ + { label: 'По рейтингу', value: '6' }, + { label: 'По степени готовности', value: '0' }, + { label: 'По названию на языке оригинала', value: '1' }, + { label: 'По названию на языке перевода', value: '2' }, + { label: 'По дате создания', value: '3' }, + { label: 'По дате последней активности', value: '4' }, + { label: 'По просмотрам', value: '5' }, + { label: 'По кол-ву переведённых глав', value: '7' }, + { label: 'По кол-ву лайков', value: '8' }, + { label: 'По кол-ву страниц', value: '10' }, + { label: 'По кол-ву бесплатных глав', value: '11' }, + { label: 'По кол-ву рецензий', value: '12' }, + { label: 'По кол-ву в закладках', value: '13' }, + { label: 'По кол-ву в избранном', value: '14' }, + ], + inputType: FilterInputs.Picker, + }, + { + key: 'type', + label: 'Тип', + values: [ + { label: 'Неважно', value: '0' }, + { label: 'Только переводы', value: '1' }, + { label: 'Только авторские', value: '2' }, + ], + inputType: FilterInputs.Picker, + }, + { + key: 'atmosphere', + label: 'Атмосфера', + values: [ + { label: 'Неважно', value: '0' }, + { label: 'Позитивная', value: '1' }, + { label: 'Dark', value: '2' }, + ], + inputType: FilterInputs.Picker, + }, + { + key: 'trash', + label: 'Другое', + values: [ + { label: 'Готовые на 100%', value: 'ready' }, + { label: 'Доступные для перевода', value: 'tr' }, + { label: 'Доступные для скачивания', value: 'gen' }, + { label: 'Завершённые проекты', value: 'wealth' }, + { label: 'Распродажа', value: 'discount' }, + { label: 'Только онгоинги', value: 'ongoings' }, + { label: 'Убрать машинный', value: 'remove_machinelate' }, + ], + inputType: FilterInputs.Checkbox, + }, + ...customFilters, + { + key: 'adult', + label: 'Возрастные ограничения', + values: [ + { label: 'Все', value: '0' }, + { label: 'Убрать 18+', value: '1' }, + { label: 'Только 18+', value: '2' }, + ], + inputType: FilterInputs.Picker, + }, + ]; + + export const popularNovels: Plugin.popularNovels = async function ( + page, + { filters, showLatestNovels } + ) { + let url = baseUrl + '/search?t=&cat=2'; + url += '&sort=' + showLatestNovels ? '4' : filters?.sort || '6'; + url += '&type=' + filters?.type || '0'; + url += '&atmosphere=' + filters?.atmosphere || '0'; + url += '&adult=' + filters?.adult || '0'; + + if (filters?.genres instanceof Array) { + url += filters.genres.map(i => '&genres[]='+i).join(''); + } + + if (filters?.tags instanceof Array) { + url += filters.tags.map(i => '&tags[]='+i).join(''); + } + + if (filters?.trash instanceof Array) { + url += filters.trash.map(i => '&' + i + '=1'); + } + + url += '&Book_page=' + page; + + const result = await fetchApi(url); + const body = await result.text(); + const loadedCheerio = parseHTML(body); + + const novels: Novel.Item[] = []; + + loadedCheerio( + 'ul[class="search-results"] > li:not([class="ad_type_catalog"])', + ).each(function () { + novels.push({ + name: loadedCheerio(this).find('p > a').text(), + cover: baseUrl + loadedCheerio(this).find('img').attr('src'), + url: baseUrl + loadedCheerio(this).find('p > a').attr('href') || '', + }); + }); + + return novels; + }; + + + export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const novel: Novel.instance = { + url: novelUrl, + chapters: [], + }; + let result = await fetchApi(novelUrl); + if (result.url.includes('mature?path=')) { + const formData = new FormData(); + formData.append('path', novelUrl); + formData.append('ok', 'Да'); + + await fetch(result.url, { + method: 'POST', + body: formData, + }); + + result = await fetchApi(novelUrl); + } + const body = await result.text(); + const loadedCheerio = parseHTML(body); + + novel.name = loadedCheerio('div[class="container"] > div > div > h1') + .text() + .trim(); + novel.cover = + baseUrl + loadedCheerio('div[class="images"] > div img').attr('src'); + novel.summary = loadedCheerio('#Info > div:nth-child(3)').text(); + let genres: string[] = []; + + loadedCheerio('div[class="span5"] > p').each(function () { + switch (loadedCheerio(this).find('strong').text()) { + case 'Автор:': + novel.author = loadedCheerio(this).find('em > a').text().trim(); + break; + case 'Выпуск:': + novel.status = + loadedCheerio(this).find('em').text().trim() === 'продолжается' + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + case 'Тэги:': + loadedCheerio(this) + .find('em > a') + .each(function () { + genres.push(loadedCheerio(this).text()); + }); + break; + case 'Жанры:': + loadedCheerio(this) + .find('em > a') + .each(function () { + genres.push(loadedCheerio(this).text()); + }); + break; + } + }); + + if (genres.length > 0) { + novel.genres = genres.reverse().join(','); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('table > tbody > tr.chapter_row').each(function () { + const chapterName = loadedCheerio(this) + .find('td[class="t"] > a') + .text() + .trim(); + const releaseDate = loadedCheerio(this) + .find('td > span') + .attr('title') + ?.trim(); + const chapterUrl = baseUrl + loadedCheerio(this) + .find('td[class="t"] > a') + .attr('href'); + + if ( + loadedCheerio(this).find('td > span[class="disabled"]').length < 1 && + releaseDate + ) { + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + } + }); + + novel.chapters = chapters; + return novel; + }; + + export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + let result = await fetchApi(chapterUrl); + if (result.url.includes('mature?path=')) { + const formData = new FormData(); + formData.append('ok', 'Да'); + + await fetch(result.url, { + method: 'POST', + body: formData, + }); + + result = await fetchApi(chapterUrl); + } + const body = await result.text(); + const loadedCheerio = parseHTML(body); + + + const chapterText = loadedCheerio('.content-text').html(); + + + return chapterText; + }; + + export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const result = await fetchApi( + baseUrl + '/search/autocomplete?query=' + searchTerm, + ); + let json: any = await result.json(); + + json.forEach((item: any) => { + const novelName = item.title_one + ' / ' + item.title_two; + const novelCover = baseUrl + item.img; + const novelUrl = baseUrl + item.url; + + novels.push({ name: novelName, cover: novelCover, url: novelUrl }); + }); + + return novels; + }; + + export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url, {}); + }; + + \ No newline at end of file diff --git a/plugins/russian/authortoday.broken.ts b/plugins/russian/authortoday.broken.ts new file mode 100644 index 0000000..bccac40 --- /dev/null +++ b/plugins/russian/authortoday.broken.ts @@ -0,0 +1,341 @@ +import cheerio from "cheerio"; +import dayjs from "dayjs"; +import { fetchApi } from "@libs/fetch"; +import { NovelStatus } from "@libs/novelStatus"; +import { FilterInputs } from "@libs/filterInputs"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; + +export const id = "AT"; +export const name = "Автор Тудей"; +export const site = "https://author.today"; +export const version = "1.0.0"; +export const icon = "src/ru/authortoday/icon.png"; + +const baseUrl = site; +const apiUrl = "https://api.author.today/"; + +const token = "Bearer guest"; +export const popularNovels: Plugin.popularNovels = async function ( + page, + { showLatestNovels, filters } +) { + let url = apiUrl + "v1/catalog/search?page=" + page; + if (filters?.genre) { + url += "&genre=" + filters.genre; + } + + url += + "&sorting=" + + (showLatestNovels ? "recent" : filters?.sort || "popular"); + + url += "&form=" + (filters?.form || "any"); + url += "&state=" + (filters?.state || "any"); + url += "&series=" + (filters?.series || "any"); + url += "&access=" + (filters?.access || "any"); + url += "&promo=" + (filters?.promo || "hide"); + + const result = await fetchApi(url, { + headers: { + Authorization: token, + }, + }); + const json = await result.json(); + + if (json?.code === "NotFound") { + return []; + } + + let novels: Novel.Item[] = []; + json.searchResults.forEach((novel) => + novels.push({ + name: novel.title, + cover: novel?.coverUrl + ? "https://cm.author.today/content/" + novel.coverUrl + : defaultCover, + url: baseUrl + "/work/" + novel.id, + }) + ); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const workID = novelUrl.split("/")[4]; + let result = await fetchApi(`${apiUrl}v1/work/${workID}/details`, { + headers: { + Authorization: token, + }, + }); + + let json = await result.json(); + let novel: Novel.instance = { + url: novelUrl, + name: json.title, + cover: json?.coverUrl ? json.coverUrl.split("?")[0] : defaultCover, + author: + json?.originalAuthor || + json?.authorFIO || + json?.coAuthorFIO || + json?.secondCoAuthorFIO || + json?.translator || + "", + genres: json?.tags?.join(", "), + status: json?.isFinished + ? NovelStatus.Completed + : NovelStatus.Ongoing, + }; + + novel.summary = ""; + novel.summary += json?.annotation ? json.annotation + "\n" : ""; + novel.summary += json?.authorNotes + ? "Примечания автора:\n" + json.authorNotes + : ""; + // all chapters + result = await fetchApi(`${apiUrl}v1/work/${workID}/content`, { + headers: { + Authorization: token, + }, + }); + json = await result.json(); + + let chapters: Chapter.Item[] = []; + json?.forEach((chapter, index) => { + if (chapter?.isAvailable && !chapter?.isDraft) { + chapters.push({ + name: chapter?.title || `Глава ${index + 1}`, + releaseTime: dayjs( + chapter?.publishTime || chapter.lastModificationTime + ).format("LLL"), + url: `${apiUrl}v1/work/${workID}/chapter/${chapter.id}/text`, + }); + } + }); + + novel.chapters = chapters; + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi(chapterUrl, { + headers: { + Authorization: token, + }, + }); + const json = await result.json(); + + if (json?.code) { + return json.code + "\n" + json?.message; + } + + let key = json.key.split("").reverse().join("") + "@_@"; + let text = ""; + + for (let i = 0; i < json.text.length; i++) { + text += String.fromCharCode( + json.text.charCodeAt(i) ^ key.charCodeAt(Math.floor(i % key.length)) + ); + } + + const loadedCheerio = cheerio.load(text); + loadedCheerio("img").each(function () { + if (!loadedCheerio(this).attr("src")?.startsWith("http")) { + const src = loadedCheerio(this).attr("src"); + loadedCheerio(this).attr("src", baseUrl + src); + } + }); + const chapterText = loadedCheerio.html(); + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = `${baseUrl}/search?category=works&q=${searchTerm}`; + const result = await fetchApi(url, { + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0", + referer: baseUrl, + }); + const body = await result.text(); + const loadedCheerio = cheerio.load(body); + let novels: Novel.Item[] = []; + + loadedCheerio("div.book-row").each(function () { + const name = loadedCheerio(this) + .find('div[class="book-title"] a') + .text() + .trim(); + let cover = loadedCheerio(this).find("img").attr("src"); + const url = + baseUrl + + "/work/" + + loadedCheerio(this) + .find('div[class="book-title"] a') + .attr("href") + ?.split("/")[2]; + + if (cover) { + cover = cover.split("?")[0]; + } else { + cover = defaultCover; + } + + novels.push({ name, cover, url }); + }); + + return novels; +}; + +export const filters = [ + { + key: "sort", + label: "Сортировка", + values: [ + { label: "По популярности", value: "popular" }, + { label: "По количеству лайков", value: "likes" }, + { label: "По комментариям", value: "comments" }, + { label: "По новизне", value: "recent" }, + { label: "По просмотрам", value: "views" }, + { label: "Набирающие популярность", value: "trending" }, + ], + inputType: FilterInputs.Picker, + }, + { + key: "genre", + label: "Жанры", + values: [ + { label: "Альтернативная история", value: "sf-history" }, + { label: "Антиутопия", value: "dystopia" }, + { label: "Бизнес-литература", value: "biznes-literatura" }, + { label: "Боевая фантастика", value: "sf-action" }, + { label: "Боевик", value: "action" }, + { label: "Боевое фэнтези", value: "fantasy-action" }, + { label: "Бояръ-Аниме", value: "boyar-anime" }, + { label: "Героическая фантастика", value: "sf-heroic" }, + { label: "Героическое фэнтези", value: "heroic-fantasy" }, + { label: "Городское фэнтези", value: "urban-fantasy" }, + { label: "Детектив", value: "detective" }, + { label: "Детская литература", value: "detskaya-literatura" }, + { label: "Документальная проза", value: "non-fiction" }, + { label: "Историческая проза", value: "historical-fiction" }, + { + label: "Исторические приключения", + value: "historical-adventure", + }, + { label: "Исторический детектив", value: "historical-mystery" }, + { + label: "Исторический любовный роман", + value: "historical-romance", + }, + { label: "Историческое фэнтези", value: "historical-fantasy" }, + { label: "Киберпанк", value: "cyberpunk" }, + { label: "Короткий любовный роман", value: "short-romance" }, + { label: "Космическая фантастика", value: "sf-space" }, + { label: "ЛитРПГ", value: "litrpg" }, + { label: "Любовное фэнтези", value: "love-fantasy" }, + { label: "Любовные романы", value: "romance" }, + { label: "Мистика", value: "paranormal" }, + { label: "Назад в СССР", value: "back-to-ussr" }, + { label: "Научная фантастика", value: "science-fiction" }, + { label: "Подростковая проза", value: "teen-prose" }, + { label: "Политический роман", value: "political-fiction" }, + { label: "Попаданцы", value: "popadantsy" }, + { label: "Попаданцы в космос", value: "popadantsy-v-kosmos" }, + { + label: "Попаданцы в магические миры", + value: "popadantsy-v-magicheskie-miry", + }, + { label: "Попаданцы во времени", value: "popadantsy-vo-vremeni" }, + { label: "Постапокалипсис", value: "postapocalyptic" }, + { label: "Поэзия", value: "poetry" }, + { label: "Приключения", value: "adventure" }, + { label: "Публицистика", value: "publicism" }, + { label: "Развитие личности", value: "razvitie-lichnosti" }, + { label: "Разное", value: "other" }, + { label: "РеалРПГ", value: "realrpg" }, + { label: "Романтическая эротика", value: "romantic-erotika" }, + { label: "Сказка", value: "fairy-tale" }, + { label: "Современная проза", value: "modern-prose" }, + { + label: "Современный любовный роман", + value: "contemporary-romance", + }, + { label: "Социальная фантастика", value: "sf-social" }, + { label: "Стимпанк", value: "steampunk" }, + { label: "Темное фэнтези", value: "dark-fantasy" }, + { label: "Триллер", value: "thriller" }, + { label: "Ужасы", value: "horror" }, + { label: "Фантастика", value: "sci-fi" }, + { + label: "Фантастический детектив", + value: "detective-science-fiction", + }, + { label: "Фанфик", value: "fanfiction" }, + { label: "Фэнтези", value: "fantasy" }, + { label: "Шпионский детектив", value: "spy-mystery" }, + { label: "Эпическое фэнтези", value: "epic-fantasy" }, + { label: "Эротика", value: "erotica" }, + { label: "Эротическая фантастика", value: "sf-erotika" }, + { label: "Эротический фанфик", value: "fanfiction-erotika" }, + { label: "Эротическое фэнтези", value: "fantasy-erotika" }, + { label: "Юмор", value: "humor" }, + { label: "Юмористическая фантастика", value: "sf-humor" }, + { label: "Юмористическое фэнтези", value: "ironical-fantasy" }, + ], + inputType: FilterInputs.Picker, + }, + { + key: "form", + label: "Форма произведения", + values: [ + { label: "Любой", value: "any" }, + { label: "Перевод", value: "translation" }, + { label: "Повесть", value: "tale" }, + { label: "Рассказ", value: "story" }, + { label: "Роман", value: "novel" }, + { label: "Сборник поэзии", value: "poetry" }, + { label: "Сборник рассказов", value: "story-book" }, + ], + inputType: FilterInputs.Picker, + }, + { + key: "state", + label: "Статус произведения", + values: [ + { label: "Любой статус", value: "any" }, + { label: "В процессе", value: "in-progress" }, + { label: "Завершено", value: "finished" }, + ], + inputType: FilterInputs.Picker, + }, + { + key: "series", + label: "Статус цикла", + values: [ + { label: "Не важно", value: "any" }, + { label: "Вне цикла", value: "out" }, + { label: "Цикл завершен", value: "finished" }, + { label: "Цикл не завершен", value: "unfinished" }, + ], + inputType: FilterInputs.Picker, + }, + { + key: "access", + label: "Тип доступа", + values: [ + { label: "Любой", value: "any" }, + { label: "Платный", value: "paid" }, + { label: "Бесплатный", value: "free" }, + ], + inputType: FilterInputs.Picker, + }, + { + key: "promo", + label: "Промо-фрагмент", + values: [ + { label: "Скрывать", value: "hide" }, + { label: "Показывать", value: "show" }, + ], + inputType: FilterInputs.Picker, + }, +]; diff --git a/plugins/russian/authortoday.js b/plugins/russian/authortoday.js deleted file mode 100644 index f1bcbb5..0000000 --- a/plugins/russian/authortoday.js +++ /dev/null @@ -1,336 +0,0 @@ -const cheerio = require("cheerio"); -const dayjs = require("dayjs"); -const fetchApi = require("@libs/fetchApi"); -const fetchFile = require("@libs/fetchFile"); -const languages = require("@libs/languages"); -const Status = require("@libs/novelStatus"); -const FilterInputs = require("@libs/filterInputs"); -const defaultCoverUri = - "https://github.com/LNReader/lnreader-sources/blob/main/icons/src/coverNotAvailable.jpg?raw=true"; - -const pluginId = "AT"; -const sourceName = "Автор Тудей"; - -const baseUrl = "https://author.today"; -const apiUrl = "https://api.author.today/"; - -const token = "Bearer guest"; -async function popularNovels(page, { showLatestNovels, filters }) { - let url = apiUrl + "v1/catalog/search?page=" + page; - if (filters?.genre) { - url += "&genre=" + filters.genre; - } - - url += - "&sorting=" + (showLatestNovels ? "recent" : filters?.sort || "popular"); - - url += "&form=" + (filters?.form || "any"); - url += "&state=" + (filters?.state || "any"); - url += "&series=" + (filters?.series || "any"); - url += "&access=" + (filters?.access || "any"); - url += "&promo=" + (filters?.promo || "hide"); - - const result = await fetchApi(url, { - headers: { - Authorization: token, - }, - }); - const json = await result.json(); - - if (json?.code === "NotFound") { - return []; - } - - let novels = []; - json.searchResults.forEach((novel) => - novels.push({ - name: novel.title, - cover: novel?.coverUrl - ? "https://cm.author.today/content/" + novel.coverUrl - : defaultCoverUri, - url: baseUrl + "/work/" + novel.id, - }) - ); - - return novels; -} - -async function parseNovelAndChapters(novelUrl) { - const workID = novelUrl.split("/")[4]; - let result = await fetchApi(`${apiUrl}v1/work/${workID}/details`, { - headers: { - Authorization: token, - }, - }); - - let json = await result.json(); - let novel = { - url: novelUrl, - name: json.title, - cover: json?.coverUrl ? json.coverUrl.split("?")[0] : defaultCoverUri, - author: - json?.originalAuthor || - json?.authorFIO || - json?.coAuthorFIO || - json?.secondCoAuthorFIO || - json?.translator || - "", - genres: json?.tags?.join(", "), - status: json?.isFinished ? Status.Completed : Status.Ongoing, - }; - - novel.summary = ""; - novel.summary += json?.annotation ? json.annotation + "\n" : ""; - novel.summary += json?.authorNotes - ? "Примечания автора:\n" + json.authorNotes - : ""; - // all chapters - result = await fetchApi(`${apiUrl}v1/work/${workID}/content`, { - headers: { - Authorization: token, - }, - }); - json = await result.json(); - - let chapters = []; - json?.forEach((chapter, index) => { - if (chapter?.isAvailable && !chapter?.isDraft) { - chapters.push({ - name: chapter?.title || `Глава ${index + 1}`, - releaseTime: dayjs( - chapter?.publishTime || chapter.lastModificationTime - ).format("LLL"), - url: `${apiUrl}v1/work/${workID}/chapter/${chapter.id}/text`, - }); - } - }); - - novel.chapters = chapters; - return novel; -} - -async function parseChapter(chapterUrl) { - const result = await fetchApi(chapterUrl, { - headers: { - Authorization: token, - }, - }); - const json = await result.json(); - - if (json?.code) { - return json.code + "\n" + json?.message; - } - - let key = json.key.split("").reverse().join("") + "@_@"; - let text = ""; - - for (let i = 0; i < json.text.length; i++) { - text += String.fromCharCode( - json.text.charCodeAt(i) ^ key.charCodeAt(Math.floor(i % key.length)) - ); - } - - const loadedCheerio = cheerio.load(text); - loadedCheerio("img").each(function () { - if (!loadedCheerio(this).attr("src")?.startsWith("http")) { - const src = loadedCheerio(this).attr("src"); - loadedCheerio(this).attr("src", baseUrl + src); - } - }); - const chapterText = loadedCheerio.html(); - return chapterText; -} - -async function searchNovels(searchTerm) { - const url = `${baseUrl}/search?category=works&q=${searchTerm}`; - const result = await fetchApi(url, { - "User-Agent": - "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0", - referer: baseUrl, - }); - const body = await result.text(); - const loadedCheerio = cheerio.load(body); - let novels = []; - - loadedCheerio("div.book-row").each(function () { - const name = loadedCheerio(this) - .find('div[class="book-title"] a') - .text() - .trim(); - let cover = loadedCheerio(this).find("img").attr("src"); - const url = - baseUrl + - "/work/" + - loadedCheerio(this) - .find('div[class="book-title"] a') - .attr("href") - .split("/")[2]; - - if (cover) { - cover = cover.split("?")[0]; - } else { - cover = defaultCoverUri; - } - - novels.push({ name, cover, url }); - }); - - return novels; -} - -const filters = [ - { - key: "sort", - label: "Сортировка", - values: [ - { label: "По популярности", value: "popular" }, - { label: "По количеству лайков", value: "likes" }, - { label: "По комментариям", value: "comments" }, - { label: "По новизне", value: "recent" }, - { label: "По просмотрам", value: "views" }, - { label: "Набирающие популярность", value: "trending" }, - ], - inputType: FilterInputs.Picker, - }, - { - key: "genre", - label: "Жанры", - values: [ - { label: "Альтернативная история", value: "sf-history" }, - { label: "Антиутопия", value: "dystopia" }, - { label: "Бизнес-литература", value: "biznes-literatura" }, - { label: "Боевая фантастика", value: "sf-action" }, - { label: "Боевик", value: "action" }, - { label: "Боевое фэнтези", value: "fantasy-action" }, - { label: "Бояръ-Аниме", value: "boyar-anime" }, - { label: "Героическая фантастика", value: "sf-heroic" }, - { label: "Героическое фэнтези", value: "heroic-fantasy" }, - { label: "Городское фэнтези", value: "urban-fantasy" }, - { label: "Детектив", value: "detective" }, - { label: "Детская литература", value: "detskaya-literatura" }, - { label: "Документальная проза", value: "non-fiction" }, - { label: "Историческая проза", value: "historical-fiction" }, - { label: "Исторические приключения", value: "historical-adventure" }, - { label: "Исторический детектив", value: "historical-mystery" }, - { label: "Исторический любовный роман", value: "historical-romance" }, - { label: "Историческое фэнтези", value: "historical-fantasy" }, - { label: "Киберпанк", value: "cyberpunk" }, - { label: "Короткий любовный роман", value: "short-romance" }, - { label: "Космическая фантастика", value: "sf-space" }, - { label: "ЛитРПГ", value: "litrpg" }, - { label: "Любовное фэнтези", value: "love-fantasy" }, - { label: "Любовные романы", value: "romance" }, - { label: "Мистика", value: "paranormal" }, - { label: "Назад в СССР", value: "back-to-ussr" }, - { label: "Научная фантастика", value: "science-fiction" }, - { label: "Подростковая проза", value: "teen-prose" }, - { label: "Политический роман", value: "political-fiction" }, - { label: "Попаданцы", value: "popadantsy" }, - { label: "Попаданцы в космос", value: "popadantsy-v-kosmos" }, - { - label: "Попаданцы в магические миры", - value: "popadantsy-v-magicheskie-miry", - }, - { label: "Попаданцы во времени", value: "popadantsy-vo-vremeni" }, - { label: "Постапокалипсис", value: "postapocalyptic" }, - { label: "Поэзия", value: "poetry" }, - { label: "Приключения", value: "adventure" }, - { label: "Публицистика", value: "publicism" }, - { label: "Развитие личности", value: "razvitie-lichnosti" }, - { label: "Разное", value: "other" }, - { label: "РеалРПГ", value: "realrpg" }, - { label: "Романтическая эротика", value: "romantic-erotika" }, - { label: "Сказка", value: "fairy-tale" }, - { label: "Современная проза", value: "modern-prose" }, - { label: "Современный любовный роман", value: "contemporary-romance" }, - { label: "Социальная фантастика", value: "sf-social" }, - { label: "Стимпанк", value: "steampunk" }, - { label: "Темное фэнтези", value: "dark-fantasy" }, - { label: "Триллер", value: "thriller" }, - { label: "Ужасы", value: "horror" }, - { label: "Фантастика", value: "sci-fi" }, - { label: "Фантастический детектив", value: "detective-science-fiction" }, - { label: "Фанфик", value: "fanfiction" }, - { label: "Фэнтези", value: "fantasy" }, - { label: "Шпионский детектив", value: "spy-mystery" }, - { label: "Эпическое фэнтези", value: "epic-fantasy" }, - { label: "Эротика", value: "erotica" }, - { label: "Эротическая фантастика", value: "sf-erotika" }, - { label: "Эротический фанфик", value: "fanfiction-erotika" }, - { label: "Эротическое фэнтези", value: "fantasy-erotika" }, - { label: "Юмор", value: "humor" }, - { label: "Юмористическая фантастика", value: "sf-humor" }, - { label: "Юмористическое фэнтези", value: "ironical-fantasy" }, - ], - inputType: FilterInputs.Picker, - }, - { - key: "form", - label: "Форма произведения", - values: [ - { label: "Любой", value: "any" }, - { label: "Перевод", value: "translation" }, - { label: "Повесть", value: "tale" }, - { label: "Рассказ", value: "story" }, - { label: "Роман", value: "novel" }, - { label: "Сборник поэзии", value: "poetry" }, - { label: "Сборник рассказов", value: "story-book" }, - ], - inputType: FilterInputs.Picker, - }, - { - key: "state", - label: "Статус произведения", - values: [ - { label: "Любой статус", value: "any" }, - { label: "В процессе", value: "in-progress" }, - { label: "Завершено", value: "finished" }, - ], - inputType: FilterInputs.Picker, - }, - { - key: "series", - label: "Статус цикла", - values: [ - { label: "Не важно", value: "any" }, - { label: "Вне цикла", value: "out" }, - { label: "Цикл завершен", value: "finished" }, - { label: "Цикл не завершен", value: "unfinished" }, - ], - inputType: FilterInputs.Picker, - }, - { - key: "access", - label: "Тип доступа", - values: [ - { label: "Любой", value: "any" }, - { label: "Платный", value: "paid" }, - { label: "Бесплатный", value: "free" }, - ], - inputType: FilterInputs.Picker, - }, - { - key: "promo", - label: "Промо-фрагмент", - values: [ - { label: "Скрывать", value: "hide" }, - { label: "Показывать", value: "show" }, - ], - inputType: FilterInputs.Picker, - }, -]; - -module.exports = { - id: pluginId, - name: sourceName, - site: baseUrl, - version: "1.0.0", - lang: languages.Russian, - icon: "src/ru/authortoday/icon.png", - filters, - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; diff --git a/plugins/russian/axelznovelspace[noveltl].ts b/plugins/russian/axelznovelspace[noveltl].ts new file mode 100644 index 0000000..28a0688 --- /dev/null +++ b/plugins/russian/axelznovelspace[noveltl].ts @@ -0,0 +1,219 @@ + + import { load as parseHTML } from "cheerio"; + import dayjs from 'dayjs'; + import { fetchFile, fetchApi } from "@libs/fetch"; + import { Novel, Plugin, Chapter } from "@typings/plugin"; + // import { parseMadaraDate } from "@libs/parseMadaraDate"; + // import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; + // import { showToast } from "@libs/showToast"; + import { Filter, FilterInputs } from "@libs/filterInputs"; + import { NovelStatus } from '@libs/novelStatus'; + import { defaultCover } from "@libs/defaultCover"; + + + export const id = "axel.znovel"; + export const name = "Аксел"; + export const icon = ""; + export const version = "1.0.0"; + export const site = "https://axel.znovel.space"; + export const filters: Filter[] = [{"key":"tags","label":"Тэги","values":[{"label":"Богини","value":316},{"label":"Везучий главный герой","value":411},{"label":"Виртуальная реальность","value":744},{"label":"Высокомерный герой","value":56},{"label":"Герой-извращенец","value":521},{"label":"Главная героиня — женщина","value":277},{"label":"Главный герой — мужчина","value":419},{"label":"Дворяне","value":487},{"label":"Девочки-волшебницы","value":415},{"label":"Демоны","value":193},{"label":"Дружба","value":293},{"label":"Есть аниме-адаптация","value":10},{"label":"Есть видеоигра","value":13},{"label":"Есть манга-адаптация","value":14},{"label":"Игра на выживание","value":696},{"label":"Из грязи в князи","value":542},{"label":"Королевская особа","value":596},{"label":"Ленивый главный герой","value":389},{"label":"Маги","value":757},{"label":"Магические технологии","value":417}],"inputType":FilterInputs.Checkbox}]; + exports["protected"] = false; + + const baseUrl = site; + const domain = baseUrl.split('//')[1]; + + export const popularNovels: Plugin.popularNovels = async function ( + page, + { filters } + ) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: + 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: filters?.tags?.length ?? 0 > 0 ? { tags: filters?.tags } : {}, + hostname: domain, + limit: 40, + page, + }, + }), + }); + + const json: any = await result.json(); + const novels: Novel.Item[] = json.data?.projects?.content?.map((novel: any) => ({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: novel.covers[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + })); + + return novels; + }; + + + export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Book', + query: + 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}', + variables: { + hostname: domain, + project: novelUrl, + url: novelUrl, + }, + }), + }); + const json: any = await result.json(); + const novel: Novel.instance = { + url: novelUrl, + name: json.data.project.title, + cover: json.data.project.covers[0]?.url + ? baseUrl + json.data.project.covers[0].url + : defaultCover, + summary: parseHTML(json.data.project.annotation?.text).text(), + author: + json.data.project.persons[0].name.firstName + + ' ' + + json.data.project.persons[0].name.lastName, + status: + json.data.project.translationStatus === 'active' + ? NovelStatus.Ongoing + : NovelStatus.Completed, + }; + + let genres = [] + .concat(json.data.project.tags, json.data.project.genres) + .map((item: any) => item?.nameRu || item?.nameEng) + .filter(item => item); + + if (genres.length > 0) { + novel.genres = genres.join(','); + } + + let novelChapters: Chapter.Item[] = []; + json.data.project.subprojects.content.forEach((work: any) => + work.volumes.content.forEach((volume: any) => + volume.chapters.forEach( + (chapter: any) => + chapter?.published && + novelChapters.push({ + name: volume.shortName + ' ' + chapter.title, + url: novelUrl + '/' + volume.url + '/' + chapter.url, + releaseTime: dayjs(chapter.publishDate).format('LLL'), + }), + ), + ), + ); + novel.chapters = novelChapters; + return novel; + }; + + export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'EReaderData', + query: + 'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}', + variables: { + chapterSelector: { + fullUrl: chapterUrl, + }, + url: chapterUrl, + }, + }), + }); + const json: any = await result.json(); + + return parseHTML(json.data.chapter.text.text).html(); + }; + + export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: + 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: { + query: searchTerm, + }, + hostname: domain, + limit: 40, + page: 1, + }, + }), + }); + const json: any = await result.json(); + const novels: Novel.Item[] = []; + json?.data?.projects?.content?.forEach((novel: any) => + novels.push({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: novel.covers[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + }), + ); + return novels; + }; + + export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url, {}); + }; + \ No newline at end of file diff --git a/plugins/russian/belnoveltl[noveltl].ts b/plugins/russian/belnoveltl[noveltl].ts new file mode 100644 index 0000000..6b2feb9 --- /dev/null +++ b/plugins/russian/belnoveltl[noveltl].ts @@ -0,0 +1,219 @@ + + import { load as parseHTML } from "cheerio"; + import dayjs from 'dayjs'; + import { fetchFile, fetchApi } from "@libs/fetch"; + import { Novel, Plugin, Chapter } from "@typings/plugin"; + // import { parseMadaraDate } from "@libs/parseMadaraDate"; + // import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; + // import { showToast } from "@libs/showToast"; + import { Filter, FilterInputs } from "@libs/filterInputs"; + import { NovelStatus } from '@libs/novelStatus'; + import { defaultCover } from "@libs/defaultCover"; + + + export const id = "bel.novel"; + export const name = "Белманга"; + export const icon = ""; + export const version = "1.0.0"; + export const site = "https://bel.novel.tl"; + export const filters: Filter[] = []; + exports["protected"] = false; + + const baseUrl = site; + const domain = baseUrl.split('//')[1]; + + export const popularNovels: Plugin.popularNovels = async function ( + page, + { filters } + ) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: + 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: filters?.tags?.length ?? 0 > 0 ? { tags: filters?.tags } : {}, + hostname: domain, + limit: 40, + page, + }, + }), + }); + + const json: any = await result.json(); + const novels: Novel.Item[] = json.data?.projects?.content?.map((novel: any) => ({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: novel.covers[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + })); + + return novels; + }; + + + export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Book', + query: + 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}', + variables: { + hostname: domain, + project: novelUrl, + url: novelUrl, + }, + }), + }); + const json: any = await result.json(); + const novel: Novel.instance = { + url: novelUrl, + name: json.data.project.title, + cover: json.data.project.covers[0]?.url + ? baseUrl + json.data.project.covers[0].url + : defaultCover, + summary: parseHTML(json.data.project.annotation?.text).text(), + author: + json.data.project.persons[0].name.firstName + + ' ' + + json.data.project.persons[0].name.lastName, + status: + json.data.project.translationStatus === 'active' + ? NovelStatus.Ongoing + : NovelStatus.Completed, + }; + + let genres = [] + .concat(json.data.project.tags, json.data.project.genres) + .map((item: any) => item?.nameRu || item?.nameEng) + .filter(item => item); + + if (genres.length > 0) { + novel.genres = genres.join(','); + } + + let novelChapters: Chapter.Item[] = []; + json.data.project.subprojects.content.forEach((work: any) => + work.volumes.content.forEach((volume: any) => + volume.chapters.forEach( + (chapter: any) => + chapter?.published && + novelChapters.push({ + name: volume.shortName + ' ' + chapter.title, + url: novelUrl + '/' + volume.url + '/' + chapter.url, + releaseTime: dayjs(chapter.publishDate).format('LLL'), + }), + ), + ), + ); + novel.chapters = novelChapters; + return novel; + }; + + export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'EReaderData', + query: + 'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}', + variables: { + chapterSelector: { + fullUrl: chapterUrl, + }, + url: chapterUrl, + }, + }), + }); + const json: any = await result.json(); + + return parseHTML(json.data.chapter.text.text).html(); + }; + + export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: + 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: { + query: searchTerm, + }, + hostname: domain, + limit: 40, + page: 1, + }, + }), + }); + const json: any = await result.json(); + const novels: Novel.Item[] = []; + json?.data?.projects?.content?.forEach((novel: any) => + novels.push({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: novel.covers[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + }), + ); + return novels; + }; + + export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url, {}); + }; + \ No newline at end of file diff --git a/plugins/russian/bookriver.broken.ts b/plugins/russian/bookriver.broken.ts new file mode 100644 index 0000000..d987fab --- /dev/null +++ b/plugins/russian/bookriver.broken.ts @@ -0,0 +1,226 @@ +import cheerio from "cheerio"; +import dayjs from "dayjs"; +import { fetchApi } from "@libs/fetch"; +import { NovelStatus } from "@libs/novelStatus"; +import { FilterInputs } from "@libs/filterInputs"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "bookriver"; +export const name = "Bookriver"; +export const site = "https://bookriver.ru"; +export const version = "1.0.0"; +export const icon = "src/ru/bookriver/icon.png"; + +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function ( + page, + { showLatestNovels, filters } +) { + let url = baseUrl + `/genre?page=${page}&perPage=24&sortingType=`; + url += showLatestNovels ? "last-update" : filters?.sort || "bestseller"; + + if (filters) { + if (Array.isArray(filters.genres) && filters.genres.length) { + url += "&g=" + filters.genres.join(","); + } + } + + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = cheerio.load(body); + let json = loadedCheerio("#__NEXT_DATA__").html(); + json = JSON.parse(json); + + let novels: Novel.Item[] = []; + json.props.pageProps.state.pagesFilter.genre.books.forEach((novel) => + novels.push({ + name: novel.name, + cover: novel.coverImages[0].url, + url: baseUrl + "/book/" + novel.slug, + }) + ); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const result = await fetchApi(novelUrl); + const body = await result.text(); + + const loadedCheerio = cheerio.load(body); + const json = loadedCheerio("#__NEXT_DATA__").html(); + const book = JSON.parse(json).props.pageProps.state.book.bookPage; + + let novel: Novel.instance = { + url: novelUrl, + name: book.name, + cover: book.coverImages[0].url, + summary: book.annotation, + author: book.author.name, + genres: book.tags.map((item) => item.name).join(", "), + status: + book.statusComplete === "writing" + ? NovelStatus.Ongoing + : NovelStatus.Completed, + }; + + let chapters: Chapter.Item[] = []; + + book.ebook.chapters.forEach((chapter) => { + if (chapter.available) { + chapters.push({ + name: chapter.name, + releaseTime: dayjs( + chapter?.firstPublishedAt || chapter.createdAt + ).format("LLL"), + url: + baseUrl + + "/reader/" + + book.slug + + "/" + + chapter.chapterId, + }); + } + }); + + novel.chapters = chapters; + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const url = "https://api.bookriver.ru/api/v1/books/chapter/text/"; + const result = await fetchApi(url + chapterUrl.split("/").pop()); + const json = await result.json(); + + const chapterText = json.data.content; + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = `${baseUrl}/search/books?keyword=${searchTerm}`; + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = cheerio.load(body); + let json = loadedCheerio("#__NEXT_DATA__").html(); + json = JSON.parse(json); + + let novels: Novel.Item[] = []; + json.props.pageProps.state.catalog.books.books.forEach((novel) => + novels.push({ + name: novel.name, + cover: novel.coverImages[0].url, + url: baseUrl + "/book/" + novel.slug, + }) + ); + + return novels; +}; + +export const filters = [ + { + key: "sort", + label: "Сортировка", + values: [ + { label: "Бестселлеры", value: "bestseller" }, + { label: "Дате добавления", value: "newest" }, + { label: "Дате обновления", value: "last-update" }, + ], + inputType: FilterInputs.Picker, + }, + { + key: "genres", + label: "жанры", + values: [ + { + label: "Альтернативная история", + value: "alternativnaya-istoriya", + }, + { label: "Боевая фантастика", value: "boevaya-fantastika" }, + { label: "Боевое фэнтези", value: "boevoe-fentezi" }, + { label: "Бытовое фэнтези", value: "bytovoe-fentezi" }, + { + label: "Героическая фантастика", + value: "geroicheskaya-fantastika", + }, + { label: "Героическое фэнтези", value: "geroicheskoe-fentezi" }, + { label: "Городское фэнтези", value: "gorodskoe-fentezi" }, + { label: "Детектив", value: "detektiv" }, + { + label: "Детективная фантастика", + value: "detektivnaya-fantastika", + }, + { label: "Жёсткая эротика", value: "zhyostkaya-erotika" }, + { label: "Исторический детектив", value: "istoricheskii-detektiv" }, + { + label: "Исторический любовный роман", + value: "istoricheskii-lyubovnyi-roman", + }, + { label: "Историческое фэнтези", value: "istoricheskoe-fentezi" }, + { label: "Киберпанк", value: "kiberpank" }, + { label: "Классический детектив", value: "klassicheskii-detektiv" }, + { + label: "Короткий любовный роман", + value: "korotkii-lyubovnyi-roman", + }, + { + label: "Космическая фантастика", + value: "kosmicheskaya-fantastika", + }, + { label: "Криминальный детектив", value: "kriminalnyi-detektiv" }, + { label: "ЛитРПГ", value: "litrpg" }, + { label: "Любовная фантастика", value: "lyubovnaya-fantastika" }, + { label: "Любовное фэнтези", value: "lyubovnoe-fentezi" }, + { label: "Любовный роман", value: "lyubovnyi-roman" }, + { label: "Мистика", value: "mistika" }, + { label: "Молодежная проза", value: "molodezhnaya-proza" }, + { label: "Научная фантастика", value: "nauchnaya-fantastika" }, + { + label: "Остросюжетный любовный роман", + value: "ostrosyuzhetnyi-lyubovnyi-roman", + }, + { label: "Политический детектив", value: "politicheskii-detektiv" }, + { label: "Попаданцы", value: "popadantsy" }, + { label: "Постапокалипсис", value: "postapokalipsis" }, + { + label: "Приключенческое фэнтези", + value: "priklyuchencheskoe-fentezi", + }, + { + label: "Романтическая эротика", + value: "romanticheskaya-erotika", + }, + { label: "С элементами эротики", value: "s-elementami-erotiki" }, + { label: "Славянское фэнтези", value: "slavyanskoe-fentezi" }, + { + label: "Современный любовный роман", + value: "sovremennyi-lyubovnyi-roman", + }, + { label: "Социальная фантастика", value: "sotsialnaya-fantastika" }, + { label: "Тёмное фэнтези", value: "temnoe-fentezi" }, + { label: "Фантастика", value: "fantastika" }, + { label: "Фэнтези", value: "fentezi" }, + { label: "Шпионский детектив", value: "shpionskii-detektiv" }, + { label: "Эпическое фэнтези", value: "epicheskoe-fentezi" }, + { label: "Эротика", value: "erotika" }, + { + label: "Эротическая фантастика", + value: "eroticheskaya-fantastika", + }, + { label: "Эротический фанфик", value: "eroticheskii-fanfik" }, + { label: "Эротическое фэнтези", value: "eroticheskoe-fentezi" }, + { + label: "Юмористический детектив", + value: "yumoristicheskii-detektiv", + }, + { + label: "Юмористическое фэнтези", + value: "yumoristicheskoe-fentezi", + }, + ], + inputType: FilterInputs.Checkbox, + }, +]; diff --git a/plugins/russian/bookriver.js b/plugins/russian/bookriver.js deleted file mode 100644 index 7b0543c..0000000 --- a/plugins/russian/bookriver.js +++ /dev/null @@ -1,184 +0,0 @@ -const cheerio = require("cheerio"); -const dayjs = require("dayjs"); -const fetchApi = require("@libs/fetchApi"); -const fetchFile = require("@libs/fetchFile"); -const languages = require("@libs/languages"); -const Status = require("@libs/novelStatus"); -const FilterInputs = require("@libs/filterInputs"); - -const pluginId = "bookriver"; -const sourceName = "Bookriver"; -const baseUrl = "https://bookriver.ru"; - -async function popularNovels(page, { showLatestNovels, filters }) { - let url = baseUrl + `/genre?page=${page}&perPage=24&sortingType=`; - url += showLatestNovels ? "last-update" : filters?.sort || "bestseller"; - - if (filters?.genres?.length) { - url += "&g=" + filters.genres.join(","); - } - - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - let json = loadedCheerio("#__NEXT_DATA__").html(); - json = JSON.parse(json); - - let novels = []; - json.props.pageProps.state.pagesFilter.genre.books.forEach((novel) => - novels.push({ - name: novel.name, - cover: novel.coverImages[0].url, - url: baseUrl + "/book/" + novel.slug, - }) - ); - - return novels; -} - -async function parseNovelAndChapters(novelUrl) { - const result = await fetchApi(novelUrl); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - const json = loadedCheerio("#__NEXT_DATA__").html(); - const book = JSON.parse(json).props.pageProps.state.book.bookPage; - - let novel = { - url: novelUrl, - name: book.name, - cover: book.coverImages[0].url, - summary: book.annotation, - author: book.author.name, - genres: book.tags.map((item) => item.name).join(", "), - status: - book.statusComplete === "writing" ? Status.Ongoing : Status.Completed, - }; - - let chapters = []; - - book.ebook.chapters.forEach((chapter) => { - if (chapter.available) { - chapters.push({ - name: chapter.name, - releaseTime: dayjs(chapter?.firstPublishedAt || chapter.createdAt).format("LLL"), - url: baseUrl + "/reader/" + book.slug + "/" + chapter.chapterId, - }); - } - }); - - novel.chapters = chapters; - return novel; -} - -async function parseChapter(chapterUrl) { - const url = "https://api.bookriver.ru/api/v1/books/chapter/text/"; - const result = await fetchApi(url + chapterUrl.split("/").pop()); - const json = await result.json(); - - const chapterText = json.data.content; - return chapterText; -} - -async function searchNovels(searchTerm) { - const url = `${baseUrl}/search/books?keyword=${searchTerm}`; - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - let json = loadedCheerio("#__NEXT_DATA__").html(); - json = JSON.parse(json); - - let novels = []; - json.props.pageProps.state.catalog.books.books.forEach((novel) => - novels.push({ - name: novel.name, - cover: novel.coverImages[0].url, - url: baseUrl + "/book/" + novel.slug, - }) - ); - - return novels; -} - -const filters = [ - { - key: "sort", - label: "Сортировка", - values: [ - { label: "Бестселлеры", value: "bestseller" }, - { label: "Дате добавления", value: "newest" }, - { label: "Дате обновления", value: "last-update" }, - ], - inputType: FilterInputs.Picker, - }, - { - key: "genres", - label: "жанры", - values: [ - { label: "Альтернативная история", value: "alternativnaya-istoriya" }, - { label: "Боевая фантастика", value: "boevaya-fantastika" }, - { label: "Боевое фэнтези", value: "boevoe-fentezi" }, - { label: "Бытовое фэнтези", value: "bytovoe-fentezi" }, - { label: "Героическая фантастика", value: "geroicheskaya-fantastika" }, - { label: "Героическое фэнтези", value: "geroicheskoe-fentezi" }, - { label: "Городское фэнтези", value: "gorodskoe-fentezi" }, - { label: "Детектив", value: "detektiv" }, - { label: "Детективная фантастика", value: "detektivnaya-fantastika" }, - { label: "Жёсткая эротика", value: "zhyostkaya-erotika" }, - { label: "Исторический детектив", value: "istoricheskii-detektiv" }, - { label: "Исторический любовный роман", value: "istoricheskii-lyubovnyi-roman" }, - { label: "Историческое фэнтези", value: "istoricheskoe-fentezi" }, - { label: "Киберпанк", value: "kiberpank" }, - { label: "Классический детектив", value: "klassicheskii-detektiv" }, - { label: "Короткий любовный роман", value: "korotkii-lyubovnyi-roman" }, - { label: "Космическая фантастика", value: "kosmicheskaya-fantastika" }, - { label: "Криминальный детектив", value: "kriminalnyi-detektiv" }, - { label: "ЛитРПГ", value: "litrpg" }, - { label: "Любовная фантастика", value: "lyubovnaya-fantastika" }, - { label: "Любовное фэнтези", value: "lyubovnoe-fentezi" }, - { label: "Любовный роман", value: "lyubovnyi-roman" }, - { label: "Мистика", value: "mistika" }, - { label: "Молодежная проза", value: "molodezhnaya-proza" }, - { label: "Научная фантастика", value: "nauchnaya-fantastika" }, - { label: "Остросюжетный любовный роман", value: "ostrosyuzhetnyi-lyubovnyi-roman" }, - { label: "Политический детектив", value: "politicheskii-detektiv" }, - { label: "Попаданцы", value: "popadantsy" }, - { label: "Постапокалипсис", value: "postapokalipsis" }, - { label: "Приключенческое фэнтези", value: "priklyuchencheskoe-fentezi" }, - { label: "Романтическая эротика", value: "romanticheskaya-erotika" }, - { label: "С элементами эротики", value: "s-elementami-erotiki" }, - { label: "Славянское фэнтези", value: "slavyanskoe-fentezi" }, - { label: "Современный любовный роман", value: "sovremennyi-lyubovnyi-roman" }, - { label: "Социальная фантастика", value: "sotsialnaya-fantastika" }, - { label: "Тёмное фэнтези", value: "temnoe-fentezi" }, - { label: "Фантастика", value: "fantastika" }, - { label: "Фэнтези", value: "fentezi" }, - { label: "Шпионский детектив", value: "shpionskii-detektiv" }, - { label: "Эпическое фэнтези", value: "epicheskoe-fentezi" }, - { label: "Эротика", value: "erotika" }, - { label: "Эротическая фантастика", value: "eroticheskaya-fantastika" }, - { label: "Эротический фанфик", value: "eroticheskii-fanfik" }, - { label: "Эротическое фэнтези", value: "eroticheskoe-fentezi" }, - { label: "Юмористический детектив", value: "yumoristicheskii-detektiv" }, - { label: "Юмористическое фэнтези", value: "yumoristicheskoe-fentezi" }, - ], - inputType: FilterInputs.Checkbox, - }, -]; - -module.exports = { - id: pluginId, - name: sourceName, - site: baseUrl, - version: "1.0.0", - lang: languages.Russian, - icon: "src/ru/bookriver/icon.png", - filters, - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; diff --git a/plugins/russian/erolate.js b/plugins/russian/erolate.js deleted file mode 100644 index 4c902c6..0000000 --- a/plugins/russian/erolate.js +++ /dev/null @@ -1,702 +0,0 @@ -const cheerio = require("cheerio"); -const dayjs = require("dayjs"); -const fetchApi = require("@libs/fetchApi"); -const fetchFile = require("@libs/fetchFile"); -const languages = require("@libs/languages"); -const Status = require("@libs/novelStatus"); -const FilterInputs = require("@libs/filterInputs"); -const defaultCoverUri = - "https://github.com/LNReader/lnreader-sources/blob/main/icons/src/coverNotAvailable.jpg?raw=true"; - -const pluginId = "EROLATE"; -const sourceName = "Erolate"; -const baseUrl = "https://erolate.com"; - -async function popularNovels(page, { showLatestNovels, filters }) { - let url = baseUrl + "/search?t=&cat=2"; - url += "&sort=" + showLatestNovels ? "4" : filters?.sort || "6"; - url += "&type=" + filters?.type || "0"; - url += "&atmosphere=" + filters?.atmosphere || "0"; - url += "&adult=" + filters?.adult || "0"; - - if (filters?.genres?.length) { - url += filters.genres.map((i) => `&genres[]=${i}`).join(""); - } - - if (filters?.tags?.length) { - url += filters.tags.map((i) => `&tags[]=${i}`).join(""); - } - - if (filters?.trash?.length) { - url += filters.trash.map((i) => `&${i}=1`); - } - - url += `&Book_page=${page}`; - - const result = await fetchApi(url); - const body = await result.text(); - const loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio( - 'ul[class="search-results"] > li:not([class="ad_type_catalog"])' - ).each(function () { - novels.push({ - name: loadedCheerio(this).find("p > a").text(), - cover: baseUrl + loadedCheerio(this).find("img").attr("src"), - url: baseUrl + loadedCheerio(this).find("p > a").attr("href"), - }); - }); - - return novels; -} - -async function parseNovelAndChapters(novelUrl) { - let result = await fetchApi(novelUrl); - if (result.url.includes("mature?path=")) { - const formData = new FormData(); - formData.append("path", novelUrl); - formData.append("ok", "Да"); - - await fetchApi(result.url, { - method: "POST", - body: formData, - }); - - result = await fetchApi(novelUrl); - } - const body = await result.text(); - const loadedCheerio = cheerio.load(body); - - let novel = { - url: novelUrl, - }; - - novel.name = loadedCheerio('div[class="container"] > div > div > h1') - .text() - .trim(); - novel.cover = - baseUrl + loadedCheerio('div[class="images"] > div img').attr("src"); - novel.summary = loadedCheerio("#Info > div:nth-child(3)").html(); - let genre = []; - - loadedCheerio('div[class="span5"] > p').each(function () { - switch (loadedCheerio(this).find("strong").text()) { - case "Автор:": - novel.author = loadedCheerio(this).find("em > a").text().trim(); - break; - case "Выпуск:": - novel.status = - loadedCheerio(this).find("em").text().trim() === "продолжается" - ? Status.Ongoing - : Status.Completed; - break; - case "Тэги:": - loadedCheerio(this) - .find("em > a") - .each(function () { - genre.push(loadedCheerio(this).text()); - }); - break; - case "Жанры:": - loadedCheerio(this) - .find("em > a") - .each(function () { - genre.push(loadedCheerio(this).text()); - }); - break; - } - }); - - if (genre?.length) { - novel.genres = genre.reverse().join(","); - } - - let chapters = []; - loadedCheerio("table > tbody > tr.chapter_row").each(function () { - const releaseTime = loadedCheerio(this) - .find("td > span") - .attr("title") - ?.trim(); - - if ( - loadedCheerio(this).find('td > span[class="disabled"]').length < 1 && - releaseTime - ) { - const name = loadedCheerio(this).find('td[class="t"] > a').text().trim(); - const url = loadedCheerio(this).find('td[class="t"] > a').attr("href"); - - chapters.push({ name, releaseTime, url: baseUrl + url }); - } - }); - - novel.chapters = chapters; - return novel; -} - -async function parseChapter(chapterUrl) { - let result = await fetchApi(chapterUrl); - if (result.url.includes("mature?path=")) { - const formData = new FormData(); - formData.append("path", chapterUrl.replace(baseUrl, "")); - formData.append("ok", "Да"); - - await fetchApi(result.url, { - method: "POST", - body: formData, - }); - - result = await fetchApi(chapterUrl); - } - const body = await result.text(); - const loadedCheerio = cheerio.load(body); - - loadedCheerio(".content-text img").each(function () { - if (!loadedCheerio(this).attr("src")?.startsWith("http")) { - const src = loadedCheerio(this).attr("src"); - loadedCheerio(this).attr("src", baseUrl + src); - } - }); - - const chapterText = loadedCheerio(".content-text").html(); - return chapterText; -} - -async function searchNovels(searchTerm) { - const result = await fetchApi( - baseUrl + "/search/autocomplete?query=" + searchTerm - ); - const json = await result.json(); - - let novels = []; - json.forEach((novel) => - novels.push({ - name: novel.title_one + " / " + novel.title_two, - cover: baseUrl + novel.img, - url: baseUrl + novel.url, - }) - ); - - return novels; -} - -const filters = [ - { - key: "sort", - label: "Сортировка", - values: [ - { label: "По рейтингу", value: "6" }, - { label: "По степени готовности", value: "0" }, - { label: "По названию на языке оригинала", value: "1" }, - { label: "По названию на языке перевода", value: "2" }, - { label: "По дате создания", value: "3" }, - { label: "По дате последней активности", value: "4" }, - { label: "По просмотрам", value: "5" }, - { label: "По кол-ву переведённых глав", value: "7" }, - { label: "По кол-ву лайков", value: "8" }, - { label: "По кол-ву страниц", value: "10" }, - { label: "По кол-ву бесплатных глав", value: "11" }, - { label: "По кол-ву рецензий", value: "12" }, - { label: "По кол-ву в закладках", value: "13" }, - { label: "По кол-ву в избранном", value: "14" }, - ], - inputType: FilterInputs.Picker, - }, - { - key: "type", - label: "Тип", - values: [ - { label: "Неважно", value: "0" }, - { label: "Только переводы", value: "1" }, - { label: "Только авторские", value: "2" }, - ], - inputType: FilterInputs.Picker, - }, - { - key: "atmosphere", - label: "Атмосфера", - values: [ - { label: "Неважно", value: "0" }, - { label: "Позитивная", value: "1" }, - { label: "Dark", value: "2" }, - ], - inputType: FilterInputs.Picker, - }, - { - key: "trash", - label: "Другое", - values: [ - { label: "Готовые на 100%", value: "ready" }, - { label: "Доступные для перевода", value: "tr" }, - { label: "Доступные для скачивания", value: "gen" }, - { label: "Завершённые проекты", value: "wealth" }, - { label: "Распродажа", value: "discount" }, - { label: "Только онгоинги", value: "ongoings" }, - { label: "Убрать машинный", value: "remove_machinelate" }, - ], - inputType: FilterInputs.Checkbox, - }, - { - key: "genres", - label: "Жанры", - values: [ - { label: "Анал", value: "2" }, - { label: "Бдсм", value: "3" }, - { label: "Большая грудь", value: "5" }, - { label: "Большая попка", value: "6" }, - { label: "Большой член", value: "7" }, - { label: "Бондаж", value: "8" }, - { label: "В первый раз", value: "9" }, - { label: "В цвете", value: "10" }, - { label: "Гарем", value: "11" }, - { label: "Гендарная интрига", value: "12" }, - { label: "Групповой секс", value: "13" }, - { label: "Детектив", value: "39" }, - { label: "Драма", value: "14" }, - { label: "Зрелые женщины (milf)", value: "15" }, - { label: "Измена", value: "16" }, - { label: "Изнасилование", value: "17" }, - { label: "Инцест", value: "18" }, - { label: "Исторический", value: "19" }, - { label: "Комедия", value: "20" }, - { label: "Контроль над разумом", value: "35" }, - { label: "Маленькая грудь", value: "21" }, - { label: "Мистика", value: "40" }, - { label: "Научная фантастика", value: "22" }, - { label: "Нетораре", value: "23" }, - { label: "Оральный секс", value: "24" }, - { label: "Повседневность", value: "41" }, - { label: "Приключения", value: "38" }, - { label: "Публичный секс", value: "33" }, - { label: "Романтика", value: "25" }, - { label: "С изображениями ", value: "36" }, - { label: "Сверхъестественное", value: "34" }, - { label: "Смат", value: "37" }, - { label: "Тентакли", value: "26" }, - { label: "Трагедия", value: "27" }, - { label: "Ужасы", value: "28" }, - { label: "Фантастика", value: "32" }, - { label: "Фэнтези", value: "29" }, - { label: "Чикан", value: "30" }, - { label: "Этти", value: "31" }, - { label: "Ahegao", value: "1" }, - ], - inputType: FilterInputs.Checkbox, - }, - { - key: "tags", - label: "Тэги", - values: [ - { label: "+18", value: "141" }, - { label: "18+", value: "200" }, - { label: "21+", value: "334" }, - { label: "Адекватные главные герои", value: "253" }, - { label: "Азартные игры", value: "210" }, - { label: "Айдолы", value: "364" }, - { label: "Акула", value: "53" }, - { label: "Альтернативное развитие событий", value: "338" }, - { label: "Аморальный главный герой", value: "219" }, - { label: "Анал", value: "7" }, - { label: "Анальный секс", value: "67" }, - { label: "Ангелы", value: "114" }, - { label: "Антигерой", value: "226" }, - { label: "Аристократия", value: "260" }, - { label: "Артефакт", value: "379" }, - { label: "Афродизиак", value: "276" }, - { label: "Ахегао", value: "240" }, - { label: "Бабушка беременна от внука", value: "393" }, - { label: "Бабушка и внук", value: "380" }, - { label: "Бдсм", value: "131" }, - { label: "Беременность", value: "148" }, - { label: "Бесплатно", value: "79" }, - { label: "Бесстрашные персонажи", value: "339" }, - { label: "Библиотека", value: "265" }, - { label: "Бистиалити", value: "391" }, - { label: "Близнецы", value: "198" }, - { label: "Блондинка", value: "91" }, - { label: "Богатые персонажи", value: "248" }, - { label: "Боги", value: "115" }, - { label: "Боевик", value: "252" }, - { label: "Большая грудь", value: "33" }, - { label: "Большая попка", value: "328" }, - { label: "Большой член", value: "76" }, - { label: "Большой член сына", value: "396" }, - { label: "Босс и подчиненный", value: "330" }, - { label: "Брак", value: "182" }, - { label: "Брак по расчёту", value: "346" }, - { label: "Брат", value: "29" }, - { label: "Брат и сестра", value: "30" }, - { label: "Брюнетка", value: "28" }, - { label: "Бэтмен", value: "369" }, - { label: "Бэтмен", value: "170" }, - { label: "В первый раз", value: "179" }, - { label: "Вагинальный секс", value: "57" }, - { label: "Вагинальный секс", value: "171" }, - { label: "Вайурист", value: "9" }, - { label: "Вампиры", value: "133" }, - { label: "Ван-пис", value: "335" }, - { label: "Веб камера", value: "289" }, - { label: "Ведьмы", value: "232" }, - { label: "Викинги", value: "307" }, - { label: "Виртуальная реальность", value: "382" }, - { label: "Вирус", value: "203" }, - { label: "Внучка", value: "189" }, - { label: "Война", value: "387" }, - { label: "Время", value: "81" }, - { label: "Второй шанс", value: "244" }, - { label: "Вуайеризм", value: "62" }, - { label: "Выживание", value: "135" }, - { label: "Гарем", value: "107" }, - { label: "Гарри поттер", value: "97" }, - { label: "Гг имба", value: "87" }, - { label: "Генетические модификации", value: "314" }, - { label: "Гипноз", value: "143" }, - { label: "Главная героиня девушка", value: "147" }, - { label: "Главный герой женщина", value: "213" }, - { label: "Главный герой извращенец", value: "116" }, - { label: "Главный герой мужчина", value: "117" }, - { label: "Глубокое горло", value: "94" }, - { label: "Гоблины", value: "230" }, - { label: "Город", value: "139" }, - { label: "Городское фэнтези", value: "297" }, - { label: "Грудное молоко", value: "225" }, - { label: "Групповой секс", value: "72" }, - { label: "Гэнг-бэнг", value: "235" }, - { label: "Гяру", value: "222" }, - { label: "Двойное проникновение", value: "42" }, - { label: "Двойной анал", value: "47" }, - { label: "Дворяне", value: "246" }, - { label: "Деамоны старший школы", value: "291" }, - { label: "Девственница", value: "119" }, - { label: "Девушки-монстры", value: "223" }, - { label: "Демоны", value: "98" }, - { label: "Детектив", value: "249" }, - { label: "Дзёсэй", value: "259" }, - { label: "Доктор", value: "326" }, - { label: "Дом", value: "138" }, - { label: "Доминирование", value: "18" }, - { label: "Дочь", value: "153" }, - { label: "Дочь беременна от отца", value: "348" }, - { label: "Древние времена", value: "383" }, - { label: "Древний китай", value: "261" }, - { label: "Другой мир", value: "365" }, - { label: "Дружба", value: "325" }, - { label: "Друзья детства", value: "183" }, - { label: "Дядя", value: "160" }, - { label: "Жена", value: "70" }, - { label: "Жена и муж", value: "99" }, - { label: "Жена шлюха", value: "236" }, - { label: "Женское доминирование", value: "100" }, - { label: "Жесткий секс", value: "283" }, - { label: "Жёсткий секс", value: "101" }, - { label: "Жестокие персонажи", value: "254" }, - { label: "Жестокий мир", value: "336" }, - { label: "Жестокость", value: "123" }, - { label: "Заботливый главный герой", value: "340" }, - { label: "Зависимость", value: "370" }, - { label: "Заключение", value: "196" }, - { label: "Заключённые", value: "384" }, - { label: "Замок", value: "140" }, - { label: "Запретная любовь", value: "273" }, - { label: "Заражение", value: "206" }, - { label: "Звёздные войны", value: "120" }, - { label: "Зверодевочки", value: "158" }, - { label: "Зло", value: "343" }, - { label: "Золовка", value: "77" }, - { label: "Золотой дождь", value: "146" }, - { label: "Зомби апокалипсис", value: "241" }, - { label: "Зоофилия", value: "144" }, - { label: "Игровые элементы", value: "385" }, - { label: "Извращения", value: "192" }, - { label: "Измена", value: "71" }, - { label: "Изменения внешности", value: "315" }, - { label: "Изменения личности", value: "316" }, - { label: "Изнасилование", value: "275" }, - { label: "Изуку мидория", value: "337" }, - { label: "Ино", value: "127" }, - { label: "Инопланетяне", value: "304" }, - { label: "Интересный сюжет", value: "349" }, - { label: "Интимные сцены", value: "322" }, - { label: "Интриги и заговоры", value: "262" }, - { label: "Интроверт", value: "38" }, - { label: "Инфекция", value: "207" }, - { label: "Инцест", value: "35" }, - { label: "Камера", value: "92" }, - { label: "Колледж", value: "82" }, - { label: "Комикс", value: "286" }, - { label: "Контроль", value: "19" }, - { label: "Контроль над разумом", value: "181" }, - { label: "Контроль разума", value: "65" }, - { label: "Кончил внутрь", value: "221" }, - { label: "Королевская семья", value: "358" }, - { label: "Коррупция", value: "49" }, - { label: "Космос", value: "204" }, - { label: "Красивая главная героиня", value: "255" }, - { label: "Красивые женщины", value: "321" }, - { label: "Красивый главный герой", value: "266" }, - { label: "Ксенофилия", value: "386" }, - { label: "Кулинария", value: "362" }, - { label: "Культвация", value: "86" }, - { label: "Кунилингус", value: "152" }, - { label: "Лактация", value: "11" }, - { label: "Лишение девственности", value: "212" }, - { label: "Лоли", value: "218" }, - { label: "Лунатизм", value: "279" }, - { label: "Любовь", value: "89" }, - { label: "Магический мир", value: "357" }, - { label: "Магия", value: "80" }, - { label: "Мама", value: "3" }, - { label: "Мама беременна от сына", value: "274" }, - { label: "Мама и дочь", value: "163" }, - { label: "Мама и сын", value: "1" }, - { label: "Мама супермена", value: "39" }, - { label: "Манипуляция временем", value: "292" }, - { label: "Марвел", value: "290" }, - { label: "Марвел", value: "173" }, - { label: "Марти стью", value: "102" }, - { label: "Массаж", value: "224" }, - { label: "Мастурбация", value: "63" }, - { label: "Мать", value: "239" }, - { label: "Мать и дочь", value: "381" }, - { label: "Мать и сын", value: "237" }, - { label: "Мафия", value: "256" }, - { label: "Мачеха", value: "197" }, - { label: "Мачеха беременна от сына", value: "395" }, - { label: "Мачеха и сын", value: "394" }, - { label: "Медсестра", value: "341" }, - { label: "Межрассовый", value: "54" }, - { label: "Месть", value: "306" }, - { label: "Меха", value: "268" }, - { label: "Ми", value: "243" }, - { label: "Милф", value: "34" }, - { label: "Милф", value: "180" }, - { label: "Минет", value: "52" }, - { label: "Мистика", value: "250" }, - { label: "Младшая сестра", value: "154" }, - { label: "Модель", value: "324" }, - { label: "Монстры", value: "333" }, - { label: "Мошеничество", value: "44" }, - { label: "Мужская беременность", value: "363" }, - { label: "Мужчина протагонист", value: "108" }, - { label: "Музыка", value: "331" }, - { label: "Мэри съюха", value: "309" }, - { label: "Наруто", value: "46" }, - { label: "Насилие", value: "128" }, - { label: "Насилие и жестокость", value: "193" }, - { label: "Научный эксперимент", value: "313" }, - { label: "Нежить", value: "373" }, - { label: "Некромантия", value: "298" }, - { label: "Ненормативная лексика", value: "124" }, - { label: "Нетораре", value: "69" }, - { label: "Нетори", value: "284" }, - { label: "Нижнее бельё", value: "392" }, - { label: "Ниндзя", value: "296" }, - { label: "Ношеные трусики", value: "166" }, - { label: "Нудизм", value: "156" }, - { label: "Няня", value: "88" }, - { label: "Обмен женами", value: "78" }, - { label: "Оборотни", value: "136" }, - { label: "Обратный гарем", value: "214" }, - { label: "Одержимость", value: "345" }, - { label: "Омегаверс", value: "368" }, - { label: "Оплодотворение", value: "157" }, - { label: "Оральный секс", value: "10" }, - { label: "Оргия", value: "356" }, - { label: "Орки", value: "388" }, - { label: "От слабого к сильному", value: "351" }, - { label: "Отец", value: "2" }, - { label: "Отец делится с сыном", value: "6" }, - { label: "Отец и дочь", value: "205" }, - { label: "Отец куколд", value: "272" }, - { label: "Офис", value: "234" }, - { label: "Падчерица", value: "271" }, - { label: "Пайзури", value: "195" }, - { label: "Папа и дочь", value: "360" }, - { label: "Паразиты", value: "208" }, - { label: "Параллельный мир", value: "320" }, - { label: "Пародия", value: "371" }, - { label: "Первый раз", value: "159" }, - { label: "Перемещение в другой мир", value: "347" }, - { label: "Перемещение во времени", value: "245" }, - { label: "Перерождение", value: "109" }, - { label: "Перерождение в злодея", value: "202" }, - { label: "Пирсинг", value: "145" }, - { label: "Писатель", value: "267" }, - { label: "Планомерное развитие событий", value: "350" }, - { label: "Повседневность", value: "247" }, - { label: "Подглядывание", value: "280" }, - { label: "Подчинение", value: "106" }, - { label: "Подчинение и унижение", value: "211" }, - { label: "Поедание киски", value: "26" }, - { label: "Покемоны", value: "132" }, - { label: "Покорная", value: "8" }, - { label: "Полигамия", value: "352" }, - { label: "Половое воспитание", value: "37" }, - { label: "Полулюди", value: "231" }, - { label: "Попаданец", value: "103" }, - { label: "Попадание в книгу", value: "361" }, - { label: "Порка", value: "20" }, - { label: "Порно", value: "278" }, - { label: "Постапокалипсис", value: "332" }, - { label: "Потеря девственности", value: "142" }, - { label: "Похищение", value: "161" }, - { label: "Приключения", value: "134" }, - { label: "Принуждение", value: "66" }, - { label: "Психология", value: "238" }, - { label: "Публично", value: "85" }, - { label: "Публичный секс", value: "233" }, - { label: "Раб", value: "353" }, - { label: "Рабы", value: "308" }, - { label: "Разват", value: "58" }, - { label: "Разврат", value: "64" }, - { label: "Райзен", value: "95" }, - { label: "Раса инопланетных космических лесбиянок", value: "294" }, - { label: "Рестленг", value: "84" }, - { label: "Риас гремори", value: "389" }, - { label: "Романтика", value: "178" }, - { label: "Рыжий", value: "90" }, - { label: "Сакура", value: "126" }, - { label: "Самолет", value: "93" }, - { label: "Санса старк", value: "277" }, - { label: "Санта", value: "372" }, - { label: "Свадьба", value: "73" }, - { label: "Сверхсила", value: "228" }, - { label: "Сверхъестественное", value: "319" }, - { label: "Свингеры", value: "201" }, - { label: "Свободные отношения", value: "186" }, - { label: "Связывание", value: "137" }, - { label: "Сёдзе", value: "264" }, - { label: "Секс", value: "21" }, - { label: "Секс без проникновения", value: "68" }, - { label: "Секс втроем", value: "43" }, - { label: "Секс игрушки", value: "59" }, - { label: "Секс рабыня", value: "55" }, - { label: "Секс с монстрами", value: "56" }, - { label: "Секс с учителем", value: "184" }, - { label: "Секса будет много", value: "151" }, - { label: "Сексуальное желание", value: "209" }, - { label: "Селфцест", value: "397" }, - { label: "Семья", value: "5" }, - { label: "Сестра", value: "31" }, - { label: "Сестра беременна от брата", value: "355" }, - { label: "Сильный с самого начала", value: "112" }, - { label: "Симбиоз", value: "50" }, - { label: "Сирена", value: "51" }, - { label: "Система", value: "104" }, - { label: "Сиськи", value: "285" }, - { label: "Сквирт", value: "164" }, - { label: "Скрытый секс", value: "287" }, - { label: "Служанка", value: "215" }, - { label: "Соб", value: "281" }, - { label: "Соблазнение", value: "162" }, - { label: "Современность", value: "217" }, - { label: "Соколиный Глаз", value: "176" }, - { label: "Соперничество", value: "216" }, - { label: "Соседи", value: "61" }, - { label: "Соседка", value: "312" }, - { label: "Сперма", value: "229" }, - { label: "Сперма на лицо", value: "199" }, - { label: "Спящие", value: "187" }, - { label: "Старшая сестра", value: "311" }, - { label: "Стеб", value: "111" }, - { label: "Студенты", value: "177" }, - { label: "Суккуб", value: "390" }, - { label: "Супергерои", value: "305" }, - { label: "Суперспособности", value: "300" }, - { label: "Сын", value: "4" }, - { label: "Твинцест", value: "188" }, - { label: "Темное фэнтези", value: "293" }, - { label: "Тентакли", value: "303" }, - { label: "Тетя", value: "113" }, - { label: "Тетя беременна от племянника", value: "376" }, - { label: "Тетя и племянник", value: "377" }, - { label: "Техника", value: "302" }, - { label: "Технологии", value: "301" }, - { label: "Тёща", value: "270" }, - { label: "Тиран", value: "310" }, - { label: "Трагическое прошлое", value: "251" }, - { label: "Трансмиграция", value: "269" }, - { label: "Трансформация", value: "317" }, - { label: "Триллер", value: "344" }, - { label: "Трусики", value: "165" }, - { label: "Убийства", value: "258" }, - { label: "Удача", value: "299" }, - { label: "Ужасы", value: "375" }, - { label: "Улучшение тела", value: "288" }, - { label: "Умная главная героиня", value: "242" }, - { label: "Умные персонажи", value: "257" }, - { label: "Умный главный герой", value: "354" }, - { label: "Университет", value: "323" }, - { label: "Ученик", value: "83" }, - { label: "Ф", value: "149" }, - { label: "Фанатичная любовь", value: "366" }, - { label: "Фанаты", value: "367" }, - { label: "Фантастика", value: "295" }, - { label: "Фанфик", value: "40" }, - { label: "Фелляция", value: "22" }, - { label: "Ферма", value: "191" }, - { label: "Фетиш", value: "60" }, - { label: "Флэш", value: "121" }, - { label: "Футанария", value: "150" }, - { label: "Фэнтези", value: "359" }, - { label: "Хентай", value: "75" }, - { label: "Хината", value: "110" }, - { label: "Холодная главная героиня", value: "263" }, - { label: "Черная вдова", value: "220" }, - { label: "Черная Вдова", value: "175" }, - { label: "Черный юмор", value: "374" }, - { label: "Читерство", value: "45" }, - { label: "Чтение мыслей", value: "227" }, - { label: "Чудовища", value: "378" }, - { label: "Шантаж", value: "129" }, - { label: "Школа", value: "118" }, - { label: "Школьная жизнь", value: "318" }, - { label: "Шлюха", value: "32" }, - { label: "Шоу-бизнес", value: "329" }, - { label: "Шоу-бизнес", value: "172" }, - { label: "Эксгибиционизм", value: "74" }, - { label: "Элементы бдсм", value: "185" }, - { label: "Эмма уотсон", value: "342" }, - { label: "Эмма Уотсон", value: "169" }, - { label: "Эротика", value: "36" }, - { label: "Этти", value: "327" }, - { label: "Юмор", value: "105" }, - { label: "Яндере", value: "155" }, - { label: "Bl", value: "125" }, - { label: "Gl", value: "96" }, - { label: "Harry potter", value: "190" }, - { label: "Harry potter", value: "168" }, - { label: "Hentai", value: "282" }, - { label: "Marvel", value: "194" }, - { label: "Marvel", value: "174" }, - { label: "Milf", value: "167" }, - ], - inputType: FilterInputs.Checkbox, - }, - { - key: "adult", - label: "Возрастные ограничения", - values: [ - { label: "Все", value: "0" }, - { label: "Убрать 18+", value: "1" }, - { label: "Только 18+", value: "2" }, - ], - inputType: FilterInputs.Picker, - }, -]; - -module.exports = { - id: pluginId, - name: sourceName, - site: baseUrl, - version: "1.0.0", - lang: languages.Russian, - icon: "src/ru/erolate/icon.png", - filters, - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; diff --git a/plugins/russian/jaomix.js b/plugins/russian/jaomix.js deleted file mode 100644 index 9f72c47..0000000 --- a/plugins/russian/jaomix.js +++ /dev/null @@ -1,215 +0,0 @@ -const cheerio = require("cheerio"); -const fetchApi = require("@libs/fetchApi"); -const fetchFile = require("@libs/fetchFile"); -const languages = require("@libs/languages"); -const Status = require("@libs/novelStatus"); -const FilterInputs = require("@libs/filterInputs"); - -const pluginId = "jaomix.ru"; -const sourceName = "Jaomix"; -const baseUrl = "https://jaomix.ru"; - -async function popularNovels(page, { showLatestNovels, filters }) { - let url = baseUrl + "/?searchrn&sortby="; - url += showLatestNovels ? "upd" : filters?.sort || "count"; - - if (filters?.type?.length) { - url += filters.type.map((i) => `&lang[]=${i}`).join(""); - } - - if (filters?.genres?.length) { - url += filters.genres.map((i) => `&genre[]=${i}`).join(""); - } - url += `&page=${page}`; - - const result = await fetchApi(url); - let body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - let novels = []; - loadedCheerio('div[class="block-home"] > div[class="one"]').each(function () { - const name = loadedCheerio(this) - .find('div[class="img-home"] > a') - .attr("title"); - const cover = loadedCheerio(this) - .find('div[class="img-home"] > a > img') - .attr("src") - .replace("-150x150", ""); - const url = loadedCheerio(this) - .find('div[class="img-home"] > a') - .attr("href"); - - novels.push({ name, cover, url }); - }); - - return novels; -} - -async function parseNovelAndChapters(novelUrl) { - const result = await fetchApi(novelUrl); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - let novel = { - url: novelUrl, - }; - - novel.name = loadedCheerio('div[class="desc-book"] > h1').text().trim(); - novel.cover = loadedCheerio('div[class="img-book"] > img').attr("src"); - novel.summary = loadedCheerio('div[id="desc-tab"]').text().trim(); - - loadedCheerio("#info-book > p").each(function () { - let text = loadedCheerio(this).text().replace(/,/g, "").split(" "); - if (text[0] === "Автор:") { - novel.author = text.splice(1).join(" "); - } else if (text[0] === "Жанры:") { - novel.genres = text.splice(1).join(","); - } else if (text[0] === "Статус:") { - novel.status = text.includes("продолжается") - ? Status.Ongoing - : Status.Completed; - } - }); - - const chapters = []; - - loadedCheerio(".download-chapter div.title").each(function () { - chapters.push({ - name: loadedCheerio(this).find("a").attr("title"), - releaseTime: loadedCheerio(this).find("time").text(), - url: loadedCheerio(this).find("a").attr("href"), - }); - }); - - novel.chapters = chapters.reverse(); - return novel; -} - -async function parseChapter(chapterUrl) { - const result = await fetchApi(chapterUrl); - const body = await result.text(); - const loadedCheerio = cheerio.load(body); - - loadedCheerio('div[class="adblock-service"]').remove(); - const chapterText = loadedCheerio('div[class="entry-content"]').html(); - - return chapterText; -} - -async function searchNovels(searchTerm) { - const url = `${baseUrl}/?searchrn=${searchTerm}&but=Поиск по названию&sortby=upd`; - const result = await fetchApi(url); - let body = await result.text(); - const loadedCheerio = cheerio.load(body); - - let novels = []; - loadedCheerio('div[class="block-home"] > div[class="one"]').each(function () { - const name = loadedCheerio(this) - .find('div[class="img-home"] > a') - .attr("title"); - const cover = loadedCheerio(this) - .find('div[class="img-home"] > a > img') - .attr("src") - .replace("-150x150", ""); - const url = loadedCheerio(this) - .find('div[class="img-home"] > a') - .attr("href"); - - novels.push({ name, cover, url }); - }); - - return novels; -} - -const filters = [ - { - key: "sort", - label: "Сортировка", - values: [ - { label: "Имя", value: "alphabet" }, - { label: "Просмотры", value: "count" }, - { label: "Дате добавления", value: "new" }, - { label: "Дате обновления", value: "upd" }, - ], - inputType: FilterInputs.Picker, - }, - { - key: "type", - label: "Тип", - values: [ - { label: "Английский", value: "Английский" }, - { label: "Китайский", value: "Китайский" }, - { label: "Корейский", value: "Корейский" }, - { label: "Японский", value: "Японский" }, - ], - inputType: FilterInputs.Checkbox, - }, - { - key: "genres", - label: "Жанры", - values: [ - { label: "Боевые Искусства", value: "Боевые Искусства" }, - { label: "Виртуальный Мир", value: "Виртуальный Мир" }, - { label: "Гарем", value: "Гарем" }, - { label: "Детектив", value: "Детектив" }, - { label: "Драма", value: "Драма" }, - { label: "Игра", value: "Игра" }, - { label: "Истории из жизни", value: "Истории из жизни" }, - { label: "Исторический", value: "Исторический" }, - { label: "История", value: "История" }, - { label: "Исэкай", value: "Исэкай" }, - { label: "Комедия", value: "Комедия" }, - { label: "Меха", value: "Меха" }, - { label: "Мистика", value: "Мистика" }, - { label: "Научная Фантастика", value: "Научная Фантастика" }, - { label: "Повседневность", value: "Повседневность" }, - { label: "Постапокалипсис", value: "Постапокалипсис" }, - { label: "Приключения", value: "Приключения" }, - { label: "Психология", value: "Психология" }, - { label: "Романтика", value: "Романтика" }, - { label: "Сверхъестественное", value: "Сверхъестественное" }, - { label: "Сёнэн", value: "Сёнэн" }, - { label: "Сёнэн-ай", value: "Сёнэн-ай" }, - { label: "Спорт", value: "Спорт" }, - { label: "Сэйнэн", value: "Сэйнэн" }, - { label: "Сюаньхуа", value: "Сюаньхуа" }, - { label: "Трагедия", value: "Трагедия" }, - { label: "Триллер", value: "Триллер" }, - { label: "Фантастика", value: "Фантастика" }, - { label: "Фэнтези", value: "Фэнтези" }, - { label: "Хоррор", value: "Хоррор" }, - { label: "Школьная жизнь", value: "Школьная жизнь" }, - { label: "Шоунен", value: "Шоунен" }, - { label: "Экшн", value: "Экшн" }, - { label: "Этти", value: "Этти" }, - { label: "Юри", value: "Юри" }, - { label: "Adult", value: "Adult" }, - { label: "Ecchi", value: "Ecchi" }, - { label: "Josei", value: "Josei" }, - { label: "Lolicon", value: "Lolicon" }, - { label: "Mature", value: "Mature" }, - { label: "Shoujo", value: "Shoujo" }, - { label: "Wuxia", value: "Wuxia" }, - { label: "Xianxia", value: "Xianxia" }, - { label: "Xuanhuan", value: "Xuanhuan" }, - { label: "Yaoi", value: "Yaoi" }, - ], - inputType: FilterInputs.Checkbox, - }, -]; - -module.exports = { - id: pluginId, - name: sourceName, - site: baseUrl, - version: "1.0.0", - lang: languages.Russian, - icon: "src/ru/jaomix/icon.png", - filters, - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; diff --git a/plugins/russian/jaomix.ts b/plugins/russian/jaomix.ts new file mode 100644 index 0000000..a543bd9 --- /dev/null +++ b/plugins/russian/jaomix.ts @@ -0,0 +1,219 @@ +import { load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { NovelStatus } from "@libs/novelStatus"; +import { FilterInputs } from "@libs/filterInputs"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "jaomix.ru"; +export const name = "Jaomix"; +export const site = "https://jaomix.ru"; +export const version = "1.0.0"; +export const icon = "src/ru/jaomix/icon.png"; + +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function ( + page, + { showLatestNovels, filters } +) { + let url = baseUrl + "/?searchrn&sortby="; + url += showLatestNovels ? "upd" : filters?.sort || "count"; + + if (filters) { + if (Array.isArray(filters.type) && filters.type.length) { + url += filters.type.map((i) => `&lang[]=${i}`).join(""); + } + + if (Array.isArray(filters.genres) && filters.genres.length) { + url += filters.genres.map((i) => `&genre[]=${i}`).join(""); + } + } + + url += `&page=${page}`; + + const result = await fetchApi(url); + let body = await result.text(); + + const loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + loadedCheerio('div[class="block-home"] > div[class="one"]').each( + function () { + const name = loadedCheerio(this) + .find('div[class="img-home"] > a') + .attr("title"); + const cover = loadedCheerio(this) + .find('div[class="img-home"] > a > img') + .attr("src") + ?.replace("-150x150", ""); + const url = loadedCheerio(this) + .find('div[class="img-home"] > a') + .attr("href"); + + if (!name || !url) return; + + novels.push({ name, cover, url }); + } + ); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const result = await fetchApi(novelUrl); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + let novel: Novel.instance = { + url: novelUrl, + }; + + novel.name = loadedCheerio('div[class="desc-book"] > h1').text().trim(); + novel.cover = loadedCheerio('div[class="img-book"] > img').attr("src"); + novel.summary = loadedCheerio('div[id="desc-tab"]').text().trim(); + + loadedCheerio("#info-book > p").each(function () { + let text = loadedCheerio(this).text().replace(/,/g, "").split(" "); + if (text[0] === "Автор:") { + novel.author = text.splice(1).join(" "); + } else if (text[0] === "Жанры:") { + novel.genres = text.splice(1).join(","); + } else if (text[0] === "Статус:") { + novel.status = text.includes("продолжается") + ? NovelStatus.Ongoing + : NovelStatus.Completed; + } + }); + + const chapters: Chapter.Item[] = []; + + loadedCheerio(".download-chapter div.title").each(function () { + chapters.push({ + name: loadedCheerio(this).find("a").attr("title")!, + releaseTime: loadedCheerio(this).find("time").text(), + url: loadedCheerio(this).find("a").attr("href")!, + }); + }); + + novel.chapters = chapters.reverse(); + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi(chapterUrl); + const body = await result.text(); + const loadedCheerio = parseHTML(body); + + loadedCheerio('div[class="adblock-service"]').remove(); + const chapterText = loadedCheerio('div[class="entry-content"]').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = `${baseUrl}/?searchrn=${searchTerm}&but=Поиск по названию&sortby=upd`; + const result = await fetchApi(url); + let body = await result.text(); + const loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + loadedCheerio('div[class="block-home"] > div[class="one"]').each( + function () { + const name = loadedCheerio(this) + .find('div[class="img-home"] > a') + .attr("title"); + const cover = loadedCheerio(this) + .find('div[class="img-home"] > a > img') + .attr("src") + ?.replace("-150x150", ""); + const url = loadedCheerio(this) + .find('div[class="img-home"] > a') + .attr("href"); + + if (!name || !url) return; + + novels.push({ name, cover, url }); + } + ); + + return novels; +}; + +export const filters = [ + { + key: "sort", + label: "Сортировка", + values: [ + { label: "Имя", value: "alphabet" }, + { label: "Просмотры", value: "count" }, + { label: "Дате добавления", value: "new" }, + { label: "Дате обновления", value: "upd" }, + ], + inputType: FilterInputs.Picker, + }, + { + key: "type", + label: "Тип", + values: [ + { label: "Английский", value: "Английский" }, + { label: "Китайский", value: "Китайский" }, + { label: "Корейский", value: "Корейский" }, + { label: "Японский", value: "Японский" }, + ], + inputType: FilterInputs.Checkbox, + }, + { + key: "genres", + label: "Жанры", + values: [ + { label: "Боевые Искусства", value: "Боевые Искусства" }, + { label: "Виртуальный Мир", value: "Виртуальный Мир" }, + { label: "Гарем", value: "Гарем" }, + { label: "Детектив", value: "Детектив" }, + { label: "Драма", value: "Драма" }, + { label: "Игра", value: "Игра" }, + { label: "Истории из жизни", value: "Истории из жизни" }, + { label: "Исторический", value: "Исторический" }, + { label: "История", value: "История" }, + { label: "Исэкай", value: "Исэкай" }, + { label: "Комедия", value: "Комедия" }, + { label: "Меха", value: "Меха" }, + { label: "Мистика", value: "Мистика" }, + { label: "Научная Фантастика", value: "Научная Фантастика" }, + { label: "Повседневность", value: "Повседневность" }, + { label: "Постапокалипсис", value: "Постапокалипсис" }, + { label: "Приключения", value: "Приключения" }, + { label: "Психология", value: "Психология" }, + { label: "Романтика", value: "Романтика" }, + { label: "Сверхъестественное", value: "Сверхъестественное" }, + { label: "Сёнэн", value: "Сёнэн" }, + { label: "Сёнэн-ай", value: "Сёнэн-ай" }, + { label: "Спорт", value: "Спорт" }, + { label: "Сэйнэн", value: "Сэйнэн" }, + { label: "Сюаньхуа", value: "Сюаньхуа" }, + { label: "Трагедия", value: "Трагедия" }, + { label: "Триллер", value: "Триллер" }, + { label: "Фантастика", value: "Фантастика" }, + { label: "Фэнтези", value: "Фэнтези" }, + { label: "Хоррор", value: "Хоррор" }, + { label: "Школьная жизнь", value: "Школьная жизнь" }, + { label: "Шоунен", value: "Шоунен" }, + { label: "Экшн", value: "Экшн" }, + { label: "Этти", value: "Этти" }, + { label: "Юри", value: "Юри" }, + { label: "Adult", value: "Adult" }, + { label: "Ecchi", value: "Ecchi" }, + { label: "Josei", value: "Josei" }, + { label: "Lolicon", value: "Lolicon" }, + { label: "Mature", value: "Mature" }, + { label: "Shoujo", value: "Shoujo" }, + { label: "Wuxia", value: "Wuxia" }, + { label: "Xianxia", value: "Xianxia" }, + { label: "Xuanhuan", value: "Xuanhuan" }, + { label: "Yaoi", value: "Yaoi" }, + ], + inputType: FilterInputs.Checkbox, + }, +]; +export const fetchImage: Plugin.fetchImage = fetchFile; diff --git a/plugins/russian/japitnoveltl[noveltl].ts b/plugins/russian/japitnoveltl[noveltl].ts new file mode 100644 index 0000000..2e74e3f --- /dev/null +++ b/plugins/russian/japitnoveltl[noveltl].ts @@ -0,0 +1,219 @@ + + import { load as parseHTML } from "cheerio"; + import dayjs from 'dayjs'; + import { fetchFile, fetchApi } from "@libs/fetch"; + import { Novel, Plugin, Chapter } from "@typings/plugin"; + // import { parseMadaraDate } from "@libs/parseMadaraDate"; + // import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; + // import { showToast } from "@libs/showToast"; + import { Filter, FilterInputs } from "@libs/filterInputs"; + import { NovelStatus } from '@libs/novelStatus'; + import { defaultCover } from "@libs/defaultCover"; + + + export const id = "japit.novel"; + export const name = "Japit Comics"; + export const icon = ""; + export const version = "1.0.0"; + export const site = "https://japit.novel.tl"; + export const filters: Filter[] = [{"key":"tags","label":"Тэги","values":[{"label":"Академия","value":5},{"label":"Андроиды","value":37},{"label":"Беззаботный герой","value":115},{"label":"Боги","value":318},{"label":"Богини","value":316},{"label":"Вампиры","value":742},{"label":"Везучий главный герой","value":411},{"label":"Влияние прошлого","value":517},{"label":"Внезапное обогащение","value":691},{"label":"Военные хроники","value":749},{"label":"Война","value":750},{"label":"Воспоминания прошлого","value":283},{"label":"Герои","value":341},{"label":"Героиня красавица","value":81},{"label":"Герой влюбляется первым","value":561},{"label":"Герой красавец","value":329},{"label":"Герой-бесстыдник","value":632},{"label":"Герой-извращенец","value":521},{"label":"Герой-трудяга","value":330},{"label":"R-15","value":570}],"inputType":FilterInputs.Checkbox}]; + exports["protected"] = false; + + const baseUrl = site; + const domain = baseUrl.split('//')[1]; + + export const popularNovels: Plugin.popularNovels = async function ( + page, + { filters } + ) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: + 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: filters?.tags?.length ?? 0 > 0 ? { tags: filters?.tags } : {}, + hostname: domain, + limit: 40, + page, + }, + }), + }); + + const json: any = await result.json(); + const novels: Novel.Item[] = json.data?.projects?.content?.map((novel: any) => ({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: novel.covers[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + })); + + return novels; + }; + + + export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Book', + query: + 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}', + variables: { + hostname: domain, + project: novelUrl, + url: novelUrl, + }, + }), + }); + const json: any = await result.json(); + const novel: Novel.instance = { + url: novelUrl, + name: json.data.project.title, + cover: json.data.project.covers[0]?.url + ? baseUrl + json.data.project.covers[0].url + : defaultCover, + summary: parseHTML(json.data.project.annotation?.text).text(), + author: + json.data.project.persons[0].name.firstName + + ' ' + + json.data.project.persons[0].name.lastName, + status: + json.data.project.translationStatus === 'active' + ? NovelStatus.Ongoing + : NovelStatus.Completed, + }; + + let genres = [] + .concat(json.data.project.tags, json.data.project.genres) + .map((item: any) => item?.nameRu || item?.nameEng) + .filter(item => item); + + if (genres.length > 0) { + novel.genres = genres.join(','); + } + + let novelChapters: Chapter.Item[] = []; + json.data.project.subprojects.content.forEach((work: any) => + work.volumes.content.forEach((volume: any) => + volume.chapters.forEach( + (chapter: any) => + chapter?.published && + novelChapters.push({ + name: volume.shortName + ' ' + chapter.title, + url: novelUrl + '/' + volume.url + '/' + chapter.url, + releaseTime: dayjs(chapter.publishDate).format('LLL'), + }), + ), + ), + ); + novel.chapters = novelChapters; + return novel; + }; + + export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'EReaderData', + query: + 'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}', + variables: { + chapterSelector: { + fullUrl: chapterUrl, + }, + url: chapterUrl, + }, + }), + }); + const json: any = await result.json(); + + return parseHTML(json.data.chapter.text.text).html(); + }; + + export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: + 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: { + query: searchTerm, + }, + hostname: domain, + limit: 40, + page: 1, + }, + }), + }); + const json: any = await result.json(); + const novels: Novel.Item[] = []; + json?.data?.projects?.content?.forEach((novel: any) => + novels.push({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: novel.covers[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + }), + ); + return novels; + }; + + export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url, {}); + }; + \ No newline at end of file diff --git a/plugins/russian/kgnoveltl[noveltl].ts b/plugins/russian/kgnoveltl[noveltl].ts new file mode 100644 index 0000000..1df6b29 --- /dev/null +++ b/plugins/russian/kgnoveltl[noveltl].ts @@ -0,0 +1,219 @@ + + import { load as parseHTML } from "cheerio"; + import dayjs from 'dayjs'; + import { fetchFile, fetchApi } from "@libs/fetch"; + import { Novel, Plugin, Chapter } from "@typings/plugin"; + // import { parseMadaraDate } from "@libs/parseMadaraDate"; + // import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; + // import { showToast } from "@libs/showToast"; + import { Filter, FilterInputs } from "@libs/filterInputs"; + import { NovelStatus } from '@libs/novelStatus'; + import { defaultCover } from "@libs/defaultCover"; + + + export const id = "kg.novel"; + export const name = "Карманная галактика"; + export const icon = ""; + export const version = "1.0.0"; + export const site = "https://kg.novel.tl"; + export const filters: Filter[] = [{"key":"tags","label":"Тэги","values":[{"label":"Алхимия","value":27},{"label":"Андроиды","value":37},{"label":"Анти-рост персонажа","value":25},{"label":"Антигерой","value":43},{"label":"Апатичный главный герой","value":46},{"label":"Аристократия","value":51},{"label":"Асоциальный главный герой","value":42},{"label":"Ассасины","value":61},{"label":"Беззаботный герой","value":115},{"label":"Безработные","value":377},{"label":"Бессмертные","value":359},{"label":"Библиотека","value":393},{"label":"Боги","value":318},{"label":"Боевая академия","value":75},{"label":"Бывший герой","value":290},{"label":"Ведьмы","value":756},{"label":"Везучий главный герой","value":411},{"label":"Верные подданные","value":410},{"label":"Взросление персонажа","value":24},{"label":"R-15","value":570}],"inputType":FilterInputs.Checkbox}]; + exports["protected"] = false; + + const baseUrl = site; + const domain = baseUrl.split('//')[1]; + + export const popularNovels: Plugin.popularNovels = async function ( + page, + { filters } + ) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: + 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: filters?.tags?.length ?? 0 > 0 ? { tags: filters?.tags } : {}, + hostname: domain, + limit: 40, + page, + }, + }), + }); + + const json: any = await result.json(); + const novels: Novel.Item[] = json.data?.projects?.content?.map((novel: any) => ({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: novel.covers[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + })); + + return novels; + }; + + + export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Book', + query: + 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}', + variables: { + hostname: domain, + project: novelUrl, + url: novelUrl, + }, + }), + }); + const json: any = await result.json(); + const novel: Novel.instance = { + url: novelUrl, + name: json.data.project.title, + cover: json.data.project.covers[0]?.url + ? baseUrl + json.data.project.covers[0].url + : defaultCover, + summary: parseHTML(json.data.project.annotation?.text).text(), + author: + json.data.project.persons[0].name.firstName + + ' ' + + json.data.project.persons[0].name.lastName, + status: + json.data.project.translationStatus === 'active' + ? NovelStatus.Ongoing + : NovelStatus.Completed, + }; + + let genres = [] + .concat(json.data.project.tags, json.data.project.genres) + .map((item: any) => item?.nameRu || item?.nameEng) + .filter(item => item); + + if (genres.length > 0) { + novel.genres = genres.join(','); + } + + let novelChapters: Chapter.Item[] = []; + json.data.project.subprojects.content.forEach((work: any) => + work.volumes.content.forEach((volume: any) => + volume.chapters.forEach( + (chapter: any) => + chapter?.published && + novelChapters.push({ + name: volume.shortName + ' ' + chapter.title, + url: novelUrl + '/' + volume.url + '/' + chapter.url, + releaseTime: dayjs(chapter.publishDate).format('LLL'), + }), + ), + ), + ); + novel.chapters = novelChapters; + return novel; + }; + + export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'EReaderData', + query: + 'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}', + variables: { + chapterSelector: { + fullUrl: chapterUrl, + }, + url: chapterUrl, + }, + }), + }); + const json: any = await result.json(); + + return parseHTML(json.data.chapter.text.text).html(); + }; + + export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: + 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: { + query: searchTerm, + }, + hostname: domain, + limit: 40, + page: 1, + }, + }), + }); + const json: any = await result.json(); + const novels: Novel.Item[] = []; + json?.data?.projects?.content?.forEach((novel: any) => + novels.push({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: novel.covers[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + }), + ); + return novels; + }; + + export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url, {}); + }; + \ No newline at end of file diff --git a/plugins/russian/kodnoveltl[noveltl].ts b/plugins/russian/kodnoveltl[noveltl].ts new file mode 100644 index 0000000..f19fd96 --- /dev/null +++ b/plugins/russian/kodnoveltl[noveltl].ts @@ -0,0 +1,219 @@ + + import { load as parseHTML } from "cheerio"; + import dayjs from 'dayjs'; + import { fetchFile, fetchApi } from "@libs/fetch"; + import { Novel, Plugin, Chapter } from "@typings/plugin"; + // import { parseMadaraDate } from "@libs/parseMadaraDate"; + // import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; + // import { showToast } from "@libs/showToast"; + import { Filter, FilterInputs } from "@libs/filterInputs"; + import { NovelStatus } from '@libs/novelStatus'; + import { defaultCover } from "@libs/defaultCover"; + + + export const id = "kod.novel"; + export const name = "K.O.D."; + export const icon = ""; + export const version = "1.0.0"; + export const site = "https://kod.novel.tl"; + export const filters: Filter[] = [{"key":"tags","label":"Тэги","values":[{"label":"Актёрское искусство","value":7},{"label":"Забота о детях","value":127},{"label":"Управление бизнесом","value":109},{"label":"Шоу-бизнес","value":642}],"inputType":FilterInputs.Checkbox}]; + exports["protected"] = false; + + const baseUrl = site; + const domain = baseUrl.split('//')[1]; + + export const popularNovels: Plugin.popularNovels = async function ( + page, + { filters } + ) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: + 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: filters?.tags?.length ?? 0 > 0 ? { tags: filters?.tags } : {}, + hostname: domain, + limit: 40, + page, + }, + }), + }); + + const json: any = await result.json(); + const novels: Novel.Item[] = json.data?.projects?.content?.map((novel: any) => ({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: novel.covers[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + })); + + return novels; + }; + + + export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Book', + query: + 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}', + variables: { + hostname: domain, + project: novelUrl, + url: novelUrl, + }, + }), + }); + const json: any = await result.json(); + const novel: Novel.instance = { + url: novelUrl, + name: json.data.project.title, + cover: json.data.project.covers[0]?.url + ? baseUrl + json.data.project.covers[0].url + : defaultCover, + summary: parseHTML(json.data.project.annotation?.text).text(), + author: + json.data.project.persons[0].name.firstName + + ' ' + + json.data.project.persons[0].name.lastName, + status: + json.data.project.translationStatus === 'active' + ? NovelStatus.Ongoing + : NovelStatus.Completed, + }; + + let genres = [] + .concat(json.data.project.tags, json.data.project.genres) + .map((item: any) => item?.nameRu || item?.nameEng) + .filter(item => item); + + if (genres.length > 0) { + novel.genres = genres.join(','); + } + + let novelChapters: Chapter.Item[] = []; + json.data.project.subprojects.content.forEach((work: any) => + work.volumes.content.forEach((volume: any) => + volume.chapters.forEach( + (chapter: any) => + chapter?.published && + novelChapters.push({ + name: volume.shortName + ' ' + chapter.title, + url: novelUrl + '/' + volume.url + '/' + chapter.url, + releaseTime: dayjs(chapter.publishDate).format('LLL'), + }), + ), + ), + ); + novel.chapters = novelChapters; + return novel; + }; + + export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'EReaderData', + query: + 'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}', + variables: { + chapterSelector: { + fullUrl: chapterUrl, + }, + url: chapterUrl, + }, + }), + }); + const json: any = await result.json(); + + return parseHTML(json.data.chapter.text.text).html(); + }; + + export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: + 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: { + query: searchTerm, + }, + hostname: domain, + limit: 40, + page: 1, + }, + }), + }); + const json: any = await result.json(); + const novels: Novel.Item[] = []; + json?.data?.projects?.content?.forEach((novel: any) => + novels.push({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: novel.covers[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + }), + ); + return novels; + }; + + export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url, {}); + }; + \ No newline at end of file diff --git a/plugins/russian/nightnovelonline[noveltl].ts b/plugins/russian/nightnovelonline[noveltl].ts new file mode 100644 index 0000000..f74a044 --- /dev/null +++ b/plugins/russian/nightnovelonline[noveltl].ts @@ -0,0 +1,219 @@ + + import { load as parseHTML } from "cheerio"; + import dayjs from 'dayjs'; + import { fetchFile, fetchApi } from "@libs/fetch"; + import { Novel, Plugin, Chapter } from "@typings/plugin"; + // import { parseMadaraDate } from "@libs/parseMadaraDate"; + // import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; + // import { showToast } from "@libs/showToast"; + import { Filter, FilterInputs } from "@libs/filterInputs"; + import { NovelStatus } from '@libs/novelStatus'; + import { defaultCover } from "@libs/defaultCover"; + + + export const id = "nightnovel"; + export const name = "Nightnovel"; + export const icon = ""; + export const version = "1.0.0"; + export const site = "https://nightnovel.online"; + export const filters: Filter[] = [{"key":"tags","label":"Тэги","values":[{"label":"Альтернативная реальность","value":30},{"label":"Амнезия","value":31},{"label":"Анал","value":33},{"label":"Ангелы","value":38},{"label":"Аристократия","value":51},{"label":"Артефакты","value":58},{"label":"Ассасины","value":61},{"label":"Банды","value":302},{"label":"БДСМ","value":77},{"label":"Беременность","value":551},{"label":"Боги","value":318},{"label":"Богини","value":316},{"label":"Боевая академия","value":75},{"label":"Божественная сила","value":317},{"label":"Влияние прошлого","value":517},{"label":"Враги становятся союзниками","value":238},{"label":"Вынужденные условия проживания","value":287},{"label":"Герои","value":341},{"label":"Героиня красавица","value":81},{"label":"R-18","value":571}],"inputType":FilterInputs.Checkbox}]; + exports["protected"] = false; + + const baseUrl = site; + const domain = baseUrl.split('//')[1]; + + export const popularNovels: Plugin.popularNovels = async function ( + page, + { filters } + ) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: + 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: filters?.tags?.length ?? 0 > 0 ? { tags: filters?.tags } : {}, + hostname: domain, + limit: 40, + page, + }, + }), + }); + + const json: any = await result.json(); + const novels: Novel.Item[] = json.data?.projects?.content?.map((novel: any) => ({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: novel.covers[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + })); + + return novels; + }; + + + export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Book', + query: + 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}', + variables: { + hostname: domain, + project: novelUrl, + url: novelUrl, + }, + }), + }); + const json: any = await result.json(); + const novel: Novel.instance = { + url: novelUrl, + name: json.data.project.title, + cover: json.data.project.covers[0]?.url + ? baseUrl + json.data.project.covers[0].url + : defaultCover, + summary: parseHTML(json.data.project.annotation?.text).text(), + author: + json.data.project.persons[0].name.firstName + + ' ' + + json.data.project.persons[0].name.lastName, + status: + json.data.project.translationStatus === 'active' + ? NovelStatus.Ongoing + : NovelStatus.Completed, + }; + + let genres = [] + .concat(json.data.project.tags, json.data.project.genres) + .map((item: any) => item?.nameRu || item?.nameEng) + .filter(item => item); + + if (genres.length > 0) { + novel.genres = genres.join(','); + } + + let novelChapters: Chapter.Item[] = []; + json.data.project.subprojects.content.forEach((work: any) => + work.volumes.content.forEach((volume: any) => + volume.chapters.forEach( + (chapter: any) => + chapter?.published && + novelChapters.push({ + name: volume.shortName + ' ' + chapter.title, + url: novelUrl + '/' + volume.url + '/' + chapter.url, + releaseTime: dayjs(chapter.publishDate).format('LLL'), + }), + ), + ), + ); + novel.chapters = novelChapters; + return novel; + }; + + export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'EReaderData', + query: + 'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}', + variables: { + chapterSelector: { + fullUrl: chapterUrl, + }, + url: chapterUrl, + }, + }), + }); + const json: any = await result.json(); + + return parseHTML(json.data.chapter.text.text).html(); + }; + + export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: + 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: { + query: searchTerm, + }, + hostname: domain, + limit: 40, + page: 1, + }, + }), + }); + const json: any = await result.json(); + const novels: Novel.Item[] = []; + json?.data?.projects?.content?.forEach((novel: any) => + novels.push({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: novel.covers[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + }), + ); + return novels; + }; + + export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url, {}); + }; + \ No newline at end of file diff --git a/plugins/russian/novelTL.broken.ts b/plugins/russian/novelTL.broken.ts new file mode 100644 index 0000000..e464179 --- /dev/null +++ b/plugins/russian/novelTL.broken.ts @@ -0,0 +1,867 @@ +import cheerio from "cheerio"; +import dayjs from "dayjs"; +import { fetchApi } from "@libs/fetch"; +import { NovelStatus } from "@libs/novelStatus"; +import { FilterInputs } from "@libs/filterInputs"; +import { defaultCover } from "@libs/defaultCover"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "TL"; +export const name = "NovelTL"; +export const site = "https://novel.tl"; +export const version = "1.0.0"; +export const icon = "src/ru/noveltl/icon.png"; + +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function ( + page, + { filters } +) { + const result = await fetchApi(baseUrl + "/api/site/v2/graphql", { + method: "post", + headers: { + Accept: "application/json, text/plain, */*", + "Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3", + "Content-Type": "application/json", + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: "Projects", + query: "query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title fullUrl covers{url}}}}", + variables: { + filter: { + tags: filters?.tags || [], + genres: filters?.genres || [], + }, + hostname: "novel.tl", + limit: 40, + page, + }, + }), + }); + + const json = await result.json(); + let novels: Novel.Item[] = []; + + json?.data?.projects?.content?.forEach((novel) => + novels.push({ + name: novel.title, + url: "https://" + novel.fullUrl, + cover: novel?.covers?.[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + }) + ); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const result = await fetchApi(baseUrl + "/api/site/v2/graphql", { + method: "post", + headers: { + Accept: "application/json, text/plain, */*", + "Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3", + "Content-Type": "application/json", + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: "Book", + query: 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus fullUrl covers{url}persons(langs:["ru","en","*"],roles:["author","illustrator"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{shortName chapters{title publishDate fullUrl published}}}}}}}', + variables: { + url: novelUrl, + }, + }), + }); + const json = await result.json(); + + let novel: Novel.instance = { + url: novelUrl, + name: json.data.project.title, + cover: json.data.project?.covers?.[0]?.url + ? baseUrl + json.data.project.covers[0].url + : defaultCover, + summary: json.data.project?.annotation?.text, + status: + json.data.project.translationStatus === "active" + ? NovelStatus.Ongoing + : NovelStatus.Completed, + }; + + let genres = [] + .concat(json.data.project.tags, json.data.project.genres) + .map((item) => item?.nameRu || item?.nameEng) + .filter((item) => item); + + if (genres?.length) { + novel.genres = genres.join(", "); + } + + json.data.project.persons.forEach((person) => { + if (person.role == "author" && person.name.firstName) { + novel.author = + person.name.firstName + " " + (person.name?.lastName || ""); + } else if (person.role == "illustrator" && person.name.firstName) { + novel.artist = + person.name.firstName + " " + (person.name?.lastName || ""); + } + }); + + let novelChapters: Chapter.Item[] = []; + json.data.project.subprojects.content.forEach((work) => + work.volumes.content.forEach((volume) => + volume.chapters.forEach( + (chapter) => + chapter?.published && + novelChapters.push({ + name: volume.shortName + " " + chapter.title, + url: "https://" + chapter.fullUrl, + releaseTime: dayjs(chapter.publishDate).format( + "LLL" + ), + }) + ) + ) + ); + + novel.chapters = novelChapters; + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi(baseUrl + "/api/site/v2/graphql", { + method: "post", + headers: { + Accept: "application/json, text/plain, */*", + "Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3", + "Content-Type": "application/json", + }, + referrer: baseUrl, + body: JSON.stringify({ + query: "query($url:String){chapter(chapter:{fullUrl:$url}){text{text}}}", + variables: { + url: chapterUrl, + }, + }), + }); + const json = await result.json(); + + const loadedCheerio = cheerio.load(json.data.chapter.text.text); + loadedCheerio("p > a[href]").each(function () { + let url = baseUrl + loadedCheerio(this).attr("href"); + if (!url.startsWith("http")) { + url = baseUrl + url; + } + loadedCheerio(this).find("picture").remove(); + loadedCheerio(this).removeAttr("href"); + loadedCheerio(``).appendTo(this); + }); + + const chapterText = loadedCheerio.html(); + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const result = await fetchApi(baseUrl + "/api/site/v2/graphql", { + method: "post", + headers: { + Accept: "application/json, text/plain, */*", + "Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3", + "Content-Type": "application/json", + }, + referrer: this.baseUrl, + body: JSON.stringify({ + operationName: "Projects", + query: "query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title fullUrl covers{url}}}}", + variables: { + filter: { + query: searchTerm, + }, + hostname: "novel.tl", + limit: 40, + page: 1, + }, + }), + }); + const json = await result.json(); + + let novels: Novel.Item[] = []; + json?.data?.projects?.content?.forEach((novel) => + novels.push({ + name: novel.title, + url: "https://" + novel.fullUrl, + cover: novel?.covers?.[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + }) + ); + + return novels; +}; + +export const filters = [ + { + key: "tags", + label: "Тэги", + values: [ + { label: "Автоматы", value: 64 }, + { label: "Агрессивные персонажи", value: 26 }, + { label: "Ад", value: 338 }, + { label: "Адаптация манги", value: 8 }, + { label: "Академия", value: 5 }, + { label: "Актёрское искусство", value: 7 }, + { label: "Актеры озвучки", value: 746 }, + { label: "Алхимия", value: 27 }, + { label: "Альтернативная реальность", value: 30 }, + { label: "Амнезия", value: 31 }, + { label: "Анабиоз", value: 170 }, + { label: "Анал", value: 33 }, + { label: "Ангелы", value: 38 }, + { label: "Андроиды", value: 37 }, + { label: "Анти-рост персонажа", value: 25 }, + { label: "Антигерой", value: 43 }, + { label: "Антикварный магазин", value: 44 }, + { label: "Антимагия", value: 41 }, + { label: "Антиутопия", value: 225 }, + { label: "Апатичный главный герой", value: 46 }, + { label: "Апокалипсис", value: 47 }, + { label: "Аристократия", value: 51 }, + { label: "Армия", value: 53 }, + { label: "Артефакты", value: 58 }, + { label: "Асоциальный главный герой", value: 42 }, + { label: "Ассасины", value: 61 }, + { label: "Атмосфера Европы", value: 245 }, + { label: "Банды", value: 302 }, + { label: "БДСМ", value: 77 }, + { label: "Бедный главный герой", value: 541 }, + { label: "Бездушный главный герой", value: 143 }, + { label: "Беззаботный герой", value: 115 }, + { label: "Безответная любовь", value: 213 }, + { label: "Безответная любовь", value: 740 }, + { label: "Безработные", value: 377 }, + { label: "Беременность", value: 551 }, + { label: "Бескорыстная любовь", value: 733 }, + { label: "Бессмертные", value: 359 }, + { label: "Бесстрашный главный герой", value: 274 }, + { label: "Библиотека", value: 393 }, + { label: "Биография", value: 86 }, + { label: "Близнецы", value: 729 }, + { label: "Богачи", value: 753 }, + { label: "Боги", value: 318 }, + { label: "Богини", value: 316 }, + { label: "Богоподобный протагонист", value: 314 }, + { label: "Боевая академия", value: 75 }, + { label: "Боевая парочка", value: 547 }, + { label: "Божественная защита", value: 208 }, + { label: "Божественная сила", value: 317 }, + { label: "Болеющие персонажи", value: 648 }, + { label: "Борьба за власть", value: 548 }, + { label: "Брак по расчету", value: 55 }, + { label: "Братство", value: 106 }, + { label: "Братья или сёстры", value: 646 }, + { label: "Брошенные дети", value: 1 }, + { label: "Бывший герой", value: 290 }, + { label: "Вампиры", value: 742 }, + { label: "Ваншот", value: 496 }, + { label: "Вдали от родителей", value: 3 }, + { label: "Ведьмы", value: 756 }, + { label: "Везучий главный герой", value: 411 }, + { label: "Верные подданные", value: 410 }, + { label: "Вероломные герои/плетение интриг", value: 424 }, + { label: "Взросление персонажа", value: 24 }, + { label: "Видит то, чего остальной люд не в силах", value: 617 }, + { label: "Виртуальная реальность", value: 744 }, + { label: "Владелец магазина", value: 677 }, + { label: "Владелец уникального оружия", value: 736 }, + { label: "Властные персонажи", value: 545 }, + { label: "Влияние прошлого", value: 517 }, + { label: "Внезапное обогащение", value: 691 }, + { label: "Внезапное обретение силы", value: 690 }, + { label: "Военные хроники", value: 749 }, + { label: "Возвращение в родной мир", value: 587 }, + { label: "Война", value: 750 }, + { label: "Вокалоид", value: 745 }, + { label: "Волшебные твари", value: 413 }, + { label: "Воскрешение", value: 586 }, + { label: "Воспитанный главный герой", value: 537 }, + { label: "Воспоминания прошлого", value: 283 }, + { label: "Враги становятся союзниками", value: 238 }, + { label: "Врата в иной мир", value: 303 }, + { label: "Временной парадокс", value: 711 }, + { label: "Вторжение на Землю", value: 228 }, + { label: "Второй шанс", value: 608 }, + { label: "Выживание", value: 695 }, + { label: "Вынужденные условия проживания", value: 287 }, + { label: "Высокомерный герой", value: 56 }, + { label: "Гадание", value: 207 }, + { label: "Галлюцинации", value: 189 }, + { label: "Гарем рабов", value: 656 }, + { label: "Геймеры", value: 301 }, + { label: "Генетические модификации", value: 306 }, + { label: "Герои", value: 341 }, + { label: "Герои непонятного пола", value: 36 }, + { label: "Героиня девушка-сорванец", value: 715 }, + { label: "Героиня красавица", value: 81 }, + { label: "Герой влюбляется первым", value: 561 }, + { label: "Герой использует щит", value: 636 }, + { label: "Герой красавец", value: 329 }, + { label: "Герой со множеством тел", value: 564 }, + { label: "Герой-бесстыдник", value: 632 }, + { label: "Герой-извращенец", value: 521 }, + { label: "Герой-трудяга", value: 330 }, + { label: "Герой-яндере", value: 421 }, + { label: "Гетерохромия", value: 342 }, + { label: "Гильдии", value: 324 }, + { label: "Гиперопека", value: 509 }, + { label: "Главная героиня — женщина", value: 277 }, + { label: "Главный герой - гений", value: 308 }, + { label: "Главный герой - Моб", value: 446 }, + { label: "Главный герой — знаменитость", value: 263 }, + { label: "Главный герой — мужчина", value: 419 }, + { label: "Главный герой в поисках гарема", value: 331 }, + { label: "Главный герой заранее готовится", value: 557 }, + { label: "Главный герой не человек", value: 488 }, + { label: "Главный герой приёмный", value: 20 }, + { label: "Главный герой сразу силён", value: 563 }, + { label: "Гладиаторы", value: 310 }, + { label: "Глухой к любви герой", value: 194 }, + { label: "Гоблины", value: 313 }, + { label: "Големы", value: 319 }, + { label: "Гомункул", value: 347 }, + { label: "Горничные", value: 418 }, + { label: "Государственные интриги", value: 212 }, + { label: "Гринд", value: 322 }, + { label: "Давняя травма", value: 518 }, + { label: "Двойник", value: 97 }, + { label: "Дворецкие", value: 111 }, + { label: "Дворфы", value: 224 }, + { label: "Дворяне", value: 487 }, + { label: "Двоюродные братья или сёстры", value: 162 }, + { label: "Девочки-волшебницы", value: 415 }, + { label: "Демоны", value: 193 }, + { label: "Депрессия", value: 196 }, + { label: "Детектив", value: 198 }, + { label: "Детективное расследование", value: 473 }, + { label: "Детское насилие", value: 125 }, + { label: "Детское обещание", value: 130 }, + { label: "Дискриминация", value: 203 }, + { label: "Длительный разрыв отношений", value: 400 }, + { label: "Домашняя жизнь", value: 45 }, + { label: "Драконы", value: 218 }, + { label: "Древний Китай", value: 34 }, + { label: "Дружба", value: 293 }, + { label: "Друзья детства", value: 128 }, + { label: "Друзья становятся врагами", value: 292 }, + { label: "Духи", value: 673 }, + { label: "Духовная сила", value: 665 }, + { label: "Духовный наставник", value: 671 }, + { label: "Душещипательная история", value: 335 }, + { label: "Души", value: 666 }, + { label: "Дьявольский путь культивации", value: 192 }, + { label: "Ёкай", value: 763 }, + { label: "Есть аниме-адаптация", value: 10 }, + { label: "Есть видеоигра", value: 13 }, + { label: "Есть дорама-адаптация", value: 11 }, + { label: "Есть манга-адаптация", value: 14 }, + { label: "Есть манхва-адаптация", value: 16 }, + { label: "Есть маньхуа-адаптация", value: 15 }, + { label: "Есть фильм", value: 17 }, + { label: "Есть CD дорама-адаптация", value: 12 }, + { label: "Жёсткий главный герой", value: 597 }, + { label: "Жестокие персонажи", value: 4 }, + { label: "Жёстокие персонажи", value: 169 }, + { label: "Животное-спутник", value: 78 }, + { label: "Жизнь в одиночку", value: 396 }, + { label: "Жрецы", value: 555 }, + { label: "Жрицы", value: 554 }, + { label: "Забота о детях", value: 127 }, + { label: "Заботливый главный герой", value: 116 }, + { label: "Загадочное заболевание", value: 471 }, + { label: "Загадочное прошлое родителей", value: 470 }, + { label: "Заговоры", value: 154 }, + { label: "Закалка тела", value: 96 }, + { label: "Закон джунглей", value: 682 }, + { label: "Закулисная борба", value: 603 }, + { label: "Замкнутый главный герой", value: 371 }, + { label: "Запечатанная сила", value: 607 }, + { label: "Затворник", value: 346 }, + { label: "Звери", value: 80 }, + { label: "Зверолюди", value: 39 }, + { label: "Звероподобные", value: 79 }, + { label: "Земледелие", value: 268 }, + { label: "Злобные благородные девы", value: 743 }, + { label: "Зловещие организации", value: 247 }, + { label: "Злой главный герой", value: 248 }, + { label: "Злые боги", value: 246 }, + { label: "Злые религии", value: 249 }, + { label: "Знаменитости", value: 118 }, + { label: "Знания из прошлой жизни", value: 553 }, + { label: "Зомби", value: 767 }, + { label: "Игра на выживание", value: 696 }, + { label: "Игровая рейтинговая система", value: 300 }, + { label: "Из грязи в князи", value: 542 }, + { label: "Известные родители", value: 262 }, + { label: "Изменение внешнего вида", value: 48 }, + { label: "Изнасилование", value: 574 }, + { label: "Инвалидность", value: 202 }, + { label: "Индустриализация", value: 364 }, + { label: "Инженер", value: 241 }, + { label: "Инцест", value: 361 }, + { label: "Искажённая личность", value: 730 }, + { label: "Искусственный интеллект", value: 59 }, + { label: "Исполнение желаний", value: 755 }, + { label: "Каннибализм", value: 113 }, + { label: "Карточные игры", value: 114 }, + { label: "Кладбищенский смотритель", value: 321 }, + { label: "Классика", value: 134 }, + { label: "Книги", value: 99 }, + { label: "Книги навыков", value: 654 }, + { label: "Книжный червь", value: 100 }, + { label: "Коллеги", value: 140 }, + { label: "Колледж/Университет", value: 145 }, + { label: "Командная работа", value: 701 }, + { label: "Комедийный подтекст", value: 147 }, + { label: "Комплекс брата", value: 105 }, + { label: "Комплекс неполноценности", value: 365 }, + { label: "Контракты", value: 155 }, + { label: "Контроль разума", value: 441 }, + { label: "Конфликт в семье", value: 261 }, + { label: "Конфликт верности", value: 153 }, + { label: "Копейщик", value: 668 }, + { label: "Королевская особа", value: 596 }, + { label: "Королевства", value: 382 }, + { label: "Короткий рассказ", value: 638 }, + { label: "Коррупция", value: 157 }, + { label: "Космические Войны", value: 158 }, + { label: "Космос", value: 507 }, + { label: "Косплей", value: 159 }, + { label: "Кража способностей", value: 2 }, + { label: "Крафтинг", value: 164 }, + { label: "Кризис личности", value: 357 }, + { label: "Кровь", value: 320 }, + { label: "Кружки, клубы", value: 138 }, + { label: "Кудэрэ", value: 384 }, + { label: "Кузнец", value: 90 }, + { label: "Кукловоды", value: 567 }, + { label: "Куклы/Марионетки", value: 211 }, + { label: "Кулинария", value: 156 }, + { label: "Культивация", value: 171 }, + { label: "Куннилингус", value: 172 }, + { label: "Легенды", value: 391 }, + { label: "Легкая жизнь", value: 229 }, + { label: "Лекари", value: 334 }, + { label: "Ленивый главный герой", value: 389 }, + { label: "Лидерство", value: 390 }, + { label: "Любит персонажа младше себя", value: 765 }, + { label: "Любит персонажа старше себя", value: 494 }, + { label: "Любовное соперничество", value: 406 }, + { label: "Любовные треугольники", value: 407 }, + { label: "Любовь детства", value: 129 }, + { label: "Любовь с первого взгляда", value: 404 }, + { label: "Любящие родители", value: 215 }, + { label: "Маги", value: 757 }, + { label: "Магические технологии", value: 417 }, + { label: "Магия", value: 412 }, + { label: "Магия призыва", value: 694 }, + { label: "Маленькие девочки", value: 397 }, + { label: "Мангака", value: 423 }, + { label: "Маниакальная любовь", value: 492 }, + { label: "Марти Сью", value: 508 }, + { label: "Мастер подземелий", value: 222 }, + { label: "Мастурбация", value: 432 }, + { label: "Матриархат", value: 433 }, + { label: "Межпространственные путешествия", value: 370 }, + { label: "Менеджмент", value: 422 }, + { label: "Месть", value: 588 }, + { label: "Мечи и магия", value: 697 }, + { label: "Мечники", value: 698 }, + { label: "Миленькие дети", value: 176 }, + { label: "Милитаризм", value: 439 }, + { label: "Миловидная история", value: 178 }, + { label: "Милый главный герой", value: 177 }, + { label: "Минет", value: 275 }, + { label: "Мировое древо", value: 760 }, + { label: "Миролюбивый главный герой", value: 510 }, + { label: "Мифология", value: 475 }, + { label: "Младшие сёстры", value: 766 }, + { label: "ММОРПГ", value: 445 }, + { label: "Множество временных линий", value: 463 }, + { label: "Модели", value: 447 }, + { label: "Молод не по годам", value: 49 }, + { label: "Монстры", value: 454 }, + { label: "Мрачность", value: 183 }, + { label: "Музыка", value: 466 }, + { label: "Музыкальные группы", value: 68 }, + { label: "Мутации", value: 468 }, + { label: "На волосок от смерти", value: 479 }, + { label: "На все руки мастер", value: 374 }, + { label: "Навыки имеют стоимость", value: 150 }, + { label: "Нагота", value: 490 }, + { label: "Наездники на драконах", value: 216 }, + { label: "Наёмники", value: 437 }, + { label: "Наивный главный герой", value: 476 }, + { label: "Наследие", value: 366 }, + { label: "Настоящая любовь", value: 200 }, + { label: "Настоящий гарем", value: 540 }, + { label: "Небесное испытание", value: 337 }, + { label: "Невезучий главный герой", value: 738 }, + { label: "Недоверчивый протагонист", value: 206 }, + { label: "Недооцененный главный герой", value: 734 }, + { label: "Недопонимание", value: 444 }, + { label: "Некромант", value: 480 }, + { label: "Нелинейное повествование", value: 489 }, + { label: "Неловкий главный герой", value: 67 }, + { label: "Нелюбимый протагонист", value: 333 }, + { label: "Немой персонаж", value: 469 }, + { label: "Ненадежный рассказчик", value: 739 }, + { label: "Непримечательный герой", value: 65 }, + { label: "Нерешительный протагонист", value: 363 }, + { label: "Несгибаемый главный герой", value: 676 }, + { label: "Несколько главных героев", value: 460 }, + { label: "Несколько миров", value: 461 }, + { label: "Несколько перерожденцев", value: 462 }, + { label: "Несколько попаданцев", value: 464 }, + { label: "Несравненная скорость культивации", value: 269 }, + { label: "Нехватка/отсутствие здравого смысла", value: 385 }, + { label: "Нечестный протагонист", value: 205 }, + { label: "НИИТ", value: 481 }, + { label: "Ниндзя", value: 486 }, + { label: "Обмен телами", value: 95 }, + { label: "Оборотни", value: 754 }, + { label: "Объект любви влюбился первым", value: 405 }, + { label: "Объект любви популярен", value: 543 }, + { label: "Огнестрельное оружие", value: 280 }, + { label: "Ограничение на время жизни", value: 394 }, + { label: "Одержимость", value: 544 }, + { label: "Один родитель", value: 651 }, + { label: "Одинокий главный герой", value: 399 }, + { label: "Одиночество", value: 398 }, + { label: "Одно тело на двоих", value: 634 }, + { label: "Око силы", value: 253 }, + { label: "Оммёдзи", value: 498 }, + { label: "Организованная преступность", value: 500 }, + { label: "Оргия", value: 501 }, + { label: "Орки", value: 499 }, + { label: "Основано на аниме", value: 74 }, + { label: "Основано на видеоигре", value: 72 }, + { label: "Основано на визуальной новелле", value: 73 }, + { label: "Основано на песне", value: 70 }, + { label: "Основано на фильме", value: 69 }, + { label: "Осторожный главный герой", value: 117 }, + { label: "От ненависти до любви один шаг", value: 239 }, + { label: "От сильного с сильнейшему", value: 684 }, + { label: "От слабого к сильному", value: 752 }, + { label: "Отаку", value: 503 }, + { label: "Отзывчивая любовь", value: 380 }, + { label: "Отзывчивый главный герой", value: 339 }, + { label: "Отношение учитель-ученик", value: 430 }, + { label: "Отношения бог-человек", value: 315 }, + { label: "Отношения между учителем и учеником", value: 687 }, + { label: "Отношения начальник-подчинённый", value: 101 }, + { label: "Отношения против воли", value: 286 }, + { label: "Отношения с нечеловеком", value: 353 }, + { label: "Отношения сэмпай-кохай", value: 621 }, + { label: "Отношения хозяин-слуга", value: 431 }, + { label: "Отомэ-игры", value: 504 }, + { label: "ОТП", value: 562 }, + { label: "Отшельники", value: 505 }, + { label: "Офисный работник", value: 600 }, + { label: "Официанты", value: 748 }, + { label: "Охотники", value: 355 }, + { label: "Падшие ангелы", value: 255 }, + { label: "Падшие дворяне", value: 256 }, + { label: "Пайзури", value: 511 }, + { label: "Паразиты", value: 513 }, + { label: "Параллельные миры", value: 512 }, + { label: "Парк развлечений", value: 32 }, + { label: "Пародия", value: 515 }, + { label: "Перемещение между мирами", value: 758 }, + { label: "Переодевание", value: 167 }, + { label: "Переплавка пилюль", value: 529 }, + { label: "Перерождение в девушку", value: 420 }, + { label: "Перерождение в другом мире", value: 580 }, + { label: "Перерождение в другом мире", value: 720 }, + { label: "Перерождение в игровом мире", value: 579 }, + { label: "Перерождение в монстра", value: 577 }, + { label: "Персонажи с мазохистскими наклонностями", value: 429 }, + { label: "Персонажи с садистскими наклонностями", value: 598 }, + { label: "Петля времени", value: 709 }, + { label: "Петтинг", value: 328 }, + { label: "Писатели", value: 761 }, + { label: "Питомцы", value: 522 }, + { label: "Племенное общество", value: 726 }, + { label: "Повелитель демонов", value: 191 }, + { label: "Повествование от нескольких лиц", value: 459 }, + { label: "Поглощение навыков", value: 653 }, + { label: "Подземелья", value: 223 }, + { label: "Поздно начинающаяся романтическая линия", value: 387 }, + { label: "Политика", value: 538 }, + { label: "Полулюди", value: 190 }, + { label: "Помолвка", value: 240 }, + { label: "Попаданец в другой мир", value: 723 }, + { label: "Попаданец в игровой мир", value: 722 }, + { label: "Постапокалипсис", value: 546 }, + { label: "Постижение Дао", value: 181 }, + { label: "Потеря девственности", value: 282 }, + { label: "Потустороннее пространство", value: 416 }, + { label: "Похищения", value: 379 }, + { label: "Прагматичный главный герой", value: 549 }, + { label: "Предательство", value: 83 }, + { label: "Предвидение", value: 550 }, + { label: "Преследователи", value: 674 }, + { label: "Преступление", value: 165 }, + { label: "Преступность", value: 166 }, + { label: "Приёмные дети", value: 19 }, + { label: "Призванный герой", value: 693 }, + { label: "Призраки", value: 309 }, + { label: "Приключения", value: 22 }, + { label: "Принуждение", value: 108 }, + { label: "Приручатель", value: 453 }, + { label: "Прислуга", value: 625 }, + { label: "Приставучий любовник", value: 136 }, + { label: "Притворная любовь", value: 552 }, + { label: "Пришельцы", value: 28 }, + { label: "Программист", value: 558 }, + { label: "Произведение - обладатель наград", value: 66 }, + { label: "Проклятия", value: 175 }, + { label: "Промывка мозгов", value: 102 }, + { label: "Пропуск времени", value: 712 }, + { label: "Пророчества", value: 559 }, + { label: "Протагонист в очках", value: 312 }, + { label: "Протагонист раб", value: 657 }, + { label: "Психокинез", value: 565 }, + { label: "Психопаты", value: 566 }, + { label: "Путешествия", value: 759 }, + { label: "Путешествия во времени", value: 713 }, + { label: "Пытки", value: 716 }, + { label: "Рабы", value: 658 }, + { label: "Развитие отношений", value: 160 }, + { label: "Развитие персонажа", value: 119 }, + { label: "Раздвоение личности", value: 458 }, + { label: "Размеренная романтика", value: 661 }, + { label: "Разница в статусе", value: 201 }, + { label: "Разорванная помолвка", value: 104 }, + { label: "Разумные предметы", value: 622 }, + { label: "Рай", value: 336 }, + { label: "Раняя романтика", value: 227 }, + { label: "Расизм", value: 573 }, + { label: "Расследования", value: 372 }, + { label: "Ребенок — главный герой", value: 126 }, + { label: "Реверс-гарем", value: 589 }, + { label: "Реверс-изнасилование", value: 590 }, + { label: "Резкая смена характера", value: 520 }, + { label: "Реинкарнация", value: 581 }, + { label: "Религии", value: 582 }, + { label: "Ресторан", value: 585 }, + { label: "Решительный главный герой", value: 199 }, + { label: "Робкий протагонист", value: 714 }, + { label: "Родительсткий комплекс", value: 514 }, + { label: "Родные не связанные кровью", value: 647 }, + { label: "Родословные", value: 94 }, + { label: "РПГ-система", value: 392 }, + { label: "Рыцари", value: 383 }, + { label: "Самовлюбленный главный герой", value: 477 }, + { label: "Самоубийства", value: 692 }, + { label: "Самураи", value: 601 }, + { label: "Сборник коротких историй", value: 144 }, + { label: "Свадьба", value: 426 }, + { label: "Связанные сюжетные линии", value: 369 }, + { label: "Святые", value: 599 }, + { label: "Секреты", value: 614 }, + { label: "Секс втроём", value: 707 }, + { label: "Секс-рабыня", value: 629 }, + { label: "Семейная любовь", value: 257 }, + { label: "Семь добродетелей", value: 627 }, + { label: "Семь смертных грехов", value: 626 }, + { label: "Серийные убийцы", value: 624 }, + { label: "Сикигами", value: 637 }, + { label: "Сильная любовь от старших", value: 214 }, + { label: "Синдром восьмиклассника", value: 132 }, + { label: "Синдром сестры", value: 652 }, + { label: "Сироты", value: 502 }, + { label: "Системный администратор", value: 699 }, + { label: "Скромный главный герой", value: 409 }, + { label: "Скрытые романтические отношения", value: 23 }, + { label: "Скрытые способности", value: 343 }, + { label: "Скряга", value: 450 }, + { label: "Скульпторы", value: 606 }, + { label: "Слабая романтика", value: 688 }, + { label: "Слабый главный герой", value: 751 }, + { label: "Сложные семейные отношения", value: 149 }, + { label: "Смена расы", value: 572 }, + { label: "Смертельное заболевание", value: 704 }, + { label: "Смерть", value: 185 }, + { label: "Смерть любимых", value: 186 }, + { label: "Современность", value: 448 }, + { label: "Современные знания в слаборазвитых мирах", value: 449 }, + { label: "Сожительство", value: 141 }, + { label: "Создание артефактов", value: 57 }, + { label: "Создание клана", value: 133 }, + { label: "Создание королевства", value: 381 }, + { label: "Создание навыков", value: 655 }, + { label: "Сокрытие истинных способностей", value: 344 }, + { label: "Сокрытие личности", value: 345 }, + { label: "Солдаты", value: 664 }, + { label: "Сон", value: 659 }, + { label: "Соседи по комнате", value: 595 }, + { label: "Социальные изгои", value: 663 }, + { label: "Спасение мира", value: 602 }, + { label: "Спорящая пара", value: 84 }, + { label: "Способность перевоплощения", value: 719 }, + { label: "Средневековье", value: 436 }, + { label: "Стеснительные персонажи", value: 643 }, + { label: "Стокгольмский синдром", value: 575 }, + { label: "Стокгольмский синдром", value: 675 }, + { label: "Стратег", value: 681 }, + { label: "Стратегические битвы", value: 680 }, + { label: "Стрелки", value: 325 }, + { label: "Стрельба из лука", value: 50 }, + { label: "Студенческий совет", value: 686 }, + { label: "Судьба", value: 197 }, + { label: "Суккубы", value: 689 }, + { label: "Суперспособности", value: 669 }, + { label: "Суровая тренировка", value: 332 }, + { label: "Сцены насилия", value: 195 }, + { label: "Таинственное прошлое", value: 472 }, + { label: "Тайная личность", value: 610 }, + { label: "Тайные организации", value: 611 }, + { label: "Телохранители", value: 98 }, + { label: "Тентакли", value: 703 }, + { label: "Террористы", value: 705 }, + { label: "Технологический разрыв", value: 702 }, + { label: "Тихие герои", value: 568 }, + { label: "Толстый протагонист", value: 271 }, + { label: "Торговцы", value: 438 }, + { label: "Травник", value: 340 }, + { label: "Трагичное прошлое", value: 718 }, + { label: "Трап", value: 725 }, + { label: "Триллер", value: 708 }, + { label: "Турнир", value: 76 }, + { label: "Тюрьма", value: 556 }, + { label: "Убийства", value: 465 }, + { label: "Уверенный в себе главный герой", value: 151 }, + { label: "Умная пара", value: 662 }, + { label: "Умный главный герой", value: 135 }, + { label: "Уникальная техника культивации", value: 735 }, + { label: "Уникальные оружия", value: 737 }, + { label: "Управление бизнесом", value: 109 }, + { label: "Управление временем", value: 710 }, + { label: "Управление пространством", value: 667 }, + { label: "Ускоренный рост", value: 6 }, + { label: "Учёные", value: 605 }, + { label: "Учителя", value: 700 }, + { label: "Фамильяры", value: 258 }, + { label: "Фанатизм", value: 264 }, + { label: "Фармацевт", value: 523 }, + { label: "Феи", value: 254 }, + { label: "Фениксы", value: 526 }, + { label: "Фетиш на грудь", value: 103 }, + { label: "Фетиш на очки", value: 311 }, + { label: "Философия", value: 524 }, + { label: "Фобии/Страхи", value: 525 }, + { label: "Фольклор", value: 285 }, + { label: "Формирование армии", value: 54 }, + { label: "Футанари", value: 295 }, + { label: "Футуристический мир", value: 296 }, + { label: "Фэнтези", value: 267 }, + { label: "Фэнтезийные существа", value: 266 }, + { label: "Хакеры", value: 326 }, + { label: "Харизматичный протагонист", value: 120 }, + { label: "Хитрый главный герой", value: 173 }, + { label: "Хладнокровный главный герой", value: 112 }, + { label: "Художники", value: 60 }, + { label: "Хулиганы", value: 188 }, + { label: "Цундере", value: 728 }, + { label: "Чат", value: 122 }, + { label: "Человек-оружие", value: 352 }, + { label: "Человекоподобный главный герой", value: 354 }, + { label: "Честный главный герой", value: 348 }, + { label: "Четкие любовные убеждения", value: 683 }, + { label: "Читы", value: 123 }, + { label: "Чудаковатые герои", value: 569 }, + { label: "Чужие воспоминания", value: 721 }, + { label: "Шантаж", value: 89 }, + { label: "Шеф-повар", value: 124 }, + { label: "Школа для девочек", value: 29 }, + { label: "Шоу-бизнес", value: 642 }, + { label: "Шпионы", value: 670 }, + { label: "Эволюция", value: 250 }, + { label: "Эгоистичный главный герой", value: 618 }, + { label: "Экзорцизм", value: 252 }, + { label: "Экономика", value: 230 }, + { label: "Эксгибиционизм", value: 251 }, + { label: "Эксперименты на людях", value: 351 }, + { label: "Элементальная магия", value: 234 }, + { label: "Элементы игрового мира", value: 299 }, + { label: "Элементы романтики", value: 594 }, + { label: "Элементы юри", value: 640 }, + { label: "Элементы яоя", value: 641 }, + { label: "Эльфы", value: 235 }, + { label: "Эпизодичность", value: 243 }, + { label: "Язвительные персонажи", value: 635 }, + { label: "Языковой барьер", value: 386 }, + { label: "Яндере", value: 762 }, + { label: "Яойщица", value: 294 }, + { label: "Японские силы самообороны", value: 378 }, + { label: "R-15", value: 570 }, + { label: "R-18", value: 571 }, + ], + inputType: FilterInputs.Checkbox, + }, + { + key: "genres", + label: "Жанры", + values: [ + { label: "16+", value: 1 }, + { label: "18+", value: 2 }, + { label: "Боевик", value: 3 }, + { label: "Боевые искусства", value: 4 }, + { label: "Вампиры", value: 47 }, + { label: "Военное", value: 44 }, + { label: "Гарем", value: 5 }, + { label: "Демоны", value: 48 }, + { label: "Детектив", value: 6 }, + { label: "Дзёсей", value: 50 }, + { label: "Драма", value: 7 }, + { label: "Игры", value: 45 }, + { label: "Историческое", value: 8 }, + { label: "Киберпанк", value: 9 }, + { label: "Комедия", value: 10 }, + { label: "Космос", value: 46 }, + { label: "Магия", value: 42 }, + { label: "Махо-сёдзё", value: 12 }, + { label: "Меха", value: 13 }, + { label: "Мистика", value: 14 }, + { label: "Музыка", value: 49 }, + { label: "Научная фантастика", value: 15 }, + { label: "Пародия", value: 16 }, + { label: "Повседневность", value: 17 }, + { label: "Приключения", value: 18 }, + { label: "Психологическое", value: 19 }, + { label: "Романтика", value: 20 }, + { label: "Сверхъестественное", value: 21 }, + { label: "Сёдзё", value: 22 }, + { label: "Сёдзё-ай", value: 23 }, + { label: "Сёнэн", value: 24 }, + { label: "Сёнэн-ай", value: 25 }, + { label: "Смена пола", value: 26 }, + { label: "Современность", value: 27 }, + { label: "Спорт", value: 28 }, + { label: "Супер сила", value: 43 }, + { label: "Сэйнэн", value: 29 }, + { label: "Трагедия", value: 32 }, + { label: "Триллер", value: 33 }, + { label: "Ужасы", value: 34 }, + { label: "Уся", value: 35 }, + { label: "Фэнтези", value: 36 }, + { label: "Школьная жизнь", value: 37 }, + { label: "Этти", value: 38 }, + { label: "Юри", value: 39 }, + { label: "Яой", value: 40 }, + { label: "LitRPG", value: 41 }, + ], + inputType: FilterInputs.Checkbox, + }, +]; diff --git a/plugins/russian/novelTL.js b/plugins/russian/novelTL.js deleted file mode 100644 index ac32f8c..0000000 --- a/plugins/russian/novelTL.js +++ /dev/null @@ -1,876 +0,0 @@ -const cheerio = require("cheerio"); -const dayjs = require("dayjs"); -const fetchApi = require("@libs/fetchApi"); -const fetchFile = require("@libs/fetchFile"); -const languages = require("@libs/languages"); -const Status = require("@libs/novelStatus"); -const FilterInputs = require("@libs/filterInputs"); -const defaultCoverUri = - "https://github.com/LNReader/lnreader-sources/blob/main/icons/src/coverNotAvailable.jpg?raw=true"; - -const pluginId = "TL"; -const sourceName = "NovelTL"; -const baseUrl = "https://novel.tl"; - -async function popularNovels(page, { filters }) { - const result = await fetchApi(baseUrl + "/api/site/v2/graphql", { - method: "post", - headers: { - Accept: "application/json, text/plain, */*", - "Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3", - "Content-Type": "application/json", - }, - referrer: baseUrl, - body: JSON.stringify({ - operationName: "Projects", - query: - "query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title fullUrl covers{url}}}}", - variables: { - filter: { - tags: filters?.tags || [], - genres: filters?.genres || [], - }, - hostname: "novel.tl", - limit: 40, - page, - }, - }), - }); - - const json = await result.json(); - let novels = []; - - json?.data?.projects?.content?.forEach((novel) => - novels.push({ - name: novel.title, - url: "https://" + novel.fullUrl, - cover: novel?.covers?.[0]?.url - ? baseUrl + novel.covers[0].url - : defaultCoverUri, - }) - ); - - return novels; -} - -async function parseNovelAndChapters(novelUrl) { - const result = await fetchApi(baseUrl + "/api/site/v2/graphql", { - method: "post", - headers: { - Accept: "application/json, text/plain, */*", - "Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3", - "Content-Type": "application/json", - }, - referrer: baseUrl, - body: JSON.stringify({ - operationName: "Book", - query: - 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus fullUrl covers{url}persons(langs:["ru","en","*"],roles:["author","illustrator"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{shortName chapters{title publishDate fullUrl published}}}}}}}', - variables: { - url: novelUrl, - }, - }), - }); - const json = await result.json(); - - let novel = { - url: novelUrl, - name: json.data.project.title, - cover: json.data.project?.covers?.[0]?.url - ? baseUrl + json.data.project.covers[0].url - : defaultCoverUri, - summary: json.data.project?.annotation?.text, - status: - json.data.project.translationStatus === "active" - ? Status.Ongoing - : Status.Completed, - }; - - let genres = [] - .concat(json.data.project.tags, json.data.project.genres) - .map((item) => item?.nameRu || item?.nameEng) - .filter((item) => item); - - if (genres?.length) { - novel.genres = genres.join(", "); - } - - json.data.project.persons.forEach((person) => { - if (person.role == "author" && person.name.firstName) { - novel.author = - person.name.firstName + " " + (person.name?.lastName || ""); - } else if (person.role == "illustrator" && person.name.firstName) { - novel.artist = - person.name.firstName + " " + (person.name?.lastName || ""); - } - }); - - let novelChapters = []; - json.data.project.subprojects.content.forEach((work) => - work.volumes.content.forEach((volume) => - volume.chapters.forEach((chapter) => - chapter?.published && - novelChapters.push({ - name: volume.shortName + " " + chapter.title, - url: "https://" + chapter.fullUrl, - releaseTime: dayjs(chapter.publishDate).format("LLL"), - }) - ) - ) - ); - - novel.chapters = novelChapters; - return novel; -} - -async function parseChapter(chapterUrl) { - const result = await fetchApi(baseUrl + "/api/site/v2/graphql", { - method: "post", - headers: { - Accept: "application/json, text/plain, */*", - "Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3", - "Content-Type": "application/json", - }, - referrer: baseUrl, - body: JSON.stringify({ - query: "query($url:String){chapter(chapter:{fullUrl:$url}){text{text}}}", - variables: { - url: chapterUrl, - }, - }), - }); - const json = await result.json(); - - const loadedCheerio = cheerio.load(json.data.chapter.text.text); - loadedCheerio("p > a[href]").each(function () { - let url = baseUrl + loadedCheerio(this).attr("href"); - if (!url.startsWith("http")) { - url = baseUrl + url; - } - loadedCheerio(this).find("picture").remove(); - loadedCheerio(this).removeAttr("href"); - loadedCheerio(``).appendTo(this); - }); - - const chapterText = loadedCheerio.html(); - return chapterText; -} - -async function searchNovels(searchTerm) { - const result = await fetchApi(baseUrl + "/api/site/v2/graphql", { - method: "post", - headers: { - Accept: "application/json, text/plain, */*", - "Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3", - "Content-Type": "application/json", - }, - referrer: this.baseUrl, - body: JSON.stringify({ - operationName: "Projects", - query: - "query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title fullUrl covers{url}}}}", - variables: { - filter: { - query: searchTerm, - }, - hostname: "novel.tl", - limit: 40, - page: 1, - }, - }), - }); - const json = await result.json(); - - let novels = []; - json?.data?.projects?.content?.forEach((novel) => - novels.push({ - name: novel.title, - url: "https://" + novel.fullUrl, - cover: novel?.covers?.[0]?.url - ? baseUrl + novel.covers[0].url - : defaultCoverUri, - }) - ); - - return novels; -} - -const filters = [ - { - key: "tags", - label: "Тэги", - values: [ - { label: "Автоматы", value: 64 }, - { label: "Агрессивные персонажи", value: 26 }, - { label: "Ад", value: 338 }, - { label: "Адаптация манги", value: 8 }, - { label: "Академия", value: 5 }, - { label: "Актёрское искусство", value: 7 }, - { label: "Актеры озвучки", value: 746 }, - { label: "Алхимия", value: 27 }, - { label: "Альтернативная реальность", value: 30 }, - { label: "Амнезия", value: 31 }, - { label: "Анабиоз", value: 170 }, - { label: "Анал", value: 33 }, - { label: "Ангелы", value: 38 }, - { label: "Андроиды", value: 37 }, - { label: "Анти-рост персонажа", value: 25 }, - { label: "Антигерой", value: 43 }, - { label: "Антикварный магазин", value: 44 }, - { label: "Антимагия", value: 41 }, - { label: "Антиутопия", value: 225 }, - { label: "Апатичный главный герой", value: 46 }, - { label: "Апокалипсис", value: 47 }, - { label: "Аристократия", value: 51 }, - { label: "Армия", value: 53 }, - { label: "Артефакты", value: 58 }, - { label: "Асоциальный главный герой", value: 42 }, - { label: "Ассасины", value: 61 }, - { label: "Атмосфера Европы", value: 245 }, - { label: "Банды", value: 302 }, - { label: "БДСМ", value: 77 }, - { label: "Бедный главный герой", value: 541 }, - { label: "Бездушный главный герой", value: 143 }, - { label: "Беззаботный герой", value: 115 }, - { label: "Безответная любовь", value: 213 }, - { label: "Безответная любовь", value: 740 }, - { label: "Безработные", value: 377 }, - { label: "Беременность", value: 551 }, - { label: "Бескорыстная любовь", value: 733 }, - { label: "Бессмертные", value: 359 }, - { label: "Бесстрашный главный герой", value: 274 }, - { label: "Библиотека", value: 393 }, - { label: "Биография", value: 86 }, - { label: "Близнецы", value: 729 }, - { label: "Богачи", value: 753 }, - { label: "Боги", value: 318 }, - { label: "Богини", value: 316 }, - { label: "Богоподобный протагонист", value: 314 }, - { label: "Боевая академия", value: 75 }, - { label: "Боевая парочка", value: 547 }, - { label: "Божественная защита", value: 208 }, - { label: "Божественная сила", value: 317 }, - { label: "Болеющие персонажи", value: 648 }, - { label: "Борьба за власть", value: 548 }, - { label: "Брак по расчету", value: 55 }, - { label: "Братство", value: 106 }, - { label: "Братья или сёстры", value: 646 }, - { label: "Брошенные дети", value: 1 }, - { label: "Бывший герой", value: 290 }, - { label: "Вампиры", value: 742 }, - { label: "Ваншот", value: 496 }, - { label: "Вдали от родителей", value: 3 }, - { label: "Ведьмы", value: 756 }, - { label: "Везучий главный герой", value: 411 }, - { label: "Верные подданные", value: 410 }, - { label: "Вероломные герои/плетение интриг", value: 424 }, - { label: "Взросление персонажа", value: 24 }, - { label: "Видит то, чего остальной люд не в силах", value: 617 }, - { label: "Виртуальная реальность", value: 744 }, - { label: "Владелец магазина", value: 677 }, - { label: "Владелец уникального оружия", value: 736 }, - { label: "Властные персонажи", value: 545 }, - { label: "Влияние прошлого", value: 517 }, - { label: "Внезапное обогащение", value: 691 }, - { label: "Внезапное обретение силы", value: 690 }, - { label: "Военные хроники", value: 749 }, - { label: "Возвращение в родной мир", value: 587 }, - { label: "Война", value: 750 }, - { label: "Вокалоид", value: 745 }, - { label: "Волшебные твари", value: 413 }, - { label: "Воскрешение", value: 586 }, - { label: "Воспитанный главный герой", value: 537 }, - { label: "Воспоминания прошлого", value: 283 }, - { label: "Враги становятся союзниками", value: 238 }, - { label: "Врата в иной мир", value: 303 }, - { label: "Временной парадокс", value: 711 }, - { label: "Вторжение на Землю", value: 228 }, - { label: "Второй шанс", value: 608 }, - { label: "Выживание", value: 695 }, - { label: "Вынужденные условия проживания", value: 287 }, - { label: "Высокомерный герой", value: 56 }, - { label: "Гадание", value: 207 }, - { label: "Галлюцинации", value: 189 }, - { label: "Гарем рабов", value: 656 }, - { label: "Геймеры", value: 301 }, - { label: "Генетические модификации", value: 306 }, - { label: "Герои", value: 341 }, - { label: "Герои непонятного пола", value: 36 }, - { label: "Героиня девушка-сорванец", value: 715 }, - { label: "Героиня красавица", value: 81 }, - { label: "Герой влюбляется первым", value: 561 }, - { label: "Герой использует щит", value: 636 }, - { label: "Герой красавец", value: 329 }, - { label: "Герой со множеством тел", value: 564 }, - { label: "Герой-бесстыдник", value: 632 }, - { label: "Герой-извращенец", value: 521 }, - { label: "Герой-трудяга", value: 330 }, - { label: "Герой-яндере", value: 421 }, - { label: "Гетерохромия", value: 342 }, - { label: "Гильдии", value: 324 }, - { label: "Гиперопека", value: 509 }, - { label: "Главная героиня — женщина", value: 277 }, - { label: "Главный герой - гений", value: 308 }, - { label: "Главный герой - Моб", value: 446 }, - { label: "Главный герой — знаменитость", value: 263 }, - { label: "Главный герой — мужчина", value: 419 }, - { label: "Главный герой в поисках гарема", value: 331 }, - { label: "Главный герой заранее готовится", value: 557 }, - { label: "Главный герой не человек", value: 488 }, - { label: "Главный герой приёмный", value: 20 }, - { label: "Главный герой сразу силён", value: 563 }, - { label: "Гладиаторы", value: 310 }, - { label: "Глухой к любви герой", value: 194 }, - { label: "Гоблины", value: 313 }, - { label: "Големы", value: 319 }, - { label: "Гомункул", value: 347 }, - { label: "Горничные", value: 418 }, - { label: "Государственные интриги", value: 212 }, - { label: "Гринд", value: 322 }, - { label: "Давняя травма", value: 518 }, - { label: "Двойник", value: 97 }, - { label: "Дворецкие", value: 111 }, - { label: "Дворфы", value: 224 }, - { label: "Дворяне", value: 487 }, - { label: "Двоюродные братья или сёстры", value: 162 }, - { label: "Девочки-волшебницы", value: 415 }, - { label: "Демоны", value: 193 }, - { label: "Депрессия", value: 196 }, - { label: "Детектив", value: 198 }, - { label: "Детективное расследование", value: 473 }, - { label: "Детское насилие", value: 125 }, - { label: "Детское обещание", value: 130 }, - { label: "Дискриминация", value: 203 }, - { label: "Длительный разрыв отношений", value: 400 }, - { label: "Домашняя жизнь", value: 45 }, - { label: "Драконы", value: 218 }, - { label: "Древний Китай", value: 34 }, - { label: "Дружба", value: 293 }, - { label: "Друзья детства", value: 128 }, - { label: "Друзья становятся врагами", value: 292 }, - { label: "Духи", value: 673 }, - { label: "Духовная сила", value: 665 }, - { label: "Духовный наставник", value: 671 }, - { label: "Душещипательная история", value: 335 }, - { label: "Души", value: 666 }, - { label: "Дьявольский путь культивации", value: 192 }, - { label: "Ёкай", value: 763 }, - { label: "Есть аниме-адаптация", value: 10 }, - { label: "Есть видеоигра", value: 13 }, - { label: "Есть дорама-адаптация", value: 11 }, - { label: "Есть манга-адаптация", value: 14 }, - { label: "Есть манхва-адаптация", value: 16 }, - { label: "Есть маньхуа-адаптация", value: 15 }, - { label: "Есть фильм", value: 17 }, - { label: "Есть CD дорама-адаптация", value: 12 }, - { label: "Жёсткий главный герой", value: 597 }, - { label: "Жестокие персонажи", value: 4 }, - { label: "Жёстокие персонажи", value: 169 }, - { label: "Животное-спутник", value: 78 }, - { label: "Жизнь в одиночку", value: 396 }, - { label: "Жрецы", value: 555 }, - { label: "Жрицы", value: 554 }, - { label: "Забота о детях", value: 127 }, - { label: "Заботливый главный герой", value: 116 }, - { label: "Загадочное заболевание", value: 471 }, - { label: "Загадочное прошлое родителей", value: 470 }, - { label: "Заговоры", value: 154 }, - { label: "Закалка тела", value: 96 }, - { label: "Закон джунглей", value: 682 }, - { label: "Закулисная борба", value: 603 }, - { label: "Замкнутый главный герой", value: 371 }, - { label: "Запечатанная сила", value: 607 }, - { label: "Затворник", value: 346 }, - { label: "Звери", value: 80 }, - { label: "Зверолюди", value: 39 }, - { label: "Звероподобные", value: 79 }, - { label: "Земледелие", value: 268 }, - { label: "Злобные благородные девы", value: 743 }, - { label: "Зловещие организации", value: 247 }, - { label: "Злой главный герой", value: 248 }, - { label: "Злые боги", value: 246 }, - { label: "Злые религии", value: 249 }, - { label: "Знаменитости", value: 118 }, - { label: "Знания из прошлой жизни", value: 553 }, - { label: "Зомби", value: 767 }, - { label: "Игра на выживание", value: 696 }, - { label: "Игровая рейтинговая система", value: 300 }, - { label: "Из грязи в князи", value: 542 }, - { label: "Известные родители", value: 262 }, - { label: "Изменение внешнего вида", value: 48 }, - { label: "Изнасилование", value: 574 }, - { label: "Инвалидность", value: 202 }, - { label: "Индустриализация", value: 364 }, - { label: "Инженер", value: 241 }, - { label: "Инцест", value: 361 }, - { label: "Искажённая личность", value: 730 }, - { label: "Искусственный интеллект", value: 59 }, - { label: "Исполнение желаний", value: 755 }, - { label: "Каннибализм", value: 113 }, - { label: "Карточные игры", value: 114 }, - { label: "Кладбищенский смотритель", value: 321 }, - { label: "Классика", value: 134 }, - { label: "Книги", value: 99 }, - { label: "Книги навыков", value: 654 }, - { label: "Книжный червь", value: 100 }, - { label: "Коллеги", value: 140 }, - { label: "Колледж/Университет", value: 145 }, - { label: "Командная работа", value: 701 }, - { label: "Комедийный подтекст", value: 147 }, - { label: "Комплекс брата", value: 105 }, - { label: "Комплекс неполноценности", value: 365 }, - { label: "Контракты", value: 155 }, - { label: "Контроль разума", value: 441 }, - { label: "Конфликт в семье", value: 261 }, - { label: "Конфликт верности", value: 153 }, - { label: "Копейщик", value: 668 }, - { label: "Королевская особа", value: 596 }, - { label: "Королевства", value: 382 }, - { label: "Короткий рассказ", value: 638 }, - { label: "Коррупция", value: 157 }, - { label: "Космические Войны", value: 158 }, - { label: "Космос", value: 507 }, - { label: "Косплей", value: 159 }, - { label: "Кража способностей", value: 2 }, - { label: "Крафтинг", value: 164 }, - { label: "Кризис личности", value: 357 }, - { label: "Кровь", value: 320 }, - { label: "Кружки, клубы", value: 138 }, - { label: "Кудэрэ", value: 384 }, - { label: "Кузнец", value: 90 }, - { label: "Кукловоды", value: 567 }, - { label: "Куклы/Марионетки", value: 211 }, - { label: "Кулинария", value: 156 }, - { label: "Культивация", value: 171 }, - { label: "Куннилингус", value: 172 }, - { label: "Легенды", value: 391 }, - { label: "Легкая жизнь", value: 229 }, - { label: "Лекари", value: 334 }, - { label: "Ленивый главный герой", value: 389 }, - { label: "Лидерство", value: 390 }, - { label: "Любит персонажа младше себя", value: 765 }, - { label: "Любит персонажа старше себя", value: 494 }, - { label: "Любовное соперничество", value: 406 }, - { label: "Любовные треугольники", value: 407 }, - { label: "Любовь детства", value: 129 }, - { label: "Любовь с первого взгляда", value: 404 }, - { label: "Любящие родители", value: 215 }, - { label: "Маги", value: 757 }, - { label: "Магические технологии", value: 417 }, - { label: "Магия", value: 412 }, - { label: "Магия призыва", value: 694 }, - { label: "Маленькие девочки", value: 397 }, - { label: "Мангака", value: 423 }, - { label: "Маниакальная любовь", value: 492 }, - { label: "Марти Сью", value: 508 }, - { label: "Мастер подземелий", value: 222 }, - { label: "Мастурбация", value: 432 }, - { label: "Матриархат", value: 433 }, - { label: "Межпространственные путешествия", value: 370 }, - { label: "Менеджмент", value: 422 }, - { label: "Месть", value: 588 }, - { label: "Мечи и магия", value: 697 }, - { label: "Мечники", value: 698 }, - { label: "Миленькие дети", value: 176 }, - { label: "Милитаризм", value: 439 }, - { label: "Миловидная история", value: 178 }, - { label: "Милый главный герой", value: 177 }, - { label: "Минет", value: 275 }, - { label: "Мировое древо", value: 760 }, - { label: "Миролюбивый главный герой", value: 510 }, - { label: "Мифология", value: 475 }, - { label: "Младшие сёстры", value: 766 }, - { label: "ММОРПГ", value: 445 }, - { label: "Множество временных линий", value: 463 }, - { label: "Модели", value: 447 }, - { label: "Молод не по годам", value: 49 }, - { label: "Монстры", value: 454 }, - { label: "Мрачность", value: 183 }, - { label: "Музыка", value: 466 }, - { label: "Музыкальные группы", value: 68 }, - { label: "Мутации", value: 468 }, - { label: "На волосок от смерти", value: 479 }, - { label: "На все руки мастер", value: 374 }, - { label: "Навыки имеют стоимость", value: 150 }, - { label: "Нагота", value: 490 }, - { label: "Наездники на драконах", value: 216 }, - { label: "Наёмники", value: 437 }, - { label: "Наивный главный герой", value: 476 }, - { label: "Наследие", value: 366 }, - { label: "Настоящая любовь", value: 200 }, - { label: "Настоящий гарем", value: 540 }, - { label: "Небесное испытание", value: 337 }, - { label: "Невезучий главный герой", value: 738 }, - { label: "Недоверчивый протагонист", value: 206 }, - { label: "Недооцененный главный герой", value: 734 }, - { label: "Недопонимание", value: 444 }, - { label: "Некромант", value: 480 }, - { label: "Нелинейное повествование", value: 489 }, - { label: "Неловкий главный герой", value: 67 }, - { label: "Нелюбимый протагонист", value: 333 }, - { label: "Немой персонаж", value: 469 }, - { label: "Ненадежный рассказчик", value: 739 }, - { label: "Непримечательный герой", value: 65 }, - { label: "Нерешительный протагонист", value: 363 }, - { label: "Несгибаемый главный герой", value: 676 }, - { label: "Несколько главных героев", value: 460 }, - { label: "Несколько миров", value: 461 }, - { label: "Несколько перерожденцев", value: 462 }, - { label: "Несколько попаданцев", value: 464 }, - { label: "Несравненная скорость культивации", value: 269 }, - { label: "Нехватка/отсутствие здравого смысла", value: 385 }, - { label: "Нечестный протагонист", value: 205 }, - { label: "НИИТ", value: 481 }, - { label: "Ниндзя", value: 486 }, - { label: "Обмен телами", value: 95 }, - { label: "Оборотни", value: 754 }, - { label: "Объект любви влюбился первым", value: 405 }, - { label: "Объект любви популярен", value: 543 }, - { label: "Огнестрельное оружие", value: 280 }, - { label: "Ограничение на время жизни", value: 394 }, - { label: "Одержимость", value: 544 }, - { label: "Один родитель", value: 651 }, - { label: "Одинокий главный герой", value: 399 }, - { label: "Одиночество", value: 398 }, - { label: "Одно тело на двоих", value: 634 }, - { label: "Око силы", value: 253 }, - { label: "Оммёдзи", value: 498 }, - { label: "Организованная преступность", value: 500 }, - { label: "Оргия", value: 501 }, - { label: "Орки", value: 499 }, - { label: "Основано на аниме", value: 74 }, - { label: "Основано на видеоигре", value: 72 }, - { label: "Основано на визуальной новелле", value: 73 }, - { label: "Основано на песне", value: 70 }, - { label: "Основано на фильме", value: 69 }, - { label: "Осторожный главный герой", value: 117 }, - { label: "От ненависти до любви один шаг", value: 239 }, - { label: "От сильного с сильнейшему", value: 684 }, - { label: "От слабого к сильному", value: 752 }, - { label: "Отаку", value: 503 }, - { label: "Отзывчивая любовь", value: 380 }, - { label: "Отзывчивый главный герой", value: 339 }, - { label: "Отношение учитель-ученик", value: 430 }, - { label: "Отношения бог-человек", value: 315 }, - { label: "Отношения между учителем и учеником", value: 687 }, - { label: "Отношения начальник-подчинённый", value: 101 }, - { label: "Отношения против воли", value: 286 }, - { label: "Отношения с нечеловеком", value: 353 }, - { label: "Отношения сэмпай-кохай", value: 621 }, - { label: "Отношения хозяин-слуга", value: 431 }, - { label: "Отомэ-игры", value: 504 }, - { label: "ОТП", value: 562 }, - { label: "Отшельники", value: 505 }, - { label: "Офисный работник", value: 600 }, - { label: "Официанты", value: 748 }, - { label: "Охотники", value: 355 }, - { label: "Падшие ангелы", value: 255 }, - { label: "Падшие дворяне", value: 256 }, - { label: "Пайзури", value: 511 }, - { label: "Паразиты", value: 513 }, - { label: "Параллельные миры", value: 512 }, - { label: "Парк развлечений", value: 32 }, - { label: "Пародия", value: 515 }, - { label: "Перемещение между мирами", value: 758 }, - { label: "Переодевание", value: 167 }, - { label: "Переплавка пилюль", value: 529 }, - { label: "Перерождение в девушку", value: 420 }, - { label: "Перерождение в другом мире", value: 580 }, - { label: "Перерождение в другом мире", value: 720 }, - { label: "Перерождение в игровом мире", value: 579 }, - { label: "Перерождение в монстра", value: 577 }, - { label: "Персонажи с мазохистскими наклонностями", value: 429 }, - { label: "Персонажи с садистскими наклонностями", value: 598 }, - { label: "Петля времени", value: 709 }, - { label: "Петтинг", value: 328 }, - { label: "Писатели", value: 761 }, - { label: "Питомцы", value: 522 }, - { label: "Племенное общество", value: 726 }, - { label: "Повелитель демонов", value: 191 }, - { label: "Повествование от нескольких лиц", value: 459 }, - { label: "Поглощение навыков", value: 653 }, - { label: "Подземелья", value: 223 }, - { label: "Поздно начинающаяся романтическая линия", value: 387 }, - { label: "Политика", value: 538 }, - { label: "Полулюди", value: 190 }, - { label: "Помолвка", value: 240 }, - { label: "Попаданец в другой мир", value: 723 }, - { label: "Попаданец в игровой мир", value: 722 }, - { label: "Постапокалипсис", value: 546 }, - { label: "Постижение Дао", value: 181 }, - { label: "Потеря девственности", value: 282 }, - { label: "Потустороннее пространство", value: 416 }, - { label: "Похищения", value: 379 }, - { label: "Прагматичный главный герой", value: 549 }, - { label: "Предательство", value: 83 }, - { label: "Предвидение", value: 550 }, - { label: "Преследователи", value: 674 }, - { label: "Преступление", value: 165 }, - { label: "Преступность", value: 166 }, - { label: "Приёмные дети", value: 19 }, - { label: "Призванный герой", value: 693 }, - { label: "Призраки", value: 309 }, - { label: "Приключения", value: 22 }, - { label: "Принуждение", value: 108 }, - { label: "Приручатель", value: 453 }, - { label: "Прислуга", value: 625 }, - { label: "Приставучий любовник", value: 136 }, - { label: "Притворная любовь", value: 552 }, - { label: "Пришельцы", value: 28 }, - { label: "Программист", value: 558 }, - { label: "Произведение - обладатель наград", value: 66 }, - { label: "Проклятия", value: 175 }, - { label: "Промывка мозгов", value: 102 }, - { label: "Пропуск времени", value: 712 }, - { label: "Пророчества", value: 559 }, - { label: "Протагонист в очках", value: 312 }, - { label: "Протагонист раб", value: 657 }, - { label: "Психокинез", value: 565 }, - { label: "Психопаты", value: 566 }, - { label: "Путешествия", value: 759 }, - { label: "Путешествия во времени", value: 713 }, - { label: "Пытки", value: 716 }, - { label: "Рабы", value: 658 }, - { label: "Развитие отношений", value: 160 }, - { label: "Развитие персонажа", value: 119 }, - { label: "Раздвоение личности", value: 458 }, - { label: "Размеренная романтика", value: 661 }, - { label: "Разница в статусе", value: 201 }, - { label: "Разорванная помолвка", value: 104 }, - { label: "Разумные предметы", value: 622 }, - { label: "Рай", value: 336 }, - { label: "Раняя романтика", value: 227 }, - { label: "Расизм", value: 573 }, - { label: "Расследования", value: 372 }, - { label: "Ребенок — главный герой", value: 126 }, - { label: "Реверс-гарем", value: 589 }, - { label: "Реверс-изнасилование", value: 590 }, - { label: "Резкая смена характера", value: 520 }, - { label: "Реинкарнация", value: 581 }, - { label: "Религии", value: 582 }, - { label: "Ресторан", value: 585 }, - { label: "Решительный главный герой", value: 199 }, - { label: "Робкий протагонист", value: 714 }, - { label: "Родительсткий комплекс", value: 514 }, - { label: "Родные не связанные кровью", value: 647 }, - { label: "Родословные", value: 94 }, - { label: "РПГ-система", value: 392 }, - { label: "Рыцари", value: 383 }, - { label: "Самовлюбленный главный герой", value: 477 }, - { label: "Самоубийства", value: 692 }, - { label: "Самураи", value: 601 }, - { label: "Сборник коротких историй", value: 144 }, - { label: "Свадьба", value: 426 }, - { label: "Связанные сюжетные линии", value: 369 }, - { label: "Святые", value: 599 }, - { label: "Секреты", value: 614 }, - { label: "Секс втроём", value: 707 }, - { label: "Секс-рабыня", value: 629 }, - { label: "Семейная любовь", value: 257 }, - { label: "Семь добродетелей", value: 627 }, - { label: "Семь смертных грехов", value: 626 }, - { label: "Серийные убийцы", value: 624 }, - { label: "Сикигами", value: 637 }, - { label: "Сильная любовь от старших", value: 214 }, - { label: "Синдром восьмиклассника", value: 132 }, - { label: "Синдром сестры", value: 652 }, - { label: "Сироты", value: 502 }, - { label: "Системный администратор", value: 699 }, - { label: "Скромный главный герой", value: 409 }, - { label: "Скрытые романтические отношения", value: 23 }, - { label: "Скрытые способности", value: 343 }, - { label: "Скряга", value: 450 }, - { label: "Скульпторы", value: 606 }, - { label: "Слабая романтика", value: 688 }, - { label: "Слабый главный герой", value: 751 }, - { label: "Сложные семейные отношения", value: 149 }, - { label: "Смена расы", value: 572 }, - { label: "Смертельное заболевание", value: 704 }, - { label: "Смерть", value: 185 }, - { label: "Смерть любимых", value: 186 }, - { label: "Современность", value: 448 }, - { label: "Современные знания в слаборазвитых мирах", value: 449 }, - { label: "Сожительство", value: 141 }, - { label: "Создание артефактов", value: 57 }, - { label: "Создание клана", value: 133 }, - { label: "Создание королевства", value: 381 }, - { label: "Создание навыков", value: 655 }, - { label: "Сокрытие истинных способностей", value: 344 }, - { label: "Сокрытие личности", value: 345 }, - { label: "Солдаты", value: 664 }, - { label: "Сон", value: 659 }, - { label: "Соседи по комнате", value: 595 }, - { label: "Социальные изгои", value: 663 }, - { label: "Спасение мира", value: 602 }, - { label: "Спорящая пара", value: 84 }, - { label: "Способность перевоплощения", value: 719 }, - { label: "Средневековье", value: 436 }, - { label: "Стеснительные персонажи", value: 643 }, - { label: "Стокгольмский синдром", value: 575 }, - { label: "Стокгольмский синдром", value: 675 }, - { label: "Стратег", value: 681 }, - { label: "Стратегические битвы", value: 680 }, - { label: "Стрелки", value: 325 }, - { label: "Стрельба из лука", value: 50 }, - { label: "Студенческий совет", value: 686 }, - { label: "Судьба", value: 197 }, - { label: "Суккубы", value: 689 }, - { label: "Суперспособности", value: 669 }, - { label: "Суровая тренировка", value: 332 }, - { label: "Сцены насилия", value: 195 }, - { label: "Таинственное прошлое", value: 472 }, - { label: "Тайная личность", value: 610 }, - { label: "Тайные организации", value: 611 }, - { label: "Телохранители", value: 98 }, - { label: "Тентакли", value: 703 }, - { label: "Террористы", value: 705 }, - { label: "Технологический разрыв", value: 702 }, - { label: "Тихие герои", value: 568 }, - { label: "Толстый протагонист", value: 271 }, - { label: "Торговцы", value: 438 }, - { label: "Травник", value: 340 }, - { label: "Трагичное прошлое", value: 718 }, - { label: "Трап", value: 725 }, - { label: "Триллер", value: 708 }, - { label: "Турнир", value: 76 }, - { label: "Тюрьма", value: 556 }, - { label: "Убийства", value: 465 }, - { label: "Уверенный в себе главный герой", value: 151 }, - { label: "Умная пара", value: 662 }, - { label: "Умный главный герой", value: 135 }, - { label: "Уникальная техника культивации", value: 735 }, - { label: "Уникальные оружия", value: 737 }, - { label: "Управление бизнесом", value: 109 }, - { label: "Управление временем", value: 710 }, - { label: "Управление пространством", value: 667 }, - { label: "Ускоренный рост", value: 6 }, - { label: "Учёные", value: 605 }, - { label: "Учителя", value: 700 }, - { label: "Фамильяры", value: 258 }, - { label: "Фанатизм", value: 264 }, - { label: "Фармацевт", value: 523 }, - { label: "Феи", value: 254 }, - { label: "Фениксы", value: 526 }, - { label: "Фетиш на грудь", value: 103 }, - { label: "Фетиш на очки", value: 311 }, - { label: "Философия", value: 524 }, - { label: "Фобии/Страхи", value: 525 }, - { label: "Фольклор", value: 285 }, - { label: "Формирование армии", value: 54 }, - { label: "Футанари", value: 295 }, - { label: "Футуристический мир", value: 296 }, - { label: "Фэнтези", value: 267 }, - { label: "Фэнтезийные существа", value: 266 }, - { label: "Хакеры", value: 326 }, - { label: "Харизматичный протагонист", value: 120 }, - { label: "Хитрый главный герой", value: 173 }, - { label: "Хладнокровный главный герой", value: 112 }, - { label: "Художники", value: 60 }, - { label: "Хулиганы", value: 188 }, - { label: "Цундере", value: 728 }, - { label: "Чат", value: 122 }, - { label: "Человек-оружие", value: 352 }, - { label: "Человекоподобный главный герой", value: 354 }, - { label: "Честный главный герой", value: 348 }, - { label: "Четкие любовные убеждения", value: 683 }, - { label: "Читы", value: 123 }, - { label: "Чудаковатые герои", value: 569 }, - { label: "Чужие воспоминания", value: 721 }, - { label: "Шантаж", value: 89 }, - { label: "Шеф-повар", value: 124 }, - { label: "Школа для девочек", value: 29 }, - { label: "Шоу-бизнес", value: 642 }, - { label: "Шпионы", value: 670 }, - { label: "Эволюция", value: 250 }, - { label: "Эгоистичный главный герой", value: 618 }, - { label: "Экзорцизм", value: 252 }, - { label: "Экономика", value: 230 }, - { label: "Эксгибиционизм", value: 251 }, - { label: "Эксперименты на людях", value: 351 }, - { label: "Элементальная магия", value: 234 }, - { label: "Элементы игрового мира", value: 299 }, - { label: "Элементы романтики", value: 594 }, - { label: "Элементы юри", value: 640 }, - { label: "Элементы яоя", value: 641 }, - { label: "Эльфы", value: 235 }, - { label: "Эпизодичность", value: 243 }, - { label: "Язвительные персонажи", value: 635 }, - { label: "Языковой барьер", value: 386 }, - { label: "Яндере", value: 762 }, - { label: "Яойщица", value: 294 }, - { label: "Японские силы самообороны", value: 378 }, - { label: "R-15", value: 570 }, - { label: "R-18", value: 571 }, - ], - inputType: FilterInputs.Checkbox, - }, - { - key: "genres", - label: "Жанры", - values: [ - { label: "16+", value: 1 }, - { label: "18+", value: 2 }, - { label: "Боевик", value: 3 }, - { label: "Боевые искусства", value: 4 }, - { label: "Вампиры", value: 47 }, - { label: "Военное", value: 44 }, - { label: "Гарем", value: 5 }, - { label: "Демоны", value: 48 }, - { label: "Детектив", value: 6 }, - { label: "Дзёсей", value: 50 }, - { label: "Драма", value: 7 }, - { label: "Игры", value: 45 }, - { label: "Историческое", value: 8 }, - { label: "Киберпанк", value: 9 }, - { label: "Комедия", value: 10 }, - { label: "Космос", value: 46 }, - { label: "Магия", value: 42 }, - { label: "Махо-сёдзё", value: 12 }, - { label: "Меха", value: 13 }, - { label: "Мистика", value: 14 }, - { label: "Музыка", value: 49 }, - { label: "Научная фантастика", value: 15 }, - { label: "Пародия", value: 16 }, - { label: "Повседневность", value: 17 }, - { label: "Приключения", value: 18 }, - { label: "Психологическое", value: 19 }, - { label: "Романтика", value: 20 }, - { label: "Сверхъестественное", value: 21 }, - { label: "Сёдзё", value: 22 }, - { label: "Сёдзё-ай", value: 23 }, - { label: "Сёнэн", value: 24 }, - { label: "Сёнэн-ай", value: 25 }, - { label: "Смена пола", value: 26 }, - { label: "Современность", value: 27 }, - { label: "Спорт", value: 28 }, - { label: "Супер сила", value: 43 }, - { label: "Сэйнэн", value: 29 }, - { label: "Трагедия", value: 32 }, - { label: "Триллер", value: 33 }, - { label: "Ужасы", value: 34 }, - { label: "Уся", value: 35 }, - { label: "Фэнтези", value: 36 }, - { label: "Школьная жизнь", value: 37 }, - { label: "Этти", value: 38 }, - { label: "Юри", value: 39 }, - { label: "Яой", value: 40 }, - { label: "LitRPG", value: 41 }, - ], - inputType: FilterInputs.Checkbox, - }, -]; - -module.exports = { - id: pluginId, - name: sourceName, - site: baseUrl, - version: "1.0.0", - lang: languages.Russian, - icon: "src/ru/noveltl/icon.png", - filters, - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; diff --git a/plugins/russian/originalnoveltl[noveltl].ts b/plugins/russian/originalnoveltl[noveltl].ts new file mode 100644 index 0000000..0260e0a --- /dev/null +++ b/plugins/russian/originalnoveltl[noveltl].ts @@ -0,0 +1,219 @@ + + import { load as parseHTML } from "cheerio"; + import dayjs from 'dayjs'; + import { fetchFile, fetchApi } from "@libs/fetch"; + import { Novel, Plugin, Chapter } from "@typings/plugin"; + // import { parseMadaraDate } from "@libs/parseMadaraDate"; + // import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; + // import { showToast } from "@libs/showToast"; + import { Filter, FilterInputs } from "@libs/filterInputs"; + import { NovelStatus } from '@libs/novelStatus'; + import { defaultCover } from "@libs/defaultCover"; + + + export const id = "original.novel"; + export const name = "Авторские работы"; + export const icon = ""; + export const version = "1.0.0"; + export const site = "https://original.novel.tl"; + export const filters: Filter[] = [{"key":"tags","label":"Тэги","values":[{"label":"Агрессивные персонажи","value":26},{"label":"Академия","value":5},{"label":"Андроиды","value":37},{"label":"Антимагия","value":41},{"label":"Аристократия","value":51},{"label":"Артефакты","value":58},{"label":"Брошенные дети","value":1},{"label":"Вероломные герои/плетение интриг","value":424},{"label":"Взросление персонажа","value":24},{"label":"Владелец уникального оружия","value":736},{"label":"Герой-извращенец","value":521},{"label":"Главный герой не человек","value":488},{"label":"Государственные интриги","value":212},{"label":"Душещипательная история","value":335},{"label":"Жестокие персонажи","value":4},{"label":"Зверолюди","value":39},{"label":"Знания из прошлой жизни","value":553},{"label":"Искусственный интеллект","value":59},{"label":"Конфликт в семье","value":261},{"label":"Королевства","value":382}],"inputType":FilterInputs.Checkbox}]; + exports["protected"] = false; + + const baseUrl = site; + const domain = baseUrl.split('//')[1]; + + export const popularNovels: Plugin.popularNovels = async function ( + page, + { filters } + ) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: + 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: filters?.tags?.length ?? 0 > 0 ? { tags: filters?.tags } : {}, + hostname: domain, + limit: 40, + page, + }, + }), + }); + + const json: any = await result.json(); + const novels: Novel.Item[] = json.data?.projects?.content?.map((novel: any) => ({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: novel.covers[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + })); + + return novels; + }; + + + export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Book', + query: + 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}', + variables: { + hostname: domain, + project: novelUrl, + url: novelUrl, + }, + }), + }); + const json: any = await result.json(); + const novel: Novel.instance = { + url: novelUrl, + name: json.data.project.title, + cover: json.data.project.covers[0]?.url + ? baseUrl + json.data.project.covers[0].url + : defaultCover, + summary: parseHTML(json.data.project.annotation?.text).text(), + author: + json.data.project.persons[0].name.firstName + + ' ' + + json.data.project.persons[0].name.lastName, + status: + json.data.project.translationStatus === 'active' + ? NovelStatus.Ongoing + : NovelStatus.Completed, + }; + + let genres = [] + .concat(json.data.project.tags, json.data.project.genres) + .map((item: any) => item?.nameRu || item?.nameEng) + .filter(item => item); + + if (genres.length > 0) { + novel.genres = genres.join(','); + } + + let novelChapters: Chapter.Item[] = []; + json.data.project.subprojects.content.forEach((work: any) => + work.volumes.content.forEach((volume: any) => + volume.chapters.forEach( + (chapter: any) => + chapter?.published && + novelChapters.push({ + name: volume.shortName + ' ' + chapter.title, + url: novelUrl + '/' + volume.url + '/' + chapter.url, + releaseTime: dayjs(chapter.publishDate).format('LLL'), + }), + ), + ), + ); + novel.chapters = novelChapters; + return novel; + }; + + export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'EReaderData', + query: + 'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}', + variables: { + chapterSelector: { + fullUrl: chapterUrl, + }, + url: chapterUrl, + }, + }), + }); + const json: any = await result.json(); + + return parseHTML(json.data.chapter.text.text).html(); + }; + + export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: + 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: { + query: searchTerm, + }, + hostname: domain, + limit: 40, + page: 1, + }, + }), + }); + const json: any = await result.json(); + const novels: Novel.Item[] = []; + json?.data?.projects?.content?.forEach((novel: any) => + novels.push({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: novel.covers[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + }), + ); + return novels; + }; + + export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url, {}); + }; + \ No newline at end of file diff --git a/plugins/russian/ranobehub.broken.ts b/plugins/russian/ranobehub.broken.ts new file mode 100644 index 0000000..2f4f6a7 --- /dev/null +++ b/plugins/russian/ranobehub.broken.ts @@ -0,0 +1,1148 @@ +import dayjs from "dayjs"; +import cheerio from "cheerio"; +import { NovelStatus } from "@libs/novelStatus"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { FilterInputs } from "@libs/filterInputs"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "RNBH.org"; +export const name = "RanobeHub"; +export const version = "1.0.0"; +export const site = "https://ranobehub.org/"; +export const icon = "src/ru/ranobehub/icon.png"; + +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function ( + page, + { showLatestNovels, filters } +) { + let url = baseUrl + `api/search?page=${page}&sort=`; + url += showLatestNovels + ? "last_chapter_at" + : filters?.sort || "computed_rating"; + url += "&status=" + (filters?.status ? filters?.status : "0"); + + if (filters) { + if (filters?.country?.length) { + url += "&country=" + filters?.country.join(","); + } + if (filters.tags && filters.events) { + let tags: string[] = []; + if (Array.isArray(filters.tags)) { + tags.push(...filters.tags); + } else tags.push(filters.tags); + if (Array.isArray(filters.events)) { + tags.push(...filters.events); + } else tags.push(filters.events); + tags = tags.filter((t) => t); + if (tags.length) { + url += "&tags:positive=" + tags.join(","); + } + } + } + + url += "&take=40"; + const result = await fetchApi(url); + const body = await result.json(); + + let novels: Novel.Item[] = []; + body.resource.forEach((novel) => + novels.push({ + name: novel.names.rus, + cover: novel.poster.medium, + url: novel.url, + }) + ); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const novelId = novelUrl + .substring("https://ranobehub.org/ranobe/".length) + .split("-")[0]; + const result = await fetchApi(`${baseUrl}api/ranobe/${novelId}`); + const json = await result.json(); + + let novel: Novel.instance = { + url: json.data.url, + novelUrl, + name: json.data.names.rus, + cover: json.data.posters.medium, + summary: json.data.description, + author: json.data?.authors[0]?.name_eng || "", + status: json.data.status.title.includes("процессе") + ? NovelStatus.Ongoing + : NovelStatus.Completed, + }; + + let tags = [] + .concat(json.data.tags.events, json.data.tags.genres) + .map((item) => item?.names?.rus || item?.names?.eng || item?.title) + .filter((item) => item); + + if (tags.length > 0) { + novel.genres = tags.join(", "); + } + + let novelChapters: Chapter.Item[] = []; + + const fetchChaptersUrl = `${baseUrl}api/ranobe/${novelId}/contents`; + + const chaptersRaw = await fetchApi(fetchChaptersUrl); + const chaptersJSON = await chaptersRaw.json(); + + chaptersJSON.volumes.forEach((volume) => + volume.chapters.forEach((chapter) => + novelChapters.push({ + name: chapter.name, + url: chapter.url, + releaseTime: dayjs( + parseInt(chapter.changed_at, 10) * 1000 + ).format("LLL"), + }) + ) + ); + + novel.chapters = novelChapters; + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi(chapterUrl); + const body = await result.text(); + + let loadedCheerio = cheerio.load(body); + + loadedCheerio(".chapter-hoticons").remove(); + loadedCheerio("div.text:nth-child(1) img").each(function () { + if (!loadedCheerio(this).attr("src")?.startsWith("http")) { + const dataMediaId = loadedCheerio(this).attr("data-media-id"); + loadedCheerio(this).attr( + "src", + `${baseUrl}api/media/${dataMediaId}` + ); + } + }); + + let chapterText = loadedCheerio("div.text:nth-child(1)").html(); + + // Remove script tags + chapterText = chapterText?.replace( + /<\s*script[^>]*>[\s\S]*?<\/script>/gim, + "" + ); + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = `${baseUrl}api/fulltext/global?query=${searchTerm}&take=5`; + + const result = await fetchApi(url); + const data = await result.json(); + + let novels: Novel.Item[] = []; + + data.find((item) => item.meta.key === "ranobe")?.data.forEach((novel) => + novels.push({ + name: novel.names.rus, + url: + "https://ranobehub.org/ranobe/" + + novel.url.match( + /https:\/\/ranobehub\.org\/ranobe\/(.*?)\?utm_source=search_name&utm_medium=search&utm_campaign=search_using/ + )[1], + cover: novel.image.replace("/small", "/medium"), + }) + ); + + return novels; +}; + +export const filters = [ + { + key: "sort", + label: "Сортировка", + values: [ + { label: "по рейтингу", value: "computed_rating" }, + { label: "по дате обновления", value: "last_chapter_at" }, + { label: "по дате добавления", value: "created_at" }, + { label: "по названию", value: "name_rus" }, + { label: "по просмотрам", value: "views" }, + { label: "по количеству глав", value: "count_chapters" }, + { label: "по объему перевода", value: "count_of_symbols" }, + ], + inputType: FilterInputs.Picker, + }, + { + key: "status", + label: "Статус перевода", + values: [ + { label: "Любой", value: "" }, + { label: "В процессе", value: "1" }, + { label: "Завершено", value: "2" }, + { label: "Заморожено", value: "3" }, + { label: "Неизвестно", value: "4" }, + ], + inputType: FilterInputs.Picker, + }, + { + key: "country", + label: "Тип", + values: [ + { label: "Китай", value: "2" }, + { label: "Корея", value: "3" }, + { label: "США", value: "4" }, + { label: "Япония", value: "1" }, + ], + inputType: FilterInputs.Checkbox, + }, + { + key: "events", + label: "События", + values: [ + { label: "[Награжденная работа]", value: "611" }, + { label: "18+", value: "338" }, + { label: "Авантюристы", value: "353" }, + { label: "Автоматоны", value: "538" }, + { label: "Агрессивные персонажи", value: "434" }, + { label: "Ад", value: "509" }, + { label: "Адаптация в радиопостановку", value: "522" }, + { label: "Академия", value: "25" }, + { label: "Актеры озвучки", value: "578" }, + { label: "Активный главный герой", value: "132" }, + { label: "Алхимия", value: "116" }, + { label: "Альтернативный мир", value: "28" }, + { label: "Амнезия/Потеря памяти", value: "247" }, + { label: "Анабиоз", value: "657" }, + { label: "Ангелы", value: "218" }, + { label: "Андрогинные персонажи", value: "217" }, + { label: "Андроиды", value: "82" }, + { label: "Анти-магия", value: "471" }, + { label: "Антигерой", value: "346" }, + { label: "Антикварный магазин", value: "572" }, + { label: "Антисоциальный главный герой", value: "562" }, + { label: "Антиутопия", value: "663" }, + { label: "Апатичный протагонист", value: "29" }, + { label: "Апокалипсис", value: "314" }, + { label: "Аранжированный брак", value: "285" }, + { label: "Армия", value: "598" }, + { label: "Артефакты", value: "117" }, + { label: "Артисты", value: "460" }, + { label: "Банды", value: "581" }, + { label: "БДСМ", value: "676" }, + { label: "Бедный главный герой", value: "309" }, + { label: "Безжалостный главный герой", value: "144" }, + { label: "Беззаботный главный герой", value: "355" }, + { label: "Безусловная любовь", value: "650" }, + { label: "Беременность", value: "131" }, + { label: "Бесполый главный герой", value: "222" }, + { label: "Бессмертные", value: "275" }, + { label: "Бесстрашный протагонист", value: "619" }, + { label: "Бесстыдный главный герой", value: "256" }, + { label: "Бесчестный главный герой", value: "699" }, + { label: "Библиотека", value: "342" }, + { label: "Бизнесмен ", value: "813" }, + { label: "Биочип", value: "120" }, + { label: "Бисексуальный главный герой", value: "822" }, + { label: "Близнецы", value: "148" }, + { label: "Боги", value: "211" }, + { label: "Богини", value: "356" }, + { label: "Боевая академия", value: "369" }, + { label: "Боевые духи", value: "347" }, + { label: "Боевые соревнования", value: "422" }, + { label: "Божественная защита", value: "336" }, + { label: "Божественные силы", value: "224" }, + { + label: "Большая разница в возрасте между героем и его любовным интересом", + value: "348", + }, + { label: "Борьба за власть", value: "544" }, + { label: "Брак", value: "363" }, + { label: "Брак по расчету", value: "65" }, + { label: "Братский комплекс", value: "31" }, + { label: "Братство", value: "413" }, + { label: "Братья и сестры", value: "518" }, + { label: "Буддизм", value: "742" }, + { label: "Быстрая культивация", value: "273" }, + { label: "Быстрообучаемый", value: "221" }, + { label: "Валькирии", value: "667" }, + { label: "Вампиры", value: "266" }, + { label: "Ваншот", value: "679" }, + { label: "Ведьмы", value: "169" }, + { label: "Вежливый главный герой", value: "289" }, + { label: "Верные подчиненные", value: "225" }, + { label: "Взрослый главный герой", value: "183" }, + { label: "Видит то, чего не видят другие", value: "636" }, + { label: "Виртуальная реальность", value: "313" }, + { label: "Владелец магазина", value: "653" }, + { label: "Внезапная сила", value: "376" }, + { label: "Внезапное богатство", value: "802" }, + { + label: "Внешний вид отличается от фактического возраста", + value: "334", + }, + { label: "Военные Летописи", value: "740" }, + { label: "Возвращение из другого мира", value: "673" }, + { label: "Войны", value: "58" }, + { label: "Вокалоид", value: "678" }, + { label: "Волшебники/Волшебницы", value: "477" }, + { label: "Волшебные звери", value: "201" }, + { label: "Воображаемый друг", value: "614" }, + { label: "Воры", value: "326" }, + { label: "Воскрешение", value: "78" }, + { label: "Враги становятся возлюбленными", value: "428" }, + { label: "Враги становятся союзниками", value: "502" }, + { label: "Врата в другой мир", value: "558" }, + { label: "Врачи", value: "286" }, + { label: "Временной парадокс", value: "163" }, + { label: "Всемогущий главный герой", value: "42" }, + { label: "Вторжение на землю", value: "77" }, + { label: "Второй шанс", value: "112" }, + { label: "Вуайеризм", value: "851" }, + { label: "Выживание", value: "290" }, + { label: "Высокомерные персонажи", value: "268" }, + { label: "Гадание", value: "540" }, + { label: "Гарем рабов", value: "828" }, + { label: "Геймеры", value: "302" }, + { label: "Генералы", value: "223" }, + { label: "Генетические модификации", value: "620" }, + { label: "Гениальный главный герой", value: "566" }, + { label: "Герои", value: "173" }, + { label: "Героиня — сорванец", value: "525" }, + { label: "Герой влюбляется первым", value: "64" }, + { label: "Гетерохромия", value: "510" }, + { label: "Гильдии", value: "323" }, + { label: "Гипнотизм", value: "768" }, + { label: "Главный герой — бог", value: "486" }, + { label: "Главный герой — гуманоид", value: "595" }, + { label: "Главный герой — женщина", value: "63" }, + { label: "Главный герой — мужчина", value: "39" }, + { label: "Главный герой — наполовину человек", value: "362" }, + { label: "Главный герой — отец", value: "859" }, + { label: "Главный герой — раб", value: "832" }, + { label: "Главный герой — ребенок", value: "415" }, + { label: "Главный герой — рубака", value: "400" }, + { label: "Главный герой — собиратель гарема", value: "439" }, + { label: "Главный герой влюбляется первым", value: "655" }, + { label: "Главный герой играет роль", value: "396" }, + { label: "Главный герой носит очки", value: "637" }, + { label: "Главный герой пацифист", value: "675" }, + { label: "Главный герой с несколькими телами", value: "628" }, + { label: "Главный герой силен с самого начала", value: "45" }, + { label: "Гладиаторы", value: "549" }, + { label: "Глуповатый главный герой", value: "295" }, + { label: "Гоблины", value: "529" }, + { label: "Големы", value: "569" }, + { label: "Гомункул", value: "850" }, + { label: "Горничные", value: "380" }, + { label: "Госпиталь", value: "1043" }, + { label: "Готовка", value: "193" }, + { label: "Гриндинг", value: "303" }, + { label: "Дао Компаньон", value: "384" }, + { label: "Даосизм", value: "792" }, + { label: "Дарк", value: "151" }, + { label: "Дварфы", value: "220" }, + { label: "Двойная личность", value: "601" }, + { label: "Двойник", value: "547" }, + { label: "Дворецкий", value: "623" }, + { label: "Дворяне", value: "41" }, + { label: "Дворянство/Аристократия", value: "354" }, + { label: "Девушки-монстры", value: "634" }, + { label: "Демоническая техника культивации", value: "706" }, + { label: "Демоны", value: "6" }, + { label: "Денежный долг", value: "841" }, + { label: "Депрессия", value: "494" }, + { label: "Детективы", value: "561" }, + { label: "Дискриминация", value: "34" }, + { + label: "Добыча денег одно из основных стремлений главного героя", + value: "307", + }, + { label: "Долгая разлука", value: "200" }, + { label: "Домашние дела", value: "178" }, + { label: "Домогательство", value: "394" }, + { label: "Драконы", value: "195" }, + { label: "Драконьи всадники", value: "555" }, + { label: "Древние времена", value: "102" }, + { label: "Древний Китай", value: "284" }, + { label: "Дружба", value: "97" }, + { label: "Друзья детства", value: "170" }, + { label: "Друзья становятся врагами", value: "507" }, + { label: "Друиды", value: "427" }, + { label: "Дух лисы", value: "842" }, + { label: "Духи/Призраки", value: "46" }, + { label: "Духовный советник", value: "351" }, + { label: "Душевность", value: "587" }, + { label: "Души", value: "136" }, + { label: "Европейская атмосфера", value: "457" }, + { label: "Ёкаи", value: "516" }, + { label: "Есть аниме-адаптация", value: "26" }, + { label: "Есть видеоигра по мотивам", value: "491" }, + { label: "Есть манга-адаптация", value: "27" }, + { label: "Есть манхва-адаптация", value: "453" }, + { label: "Есть маньхуа-адаптация", value: "298" }, + { label: "Есть сериал-адаптация", value: "421" }, + { label: "Есть фильм по мотивам", value: "47" }, + { label: "Женища-наставник", value: "438" }, + { + label: "Жертва изнасилования влюбляется в насильника", + value: "414", + }, + { label: "Жесткая, двуличная личность", value: "249" }, + { label: "Жестокие персонажи", value: "436" }, + { label: "Жестокое обращение с ребенком", value: "617" }, + { label: "Жестокость", value: "127" }, + { label: "Животноводство", value: "765" }, + { label: "Животные черты", value: "466" }, + { label: "Жизнь в квартире", value: "554" }, + { label: "Жрицы", value: "564" }, + { label: "Заботливый главный герой", value: "176" }, + { label: "Забывчивый главный герой", value: "579" }, + { label: "Заговоры", value: "177" }, + { label: "Закалка тела", value: "269" }, + { label: "Законники", value: "769" }, + { label: "Замкнутый главный герой", value: "533" }, + { label: "Запечатанная сила", value: "344" }, + { label: "Застенчивые персонажи", value: "443" }, + { label: "Звери", value: "119" }, + { label: "Звери-компаньоны", value: "192" }, + { label: "Злой протагонист", value: "125" }, + { label: "Злые боги", value: "437" }, + { label: "Злые организации", value: "503" }, + { label: "Злые религии", value: "725" }, + { label: "Знаменитости", value: "397" }, + { label: "Знаменитый главный герой", value: "469" }, + { label: "Знания современного мира", value: "185" }, + { label: "Зомби", value: "321" }, + { label: "Игра на выживание", value: "162" }, + { label: "Игривый протагонист", value: "700" }, + { label: "Игровая система рейтинга", value: "301" }, + { label: "Игровые элементы", value: "152" }, + { label: "Игрушки (18+)", value: "644" }, + { label: "Из грязи в князи", value: "330" }, + { label: "Из женщины в мужчину ", value: "688" }, + { label: "Из мужчины в женщину", value: "698" }, + { label: "Из полного в худого", value: "809" }, + { label: "Из слабого в сильного", value: "81" }, + { label: "Из страшно(го/й) в красиво(го/ю)", value: "810" }, + { label: "Извращенный главный герой", value: "349" }, + { label: "Изгои", value: "472" }, + { label: "Изменение расы", value: "627" }, + { label: "Изменения внешнего вида", value: "191" }, + { label: "Изменения личности", value: "99" }, + { label: "Изнасилование", value: "110" }, + { label: "Изображения жестокости", value: "260" }, + { label: "Империи", value: "749" }, + { label: "Инвалидность", value: "656" }, + { label: "Индустриализация", value: "180" }, + { label: "Инженер", value: "35" }, + { label: "Инцест", value: "463" }, + { label: "Искусственный интеллект", value: "118" }, + { label: "Исследования", value: "635" }, + { label: "Каннибализм", value: "377" }, + { label: "Карточные игры", value: "654" }, + { label: "Киберспорт", value: "658" }, + { label: "Кланы", value: "950" }, + { + label: "Класс безработного [Игровой класс в игре]", + value: "727", + }, + { label: "Клоны", value: "365" }, + { label: "Клубы", value: "96" }, + { label: "Книги", value: "341" }, + { label: "Книги навыков", value: "312" }, + { label: "Книжный червь", value: "455" }, + { label: "Коварство", value: "111" }, + { label: "Коллеги", value: "683" }, + { label: "Колледж/Университет", value: "712" }, + { label: "Кома", value: "826" }, + { label: "Командная работа", value: "426" }, + { label: "Комедийный оттенок", value: "523" }, + { label: "Комплекс неполноценности", value: "489" }, + { label: "Комплекс семейных отношений", value: "104" }, + { label: "Конкуренция", value: "746" }, + { label: "Контракты", value: "483" }, + { label: "Контроль разума/сознания", value: "262" }, + { label: "Копейщик", value: "339" }, + { label: "Королевская битва", value: "1025" }, + { label: "Королевская власть", value: "53" }, + { label: "Королевства", value: "141" }, + { label: "Коррупция", value: "378" }, + { label: "Космические войны", value: "674" }, + { label: "Красивый герой", value: "107" }, + { label: "Крафт", value: "287" }, + { label: "Кризис личности", value: "599" }, + { label: "Кругосветное путешествие", value: "257" }, + { label: "Кудере", value: "440" }, + { label: "Кузены", value: "701" }, + { label: "Кузнец", value: "454" }, + { label: "Кукловоды", value: "431" }, + { label: "Куклы/марионетки", value: "573" }, + { label: "Культивация", value: "123" }, + { label: "Куннилингус", value: "632" }, + { label: "Легенды", value: "430" }, + { label: "Легкая жизнь", value: "604" }, + { label: "Ленивый главный герой", value: "570" }, + { label: "Лидерство", value: "424" }, + { label: "Лоли", value: "92" }, + { label: "Лотерея", value: "401" }, + { label: "Любовный интерес влюбляется первым", value: "647" }, + { + label: "Любовный интерес главного героя носит очки", + value: "576", + }, + { label: "Любовный треугольник", value: "98" }, + { label: "Любовь детства", value: "500" }, + { label: "Любовь с первого взгляда", value: "730" }, + { label: "Магические надписи", value: "373" }, + { label: "Магические печати", value: "277" }, + { label: "Магические технологии", value: "357" }, + { + label: "Магическое пространство/измерение, доступное не всем персонажам", + value: "244", + }, + { label: "Магия", value: "38" }, + { label: "Магия призыва", value: "333" }, + { label: "Мазохистские персонажи", value: "660" }, + { label: "Манипулятивные персонажи", value: "130" }, + { label: "Мания", value: "91" }, + { label: "Мастер на все руки", value: "390" }, + { label: "Мастурбация", value: "846" }, + { label: "Махо-сёдзё", value: "496" }, + { label: "Медицинские знания", value: "441" }, + { label: "Медленная романтическая линия", value: "113" }, + { label: "Медленное развитие на старте ", value: "670" }, + { label: "Межпространственные путешествия", value: "316" }, + { label: "Менеджмент", value: "182" }, + { label: "Мертвый главный герой", value: "794" }, + { label: "Месть", value: "88" }, + { label: "Метаморфы", value: "234" }, + { label: "Меч и магия", value: "55" }, + { label: "Мечник", value: "607" }, + { label: "Мечты", value: "733" }, + { label: "Милая история", value: "709" }, + { label: "Милое дитя", value: "596" }, + { label: "Милый главный герой", value: "697" }, + { label: "Мировое дерево", value: "385" }, + { label: "Мистический ореол вокруг семьи", value: "409" }, + { label: "Мифические звери", value: "418" }, + { label: "Мифология", value: "468" }, + { label: "Младшие братья", value: "843" }, + { label: "Младшие сестры", value: "465" }, + { label: "ММОРПГ (ЛитРПГ)", value: "306" }, + { label: "Множество перемещенных людей", value: "488" }, + { label: "Множество реальностей", value: "278" }, + { label: "Множество реинкарнированных людей", value: "227" }, + { label: "Модели", value: "649" }, + { label: "Молчаливый персонаж", value: "705" }, + { label: "Монстры", value: "69" }, + { label: "Мужская гей-пара", value: "685" }, + { label: "Мужчина-яндере", value: "155" }, + { label: "Музыка", value: "589" }, + { label: "Музыкальные группы", value: "588" }, + { label: "Мутации", value: "668" }, + { label: "Мутированные существа", value: "317" }, + { label: "Навык кражи", value: "626" }, + { label: "Навязчивая любовь", value: "85" }, + { label: "Наемники", value: "324" }, + { label: "Назойливый возлюбленный", value: "501" }, + { label: "Наивный главный герой", value: "66" }, + { label: "Наркотики", value: "661" }, + { label: "Нарциссический главный герой", value: "470" }, + { label: "Насилие сексуального характера", value: "824" }, + { label: "Наследование", value: "372" }, + { label: "Национализм", value: "318" }, + { label: "Не блещущий внешне главный герой", value: "328" }, + { label: "Не родные братья и сестры", value: "464" }, + { label: "Небеса", value: "508" }, + { label: "Небесное испытание", value: "274" }, + { label: "Негуманоидный главный герой", value: "622" }, + { label: "Недоверчивый главный герой", value: "485" }, + { label: "Недооцененный главный герой", value: "140" }, + { label: "Недоразумения", value: "202" }, + { label: "Неизлечимая болезнь", value: "75" }, + { label: "Некромант", value: "308" }, + { label: "Нелинейная история", value: "157" }, + { label: "Ненавистный главный герой", value: "487" }, + { label: "Ненадежный рассказчик", value: "165" }, + { label: "Нерезиденты", value: "821" }, + { label: "Нерешительный главный герой", value: "741" }, + { label: "Несерьезный главный герой", value: "294" }, + { label: "Несколько временных линий", value: "517" }, + { label: "Несколько главных героев", value: "475" }, + { label: "Несколько идентичностей", value: "615" }, + { label: "Несколько личностей", value: "474" }, + { label: "Нетораре", value: "721" }, + { label: "Нетори", value: "450" }, + { label: "Неудачливый главный герой", value: "567" }, + { label: "Ниндзя", value: "358" }, + { label: "Обещание из детства", value: "738" }, + { label: "Обманщик", value: "411" }, + { label: "Обмен телами", value: "94" }, + { label: "Обнаженка", value: "417" }, + { label: "Обольщение", value: "718" }, + { label: "Оборотни", value: "624" }, + { label: "Обратный гарем", value: "255" }, + { label: "Общество монстров", value: "226" }, + { label: "Обязательство", value: "548" }, + { label: "Огнестрельное оружие", value: "179" }, + { label: "Ограниченная продолжительность жизни", value: "73" }, + { label: "Одержимость", value: "447" }, + { label: "Одинокий главный герой", value: "304" }, + { label: "Одиночество", value: "199" }, + { label: "Одиночное проживание", value: "207" }, + { label: "Околосмертные переживания", value: "773" }, + { label: "Оммёдзи", value: "542" }, + { label: "Омоложение", value: "237" }, + { label: "Организованная преступность", value: "478" }, + { label: "Оргия", value: "825" }, + { label: "Орки", value: "228" }, + { label: "Освоение навыков", value: "235" }, + { label: "Основано на аниме", value: "553" }, + { label: "Основано на видео игре", value: "704" }, + { label: "Основано на визуальной новелле ", value: "861" }, + { label: "Основано на песне", value: "677" }, + { label: "Основано на фильме", value: "552" }, + { label: "Осторожный главный герой", value: "122" }, + { label: "Отаку", value: "512" }, + { label: "Открытый космос", value: "150" }, + { label: "Отношения в сети", value: "713" }, + { label: "Отношения между богом и человеком", value: "603" }, + { label: "Отношения между людьми и нелюдьми", value: "205" }, + { label: "Отношения на расстоянии", value: "852" }, + { label: "Отношения начальник-подчиненный", value: "590" }, + { label: "Отношения Сенпай-Коухай", value: "513" }, + { label: "Отношения ученика и учителя", value: "451" }, + { label: "Отношения учитель-ученик", value: "343" }, + { label: "Отношения хозяин-слуга", value: "206" }, + { label: "Отомэ игра", value: "723" }, + { label: "Отсутствие здравого смысла", value: "605" }, + { label: "Отсутствие родителей", value: "575" }, + { label: "Офисный роман", value: "686" }, + { label: "Официанты", value: "681" }, + { label: "Охотники", value: "288" }, + { label: "Очаровательный главный герой", value: "659" }, + { label: "Падшее дворянство", value: "618" }, + { label: "Падшие ангелы", value: "505" }, + { label: "Пайзури", value: "630" }, + { label: "Паразиты", value: "535" }, + { label: "Параллельные миры", value: "86" }, + { label: "Парк развлечений", value: "586" }, + { label: "Пародия", value: "319" }, + { label: "Певцы/Певицы", value: "734" }, + { label: "Первая любовь", value: "716" }, + { label: "Первоисточник новеллы — манга", value: "560" }, + { label: "Первый раз", value: "845" }, + { + label: "Перемещение в другой мир, имея при себе современные достижения", + value: "732", + }, + { label: "Перемещение в игровой мир", value: "532" }, + { label: "Перемещение в иной мир", value: "754" }, + { label: "Перерождение в ином мире", value: "755" }, + { label: "Переселение души/Трансмиграция", value: "139" }, + { label: "Персонаж использует щит", value: "631" }, + { label: "Петля времени", value: "79" }, + { label: "Пираты", value: "817" }, + { label: "Писатели", value: "408" }, + { label: "Питомцы", value: "253" }, + { label: "Племенное общество", value: "291" }, + { label: "Повелитель демонов", value: "171" }, + { + label: "Повествование от нескольких лиц/Несколько точек зрения", + value: "156", + }, + { label: "Подземелья", value: "219" }, + { label: "Пожелания", value: "166" }, + { label: "Познание Дао", value: "360" }, + { label: "Покинутое дитя", value: "534" }, + { label: "Полигамия", value: "296" }, + { label: "Политика", value: "43" }, + { label: "Полиция", value: "801" }, + { label: "Полулюди", value: "335" }, + { label: "Пользователь уникального оружия", value: "432" }, + { label: "Популярный любовный интерес", value: "241" }, + { label: "Постапокалиптика", value: "60" }, + { label: "Потерянные цивилизации", value: "751" }, + { label: "Похищения людей", value: "707" }, + { label: "Поэзия", value: "404" }, + { label: "Правонарушители", value: "694" }, + { label: "Прагматичный главный герой", value: "536" }, + { label: "Преданный любовный интерес", value: "106" }, + { label: "Предательство", value: "103" }, + { label: "Предвидение", value: "52" }, + { label: "Прекрасная героиня", value: "30" }, + { label: "Преступники", value: "728" }, + { label: "Преступность", value: "83" }, + { label: "Призванный герой", value: "147" }, + { label: "Призраки", value: "51" }, + { label: "Принуждение к отношениям", value: "602" }, + { label: "Принцессы", value: "933" }, + { label: "Притворная пара", value: "600" }, + { label: "Причудливые персонажи", value: "297" }, + { label: "Пришельцы/Инопланетяне", value: "76" }, + { label: "Программист", value: "666" }, + { label: "Проклятия", value: "48" }, + { label: "Промывание мозгов", value: "519" }, + { label: "Пропуск времени", value: "138" }, + { label: "Пророчества", value: "429" }, + { label: "Проститутки", value: "844" }, + { label: "Пространственное манипулирование", value: "375" }, + { label: "Прошлое играет большую роль", value: "215" }, + { label: "Прыжки между мирами", value: "737" }, + { label: "Психические силы", value: "265" }, + { label: "Психопаты", value: "158" }, + { label: "Путешествие во времени", value: "80" }, + { label: "Пытка", value: "168" }, + { label: "Рабы", value: "829" }, + { label: "Развод", value: "771" }, + { label: "Разумные предметы", value: "174" }, + { label: "Расизм", value: "320" }, + { label: "Рассказ", value: "61" }, + { label: "Расторжения помолвки", value: "243" }, + { label: "Расы зооморфов", value: "209" }, + { label: "Ревность", value: "717" }, + { label: "Редакторы", value: "684" }, + { label: "Реинкарнация", value: "281" }, + { label: "Реинкарнация в монстра", value: "204" }, + { label: "Реинкарнация в объект", value: "692" }, + { label: "Религии", value: "565" }, + { label: "Репортеры", value: "837" }, + { label: "Ресторан", value: "652" }, + { label: "Решительный главный герой", value: "124" }, + { label: "Робкий главный герой", value: "800" }, + { label: "Родитель одиночка", value: "687" }, + { label: "Родительский комплекс", value: "531" }, + { label: "Родословная", value: "121" }, + { label: "Романтический подсюжет ", value: "159" }, + { label: "Рост персонажа", value: "95" }, + { label: "Рыцари", value: "142" }, + { label: "Садистские персонажи", value: "642" }, + { label: "Самоотверженный главный герой", value: "748" }, + { label: "Самоубийства", value: "616" }, + { label: "Самурай", value: "646" }, + { label: "Сборник коротких историй", value: "456" }, + { label: "Связанные сюжетные линии", value: "473" }, + { label: "Святые", value: "761" }, + { label: "Священники", value: "325" }, + { label: "Сдержанный главный герой", value: "799" }, + { label: "Секретные организации", value: "331" }, + { label: "Секреты", value: "577" }, + { label: "Секс рабы", value: "830" }, + { label: "Семейный конфликт", value: "770" }, + { label: "Семь добродетелей", value: "233" }, + { label: "Семь смертных грехов", value: "134" }, + { label: "Семья", value: "251" }, + { label: "Сёнэн-ай подсюжет ", value: "664" }, + { label: "Серийные убийцы", value: "497" }, + { label: "Сестринский комплекс", value: "498" }, + { label: "Сила духа", value: "282" }, + { label: "Сила, требующая платы за пользование", value: "520" }, + { label: "Сильная пара", value: "109" }, + { label: "Сильный в сильнейшего", value: "359" }, + { label: "Сильный любовный интерес", value: "161" }, + { label: "Синдром восьмиклассника", value: "90" }, + { label: "Сироты", value: "214" }, + { label: "Система уровней", value: "198" }, + { label: "Системный администратор", value: "476" }, + { label: "Скрытие истинной личности", value: "371" }, + { label: "Скрытие истинных способностей", value: "252" }, + { label: "Скрытный главный герой", value: "133" }, + { label: "Скрытые способности", value: "128" }, + { label: "Скульпторы", value: "366" }, + { label: "Слабо выраженная романтическая линия", value: "213" }, + { label: "Слабый главный герой", value: "188" }, + { label: "Слепой главный герой", value: "864" }, + { label: "Слуги", value: "232" }, + { label: "Смерть", value: "49" }, + { label: "Смерть близких", value: "361" }, + { label: "Собственнические персонажи", value: "108" }, + { label: "Современность", value: "40" }, + { label: "Сожительство", value: "482" }, + { label: "Создание армии", value: "175" }, + { label: "Создание артефактов", value: "292" }, + { label: "Создание клана", value: "448" }, + { label: "Создание королевства", value: "181" }, + { label: "Создание навыков", value: "236" }, + { label: "Создание секты", value: "393" }, + { label: "Солдаты/Военные", value: "71" }, + { label: "Сон", value: "571" }, + { label: "Состоятельные персонажи", value: "67" }, + { label: "Социальная иерархия на основе силы", value: "137" }, + { label: "Социальные изгои", value: "480" }, + { label: "Спасение мира", value: "597" }, + { label: "Специальные способности", value: "54" }, + { label: "Спокойный главный герой", value: "32" }, + { label: "Справедливый главный герой", value: "702" }, + { label: "Средневековье", value: "184" }, + { label: "Ссорящаяся пара", value: "293" }, + { label: "Сталкеры", value: "689" }, + { label: "Старение", value: "190" }, + { label: "Стоические персонажи", value: "444" }, + { label: "Стокгольмский синдром", value: "643" }, + { label: "Стратег", value: "425" }, + { label: "Стратегические битвы", value: "160" }, + { label: "Стратегия", value: "1038" }, + { label: "Стрелки", value: "458" }, + { label: "Стрельба из лука", value: "383" }, + { label: "Студенческий совет", value: "490" }, + { label: "Судьба", value: "271" }, + { label: "Суккубы", value: "484" }, + { label: "Супер герои", value: "1039" }, + { label: "Суровая подготовка", value: "261" }, + { label: "Таинственная болезнь", value: "74" }, + { label: "Таинственное прошлое", value: "263" }, + { label: "Тайная личность", value: "310" }, + { label: "Тайные отношения", value: "812" }, + { label: "Танцоры", value: "840" }, + { label: "Телохранители", value: "452" }, + { label: "Тентакли", value: "693" }, + { label: "Террористы", value: "515" }, + { label: "Технологический разрыв", value: "621" }, + { label: "Тихие персонажи", value: "546" }, + { label: "Толстый главный герой", value: "299" }, + { label: "Торговцы", value: "416" }, + { label: "Травля/Буллинг", value: "89" }, + { label: "Травник", value: "708" }, + { label: "Трагическое прошлое", value: "164" }, + { label: "Трансплантация воспоминаний", value: "367" }, + { label: "Трап (Путаница с гендером персонажа)", value: "582" }, + { label: "Трудолюбивый главный герой", value: "37" }, + { label: "Тюрьма", value: "479" }, + { label: "Убийства", value: "84" }, + { label: "Убийцы", value: "248" }, + { label: "Убийцы драконов", value: "606" }, + { label: "Уверенный главный герой", value: "270" }, + { label: "Удачливый главный герой", value: "402" }, + { label: "Укротитель монстров", value: "337" }, + { label: "Умения из прошлой жизни", value: "280" }, + { label: "Умная пара", value: "493" }, + { label: "Умный главный герой", value: "33" }, + { label: "Уникальная техника Культивации", value: "254" }, + { label: "Уникальное оружие", value: "340" }, + { label: "Управление бизнесом", value: "315" }, + { label: "Управление временем", value: "167" }, + { label: "Управление кровью", value: "764" }, + { label: "Упрямый главный герой", value: "672" }, + { label: "Уродливый главный герой", value: "803" }, + { label: "Ускоренный рост", value: "300" }, + { label: "Усыновленные дети", value: "481" }, + { label: "Усыновленный главный герой", value: "412" }, + { label: "Уход за детьми", value: "398" }, + { label: "Учителя", value: "345" }, + { label: "Фамильяры", value: "541" }, + { label: "Фанатизм", value: "613" }, + { label: "Фантастические существа", value: "322" }, + { label: "Фанфикшн", value: "388" }, + { label: "Фармацевт", value: "715" }, + { label: "Фарминг", value: "379" }, + { label: "Феи", value: "210" }, + { label: "Фелляция", value: "584" }, + { label: "Фениксы", value: "374" }, + { label: "Фетиш груди", value: "499" }, + { label: "Философия", value: "87" }, + { label: "Фильмы", value: "403" }, + { label: "Флэшбэки", value: "528" }, + { label: "Фобии", value: "100" }, + { label: "Фольклор", value: "467" }, + { label: "Футанари", value: "568" }, + { label: "Футуристический сеттинг", value: "382" }, + { label: "Фэнтези мир", value: "126" }, + { label: "Хакеры", value: "399" }, + { label: "Харизматический герой", value: "391" }, + { label: "Хикикомори/Затворники", value: "462" }, + { label: "Хитроумный главный герой", value: "105" }, + { label: "Хозяин подземелий", value: "557" }, + { label: "Холодный главный герой", value: "259" }, + { label: "Хорошие отношения с семьей", value: "506" }, + { label: "Хранители могил", value: "68" }, + { label: "Целители", value: "389" }, + { label: "Цзянши", value: "735" }, + { label: "Цундэрэ", value: "445" }, + { label: "Чаты", value: "580" }, + { label: "Человеческое оружие", value: "521" }, + { label: "Честный главный герой", value: "240" }, + { label: "Читы", value: "238" }, + { label: "Шантаж", value: "526" }, + { label: "Шеф-повар", value: "239" }, + { label: "Шикигами", value: "543" }, + { label: "Школа только для девочек", value: "633" }, + { label: "Шота", value: "514" }, + { label: "Шоу-бизнес", value: "407" }, + { label: "Шпионы", value: "563" }, + { label: "Эволюция", value: "196" }, + { label: "Эгоистичный главный герой", value: "539" }, + { label: "Эйдетическая память", value: "392" }, + { label: "Экзорсизм", value: "504" }, + { label: "Экономика", value: "492" }, + { label: "Эксгибиционизм", value: "639" }, + { label: "Эксперименты с людьми", value: "129" }, + { label: "Элементальная магия", value: "395" }, + { label: "Эльфы", value: "172" }, + { label: "Эмоционально слабый главный герой", value: "816" }, + { label: "Эпизодический", value: "612" }, + { label: "Юный любовный интерес", value: "446" }, + { label: "Яды", value: "410" }, + { label: "Языкастые персонажи", value: "406" }, + { label: "Языковой барьер", value: "59" }, + { label: "Яндере", value: "208" }, + { label: "Японские силы самообороны", value: "559" }, + { label: "Ярко выраженная романтическая линия", value: "272" }, + { label: "Abusive Characters", value: "625" }, + { label: "Adapted to Visual Novel", value: "1046" }, + { label: "Adopted-lead", value: "886" }, + { label: "Adultery", value: "866" }, + { label: "Affair", value: "645" }, + { label: "Age-gap", value: "923" }, + { label: "Almost-human-lead", value: "975" }, + { label: "An*l", value: "780" }, + { label: "Androgynous-male-lead", value: "1002" }, + { label: "Anti-hero-lead", value: "976" }, + { label: "Apathetic-lead", value: "967" }, + { label: "Arms Dealers", value: "839" }, + { label: "Army-commander", value: "1003" }, + { label: "Artifact-refining", value: "924" }, + { label: "Autism", value: "855" }, + { label: "Awkward Protagonist", value: "669" }, + { label: "Awkward-lead", value: "892" }, + { label: "Bestiality", value: "796" }, + { label: "Big-breasts", value: "908" }, + { label: "Birth-of-a-nation", value: "1004" }, + { label: "Bisexual-lead", value: "1027" }, + { label: "Body-refining", value: "947" }, + { label: "Body-swap/s", value: "1036" }, + { label: "Boss-subordinate-relationship", value: "876" }, + { label: "Bride-kidnapping", value: "948" }, + { label: "Caring-lead", value: "879" }, + { label: "Cautious-lead", value: "1032" }, + { label: "Changed-man", value: "925" }, + { label: "Charismatic-lead", value: "1005" }, + { label: "Child-lead", value: "936" }, + { label: "Clever-lead", value: "880" }, + { label: "Clumsy Love Interests", value: "815" }, + { label: "Cold Love Interests", value: "651" }, + { label: "Coming of Age", value: "860" }, + { label: "Confident-lead", value: "894" }, + { label: "Confident-male-lead", value: "951" }, + { label: "Confinement", value: "767" }, + { label: "Conflicting Loyalties", value: "793" }, + { label: "Couple Growth", value: "690" }, + { label: "Court Official", value: "863" }, + { label: "Cowardly Protagonist", value: "703" }, + { label: "Cowardly-lead", value: "1000" }, + { label: "Cross-dressing", value: "250" }, + { label: "Cunnilingus", value: "867" }, + { label: "Cunning-lead", value: "952" }, + { label: "Cunning-male-lead", value: "953" }, + { label: "Curious Protagonist", value: "849" }, + { label: "Cute-lead", value: "887" }, + { label: "Dense-lead", value: "868" }, + { label: "Determined-lead", value: "972" }, + { label: "Developing-technology", value: "1006" }, + { label: "Devil/s", value: "1007" }, + { label: "Different Social Status", value: "758" }, + { label: "Disfigurement", value: "838" }, + { label: "Doting Love Interests", value: "665" }, + { label: "Doting Older Siblings", value: "609" }, + { label: "Doting Parents", value: "530" }, + { label: "Dungeon/s-exploring", value: "990" }, + { label: "Elderly Protagonist", value: "774" }, + { label: "Enlightenment", value: "729" }, + { label: "Eunuch", value: "1047" }, + { label: "Eye Powers", value: "386" }, + { label: "Family Business", value: "785" }, + { label: "Famous Parents", value: "790" }, + { label: "Famous-lead", value: "897" }, + { label: "Fanfic", value: "942" }, + { label: "Fated Lovers", value: "797" }, + { label: "Fellatio", value: "870" }, + { label: "Female-lead", value: "888" }, + { label: "First-time-intercourse", value: "871" }, + { label: "Fleet Battles", value: "836" }, + { label: "Forced Living Arrangements", value: "808" }, + { label: "Forced Marriage", value: "835" }, + { label: "Former Hero", value: "752" }, + { label: "Fujoshi", value: "1045" }, + { label: "Galge", value: "848" }, + { label: "Gambling", value: "1031" }, + { label: "Gamelit", value: "994" }, + { label: "Genderless-lead", value: "1008" }, + { label: "Glasses-wearing-lead", value: "995" }, + { label: "Guardian Relationship", value: "197" }, + { label: "H*ndjob", value: "629" }, + { label: "Half-human-lead", value: "956" }, + { label: "Hard-working-lead", value: "957" }, + { label: "Hard-working-male-lead", value: "958" }, + { label: "Hard-working-protagonist/s", value: "1030" }, + { label: "Harem-seeking-lead", value: "929" }, + { label: "Harem-subtext", value: "909" }, + { label: "Helpful Protagonist", value: "760" }, + { label: "Helpful-lead", value: "882" }, + { label: "Hidden-gem", value: "998" }, + { label: "High-fantasy", value: "991" }, + { label: "Human-becomes-demon/monster", value: "1009" }, + { label: "Human-nonhuman-relationship", value: "914" }, + { label: "Humanoid-lead", value: "915" }, + { label: "Imperial Harem", value: "805" }, + { label: "Incubus", value: "1049" }, + { label: "Insects", value: "831" }, + { label: "Interspatial-storage", value: "959" }, + { label: "Kind Love Interests", value: "757" }, + { label: "Large-number-of-skills", value: "1010" }, + { label: "Legendary-hero", value: "1011" }, + { label: "Litrpg", value: "977" }, + { label: "Love Rivals", value: "788" }, + { label: "Lovers Reunited", value: "853" }, + { label: "Low-fantasy", value: "1037" }, + { label: "Master-disciple-relationship", value: "899" }, + { label: "Matriarchy", value: "807" }, + { label: "Mature-lead", value: "900" }, + { label: "Mind Break", value: "862" }, + { label: "Mismatched Couple", value: "695" }, + { label: "Mob Protagonist", value: "648" }, + { label: "Mob-lead", value: "902" }, + { label: "Modern", value: "988" }, + { label: "Monster-pov", value: "1012" }, + { label: "Mpreg", value: "856" }, + { label: "Multiple-povs", value: "1013" }, + { label: "Mystery Solving", value: "710" }, + { label: "Naive-lead", value: "1014" }, + { label: "Near-death-experience", value: "996" }, + { label: "Neet", value: "495" }, + { label: "Nightmares", value: "833" }, + { label: "Nobility", value: "1015" }, + { label: "Non-human-lead", value: "978" }, + { label: "Non-humanoid-lead", value: "1016" }, + { label: "Not-so-secret-identity", value: "979" }, + { label: "Omegaverse", value: "857" }, + { label: "Online-game", value: "910" }, + { label: "Online-gaming", value: "911" }, + { label: "Outdoor Interc**rse", value: "847" }, + { label: "Outdoor-intercourse", value: "903" }, + { label: "Overprotective Siblings", value: "610" }, + { label: "Part-Time Job", value: "865" }, + { label: "Past Trauma", value: "726" }, + { label: "Past-memories", value: "1017" }, + { label: "Persistent Love Interests", value: "714" }, + { label: "Perverted-lead", value: "932" }, + { label: "Photography", value: "798" }, + { label: "Pill Based Cultivation", value: "279" }, + { label: "Pill Concocting", value: "245" }, + { label: "Pilots", value: "820" }, + { label: "Playboys", value: "795" }, + { label: "Playful-lead", value: "989" }, + { label: "Polyandry", value: "811" }, + { label: "Portal-fantasy-/-isekai", value: "992" }, + { label: "Pragmatic-lead", value: "884" }, + { label: "Profanity", value: "980" }, + { label: "Protagonist-loyal-to-love-interest", value: "905" }, + { label: "R-15 (Японское возрастное ограничение)", value: "230" }, + { label: "Rebellion", value: "834" }, + { label: "Reincarnated in a Game World", value: "804" }, + { label: "Reluctant Protagonist", value: "696" }, + { label: "Reverse R*pe", value: "776" }, + { label: "Reversible Couple", value: "854" }, + { label: "Ruling-class", value: "943" }, + { label: "S*x Friends", value: "750" }, + { label: "S*xual Cultivation Technique", value: "823" }, + { label: "Salaryman", value: "1035" }, + { label: "Satire", value: "981" }, + { label: "Schemes And Conspiracies", value: "745" }, + { label: "Scientist/s", value: "997" }, + { label: "Scientists", value: "1029" }, + { label: "Secret Crush", value: "827" }, + { label: "Seme Protagonist", value: "1034" }, + { label: "Seme-lead", value: "1028" }, + { label: "Sentimental Protagonist", value: "1048" }, + { label: "Seven-heavenly-virtues", value: "1018" }, + { label: "Sexual Cultivation Technique", value: "350" }, + { label: "Sexual-content", value: "982" }, + { label: "Sexuality", value: "983" }, + { label: "Sharing A Body", value: "814" }, + { label: "Shotacon", value: "858" }, + { label: "Shoujo-Ai Subplot", value: "777" }, + { label: "Sibling Rivalry", value: "766" }, + { label: "Sibling's Care", value: "1042" }, + { label: "Sickly Characters", value: "787" }, + { label: "Skills", value: "1019" }, + { label: "Slave-harem", value: "1001" }, + { label: "Slime", value: "1020" }, + { label: "Smart-male-lead", value: "963" }, + { label: "Spirit Users", value: "739" }, + { label: "Straight Seme", value: "819" }, + { label: "Straight Uke", value: "818" }, + { label: "Strength-based-social-hierarchy", value: "974" }, + { label: "Strong-lead", value: "944" }, + { label: "Strong-male-lead", value: "964" }, + { label: "Student-teacher-relationship", value: "1026" }, + { label: "Suspense", value: "987" }, + { label: "Sword-and-sorcery", value: "875" }, + { label: "Threesome", value: "574" }, + { label: "Transformation Ability", value: "736" }, + { label: "Traumatising-content", value: "1040" }, + { label: "Underestimated-lead", value: "919" }, + { label: "Underestimated-male-lead", value: "966" }, + { label: "Unique-abilities", value: "1021" }, + { label: "Unique-skill", value: "1022" }, + { label: "Unique-skills", value: "1023" }, + { label: "Unlimited Flow", value: "1044" }, + { label: "Unrequited Love", value: "806" }, + { label: "Urban-fantasy", value: "1041" }, + { label: "Villainess Noble Girls", value: "719" }, + { label: "Weak-lead", value: "885" }, + { label: "Web-novel", value: "1024" }, + ], + inputType: FilterInputs.Checkbox, + }, + { + key: "tags", + label: "Жанры", + values: [ + { label: "Боевые искусства", value: "22" }, + { label: "Гарем", value: "114" }, + { label: "Гендер бендер", value: "246" }, + { label: "Дзёсэй", value: "216" }, + { label: "Для взрослых", value: "115" }, + { label: "Для взрослых", value: "258" }, + { label: "Драма", value: "7" }, + { label: "Исторический", value: "101" }, + { label: "Комедия", value: "17" }, + { label: "Лоликон", value: "638" }, + { label: "Магический реализм", value: "922" }, + { label: "Меха", value: "24" }, + { label: "Милитари", value: "12" }, + { label: "Мистика", value: "2" }, + { label: "Научная фантастика", value: "13" }, + { label: "Непристойность", value: "747" }, + { label: "Повседневность", value: "93" }, + { label: "Приключение", value: "11" }, + { label: "Психология", value: "18" }, + { label: "Романтика", value: "9" }, + { label: "Сверхъестественное", value: "20" }, + { label: "Сёдзё", value: "15" }, + { label: "Сёдзё-ай", value: "23" }, + { label: "Сёнэн", value: "189" }, + { label: "Сёнэн-ай", value: "680" }, + { label: "Спорт", value: "420" }, + { label: "Сэйнэн", value: "5" }, + { label: "Сюаньхуа", value: "242" }, + { label: "Сянься", value: "364" }, + { label: "Трагедия", value: "19" }, + { label: "Триллер", value: "3" }, + { label: "Ужасы", value: "1" }, + { label: "Уся", value: "720" }, + { label: "Фэнтези", value: "8" }, + { label: "Школьная жизнь", value: "21" }, + { label: "Экшн", value: "14" }, + { label: "Эччи", value: "327" }, + { label: "Юри", value: "691" }, + { label: "Яой", value: "682" }, + { label: "Eastern fantasy", value: "907" }, + { label: "Isekai", value: "999" }, + { label: "Video games", value: "993" }, + ], + inputType: FilterInputs.Checkbox, + }, +]; diff --git a/plugins/russian/ranobehub.js b/plugins/russian/ranobehub.js deleted file mode 100644 index 1cd9e86..0000000 --- a/plugins/russian/ranobehub.js +++ /dev/null @@ -1,1133 +0,0 @@ -const dayjs = require("dayjs"); -const cheerio = require("cheerio"); -const Status = require("@libs/novelStatus"); -const fetchApi = require("@libs/fetchApi"); -const fetchFile = require("@libs/fetchFile"); -const languages = require("@libs/languages"); -const FilterInputs = require("@libs/filterInputs"); - -const pluginId = "RNBH.org"; -const sourceName = "RanobeHub"; -const baseUrl = "https://ranobehub.org/"; - -async function popularNovels(page, { showLatestNovels, filters }) { - let url = baseUrl + `api/search?page=${page}&sort=`; - url += showLatestNovels ? "last_chapter_at" : filters?.sort || "computed_rating"; - url += "&status=" + (filters?.status ? filters?.status : "0"); - - if (filters?.country?.length) { - url += "&country=" + filters?.country.join(","); - } - - let tags = [].concat(filters?.tags, filters?.events).filter((t) => t); - if (tags?.length) { - url += "&tags:positive=" + tags.join(","); - } - url += "&take=40"; - const result = await fetchApi(url); - const body = await result.json(); - - let novels = []; - body.resource.forEach((novel) => - novels.push({ - name: novel.names.rus, - cover: novel.poster.medium, - url: novel.url, - }) - ); - - return novels; -} - -async function parseNovelAndChapters(novelUrl) { - const novelId = novelUrl - .substring("https://ranobehub.org/ranobe/".length) - .split("-")[0]; - const result = await fetchApi(`${baseUrl}api/ranobe/${novelId}`); - const json = await result.json(); - - let novel = { - url: json.data.url, - novelUrl, - name: json.data.names.rus, - cover: json.data.posters.medium, - summary: json.data.description, - author: json.data?.authors[0]?.name_eng || "", - status: json.data.status.title.includes("процессе") - ? Status.Ongoing - : Status.Completed, - }; - - let tags = [] - .concat(json.data.tags.events, json.data.tags.genres) - .map((item) => item?.names?.rus || item?.names?.eng || item?.title) - .filter((item) => item); - - if (tags.length > 0) { - novel.genres = tags.join(", "); - } - - let novelChapters = []; - - const fetchChaptersUrl = `${baseUrl}api/ranobe/${novelId}/contents`; - - const chaptersRaw = await fetchApi(fetchChaptersUrl); - const chaptersJSON = await chaptersRaw.json(); - - chaptersJSON.volumes.forEach((volume) => - volume.chapters.forEach((chapter) => - novelChapters.push({ - name: chapter.name, - url: chapter.url, - releaseTime: dayjs(parseInt(chapter.changed_at, 10) * 1000).format("LLL"), - }) - ) - ); - - novel.chapters = novelChapters; - return novel; -} - -async function parseChapter(chapterUrl) { - const result = await fetchApi(chapterUrl); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - loadedCheerio(".chapter-hoticons").remove(); - loadedCheerio("div.text:nth-child(1) img").each(function () { - if (!loadedCheerio(this).attr("src")?.startsWith("http")) { - const dataMediaId = loadedCheerio(this).attr("data-media-id"); - loadedCheerio(this).attr("src", `${baseUrl}api/media/${dataMediaId}`); - } - }); - - let chapterText = loadedCheerio("div.text:nth-child(1)").html(); - - // Remove script tags - chapterText = chapterText?.replace( - /<\s*script[^>]*>[\s\S]*?<\/script>/gim, - "" - ); - return chapterText; -} - -async function searchNovels(searchTerm) { - const url = `${baseUrl}api/fulltext/global?query=${searchTerm}&take=5`; - - const result = await fetchApi(url); - const data = await result.json(); - - let novels = []; - - data - .find((item) => item.meta.key === "ranobe") - ?.data.forEach((novel) => - novels.push({ - name: novel.names.rus, - url: - "https://ranobehub.org/ranobe/" + - novel.url.match( - /https:\/\/ranobehub\.org\/ranobe\/(.*?)\?utm_source=search_name&utm_medium=search&utm_campaign=search_using/ - )[1], - cover: novel.image.replace("/small", "/medium"), - }) - ); - - return novels; -} - -const filters = [ - { - key: "sort", - label: "Сортировка", - values: [ - { label: "по рейтингу", value: "computed_rating" }, - { label: "по дате обновления", value: "last_chapter_at" }, - { label: "по дате добавления", value: "created_at" }, - { label: "по названию", value: "name_rus" }, - { label: "по просмотрам", value: "views" }, - { label: "по количеству глав", value: "count_chapters" }, - { label: "по объему перевода", value: "count_of_symbols" }, - ], - inputType: FilterInputs.Picker, - }, - { - key: "status", - label: "Статус перевода", - values: [ - { label: "Любой", value: "" }, - { label: "В процессе", value: "1" }, - { label: "Завершено", value: "2" }, - { label: "Заморожено", value: "3" }, - { label: "Неизвестно", value: "4" }, - ], - inputType: FilterInputs.Picker, - }, - { - key: "country", - label: "Тип", - values: [ - { label: "Китай", value: "2" }, - { label: "Корея", value: "3" }, - { label: "США", value: "4" }, - { label: "Япония", value: "1" }, - ], - inputType: FilterInputs.Checkbox, - }, - { - key: "events", - label: "События", - values: [ - { label: "[Награжденная работа]", value: "611" }, - { label: "18+", value: "338" }, - { label: "Авантюристы", value: "353" }, - { label: "Автоматоны", value: "538" }, - { label: "Агрессивные персонажи", value: "434" }, - { label: "Ад", value: "509" }, - { label: "Адаптация в радиопостановку", value: "522" }, - { label: "Академия", value: "25" }, - { label: "Актеры озвучки", value: "578" }, - { label: "Активный главный герой", value: "132" }, - { label: "Алхимия", value: "116" }, - { label: "Альтернативный мир", value: "28" }, - { label: "Амнезия/Потеря памяти", value: "247" }, - { label: "Анабиоз", value: "657" }, - { label: "Ангелы", value: "218" }, - { label: "Андрогинные персонажи", value: "217" }, - { label: "Андроиды", value: "82" }, - { label: "Анти-магия", value: "471" }, - { label: "Антигерой", value: "346" }, - { label: "Антикварный магазин", value: "572" }, - { label: "Антисоциальный главный герой", value: "562" }, - { label: "Антиутопия", value: "663" }, - { label: "Апатичный протагонист", value: "29" }, - { label: "Апокалипсис", value: "314" }, - { label: "Аранжированный брак", value: "285" }, - { label: "Армия", value: "598" }, - { label: "Артефакты", value: "117" }, - { label: "Артисты", value: "460" }, - { label: "Банды", value: "581" }, - { label: "БДСМ", value: "676" }, - { label: "Бедный главный герой", value: "309" }, - { label: "Безжалостный главный герой", value: "144" }, - { label: "Беззаботный главный герой", value: "355" }, - { label: "Безусловная любовь", value: "650" }, - { label: "Беременность", value: "131" }, - { label: "Бесполый главный герой", value: "222" }, - { label: "Бессмертные", value: "275" }, - { label: "Бесстрашный протагонист", value: "619" }, - { label: "Бесстыдный главный герой", value: "256" }, - { label: "Бесчестный главный герой", value: "699" }, - { label: "Библиотека", value: "342" }, - { label: "Бизнесмен ", value: "813" }, - { label: "Биочип", value: "120" }, - { label: "Бисексуальный главный герой", value: "822" }, - { label: "Близнецы", value: "148" }, - { label: "Боги", value: "211" }, - { label: "Богини", value: "356" }, - { label: "Боевая академия", value: "369" }, - { label: "Боевые духи", value: "347" }, - { label: "Боевые соревнования", value: "422" }, - { label: "Божественная защита", value: "336" }, - { label: "Божественные силы", value: "224" }, - { - label: - "Большая разница в возрасте между героем и его любовным интересом", - value: "348", - }, - { label: "Борьба за власть", value: "544" }, - { label: "Брак", value: "363" }, - { label: "Брак по расчету", value: "65" }, - { label: "Братский комплекс", value: "31" }, - { label: "Братство", value: "413" }, - { label: "Братья и сестры", value: "518" }, - { label: "Буддизм", value: "742" }, - { label: "Быстрая культивация", value: "273" }, - { label: "Быстрообучаемый", value: "221" }, - { label: "Валькирии", value: "667" }, - { label: "Вампиры", value: "266" }, - { label: "Ваншот", value: "679" }, - { label: "Ведьмы", value: "169" }, - { label: "Вежливый главный герой", value: "289" }, - { label: "Верные подчиненные", value: "225" }, - { label: "Взрослый главный герой", value: "183" }, - { label: "Видит то, чего не видят другие", value: "636" }, - { label: "Виртуальная реальность", value: "313" }, - { label: "Владелец магазина", value: "653" }, - { label: "Внезапная сила", value: "376" }, - { label: "Внезапное богатство", value: "802" }, - { - label: "Внешний вид отличается от фактического возраста", - value: "334", - }, - { label: "Военные Летописи", value: "740" }, - { label: "Возвращение из другого мира", value: "673" }, - { label: "Войны", value: "58" }, - { label: "Вокалоид", value: "678" }, - { label: "Волшебники/Волшебницы", value: "477" }, - { label: "Волшебные звери", value: "201" }, - { label: "Воображаемый друг", value: "614" }, - { label: "Воры", value: "326" }, - { label: "Воскрешение", value: "78" }, - { label: "Враги становятся возлюбленными", value: "428" }, - { label: "Враги становятся союзниками", value: "502" }, - { label: "Врата в другой мир", value: "558" }, - { label: "Врачи", value: "286" }, - { label: "Временной парадокс", value: "163" }, - { label: "Всемогущий главный герой", value: "42" }, - { label: "Вторжение на землю", value: "77" }, - { label: "Второй шанс", value: "112" }, - { label: "Вуайеризм", value: "851" }, - { label: "Выживание", value: "290" }, - { label: "Высокомерные персонажи", value: "268" }, - { label: "Гадание", value: "540" }, - { label: "Гарем рабов", value: "828" }, - { label: "Геймеры", value: "302" }, - { label: "Генералы", value: "223" }, - { label: "Генетические модификации", value: "620" }, - { label: "Гениальный главный герой", value: "566" }, - { label: "Герои", value: "173" }, - { label: "Героиня — сорванец", value: "525" }, - { label: "Герой влюбляется первым", value: "64" }, - { label: "Гетерохромия", value: "510" }, - { label: "Гильдии", value: "323" }, - { label: "Гипнотизм", value: "768" }, - { label: "Главный герой — бог", value: "486" }, - { label: "Главный герой — гуманоид", value: "595" }, - { label: "Главный герой — женщина", value: "63" }, - { label: "Главный герой — мужчина", value: "39" }, - { label: "Главный герой — наполовину человек", value: "362" }, - { label: "Главный герой — отец", value: "859" }, - { label: "Главный герой — раб", value: "832" }, - { label: "Главный герой — ребенок", value: "415" }, - { label: "Главный герой — рубака", value: "400" }, - { label: "Главный герой — собиратель гарема", value: "439" }, - { label: "Главный герой влюбляется первым", value: "655" }, - { label: "Главный герой играет роль", value: "396" }, - { label: "Главный герой носит очки", value: "637" }, - { label: "Главный герой пацифист", value: "675" }, - { label: "Главный герой с несколькими телами", value: "628" }, - { label: "Главный герой силен с самого начала", value: "45" }, - { label: "Гладиаторы", value: "549" }, - { label: "Глуповатый главный герой", value: "295" }, - { label: "Гоблины", value: "529" }, - { label: "Големы", value: "569" }, - { label: "Гомункул", value: "850" }, - { label: "Горничные", value: "380" }, - { label: "Госпиталь", value: "1043" }, - { label: "Готовка", value: "193" }, - { label: "Гриндинг", value: "303" }, - { label: "Дао Компаньон", value: "384" }, - { label: "Даосизм", value: "792" }, - { label: "Дарк", value: "151" }, - { label: "Дварфы", value: "220" }, - { label: "Двойная личность", value: "601" }, - { label: "Двойник", value: "547" }, - { label: "Дворецкий", value: "623" }, - { label: "Дворяне", value: "41" }, - { label: "Дворянство/Аристократия", value: "354" }, - { label: "Девушки-монстры", value: "634" }, - { label: "Демоническая техника культивации", value: "706" }, - { label: "Демоны", value: "6" }, - { label: "Денежный долг", value: "841" }, - { label: "Депрессия", value: "494" }, - { label: "Детективы", value: "561" }, - { label: "Дискриминация", value: "34" }, - { - label: "Добыча денег одно из основных стремлений главного героя", - value: "307", - }, - { label: "Долгая разлука", value: "200" }, - { label: "Домашние дела", value: "178" }, - { label: "Домогательство", value: "394" }, - { label: "Драконы", value: "195" }, - { label: "Драконьи всадники", value: "555" }, - { label: "Древние времена", value: "102" }, - { label: "Древний Китай", value: "284" }, - { label: "Дружба", value: "97" }, - { label: "Друзья детства", value: "170" }, - { label: "Друзья становятся врагами", value: "507" }, - { label: "Друиды", value: "427" }, - { label: "Дух лисы", value: "842" }, - { label: "Духи/Призраки", value: "46" }, - { label: "Духовный советник", value: "351" }, - { label: "Душевность", value: "587" }, - { label: "Души", value: "136" }, - { label: "Европейская атмосфера", value: "457" }, - { label: "Ёкаи", value: "516" }, - { label: "Есть аниме-адаптация", value: "26" }, - { label: "Есть видеоигра по мотивам", value: "491" }, - { label: "Есть манга-адаптация", value: "27" }, - { label: "Есть манхва-адаптация", value: "453" }, - { label: "Есть маньхуа-адаптация", value: "298" }, - { label: "Есть сериал-адаптация", value: "421" }, - { label: "Есть фильм по мотивам", value: "47" }, - { label: "Женища-наставник", value: "438" }, - { label: "Жертва изнасилования влюбляется в насильника", value: "414" }, - { label: "Жесткая, двуличная личность", value: "249" }, - { label: "Жестокие персонажи", value: "436" }, - { label: "Жестокое обращение с ребенком", value: "617" }, - { label: "Жестокость", value: "127" }, - { label: "Животноводство", value: "765" }, - { label: "Животные черты", value: "466" }, - { label: "Жизнь в квартире", value: "554" }, - { label: "Жрицы", value: "564" }, - { label: "Заботливый главный герой", value: "176" }, - { label: "Забывчивый главный герой", value: "579" }, - { label: "Заговоры", value: "177" }, - { label: "Закалка тела", value: "269" }, - { label: "Законники", value: "769" }, - { label: "Замкнутый главный герой", value: "533" }, - { label: "Запечатанная сила", value: "344" }, - { label: "Застенчивые персонажи", value: "443" }, - { label: "Звери", value: "119" }, - { label: "Звери-компаньоны", value: "192" }, - { label: "Злой протагонист", value: "125" }, - { label: "Злые боги", value: "437" }, - { label: "Злые организации", value: "503" }, - { label: "Злые религии", value: "725" }, - { label: "Знаменитости", value: "397" }, - { label: "Знаменитый главный герой", value: "469" }, - { label: "Знания современного мира", value: "185" }, - { label: "Зомби", value: "321" }, - { label: "Игра на выживание", value: "162" }, - { label: "Игривый протагонист", value: "700" }, - { label: "Игровая система рейтинга", value: "301" }, - { label: "Игровые элементы", value: "152" }, - { label: "Игрушки (18+)", value: "644" }, - { label: "Из грязи в князи", value: "330" }, - { label: "Из женщины в мужчину ", value: "688" }, - { label: "Из мужчины в женщину", value: "698" }, - { label: "Из полного в худого", value: "809" }, - { label: "Из слабого в сильного", value: "81" }, - { label: "Из страшно(го/й) в красиво(го/ю)", value: "810" }, - { label: "Извращенный главный герой", value: "349" }, - { label: "Изгои", value: "472" }, - { label: "Изменение расы", value: "627" }, - { label: "Изменения внешнего вида", value: "191" }, - { label: "Изменения личности", value: "99" }, - { label: "Изнасилование", value: "110" }, - { label: "Изображения жестокости", value: "260" }, - { label: "Империи", value: "749" }, - { label: "Инвалидность", value: "656" }, - { label: "Индустриализация", value: "180" }, - { label: "Инженер", value: "35" }, - { label: "Инцест", value: "463" }, - { label: "Искусственный интеллект", value: "118" }, - { label: "Исследования", value: "635" }, - { label: "Каннибализм", value: "377" }, - { label: "Карточные игры", value: "654" }, - { label: "Киберспорт", value: "658" }, - { label: "Кланы", value: "950" }, - { label: "Класс безработного [Игровой класс в игре]", value: "727" }, - { label: "Клоны", value: "365" }, - { label: "Клубы", value: "96" }, - { label: "Книги", value: "341" }, - { label: "Книги навыков", value: "312" }, - { label: "Книжный червь", value: "455" }, - { label: "Коварство", value: "111" }, - { label: "Коллеги", value: "683" }, - { label: "Колледж/Университет", value: "712" }, - { label: "Кома", value: "826" }, - { label: "Командная работа", value: "426" }, - { label: "Комедийный оттенок", value: "523" }, - { label: "Комплекс неполноценности", value: "489" }, - { label: "Комплекс семейных отношений", value: "104" }, - { label: "Конкуренция", value: "746" }, - { label: "Контракты", value: "483" }, - { label: "Контроль разума/сознания", value: "262" }, - { label: "Копейщик", value: "339" }, - { label: "Королевская битва", value: "1025" }, - { label: "Королевская власть", value: "53" }, - { label: "Королевства", value: "141" }, - { label: "Коррупция", value: "378" }, - { label: "Космические войны", value: "674" }, - { label: "Красивый герой", value: "107" }, - { label: "Крафт", value: "287" }, - { label: "Кризис личности", value: "599" }, - { label: "Кругосветное путешествие", value: "257" }, - { label: "Кудере", value: "440" }, - { label: "Кузены", value: "701" }, - { label: "Кузнец", value: "454" }, - { label: "Кукловоды", value: "431" }, - { label: "Куклы/марионетки", value: "573" }, - { label: "Культивация", value: "123" }, - { label: "Куннилингус", value: "632" }, - { label: "Легенды", value: "430" }, - { label: "Легкая жизнь", value: "604" }, - { label: "Ленивый главный герой", value: "570" }, - { label: "Лидерство", value: "424" }, - { label: "Лоли", value: "92" }, - { label: "Лотерея", value: "401" }, - { label: "Любовный интерес влюбляется первым", value: "647" }, - { label: "Любовный интерес главного героя носит очки", value: "576" }, - { label: "Любовный треугольник", value: "98" }, - { label: "Любовь детства", value: "500" }, - { label: "Любовь с первого взгляда", value: "730" }, - { label: "Магические надписи", value: "373" }, - { label: "Магические печати", value: "277" }, - { label: "Магические технологии", value: "357" }, - { - label: - "Магическое пространство/измерение, доступное не всем персонажам", - value: "244", - }, - { label: "Магия", value: "38" }, - { label: "Магия призыва", value: "333" }, - { label: "Мазохистские персонажи", value: "660" }, - { label: "Манипулятивные персонажи", value: "130" }, - { label: "Мания", value: "91" }, - { label: "Мастер на все руки", value: "390" }, - { label: "Мастурбация", value: "846" }, - { label: "Махо-сёдзё", value: "496" }, - { label: "Медицинские знания", value: "441" }, - { label: "Медленная романтическая линия", value: "113" }, - { label: "Медленное развитие на старте ", value: "670" }, - { label: "Межпространственные путешествия", value: "316" }, - { label: "Менеджмент", value: "182" }, - { label: "Мертвый главный герой", value: "794" }, - { label: "Месть", value: "88" }, - { label: "Метаморфы", value: "234" }, - { label: "Меч и магия", value: "55" }, - { label: "Мечник", value: "607" }, - { label: "Мечты", value: "733" }, - { label: "Милая история", value: "709" }, - { label: "Милое дитя", value: "596" }, - { label: "Милый главный герой", value: "697" }, - { label: "Мировое дерево", value: "385" }, - { label: "Мистический ореол вокруг семьи", value: "409" }, - { label: "Мифические звери", value: "418" }, - { label: "Мифология", value: "468" }, - { label: "Младшие братья", value: "843" }, - { label: "Младшие сестры", value: "465" }, - { label: "ММОРПГ (ЛитРПГ)", value: "306" }, - { label: "Множество перемещенных людей", value: "488" }, - { label: "Множество реальностей", value: "278" }, - { label: "Множество реинкарнированных людей", value: "227" }, - { label: "Модели", value: "649" }, - { label: "Молчаливый персонаж", value: "705" }, - { label: "Монстры", value: "69" }, - { label: "Мужская гей-пара", value: "685" }, - { label: "Мужчина-яндере", value: "155" }, - { label: "Музыка", value: "589" }, - { label: "Музыкальные группы", value: "588" }, - { label: "Мутации", value: "668" }, - { label: "Мутированные существа", value: "317" }, - { label: "Навык кражи", value: "626" }, - { label: "Навязчивая любовь", value: "85" }, - { label: "Наемники", value: "324" }, - { label: "Назойливый возлюбленный", value: "501" }, - { label: "Наивный главный герой", value: "66" }, - { label: "Наркотики", value: "661" }, - { label: "Нарциссический главный герой", value: "470" }, - { label: "Насилие сексуального характера", value: "824" }, - { label: "Наследование", value: "372" }, - { label: "Национализм", value: "318" }, - { label: "Не блещущий внешне главный герой", value: "328" }, - { label: "Не родные братья и сестры", value: "464" }, - { label: "Небеса", value: "508" }, - { label: "Небесное испытание", value: "274" }, - { label: "Негуманоидный главный герой", value: "622" }, - { label: "Недоверчивый главный герой", value: "485" }, - { label: "Недооцененный главный герой", value: "140" }, - { label: "Недоразумения", value: "202" }, - { label: "Неизлечимая болезнь", value: "75" }, - { label: "Некромант", value: "308" }, - { label: "Нелинейная история", value: "157" }, - { label: "Ненавистный главный герой", value: "487" }, - { label: "Ненадежный рассказчик", value: "165" }, - { label: "Нерезиденты", value: "821" }, - { label: "Нерешительный главный герой", value: "741" }, - { label: "Несерьезный главный герой", value: "294" }, - { label: "Несколько временных линий", value: "517" }, - { label: "Несколько главных героев", value: "475" }, - { label: "Несколько идентичностей", value: "615" }, - { label: "Несколько личностей", value: "474" }, - { label: "Нетораре", value: "721" }, - { label: "Нетори", value: "450" }, - { label: "Неудачливый главный герой", value: "567" }, - { label: "Ниндзя", value: "358" }, - { label: "Обещание из детства", value: "738" }, - { label: "Обманщик", value: "411" }, - { label: "Обмен телами", value: "94" }, - { label: "Обнаженка", value: "417" }, - { label: "Обольщение", value: "718" }, - { label: "Оборотни", value: "624" }, - { label: "Обратный гарем", value: "255" }, - { label: "Общество монстров", value: "226" }, - { label: "Обязательство", value: "548" }, - { label: "Огнестрельное оружие", value: "179" }, - { label: "Ограниченная продолжительность жизни", value: "73" }, - { label: "Одержимость", value: "447" }, - { label: "Одинокий главный герой", value: "304" }, - { label: "Одиночество", value: "199" }, - { label: "Одиночное проживание", value: "207" }, - { label: "Околосмертные переживания", value: "773" }, - { label: "Оммёдзи", value: "542" }, - { label: "Омоложение", value: "237" }, - { label: "Организованная преступность", value: "478" }, - { label: "Оргия", value: "825" }, - { label: "Орки", value: "228" }, - { label: "Освоение навыков", value: "235" }, - { label: "Основано на аниме", value: "553" }, - { label: "Основано на видео игре", value: "704" }, - { label: "Основано на визуальной новелле ", value: "861" }, - { label: "Основано на песне", value: "677" }, - { label: "Основано на фильме", value: "552" }, - { label: "Осторожный главный герой", value: "122" }, - { label: "Отаку", value: "512" }, - { label: "Открытый космос", value: "150" }, - { label: "Отношения в сети", value: "713" }, - { label: "Отношения между богом и человеком", value: "603" }, - { label: "Отношения между людьми и нелюдьми", value: "205" }, - { label: "Отношения на расстоянии", value: "852" }, - { label: "Отношения начальник-подчиненный", value: "590" }, - { label: "Отношения Сенпай-Коухай", value: "513" }, - { label: "Отношения ученика и учителя", value: "451" }, - { label: "Отношения учитель-ученик", value: "343" }, - { label: "Отношения хозяин-слуга", value: "206" }, - { label: "Отомэ игра", value: "723" }, - { label: "Отсутствие здравого смысла", value: "605" }, - { label: "Отсутствие родителей", value: "575" }, - { label: "Офисный роман", value: "686" }, - { label: "Официанты", value: "681" }, - { label: "Охотники", value: "288" }, - { label: "Очаровательный главный герой", value: "659" }, - { label: "Падшее дворянство", value: "618" }, - { label: "Падшие ангелы", value: "505" }, - { label: "Пайзури", value: "630" }, - { label: "Паразиты", value: "535" }, - { label: "Параллельные миры", value: "86" }, - { label: "Парк развлечений", value: "586" }, - { label: "Пародия", value: "319" }, - { label: "Певцы/Певицы", value: "734" }, - { label: "Первая любовь", value: "716" }, - { label: "Первоисточник новеллы — манга", value: "560" }, - { label: "Первый раз", value: "845" }, - { - label: "Перемещение в другой мир, имея при себе современные достижения", - value: "732", - }, - { label: "Перемещение в игровой мир", value: "532" }, - { label: "Перемещение в иной мир", value: "754" }, - { label: "Перерождение в ином мире", value: "755" }, - { label: "Переселение души/Трансмиграция", value: "139" }, - { label: "Персонаж использует щит", value: "631" }, - { label: "Петля времени", value: "79" }, - { label: "Пираты", value: "817" }, - { label: "Писатели", value: "408" }, - { label: "Питомцы", value: "253" }, - { label: "Племенное общество", value: "291" }, - { label: "Повелитель демонов", value: "171" }, - { - label: "Повествование от нескольких лиц/Несколько точек зрения", - value: "156", - }, - { label: "Подземелья", value: "219" }, - { label: "Пожелания", value: "166" }, - { label: "Познание Дао", value: "360" }, - { label: "Покинутое дитя", value: "534" }, - { label: "Полигамия", value: "296" }, - { label: "Политика", value: "43" }, - { label: "Полиция", value: "801" }, - { label: "Полулюди", value: "335" }, - { label: "Пользователь уникального оружия", value: "432" }, - { label: "Популярный любовный интерес", value: "241" }, - { label: "Постапокалиптика", value: "60" }, - { label: "Потерянные цивилизации", value: "751" }, - { label: "Похищения людей", value: "707" }, - { label: "Поэзия", value: "404" }, - { label: "Правонарушители", value: "694" }, - { label: "Прагматичный главный герой", value: "536" }, - { label: "Преданный любовный интерес", value: "106" }, - { label: "Предательство", value: "103" }, - { label: "Предвидение", value: "52" }, - { label: "Прекрасная героиня", value: "30" }, - { label: "Преступники", value: "728" }, - { label: "Преступность", value: "83" }, - { label: "Призванный герой", value: "147" }, - { label: "Призраки", value: "51" }, - { label: "Принуждение к отношениям", value: "602" }, - { label: "Принцессы", value: "933" }, - { label: "Притворная пара", value: "600" }, - { label: "Причудливые персонажи", value: "297" }, - { label: "Пришельцы/Инопланетяне", value: "76" }, - { label: "Программист", value: "666" }, - { label: "Проклятия", value: "48" }, - { label: "Промывание мозгов", value: "519" }, - { label: "Пропуск времени", value: "138" }, - { label: "Пророчества", value: "429" }, - { label: "Проститутки", value: "844" }, - { label: "Пространственное манипулирование", value: "375" }, - { label: "Прошлое играет большую роль", value: "215" }, - { label: "Прыжки между мирами", value: "737" }, - { label: "Психические силы", value: "265" }, - { label: "Психопаты", value: "158" }, - { label: "Путешествие во времени", value: "80" }, - { label: "Пытка", value: "168" }, - { label: "Рабы", value: "829" }, - { label: "Развод", value: "771" }, - { label: "Разумные предметы", value: "174" }, - { label: "Расизм", value: "320" }, - { label: "Рассказ", value: "61" }, - { label: "Расторжения помолвки", value: "243" }, - { label: "Расы зооморфов", value: "209" }, - { label: "Ревность", value: "717" }, - { label: "Редакторы", value: "684" }, - { label: "Реинкарнация", value: "281" }, - { label: "Реинкарнация в монстра", value: "204" }, - { label: "Реинкарнация в объект", value: "692" }, - { label: "Религии", value: "565" }, - { label: "Репортеры", value: "837" }, - { label: "Ресторан", value: "652" }, - { label: "Решительный главный герой", value: "124" }, - { label: "Робкий главный герой", value: "800" }, - { label: "Родитель одиночка", value: "687" }, - { label: "Родительский комплекс", value: "531" }, - { label: "Родословная", value: "121" }, - { label: "Романтический подсюжет ", value: "159" }, - { label: "Рост персонажа", value: "95" }, - { label: "Рыцари", value: "142" }, - { label: "Садистские персонажи", value: "642" }, - { label: "Самоотверженный главный герой", value: "748" }, - { label: "Самоубийства", value: "616" }, - { label: "Самурай", value: "646" }, - { label: "Сборник коротких историй", value: "456" }, - { label: "Связанные сюжетные линии", value: "473" }, - { label: "Святые", value: "761" }, - { label: "Священники", value: "325" }, - { label: "Сдержанный главный герой", value: "799" }, - { label: "Секретные организации", value: "331" }, - { label: "Секреты", value: "577" }, - { label: "Секс рабы", value: "830" }, - { label: "Семейный конфликт", value: "770" }, - { label: "Семь добродетелей", value: "233" }, - { label: "Семь смертных грехов", value: "134" }, - { label: "Семья", value: "251" }, - { label: "Сёнэн-ай подсюжет ", value: "664" }, - { label: "Серийные убийцы", value: "497" }, - { label: "Сестринский комплекс", value: "498" }, - { label: "Сила духа", value: "282" }, - { label: "Сила, требующая платы за пользование", value: "520" }, - { label: "Сильная пара", value: "109" }, - { label: "Сильный в сильнейшего", value: "359" }, - { label: "Сильный любовный интерес", value: "161" }, - { label: "Синдром восьмиклассника", value: "90" }, - { label: "Сироты", value: "214" }, - { label: "Система уровней", value: "198" }, - { label: "Системный администратор", value: "476" }, - { label: "Скрытие истинной личности", value: "371" }, - { label: "Скрытие истинных способностей", value: "252" }, - { label: "Скрытный главный герой", value: "133" }, - { label: "Скрытые способности", value: "128" }, - { label: "Скульпторы", value: "366" }, - { label: "Слабо выраженная романтическая линия", value: "213" }, - { label: "Слабый главный герой", value: "188" }, - { label: "Слепой главный герой", value: "864" }, - { label: "Слуги", value: "232" }, - { label: "Смерть", value: "49" }, - { label: "Смерть близких", value: "361" }, - { label: "Собственнические персонажи", value: "108" }, - { label: "Современность", value: "40" }, - { label: "Сожительство", value: "482" }, - { label: "Создание армии", value: "175" }, - { label: "Создание артефактов", value: "292" }, - { label: "Создание клана", value: "448" }, - { label: "Создание королевства", value: "181" }, - { label: "Создание навыков", value: "236" }, - { label: "Создание секты", value: "393" }, - { label: "Солдаты/Военные", value: "71" }, - { label: "Сон", value: "571" }, - { label: "Состоятельные персонажи", value: "67" }, - { label: "Социальная иерархия на основе силы", value: "137" }, - { label: "Социальные изгои", value: "480" }, - { label: "Спасение мира", value: "597" }, - { label: "Специальные способности", value: "54" }, - { label: "Спокойный главный герой", value: "32" }, - { label: "Справедливый главный герой", value: "702" }, - { label: "Средневековье", value: "184" }, - { label: "Ссорящаяся пара", value: "293" }, - { label: "Сталкеры", value: "689" }, - { label: "Старение", value: "190" }, - { label: "Стоические персонажи", value: "444" }, - { label: "Стокгольмский синдром", value: "643" }, - { label: "Стратег", value: "425" }, - { label: "Стратегические битвы", value: "160" }, - { label: "Стратегия", value: "1038" }, - { label: "Стрелки", value: "458" }, - { label: "Стрельба из лука", value: "383" }, - { label: "Студенческий совет", value: "490" }, - { label: "Судьба", value: "271" }, - { label: "Суккубы", value: "484" }, - { label: "Супер герои", value: "1039" }, - { label: "Суровая подготовка", value: "261" }, - { label: "Таинственная болезнь", value: "74" }, - { label: "Таинственное прошлое", value: "263" }, - { label: "Тайная личность", value: "310" }, - { label: "Тайные отношения", value: "812" }, - { label: "Танцоры", value: "840" }, - { label: "Телохранители", value: "452" }, - { label: "Тентакли", value: "693" }, - { label: "Террористы", value: "515" }, - { label: "Технологический разрыв", value: "621" }, - { label: "Тихие персонажи", value: "546" }, - { label: "Толстый главный герой", value: "299" }, - { label: "Торговцы", value: "416" }, - { label: "Травля/Буллинг", value: "89" }, - { label: "Травник", value: "708" }, - { label: "Трагическое прошлое", value: "164" }, - { label: "Трансплантация воспоминаний", value: "367" }, - { label: "Трап (Путаница с гендером персонажа)", value: "582" }, - { label: "Трудолюбивый главный герой", value: "37" }, - { label: "Тюрьма", value: "479" }, - { label: "Убийства", value: "84" }, - { label: "Убийцы", value: "248" }, - { label: "Убийцы драконов", value: "606" }, - { label: "Уверенный главный герой", value: "270" }, - { label: "Удачливый главный герой", value: "402" }, - { label: "Укротитель монстров", value: "337" }, - { label: "Умения из прошлой жизни", value: "280" }, - { label: "Умная пара", value: "493" }, - { label: "Умный главный герой", value: "33" }, - { label: "Уникальная техника Культивации", value: "254" }, - { label: "Уникальное оружие", value: "340" }, - { label: "Управление бизнесом", value: "315" }, - { label: "Управление временем", value: "167" }, - { label: "Управление кровью", value: "764" }, - { label: "Упрямый главный герой", value: "672" }, - { label: "Уродливый главный герой", value: "803" }, - { label: "Ускоренный рост", value: "300" }, - { label: "Усыновленные дети", value: "481" }, - { label: "Усыновленный главный герой", value: "412" }, - { label: "Уход за детьми", value: "398" }, - { label: "Учителя", value: "345" }, - { label: "Фамильяры", value: "541" }, - { label: "Фанатизм", value: "613" }, - { label: "Фантастические существа", value: "322" }, - { label: "Фанфикшн", value: "388" }, - { label: "Фармацевт", value: "715" }, - { label: "Фарминг", value: "379" }, - { label: "Феи", value: "210" }, - { label: "Фелляция", value: "584" }, - { label: "Фениксы", value: "374" }, - { label: "Фетиш груди", value: "499" }, - { label: "Философия", value: "87" }, - { label: "Фильмы", value: "403" }, - { label: "Флэшбэки", value: "528" }, - { label: "Фобии", value: "100" }, - { label: "Фольклор", value: "467" }, - { label: "Футанари", value: "568" }, - { label: "Футуристический сеттинг", value: "382" }, - { label: "Фэнтези мир", value: "126" }, - { label: "Хакеры", value: "399" }, - { label: "Харизматический герой", value: "391" }, - { label: "Хикикомори/Затворники", value: "462" }, - { label: "Хитроумный главный герой", value: "105" }, - { label: "Хозяин подземелий", value: "557" }, - { label: "Холодный главный герой", value: "259" }, - { label: "Хорошие отношения с семьей", value: "506" }, - { label: "Хранители могил", value: "68" }, - { label: "Целители", value: "389" }, - { label: "Цзянши", value: "735" }, - { label: "Цундэрэ", value: "445" }, - { label: "Чаты", value: "580" }, - { label: "Человеческое оружие", value: "521" }, - { label: "Честный главный герой", value: "240" }, - { label: "Читы", value: "238" }, - { label: "Шантаж", value: "526" }, - { label: "Шеф-повар", value: "239" }, - { label: "Шикигами", value: "543" }, - { label: "Школа только для девочек", value: "633" }, - { label: "Шота", value: "514" }, - { label: "Шоу-бизнес", value: "407" }, - { label: "Шпионы", value: "563" }, - { label: "Эволюция", value: "196" }, - { label: "Эгоистичный главный герой", value: "539" }, - { label: "Эйдетическая память", value: "392" }, - { label: "Экзорсизм", value: "504" }, - { label: "Экономика", value: "492" }, - { label: "Эксгибиционизм", value: "639" }, - { label: "Эксперименты с людьми", value: "129" }, - { label: "Элементальная магия", value: "395" }, - { label: "Эльфы", value: "172" }, - { label: "Эмоционально слабый главный герой", value: "816" }, - { label: "Эпизодический", value: "612" }, - { label: "Юный любовный интерес", value: "446" }, - { label: "Яды", value: "410" }, - { label: "Языкастые персонажи", value: "406" }, - { label: "Языковой барьер", value: "59" }, - { label: "Яндере", value: "208" }, - { label: "Японские силы самообороны", value: "559" }, - { label: "Ярко выраженная романтическая линия", value: "272" }, - { label: "Abusive Characters", value: "625" }, - { label: "Adapted to Visual Novel", value: "1046" }, - { label: "Adopted-lead", value: "886" }, - { label: "Adultery", value: "866" }, - { label: "Affair", value: "645" }, - { label: "Age-gap", value: "923" }, - { label: "Almost-human-lead", value: "975" }, - { label: "An*l", value: "780" }, - { label: "Androgynous-male-lead", value: "1002" }, - { label: "Anti-hero-lead", value: "976" }, - { label: "Apathetic-lead", value: "967" }, - { label: "Arms Dealers", value: "839" }, - { label: "Army-commander", value: "1003" }, - { label: "Artifact-refining", value: "924" }, - { label: "Autism", value: "855" }, - { label: "Awkward Protagonist", value: "669" }, - { label: "Awkward-lead", value: "892" }, - { label: "Bestiality", value: "796" }, - { label: "Big-breasts", value: "908" }, - { label: "Birth-of-a-nation", value: "1004" }, - { label: "Bisexual-lead", value: "1027" }, - { label: "Body-refining", value: "947" }, - { label: "Body-swap/s", value: "1036" }, - { label: "Boss-subordinate-relationship", value: "876" }, - { label: "Bride-kidnapping", value: "948" }, - { label: "Caring-lead", value: "879" }, - { label: "Cautious-lead", value: "1032" }, - { label: "Changed-man", value: "925" }, - { label: "Charismatic-lead", value: "1005" }, - { label: "Child-lead", value: "936" }, - { label: "Clever-lead", value: "880" }, - { label: "Clumsy Love Interests", value: "815" }, - { label: "Cold Love Interests", value: "651" }, - { label: "Coming of Age", value: "860" }, - { label: "Confident-lead", value: "894" }, - { label: "Confident-male-lead", value: "951" }, - { label: "Confinement", value: "767" }, - { label: "Conflicting Loyalties", value: "793" }, - { label: "Couple Growth", value: "690" }, - { label: "Court Official", value: "863" }, - { label: "Cowardly Protagonist", value: "703" }, - { label: "Cowardly-lead", value: "1000" }, - { label: "Cross-dressing", value: "250" }, - { label: "Cunnilingus", value: "867" }, - { label: "Cunning-lead", value: "952" }, - { label: "Cunning-male-lead", value: "953" }, - { label: "Curious Protagonist", value: "849" }, - { label: "Cute-lead", value: "887" }, - { label: "Dense-lead", value: "868" }, - { label: "Determined-lead", value: "972" }, - { label: "Developing-technology", value: "1006" }, - { label: "Devil/s", value: "1007" }, - { label: "Different Social Status", value: "758" }, - { label: "Disfigurement", value: "838" }, - { label: "Doting Love Interests", value: "665" }, - { label: "Doting Older Siblings", value: "609" }, - { label: "Doting Parents", value: "530" }, - { label: "Dungeon/s-exploring", value: "990" }, - { label: "Elderly Protagonist", value: "774" }, - { label: "Enlightenment", value: "729" }, - { label: "Eunuch", value: "1047" }, - { label: "Eye Powers", value: "386" }, - { label: "Family Business", value: "785" }, - { label: "Famous Parents", value: "790" }, - { label: "Famous-lead", value: "897" }, - { label: "Fanfic", value: "942" }, - { label: "Fated Lovers", value: "797" }, - { label: "Fellatio", value: "870" }, - { label: "Female-lead", value: "888" }, - { label: "First-time-intercourse", value: "871" }, - { label: "Fleet Battles", value: "836" }, - { label: "Forced Living Arrangements", value: "808" }, - { label: "Forced Marriage", value: "835" }, - { label: "Former Hero", value: "752" }, - { label: "Fujoshi", value: "1045" }, - { label: "Galge", value: "848" }, - { label: "Gambling", value: "1031" }, - { label: "Gamelit", value: "994" }, - { label: "Genderless-lead", value: "1008" }, - { label: "Glasses-wearing-lead", value: "995" }, - { label: "Guardian Relationship", value: "197" }, - { label: "H*ndjob", value: "629" }, - { label: "Half-human-lead", value: "956" }, - { label: "Hard-working-lead", value: "957" }, - { label: "Hard-working-male-lead", value: "958" }, - { label: "Hard-working-protagonist/s", value: "1030" }, - { label: "Harem-seeking-lead", value: "929" }, - { label: "Harem-subtext", value: "909" }, - { label: "Helpful Protagonist", value: "760" }, - { label: "Helpful-lead", value: "882" }, - { label: "Hidden-gem", value: "998" }, - { label: "High-fantasy", value: "991" }, - { label: "Human-becomes-demon/monster", value: "1009" }, - { label: "Human-nonhuman-relationship", value: "914" }, - { label: "Humanoid-lead", value: "915" }, - { label: "Imperial Harem", value: "805" }, - { label: "Incubus", value: "1049" }, - { label: "Insects", value: "831" }, - { label: "Interspatial-storage", value: "959" }, - { label: "Kind Love Interests", value: "757" }, - { label: "Large-number-of-skills", value: "1010" }, - { label: "Legendary-hero", value: "1011" }, - { label: "Litrpg", value: "977" }, - { label: "Love Rivals", value: "788" }, - { label: "Lovers Reunited", value: "853" }, - { label: "Low-fantasy", value: "1037" }, - { label: "Master-disciple-relationship", value: "899" }, - { label: "Matriarchy", value: "807" }, - { label: "Mature-lead", value: "900" }, - { label: "Mind Break", value: "862" }, - { label: "Mismatched Couple", value: "695" }, - { label: "Mob Protagonist", value: "648" }, - { label: "Mob-lead", value: "902" }, - { label: "Modern", value: "988" }, - { label: "Monster-pov", value: "1012" }, - { label: "Mpreg", value: "856" }, - { label: "Multiple-povs", value: "1013" }, - { label: "Mystery Solving", value: "710" }, - { label: "Naive-lead", value: "1014" }, - { label: "Near-death-experience", value: "996" }, - { label: "Neet", value: "495" }, - { label: "Nightmares", value: "833" }, - { label: "Nobility", value: "1015" }, - { label: "Non-human-lead", value: "978" }, - { label: "Non-humanoid-lead", value: "1016" }, - { label: "Not-so-secret-identity", value: "979" }, - { label: "Omegaverse", value: "857" }, - { label: "Online-game", value: "910" }, - { label: "Online-gaming", value: "911" }, - { label: "Outdoor Interc**rse", value: "847" }, - { label: "Outdoor-intercourse", value: "903" }, - { label: "Overprotective Siblings", value: "610" }, - { label: "Part-Time Job", value: "865" }, - { label: "Past Trauma", value: "726" }, - { label: "Past-memories", value: "1017" }, - { label: "Persistent Love Interests", value: "714" }, - { label: "Perverted-lead", value: "932" }, - { label: "Photography", value: "798" }, - { label: "Pill Based Cultivation", value: "279" }, - { label: "Pill Concocting", value: "245" }, - { label: "Pilots", value: "820" }, - { label: "Playboys", value: "795" }, - { label: "Playful-lead", value: "989" }, - { label: "Polyandry", value: "811" }, - { label: "Portal-fantasy-/-isekai", value: "992" }, - { label: "Pragmatic-lead", value: "884" }, - { label: "Profanity", value: "980" }, - { label: "Protagonist-loyal-to-love-interest", value: "905" }, - { label: "R-15 (Японское возрастное ограничение)", value: "230" }, - { label: "Rebellion", value: "834" }, - { label: "Reincarnated in a Game World", value: "804" }, - { label: "Reluctant Protagonist", value: "696" }, - { label: "Reverse R*pe", value: "776" }, - { label: "Reversible Couple", value: "854" }, - { label: "Ruling-class", value: "943" }, - { label: "S*x Friends", value: "750" }, - { label: "S*xual Cultivation Technique", value: "823" }, - { label: "Salaryman", value: "1035" }, - { label: "Satire", value: "981" }, - { label: "Schemes And Conspiracies", value: "745" }, - { label: "Scientist/s", value: "997" }, - { label: "Scientists", value: "1029" }, - { label: "Secret Crush", value: "827" }, - { label: "Seme Protagonist", value: "1034" }, - { label: "Seme-lead", value: "1028" }, - { label: "Sentimental Protagonist", value: "1048" }, - { label: "Seven-heavenly-virtues", value: "1018" }, - { label: "Sexual Cultivation Technique", value: "350" }, - { label: "Sexual-content", value: "982" }, - { label: "Sexuality", value: "983" }, - { label: "Sharing A Body", value: "814" }, - { label: "Shotacon", value: "858" }, - { label: "Shoujo-Ai Subplot", value: "777" }, - { label: "Sibling Rivalry", value: "766" }, - { label: "Sibling's Care", value: "1042" }, - { label: "Sickly Characters", value: "787" }, - { label: "Skills", value: "1019" }, - { label: "Slave-harem", value: "1001" }, - { label: "Slime", value: "1020" }, - { label: "Smart-male-lead", value: "963" }, - { label: "Spirit Users", value: "739" }, - { label: "Straight Seme", value: "819" }, - { label: "Straight Uke", value: "818" }, - { label: "Strength-based-social-hierarchy", value: "974" }, - { label: "Strong-lead", value: "944" }, - { label: "Strong-male-lead", value: "964" }, - { label: "Student-teacher-relationship", value: "1026" }, - { label: "Suspense", value: "987" }, - { label: "Sword-and-sorcery", value: "875" }, - { label: "Threesome", value: "574" }, - { label: "Transformation Ability", value: "736" }, - { label: "Traumatising-content", value: "1040" }, - { label: "Underestimated-lead", value: "919" }, - { label: "Underestimated-male-lead", value: "966" }, - { label: "Unique-abilities", value: "1021" }, - { label: "Unique-skill", value: "1022" }, - { label: "Unique-skills", value: "1023" }, - { label: "Unlimited Flow", value: "1044" }, - { label: "Unrequited Love", value: "806" }, - { label: "Urban-fantasy", value: "1041" }, - { label: "Villainess Noble Girls", value: "719" }, - { label: "Weak-lead", value: "885" }, - { label: "Web-novel", value: "1024" }, - ], - inputType: FilterInputs.Checkbox, - }, - { - key: "tags", - label: "Жанры", - values: [ - { label: "Боевые искусства", value: "22" }, - { label: "Гарем", value: "114" }, - { label: "Гендер бендер", value: "246" }, - { label: "Дзёсэй", value: "216" }, - { label: "Для взрослых", value: "115" }, - { label: "Для взрослых", value: "258" }, - { label: "Драма", value: "7" }, - { label: "Исторический", value: "101" }, - { label: "Комедия", value: "17" }, - { label: "Лоликон", value: "638" }, - { label: "Магический реализм", value: "922" }, - { label: "Меха", value: "24" }, - { label: "Милитари", value: "12" }, - { label: "Мистика", value: "2" }, - { label: "Научная фантастика", value: "13" }, - { label: "Непристойность", value: "747" }, - { label: "Повседневность", value: "93" }, - { label: "Приключение", value: "11" }, - { label: "Психология", value: "18" }, - { label: "Романтика", value: "9" }, - { label: "Сверхъестественное", value: "20" }, - { label: "Сёдзё", value: "15" }, - { label: "Сёдзё-ай", value: "23" }, - { label: "Сёнэн", value: "189" }, - { label: "Сёнэн-ай", value: "680" }, - { label: "Спорт", value: "420" }, - { label: "Сэйнэн", value: "5" }, - { label: "Сюаньхуа", value: "242" }, - { label: "Сянься", value: "364" }, - { label: "Трагедия", value: "19" }, - { label: "Триллер", value: "3" }, - { label: "Ужасы", value: "1" }, - { label: "Уся", value: "720" }, - { label: "Фэнтези", value: "8" }, - { label: "Школьная жизнь", value: "21" }, - { label: "Экшн", value: "14" }, - { label: "Эччи", value: "327" }, - { label: "Юри", value: "691" }, - { label: "Яой", value: "682" }, - { label: "Eastern fantasy", value: "907" }, - { label: "Isekai", value: "999" }, - { label: "Video games", value: "993" }, - ], - inputType: FilterInputs.Checkbox, - }, -]; - -module.exports = { - id: pluginId, - name: sourceName, - version: "1.0.0", - site: baseUrl, - icon: "src/ru/ranobehub/icon.png", - lang: languages.Russian, - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, - filters, -}; diff --git a/plugins/russian/ranobelib.broken.ts b/plugins/russian/ranobelib.broken.ts new file mode 100644 index 0000000..8218f91 --- /dev/null +++ b/plugins/russian/ranobelib.broken.ts @@ -0,0 +1,406 @@ +import cheerio from "cheerio"; +import dayjs from "dayjs"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { NovelStatus } from "@libs/novelStatus"; +import { FilterInputs } from "@libs/filterInputs"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "RLIB"; +export const name = "RanobeLib"; +export const site = "https://ranobelib.me"; +export const version = "1.0.0"; +export const icon = "src/ru/ranobelib/icon.png"; + +const baseUrl = site; +var ui = null; + +export const popularNovels: Plugin.popularNovels = async function ( + page, + { showLatestNovels, filters } +) { + let url = `${baseUrl}/manga-list?sort=`; + url += showLatestNovels ? "last_chapter_at" : filters?.sort || "rate"; + url += "&dir=" + (filters?.order || "desc"); + + if (filters) { + if (Array.isArray(filters.type) && filters.type.length) { + url += filters.type.map((i) => `&types[]=${i}`).join(""); + } + + if (Array.isArray(filters.format) && filters.format.length) { + url += filters.format + .map((i) => `&format[include][]=${i}`) + .join(""); + } + + if (Array.isArray(filters.status) && filters.status.length) { + url += filters.status.map((i) => `&status[]=${i}`).join(""); + } + + if (Array.isArray(filters.statuss) && filters.statuss.length) { + url += filters.statuss.map((i) => `&manga_status[]=${i}`).join(""); + } + + if (Array.isArray(filters.genres) && filters.genres.length) { + url += filters.genres + .map((i) => `&genres[include][]=${i}`) + .join(""); + } + + if (Array.isArray(filters.tags) && filters.tags.length) { + url += filters.tags.map((i) => `&tags[include][]=${i}`).join(""); + } + } + + url += "&page=" + page; + + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = cheerio.load(body); + ui = loadedCheerio("a.header-right-menu__item") + .attr("href") + ?.replace?.(/[^0-9]/g, ""); + + let novels: Novel.Item[] = []; + loadedCheerio(".media-card-wrap").each(function () { + const name = loadedCheerio(this).find(".media-card__title").text(); + const cover = loadedCheerio(this).find("a.media-card").attr("data-src"); + const url = loadedCheerio(this).find("a.media-card").attr("href"); + if (!url) return; + novels.push({ name, cover, url }); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const result = await fetchApi(novelUrl); + const body = await result.text(); + + const loadedCheerio = cheerio.load(body); + + let novel: Novel.instance = { + url: novelUrl, + }; + + novel.name = loadedCheerio(".media-name__main").text().trim(); + + novel.cover = loadedCheerio(".media-sidebar__cover img").attr("src"); + + novel.summary = loadedCheerio(".media-description__text").text().trim(); + + novel.genres = loadedCheerio('div[class="media-tags"]') + .text() + .trim() + .replace(/[\n\r]+/g, ", ") + .replace(/ /g, ""); + + loadedCheerio( + 'div[class="media-info-list paper"] > [class="media-info-list__item"]' + ).each(function () { + let name = loadedCheerio(this) + .find('div[class="media-info-list__title"]') + .text(); + + if (name === "Статус перевода") { + novel.status = + loadedCheerio(this) + .find("div:nth-child(2)") + .text() + .trim() === "Продолжается" + ? NovelStatus.Ongoing + : NovelStatus.Completed; + } else if (name === "Автор") { + novel.author = loadedCheerio(this) + .find("div:nth-child(2)") + .text() + .trim(); + } + }); + + let chapters: Chapter.Item[] = []; + + let chaptersJson = body.match( + /window.__DATA__ = [\s\S]*?window._SITE_COLOR_/gm + ); + + chaptersJson = chaptersJson?.[0] + ?.replace("window.__DATA__ = ", "") + ?.replace("window._SITE_COLOR_", "") + ?.trim() + ?.slice(0, -1); + + chaptersJson = JSON.parse(chaptersJson); + ui = chaptersJson?.user?.id; + + chaptersJson.chapters.list.forEach((chapter) => + chapters.push({ + name: `Том ${chapter.chapter_volume} Глава ${chapter.chapter_number} ${chapter.chapter_name}`?.trim(), + releaseTime: dayjs(chapter.chapter_created_at).format("LLL"), + url: + `${baseUrl}/${chaptersJson.manga.slug}/v${chapter.chapter_volume}/c${chapter.chapter_number}?bid=` + + (chapter?.branch_id || ""), + }) + ); + + novel.chapters = chapters.reverse(); + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi(chapterUrl + (ui ? `&ui=${ui}` : "")); + const body = await result.text(); + + const loadedCheerio = cheerio.load(body); + + loadedCheerio(".reader-container img").each(function () { + const url = + loadedCheerio(this).attr("data-src") || + loadedCheerio(this).attr("src"); + if (!url?.startsWith("http")) { + loadedCheerio(this).attr("src", baseUrl + url); + } else { + loadedCheerio(this).attr("src", url); + } + loadedCheerio(this).removeAttr("data-src"); + }); + + const chapterText = loadedCheerio(".reader-container").html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const result = await fetchApi( + `${baseUrl}/search?q=${searchTerm}&type=manga` + ); + const body = await result.json(); + + let novels: Novel.Item[] = []; + body.forEach((novel) => + novels.push({ + name: novel?.rus_name || novel.name, + cover: novel.coverImage, + url: novel?.href || baseUrl + "/" + novel.slug, + }) + ); + + return novels; +}; + +export const filters = [ + { + key: "sort", + label: "Сортировка", + values: [ + { label: "Рейтинг", value: "rate" }, + { label: "Имя", value: "name" }, + { label: "Просмотры", value: "views" }, + { label: "Дате добавления", value: "created_at" }, + { label: "Дате обновления", value: "last_chapter_at" }, + { label: "Количество глав", value: "chap_count" }, + ], + inputType: FilterInputs.Picker, + }, + { + key: "order", + label: "Порядок", + values: [ + { label: "По убыванию", value: "desc" }, + { label: "По возрастанию", value: "asc" }, + ], + inputType: FilterInputs.Picker, + }, + { + key: "type", + label: "Тип", + values: [ + { label: "Авторский", value: "14" }, + { label: "Английский", value: "13" }, + { label: "Китай", value: "12" }, + { label: "Корея", value: "11" }, + { label: "Фанфик", value: "15" }, + { label: "Япония", value: "10" }, + ], + inputType: FilterInputs.Checkbox, + }, + { + key: "format", + label: "Формат выпуска", + values: [ + { label: "4-кома (Ёнкома)", value: "1" }, + { label: "В цвете", value: "4" }, + { label: "Веб", value: "6" }, + { label: "Вебтун", value: "7" }, + { label: "Додзинси", value: "3" }, + { label: "Сборник", value: "2" }, + { label: "Сингл", value: "5" }, + ], + inputType: FilterInputs.Checkbox, + }, + { + key: "status", + label: "Статус перевода", + values: [ + { label: "Продолжается", value: "1" }, + { label: "Завершен", value: "2" }, + { label: "Заморожен", value: "3" }, + { label: "Заброшен", value: "4" }, + ], + inputType: FilterInputs.Checkbox, + }, + { + key: "statuss", + label: "Статус тайтла", + values: [ + { label: "Онгоинг", value: "1" }, + { label: "Завершён", value: "2" }, + { label: "Анонс", value: "3" }, + { label: "Приостановлен", value: "4" }, + { label: "Выпуск прекращён", value: "5" }, + ], + inputType: FilterInputs.Checkbox, + }, + { + key: "genres", + label: "Жанры", + values: [ + { label: "Арт", value: "32" }, + { label: "Безумие", value: "91" }, + { label: "Боевик", value: "34" }, + { label: "Боевые искусства", value: "35" }, + { label: "Вампиры", value: "36" }, + { label: "Военное", value: "89" }, + { label: "Гарем", value: "37" }, + { label: "Гендерная интрига", value: "38" }, + { label: "Героическое фэнтези", value: "39" }, + { label: "Демоны", value: "81" }, + { label: "Детектив", value: "40" }, + { label: "Детское", value: "88" }, + { label: "Дзёсэй", value: "41" }, + { label: "Драма", value: "43" }, + { label: "Игра", value: "44" }, + { label: "Исекай", value: "79" }, + { label: "История", value: "45" }, + { label: "Киберпанк", value: "46" }, + { label: "Кодомо", value: "76" }, + { label: "Комедия", value: "47" }, + { label: "Космос", value: "83" }, + { label: "Магия", value: "85" }, + { label: "Махо-сёдзё", value: "48" }, + { label: "Машины", value: "90" }, + { label: "Меха", value: "49" }, + { label: "Мистика", value: "50" }, + { label: "Музыка", value: "80" }, + { label: "Научная фантастика", value: "51" }, + { label: "Омегаверс", value: "77" }, + { label: "Пародия", value: "86" }, + { label: "Повседневность", value: "52" }, + { label: "Полиция", value: "82" }, + { label: "Постапокалиптика", value: "53" }, + { label: "Приключения", value: "54" }, + { label: "Психология", value: "55" }, + { label: "Романтика", value: "56" }, + { label: "Самурайский боевик", value: "57" }, + { label: "Сверхъестественное", value: "58" }, + { label: "Сёдзё", value: "59" }, + { label: "Сёдзё-ай", value: "60" }, + { label: "Сёнэн", value: "61" }, + { label: "Сёнэн-ай", value: "62" }, + { label: "Спорт", value: "63" }, + { label: "Супер сила", value: "87" }, + { label: "Сэйнэн", value: "64" }, + { label: "Трагедия", value: "65" }, + { label: "Триллер", value: "66" }, + { label: "Ужасы", value: "67" }, + { label: "Фантастика", value: "68" }, + { label: "Фэнтези", value: "69" }, + { label: "Школа", value: "70" }, + { label: "Эротика", value: "71" }, + { label: "Этти", value: "72" }, + { label: "Юри", value: "73" }, + { label: "Яой", value: "74" }, + ], + inputType: FilterInputs.Checkbox, + }, + { + key: "tags", + label: "Теги", + values: [ + { label: "Авантюристы", value: "328" }, + { label: "Антигерой", value: "176" }, + { label: "Бессмертные", value: "333" }, + { label: "Боги", value: "218" }, + { label: "Борьба за власть", value: "309" }, + { label: "Брат и сестра", value: "360" }, + { label: "Ведьма", value: "339" }, + { label: "Видеоигры", value: "204" }, + { label: "Виртуальная реальность", value: "214" }, + { label: "Владыка демонов", value: "349" }, + { label: "Военные", value: "198" }, + { label: "Воспоминания из другого мира", value: "310" }, + { label: "Выживание", value: "212" }, + { label: "ГГ женщина", value: "294" }, + { label: "ГГ имба", value: "292" }, + { label: "ГГ мужчина", value: "295" }, + { label: "ГГ не ояш", value: "325" }, + { label: "ГГ не человек", value: "331" }, + { label: "ГГ ояш", value: "326" }, + { label: "Главный герой бог", value: "324" }, + { label: "Глупый ГГ", value: "298" }, + { label: "Горничные", value: "171" }, + { label: "Гуро", value: "306" }, + { label: "Гяру", value: "197" }, + { label: "Демоны", value: "157" }, + { label: "Драконы", value: "313" }, + { label: "Древний мир", value: "317" }, + { label: "Зверолюди", value: "163" }, + { label: "Зомби", value: "155" }, + { label: "Исторические фигуры", value: "323" }, + { label: "Кулинария", value: "158" }, + { label: "Культивация", value: "161" }, + { label: "ЛГБТ", value: "344" }, + { label: "ЛитРПГ", value: "319" }, + { label: "Лоли", value: "206" }, + { label: "Магия", value: "170" }, + { label: "Машинный перевод", value: "345" }, + { label: "Медицина", value: "159" }, + { label: "Межгалактическая война", value: "330" }, + { label: "Монстр Девушки", value: "207" }, + { label: "Монстры", value: "208" }, + { label: "Мрачный мир", value: "316" }, + { label: "Музыка", value: "358" }, + { label: "Музыка", value: "209" }, + { label: "Ниндзя", value: "199" }, + { label: "Обратный Гарем", value: "210" }, + { label: "Офисные Работники", value: "200" }, + { label: "Пираты", value: "341" }, + { label: "Подземелья", value: "314" }, + { label: "Политика", value: "311" }, + { label: "Полиция", value: "201" }, + { label: "Преступники / Криминал", value: "205" }, + { label: "Призраки / Духи", value: "196" }, + { label: "Призыватели", value: "329" }, + { label: "Прыжки между мирами", value: "321" }, + { label: "Путешествие в другой мир", value: "318" }, + { label: "Путешествие во времени", value: "213" }, + { label: "Рабы", value: "355" }, + { label: "Ранги силы", value: "312" }, + { label: "Реинкарнация", value: "154" }, + { label: "Самураи", value: "202" }, + { label: "Скрытие личности", value: "315" }, + { label: "Средневековье", value: "174" }, + { label: "Традиционные игры", value: "203" }, + { label: "Умный ГГ", value: "303" }, + { label: "Характерный рост", value: "332" }, + { label: "Хикикомори", value: "167" }, + { label: "Эволюция", value: "322" }, + { label: "Элементы РПГ", value: "327" }, + { label: "Эльфы", value: "217" }, + { label: "Якудза", value: "165" }, + ], + inputType: FilterInputs.Checkbox, + }, +]; diff --git a/plugins/russian/ranobelib.js b/plugins/russian/ranobelib.js deleted file mode 100644 index 545ced4..0000000 --- a/plugins/russian/ranobelib.js +++ /dev/null @@ -1,402 +0,0 @@ -const cheerio = require("cheerio"); -const dayjs = require("dayjs"); -const fetchApi = require("@libs/fetchApi"); -const fetchFile = require("@libs/fetchFile"); -const languages = require("@libs/languages"); -const Status = require("@libs/novelStatus"); -const FilterInputs = require("@libs/filterInputs"); - -const pluginId = "RLIB"; -const sourceName = "RanobeLib"; -const baseUrl = "https://ranobelib.me"; -var ui = null; - -async function popularNovels(page, { showLatestNovels, filters }) { - let url = `${baseUrl}/manga-list?sort=`; - url += showLatestNovels ? "last_chapter_at" : filters?.sort || "rate"; - url += "&dir=" + (filters?.order || "desc"); - - if (filters?.type?.length) { - url += filters.type.map((i) => `&types[]=${i}`).join(""); - } - - if (filters?.format?.length) { - url += filters.format.map((i) => `&format[include][]=${i}`).join(""); - } - - if (filters?.status?.length) { - url += filters.status.map((i) => `&status[]=${i}`).join(""); - } - - if (filters?.statuss?.length) { - url += filters.statuss.map((i) => `&manga_status[]=${i}`).join(""); - } - - if (filters?.genres?.length) { - url += filters.genres.map((i) => `&genres[include][]=${i}`).join(""); - } - - if (filters?.tags?.length) { - url += filters.tags.map((i) => `&tags[include][]=${i}`).join(""); - } - - url += "&page=" + page; - - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - ui = loadedCheerio("a.header-right-menu__item") - .attr("href") - ?.replace?.(/[^0-9]/g, ""); - - let novels = []; - loadedCheerio(".media-card-wrap").each(function () { - const name = loadedCheerio(this).find(".media-card__title").text(); - const cover = loadedCheerio(this).find("a.media-card").attr("data-src"); - const url = loadedCheerio(this).find("a.media-card").attr("href"); - - novels.push({ name, cover, url }); - }); - - return novels; -} - -async function parseNovelAndChapters(novelUrl) { - const result = await fetchApi(novelUrl); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - let novel = { - url: novelUrl, - }; - - novel.name = loadedCheerio(".media-name__main").text().trim(); - - novel.cover = loadedCheerio(".media-sidebar__cover img").attr("src"); - - novel.summary = loadedCheerio(".media-description__text").text().trim(); - - novel.genres = loadedCheerio('div[class="media-tags"]') - .text() - .trim() - .replace(/[\n\r]+/g, ", ") - .replace(/ /g, ""); - - loadedCheerio( - 'div[class="media-info-list paper"] > [class="media-info-list__item"]' - ).each(function () { - let name = loadedCheerio(this) - .find('div[class="media-info-list__title"]') - .text(); - - if (name === "Статус перевода") { - novel.status = - loadedCheerio(this).find("div:nth-child(2)").text().trim() === - "Продолжается" - ? Status.Ongoing - : Status.Completed; - } else if (name === "Автор") { - novel.author = loadedCheerio(this).find("div:nth-child(2)").text().trim(); - } else if (name === "Художник") { - novel.artist = loadedCheerio(this).find("div:nth-child(2)").text().trim(); - } - }); - - let chapters = []; - - let chaptersJson = body.match( - /window.__DATA__ = [\s\S]*?window._SITE_COLOR_/gm - ); - - chaptersJson = chaptersJson?.[0] - ?.replace("window.__DATA__ = ", "") - ?.replace("window._SITE_COLOR_", "") - ?.trim() - ?.slice(0, -1); - - chaptersJson = JSON.parse(chaptersJson); - ui = chaptersJson?.user?.id; - - chaptersJson.chapters.list.forEach((chapter) => - chapters.push({ - name: `Том ${chapter.chapter_volume} Глава ${chapter.chapter_number} ${chapter.chapter_name}`?.trim(), - releaseTime: dayjs(chapter.chapter_created_at).format("LLL"), - url: - `${baseUrl}/${chaptersJson.manga.slug}/v${chapter.chapter_volume}/c${chapter.chapter_number}?bid=` + - (chapter?.branch_id || ""), - }) - ); - - novel.chapters = chapters.reverse(); - return novel; -} - -async function parseChapter(chapterUrl) { - const result = await fetchApi(chapterUrl + (ui ? `&ui=${ui}` : "")); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - loadedCheerio(".reader-container img").each(function () { - const url = - loadedCheerio(this).attr("data-src") || loadedCheerio(this).attr("src"); - if (!url?.startsWith("http")) { - loadedCheerio(this).attr("src", baseUrl + url); - } else { - loadedCheerio(this).attr("src", url); - } - loadedCheerio(this).removeAttr("data-src"); - }); - - const chapterText = loadedCheerio(".reader-container").html(); - - return chapterText; -} - -async function searchNovels(searchTerm) { - const result = await fetchApi(`${baseUrl}/search?q=${searchTerm}&type=manga`); - const body = await result.json(); - - let novels = []; - body.forEach((novel) => - novels.push({ - name: novel?.rus_name || novel.name, - cover: novel.coverImage, - url: novel?.href || baseUrl + "/" + novel.slug, - }) - ); - - return novels; -} - -const filters = [ - { - key: "sort", - label: "Сортировка", - values: [ - { label: "Рейтинг", value: "rate" }, - { label: "Имя", value: "name" }, - { label: "Просмотры", value: "views" }, - { label: "Дате добавления", value: "created_at" }, - { label: "Дате обновления", value: "last_chapter_at" }, - { label: "Количество глав", value: "chap_count" }, - ], - inputType: FilterInputs.Picker, - }, - { - key: "order", - label: "Порядок", - values: [ - { label: "По убыванию", value: "desc" }, - { label: "По возрастанию", value: "asc" }, - ], - inputType: FilterInputs.Picker, - }, - { - key: "type", - label: "Тип", - values: [ - { label: "Авторский", value: "14" }, - { label: "Английский", value: "13" }, - { label: "Китай", value: "12" }, - { label: "Корея", value: "11" }, - { label: "Фанфик", value: "15" }, - { label: "Япония", value: "10" }, - ], - inputType: FilterInputs.Checkbox, - }, - { - key: "format", - label: "Формат выпуска", - values: [ - { label: "4-кома (Ёнкома)", value: "1" }, - { label: "В цвете", value: "4" }, - { label: "Веб", value: "6" }, - { label: "Вебтун", value: "7" }, - { label: "Додзинси", value: "3" }, - { label: "Сборник", value: "2" }, - { label: "Сингл", value: "5" }, - ], - inputType: FilterInputs.Checkbox, - }, - { - key: "status", - label: "Статус перевода", - values: [ - { label: "Продолжается", value: "1" }, - { label: "Завершен", value: "2" }, - { label: "Заморожен", value: "3" }, - { label: "Заброшен", value: "4" }, - ], - inputType: FilterInputs.Checkbox, - }, - { - key: "statuss", - label: "Статус тайтла", - values: [ - { label: "Онгоинг", value: "1" }, - { label: "Завершён", value: "2" }, - { label: "Анонс", value: "3" }, - { label: "Приостановлен", value: "4" }, - { label: "Выпуск прекращён", value: "5" }, - ], - inputType: FilterInputs.Checkbox, - }, - { - key: "genres", - label: "Жанры", - values: [ - { label: "Арт", value: "32" }, - { label: "Безумие", value: "91" }, - { label: "Боевик", value: "34" }, - { label: "Боевые искусства", value: "35" }, - { label: "Вампиры", value: "36" }, - { label: "Военное", value: "89" }, - { label: "Гарем", value: "37" }, - { label: "Гендерная интрига", value: "38" }, - { label: "Героическое фэнтези", value: "39" }, - { label: "Демоны", value: "81" }, - { label: "Детектив", value: "40" }, - { label: "Детское", value: "88" }, - { label: "Дзёсэй", value: "41" }, - { label: "Драма", value: "43" }, - { label: "Игра", value: "44" }, - { label: "Исекай", value: "79" }, - { label: "История", value: "45" }, - { label: "Киберпанк", value: "46" }, - { label: "Кодомо", value: "76" }, - { label: "Комедия", value: "47" }, - { label: "Космос", value: "83" }, - { label: "Магия", value: "85" }, - { label: "Махо-сёдзё", value: "48" }, - { label: "Машины", value: "90" }, - { label: "Меха", value: "49" }, - { label: "Мистика", value: "50" }, - { label: "Музыка", value: "80" }, - { label: "Научная фантастика", value: "51" }, - { label: "Омегаверс", value: "77" }, - { label: "Пародия", value: "86" }, - { label: "Повседневность", value: "52" }, - { label: "Полиция", value: "82" }, - { label: "Постапокалиптика", value: "53" }, - { label: "Приключения", value: "54" }, - { label: "Психология", value: "55" }, - { label: "Романтика", value: "56" }, - { label: "Самурайский боевик", value: "57" }, - { label: "Сверхъестественное", value: "58" }, - { label: "Сёдзё", value: "59" }, - { label: "Сёдзё-ай", value: "60" }, - { label: "Сёнэн", value: "61" }, - { label: "Сёнэн-ай", value: "62" }, - { label: "Спорт", value: "63" }, - { label: "Супер сила", value: "87" }, - { label: "Сэйнэн", value: "64" }, - { label: "Трагедия", value: "65" }, - { label: "Триллер", value: "66" }, - { label: "Ужасы", value: "67" }, - { label: "Фантастика", value: "68" }, - { label: "Фэнтези", value: "69" }, - { label: "Школа", value: "70" }, - { label: "Эротика", value: "71" }, - { label: "Этти", value: "72" }, - { label: "Юри", value: "73" }, - { label: "Яой", value: "74" }, - ], - inputType: FilterInputs.Checkbox, - }, - { - key: "tags", - label: "Теги", - values: [ - { label: "Авантюристы", value: "328" }, - { label: "Антигерой", value: "176" }, - { label: "Бессмертные", value: "333" }, - { label: "Боги", value: "218" }, - { label: "Борьба за власть", value: "309" }, - { label: "Брат и сестра", value: "360" }, - { label: "Ведьма", value: "339" }, - { label: "Видеоигры", value: "204" }, - { label: "Виртуальная реальность", value: "214" }, - { label: "Владыка демонов", value: "349" }, - { label: "Военные", value: "198" }, - { label: "Воспоминания из другого мира", value: "310" }, - { label: "Выживание", value: "212" }, - { label: "ГГ женщина", value: "294" }, - { label: "ГГ имба", value: "292" }, - { label: "ГГ мужчина", value: "295" }, - { label: "ГГ не ояш", value: "325" }, - { label: "ГГ не человек", value: "331" }, - { label: "ГГ ояш", value: "326" }, - { label: "Главный герой бог", value: "324" }, - { label: "Глупый ГГ", value: "298" }, - { label: "Горничные", value: "171" }, - { label: "Гуро", value: "306" }, - { label: "Гяру", value: "197" }, - { label: "Демоны", value: "157" }, - { label: "Драконы", value: "313" }, - { label: "Древний мир", value: "317" }, - { label: "Зверолюди", value: "163" }, - { label: "Зомби", value: "155" }, - { label: "Исторические фигуры", value: "323" }, - { label: "Кулинария", value: "158" }, - { label: "Культивация", value: "161" }, - { label: "ЛГБТ", value: "344" }, - { label: "ЛитРПГ", value: "319" }, - { label: "Лоли", value: "206" }, - { label: "Магия", value: "170" }, - { label: "Машинный перевод", value: "345" }, - { label: "Медицина", value: "159" }, - { label: "Межгалактическая война", value: "330" }, - { label: "Монстр Девушки", value: "207" }, - { label: "Монстры", value: "208" }, - { label: "Мрачный мир", value: "316" }, - { label: "Музыка", value: "358" }, - { label: "Музыка", value: "209" }, - { label: "Ниндзя", value: "199" }, - { label: "Обратный Гарем", value: "210" }, - { label: "Офисные Работники", value: "200" }, - { label: "Пираты", value: "341" }, - { label: "Подземелья", value: "314" }, - { label: "Политика", value: "311" }, - { label: "Полиция", value: "201" }, - { label: "Преступники / Криминал", value: "205" }, - { label: "Призраки / Духи", value: "196" }, - { label: "Призыватели", value: "329" }, - { label: "Прыжки между мирами", value: "321" }, - { label: "Путешествие в другой мир", value: "318" }, - { label: "Путешествие во времени", value: "213" }, - { label: "Рабы", value: "355" }, - { label: "Ранги силы", value: "312" }, - { label: "Реинкарнация", value: "154" }, - { label: "Самураи", value: "202" }, - { label: "Скрытие личности", value: "315" }, - { label: "Средневековье", value: "174" }, - { label: "Традиционные игры", value: "203" }, - { label: "Умный ГГ", value: "303" }, - { label: "Характерный рост", value: "332" }, - { label: "Хикикомори", value: "167" }, - { label: "Эволюция", value: "322" }, - { label: "Элементы РПГ", value: "327" }, - { label: "Эльфы", value: "217" }, - { label: "Якудза", value: "165" }, - ], - inputType: FilterInputs.Checkbox, - }, -]; - -module.exports = { - id: pluginId, - name: sourceName, - site: baseUrl, - version: "1.0.0", - lang: languages.Russian, - icon: "src/ru/ranobelib/icon.png", - filters, - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; diff --git a/plugins/russian/ranoberf.broken.ts b/plugins/russian/ranoberf.broken.ts new file mode 100644 index 0000000..7190a47 --- /dev/null +++ b/plugins/russian/ranoberf.broken.ts @@ -0,0 +1,140 @@ +import cheerio from "cheerio"; +import dayjs from "dayjs"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { NovelStatus } from "@libs/novelStatus"; +import { FilterInputs } from "@libs/filterInputs"; +import { defaultCover } from "@libs/defaultCover"; +import { Novel, Plugin } from "@typings/plugin"; + +export const id = "RNRF"; +export const name = "РанобэРФ"; +export const site = "https://ранобэ.рф"; +export const version = "1.0.0"; +export const icon = "src/ru/ranoberf/icon.png"; + +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function ( + page, + { showLatestNovels, filters } +) { + let url = baseUrl + "/books?order="; + url += showLatestNovels + ? "lastPublishedChapter" + : filters?.sort || "popular"; + url += "&page=" + page; + + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = cheerio.load(body); + let json = loadedCheerio("#__NEXT_DATA__").html(); + json = JSON.parse(json); + + let novels: Novel.Item[] = []; + json.props.pageProps.totalData.items.forEach((novel) => + novels.push({ + name: novel.title, + cover: novel?.verticalImage?.url + ? baseUrl + novel.verticalImage.url + : defaultCover, + url: baseUrl + "/" + novel.slug, + }) + ); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const result = await fetchApi(novelUrl); + const body = await result.text(); + + const loadedCheerio = cheerio.load(body); + const json = loadedCheerio("#__NEXT_DATA__").html(); + const book = JSON.parse(json).props.pageProps.book; + + let novel: Novel.instance = { + url: novelUrl, + name: book.title, + cover: book?.verticalImage?.url + ? baseUrl + book.verticalImage.url + : defaultCover, + summary: book.description, + author: book?.author || "", + genres: book.genres.map((item) => item.title).join(", "), + status: book.additionalInfo.includes("Активен") + ? NovelStatus.Ongoing + : NovelStatus.Completed, + }; + + let chapters: Novel.Item[] = []; + + book.chapters.forEach((chapter) => { + if (!chapter.isDonate || chapter.isUserPaid) { + chapters.push({ + name: chapter.title, + releaseTime: dayjs(chapter.publishedAt).format("LLL"), + url: baseUrl + chapter.url, + }); + } + }); + + novel.chapters = chapters.reverse(); + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi(chapterUrl); + const body = await result.text(); + + const loadedCheerio = cheerio.load(body); + let json = loadedCheerio("#__NEXT_DATA__").html(); + json = JSON.parse(json); + + let temp = cheerio.load(json.props.pageProps.chapter.content.text); + + temp("img").each(function () { + if (!loadedCheerio(this).attr("src")?.startsWith("http")) { + const src = loadedCheerio(this).attr("src"); + loadedCheerio(this).attr("src", baseUrl + src); + } + }); + + const chapterText = temp.html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = `${baseUrl}/v3/books?filter[or][0][title][like]=${searchTerm}&filter[or][1][titleEn][like]=${searchTerm}&filter[or][2][fullTitle][like]=${searchTerm}&filter[status][]=active&filter[status][]=abandoned&filter[status][]=completed&expand=verticalImage`; + const result = await fetchApi(url); + const body = await result.json(); + let novels: Novel.Item[] = []; + + body.items.forEach((novel) => + novels.push({ + name: novel.title, + cover: novel?.verticalImage?.url + ? baseUrl + novel.verticalImage.url + : defaultCover, + url: baseUrl + "/" + novel.slug, + }) + ); + + return novels; +}; + +export const filters = [ + { + key: "sort", + label: "Сортировка", + values: [ + { label: "Рейтинг", value: "popular" }, + { label: "Дате добавления", value: "new" }, + { label: "Дате обновления", value: "lastPublishedChapter" }, + { label: "Законченные", value: "completed" }, + ], + inputType: FilterInputs.Picker, + }, +]; diff --git a/plugins/russian/ranoberf.js b/plugins/russian/ranoberf.js deleted file mode 100644 index e795d68..0000000 --- a/plugins/russian/ranoberf.js +++ /dev/null @@ -1,147 +0,0 @@ -const cheerio = require("cheerio"); -const dayjs = require("dayjs"); -const fetchApi = require("@libs/fetchApi"); -const fetchFile = require("@libs/fetchFile"); -const languages = require("@libs/languages"); -const Status = require("@libs/novelStatus"); -const FilterInputs = require("@libs/filterInputs"); -const defaultCoverUri = - "https://github.com/LNReader/lnreader-sources/blob/main/icons/src/coverNotAvailable.jpg?raw=true"; - -const pluginId = "RNRF"; -const sourceName = "РанобэРФ"; -const baseUrl = "https://ранобэ.рф"; - -async function popularNovels(page, { showLatestNovels, filters }) { - let url = baseUrl + "/books?order="; - url += showLatestNovels ? "lastPublishedChapter" : filters?.sort || "popular"; - url += "&page=" + page; - - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - let json = loadedCheerio("#__NEXT_DATA__").html(); - json = JSON.parse(json); - - let novels = []; - json.props.pageProps.totalData.items.forEach((novel) => - novels.push({ - name: novel.title, - cover: novel?.verticalImage?.url - ? baseUrl + novel.verticalImage.url - : defaultCoverUri, - url: baseUrl + "/" + novel.slug, - }) - ); - - return novels; -} - -async function parseNovelAndChapters(novelUrl) { - const result = await fetchApi(novelUrl); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - const json = loadedCheerio("#__NEXT_DATA__").html(); - const book = JSON.parse(json).props.pageProps.book; - - let novel = { - url: novelUrl, - name: book.title, - cover: book?.verticalImage?.url - ? baseUrl + book.verticalImage.url - : defaultCoverUri, - summary: book.description, - author: book?.author || "", - genres: book.genres.map((item) => item.title).join(", "), - status: book.additionalInfo.includes("Активен") - ? Status.Ongoing - : Status.Completed, - }; - - let chapters = []; - - book.chapters.forEach((chapter) => { - if (!chapter.isDonate || chapter.isUserPaid) { - chapters.push({ - name: chapter.title, - releaseTime: dayjs(chapter.publishedAt).format("LLL"), - url: baseUrl + chapter.url, - }); - } - }); - - novel.chapters = chapters.reverse(); - return novel; -} - -async function parseChapter(chapterUrl) { - const result = await fetchApi(chapterUrl); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - let json = loadedCheerio("#__NEXT_DATA__").html(); - json = JSON.parse(json); - - let temp = cheerio.load(json.props.pageProps.chapter.content.text); - - temp("img").each(function () { - if (!loadedCheerio(this).attr("src")?.startsWith("http")) { - const src = loadedCheerio(this).attr("src"); - loadedCheerio(this).attr("src", baseUrl + src); - } - }); - - const chapterText = temp.html(); - - return chapterText; -} - -async function searchNovels(searchTerm) { - const url = `${baseUrl}/v3/books?filter[or][0][title][like]=${searchTerm}&filter[or][1][titleEn][like]=${searchTerm}&filter[or][2][fullTitle][like]=${searchTerm}&filter[status][]=active&filter[status][]=abandoned&filter[status][]=completed&expand=verticalImage`; - const result = await fetchApi(url); - const body = await result.json(); - let novels = []; - - body.items.forEach((novel) => - novels.push({ - name: novel.title, - cover: novel?.verticalImage?.url - ? baseUrl + novel.verticalImage.url - : defaultCoverUri, - url: baseUrl + "/" + novel.slug, - }) - ); - - return novels; -} - -const filters = [ - { - key: "sort", - label: "Сортировка", - values: [ - { label: "Рейтинг", value: "popular" }, - { label: "Дате добавления", value: "new" }, - { label: "Дате обновления", value: "lastPublishedChapter" }, - { label: "Законченные", value: "completed" }, - ], - inputType: FilterInputs.Picker, - }, -]; - -module.exports = { - id: pluginId, - name: sourceName, - site: baseUrl, - version: "1.0.0", - lang: languages.Russian, - icon: "src/ru/ranoberf/icon.png", - filters, - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; diff --git a/plugins/russian/rednoveltl[noveltl].ts b/plugins/russian/rednoveltl[noveltl].ts new file mode 100644 index 0000000..7d8151e --- /dev/null +++ b/plugins/russian/rednoveltl[noveltl].ts @@ -0,0 +1,219 @@ + + import { load as parseHTML } from "cheerio"; + import dayjs from 'dayjs'; + import { fetchFile, fetchApi } from "@libs/fetch"; + import { Novel, Plugin, Chapter } from "@typings/plugin"; + // import { parseMadaraDate } from "@libs/parseMadaraDate"; + // import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; + // import { showToast } from "@libs/showToast"; + import { Filter, FilterInputs } from "@libs/filterInputs"; + import { NovelStatus } from '@libs/novelStatus'; + import { defaultCover } from "@libs/defaultCover"; + + + export const id = "red.novel"; + export const name = "Север и Юг"; + export const icon = ""; + export const version = "1.0.0"; + export const site = "https://red.novel.tl"; + export const filters: Filter[] = [{"key":"tags","label":"Тэги","values":[{"label":"Героиня красавица","value":81},{"label":"Герой влюбляется первым","value":561},{"label":"Главный герой — мужчина","value":419},{"label":"Дружба","value":293},{"label":"Жизнь в одиночку","value":396},{"label":"Сложные семейные отношения","value":149}],"inputType":FilterInputs.Checkbox}]; + exports["protected"] = false; + + const baseUrl = site; + const domain = baseUrl.split('//')[1]; + + export const popularNovels: Plugin.popularNovels = async function ( + page, + { filters } + ) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: + 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: filters?.tags?.length ?? 0 > 0 ? { tags: filters?.tags } : {}, + hostname: domain, + limit: 40, + page, + }, + }), + }); + + const json: any = await result.json(); + const novels: Novel.Item[] = json.data?.projects?.content?.map((novel: any) => ({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: novel.covers[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + })); + + return novels; + }; + + + export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Book', + query: + 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}', + variables: { + hostname: domain, + project: novelUrl, + url: novelUrl, + }, + }), + }); + const json: any = await result.json(); + const novel: Novel.instance = { + url: novelUrl, + name: json.data.project.title, + cover: json.data.project.covers[0]?.url + ? baseUrl + json.data.project.covers[0].url + : defaultCover, + summary: parseHTML(json.data.project.annotation?.text).text(), + author: + json.data.project.persons[0].name.firstName + + ' ' + + json.data.project.persons[0].name.lastName, + status: + json.data.project.translationStatus === 'active' + ? NovelStatus.Ongoing + : NovelStatus.Completed, + }; + + let genres = [] + .concat(json.data.project.tags, json.data.project.genres) + .map((item: any) => item?.nameRu || item?.nameEng) + .filter(item => item); + + if (genres.length > 0) { + novel.genres = genres.join(','); + } + + let novelChapters: Chapter.Item[] = []; + json.data.project.subprojects.content.forEach((work: any) => + work.volumes.content.forEach((volume: any) => + volume.chapters.forEach( + (chapter: any) => + chapter?.published && + novelChapters.push({ + name: volume.shortName + ' ' + chapter.title, + url: novelUrl + '/' + volume.url + '/' + chapter.url, + releaseTime: dayjs(chapter.publishDate).format('LLL'), + }), + ), + ), + ); + novel.chapters = novelChapters; + return novel; + }; + + export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'EReaderData', + query: + 'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}', + variables: { + chapterSelector: { + fullUrl: chapterUrl, + }, + url: chapterUrl, + }, + }), + }); + const json: any = await result.json(); + + return parseHTML(json.data.chapter.text.text).html(); + }; + + export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: + 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: { + query: searchTerm, + }, + hostname: domain, + limit: 40, + page: 1, + }, + }), + }); + const json: any = await result.json(); + const novels: Novel.Item[] = []; + json?.data?.projects?.content?.forEach((novel: any) => + novels.push({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: novel.covers[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + }), + ); + return novels; + }; + + export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url, {}); + }; + \ No newline at end of file diff --git a/plugins/russian/renovels.broken.ts b/plugins/russian/renovels.broken.ts new file mode 100644 index 0000000..541b4be --- /dev/null +++ b/plugins/russian/renovels.broken.ts @@ -0,0 +1,932 @@ +import dayjs from "dayjs"; +import { fetchApi } from "@libs/fetch"; +import { NovelStatus } from "@libs/novelStatus"; +import { FilterInputs } from "@libs/filterInputs"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "ReN"; +export const name = "Renovels"; +export const site = "https://renovels.org"; +export const version = "1.0.0"; +export const icon = "src/ru/renovels/icon.png"; + +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function ( + page, + { showLatestNovels, filters } +) { + let url = baseUrl + "/api/search/catalog/?count=30&ordering="; + url += filters?.order ? filters?.order?.replace("+", "") : "-"; + url += showLatestNovels ? "chapter_date" : filters?.sort || "rating"; + + if (filters?.type?.length) { + url += filters.type.map((i) => `&types=${i}`).join(""); + } + + if (filters?.statuss?.length) { + url += filters?.statuss.map((i) => `&status=${i}`).join(""); + } + + if (filters?.genres?.length) { + url += filters.genres.map((i) => `&genres=${i}`).join(""); + } + + if (filters?.categories?.length) { + url += filters.categories.map((i) => `&categories=${i}`).join(""); + } + + url += "&page=" + page; + + const result = await fetchApi(url); + let body = await result.json(); + + let novels: Novel.Item[] = []; + + body.content.forEach((novel) => + novels.push({ + name: novel.rus_name, + cover: + baseUrl + (novel.img?.high || novel.img?.mid || novel.img.low), + url: baseUrl + "/novel/" + novel.dir, + }) + ); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + let novelID = novelUrl.split("/")[4]; + const result = await fetchApi(baseUrl + "/api/titles/" + novelID); + let body = await result.json(); + + let novel: Novel.instance = { + url: novelUrl, + name: body.content.rus_name, + summary: body.content.description, + cover: + baseUrl + + (body.content.img?.high || + body.content.img?.mid || + body.content.img.low), + status: + body.content.status.name === "Продолжается" + ? NovelStatus.Ongoing + : NovelStatus.Completed, + }; + + let tags = [] + .concat(body.content.genres, body.content.categories, []) + .map((item) => item.name); + + if (tags.length > 0) { + novel.genres = tags.join(", "); + } + + let all = (body.content.count_chapters / 100 + 1) ^ 0; + let chapters: Chapter.Item[] = []; + + for (let i = 0; i < all; i++) { + let chapterResult = await fetchApi( + baseUrl + + "/api/titles/chapters/?branch_id=" + + body.content.branches[0].id + + "&count=100&page=" + + (i + 1) + ); + let volumes = await chapterResult.json(); + + volumes.content.forEach((chapter) => { + if (!chapter.is_paid || chapter.is_bought) { + chapters.push({ + name: `Том ${chapter.tome} Глава ${chapter.chapter} ${chapter.name}`?.trim(), + releaseTime: dayjs(chapter.upload_date).format("LLL"), + url: `${baseUrl}/novel/${novelID}/${chapter.id}/`, + }); + } + }); + } + + novel.chapters = chapters.reverse(); + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + let url = baseUrl + "/api/titles/chapters/" + chapterUrl.split("/")[5]; + const result = await fetchApi(url); + const body = await result.json(); + + return body.content.content; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = `${baseUrl}/api/search/?query=${searchTerm}&count=100&field=titles`; + const result = await fetchApi(url); + let body = await result.json(); + let novels: Novel.Item[] = []; + + body.content.forEach((novel) => + novels.push({ + name: novel.rus_name, + cover: + baseUrl + (novel.img?.high || novel.img?.mid || novel.img.low), + url: baseUrl + "/novel/" + novel.dir, + }) + ); + + return novels; +}; + +export const filters = [ + { + key: "sort", + label: "Сортировка", + values: [ + { label: "Рейтинг", value: "rating" }, + { label: "Просмотры", value: "views" }, + { label: "Лайкам", value: "votes" }, + { label: "Дате добавления", value: "id" }, + { label: "Дате обновления", value: "chapter_date" }, + { label: "Количество глав", value: "count_chapters" }, + ], + inputType: FilterInputs.Picker, + }, + { + key: "order", + label: "Порядок", + values: [ + { label: "По убыванию", value: "-" }, // desc + { label: "По возрастанию", value: "+" }, // asc + ], + inputType: FilterInputs.Picker, + }, + { + key: "type", + label: "Тип", + values: [ + { label: "Авторское", value: "1" }, + { label: "Другое", value: "7" }, + { label: "Запад", value: "5" }, + { label: "Китай", value: "4" }, + { label: "Корея", value: "3" }, + { label: "Фанфики", value: "6" }, + { label: "Япония", value: "2" }, + ], + inputType: FilterInputs.Checkbox, + }, + { + key: "statuss", + label: "Статус тайтла", + values: [ + { label: "Закончен", value: "0" }, + { label: "Продолжается", value: "1" }, + { label: "Заморожен", value: "2" }, + { label: "Нет переводчика", value: "3" }, + { label: "Анонс", value: "4" }, + { label: "Лицензировано", value: "5" }, + ], + inputType: FilterInputs.Checkbox, + }, + { + key: "genres", + label: "Жанры", + values: [ + { label: "Боевик", value: "112" }, + { label: "Война", value: "123" }, + { label: "Детектив", value: "114" }, + { label: "Драма", value: "125" }, + { label: "Историческая проза", value: "115" }, + { label: "ЛитРПГ", value: "109" }, + { label: "Любовные романы", value: "116" }, + { label: "Мистика", value: "103" }, + { label: "Научная фантастика", value: "121" }, + { label: "Повседневность", value: "128" }, + { label: "Подростковая проза", value: "113" }, + { label: "Политический роман", value: "119" }, + { label: "Попаданцы", value: "108" }, + { label: "Поэзия", value: "105" }, + { label: "Разное", value: "111" }, + { label: "РеалРПГ", value: "117" }, + { label: "Романтика", value: "126" }, + { label: "Современная проза", value: "102" }, + { label: "Современная фантастика", value: "127" }, + { label: "Спорт", value: "122" }, + { label: "Трагедия", value: "129" }, + { label: "Триллер", value: "110" }, + { label: "Триллер и саспенс", value: "124" }, + { label: "Ужасы", value: "106" }, + { label: "Фантастика", value: "101" }, + { label: "Фанфик", value: "107" }, + { label: "Фьюжн роман", value: "120" }, + { label: "Фэнтези", value: "100" }, + { label: "Эротика", value: "118" }, + { label: "Юмор", value: "104" }, + ], + inputType: FilterInputs.Checkbox, + }, + { + key: "categories", + label: "Категории", + values: [ + { label: "[Награжденная работа]", value: "648" }, + { label: "18+", value: "423" }, + { label: "Абьюзеры", value: "663" }, + { label: "Авантюристы", value: "436" }, + { label: "Автоматоны", value: "593" }, + { label: "Агрессивные персонажи", value: "502" }, + { label: "Ад", value: "569" }, + { label: "Адаптация в радиопостановку", value: "581" }, + { label: "Академия", value: "153" }, + { label: "Актеры озвучки", value: "627" }, + { label: "Активный главный герой", value: "251" }, + { label: "Алхимия", value: "233" }, + { label: "Альтернативная история", value: "147" }, + { label: "Альтернативный мир", value: "156" }, + { label: "Амнезия/Потеря памяти", value: "344" }, + { label: "Анабиоз", value: "685" }, + { label: "Ангелы", value: "183" }, + { label: "Андрогинные персонажи", value: "321" }, + { label: "Андроиды", value: "203" }, + { label: "Анти-магия", value: "533" }, + { label: "Антигерой", value: "432" }, + { label: "Антикварный магазин", value: "623" }, + { label: "Антисоциальный главный герой", value: "613" }, + { label: "Антиутопия", value: "142" }, + { label: "Апатичный протагонист", value: "157" }, + { label: "Апокалипсис", value: "402" }, + { label: "Аранжированный брак", value: "375" }, + { label: "Армия", value: "639" }, + { label: "Артефакты", value: "234" }, + { label: "Артисты", value: "523" }, + { label: "Банды", value: "630" }, + { label: "БДСМ", value: "699" }, + { label: "Бедный главный герой", value: "398" }, + { label: "Безжалостный главный герой", value: "261" }, + { label: "Беззаботный главный герой", value: "438" }, + { label: "Безусловная любовь", value: "679" }, + { label: "Беременность", value: "250" }, + { label: "Бесполый главный герой", value: "325" }, + { label: "Бессмертные", value: "368" }, + { label: "Бесстрашный протагонист", value: "656" }, + { label: "Бесстыдный главный герой", value: "352" }, + { label: "Бесчестный главный герой", value: "716" }, + { label: "Библиотека", value: "428" }, + { label: "Бизнес-литература", value: "121" }, + { label: "Бизнесмен", value: "769" }, + { label: "Биочип", value: "238" }, + { label: "Бисексуальный главный герой", value: "773" }, + { label: "Близнецы", value: "263" }, + { label: "Боги", value: "317" }, + { label: "Богини", value: "439" }, + { label: "Боевая академия", value: "450" }, + { label: "Боевая фантастика", value: "149" }, + { label: "Боевое фэнтези", value: "104" }, + { label: "Боевые духи", value: "433" }, + { label: "Боевые соревнования", value: "493" }, + { label: "Божественная защита", value: "421" }, + { label: "Божественные силы", value: "327" }, + { label: "Борьба за власть", value: "599" }, + { label: "Брак", value: "446" }, + { label: "Брак по расчету", value: "188" }, + { label: "Братский комплекс", value: "159" }, + { label: "Братство", value: "487" }, + { label: "Братья и сестры", value: "577" }, + { label: "Буддизм", value: "745" }, + { label: "Быстрая культивация", value: "366" }, + { label: "Быстрообучаемый", value: "324" }, + { label: "Валькирии", value: "692" }, + { label: "Вампиры", value: "360" }, + { label: "Ваншот", value: "702" }, + { label: "Ведьмы", value: "281" }, + { label: "Вежливый главный герой", value: "379" }, + { label: "Верные подчиненные", value: "328" }, + { label: "Взрослый главный герой", value: "295" }, + { label: "Видит то, чего не видят другие", value: "671" }, + { label: "Виртуальная реальность", value: "401" }, + { label: "Владелец магазина", value: "681" }, + { label: "Внезапная сила", value: "455" }, + { label: "Внезапное богатство", value: "765" }, + { label: "Внешний вид отличается от факт", value: "419" }, + { label: "Военные Летописи", value: "743" }, + { label: "Возвращение из другого мира", value: "696" }, + { label: "Войны", value: "181" }, + { label: "Вокалоид", value: "701" }, + { label: "Волшебники/Волшебницы", value: "539" }, + { label: "Волшебные звери", value: "308" }, + { label: "Воображаемый друг", value: "651" }, + { label: "Воры", value: "414" }, + { label: "Воскрешение", value: "199" }, + { label: "Враги становятся возлюбленными", value: "498" }, + { label: "Враги становятся союзниками", value: "562" }, + { label: "Врата в другой мир", value: "609" }, + { label: "Врачи", value: "376" }, + { label: "Временной парадокс", value: "275" }, + { label: "Всемогущий главный герой", value: "169" }, + { label: "Вторжение на землю", value: "198" }, + { label: "Второй шанс", value: "231" }, + { label: "Вуайеризм", value: "790" }, + { label: "Выживание", value: "380" }, + { label: "Высокомерные персонажи", value: "361" }, + { label: "Гадание", value: "595" }, + { label: "Гарем рабов", value: "777" }, + { label: "Гг - женщина", value: "186" }, + { label: "Гг - мужчина", value: "166" }, + { label: "Гг силен с самого начала", value: "171" }, + { label: "Геймеры", value: "392" }, + { label: "Генералы", value: "326" }, + { label: "Генетические модификации", value: "657" }, + { label: "Гениальный главный герой", value: "617" }, + { label: "Герои", value: "285" }, + { label: "Героиня — сорванец", value: "583" }, + { label: "Героическая фантастика", value: "140" }, + { label: "Героическое фэнтези", value: "107" }, + { label: "Герой влюбляется первым", value: "187" }, + { label: "Гетерохромия", value: "570" }, + { label: "Гильдии", value: "411" }, + { label: "Гипнотизм", value: "755" }, + { label: "Главный герой — бог", value: "547" }, + { label: "Главный герой — гуманоид", value: "636" }, + { label: "Главный герой — наполовину чел", value: "445" }, + { label: "Главный герой — отец", value: "792" }, + { label: "Главный герой — раб", value: "780" }, + { label: "Главный герой — ребенок", value: "488" }, + { label: "Главный герой — рубака", value: "476" }, + { label: "Главный герой влюбляется первы", value: "683" }, + { label: "Главный герой играет роль", value: "472" }, + { label: "Главный герой носит очки", value: "672" }, + { label: "Главный герой пацифист", value: "698" }, + { label: "Гладиаторы", value: "603" }, + { label: "Глуповатый главный герой", value: "385" }, + { label: "Гоблины", value: "586" }, + { label: "Големы", value: "620" }, + { label: "Гомункул", value: "789" }, + { label: "Горничные", value: "459" }, + { label: "Городское фэнтези", value: "105" }, + { label: "Госпиталь", value: "800" }, + { label: "Готовка", value: "302" }, + { label: "Гриндинг", value: "393" }, + { label: "Дао Компаньон", value: "462" }, + { label: "Даосизм", value: "760" }, + { label: "Дварфы", value: "323" }, + { label: "Двойная личность", value: "642" }, + { label: "Двойник", value: "601" }, + { label: "Дворецкий", value: "660" }, + { label: "Дворяне", value: "168" }, + { label: "Дворянство/Аристократия", value: "437" }, + { label: "Девушки-монстры", value: "669" }, + { label: "Демоническая техника культивац", value: "722" }, + { label: "Демоны", value: "152" }, + { label: "Денежный долг", value: "783" }, + { label: "Депрессия", value: "555" }, + { label: "Детектив", value: "116" }, + { label: "Детективы", value: "612" }, + { label: "Детская литература", value: "123" }, + { label: "Дискриминация", value: "162" }, + { label: "Добыча денег в приоритете", value: "396" }, + { label: "Документальная проза", value: "122" }, + { label: "Долгая разлука", value: "307" }, + { label: "Домашние дела", value: "290" }, + { label: "Домогательство", value: "470" }, + { label: "Драконы", value: "303" }, + { label: "Драконьи всадники", value: "607" }, + { label: "Древние времена", value: "221" }, + { label: "Древний Китай", value: "374" }, + { label: "Дружба", value: "217" }, + { label: "Друзья детства", value: "282" }, + { label: "Друзья становятся врагами", value: "567" }, + { label: "Друиды", value: "497" }, + { label: "Дух лисы", value: "784" }, + { label: "Духи/призраки", value: "172" }, + { label: "Духовный советник", value: "435" }, + { label: "Душевность", value: "633" }, + { label: "Души", value: "254" }, + { label: "Европейская атмосфера", value: "521" }, + { label: "Ёкаи", value: "575" }, + { label: "Есть аниме-адаптация", value: "154" }, + { label: "Есть видеоигра по мотивам", value: "552" }, + { label: "Есть манга", value: "155" }, + { label: "Есть манхва-адаптация", value: "517" }, + { label: "Есть маньхуа-адаптация", value: "388" }, + { label: "Есть сериал-адаптация", value: "492" }, + { label: "Есть фильм", value: "173" }, + { label: "Женища-наставник", value: "505" }, + { label: "Жесткая, двуличная личность", value: "346" }, + { label: "Жестокие персонажи", value: "503" }, + { label: "Жестокое обращение с ребенком", value: "654" }, + { label: "Жестокость", value: "245" }, + { label: "Животноводство", value: "754" }, + { label: "Животные черты", value: "528" }, + { label: "Жизнь в квартире", value: "606" }, + { label: "Жрицы", value: "615" }, + { label: "Заботливый главный герой", value: "288" }, + { label: "Забывчивый главный герой", value: "628" }, + { label: "Заговоры", value: "289" }, + { label: "Закалка тела", value: "362" }, + { label: "Законники", value: "756" }, + { label: "Замкнутый главный герой", value: "589" }, + { label: "Запечатанная сила", value: "430" }, + { label: "Застенчивые персонажи", value: "508" }, + { label: "Звери", value: "237" }, + { label: "Звери-компаньоны", value: "301" }, + { label: "Злой протагонист", value: "243" }, + { label: "Злые боги", value: "504" }, + { label: "Злые организации", value: "563" }, + { label: "Злые религии", value: "734" }, + { label: "Знаменитости", value: "473" }, + { label: "Знаменитый главный герой", value: "531" }, + { label: "Знания современного мира", value: "297" }, + { label: "Зомби", value: "409" }, + { label: "Игра на выживание", value: "274" }, + { label: "Игривый протагонист", value: "717" }, + { label: "Игровая система рейтинга", value: "391" }, + { label: "Игровые элементы", value: "266" }, + { label: "Игрушки (18+)", value: "676" }, + { label: "Из грязи в князи", value: "416" }, + { label: "Из женщины в мужчину", value: "709" }, + { label: "Из мужчины в женщину", value: "715" }, + { label: "Из полного в худого", value: "767" }, + { label: "Из слабого в сильного", value: "202" }, + { label: "Извращенный главный герой", value: "434" }, + { label: "Изгои", value: "534" }, + { label: "Изменение расы", value: "664" }, + { label: "Изменения внешнего вида", value: "300" }, + { label: "Изменения личности", value: "219" }, + { label: "Изнасилование", value: "229" }, + { label: "Изображения жестокости", value: "355" }, + { label: "ИИ", value: "235" }, + { label: "Империи", value: "748" }, + { label: "Инвалидность", value: "684" }, + { label: "Индустриализация", value: "292" }, + { label: "Инженер", value: "163" }, + { label: "Инцест", value: "525" }, + { label: "Искусственный интеллект", value: "236" }, + { label: "Исследования", value: "670" }, + { label: "Историческая проза", value: "119" }, + { label: "Исторические приключения", value: "120" }, + { label: "Исторический детектив", value: "117" }, + { label: "Исторический любовный роман", value: "138" }, + { label: "Историческое фэнтези", value: "110" }, + { label: "Каннибализм", value: "456" }, + { label: "Карточные игры", value: "682" }, + { label: "Киберпанк", value: "141" }, + { label: "Киберспорт", value: "686" }, + { label: "Кланы", value: "796" }, + { label: "Класс безработного", value: "735" }, + { label: "Клоны", value: "447" }, + { label: "Клубы", value: "216" }, + { label: "Книги", value: "427" }, + { label: "Книги навыков", value: "400" }, + { label: "Книжный червь", value: "519" }, + { label: "Коварство", value: "230" }, + { label: "Коллеги", value: "704" }, + { label: "Колледж/Университет", value: "726" }, + { label: "Кома", value: "776" }, + { label: "Командная работа", value: "496" }, + { label: "Комедийный оттенок", value: "582" }, + { label: "Комплекс неполноценности", value: "550" }, + { label: "Комплекс семейных отношений", value: "223" }, + { label: "Конкуренция", value: "746" }, + { label: "Контроль разума/сознания", value: "357" }, + { label: "Копейщик", value: "425" }, + { label: "Королевская битва", value: "797" }, + { label: "Королевская власть", value: "178" }, + { label: "Королевства", value: "259" }, + { label: "Короткий любовный роман", value: "136" }, + { label: "Коррупция", value: "457" }, + { label: "Космическая фантастика", value: "146" }, + { label: "Космические войны", value: "697" }, + { label: "Красивый герой", value: "226" }, + { label: "Крафт", value: "377" }, + { label: "Кризис личности", value: "640" }, + { label: "Кругосветное путешествие", value: "353" }, + { label: "Кудере", value: "506" }, + { label: "Кузены", value: "718" }, + { label: "Кузнец", value: "518" }, + { label: "Кукловоды", value: "501" }, + { label: "Куклы/марионетки", value: "624" }, + { label: "Культивация", value: "241" }, + { label: "Куннилингус", value: "667" }, + { label: "Легенды", value: "500" }, + { label: "Легкая жизнь", value: "644" }, + { label: "Ленивый главный герой", value: "621" }, + { label: "Лидерство", value: "494" }, + { label: "Лоли", value: "213" }, + { label: "Лотерея", value: "477" }, + { label: "Любовное фэнтези", value: "135" }, + { label: "Любовный треугольник", value: "218" }, + { label: "Любовь детства", value: "560" }, + { label: "Любовь с первого взгляда", value: "737" }, + { label: "Магические надписи", value: "453" }, + { label: "Магические печати", value: "369" }, + { label: "Магические технологии", value: "440" }, + { label: "Магия", value: "165" }, + { label: "Магия призыва", value: "418" }, + { label: "Мазохистские персонажи", value: "688" }, + { label: "Манипулятивные персонажи", value: "249" }, + { label: "Мания", value: "212" }, + { label: "Мастер на все руки", value: "466" }, + { label: "Мастурбация", value: "788" }, + { label: "Махо-сёдзё", value: "556" }, + { label: "Медицинские знания", value: "507" }, + { label: "Медленная романтическая линия", value: "232" }, + { label: "Медленное развитие на старте", value: "694" }, + { label: "Межпространственные путешестви", value: "404" }, + { label: "Менеджмент", value: "294" }, + { label: "Мертвый главный герой", value: "761" }, + { label: "Месть", value: "209" }, + { label: "Метаморфы", value: "335" }, + { label: "Меч и магия", value: "180" }, + { label: "Мечник", value: "647" }, + { label: "Мечты", value: "738" }, + { label: "Милая история", value: "725" }, + { label: "Милое дитя", value: "637" }, + { label: "Милый главный герой", value: "714" }, + { label: "Мировое дерево", value: "463" }, + { label: "Мистика", value: "151" }, + { label: "Мистический ореол вокруг семьи", value: "483" }, + { label: "Мифические звери", value: "491" }, + { label: "Мифология", value: "530" }, + { label: "Младшие братья", value: "785" }, + { label: "Младшие сестры", value: "527" }, + { label: "ММОРПГ (ЛитРПГ)", value: "395" }, + { label: "Множество перемещенных людей", value: "549" }, + { label: "Множество реальностей", value: "370" }, + { label: "Множество реинкарнированных лю", value: "330" }, + { label: "Модели", value: "678" }, + { label: "Молчаливый персонаж", value: "721" }, + { label: "Монстры", value: "192" }, + { label: "Мужская гей-пара", value: "706" }, + { label: "Мужчина-яндере", value: "267" }, + { label: "Музыка", value: "635" }, + { label: "Музыкальные группы", value: "634" }, + { label: "Мутации", value: "693" }, + { label: "Мутированные существа", value: "405" }, + { label: "Навык кражи", value: "662" }, + { label: "Навязчивая любовь", value: "206" }, + { label: "Наемники", value: "412" }, + { label: "Назойливый возлюбленный", value: "561" }, + { label: "Наивный главный герой", value: "189" }, + { label: "Наркотики", value: "689" }, + { label: "Нарциссический главный герой", value: "532" }, + { label: "Насилие сексуального характера", value: "774" }, + { label: "Наследование", value: "452" }, + { label: "Научная фантастика", value: "144" }, + { label: "Национализм", value: "406" }, + { label: "Не блещущий внешне главный гер", value: "415" }, + { label: "Не родные братья и сестры", value: "526" }, + { label: "Небеса", value: "568" }, + { label: "Небесное испытание", value: "367" }, + { label: "Негуманоидный главный герой", value: "659" }, + { label: "Недоверчивый главный герой", value: "546" }, + { label: "Недооцененный главный герой", value: "258" }, + { label: "Недоразумения", value: "309" }, + { label: "Неизлечимая болезнь", value: "196" }, + { label: "Некромант", value: "397" }, + { label: "Нелинейная история", value: "269" }, + { label: "Ненавистный главный герой", value: "548" }, + { label: "Ненадежный рассказчик", value: "277" }, + { label: "Нерезиденты", value: "772" }, + { label: "Нерешительный главный герой", value: "744" }, + { label: "Несерьезный главный герой", value: "384" }, + { label: "Несколько временных линий", value: "576" }, + { label: "Несколько гг", value: "268" }, + { label: "Несколько главных героев", value: "537" }, + { label: "Несколько идентичностей", value: "652" }, + { label: "Несколько личностей", value: "536" }, + { label: "Нетораре", value: "732" }, + { label: "Нетори", value: "514" }, + { label: "Неудачливый главный герой", value: "618" }, + { label: "Ниндзя", value: "441" }, + { label: "Новелла", value: "103" }, + { label: "Обещание из детства", value: "742" }, + { label: "Обманщик", value: "485" }, + { label: "Обмен телами", value: "214" }, + { label: "Обнаженка", value: "490" }, + { label: "Обольщение", value: "731" }, + { label: "Оборотни", value: "661" }, + { label: "Обратный гарем", value: "351" }, + { label: "Общество монстров", value: "329" }, + { label: "Обязательство", value: "602" }, + { label: "Огнестрельное оружие", value: "291" }, + { label: "Ограниченное время жизни", value: "194" }, + { label: "Одержимость", value: "512" }, + { label: "Одинокий главный герой", value: "394" }, + { label: "Одиночество", value: "306" }, + { label: "Одиночное проживание", value: "313" }, + { label: "Околосмертные переживания", value: "759" }, + { label: "Оммёдзи", value: "597" }, + { label: "Омоложение", value: "338" }, + { label: "Организованная преступность", value: "540" }, + { label: "Оргия", value: "775" }, + { label: "Орки", value: "331" }, + { label: "Освоение навыков", value: "336" }, + { label: "Основано на аниме", value: "605" }, + { label: "Основано на видео игре", value: "720" }, + { label: "Основано на визуальной новелле", value: "793" }, + { label: "Основано на песне", value: "700" }, + { label: "Основано на фильме", value: "604" }, + { label: "Осторожный главный герой", value: "240" }, + { label: "Отаку", value: "571" }, + { label: "Открытый космос", value: "264" }, + { label: "Отношения в сети", value: "727" }, + { label: "Отношения между людьми и нелюд", value: "311" }, + { label: "Отношения на расстоянии", value: "791" }, + { label: "Отношения Сенпай-Коухай", value: "572" }, + { label: "Отношения ученика и учителя", value: "515" }, + { label: "Отношения учитель-ученик", value: "429" }, + { label: "Отношения хозяин-слуга", value: "312" }, + { label: "Отомэ игра", value: "733" }, + { label: "Отсутствие здравого смысла", value: "645" }, + { label: "Отсутствие родителей", value: "625" }, + { label: "Офисный роман", value: "707" }, + { label: "Официанты", value: "703" }, + { label: "Охотники", value: "378" }, + { label: "Очаровательный главный герой", value: "687" }, + { label: "Падшее дворянство", value: "655" }, + { label: "Падшие ангелы", value: "565" }, + { label: "Пайзури", value: "665" }, + { label: "Паразиты", value: "591" }, + { label: "Параллельные миры", value: "207" }, + { label: "Парк", value: "265" }, + { label: "Парк развлечений", value: "632" }, + { label: "Пародия", value: "407" }, + { label: "Певцы/Певицы", value: "739" }, + { label: "Первая любовь", value: "729" }, + { label: "Первоисточник новеллы — манга", value: "611" }, + { label: "Первый раз", value: "787" }, + { label: "Перемещение в игровой мир", value: "588" }, + { label: "Перемещение в иной мир", value: "750" }, + { label: "Перерождение в ином мире", value: "751" }, + { label: "Переселение души/трансмиграция", value: "257" }, + { label: "Персонаж использует щит", value: "666" }, + { label: "Петля времени", value: "200" }, + { label: "Пираты", value: "771" }, + { label: "Писатели", value: "482" }, + { label: "Питомцы", value: "349" }, + { label: "Племенное общество", value: "381" }, + { label: "Повелитель демонов", value: "283" }, + { label: "Подземелья", value: "322" }, + { label: "Подростковая проза", value: "114" }, + { label: "Пожелания", value: "278" }, + { label: "Познание Дао", value: "443" }, + { label: "Покинутое дитя", value: "590" }, + { label: "Полигамия", value: "386" }, + { label: "Политика", value: "170" }, + { label: "Политический роман", value: "133" }, + { label: "Полиция", value: "764" }, + { label: "Полулюди", value: "420" }, + { label: "Популярный любовный интерес", value: "342" }, + { label: "Постапокалипсис", value: "145" }, + { label: "Постапокалиптика", value: "184" }, + { label: "Потерянные цивилизации", value: "749" }, + { label: "Похищения людей", value: "723" }, + { label: "Поэзия", value: "111" }, + { label: "Правонарушители", value: "713" }, + { label: "Прагматичный главный герой", value: "592" }, + { label: "Преданный любовный интерес", value: "225" }, + { label: "Предательство", value: "222" }, + { label: "Предвидение", value: "177" }, + { label: "Прекрасная героиня", value: "158" }, + { label: "Преступники", value: "736" }, + { label: "Преступность", value: "204" }, + { label: "Призванный герой", value: "262" }, + { label: "Призраки", value: "176" }, + { label: "Принуждение к отношениям", value: "643" }, + { label: "Принцессы", value: "795" }, + { label: "Притворная пара", value: "641" }, + { label: "Причудливые персонажи", value: "387" }, + { label: "Пришельцы/инопланетяне", value: "197" }, + { label: "Программист", value: "691" }, + { label: "Проклятия", value: "174" }, + { label: "Промывание мозгов", value: "578" }, + { label: "Пропуск времени", value: "256" }, + { label: "Пророчества", value: "499" }, + { label: "Проститутки", value: "786" }, + { label: "Прошлое играет большую роль", value: "320" }, + { label: "Прыжки между мирами", value: "741" }, + { label: "Психические силы", value: "359" }, + { label: "Психопаты", value: "270" }, + { label: "Публицистика", value: "124" }, + { label: "Путешествие во времени", value: "201" }, + { label: "Пытка", value: "280" }, + { label: "Рабы", value: "778" }, + { label: "Развитие личности", value: "125" }, + { label: "Развод", value: "758" }, + { label: "Разумные предметы", value: "286" }, + { label: "Расизм", value: "408" }, + { label: "Рассказ", value: "185" }, + { label: "Расторжения помолвки", value: "343" }, + { label: "Расы зооморфов", value: "315" }, + { label: "Реализм", value: "246" }, + { label: "Ревность", value: "730" }, + { label: "Редакторы", value: "705" }, + { label: "Реинкарнация", value: "372" }, + { label: "Реинкарнация в монстра", value: "310" }, + { label: "Реинкарнация в объект", value: "711" }, + { label: "Религии", value: "616" }, + { label: "Репортеры", value: "781" }, + { label: "Ресторан", value: "680" }, + { label: "Решительный главный герой", value: "242" }, + { label: "Робкий главный герой", value: "763" }, + { label: "Родитель одиночка", value: "708" }, + { label: "Родительский комплекс", value: "587" }, + { label: "Родословная", value: "239" }, + { label: "Романтическая эротика", value: "132" }, + { label: "Романтический подсюжет", value: "271" }, + { label: "Рост персонажа", value: "215" }, + { label: "Рыцари", value: "260" }, + { label: "Садистские персонажи", value: "674" }, + { label: "Самоотверженный главный герой", value: "747" }, + { label: "Самурай", value: "677" }, + { label: "Сборник коротких историй", value: "520" }, + { label: "Связанные сюжетные линии", value: "535" }, + { label: "Святые", value: "752" }, + { label: "Священники", value: "413" }, + { label: "Сдержанный главный герой", value: "762" }, + { label: "Секретные организации", value: "417" }, + { label: "Секреты", value: "626" }, + { label: "Секс рабы", value: "779" }, + { label: "Семейный конфликт", value: "757" }, + { label: "Семь добродетелей", value: "334" }, + { label: "Семь смертных грехов", value: "253" }, + { label: "Семья", value: "347" }, + { label: "Сёнэн-ай подсюжет", value: "690" }, + { label: "Серийные убийцы", value: "557" }, + { label: "Сестринский комплекс", value: "558" }, + { label: "Сила духа", value: "373" }, + { label: "Сила, требующая платы за польз", value: "579" }, + { label: "Сильная пара", value: "228" }, + { label: "Сильный в сильнейшего", value: "442" }, + { label: "Сильный любовный интерес", value: "273" }, + { label: "Синдром восьмиклассника", value: "211" }, + { label: "Сироты", value: "319" }, + { label: "Система уровней", value: "305" }, + { label: "Системный администратор", value: "538" }, + { label: "Сказка", value: "126" }, + { label: "Скрытие истинной личности", value: "451" }, + { label: "Скрытие истинных способностей", value: "348" }, + { label: "Скрытный главный герой", value: "252" }, + { label: "Скрытые способности", value: "247" }, + { label: "Скульпторы", value: "448" }, + { label: "Слабо выраженная романтическая", value: "318" }, + { label: "Слабый главный герой", value: "298" }, + { label: "Слепой главный герой", value: "794" }, + { label: "Слуги", value: "333" }, + { label: "Слэш", value: "131" }, + { label: "Смерть", value: "175" }, + { label: "Смерть близких", value: "444" }, + { label: "Собственнические персонажи", value: "227" }, + { label: "Современная проза", value: "150" }, + { label: "Современность", value: "167" }, + { label: "Современный любовный роман", value: "137" }, + { label: "Сожительство", value: "544" }, + { label: "Создание армии", value: "287" }, + { label: "Создание артефактов", value: "382" }, + { label: "Создание клана", value: "513" }, + { label: "Создание королевства", value: "293" }, + { label: "Создание навыков", value: "337" }, + { label: "Создание секты", value: "469" }, + { label: "Солдаты/военные", value: "193" }, + { label: "Сон", value: "622" }, + { label: "Состоятельные персонажи", value: "190" }, + { label: "Социальная иерархия по силе", value: "255" }, + { label: "Социальная фантастика", value: "148" }, + { label: "Социальные изгои", value: "542" }, + { label: "Спасение мира", value: "638" }, + { label: "Специальные способности", value: "179" }, + { label: "Спокойный главный герой", value: "160" }, + { label: "Справедливый главный герой", value: "719" }, + { label: "Средневековье", value: "296" }, + { label: "Ссорящаяся пара", value: "383" }, + { label: "Сталкеры", value: "710" }, + { label: "Старение", value: "299" }, + { label: "Стимпанк", value: "139" }, + { label: "Стоические персонажи", value: "509" }, + { label: "Стокгольмский синдром", value: "675" }, + { label: "Стратег", value: "495" }, + { label: "Стратегические битвы", value: "272" }, + { label: "Стратегия", value: "798" }, + { label: "Стрелки", value: "522" }, + { label: "Стрельба из лука", value: "461" }, + { label: "Студенческий совет", value: "551" }, + { label: "Судьба", value: "364" }, + { label: "Суккубы", value: "545" }, + { label: "Супер герои", value: "799" }, + { label: "Суровая подготовка", value: "356" }, + { label: "Таинственная болезнь", value: "195" }, + { label: "Таинственное прошлое", value: "358" }, + { label: "Тайная личность", value: "399" }, + { label: "Тайные отношения", value: "768" }, + { label: "Танцоры", value: "782" }, + { label: "Телохранители", value: "516" }, + { label: "Темное фэнтези", value: "106" }, + { label: "Тентакли", value: "712" }, + { label: "Террористы", value: "574" }, + { label: "Технологический разрыв", value: "658" }, + { label: "Тихие персонажи", value: "600" }, + { label: "Толстый главный герой", value: "389" }, + { label: "Торговцы", value: "489" }, + { label: "Травля/буллинг", value: "210" }, + { label: "Травник", value: "724" }, + { label: "Трагическое прошлое", value: "276" }, + { label: "Трансплантация воспоминаний", value: "449" }, + { label: "Триллер", value: "134" }, + { label: "Трудолюбивый главный герой", value: "164" }, + { label: "Тюрьма", value: "541" }, + { label: "Убийства", value: "205" }, + { label: "Убийцы", value: "345" }, + { label: "Убийцы драконов", value: "646" }, + { label: "Уверенный главный герой", value: "363" }, + { label: "Удачливый главный герой", value: "478" }, + { label: "Ужасы", value: "112" }, + { label: "Укротитель монстров", value: "422" }, + { label: "Умения из прошлой жизни", value: "371" }, + { label: "Умная пара", value: "554" }, + { label: "Умный главный герой", value: "161" }, + { label: "Уникальная техника Культивации", value: "350" }, + { label: "Уникальное оружие", value: "426" }, + { label: "Управление бизнесом", value: "403" }, + { label: "Управление временем", value: "279" }, + { label: "Управление кровью", value: "753" }, + { label: "Упрямый главный герой", value: "695" }, + { label: "Уродливый главный герой", value: "766" }, + { label: "Ускоренный рост", value: "390" }, + { label: "Усыновленные дети", value: "543" }, + { label: "Усыновленный главный герой", value: "486" }, + { label: "Уход за детьми", value: "474" }, + { label: "Учителя", value: "431" }, + { label: "Фамильяры", value: "596" }, + { label: "Фанатизм", value: "650" }, + { label: "Фантастические существа", value: "410" }, + { label: "Фантастический детектив", value: "115" }, + { label: "Фанфик", value: "113" }, + { label: "Фанфикшн", value: "464" }, + { label: "Фармацевт", value: "728" }, + { label: "Фарминг", value: "458" }, + { label: "Феи", value: "316" }, + { label: "Фелляция", value: "631" }, + { label: "Фемслэш", value: "127" }, + { label: "Фениксы", value: "454" }, + { label: "Фетиш груди", value: "559" }, + { label: "Философия", value: "208" }, + { label: "Фильмы", value: "479" }, + { label: "Флэшбэки", value: "585" }, + { label: "Фобии", value: "220" }, + { label: "Фольклор", value: "529" }, + { label: "Футанари", value: "619" }, + { label: "Футуристический сеттинг", value: "460" }, + { label: "Фэнтези мир", value: "244" }, + { label: "Хакеры", value: "475" }, + { label: "Харизматический герой", value: "467" }, + { label: "Хикикомори/Затворники", value: "524" }, + { label: "Хитроумный главный герой", value: "224" }, + { label: "Хозяин подземелий", value: "608" }, + { label: "Холодный главный герой", value: "354" }, + { label: "Хорошие отношения с семьей", value: "566" }, + { label: "Хранители могил", value: "191" }, + { label: "Целители", value: "465" }, + { label: "Цзянши", value: "740" }, + { label: "Цундэрэ", value: "510" }, + { label: "Чаты", value: "629" }, + { label: "Человеческое оружие", value: "580" }, + { label: "Честный главный герой", value: "341" }, + { label: "Читы", value: "339" }, + { label: "Шантаж", value: "584" }, + { label: "Шеф-повар", value: "340" }, + { label: "Шикигами", value: "598" }, + { label: "Школа только для девочек", value: "668" }, + { label: "Шота", value: "573" }, + { label: "Шоу-бизнес", value: "481" }, + { label: "Шпионский детектив", value: "118" }, + { label: "Шпионы", value: "614" }, + { label: "Эволюция", value: "304" }, + { label: "Эгоистичный главный герой", value: "594" }, + { label: "Эйдетическая память", value: "468" }, + { label: "Экзорсизм", value: "564" }, + { label: "Экономика", value: "553" }, + { label: "Эксгибиционизм", value: "673" }, + { label: "Эксперименты с людьми", value: "248" }, + { label: "Элементальная магия", value: "471" }, + { label: "Эльфы", value: "284" }, + { label: "Эмоционально слабый гг", value: "770" }, + { label: "Эпизодический", value: "649" }, + { label: "Эпическое фэнтези", value: "109" }, + { label: "Эротическая фантастика", value: "129" }, + { label: "Эротический фанфик", value: "128" }, + { label: "Эротическое фэнтези", value: "130" }, + { label: "Юмористическая фантастика", value: "143" }, + { label: "Юмористическое фэнтези", value: "108" }, + { label: "Юный любовный интерес", value: "511" }, + { label: "Яды", value: "484" }, + { label: "Языкастые персонажи", value: "480" }, + { label: "Языковой барьер", value: "182" }, + { label: "Яндере", value: "314" }, + { label: "Японские силы самообороны", value: "610" }, + { label: "Ярко выраженная романтика", value: "365" }, + { label: "R-15 Японское возрастное огр.", value: "332" }, + { label: "R-18", value: "424" }, + ], + inputType: FilterInputs.Checkbox, + }, +]; diff --git a/plugins/russian/renovels.js b/plugins/russian/renovels.js deleted file mode 100644 index aa36a42..0000000 --- a/plugins/russian/renovels.js +++ /dev/null @@ -1,936 +0,0 @@ -const dayjs = require("dayjs"); -const fetchApi = require("@libs/fetchApi"); -const fetchFile = require("@libs/fetchFile"); -const languages = require("@libs/languages"); -const Status = require("@libs/novelStatus"); -const FilterInputs = require("@libs/filterInputs"); - -const pluginId = "ReN"; -const sourceName = "Renovels"; -const baseUrl = "https://renovels.org"; - -async function popularNovels(page, { showLatestNovels, filters }) { - let url = baseUrl + "/api/search/catalog/?count=30&ordering="; - url += filters?.order ? filters?.order?.replace("+", "") : "-"; - url += showLatestNovels ? "chapter_date" : filters?.sort || "rating"; - - if (filters?.type?.length) { - url += filters.type.map((i) => `&types=${i}`).join(""); - } - - if (filters?.statuss?.length) { - url += filters?.statuss.map((i) => `&status=${i}`).join(""); - } - - if (filters?.genres?.length) { - url += filters.genres.map((i) => `&genres=${i}`).join(""); - } - - if (filters?.categories?.length) { - url += filters.categories.map((i) => `&categories=${i}`).join(""); - } - - url += "&page=" + page; - - const result = await fetchApi(url); - let body = await result.json(); - - let novels = []; - - body.content.forEach((novel) => - novels.push({ - name: novel.rus_name, - cover: baseUrl + (novel.img?.high || novel.img?.mid || novel.img.low), - url: baseUrl + "/novel/" + novel.dir, - }) - ); - - return novels; -} - -async function parseNovelAndChapters(novelUrl) { - let novelID = novelUrl.split("/")[4]; - const result = await fetchApi(baseUrl + "/api/titles/" + novelID); - let body = await result.json(); - - let novel = { - url: novelUrl, - name: body.content.rus_name, - summary: body.content.description, - cover: - baseUrl + - (body.content.img?.high || body.content.img?.mid || body.content.img.low), - status: - body.content.status.name === "Продолжается" - ? Status.Ongoing - : Status.Completed, - }; - - let tags = [] - .concat(body.content.genres, body.content.categories, []) - .map((item) => item.name); - - if (tags.length > 0) { - novel.genres = tags.join(", "); - } - - let all = (body.content.count_chapters / 100 + 1) ^ 0; - let chapters = []; - - for (let i = 0; i < all; i++) { - let chapterResult = await fetchApi( - baseUrl + - "/api/titles/chapters/?branch_id=" + - body.content.branches[0].id + - "&count=100&page=" + - (i + 1) - ); - let volumes = await chapterResult.json(); - - volumes.content.forEach((chapter) => { - if (!chapter.is_paid || chapter.is_bought) { - chapters.push({ - name: `Том ${chapter.tome} Глава ${chapter.chapter} ${chapter.name}`?.trim(), - releaseTime: dayjs(chapter.upload_date).format("LLL"), - url: `${baseUrl}/novel/${novelID}/${chapter.id}/`, - }); - } - }); - } - - novel.chapters = chapters.reverse(); - return novel; -} - -async function parseChapter(chapterUrl) { - let url = baseUrl + "/api/titles/chapters/" + chapterUrl.split("/")[5]; - const result = await fetchApi(url); - const body = await result.json(); - - return body.content.content; -} - -async function searchNovels(searchTerm) { - const url = `${baseUrl}/api/search/?query=${searchTerm}&count=100&field=titles`; - const result = await fetchApi(url); - let body = await result.json(); - let novels = []; - - body.content.forEach((novel) => - novels.push({ - name: novel.rus_name, - cover: baseUrl + (novel.img?.high || novel.img?.mid || novel.img.low), - url: baseUrl + "/novel/" + novel.dir, - }) - ); - - return novels; -} - -const filters = [ - { - key: "sort", - label: "Сортировка", - values: [ - { label: "Рейтинг", value: "rating" }, - { label: "Просмотры", value: "views" }, - { label: "Лайкам", value: "votes" }, - { label: "Дате добавления", value: "id" }, - { label: "Дате обновления", value: "chapter_date" }, - { label: "Количество глав", value: "count_chapters" }, - ], - inputType: FilterInputs.Picker, - }, - { - key: "order", - label: "Порядок", - values: [ - { label: "По убыванию", value: "-" }, // desc - { label: "По возрастанию", value: "+" }, // asc - ], - inputType: FilterInputs.Picker, - }, - { - key: "type", - label: "Тип", - values: [ - { label: "Авторское", value: "1" }, - { label: "Другое", value: "7" }, - { label: "Запад", value: "5" }, - { label: "Китай", value: "4" }, - { label: "Корея", value: "3" }, - { label: "Фанфики", value: "6" }, - { label: "Япония", value: "2" }, - ], - inputType: FilterInputs.Checkbox, - }, - { - key: "statuss", - label: "Статус тайтла", - values: [ - { label: "Закончен", value: "0" }, - { label: "Продолжается", value: "1" }, - { label: "Заморожен", value: "2" }, - { label: "Нет переводчика", value: "3" }, - { label: "Анонс", value: "4" }, - { label: "Лицензировано", value: "5" }, - ], - inputType: FilterInputs.Checkbox, - }, - { - key: "genres", - label: "Жанры", - values: [ - { label: "Боевик", value: "112" }, - { label: "Война", value: "123" }, - { label: "Детектив", value: "114" }, - { label: "Драма", value: "125" }, - { label: "Историческая проза", value: "115" }, - { label: "ЛитРПГ", value: "109" }, - { label: "Любовные романы", value: "116" }, - { label: "Мистика", value: "103" }, - { label: "Научная фантастика", value: "121" }, - { label: "Повседневность", value: "128" }, - { label: "Подростковая проза", value: "113" }, - { label: "Политический роман", value: "119" }, - { label: "Попаданцы", value: "108" }, - { label: "Поэзия", value: "105" }, - { label: "Разное", value: "111" }, - { label: "РеалРПГ", value: "117" }, - { label: "Романтика", value: "126" }, - { label: "Современная проза", value: "102" }, - { label: "Современная фантастика", value: "127" }, - { label: "Спорт", value: "122" }, - { label: "Трагедия", value: "129" }, - { label: "Триллер", value: "110" }, - { label: "Триллер и саспенс", value: "124" }, - { label: "Ужасы", value: "106" }, - { label: "Фантастика", value: "101" }, - { label: "Фанфик", value: "107" }, - { label: "Фьюжн роман", value: "120" }, - { label: "Фэнтези", value: "100" }, - { label: "Эротика", value: "118" }, - { label: "Юмор", value: "104" }, - ], - inputType: FilterInputs.Checkbox, - }, - { - key: "categories", - label: "Категории", - values: [ - { label: "[Награжденная работа]", value: "648" }, - { label: "18+", value: "423" }, - { label: "Абьюзеры", value: "663" }, - { label: "Авантюристы", value: "436" }, - { label: "Автоматоны", value: "593" }, - { label: "Агрессивные персонажи", value: "502" }, - { label: "Ад", value: "569" }, - { label: "Адаптация в радиопостановку", value: "581" }, - { label: "Академия", value: "153" }, - { label: "Актеры озвучки", value: "627" }, - { label: "Активный главный герой", value: "251" }, - { label: "Алхимия", value: "233" }, - { label: "Альтернативная история", value: "147" }, - { label: "Альтернативный мир", value: "156" }, - { label: "Амнезия/Потеря памяти", value: "344" }, - { label: "Анабиоз", value: "685" }, - { label: "Ангелы", value: "183" }, - { label: "Андрогинные персонажи", value: "321" }, - { label: "Андроиды", value: "203" }, - { label: "Анти-магия", value: "533" }, - { label: "Антигерой", value: "432" }, - { label: "Антикварный магазин", value: "623" }, - { label: "Антисоциальный главный герой", value: "613" }, - { label: "Антиутопия", value: "142" }, - { label: "Апатичный протагонист", value: "157" }, - { label: "Апокалипсис", value: "402" }, - { label: "Аранжированный брак", value: "375" }, - { label: "Армия", value: "639" }, - { label: "Артефакты", value: "234" }, - { label: "Артисты", value: "523" }, - { label: "Банды", value: "630" }, - { label: "БДСМ", value: "699" }, - { label: "Бедный главный герой", value: "398" }, - { label: "Безжалостный главный герой", value: "261" }, - { label: "Беззаботный главный герой", value: "438" }, - { label: "Безусловная любовь", value: "679" }, - { label: "Беременность", value: "250" }, - { label: "Бесполый главный герой", value: "325" }, - { label: "Бессмертные", value: "368" }, - { label: "Бесстрашный протагонист", value: "656" }, - { label: "Бесстыдный главный герой", value: "352" }, - { label: "Бесчестный главный герой", value: "716" }, - { label: "Библиотека", value: "428" }, - { label: "Бизнес-литература", value: "121" }, - { label: "Бизнесмен", value: "769" }, - { label: "Биочип", value: "238" }, - { label: "Бисексуальный главный герой", value: "773" }, - { label: "Близнецы", value: "263" }, - { label: "Боги", value: "317" }, - { label: "Богини", value: "439" }, - { label: "Боевая академия", value: "450" }, - { label: "Боевая фантастика", value: "149" }, - { label: "Боевое фэнтези", value: "104" }, - { label: "Боевые духи", value: "433" }, - { label: "Боевые соревнования", value: "493" }, - { label: "Божественная защита", value: "421" }, - { label: "Божественные силы", value: "327" }, - { label: "Борьба за власть", value: "599" }, - { label: "Брак", value: "446" }, - { label: "Брак по расчету", value: "188" }, - { label: "Братский комплекс", value: "159" }, - { label: "Братство", value: "487" }, - { label: "Братья и сестры", value: "577" }, - { label: "Буддизм", value: "745" }, - { label: "Быстрая культивация", value: "366" }, - { label: "Быстрообучаемый", value: "324" }, - { label: "Валькирии", value: "692" }, - { label: "Вампиры", value: "360" }, - { label: "Ваншот", value: "702" }, - { label: "Ведьмы", value: "281" }, - { label: "Вежливый главный герой", value: "379" }, - { label: "Верные подчиненные", value: "328" }, - { label: "Взрослый главный герой", value: "295" }, - { label: "Видит то, чего не видят другие", value: "671" }, - { label: "Виртуальная реальность", value: "401" }, - { label: "Владелец магазина", value: "681" }, - { label: "Внезапная сила", value: "455" }, - { label: "Внезапное богатство", value: "765" }, - { label: "Внешний вид отличается от факт", value: "419" }, - { label: "Военные Летописи", value: "743" }, - { label: "Возвращение из другого мира", value: "696" }, - { label: "Войны", value: "181" }, - { label: "Вокалоид", value: "701" }, - { label: "Волшебники/Волшебницы", value: "539" }, - { label: "Волшебные звери", value: "308" }, - { label: "Воображаемый друг", value: "651" }, - { label: "Воры", value: "414" }, - { label: "Воскрешение", value: "199" }, - { label: "Враги становятся возлюбленными", value: "498" }, - { label: "Враги становятся союзниками", value: "562" }, - { label: "Врата в другой мир", value: "609" }, - { label: "Врачи", value: "376" }, - { label: "Временной парадокс", value: "275" }, - { label: "Всемогущий главный герой", value: "169" }, - { label: "Вторжение на землю", value: "198" }, - { label: "Второй шанс", value: "231" }, - { label: "Вуайеризм", value: "790" }, - { label: "Выживание", value: "380" }, - { label: "Высокомерные персонажи", value: "361" }, - { label: "Гадание", value: "595" }, - { label: "Гарем рабов", value: "777" }, - { label: "Гг - женщина", value: "186" }, - { label: "Гг - мужчина", value: "166" }, - { label: "Гг силен с самого начала", value: "171" }, - { label: "Геймеры", value: "392" }, - { label: "Генералы", value: "326" }, - { label: "Генетические модификации", value: "657" }, - { label: "Гениальный главный герой", value: "617" }, - { label: "Герои", value: "285" }, - { label: "Героиня — сорванец", value: "583" }, - { label: "Героическая фантастика", value: "140" }, - { label: "Героическое фэнтези", value: "107" }, - { label: "Герой влюбляется первым", value: "187" }, - { label: "Гетерохромия", value: "570" }, - { label: "Гильдии", value: "411" }, - { label: "Гипнотизм", value: "755" }, - { label: "Главный герой — бог", value: "547" }, - { label: "Главный герой — гуманоид", value: "636" }, - { label: "Главный герой — наполовину чел", value: "445" }, - { label: "Главный герой — отец", value: "792" }, - { label: "Главный герой — раб", value: "780" }, - { label: "Главный герой — ребенок", value: "488" }, - { label: "Главный герой — рубака", value: "476" }, - { label: "Главный герой влюбляется первы", value: "683" }, - { label: "Главный герой играет роль", value: "472" }, - { label: "Главный герой носит очки", value: "672" }, - { label: "Главный герой пацифист", value: "698" }, - { label: "Гладиаторы", value: "603" }, - { label: "Глуповатый главный герой", value: "385" }, - { label: "Гоблины", value: "586" }, - { label: "Големы", value: "620" }, - { label: "Гомункул", value: "789" }, - { label: "Горничные", value: "459" }, - { label: "Городское фэнтези", value: "105" }, - { label: "Госпиталь", value: "800" }, - { label: "Готовка", value: "302" }, - { label: "Гриндинг", value: "393" }, - { label: "Дао Компаньон", value: "462" }, - { label: "Даосизм", value: "760" }, - { label: "Дварфы", value: "323" }, - { label: "Двойная личность", value: "642" }, - { label: "Двойник", value: "601" }, - { label: "Дворецкий", value: "660" }, - { label: "Дворяне", value: "168" }, - { label: "Дворянство/Аристократия", value: "437" }, - { label: "Девушки-монстры", value: "669" }, - { label: "Демоническая техника культивац", value: "722" }, - { label: "Демоны", value: "152" }, - { label: "Денежный долг", value: "783" }, - { label: "Депрессия", value: "555" }, - { label: "Детектив", value: "116" }, - { label: "Детективы", value: "612" }, - { label: "Детская литература", value: "123" }, - { label: "Дискриминация", value: "162" }, - { label: "Добыча денег в приоритете", value: "396" }, - { label: "Документальная проза", value: "122" }, - { label: "Долгая разлука", value: "307" }, - { label: "Домашние дела", value: "290" }, - { label: "Домогательство", value: "470" }, - { label: "Драконы", value: "303" }, - { label: "Драконьи всадники", value: "607" }, - { label: "Древние времена", value: "221" }, - { label: "Древний Китай", value: "374" }, - { label: "Дружба", value: "217" }, - { label: "Друзья детства", value: "282" }, - { label: "Друзья становятся врагами", value: "567" }, - { label: "Друиды", value: "497" }, - { label: "Дух лисы", value: "784" }, - { label: "Духи/призраки", value: "172" }, - { label: "Духовный советник", value: "435" }, - { label: "Душевность", value: "633" }, - { label: "Души", value: "254" }, - { label: "Европейская атмосфера", value: "521" }, - { label: "Ёкаи", value: "575" }, - { label: "Есть аниме-адаптация", value: "154" }, - { label: "Есть видеоигра по мотивам", value: "552" }, - { label: "Есть манга", value: "155" }, - { label: "Есть манхва-адаптация", value: "517" }, - { label: "Есть маньхуа-адаптация", value: "388" }, - { label: "Есть сериал-адаптация", value: "492" }, - { label: "Есть фильм", value: "173" }, - { label: "Женища-наставник", value: "505" }, - { label: "Жесткая, двуличная личность", value: "346" }, - { label: "Жестокие персонажи", value: "503" }, - { label: "Жестокое обращение с ребенком", value: "654" }, - { label: "Жестокость", value: "245" }, - { label: "Животноводство", value: "754" }, - { label: "Животные черты", value: "528" }, - { label: "Жизнь в квартире", value: "606" }, - { label: "Жрицы", value: "615" }, - { label: "Заботливый главный герой", value: "288" }, - { label: "Забывчивый главный герой", value: "628" }, - { label: "Заговоры", value: "289" }, - { label: "Закалка тела", value: "362" }, - { label: "Законники", value: "756" }, - { label: "Замкнутый главный герой", value: "589" }, - { label: "Запечатанная сила", value: "430" }, - { label: "Застенчивые персонажи", value: "508" }, - { label: "Звери", value: "237" }, - { label: "Звери-компаньоны", value: "301" }, - { label: "Злой протагонист", value: "243" }, - { label: "Злые боги", value: "504" }, - { label: "Злые организации", value: "563" }, - { label: "Злые религии", value: "734" }, - { label: "Знаменитости", value: "473" }, - { label: "Знаменитый главный герой", value: "531" }, - { label: "Знания современного мира", value: "297" }, - { label: "Зомби", value: "409" }, - { label: "Игра на выживание", value: "274" }, - { label: "Игривый протагонист", value: "717" }, - { label: "Игровая система рейтинга", value: "391" }, - { label: "Игровые элементы", value: "266" }, - { label: "Игрушки (18+)", value: "676" }, - { label: "Из грязи в князи", value: "416" }, - { label: "Из женщины в мужчину", value: "709" }, - { label: "Из мужчины в женщину", value: "715" }, - { label: "Из полного в худого", value: "767" }, - { label: "Из слабого в сильного", value: "202" }, - { label: "Извращенный главный герой", value: "434" }, - { label: "Изгои", value: "534" }, - { label: "Изменение расы", value: "664" }, - { label: "Изменения внешнего вида", value: "300" }, - { label: "Изменения личности", value: "219" }, - { label: "Изнасилование", value: "229" }, - { label: "Изображения жестокости", value: "355" }, - { label: "ИИ", value: "235" }, - { label: "Империи", value: "748" }, - { label: "Инвалидность", value: "684" }, - { label: "Индустриализация", value: "292" }, - { label: "Инженер", value: "163" }, - { label: "Инцест", value: "525" }, - { label: "Искусственный интеллект", value: "236" }, - { label: "Исследования", value: "670" }, - { label: "Историческая проза", value: "119" }, - { label: "Исторические приключения", value: "120" }, - { label: "Исторический детектив", value: "117" }, - { label: "Исторический любовный роман", value: "138" }, - { label: "Историческое фэнтези", value: "110" }, - { label: "Каннибализм", value: "456" }, - { label: "Карточные игры", value: "682" }, - { label: "Киберпанк", value: "141" }, - { label: "Киберспорт", value: "686" }, - { label: "Кланы", value: "796" }, - { label: "Класс безработного", value: "735" }, - { label: "Клоны", value: "447" }, - { label: "Клубы", value: "216" }, - { label: "Книги", value: "427" }, - { label: "Книги навыков", value: "400" }, - { label: "Книжный червь", value: "519" }, - { label: "Коварство", value: "230" }, - { label: "Коллеги", value: "704" }, - { label: "Колледж/Университет", value: "726" }, - { label: "Кома", value: "776" }, - { label: "Командная работа", value: "496" }, - { label: "Комедийный оттенок", value: "582" }, - { label: "Комплекс неполноценности", value: "550" }, - { label: "Комплекс семейных отношений", value: "223" }, - { label: "Конкуренция", value: "746" }, - { label: "Контроль разума/сознания", value: "357" }, - { label: "Копейщик", value: "425" }, - { label: "Королевская битва", value: "797" }, - { label: "Королевская власть", value: "178" }, - { label: "Королевства", value: "259" }, - { label: "Короткий любовный роман", value: "136" }, - { label: "Коррупция", value: "457" }, - { label: "Космическая фантастика", value: "146" }, - { label: "Космические войны", value: "697" }, - { label: "Красивый герой", value: "226" }, - { label: "Крафт", value: "377" }, - { label: "Кризис личности", value: "640" }, - { label: "Кругосветное путешествие", value: "353" }, - { label: "Кудере", value: "506" }, - { label: "Кузены", value: "718" }, - { label: "Кузнец", value: "518" }, - { label: "Кукловоды", value: "501" }, - { label: "Куклы/марионетки", value: "624" }, - { label: "Культивация", value: "241" }, - { label: "Куннилингус", value: "667" }, - { label: "Легенды", value: "500" }, - { label: "Легкая жизнь", value: "644" }, - { label: "Ленивый главный герой", value: "621" }, - { label: "Лидерство", value: "494" }, - { label: "Лоли", value: "213" }, - { label: "Лотерея", value: "477" }, - { label: "Любовное фэнтези", value: "135" }, - { label: "Любовный треугольник", value: "218" }, - { label: "Любовь детства", value: "560" }, - { label: "Любовь с первого взгляда", value: "737" }, - { label: "Магические надписи", value: "453" }, - { label: "Магические печати", value: "369" }, - { label: "Магические технологии", value: "440" }, - { label: "Магия", value: "165" }, - { label: "Магия призыва", value: "418" }, - { label: "Мазохистские персонажи", value: "688" }, - { label: "Манипулятивные персонажи", value: "249" }, - { label: "Мания", value: "212" }, - { label: "Мастер на все руки", value: "466" }, - { label: "Мастурбация", value: "788" }, - { label: "Махо-сёдзё", value: "556" }, - { label: "Медицинские знания", value: "507" }, - { label: "Медленная романтическая линия", value: "232" }, - { label: "Медленное развитие на старте", value: "694" }, - { label: "Межпространственные путешестви", value: "404" }, - { label: "Менеджмент", value: "294" }, - { label: "Мертвый главный герой", value: "761" }, - { label: "Месть", value: "209" }, - { label: "Метаморфы", value: "335" }, - { label: "Меч и магия", value: "180" }, - { label: "Мечник", value: "647" }, - { label: "Мечты", value: "738" }, - { label: "Милая история", value: "725" }, - { label: "Милое дитя", value: "637" }, - { label: "Милый главный герой", value: "714" }, - { label: "Мировое дерево", value: "463" }, - { label: "Мистика", value: "151" }, - { label: "Мистический ореол вокруг семьи", value: "483" }, - { label: "Мифические звери", value: "491" }, - { label: "Мифология", value: "530" }, - { label: "Младшие братья", value: "785" }, - { label: "Младшие сестры", value: "527" }, - { label: "ММОРПГ (ЛитРПГ)", value: "395" }, - { label: "Множество перемещенных людей", value: "549" }, - { label: "Множество реальностей", value: "370" }, - { label: "Множество реинкарнированных лю", value: "330" }, - { label: "Модели", value: "678" }, - { label: "Молчаливый персонаж", value: "721" }, - { label: "Монстры", value: "192" }, - { label: "Мужская гей-пара", value: "706" }, - { label: "Мужчина-яндере", value: "267" }, - { label: "Музыка", value: "635" }, - { label: "Музыкальные группы", value: "634" }, - { label: "Мутации", value: "693" }, - { label: "Мутированные существа", value: "405" }, - { label: "Навык кражи", value: "662" }, - { label: "Навязчивая любовь", value: "206" }, - { label: "Наемники", value: "412" }, - { label: "Назойливый возлюбленный", value: "561" }, - { label: "Наивный главный герой", value: "189" }, - { label: "Наркотики", value: "689" }, - { label: "Нарциссический главный герой", value: "532" }, - { label: "Насилие сексуального характера", value: "774" }, - { label: "Наследование", value: "452" }, - { label: "Научная фантастика", value: "144" }, - { label: "Национализм", value: "406" }, - { label: "Не блещущий внешне главный гер", value: "415" }, - { label: "Не родные братья и сестры", value: "526" }, - { label: "Небеса", value: "568" }, - { label: "Небесное испытание", value: "367" }, - { label: "Негуманоидный главный герой", value: "659" }, - { label: "Недоверчивый главный герой", value: "546" }, - { label: "Недооцененный главный герой", value: "258" }, - { label: "Недоразумения", value: "309" }, - { label: "Неизлечимая болезнь", value: "196" }, - { label: "Некромант", value: "397" }, - { label: "Нелинейная история", value: "269" }, - { label: "Ненавистный главный герой", value: "548" }, - { label: "Ненадежный рассказчик", value: "277" }, - { label: "Нерезиденты", value: "772" }, - { label: "Нерешительный главный герой", value: "744" }, - { label: "Несерьезный главный герой", value: "384" }, - { label: "Несколько временных линий", value: "576" }, - { label: "Несколько гг", value: "268" }, - { label: "Несколько главных героев", value: "537" }, - { label: "Несколько идентичностей", value: "652" }, - { label: "Несколько личностей", value: "536" }, - { label: "Нетораре", value: "732" }, - { label: "Нетори", value: "514" }, - { label: "Неудачливый главный герой", value: "618" }, - { label: "Ниндзя", value: "441" }, - { label: "Новелла", value: "103" }, - { label: "Обещание из детства", value: "742" }, - { label: "Обманщик", value: "485" }, - { label: "Обмен телами", value: "214" }, - { label: "Обнаженка", value: "490" }, - { label: "Обольщение", value: "731" }, - { label: "Оборотни", value: "661" }, - { label: "Обратный гарем", value: "351" }, - { label: "Общество монстров", value: "329" }, - { label: "Обязательство", value: "602" }, - { label: "Огнестрельное оружие", value: "291" }, - { label: "Ограниченное время жизни", value: "194" }, - { label: "Одержимость", value: "512" }, - { label: "Одинокий главный герой", value: "394" }, - { label: "Одиночество", value: "306" }, - { label: "Одиночное проживание", value: "313" }, - { label: "Околосмертные переживания", value: "759" }, - { label: "Оммёдзи", value: "597" }, - { label: "Омоложение", value: "338" }, - { label: "Организованная преступность", value: "540" }, - { label: "Оргия", value: "775" }, - { label: "Орки", value: "331" }, - { label: "Освоение навыков", value: "336" }, - { label: "Основано на аниме", value: "605" }, - { label: "Основано на видео игре", value: "720" }, - { label: "Основано на визуальной новелле", value: "793" }, - { label: "Основано на песне", value: "700" }, - { label: "Основано на фильме", value: "604" }, - { label: "Осторожный главный герой", value: "240" }, - { label: "Отаку", value: "571" }, - { label: "Открытый космос", value: "264" }, - { label: "Отношения в сети", value: "727" }, - { label: "Отношения между людьми и нелюд", value: "311" }, - { label: "Отношения на расстоянии", value: "791" }, - { label: "Отношения Сенпай-Коухай", value: "572" }, - { label: "Отношения ученика и учителя", value: "515" }, - { label: "Отношения учитель-ученик", value: "429" }, - { label: "Отношения хозяин-слуга", value: "312" }, - { label: "Отомэ игра", value: "733" }, - { label: "Отсутствие здравого смысла", value: "645" }, - { label: "Отсутствие родителей", value: "625" }, - { label: "Офисный роман", value: "707" }, - { label: "Официанты", value: "703" }, - { label: "Охотники", value: "378" }, - { label: "Очаровательный главный герой", value: "687" }, - { label: "Падшее дворянство", value: "655" }, - { label: "Падшие ангелы", value: "565" }, - { label: "Пайзури", value: "665" }, - { label: "Паразиты", value: "591" }, - { label: "Параллельные миры", value: "207" }, - { label: "Парк", value: "265" }, - { label: "Парк развлечений", value: "632" }, - { label: "Пародия", value: "407" }, - { label: "Певцы/Певицы", value: "739" }, - { label: "Первая любовь", value: "729" }, - { label: "Первоисточник новеллы — манга", value: "611" }, - { label: "Первый раз", value: "787" }, - { label: "Перемещение в игровой мир", value: "588" }, - { label: "Перемещение в иной мир", value: "750" }, - { label: "Перерождение в ином мире", value: "751" }, - { label: "Переселение души/трансмиграция", value: "257" }, - { label: "Персонаж использует щит", value: "666" }, - { label: "Петля времени", value: "200" }, - { label: "Пираты", value: "771" }, - { label: "Писатели", value: "482" }, - { label: "Питомцы", value: "349" }, - { label: "Племенное общество", value: "381" }, - { label: "Повелитель демонов", value: "283" }, - { label: "Подземелья", value: "322" }, - { label: "Подростковая проза", value: "114" }, - { label: "Пожелания", value: "278" }, - { label: "Познание Дао", value: "443" }, - { label: "Покинутое дитя", value: "590" }, - { label: "Полигамия", value: "386" }, - { label: "Политика", value: "170" }, - { label: "Политический роман", value: "133" }, - { label: "Полиция", value: "764" }, - { label: "Полулюди", value: "420" }, - { label: "Популярный любовный интерес", value: "342" }, - { label: "Постапокалипсис", value: "145" }, - { label: "Постапокалиптика", value: "184" }, - { label: "Потерянные цивилизации", value: "749" }, - { label: "Похищения людей", value: "723" }, - { label: "Поэзия", value: "111" }, - { label: "Правонарушители", value: "713" }, - { label: "Прагматичный главный герой", value: "592" }, - { label: "Преданный любовный интерес", value: "225" }, - { label: "Предательство", value: "222" }, - { label: "Предвидение", value: "177" }, - { label: "Прекрасная героиня", value: "158" }, - { label: "Преступники", value: "736" }, - { label: "Преступность", value: "204" }, - { label: "Призванный герой", value: "262" }, - { label: "Призраки", value: "176" }, - { label: "Принуждение к отношениям", value: "643" }, - { label: "Принцессы", value: "795" }, - { label: "Притворная пара", value: "641" }, - { label: "Причудливые персонажи", value: "387" }, - { label: "Пришельцы/инопланетяне", value: "197" }, - { label: "Программист", value: "691" }, - { label: "Проклятия", value: "174" }, - { label: "Промывание мозгов", value: "578" }, - { label: "Пропуск времени", value: "256" }, - { label: "Пророчества", value: "499" }, - { label: "Проститутки", value: "786" }, - { label: "Прошлое играет большую роль", value: "320" }, - { label: "Прыжки между мирами", value: "741" }, - { label: "Психические силы", value: "359" }, - { label: "Психопаты", value: "270" }, - { label: "Публицистика", value: "124" }, - { label: "Путешествие во времени", value: "201" }, - { label: "Пытка", value: "280" }, - { label: "Рабы", value: "778" }, - { label: "Развитие личности", value: "125" }, - { label: "Развод", value: "758" }, - { label: "Разумные предметы", value: "286" }, - { label: "Расизм", value: "408" }, - { label: "Рассказ", value: "185" }, - { label: "Расторжения помолвки", value: "343" }, - { label: "Расы зооморфов", value: "315" }, - { label: "Реализм", value: "246" }, - { label: "Ревность", value: "730" }, - { label: "Редакторы", value: "705" }, - { label: "Реинкарнация", value: "372" }, - { label: "Реинкарнация в монстра", value: "310" }, - { label: "Реинкарнация в объект", value: "711" }, - { label: "Религии", value: "616" }, - { label: "Репортеры", value: "781" }, - { label: "Ресторан", value: "680" }, - { label: "Решительный главный герой", value: "242" }, - { label: "Робкий главный герой", value: "763" }, - { label: "Родитель одиночка", value: "708" }, - { label: "Родительский комплекс", value: "587" }, - { label: "Родословная", value: "239" }, - { label: "Романтическая эротика", value: "132" }, - { label: "Романтический подсюжет", value: "271" }, - { label: "Рост персонажа", value: "215" }, - { label: "Рыцари", value: "260" }, - { label: "Садистские персонажи", value: "674" }, - { label: "Самоотверженный главный герой", value: "747" }, - { label: "Самурай", value: "677" }, - { label: "Сборник коротких историй", value: "520" }, - { label: "Связанные сюжетные линии", value: "535" }, - { label: "Святые", value: "752" }, - { label: "Священники", value: "413" }, - { label: "Сдержанный главный герой", value: "762" }, - { label: "Секретные организации", value: "417" }, - { label: "Секреты", value: "626" }, - { label: "Секс рабы", value: "779" }, - { label: "Семейный конфликт", value: "757" }, - { label: "Семь добродетелей", value: "334" }, - { label: "Семь смертных грехов", value: "253" }, - { label: "Семья", value: "347" }, - { label: "Сёнэн-ай подсюжет", value: "690" }, - { label: "Серийные убийцы", value: "557" }, - { label: "Сестринский комплекс", value: "558" }, - { label: "Сила духа", value: "373" }, - { label: "Сила, требующая платы за польз", value: "579" }, - { label: "Сильная пара", value: "228" }, - { label: "Сильный в сильнейшего", value: "442" }, - { label: "Сильный любовный интерес", value: "273" }, - { label: "Синдром восьмиклассника", value: "211" }, - { label: "Сироты", value: "319" }, - { label: "Система уровней", value: "305" }, - { label: "Системный администратор", value: "538" }, - { label: "Сказка", value: "126" }, - { label: "Скрытие истинной личности", value: "451" }, - { label: "Скрытие истинных способностей", value: "348" }, - { label: "Скрытный главный герой", value: "252" }, - { label: "Скрытые способности", value: "247" }, - { label: "Скульпторы", value: "448" }, - { label: "Слабо выраженная романтическая", value: "318" }, - { label: "Слабый главный герой", value: "298" }, - { label: "Слепой главный герой", value: "794" }, - { label: "Слуги", value: "333" }, - { label: "Слэш", value: "131" }, - { label: "Смерть", value: "175" }, - { label: "Смерть близких", value: "444" }, - { label: "Собственнические персонажи", value: "227" }, - { label: "Современная проза", value: "150" }, - { label: "Современность", value: "167" }, - { label: "Современный любовный роман", value: "137" }, - { label: "Сожительство", value: "544" }, - { label: "Создание армии", value: "287" }, - { label: "Создание артефактов", value: "382" }, - { label: "Создание клана", value: "513" }, - { label: "Создание королевства", value: "293" }, - { label: "Создание навыков", value: "337" }, - { label: "Создание секты", value: "469" }, - { label: "Солдаты/военные", value: "193" }, - { label: "Сон", value: "622" }, - { label: "Состоятельные персонажи", value: "190" }, - { label: "Социальная иерархия по силе", value: "255" }, - { label: "Социальная фантастика", value: "148" }, - { label: "Социальные изгои", value: "542" }, - { label: "Спасение мира", value: "638" }, - { label: "Специальные способности", value: "179" }, - { label: "Спокойный главный герой", value: "160" }, - { label: "Справедливый главный герой", value: "719" }, - { label: "Средневековье", value: "296" }, - { label: "Ссорящаяся пара", value: "383" }, - { label: "Сталкеры", value: "710" }, - { label: "Старение", value: "299" }, - { label: "Стимпанк", value: "139" }, - { label: "Стоические персонажи", value: "509" }, - { label: "Стокгольмский синдром", value: "675" }, - { label: "Стратег", value: "495" }, - { label: "Стратегические битвы", value: "272" }, - { label: "Стратегия", value: "798" }, - { label: "Стрелки", value: "522" }, - { label: "Стрельба из лука", value: "461" }, - { label: "Студенческий совет", value: "551" }, - { label: "Судьба", value: "364" }, - { label: "Суккубы", value: "545" }, - { label: "Супер герои", value: "799" }, - { label: "Суровая подготовка", value: "356" }, - { label: "Таинственная болезнь", value: "195" }, - { label: "Таинственное прошлое", value: "358" }, - { label: "Тайная личность", value: "399" }, - { label: "Тайные отношения", value: "768" }, - { label: "Танцоры", value: "782" }, - { label: "Телохранители", value: "516" }, - { label: "Темное фэнтези", value: "106" }, - { label: "Тентакли", value: "712" }, - { label: "Террористы", value: "574" }, - { label: "Технологический разрыв", value: "658" }, - { label: "Тихие персонажи", value: "600" }, - { label: "Толстый главный герой", value: "389" }, - { label: "Торговцы", value: "489" }, - { label: "Травля/буллинг", value: "210" }, - { label: "Травник", value: "724" }, - { label: "Трагическое прошлое", value: "276" }, - { label: "Трансплантация воспоминаний", value: "449" }, - { label: "Триллер", value: "134" }, - { label: "Трудолюбивый главный герой", value: "164" }, - { label: "Тюрьма", value: "541" }, - { label: "Убийства", value: "205" }, - { label: "Убийцы", value: "345" }, - { label: "Убийцы драконов", value: "646" }, - { label: "Уверенный главный герой", value: "363" }, - { label: "Удачливый главный герой", value: "478" }, - { label: "Ужасы", value: "112" }, - { label: "Укротитель монстров", value: "422" }, - { label: "Умения из прошлой жизни", value: "371" }, - { label: "Умная пара", value: "554" }, - { label: "Умный главный герой", value: "161" }, - { label: "Уникальная техника Культивации", value: "350" }, - { label: "Уникальное оружие", value: "426" }, - { label: "Управление бизнесом", value: "403" }, - { label: "Управление временем", value: "279" }, - { label: "Управление кровью", value: "753" }, - { label: "Упрямый главный герой", value: "695" }, - { label: "Уродливый главный герой", value: "766" }, - { label: "Ускоренный рост", value: "390" }, - { label: "Усыновленные дети", value: "543" }, - { label: "Усыновленный главный герой", value: "486" }, - { label: "Уход за детьми", value: "474" }, - { label: "Учителя", value: "431" }, - { label: "Фамильяры", value: "596" }, - { label: "Фанатизм", value: "650" }, - { label: "Фантастические существа", value: "410" }, - { label: "Фантастический детектив", value: "115" }, - { label: "Фанфик", value: "113" }, - { label: "Фанфикшн", value: "464" }, - { label: "Фармацевт", value: "728" }, - { label: "Фарминг", value: "458" }, - { label: "Феи", value: "316" }, - { label: "Фелляция", value: "631" }, - { label: "Фемслэш", value: "127" }, - { label: "Фениксы", value: "454" }, - { label: "Фетиш груди", value: "559" }, - { label: "Философия", value: "208" }, - { label: "Фильмы", value: "479" }, - { label: "Флэшбэки", value: "585" }, - { label: "Фобии", value: "220" }, - { label: "Фольклор", value: "529" }, - { label: "Футанари", value: "619" }, - { label: "Футуристический сеттинг", value: "460" }, - { label: "Фэнтези мир", value: "244" }, - { label: "Хакеры", value: "475" }, - { label: "Харизматический герой", value: "467" }, - { label: "Хикикомори/Затворники", value: "524" }, - { label: "Хитроумный главный герой", value: "224" }, - { label: "Хозяин подземелий", value: "608" }, - { label: "Холодный главный герой", value: "354" }, - { label: "Хорошие отношения с семьей", value: "566" }, - { label: "Хранители могил", value: "191" }, - { label: "Целители", value: "465" }, - { label: "Цзянши", value: "740" }, - { label: "Цундэрэ", value: "510" }, - { label: "Чаты", value: "629" }, - { label: "Человеческое оружие", value: "580" }, - { label: "Честный главный герой", value: "341" }, - { label: "Читы", value: "339" }, - { label: "Шантаж", value: "584" }, - { label: "Шеф-повар", value: "340" }, - { label: "Шикигами", value: "598" }, - { label: "Школа только для девочек", value: "668" }, - { label: "Шота", value: "573" }, - { label: "Шоу-бизнес", value: "481" }, - { label: "Шпионский детектив", value: "118" }, - { label: "Шпионы", value: "614" }, - { label: "Эволюция", value: "304" }, - { label: "Эгоистичный главный герой", value: "594" }, - { label: "Эйдетическая память", value: "468" }, - { label: "Экзорсизм", value: "564" }, - { label: "Экономика", value: "553" }, - { label: "Эксгибиционизм", value: "673" }, - { label: "Эксперименты с людьми", value: "248" }, - { label: "Элементальная магия", value: "471" }, - { label: "Эльфы", value: "284" }, - { label: "Эмоционально слабый гг", value: "770" }, - { label: "Эпизодический", value: "649" }, - { label: "Эпическое фэнтези", value: "109" }, - { label: "Эротическая фантастика", value: "129" }, - { label: "Эротический фанфик", value: "128" }, - { label: "Эротическое фэнтези", value: "130" }, - { label: "Юмористическая фантастика", value: "143" }, - { label: "Юмористическое фэнтези", value: "108" }, - { label: "Юный любовный интерес", value: "511" }, - { label: "Яды", value: "484" }, - { label: "Языкастые персонажи", value: "480" }, - { label: "Языковой барьер", value: "182" }, - { label: "Яндере", value: "314" }, - { label: "Японские силы самообороны", value: "610" }, - { label: "Ярко выраженная романтика", value: "365" }, - { label: "R-15 Японское возрастное огр.", value: "332" }, - { label: "R-18", value: "424" }, - ], - inputType: FilterInputs.Checkbox, - }, -]; - -module.exports = { - id: pluginId, - name: sourceName, - site: baseUrl, - version: "1.0.0", - lang: languages.Russian, - icon: "src/ru/renovels/icon.png", - filters, - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; diff --git a/plugins/russian/rulate.js b/plugins/russian/rulate.js deleted file mode 100644 index d91e2b9..0000000 --- a/plugins/russian/rulate.js +++ /dev/null @@ -1,1442 +0,0 @@ -const cheerio = require("cheerio"); -const dayjs = require("dayjs"); -const fetchApi = require("@libs/fetchApi"); -const fetchFile = require("@libs/fetchFile"); -const languages = require("@libs/languages"); -const Status = require("@libs/novelStatus"); -const FilterInputs = require("@libs/filterInputs"); -const defaultCoverUri = - "https://github.com/LNReader/lnreader-sources/blob/main/icons/src/coverNotAvailable.jpg?raw=true"; - -const pluginId = "RULATE"; -const sourceName = "Rulate"; -const baseUrl = "https://tl.rulate.ru"; - -async function popularNovels(page, { showLatestNovels, filters }) { - let url = baseUrl + "/search?t=&cat=2"; - url += "&sort=" + showLatestNovels ? "4" : filters?.sort || "6"; - url += "&type=" + filters?.type || "0"; - url += "&atmosphere=" + filters?.atmosphere || "0"; - url += "&adult=" + filters?.adult || "0"; - - if (filters?.genres?.length) { - url += filters.genres.map((i) => `&genres[]=${i}`).join(""); - } - - if (filters?.tags?.length) { - url += filters.tags.map((i) => `&tags[]=${i}`).join(""); - } - - if (filters?.trash?.length) { - url += filters.trash.map((i) => `&${i}=1`); - } - - url += `&Book_page=${page}`; - - const result = await fetchApi(url); - const body = await result.text(); - const loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio( - 'ul[class="search-results"] > li:not([class="ad_type_catalog"])' - ).each(function () { - novels.push({ - name: loadedCheerio(this).find("p > a").text(), - cover: baseUrl + loadedCheerio(this).find("img").attr("src"), - url: baseUrl + loadedCheerio(this).find("p > a").attr("href"), - }); - }); - - return novels; -} - -async function parseNovelAndChapters(novelUrl) { - let result = await fetchApi(novelUrl); - if (result.url.includes("mature?path=")) { - const formData = new FormData(); - formData.append("path", novelUrl); - formData.append("ok", "Да"); - - await fetchApi(result.url, { - method: "POST", - body: formData, - }); - - result = await fetchApi(novelUrl); - } - const body = await result.text(); - const loadedCheerio = cheerio.load(body); - - let novel = { - url: novelUrl, - }; - - novel.name = loadedCheerio('div[class="container"] > div > div > h1') - .text() - .trim(); - novel.cover = - baseUrl + loadedCheerio('div[class="images"] > div img').attr("src"); - novel.summary = loadedCheerio("#Info > div:nth-child(3)").html(); - let genre = []; - - loadedCheerio('div[class="span5"] > p').each(function () { - switch (loadedCheerio(this).find("strong").text()) { - case "Автор:": - novel.author = loadedCheerio(this).find("em > a").text().trim(); - break; - case "Выпуск:": - novel.status = - loadedCheerio(this).find("em").text().trim() === "продолжается" - ? Status.Ongoing - : Status.Completed; - break; - case "Тэги:": - loadedCheerio(this) - .find("em > a") - .each(function () { - genre.push(loadedCheerio(this).text()); - }); - break; - case "Жанры:": - loadedCheerio(this) - .find("em > a") - .each(function () { - genre.push(loadedCheerio(this).text()); - }); - break; - } - }); - - if (genre?.length) { - novel.genres = genre.reverse().join(","); - } - - let chapters = []; - loadedCheerio("table > tbody > tr.chapter_row").each(function () { - const releaseTime = loadedCheerio(this) - .find("td > span") - .attr("title") - ?.trim(); - - if ( - loadedCheerio(this).find('td > span[class="disabled"]').length < 1 && - releaseTime - ) { - const name = loadedCheerio(this).find('td[class="t"] > a').text().trim(); - const url = loadedCheerio(this).find('td[class="t"] > a').attr("href"); - - chapters.push({ name, releaseTime, url: baseUrl + url }); - } - }); - - novel.chapters = chapters; - return novel; -} - -async function parseChapter(chapterUrl) { - let result = await fetchApi(chapterUrl); - if (result.url.includes("mature?path=")) { - const formData = new FormData(); - formData.append("path", chapterUrl.replace(baseUrl, "")); - formData.append("ok", "Да"); - - await fetchApi(result.url, { - method: "POST", - body: formData, - }); - - result = await fetchApi(chapterUrl); - } - const body = await result.text(); - const loadedCheerio = cheerio.load(body); - - loadedCheerio(".content-text img").each(function () { - if (!loadedCheerio(this).attr("src")?.startsWith("http")) { - const src = loadedCheerio(this).attr("src"); - loadedCheerio(this).attr("src", baseUrl + src); - } - }); - - const chapterText = loadedCheerio(".content-text").html(); - return chapterText; -} - -async function searchNovels(searchTerm) { - const result = await fetchApi( - baseUrl + "/search/autocomplete?query=" + searchTerm - ); - const json = await result.json(); - - let novels = []; - json.forEach((novel) => - novels.push({ - name: novel.title_one + " / " + novel.title_two, - cover: baseUrl + novel.img, - url: baseUrl + novel.url, - }) - ); - - return novels; -} - -const filters = [ - { - key: "sort", - label: "Сортировка", - values: [ - { label: "По рейтингу", value: "6" }, - { label: "По степени готовности", value: "0" }, - { label: "По названию на языке оригинала", value: "1" }, - { label: "По названию на языке перевода", value: "2" }, - { label: "По дате создания", value: "3" }, - { label: "По дате последней активности", value: "4" }, - { label: "По просмотрам", value: "5" }, - { label: "По кол-ву переведённых глав", value: "7" }, - { label: "По кол-ву лайков", value: "8" }, - { label: "По кол-ву страниц", value: "10" }, - { label: "По кол-ву бесплатных глав", value: "11" }, - { label: "По кол-ву рецензий", value: "12" }, - { label: "По кол-ву в закладках", value: "13" }, - { label: "По кол-ву в избранном", value: "14" }, - ], - inputType: FilterInputs.Picker, - }, - { - key: "type", - label: "Тип", - values: [ - { label: "Неважно", value: "0" }, - { label: "Только переводы", value: "1" }, - { label: "Только авторские", value: "2" }, - ], - inputType: FilterInputs.Picker, - }, - { - key: "atmosphere", - label: "Атмосфера", - values: [ - { label: "Неважно", value: "0" }, - { label: "Позитивная", value: "1" }, - { label: "Dark", value: "2" }, - ], - inputType: FilterInputs.Picker, - }, - { - key: "trash", - label: "Другое", - values: [ - { label: "Готовые на 100%", value: "ready" }, - { label: "Доступные для перевода", value: "tr" }, - { label: "Доступные для скачивания", value: "gen" }, - { label: "Завершённые проекты", value: "wealth" }, - { label: "Распродажа", value: "discount" }, - { label: "Только онгоинги", value: "ongoings" }, - { label: "Убрать машинный", value: "remove_machinelate" }, - ], - inputType: FilterInputs.Checkbox, - }, - { - key: "genres", - label: "Жанры", - values: [ - { label: "18+", value: "52" }, - { label: "Арт", value: "1" }, - { label: "Боевик", value: "2" }, - { label: "Боевые искусства", value: "3" }, - { label: "Гарем", value: "5" }, - { label: "Героическое фэнтези", value: "7" }, - { label: "Детектив", value: "8" }, - { label: "Дзёсэй", value: "9" }, - { label: "Додзинси", value: "10" }, - { label: "Драма", value: "11" }, - { label: "История", value: "13" }, - { label: "Киберпанк", value: "46" }, - { label: "Кодомо", value: "14" }, - { label: "Комедия", value: "15" }, - { label: "Литрпг", value: "48" }, - { label: "Махо-сёдзё", value: "16" }, - { label: "Мелодрама", value: "49" }, - { label: "Меха", value: "17" }, - { label: "Мистика", value: "18" }, - { label: "Научная фантастика", value: "19" }, - { label: "Повседневность", value: "20" }, - { label: "Постапокалиптика", value: "21" }, - { label: "Приключения", value: "22" }, - { label: "Психология", value: "23" }, - { label: "Романтика", value: "24" }, - { label: "Самурайский боевик", value: "25" }, - { label: "Сверхъестественное", value: "26" }, - { label: "Сёдзё", value: "27" }, - { label: "Сёнэн", value: "29" }, - { label: "Смат", value: "45" }, - { label: "Спорт", value: "31" }, - { label: "Сэйнэн", value: "32" }, - { label: "Сюаньхуа", value: "44" }, - { label: "Сюаньхуань", value: "47" }, - { label: "Сянься (XianXia)", value: "42" }, - { label: "Трагедия", value: "33" }, - { label: "Триллер", value: "34" }, - { label: "Ужасы", value: "35" }, - { label: "Уся (wuxia)", value: "41" }, - { label: "Фантастика", value: "36" }, - { label: "Фанфик", value: "50" }, - { label: "Фэнтези", value: "37" }, - { label: "Школа", value: "38" }, - { label: "Этти", value: "39" }, - { label: "Bl", value: "43" }, - { label: "Gl", value: "40" }, - ], - inputType: FilterInputs.Checkbox, - }, - { - key: "tags", - label: "Тэги", - values: [ - { label: "12+", value: "3316" }, - { label: "16+", value: "83" }, - { label: "18+", value: "1749" }, - { label: "21+", value: "3191" }, - { label: "Абсурд", value: "7973" }, - { label: "Авантюристы", value: "222" }, - { label: "Автомат", value: "5272" }, - { label: "Авторский мир", value: "1062" }, - { label: "Агрессивные персонажи", value: "3249" }, - { label: "Ад", value: "2623" }, - { label: "Ад и рай", value: "817" }, - { label: "Адам дженсен", value: "1649" }, - { label: "Адаптация произведения", value: "192" }, - { label: "Адвокат", value: "6340" }, - { label: "Адекватные главные герои", value: "1953" }, - { label: "Азартные игры", value: "5301" }, - { label: "Азгард", value: "3494" }, - { label: "Азула", value: "5550" }, - { label: "Аид", value: "7641" }, - { label: "Айдолы", value: "4081" }, - { label: "Академия", value: "316" }, - { label: "Акамэ", value: "5059" }, - { label: "Аквакинез", value: "5952" }, - { label: "Акира", value: "2639" }, - { label: "Акнология", value: "4634" }, - { label: "Актерское мастерство", value: "4798" }, - { label: "Актёры", value: "7506" }, - { label: "Активные герои", value: "6376" }, - { label: "Акутагава рюноске", value: "4113" }, - { label: "Аладдин", value: "6489" }, - { label: "Алая ведьма", value: "4767" }, - { label: "Алиса", value: "4761" }, - { label: "Алкоголь", value: "5508" }, - { label: "Аллегория", value: "271" }, - { label: "Алукард", value: "4914" }, - { label: "Алхимики", value: "1164" }, - { label: "Алхимия", value: "1473" }, - { label: "Алчность", value: "6897" }, - { label: "Альбус дамблдор", value: "5784" }, - { label: "Альтернативное развитие событий", value: "5330" }, - { label: "Альянсы", value: "328" }, - { label: "Амбициозный главный герой", value: "3751" }, - { label: "Америка", value: "4082" }, - { label: "Амнезия", value: "2207" }, - { label: "Аморальный герой", value: "18" }, - { label: "Анальный секс", value: "1942" }, - { label: "Анбу", value: "8129" }, - { label: "Ангелы", value: "857" }, - { label: "Ангелы и демоны", value: "1327" }, - { label: "Ангст", value: "4638" }, - { label: "Андроиды", value: "1993" }, - { label: "Анимализм", value: "5249" }, - { label: "Аниме", value: "1294" }, - { label: "Анко митараши", value: "5768" }, - { label: "Антагонист", value: "2144" }, - { label: "Антигерой", value: "7725" }, - { label: "Антиквариат", value: "3552" }, - { label: "Антимаг", value: "1348" }, - { label: "Антиутопия", value: "662" }, - { label: "Апокалипсис", value: "1498" }, - { label: "Аристократия", value: "371" }, - { label: "Аристократы", value: "7338" }, - { label: "Армия", value: "1723" }, - { label: "Артас", value: "6700" }, - { label: "Артефакты", value: "2296" }, - { label: "Археология", value: "6523" }, - { label: "Асгард", value: "3635" }, - { label: "Ассасин", value: "1415" }, - { label: "Асуна", value: "5044" }, - { label: "Аугментации", value: "7751" }, - { label: "Ацуши накаджима", value: "4109" }, - { label: "Аянокоджи киетака", value: "6862" }, - { label: "Бандиты", value: "2188" }, - { label: "Басейн", value: "6847" }, - { label: "Баскетбол", value: "7933" }, - { label: "Бастарды", value: "76" }, - { label: "Бдсм", value: "444" }, - { label: "Бедность", value: "1207" }, - { label: "Без культивации", value: "668" }, - { label: "Без любовной линий", value: "2225" }, - { label: "Без перерождений", value: "262" }, - { label: "Без попаданца", value: "8065" }, - { label: "Без системы", value: "747" }, - { label: "Без цензуры", value: "7525" }, - { label: "Без юмора", value: "763" }, - { label: "Бездна", value: "1026" }, - { label: "Безжалостное домашнее животное", value: "1245" }, - { label: "Безжалостные персонажи", value: "2986" }, - { label: "Беззаботные персонажи ", value: "3123" }, - { label: "Безответная любовь", value: "7815" }, - { label: "Безработный", value: "2202" }, - { label: "Безумие", value: "37" }, - { label: "Безумные персонажи", value: "2049" }, - { label: "Бейсбол", value: "7623" }, - { label: "Беременность", value: "2122" }, - { label: "Бесконечный поток", value: "7556" }, - { label: "Беспечные персонажи", value: "742" }, - { label: "Бесплатно", value: "8403" }, - { label: "Бессмертные персонажи", value: "566" }, - { label: "Бесстрашные персонажи", value: "6921" }, - { label: "Бесстыдные персонажи", value: "1257" }, - { label: "Бесстыдный главный герой", value: "8190" }, - { label: "Библиотека", value: "4210" }, - { label: "Библия", value: "488" }, - { label: "Бизнес", value: "704" }, - { label: "Биология", value: "2220" }, - { label: "Биомехи", value: "1968" }, - { label: "Биседзе", value: "777" }, - { label: "Битва за веру", value: "721" }, - { label: "Битва за трон", value: "7211" }, - { label: "Битва королевств", value: "1151" }, - { label: "Битва рас", value: "6989" }, - { label: "Битвы", value: "1740" }, - { label: "Благородные персонажи", value: "2201" }, - { label: "Близнецы", value: "2931" }, - { label: "Богатство", value: "6522" }, - { label: "Богатые персонажи", value: "520" }, - { label: "Боги", value: "173" }, - { label: "Богини", value: "30" }, - { label: "Богиня", value: "8324" }, - { label: "Богоубийца", value: "1253" }, - { label: "Богохульство", value: "95" }, - { label: "Боевая академия", value: "2108" }, - { label: "Боевик", value: "529" }, - { label: "Боевые искусства", value: "2090" }, - { label: "Большая грудь", value: "4717" }, - { label: "Большой член", value: "5299" }, - { label: "Борос", value: "6533" }, - { label: "Борьба", value: "1925" }, - { label: "Борьба за власть", value: "8072" }, - { label: "Босс и подчиненный", value: "7157" }, - { label: "Бояръ", value: "2307" }, - { label: "Брак", value: "1558" }, - { label: "Брак по расчету", value: "1389" }, - { label: "Брат и сестра", value: "615" }, - { label: "Братский комплекс", value: "2321" }, - { label: "Броманс", value: "1103" }, - { label: "Брошенные дети", value: "2846" }, - { label: "Буддизм", value: "1871" }, - { label: "Будущее", value: "1820" }, - { label: "Бывший муж", value: "8080" }, - { label: "Быстрое развитие", value: "1443" }, - { label: "Бэтгерл", value: "2584" }, - { label: "Бэтмен", value: "4668" }, - { label: "В первый раз", value: "6212" }, - { label: "В этот же мир", value: "49" }, - { label: "Вагинальный секс", value: "4742" }, - { label: "Ваканда", value: "8331" }, - { label: "Валькирии", value: "4047" }, - { label: "Вампиры", value: "2206" }, - { label: "Веб камера", value: "5664" }, - { label: "Веб новелла", value: "2349" }, - { label: "Ведьмак", value: "843" }, - { label: "Ведьмы", value: "643" }, - { label: "Везучие персонажи", value: "1879" }, - { label: "Веном", value: "3994" }, - { label: "Вестерн", value: "2131" }, - { label: "Вестерос", value: "8171" }, - { label: "Взросление", value: "624" }, - { label: "Взрослый главный герой", value: "2416" }, - { label: "Видео игры", value: "2630" }, - { label: "Викинги", value: "267" }, - { label: "Вино", value: "6972" }, - { label: "Вирт", value: "6213" }, - { label: "Виртуальная реальность", value: "325" }, - { label: "Вирусы", value: "3327" }, - { label: "Владыка демонов", value: "555" }, - { label: "Власть", value: "756" }, - { label: "Влюбленность", value: "6132" }, - { label: "Внешний вид отличается от личности", value: "1938" }, - { label: "Внучка", value: "8399" }, - { label: "Военные", value: "1096" }, - { label: "Возрождение", value: "1160" }, - { label: "Воины", value: "287" }, - { label: "Война", value: "443" }, - { label: "Войны", value: "2431" }, - { label: "Воландеморт", value: "4278" }, - { label: "Волейбол", value: "6415" }, - { label: "Волки", value: "1166" }, - { label: "Волшебники", value: "122" }, - { label: "Волшебные существа", value: "1689" }, - { label: "Волшебство", value: "719" }, - { label: "Вор", value: "1399" }, - { label: "Воровка", value: "6948" }, - { label: "Ворон", value: "848" }, - { label: "Воскрешение", value: "6915" }, - { label: "Воспоминания из другого мира", value: "1366" }, - { label: "Воспоминания из прошлого", value: "1372" }, - { label: "Воссоединие семьи", value: "8130" }, - { label: "Восхождение", value: "380" }, - { label: "Враги становятся любовниками", value: "978" }, - { label: "Врата", value: "40" }, - { label: "Врата в другой мир", value: "1957" }, - { label: "Врач", value: "1243" }, - { label: "Временная петля", value: "2852" }, - { label: "Врмморпг", value: "2043" }, - { label: "Все дозволено", value: "96" }, - { label: "Всемогущий главный герой", value: "1414" }, - { label: "Второй шанс", value: "2372" }, - { label: "Вуайеризм", value: "2498" }, - { label: "Выживание", value: "501" }, - { label: "Вымышленные существа", value: "3466" }, - { label: "Вынужденный брак", value: "7705" }, - { label: "Высокие технологии", value: "653" }, - { label: "Высокомерные персонажи", value: "3250" }, - { label: "Галустян", value: "5199" }, - { label: "Гангстеры", value: "7704" }, - { label: "Гарем", value: "440" }, - { label: "Гарри поттер", value: "226" }, - { label: "Геймеры", value: "2130" }, - { label: "Генетические модификации", value: "1750" }, - { label: "Гениальный главный герой", value: "1500" }, - { label: "Гений", value: "1650" }, - { label: "Гермиона грейнджер", value: "4280" }, - { label: "Герои", value: "185" }, - { label: "Героическое фэнтези", value: "1148" }, - { label: "Гигаполис", value: "8393" }, - { label: "Гильгамеш", value: "6031" }, - { label: "Гильдии", value: "900" }, - { label: "Гильдия авантюристов", value: "1858" }, - { label: "Гинтама", value: "4544" }, - { label: "Гипноз", value: "358" }, - { label: "Главная героиня влюбляется первой", value: "973" }, - { label: "Главный герой бог", value: "164" }, - { label: "Главный герой влюбляется первым", value: "3629" }, - { label: "Главный герой выживальщик", value: "120" }, - { label: "Главный герой гонг", value: "7877" }, - { label: "Главный герой девушка", value: "257" }, - { label: "Главный герой демон", value: "136" }, - { label: "Главный герой женщина", value: "7329" }, - { label: "Главный герой жестокий", value: "1553" }, - { label: "Главный герой извращенец", value: "94" }, - { label: "Главный герой имба", value: "2421" }, - { label: "Главный герой киборг", value: "7780" }, - { label: "Главный герой монстр", value: "869" }, - { label: "Главный герой мужчина", value: "1997" }, - { label: "Главный герой на стороне сил зла", value: "1514" }, - { label: "Главный герой не адекватный", value: "5" }, - { label: "Главный герой не психопат", value: "7804" }, - { label: "Главный герой не человек", value: "2172" }, - { label: "Главный герой не эмоционален", value: "1150" }, - { label: "Главный герой подросток", value: "2215" }, - { label: "Главный герой позитивный", value: "7787" }, - { label: "Главный герой психопат", value: "7803" }, - { label: "Главный герой ребенок", value: "2347" }, - { label: "Главный герой русский", value: "2417" }, - { label: "Главный герой сильный с самого начала", value: "88" }, - { label: "Главный герой скрывает свою силу", value: "1059" }, - { label: "Главный герой эгоист", value: "8326" }, - { label: "Глотание спермы", value: "4491" }, - { label: "Гномы", value: "1870" }, - { label: "Гоблины", value: "2029" }, - { label: "Годзилла", value: "6990" }, - { label: "Големы", value: "1690" }, - { label: "Голливуд", value: "7754" }, - { label: "Головоломка", value: "1697" }, - { label: "Горничные", value: "8091" }, - { label: "Гробница", value: "388" }, - { label: "Групповой секс", value: "1314" }, - { label: "Дазай осаму", value: "4112" }, - { label: "Даньмэй", value: "3411" }, - { label: "Даосизм", value: "258" }, - { label: "Дарк соулс", value: "6057" }, - { label: "Дарт вейдер", value: "6826" }, - { label: "Дафна гринграсс", value: "5192" }, - { label: "Дворяне", value: "7204" }, - { label: "Девушки", value: "7668" }, - { label: "Девушки монстры", value: "2231" }, - { label: "Дедушка и внучка", value: "8400" }, - { label: "Дейви джонс", value: "8061" }, - { label: "Действие", value: "1425" }, - { label: "Демон лорд", value: "153" }, - { label: "Демонология", value: "2024" }, - { label: "Демоны", value: "1406" }, - { label: "Детектив", value: "1265" }, - { label: "Дети", value: "580" }, - { label: "Джарвис", value: "933" }, - { label: "Джедай", value: "4442" }, - { label: "Джин", value: "4024" }, - { label: "Джокер", value: "3351" }, - { label: "Джон сноу", value: "7412" }, - { label: "Джоффри баратеон", value: "3936" }, - { label: "Дзесэй", value: "678" }, - { label: "Дин винчестер", value: "6953" }, - { label: "Династии", value: "2982" }, - { label: "Динозавры", value: "3666" }, - { label: "Директор", value: "1417" }, - { label: "Для девушек", value: "1441" }, - { label: "Дневник", value: "298" }, - { label: "Добби", value: "6762" }, - { label: "Добрый мир", value: "1685" }, - { label: "Договоры с богами", value: "2152" }, - { label: "Договоры с демонами", value: "1922" }, - { label: "Доктор", value: "1991" }, - { label: "Доктор стрэндж", value: "7634" }, - { label: "Доминирование", value: "2054" }, - { label: "Дота", value: "6965" }, - { label: "Доудзюцу", value: "7133" }, - { label: "Дочь", value: "3291" }, - { label: "Драко малфой", value: "46" }, - { label: "Драконы", value: "282" }, - { label: "Драконы оборотни", value: "8082" }, - { label: "Драма", value: "611" }, - { label: "Древние времена", value: "7781" }, - { label: "Древний египет", value: "7614" }, - { label: "Древний китай", value: "473" }, - { label: "Древний мир", value: "4" }, - { label: "Дрочка", value: "4565" }, - { label: "Другая вселенная", value: "39" }, - { label: "Другие планеты", value: "2371" }, - { label: "Другой мир", value: "2248" }, - { label: "Дружба", value: "1903" }, - { label: "Друзья детства", value: "1153" }, - { label: "Духи", value: "114" }, - { label: "Духовные силы", value: "1955" }, - { label: "Души", value: "1212" }, - { label: "Дьявол", value: "1094" }, - { label: "Дьявольские фрукты", value: "1564" }, - { label: "Дэдпул", value: "4399" }, - { label: "Евнух", value: "6293" }, - { label: "Европейская атмосфера", value: "5837" }, - { label: "Египет", value: "528" }, - { label: "Еда", value: "2056" }, - { label: "Единороги", value: "437" }, - { label: "Екаи", value: "655" }, - { label: "Жалкий главный герой", value: "97" }, - { label: "Жена", value: "5729" }, - { label: "Женское доминирование", value: "4622" }, - { label: "Женщина в теле мужчины", value: "2748" }, - { label: "Женщина кошка", value: "7513" }, - { label: "Жесткий секс", value: "2238" }, - { label: "Жестокие персонажи ", value: "3153" }, - { label: "Жестокий мир", value: "1532" }, - { label: "Жестокое обращение с детьми", value: "84" }, - { label: "Жестокость", value: "7880" }, - { label: "Животные", value: "7488" }, - { label: "Животные компаньоны", value: "2061" }, - { label: "Жизнь и смерть", value: "2014" }, - { label: "Жнец", value: "6288" }, - { label: "Жойен рид", value: "3926" }, - { label: "Заботливый главный герой", value: "338" }, - { label: "Закалка тела", value: "148" }, - { label: "Заклинания", value: "558" }, - { label: "Заключенные", value: "3052" }, - { label: "Занпакто", value: "1102" }, - { label: "Запретная любовь", value: "8033" }, - { label: "Запутанный сюжет", value: "1041" }, - { label: "Захват мира", value: "315" }, - { label: "Зачарованные", value: "4169" }, - { label: "Звери", value: "1638" }, - { label: "Зверодевочки", value: "2006" }, - { label: "Зверолюди", value: "1018" }, - { label: "Земля", value: "82" }, - { label: "Злая организация", value: "7768" }, - { label: "Зло", value: "1512" }, - { label: "Злодей", value: "1060" }, - { label: "Злодейка", value: "2376" }, - { label: "Злой главный герой", value: "616" }, - { label: "Злые боги", value: "7109" }, - { label: "Злые персонажи", value: "3243" }, - { label: "Змей", value: "5721" }, - { label: "Знаменитости", value: "1341" }, - { label: "Знание медицины", value: "1411" }, - { label: "Золотой дождь", value: "1828" }, - { label: "Золушка", value: "2980" }, - { label: "Зомби", value: "1743" }, - { label: "Зомби апокалипсис", value: "912" }, - { label: "Зона 51", value: "3051" }, - { label: "Зооморфы", value: "375" }, - { label: "Зоофилия", value: "1165" }, - { label: "Зорро", value: "6736" }, - { label: "Зрелые женщины", value: "1458" }, - { label: "Игра", value: "663" }, - { label: "Игра на выживание", value: "2182" }, - { label: "Игровая система", value: "384" }, - { label: "Игровые элементы", value: "720" }, - { label: "Извращения", value: "651" }, - { label: "Измена", value: "1874" }, - { label: "Изменение характера", value: "1113" }, - { label: "Изменения внешности", value: "259" }, - { label: "Изменения личности", value: "1080" }, - { label: "Изнасилование", value: "1283" }, - { label: "Изобретения", value: "126" }, - { label: "Изуку мидория", value: "6967" }, - { label: "Император", value: "867" }, - { label: "Империи", value: "712" }, - { label: "Империя", value: "1716" }, - { label: "Инвалидность", value: "2073" }, - { label: "Инквизитор", value: "143" }, - { label: "Инкуб", value: "3264" }, - { label: "Инопланетное вторжение", value: "209" }, - { label: "Инопланетяне", value: "866" }, - { label: "Интерактив с читателями", value: "472" }, - { label: "Интересный сюжет", value: "7374" }, - { label: "Интернет", value: "739" }, - { label: "Интимные сцены", value: "3450" }, - { label: "Интрига", value: "317" }, - { label: "Интриги и заговоры", value: "1009" }, - { label: "Инцест", value: "1934" }, - { label: "Иные миры", value: "35" }, - { label: "Исекай", value: "2979" }, - { label: "Искусственный интеллект", value: "1900" }, - { label: "Исследование", value: "1626" }, - { label: "Исторический роман", value: "573" }, - { label: "Итачи учиха", value: "4556" }, - { label: "Ичиго", value: "7254" }, - { label: "Йода", value: "6829" }, - { label: "Йоруичи", value: "6755" }, - { label: "Калипсо", value: "8062" }, - { label: "Каннибализм", value: "2249" }, - { label: "Капитан америка", value: "6301" }, - { label: "Каракура", value: "7473" }, - { label: "Карточная игра", value: "1557" }, - { label: "Кастиэль", value: "6954" }, - { label: "Кафе", value: "3934" }, - { label: "Кацуки бакуго", value: "7696" }, - { label: "Квесты", value: "1707" }, - { label: "Киберпанк", value: "1614" }, - { label: "Киберспорт", value: "1984" }, - { label: "Кино", value: "7126" }, - { label: "Китай", value: "3762" }, - { label: "Кицунэ", value: "241" }, - { label: "Кланы", value: "834" }, - { label: "Клоны", value: "1624" }, - { label: "Книги", value: "1068" }, - { label: "Книжный червь", value: "521" }, - { label: "Колдовство", value: "2681" }, - { label: "Колледж", value: "5058" }, - { label: "Коллекционер", value: "2588" }, - { label: "Кольцо", value: "1628" }, - { label: "Комедия", value: "1430" }, - { label: "Коммунисты", value: "1042" }, - { label: "Коноха", value: "2827" }, - { label: "Контракт", value: "4304" }, - { label: "Контроль разума", value: "485" }, - { label: "Конфеты", value: "341" }, - { label: "Кончил внутрь", value: "4589" }, - { label: "Копрофилия", value: "2568" }, - { label: "Корейцы", value: "6535" }, - { label: "Корея", value: "2443" }, - { label: "Королевская власть", value: "55" }, - { label: "Королевская семья", value: "276" }, - { label: "Королевство", value: "1695" }, - { label: "Короткий рассказ", value: "239" }, - { label: "Коррупция", value: "831" }, - { label: "Космическая опера", value: "2245" }, - { label: "Космические рейнджеры", value: "5860" }, - { label: "Космос", value: "329" }, - { label: "Кот", value: "5171" }, - { label: "Кража навыков", value: "1159" }, - { label: "Красивая главная героиня", value: "186" }, - { label: "Красивые женщины", value: "66" }, - { label: "Красивые персонажи ", value: "5787" }, - { label: "Красивый главный герой", value: "895" }, - { label: "Крафт", value: "1599" }, - { label: "Кровь и расчлененка", value: "7752" }, - { label: "Кроссовер", value: "75" }, - { label: "Ксенофилия", value: "2265" }, - { label: "Ктулху", value: "920" }, - { label: "Кузнец", value: "853" }, - { label: "Кукла", value: "3085" }, - { label: "Кулинария", value: "2230" }, - { label: "Культивация", value: "1901" }, - { label: "Культивирование", value: "1860" }, - { label: "Кунилингус", value: "4651" }, - { label: "Курама", value: "5709" }, - { label: "Курилин", value: "6134" }, - { label: "Кушина узумаки", value: "5222" }, - { label: "Лавкрафт", value: "6063" }, - { label: "Лара крофт", value: "6435" }, - { label: "Легенда о зельде", value: "5707" }, - { label: "Ленивый главный герой", value: "1516" }, - { label: "Леон золдик", value: "7469" }, - { label: "Лес", value: "6645" }, - { label: "Лечебная магия", value: "562" }, - { label: "Литрпг", value: "505" }, - { label: "Локи", value: "1725" }, - { label: "Лоли", value: "1576" }, - { label: "Лопата", value: "4989" }, - { label: "Лорд", value: "8404" }, - { label: "Лориен", value: "7397" }, - { label: "Лошади", value: "6110" }, - { label: "Лунатизм", value: "3520" }, - { label: "Луффи", value: "274" }, - { label: "Лучник", value: "1038" }, - { label: "Любовный интерес влюбляется первым", value: "7753" }, - { label: "Любовный треугольник", value: "2109" }, - { label: "Любовь", value: "2297" }, - { label: "Любовь с первого взгляда", value: "7748" }, - { label: "Любящие родители", value: "2699" }, - { label: "Люди", value: "3572" }, - { label: "Люцифер", value: "5883" }, - { label: "Маг", value: "780" }, - { label: "Магазин", value: "4762" }, - { label: "Маги", value: "58" }, - { label: "Магическая академия", value: "6788" }, - { label: "Магические звери", value: "684" }, - { label: "Магические предметы", value: "1648" }, - { label: "Магический мир", value: "578" }, - { label: "Магия", value: "150" }, - { label: "Магия воды", value: "6450" }, - { label: "Магия воздуха", value: "7627" }, - { label: "Магия земли", value: "7626" }, - { label: "Магия и технология", value: "107" }, - { label: "Магия крови", value: "7976" }, - { label: "Магия льда", value: "1379" }, - { label: "Магия огня", value: "7625" }, - { label: "Магия призыва", value: "885" }, - { label: "Магия природы", value: "6226" }, - { label: "Магия стихий", value: "7673" }, - { label: "Магия тьмы", value: "3596" }, - { label: "Мадара", value: "2929" }, - { label: "Мазохизм", value: "1961" }, - { label: "Мама", value: "12" }, - { label: "Мама и дочь", value: "7894" }, - { label: "Мама и сын", value: "7895" }, - { label: "Манга", value: "2464" }, - { label: "Манипуляция временем", value: "932" }, - { label: "Маньяк", value: "2026" }, - { label: "Маргери тирелл", value: "3935" }, - { label: "Марионетки", value: "3175" }, - { label: "Марс", value: "4013" }, - { label: "Марти стью", value: "594" }, - { label: "Мастер на все руки", value: "3189" }, - { label: "Мастурбация", value: "3078" }, - { label: "Матриархат", value: "7197" }, - { label: "Мать и сестры", value: "7946" }, - { label: "Мафия", value: "353" }, - { label: "Мачеха", value: "4664" }, - { label: "Мегаполис", value: "3322" }, - { label: "Медицина", value: "441" }, - { label: "Медленная романтика", value: "8383" }, - { label: "Медленное развитие истории", value: "7462" }, - { label: "Межрасовый секс", value: "3489" }, - { label: "Мелиодас", value: "7224" }, - { label: "Мелодрама", value: "1507" }, - { label: "Менеджмент", value: "368" }, - { label: "Мерлин", value: "3977" }, - { label: "Месть", value: "1218" }, - { label: "Меха", value: "2303" }, - { label: "Меч", value: "48" }, - { label: "Мечники", value: "969" }, - { label: "Мечта", value: "527" }, - { label: "Мизуки", value: "4269" }, - { label: "Микото", value: "3862" }, - { label: "Милая главная героиня", value: "134" }, - { label: "Милф", value: "1987" }, - { label: "Милый главный герой", value: "2678" }, - { label: "Мимик", value: "5706" }, - { label: "Мина ашидо", value: "7695" }, - { label: "Минато намикадзе", value: "5223" }, - { label: "Минет", value: "3289" }, - { label: "Мир будущего", value: "1770" }, - { label: "Мир земли", value: "8394" }, - { label: "Мир меча и магии", value: "1271" }, - { label: "Мистер пропер", value: "565" }, - { label: "Мистика", value: "510" }, - { label: "Мифические существа", value: "1878" }, - { label: "Мифы и легенды", value: "4463" }, - { label: "Младшая сестра", value: "1523" }, - { label: "Мморпг", value: "620" }, - { label: "Много главных героев", value: "1191" }, - { label: "Много крови", value: "5318" }, - { label: "Много персонажей", value: "1539" }, - { label: "Множество королевств", value: "32" }, - { label: "Модерн", value: "5457" }, - { label: "Мокутон", value: "4438" }, - { label: "Молоко", value: "5469" }, - { label: "Монархия", value: "3590" }, - { label: "Монстры", value: "307" }, - { label: "Море", value: "922" }, - { label: "Моритика ринносукэ", value: "1142" }, - { label: "Мортал комбат", value: "5861" }, - { label: "Мошенник", value: "959" }, - { label: "Мрачный мир", value: "1" }, - { label: "Мужчина протагонист", value: "31" }, - { label: "Музыка", value: "1183" }, - { label: "Мультивселенная", value: "7410" }, - { label: "Мурим", value: "7342" }, - { label: "Мутанты", value: "2392" }, - { label: "Мутация", value: "29" }, - { label: "Мясо", value: "1503" }, - { label: "На реальных событиях", value: "26" }, - { label: "Навыки", value: "415" }, - { label: "Нагато узумаки", value: "5221" }, - { label: "Наемники", value: "1668" }, - { label: "Назарик", value: "5369" }, - { label: "Наивный главный герой", value: "159" }, - { label: "Наложница", value: "4867" }, - { label: "Наноботы", value: "2332" }, - { label: "Наркотики", value: "1436" }, - { label: "Наруто", value: "7177" }, - { label: "Наруто и хината", value: "5766" }, - { label: "Насилие и жестокость", value: "1440" }, - { label: "Наука", value: "2150" }, - { label: "Научная фантастика", value: "2244" }, - { label: "Национализм", value: "5556" }, - { label: "Нацу", value: "5826" }, - { label: "Нацуки субару", value: "6656" }, - { label: "Начальная школа", value: "3920" }, - { label: "Наши дни", value: "1814" }, - { label: "Не всесильный главный герой", value: "42" }, - { label: "Не по канону", value: "1611" }, - { label: "Неверность", value: "5735" }, - { label: "Невеста", value: "6011" }, - { label: "Неджи хьюга", value: "3099" }, - { label: "Недооценённый главный герой", value: "43" }, - { label: "Нежить", value: "1949" }, - { label: "Некромаг", value: "7441" }, - { label: "Некромант", value: "1914" }, - { label: "Некромантия", value: "754" }, - { label: "Некрофилия", value: "1285" }, - { label: "Ненормативная лексика", value: "160" }, - { label: "Необычные герои", value: "67" }, - { label: "Нет людей", value: "7486" }, - { label: "Нетораре", value: "403" }, - { label: "Нетори", value: "5393" }, - { label: "Нефилимы", value: "7764" }, - { label: "Нецензурная лексика", value: "4912" }, - { label: "Нечисть", value: "4559" }, - { label: "Ник фьюри", value: "6304" }, - { label: "Ниндзюцу", value: "2354" }, - { label: "Ниндзя", value: "1330" }, - { label: "Новелла", value: "2105" }, - { label: "Нудизм", value: "2233" }, - { label: "Нэко", value: "147" }, - { label: "Оби ван кеноби", value: "6830" }, - { label: "Обито учиха", value: "2814" }, - { label: "Оборотни", value: "53" }, - { label: "Обратный гарем", value: "3303" }, - { label: "Овощ", value: "6098" }, - { label: "Огнестрельное оружие", value: "3155" }, - { label: "Одержимость", value: "708" }, - { label: "Один в поле воин", value: "6498" }, - { label: "Онлайн игра", value: "1565" }, - { label: "Ооками теппей", value: "849" }, - { label: "Оральный секс", value: "605" }, - { label: "Оргия", value: "974" }, - { label: "Оригинальные персонажи", value: "7741" }, - { label: "Орки", value: "1597" }, - { label: "Оружие", value: "125" }, - { label: "Основано на видео игре", value: "602" }, - { label: "Особые способности", value: "2904" }, - { label: "От бедности к богатству", value: "901" }, - { label: "От слабого до сильного", value: "118" }, - { label: "Отель", value: "2879" }, - { label: "Отец", value: "3290" }, - { label: "Отношения", value: "398" }, - { label: "Отношения человек/нечеловек", value: "7406" }, - { label: "Офис", value: "3460" }, - { label: "Охотники", value: "2263" }, - { label: "Оцуцуки", value: "5101" }, - { label: "Ошейник", value: "2362" }, - { label: "Ояш", value: "8048" }, - { label: "Падчерица", value: "7023" }, - { label: "Падший ангел", value: "184" }, - { label: "Пайзури", value: "3560" }, - { label: "Палач", value: "852" }, - { label: "Палочка", value: "2682" }, - { label: "Пандорум", value: "4360" }, - { label: "Папа и дочь", value: "7647" }, - { label: "Паразит", value: "1213" }, - { label: "Параллельный мир", value: "2092" }, - { label: "Парень", value: "619" }, - { label: "Паркур", value: "3391" }, - { label: "Пародия", value: "898" }, - { label: "Пасхалки", value: "455" }, - { label: "Паук", value: "2209" }, - { label: "Пекарь", value: "5073" }, - { label: "Первая книга", value: "269" }, - { label: "Первая любовь", value: "640" }, - { label: "Первый раз", value: "1465" }, - { label: "Переводы", value: "24" }, - { label: "Перевоплощение", value: "1596" }, - { label: "Перевоплощение в игровом мире", value: "92" }, - { label: "Перемещение в другой мир", value: "1288" }, - { label: "Перемещение во времени", value: "146" }, - { label: "Перерождение", value: "894" }, - { label: "Перерождение в демона", value: "1111" }, - { label: "Перерождение в дракона", value: "6867" }, - { label: "Перерождение в злодейку", value: "5619" }, - { label: "Перерождение в злодея", value: "749" }, - { label: "Перерождение в ином мире", value: "109" }, - { label: "Перерождение в монстра", value: "917" }, - { label: "Перерождений", value: "335" }, - { label: "Переселение", value: "813" }, - { label: "Переселение души", value: "1864" }, - { label: "Перси джексон", value: "6329" }, - { label: "Пилот", value: "5521" }, - { label: "Пилюли", value: "1531" }, - { label: "Пират", value: "1579" }, - { label: "Пирог", value: "80" }, - { label: "Писатель", value: "734" }, - { label: "Пистолеты", value: "4916" }, - { label: "Письмо", value: "840" }, - { label: "Питомцы", value: "3122" }, - { label: "Планомерное развитие событий", value: "62" }, - { label: "Племянник", value: "8303" }, - { label: "Пленница", value: "4897" }, - { label: "Плети", value: "4839" }, - { label: "Пляж", value: "4243" }, - { label: "Повар", value: "658" }, - { label: "Повествование от первого лица", value: "1054" }, - { label: "Повествование от разных лиц", value: "600" }, - { label: "Повествование от третьего лица", value: "2116" }, - { label: "Повседневность", value: "802" }, - { label: "Подавитель", value: "497" }, - { label: "Подземелье", value: "798" }, - { label: "Подростки", value: "636" }, - { label: "Подруга", value: "5804" }, - { label: "Подчинение", value: "8343" }, - { label: "Подчинение и унижение", value: "4500" }, - { label: "Покемоны", value: "2840" }, - { label: "Полиамория", value: "1300" }, - { label: "Полигамия", value: "1336" }, - { label: "Политика", value: "1313" }, - { label: "Полицейский", value: "1907" }, - { label: "Полубог", value: "7170" }, - { label: "Полукровка", value: "999" }, - { label: "Полулюди", value: "2273" }, - { label: "Поношенные трусики", value: "1204" }, - { label: "Попаданец", value: "2396" }, - { label: "Попаданец в другой мир", value: "685" }, - { label: "Попаданец в игру", value: "6466" }, - { label: "Попадание в книгу", value: "6957" }, - { label: "Порно", value: "4023" }, - { label: "Постапокалипсис", value: "1079" }, - { label: "Потеря девственности", value: "5879" }, - { label: "Похищение", value: "7452" }, - { label: "Поэзия", value: "1120" }, - { label: "Правитель", value: "2261" }, - { label: "Превращения в животных", value: "3410" }, - { label: "Преданный любовный интерес", value: "2055" }, - { label: "Предательство", value: "70" }, - { label: "Президент", value: "4334" }, - { label: "Преступник", value: "4049" }, - { label: "Преступный мир", value: "965" }, - { label: "Призрак", value: "511" }, - { label: "Призыв", value: "332" }, - { label: "Призыв в другой мир", value: "781" }, - { label: "Призыв существ", value: "412" }, - { label: "Приключения", value: "964" }, - { label: "Приложение на телефоне", value: "5345" }, - { label: "Принудительный брак", value: "2954" }, - { label: "Принуждение", value: "1442" }, - { label: "Принц", value: "362" }, - { label: "Принцесса", value: "2403" }, - { label: "Приручение мостров", value: "2338" }, - { label: "Прислуга", value: "5185" }, - { label: "Пришельцы", value: "273" }, - { label: "Проза", value: "2012" }, - { label: "Прокачка", value: "1070" }, - { label: "Проклятия", value: "1347" }, - { label: "Проработанный мир", value: "1711" }, - { label: "Пророчество", value: "7762" }, - { label: "Псайкеры", value: "3659" }, - { label: "Псионика", value: "5216" }, - { label: "Психбольница", value: "6969" }, - { label: "Психические расстройства", value: "2445" }, - { label: "Психологические травмы", value: "3418" }, - { label: "Психология", value: "17" }, - { label: "Публичный дом", value: "7860" }, - { label: "Публичный секс", value: "6647" }, - { label: "Путешествие в другой мир", value: "10" }, - { label: "Пух", value: "4286" }, - { label: "Пушечное мясо", value: "7234" }, - { label: "Пытки", value: "1756" }, - { label: "Пьяный мастер", value: "1655" }, - { label: "Пэнси паркинсон", value: "7411" }, - { label: "Раб", value: "977" }, - { label: "Рабы", value: "1240" }, - { label: "Разведение животных", value: "3990" }, - { label: "Развитие", value: "208" }, - { label: "Развитие персонажа", value: "1780" }, - { label: "Развитие поселения", value: "7203" }, - { label: "Развитие технологий", value: "1875" }, - { label: "Разврат", value: "1924" }, - { label: "Раздвоение личности", value: "2062" }, - { label: "Разные расы", value: "1188" }, - { label: "Разорванная помолвка", value: "2057" }, - { label: "Разработчик", value: "2333" }, - { label: "Ранги", value: "1568" }, - { label: "Ранобэ", value: "1764" }, - { label: "Расизм", value: "3554" }, - { label: "Растение", value: "407" }, - { label: "Расчленение", value: "7125" }, - { label: "Реализм", value: "1180" }, - { label: "Ребенок", value: "2881" }, - { label: "Ревность", value: "293" }, - { label: "Революция", value: "3996" }, - { label: "Реинкарнация", value: "1302" }, - { label: "Реинкарнация в другом мире", value: "2180" }, - { label: "Реинкарнация в игровой мир", value: "304" }, - { label: "Рейджер", value: "5147" }, - { label: "Религия", value: "2184" }, - { label: "Рем", value: "6655" }, - { label: "Ремесленник", value: "1483" }, - { label: "Ремонт", value: "5703" }, - { label: "Репликаторы", value: "3495" }, - { label: "Ресторан", value: "7519" }, - { label: "Риас гремори", value: "6950" }, - { label: "Рим", value: "612" }, - { label: "Риннеган", value: "4230" }, - { label: "Ритуал", value: "1233" }, - { label: "Робот", value: "4011" }, - { label: "Роботы", value: "467" }, - { label: "Рок ли", value: "7002" }, - { label: "Рокси", value: "7380" }, - { label: "Романтика", value: "1025" }, - { label: "Рон уизли", value: "4279" }, - { label: "Ророноа зоро", value: "1590" }, - { label: "Росомаха", value: "1035" }, - { label: "Российская империя", value: "2146" }, - { label: "Россия", value: "4246" }, - { label: "Рудеус", value: "7379" }, - { label: "Руны", value: "2393" }, - { label: "Русалки", value: "3127" }, - { label: "Русь", value: "2016" }, - { label: "Рыцари", value: "1902" }, - { label: "Рэо хатаке", value: "2823" }, - { label: "Сабли", value: "6136" }, - { label: "Сайян", value: "6802" }, - { label: "Сакура", value: "2515" }, - { label: "Самец", value: "1592" }, - { label: "Самоубийство", value: "4312" }, - { label: "Самураи", value: "576" }, - { label: "Санса старк", value: "3925" }, - { label: "Сарказм", value: "4658" }, - { label: "Саске", value: "7237" }, - { label: "Сатира", value: "215" }, - { label: "Сборник", value: "7367" }, - { label: "Свадьба", value: "1524" }, - { label: "Сверхсила", value: "1055" }, - { label: "Сверхъестественное", value: "1074" }, - { label: "Свингеры", value: "514" }, - { label: "Свинцовые книги", value: "5322" }, - { label: "Связывание", value: "4506" }, - { label: "Священик", value: "61" }, - { label: "Северус снейп", value: "5783" }, - { label: "Сёдзе", value: "2219" }, - { label: "Секс", value: "2472" }, - { label: "Секс без проникновения", value: "151" }, - { label: "Секс игрушки", value: "1124" }, - { label: "Секс по принуждению", value: "8335" }, - { label: "Секс по согласию", value: "7307" }, - { label: "Секс рабыня", value: "7174" }, - { label: "Секс с близнецами", value: "7457" }, - { label: "Секс с монстрами", value: "6073" }, - { label: "Секс с учителем", value: "2570" }, - { label: "Секса будет много", value: "7265" }, - { label: "Сексуальные и психологические отклонения", value: "888" }, - { label: "Секты", value: "1234" }, - { label: "Селфцест", value: "7498" }, - { label: "Сельское хозяйство", value: "787" }, - { label: "Семейный конфликт", value: "935" }, - { label: "Семья", value: "728" }, - { label: "Сенджу", value: "6069" }, - { label: "Сёнэн", value: "2058" }, - { label: "Серийный убийца", value: "261" }, - { label: "Сестра", value: "2342" }, - { label: "Сила глаза", value: "1019" }, - { label: "Сильная героиня", value: "7292" }, - { label: "Сильная главная героиня", value: "1994" }, - { label: "Сильный главный герой", value: "570" }, - { label: "Сильфиетта", value: "7381" }, - { label: "Симбиот", value: "2648" }, - { label: "Сириус блэк", value: "7655" }, - { label: "Сирота", value: "297" }, - { label: "Система", value: "251" }, - { label: "Система уровней", value: "2174" }, - { label: "Сиськи", value: "16" }, - { label: "Ситх", value: "1618" }, - { label: "Сказка", value: "3570" }, - { label: "Скайрим", value: "4032" }, - { label: "Скайуокер", value: "6828" }, - { label: "Сквиб", value: "4101" }, - { label: "Сквирт", value: "4526" }, - { label: "Скелет", value: "1315" }, - { label: "Скрытые способности", value: "3417" }, - { label: "Скульптор", value: "1178" }, - { label: "Слабая главная героиня", value: "5805" }, - { label: "Слабый главный герой", value: "731" }, - { label: "Славяне", value: "2168" }, - { label: "Слизерин", value: "6979" }, - { label: "Слизь", value: "1095" }, - { label: "Сломанная игрушка", value: "8396" }, - { label: "Служанка", value: "3486" }, - { label: "Смартфон", value: "457" }, - { label: "Смена сущности", value: "8391" }, - { label: "Смерть", value: "1779" }, - { label: "Смерть основного персонажа", value: "1530" }, - { label: "Собаки", value: "6109" }, - { label: "Соблазн", value: "7994" }, - { label: "Соблазнение", value: "2290" }, - { label: "Современность", value: "568" }, - { label: "Современные знания", value: "988" }, - { label: "Сожительство", value: "3026" }, - { label: "Создание армии", value: "1992" }, - { label: "Создание королевства", value: "1497" }, - { label: "Создание пилюль", value: "816" }, - { label: "Создатель", value: "1045" }, - { label: "Сокровища", value: "6521" }, - { label: "Сокрытие истинной личности", value: "2894" }, - { label: "Сома и эрина", value: "5737" }, - { label: "Соперничество", value: "7699" }, - { label: "Соседи", value: "4075" }, - { label: "Сотрудник", value: "6339" }, - { label: "Социология", value: "7618" }, - { label: "Спокойная главная героиня", value: "236" }, - { label: "Спокойный главный герой", value: "1656" }, - { label: "Спор", value: "3484" }, - { label: "Спорт", value: "1387" }, - { label: "Спортивное тело", value: "8317" }, - { label: "Способность кражи", value: "7784" }, - { label: "Сражения", value: "23" }, - { label: "Средневековье", value: "1022" }, - { label: "Сталкер", value: "1932" }, - { label: "Становление государства", value: "111" }, - { label: "Старшая школа", value: "1658" }, - { label: "Стёб", value: "454" }, - { label: "Стекло", value: "7453" }, - { label: "Стеснительный персонаж", value: "152" }, - { label: "Стимпанк", value: "1894" }, - { label: "Стихи", value: "1309" }, - { label: "Стратегия", value: "569" }, - { label: "Стриптиз", value: "6914" }, - { label: "Строительство", value: "921" }, - { label: "Студент", value: "1464" }, - { label: "Студентка", value: "7518" }, - { label: "Суккубы", value: "2662" }, - { label: "Супер зрение", value: "6574" }, - { label: "Супер удача", value: "489" }, - { label: "Супергерои", value: "1481" }, - { label: "Суперзлодеи", value: "2125" }, - { label: "Супермен", value: "6997" }, - { label: "Суперсилы", value: "2276" }, - { label: "Существа", value: "6033" }, - { label: "Сферы", value: "1077" }, - { label: "Схемы и заговоры", value: "7116" }, - { label: "Сценарий", value: "7489" }, - { label: "Счастливый конец", value: "3293" }, - { label: "Сын", value: "3297" }, - { label: "Сэйнэн", value: "345" }, - { label: "Сэм винчестер", value: "7461" }, - { label: "Сюаньхуа", value: "2560" }, - { label: "Сюжетные повороты", value: "7202" }, - { label: "Сянься", value: "1126" }, - { label: "Таблетки для развития", value: "589" }, - { label: "Таинственное прошлое", value: "769" }, - { label: "Тайная личность", value: "8096" }, - { label: "Тайная любовь", value: "113" }, - { label: "Тайны", value: "7481" }, - { label: "Тайные отношения", value: "7450" }, - { label: "Тайцы", value: "6413" }, - { label: "Танки", value: "1787" }, - { label: "Твинцест", value: "2450" }, - { label: "Творчество", value: "20" }, - { label: "Тейлор эберт", value: "3526" }, - { label: "Телекинез", value: "2203" }, - { label: "Телепортация", value: "914" }, - { label: "Телохранитель", value: "723" }, - { label: "Темари", value: "4810" }, - { label: "Темная магия", value: "4788" }, - { label: "Темное фэнтези", value: "1675" }, - { label: "Темные эльфы", value: "7198" }, - { label: "Тенсейган", value: "4367" }, - { label: "Тентакли", value: "3236" }, - { label: "Тетя", value: "4194" }, - { label: "Техномагия", value: "508" }, - { label: "Тиран", value: "1174" }, - { label: "Титаны", value: "3308" }, - { label: "Тодороки шото", value: "5105" }, - { label: "Токио", value: "5791" }, - { label: "Том реддл", value: "6652" }, - { label: "Тони старк", value: "1338" }, - { label: "Тор", value: "1800" }, - { label: "Тор и локи", value: "2645" }, - { label: "Торговля", value: "1637" }, - { label: "Травничество", value: "221" }, - { label: "Трагедия", value: "470" }, - { label: "Трагическое прошлое", value: "644" }, - { label: "Трактир", value: "1918" }, - { label: "Трансмиграция", value: "1071" }, - { label: "Трансформация", value: "156" }, - { label: "Трансформеры", value: "4616" }, - { label: "Тревор филипс", value: "5427" }, - { label: "Триллер", value: "1676" }, - { label: "Тройняшки", value: "7169" }, - { label: "Троли", value: "4250" }, - { label: "Трудолюбивый главный герой", value: "248" }, - { label: "Тупой главный герой", value: "3710" }, - { label: "Тьма", value: "1919" }, - { label: "Тюрьма", value: "5597" }, - { label: "Тяжёлое детство", value: "2253" }, - { label: "Убийства", value: "378" }, - { label: "Убийцы", value: "1766" }, - { label: "Убийцы драконов", value: "1484" }, - { label: "Увечья", value: "3266" }, - { label: "Удача", value: "6575" }, - { label: "Ужасы", value: "1747" }, - { label: "Узумаки", value: "7579" }, - { label: "Укротитель", value: "367" }, - { label: "Умения", value: "531" }, - { label: "Умная главная героиня", value: "1381" }, - { label: "Умные персонажи", value: "2756" }, - { label: "Умный главный герой", value: "2093" }, - { label: "Университет", value: "2102" }, - { label: "Унижение", value: "8342" }, - { label: "Уничтожение мира", value: "81" }, - { label: "Управление временем", value: "5648" }, - { label: "Уровни", value: "8" }, - { label: "Усиление", value: "3549" }, - { label: "Усопп", value: "6737" }, - { label: "Устроенный брак", value: "2223" }, - { label: "Усыновление", value: "395" }, - { label: "Уся", value: "1172" }, - { label: "Утопия", value: "4677" }, - { label: "Уход за детьми", value: "7609" }, - { label: "Учеба в университете", value: "2797" }, - { label: "Учебное заведение", value: "1817" }, - { label: "Ученые", value: "1843" }, - { label: "Учитель", value: "845" }, - { label: "Учиха", value: "5348" }, - { label: "Фамильяры", value: "1471" }, - { label: "Фанатичная любовь", value: "6721" }, - { label: "Фанаты", value: "7739" }, - { label: "Фантастика", value: "286" }, - { label: "Фантастический мир", value: "2414" }, - { label: "Фанфик", value: "767" }, - { label: "Фармацевт", value: "1706" }, - { label: "Фашисты", value: "3118" }, - { label: "Феи", value: "7185" }, - { label: "Фелляция", value: "5880" }, - { label: "Феминизм", value: "6527" }, - { label: "Феникс", value: "2413" }, - { label: "Фенрир", value: "2740" }, - { label: "Феодализм", value: "280" }, - { label: "Ферма", value: "285" }, - { label: "Фетиш", value: "2398" }, - { label: "Фехтовальщик", value: "4685" }, - { label: "Филиппины", value: "128" }, - { label: "Философия", value: "1859" }, - { label: "Фистинг", value: "4206" }, - { label: "Флафф", value: "5048" }, - { label: "Флер делакур", value: "5191" }, - { label: "Флэш", value: "4667" }, - { label: "Фокусник", value: "7602" }, - { label: "Фольклор", value: "4086" }, - { label: "Фугаку", value: "6161" }, - { label: "Фурри", value: "6631" }, - { label: "Футбол", value: "1121" }, - { label: "Фэнтези", value: "174" }, - { label: "Фэнтезийный мир", value: "1556" }, - { label: "Хакер", value: "475" }, - { label: "Халк", value: "1196" }, - { label: "Хан нуньен синг", value: "4817" }, - { label: "Ханма юджиро", value: "4369" }, - { label: "Хаос", value: "3117" }, - { label: "Харизматичный главный герой", value: "2350" }, - { label: "Хатаке какаши", value: "2820" }, - { label: "Хентай", value: "1682" }, - { label: "Хиддлстон", value: "5903" }, - { label: "Химавари", value: "2583" }, - { label: "Хината", value: "2513" }, - { label: "Хитрый главный герой", value: "2036" }, - { label: "Хоббит", value: "6698" }, - { label: "Хогвартс", value: "5954" }, - { label: "Холодная главная героиня", value: "6507" }, - { label: "Холодное оружие", value: "4889" }, - { label: "Холодный главный герой", value: "2686" }, - { label: "Храм", value: "2135" }, - { label: "Христианство", value: "877" }, - { label: "Художественная литература", value: "2380" }, - { label: "Художница", value: "518" }, - { label: "Хулиганы", value: "854" }, - { label: "Цветок", value: "902" }, - { label: "Цезарь", value: "1705" }, - { label: "Целители", value: "4523" }, - { label: "Церковь", value: "1980" }, - { label: "Цундере", value: "1078" }, - { label: "Чакра", value: "1393" }, - { label: "Черный юмор", value: "2868" }, - { label: "Честная главная героиня", value: "5855" }, - { label: "Честный главный герой", value: "129" }, - { label: "Читы", value: "451" }, - { label: "Шантаж", value: "7175" }, - { label: "Шаринган", value: "5165" }, - { label: "Шинигами", value: "4661" }, - { label: "Шиноби", value: "1227" }, - { label: "Школьная жизнь", value: "284" }, - { label: "Школьники", value: "218" }, - { label: "Школьницы", value: "5946" }, - { label: "Шлюха", value: "5822" }, - { label: "Шоу-бизнес", value: "7119" }, - { label: "Шпионы", value: "4262" }, - { label: "Эволюция", value: "2040" }, - { label: "Экзорцизм", value: "676" }, - { label: "Экономика", value: "121" }, - { label: "Эксгибиционизм", value: "1643" }, - { label: "Экспансия", value: "110" }, - { label: "Эксперименты над людьми", value: "7408" }, - { label: "Экшен", value: "2859" }, - { label: "Элементальная магия", value: "1264" }, - { label: "Элементы бдсм", value: "1323" }, - { label: "Эльфы", value: "1362" }, - { label: "Эротика", value: "2053" }, - { label: "Этти", value: "1317" }, - { label: "Юмор", value: "7360" }, - { label: "Яды", value: "201" }, - { label: "Якудза", value: "4599" }, - { label: "Яндере", value: "7111" }, - { label: "Bl", value: "8282" }, - { label: "Gl", value: "8284" }, - { label: "Rpg", value: "90" }, - { label: "Star wars", value: "1985" }, - { label: "Time skip", value: "824" }, - { label: "Warrior of light", value: "5289" }, - ], - inputType: FilterInputs.Checkbox, - }, - { - key: "adult", - label: "Возрастные ограничения", - values: [ - { label: "Все", value: "0" }, - { label: "Убрать 18+", value: "1" }, - { label: "Только 18+", value: "2" }, - ], - inputType: FilterInputs.Picker, - }, -]; - -module.exports = { - id: pluginId, - name: sourceName, - site: baseUrl, - version: "1.0.0", - lang: languages.Russian, - icon: "src/ru/rulate/icon.png", - filters, - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; diff --git a/plugins/russian/ruranoberu[noveltl].ts b/plugins/russian/ruranoberu[noveltl].ts new file mode 100644 index 0000000..d1d04bf --- /dev/null +++ b/plugins/russian/ruranoberu[noveltl].ts @@ -0,0 +1,219 @@ + + import { load as parseHTML } from "cheerio"; + import dayjs from 'dayjs'; + import { fetchFile, fetchApi } from "@libs/fetch"; + import { Novel, Plugin, Chapter } from "@typings/plugin"; + // import { parseMadaraDate } from "@libs/parseMadaraDate"; + // import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; + // import { showToast } from "@libs/showToast"; + import { Filter, FilterInputs } from "@libs/filterInputs"; + import { NovelStatus } from '@libs/novelStatus'; + import { defaultCover } from "@libs/defaultCover"; + + + export const id = "ruranobe"; + export const name = "РуРанобэ"; + export const icon = ""; + export const version = "1.0.0"; + export const site = "https://ruranobe.ru"; + export const filters: Filter[] = [{"key":"tags","label":"Тэги","values":[{"label":"Автоматы","value":64},{"label":"Агрессивные персонажи","value":26},{"label":"Ад","value":338},{"label":"Адаптация манги","value":8},{"label":"Академия","value":5},{"label":"Алхимия","value":27},{"label":"Альтернативная реальность","value":30},{"label":"Амнезия","value":31},{"label":"Анабиоз","value":170},{"label":"Ангелы","value":38},{"label":"Андроиды","value":37},{"label":"Антигерой","value":43},{"label":"Антимагия","value":41},{"label":"Апатичный главный герой","value":46},{"label":"Апокалипсис","value":47},{"label":"Аристократия","value":51},{"label":"Артефакты","value":58},{"label":"Асоциальный главный герой","value":42},{"label":"R-15","value":570},{"label":"R-18","value":571}],"inputType":FilterInputs.Checkbox}]; + exports["protected"] = false; + + const baseUrl = site; + const domain = baseUrl.split('//')[1]; + + export const popularNovels: Plugin.popularNovels = async function ( + page, + { filters } + ) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: + 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: filters?.tags?.length ?? 0 > 0 ? { tags: filters?.tags } : {}, + hostname: domain, + limit: 40, + page, + }, + }), + }); + + const json: any = await result.json(); + const novels: Novel.Item[] = json.data?.projects?.content?.map((novel: any) => ({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: novel.covers[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + })); + + return novels; + }; + + + export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Book', + query: + 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}', + variables: { + hostname: domain, + project: novelUrl, + url: novelUrl, + }, + }), + }); + const json: any = await result.json(); + const novel: Novel.instance = { + url: novelUrl, + name: json.data.project.title, + cover: json.data.project.covers[0]?.url + ? baseUrl + json.data.project.covers[0].url + : defaultCover, + summary: parseHTML(json.data.project.annotation?.text).text(), + author: + json.data.project.persons[0].name.firstName + + ' ' + + json.data.project.persons[0].name.lastName, + status: + json.data.project.translationStatus === 'active' + ? NovelStatus.Ongoing + : NovelStatus.Completed, + }; + + let genres = [] + .concat(json.data.project.tags, json.data.project.genres) + .map((item: any) => item?.nameRu || item?.nameEng) + .filter(item => item); + + if (genres.length > 0) { + novel.genres = genres.join(','); + } + + let novelChapters: Chapter.Item[] = []; + json.data.project.subprojects.content.forEach((work: any) => + work.volumes.content.forEach((volume: any) => + volume.chapters.forEach( + (chapter: any) => + chapter?.published && + novelChapters.push({ + name: volume.shortName + ' ' + chapter.title, + url: novelUrl + '/' + volume.url + '/' + chapter.url, + releaseTime: dayjs(chapter.publishDate).format('LLL'), + }), + ), + ), + ); + novel.chapters = novelChapters; + return novel; + }; + + export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'EReaderData', + query: + 'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}', + variables: { + chapterSelector: { + fullUrl: chapterUrl, + }, + url: chapterUrl, + }, + }), + }); + const json: any = await result.json(); + + return parseHTML(json.data.chapter.text.text).html(); + }; + + export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: + 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: { + query: searchTerm, + }, + hostname: domain, + limit: 40, + page: 1, + }, + }), + }); + const json: any = await result.json(); + const novels: Novel.Item[] = []; + json?.data?.projects?.content?.forEach((novel: any) => + novels.push({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: novel.covers[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + }), + ); + return novels; + }; + + export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url, {}); + }; + \ No newline at end of file diff --git a/plugins/russian/sferdrakonnoveltl[noveltl].ts b/plugins/russian/sferdrakonnoveltl[noveltl].ts new file mode 100644 index 0000000..4d0acf5 --- /dev/null +++ b/plugins/russian/sferdrakonnoveltl[noveltl].ts @@ -0,0 +1,219 @@ + + import { load as parseHTML } from "cheerio"; + import dayjs from 'dayjs'; + import { fetchFile, fetchApi } from "@libs/fetch"; + import { Novel, Plugin, Chapter } from "@typings/plugin"; + // import { parseMadaraDate } from "@libs/parseMadaraDate"; + // import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; + // import { showToast } from "@libs/showToast"; + import { Filter, FilterInputs } from "@libs/filterInputs"; + import { NovelStatus } from '@libs/novelStatus'; + import { defaultCover } from "@libs/defaultCover"; + + + export const id = "sferdrakon.novel"; + export const name = "Уголок дракона"; + export const icon = ""; + export const version = "1.0.0"; + export const site = "https://sferdrakon.novel.tl"; + export const filters: Filter[] = [{"key":"tags","label":"Тэги","values":[{"label":"Главный герой — мужчина","value":419},{"label":"Демоны","value":193},{"label":"Драконы","value":218},{"label":"Есть аниме-адаптация","value":10},{"label":"Есть видеоигра","value":13},{"label":"Есть манга-адаптация","value":14},{"label":"Звероподобные","value":79},{"label":"Изнасилование","value":574},{"label":"Королевская особа","value":596},{"label":"Магия","value":412},{"label":"Магия призыва","value":694},{"label":"Наездники на драконах","value":216},{"label":"Рыцари","value":383},{"label":"Цундере","value":728},{"label":"Эльфы","value":235}],"inputType":FilterInputs.Checkbox}]; + exports["protected"] = false; + + const baseUrl = site; + const domain = baseUrl.split('//')[1]; + + export const popularNovels: Plugin.popularNovels = async function ( + page, + { filters } + ) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: + 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: filters?.tags?.length ?? 0 > 0 ? { tags: filters?.tags } : {}, + hostname: domain, + limit: 40, + page, + }, + }), + }); + + const json: any = await result.json(); + const novels: Novel.Item[] = json.data?.projects?.content?.map((novel: any) => ({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: novel.covers[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + })); + + return novels; + }; + + + export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Book', + query: + 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}', + variables: { + hostname: domain, + project: novelUrl, + url: novelUrl, + }, + }), + }); + const json: any = await result.json(); + const novel: Novel.instance = { + url: novelUrl, + name: json.data.project.title, + cover: json.data.project.covers[0]?.url + ? baseUrl + json.data.project.covers[0].url + : defaultCover, + summary: parseHTML(json.data.project.annotation?.text).text(), + author: + json.data.project.persons[0].name.firstName + + ' ' + + json.data.project.persons[0].name.lastName, + status: + json.data.project.translationStatus === 'active' + ? NovelStatus.Ongoing + : NovelStatus.Completed, + }; + + let genres = [] + .concat(json.data.project.tags, json.data.project.genres) + .map((item: any) => item?.nameRu || item?.nameEng) + .filter(item => item); + + if (genres.length > 0) { + novel.genres = genres.join(','); + } + + let novelChapters: Chapter.Item[] = []; + json.data.project.subprojects.content.forEach((work: any) => + work.volumes.content.forEach((volume: any) => + volume.chapters.forEach( + (chapter: any) => + chapter?.published && + novelChapters.push({ + name: volume.shortName + ' ' + chapter.title, + url: novelUrl + '/' + volume.url + '/' + chapter.url, + releaseTime: dayjs(chapter.publishDate).format('LLL'), + }), + ), + ), + ); + novel.chapters = novelChapters; + return novel; + }; + + export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'EReaderData', + query: + 'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}', + variables: { + chapterSelector: { + fullUrl: chapterUrl, + }, + url: chapterUrl, + }, + }), + }); + const json: any = await result.json(); + + return parseHTML(json.data.chapter.text.text).html(); + }; + + export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: + 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: { + query: searchTerm, + }, + hostname: domain, + limit: 40, + page: 1, + }, + }), + }); + const json: any = await result.json(); + const novels: Novel.Item[] = []; + json?.data?.projects?.content?.forEach((novel: any) => + novels.push({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: novel.covers[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + }), + ); + return novels; + }; + + export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url, {}); + }; + \ No newline at end of file diff --git a/plugins/russian/sinoveltl[noveltl].ts b/plugins/russian/sinoveltl[noveltl].ts new file mode 100644 index 0000000..c190524 --- /dev/null +++ b/plugins/russian/sinoveltl[noveltl].ts @@ -0,0 +1,219 @@ + + import { load as parseHTML } from "cheerio"; + import dayjs from 'dayjs'; + import { fetchFile, fetchApi } from "@libs/fetch"; + import { Novel, Plugin, Chapter } from "@typings/plugin"; + // import { parseMadaraDate } from "@libs/parseMadaraDate"; + // import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; + // import { showToast } from "@libs/showToast"; + import { Filter, FilterInputs } from "@libs/filterInputs"; + import { NovelStatus } from '@libs/novelStatus'; + import { defaultCover } from "@libs/defaultCover"; + + + export const id = "si.novel"; + export const name = "Сказочные истории"; + export const icon = ""; + export const version = "1.0.0"; + export const site = "https://si.novel.tl"; + export const filters: Filter[] = [{"key":"tags","label":"Тэги","values":[{"label":"Аристократия","value":51},{"label":"Война","value":750},{"label":"Главная героиня — женщина","value":277},{"label":"Драконы","value":218},{"label":"Древний Китай","value":34},{"label":"Жестокие персонажи","value":4},{"label":"Злобные благородные девы","value":743},{"label":"Комедийный подтекст","value":147},{"label":"Культивация","value":171},{"label":"Магия","value":412},{"label":"Отомэ-игры","value":504},{"label":"Перерождение в другом мире","value":580},{"label":"Перерождение в другом мире","value":720},{"label":"Повелитель демонов","value":191},{"label":"Попаданец в другой мир","value":723},{"label":"Смерть","value":185},{"label":"Средневековье","value":436},{"label":"Фэнтези","value":267},{"label":"Харизматичный протагонист","value":120},{"label":"Хитрый главный герой","value":173}],"inputType":FilterInputs.Checkbox}]; + exports["protected"] = false; + + const baseUrl = site; + const domain = baseUrl.split('//')[1]; + + export const popularNovels: Plugin.popularNovels = async function ( + page, + { filters } + ) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: + 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: filters?.tags?.length ?? 0 > 0 ? { tags: filters?.tags } : {}, + hostname: domain, + limit: 40, + page, + }, + }), + }); + + const json: any = await result.json(); + const novels: Novel.Item[] = json.data?.projects?.content?.map((novel: any) => ({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: novel.covers[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + })); + + return novels; + }; + + + export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Book', + query: + 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}', + variables: { + hostname: domain, + project: novelUrl, + url: novelUrl, + }, + }), + }); + const json: any = await result.json(); + const novel: Novel.instance = { + url: novelUrl, + name: json.data.project.title, + cover: json.data.project.covers[0]?.url + ? baseUrl + json.data.project.covers[0].url + : defaultCover, + summary: parseHTML(json.data.project.annotation?.text).text(), + author: + json.data.project.persons[0].name.firstName + + ' ' + + json.data.project.persons[0].name.lastName, + status: + json.data.project.translationStatus === 'active' + ? NovelStatus.Ongoing + : NovelStatus.Completed, + }; + + let genres = [] + .concat(json.data.project.tags, json.data.project.genres) + .map((item: any) => item?.nameRu || item?.nameEng) + .filter(item => item); + + if (genres.length > 0) { + novel.genres = genres.join(','); + } + + let novelChapters: Chapter.Item[] = []; + json.data.project.subprojects.content.forEach((work: any) => + work.volumes.content.forEach((volume: any) => + volume.chapters.forEach( + (chapter: any) => + chapter?.published && + novelChapters.push({ + name: volume.shortName + ' ' + chapter.title, + url: novelUrl + '/' + volume.url + '/' + chapter.url, + releaseTime: dayjs(chapter.publishDate).format('LLL'), + }), + ), + ), + ); + novel.chapters = novelChapters; + return novel; + }; + + export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'EReaderData', + query: + 'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}', + variables: { + chapterSelector: { + fullUrl: chapterUrl, + }, + url: chapterUrl, + }, + }), + }); + const json: any = await result.json(); + + return parseHTML(json.data.chapter.text.text).html(); + }; + + export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: + 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: { + query: searchTerm, + }, + hostname: domain, + limit: 40, + page: 1, + }, + }), + }); + const json: any = await result.json(); + const novels: Novel.Item[] = []; + json?.data?.projects?.content?.forEach((novel: any) => + novels.push({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: novel.covers[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + }), + ); + return novels; + }; + + export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url, {}); + }; + \ No newline at end of file diff --git a/plugins/russian/snailulitkanoveltl[noveltl].ts b/plugins/russian/snailulitkanoveltl[noveltl].ts new file mode 100644 index 0000000..6b55f8e --- /dev/null +++ b/plugins/russian/snailulitkanoveltl[noveltl].ts @@ -0,0 +1,219 @@ + + import { load as parseHTML } from "cheerio"; + import dayjs from 'dayjs'; + import { fetchFile, fetchApi } from "@libs/fetch"; + import { Novel, Plugin, Chapter } from "@typings/plugin"; + // import { parseMadaraDate } from "@libs/parseMadaraDate"; + // import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; + // import { showToast } from "@libs/showToast"; + import { Filter, FilterInputs } from "@libs/filterInputs"; + import { NovelStatus } from '@libs/novelStatus'; + import { defaultCover } from "@libs/defaultCover"; + + + export const id = "snailulitka.novel"; + export const name = "Snailulitka"; + export const icon = ""; + export const version = "1.0.0"; + export const site = "https://snailulitka.novel.tl"; + export const filters: Filter[] = [{"key":"tags","label":"Тэги","values":[{"label":"Алхимия","value":27},{"label":"Ассасины","value":61},{"label":"Бедный главный герой","value":541},{"label":"Богачи","value":753},{"label":"Виртуальная реальность","value":744},{"label":"Война","value":750},{"label":"Геймеры","value":301},{"label":"Героиня красавица","value":81},{"label":"Герой красавец","value":329},{"label":"Гильдии","value":324},{"label":"Главный герой - гений","value":308},{"label":"Главный герой — знаменитость","value":263},{"label":"Главный герой — мужчина","value":419},{"label":"Дворфы","value":224},{"label":"Драконы","value":218},{"label":"Есть маньхуа-адаптация","value":15},{"label":"Игровая рейтинговая система","value":300},{"label":"Книги навыков","value":654},{"label":"Лекари","value":334},{"label":"Месть","value":588}],"inputType":FilterInputs.Checkbox}]; + exports["protected"] = false; + + const baseUrl = site; + const domain = baseUrl.split('//')[1]; + + export const popularNovels: Plugin.popularNovels = async function ( + page, + { filters } + ) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: + 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: filters?.tags?.length ?? 0 > 0 ? { tags: filters?.tags } : {}, + hostname: domain, + limit: 40, + page, + }, + }), + }); + + const json: any = await result.json(); + const novels: Novel.Item[] = json.data?.projects?.content?.map((novel: any) => ({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: novel.covers[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + })); + + return novels; + }; + + + export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Book', + query: + 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}', + variables: { + hostname: domain, + project: novelUrl, + url: novelUrl, + }, + }), + }); + const json: any = await result.json(); + const novel: Novel.instance = { + url: novelUrl, + name: json.data.project.title, + cover: json.data.project.covers[0]?.url + ? baseUrl + json.data.project.covers[0].url + : defaultCover, + summary: parseHTML(json.data.project.annotation?.text).text(), + author: + json.data.project.persons[0].name.firstName + + ' ' + + json.data.project.persons[0].name.lastName, + status: + json.data.project.translationStatus === 'active' + ? NovelStatus.Ongoing + : NovelStatus.Completed, + }; + + let genres = [] + .concat(json.data.project.tags, json.data.project.genres) + .map((item: any) => item?.nameRu || item?.nameEng) + .filter(item => item); + + if (genres.length > 0) { + novel.genres = genres.join(','); + } + + let novelChapters: Chapter.Item[] = []; + json.data.project.subprojects.content.forEach((work: any) => + work.volumes.content.forEach((volume: any) => + volume.chapters.forEach( + (chapter: any) => + chapter?.published && + novelChapters.push({ + name: volume.shortName + ' ' + chapter.title, + url: novelUrl + '/' + volume.url + '/' + chapter.url, + releaseTime: dayjs(chapter.publishDate).format('LLL'), + }), + ), + ), + ); + novel.chapters = novelChapters; + return novel; + }; + + export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'EReaderData', + query: + 'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}', + variables: { + chapterSelector: { + fullUrl: chapterUrl, + }, + url: chapterUrl, + }, + }), + }); + const json: any = await result.json(); + + return parseHTML(json.data.chapter.text.text).html(); + }; + + export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: + 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: { + query: searchTerm, + }, + hostname: domain, + limit: 40, + page: 1, + }, + }), + }); + const json: any = await result.json(); + const novels: Novel.Item[] = []; + json?.data?.projects?.content?.forEach((novel: any) => + novels.push({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: novel.covers[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + }), + ); + return novels; + }; + + export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url, {}); + }; + \ No newline at end of file diff --git a/plugins/russian/tsundokunoveltl[noveltl].ts b/plugins/russian/tsundokunoveltl[noveltl].ts new file mode 100644 index 0000000..8ad07ad --- /dev/null +++ b/plugins/russian/tsundokunoveltl[noveltl].ts @@ -0,0 +1,219 @@ + + import { load as parseHTML } from "cheerio"; + import dayjs from 'dayjs'; + import { fetchFile, fetchApi } from "@libs/fetch"; + import { Novel, Plugin, Chapter } from "@typings/plugin"; + // import { parseMadaraDate } from "@libs/parseMadaraDate"; + // import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; + // import { showToast } from "@libs/showToast"; + import { Filter, FilterInputs } from "@libs/filterInputs"; + import { NovelStatus } from '@libs/novelStatus'; + import { defaultCover } from "@libs/defaultCover"; + + + export const id = "tsundoku.novel"; + export const name = "TsundokuTrans"; + export const icon = ""; + export const version = "1.0.0"; + export const site = "https://tsundoku.novel.tl"; + export const filters: Filter[] = [{"key":"tags","label":"Тэги","values":[{"label":"Боги","value":318},{"label":"Боевая академия","value":75},{"label":"Вампиры","value":742},{"label":"Влияние прошлого","value":517},{"label":"Война","value":750},{"label":"Главная героиня — женщина","value":277},{"label":"Главный герой — мужчина","value":419},{"label":"Демоны","value":193},{"label":"Драконы","value":218},{"label":"Королевская особа","value":596},{"label":"Коррупция","value":157},{"label":"Магия","value":412},{"label":"Мастер подземелий","value":222},{"label":"Мечи и магия","value":697},{"label":"Мечники","value":698},{"label":"Монстры","value":454},{"label":"Недооцененный главный герой","value":734},{"label":"От слабого к сильному","value":752},{"label":"R-15","value":570},{"label":"R-18","value":571}],"inputType":FilterInputs.Checkbox}]; + exports["protected"] = false; + + const baseUrl = site; + const domain = baseUrl.split('//')[1]; + + export const popularNovels: Plugin.popularNovels = async function ( + page, + { filters } + ) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: + 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: filters?.tags?.length ?? 0 > 0 ? { tags: filters?.tags } : {}, + hostname: domain, + limit: 40, + page, + }, + }), + }); + + const json: any = await result.json(); + const novels: Novel.Item[] = json.data?.projects?.content?.map((novel: any) => ({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: novel.covers[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + })); + + return novels; + }; + + + export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Book', + query: + 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}', + variables: { + hostname: domain, + project: novelUrl, + url: novelUrl, + }, + }), + }); + const json: any = await result.json(); + const novel: Novel.instance = { + url: novelUrl, + name: json.data.project.title, + cover: json.data.project.covers[0]?.url + ? baseUrl + json.data.project.covers[0].url + : defaultCover, + summary: parseHTML(json.data.project.annotation?.text).text(), + author: + json.data.project.persons[0].name.firstName + + ' ' + + json.data.project.persons[0].name.lastName, + status: + json.data.project.translationStatus === 'active' + ? NovelStatus.Ongoing + : NovelStatus.Completed, + }; + + let genres = [] + .concat(json.data.project.tags, json.data.project.genres) + .map((item: any) => item?.nameRu || item?.nameEng) + .filter(item => item); + + if (genres.length > 0) { + novel.genres = genres.join(','); + } + + let novelChapters: Chapter.Item[] = []; + json.data.project.subprojects.content.forEach((work: any) => + work.volumes.content.forEach((volume: any) => + volume.chapters.forEach( + (chapter: any) => + chapter?.published && + novelChapters.push({ + name: volume.shortName + ' ' + chapter.title, + url: novelUrl + '/' + volume.url + '/' + chapter.url, + releaseTime: dayjs(chapter.publishDate).format('LLL'), + }), + ), + ), + ); + novel.chapters = novelChapters; + return novel; + }; + + export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'EReaderData', + query: + 'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}', + variables: { + chapterSelector: { + fullUrl: chapterUrl, + }, + url: chapterUrl, + }, + }), + }); + const json: any = await result.json(); + + return parseHTML(json.data.chapter.text.text).html(); + }; + + export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: + 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: { + query: searchTerm, + }, + hostname: domain, + limit: 40, + page: 1, + }, + }), + }); + const json: any = await result.json(); + const novels: Novel.Item[] = []; + json?.data?.projects?.content?.forEach((novel: any) => + novels.push({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: novel.covers[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + }), + ); + return novels; + }; + + export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url, {}); + }; + \ No newline at end of file diff --git a/plugins/russian/ukrnoveltl[noveltl].ts b/plugins/russian/ukrnoveltl[noveltl].ts new file mode 100644 index 0000000..e79f873 --- /dev/null +++ b/plugins/russian/ukrnoveltl[noveltl].ts @@ -0,0 +1,219 @@ + + import { load as parseHTML } from "cheerio"; + import dayjs from 'dayjs'; + import { fetchFile, fetchApi } from "@libs/fetch"; + import { Novel, Plugin, Chapter } from "@typings/plugin"; + // import { parseMadaraDate } from "@libs/parseMadaraDate"; + // import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; + // import { showToast } from "@libs/showToast"; + import { Filter, FilterInputs } from "@libs/filterInputs"; + import { NovelStatus } from '@libs/novelStatus'; + import { defaultCover } from "@libs/defaultCover"; + + + export const id = "ukr.novel"; + export const name = "Переклади українською мовою"; + export const icon = ""; + export const version = "1.0.0"; + export const site = "https://ukr.novel.tl"; + export const filters: Filter[] = [{"key":"tags","label":"Тэги","values":[{"label":"Альтернативная реальность","value":30},{"label":"Апатичный главный герой","value":46},{"label":"Асоциальный главный герой","value":42},{"label":"Владелец уникального оружия","value":736},{"label":"Гарем рабов","value":656},{"label":"Герои","value":341},{"label":"Героиня красавица","value":81},{"label":"Герой использует щит","value":636},{"label":"Герой-трудяга","value":330},{"label":"Главный герой — мужчина","value":419},{"label":"Глухой к любви герой","value":194},{"label":"Демоны","value":193},{"label":"Друзья детства","value":128},{"label":"Есть аниме-адаптация","value":10},{"label":"Есть манга-адаптация","value":14},{"label":"Есть CD дорама-адаптация","value":12},{"label":"Зверолюди","value":39},{"label":"Магия","value":412},{"label":"Магия призыва","value":694},{"label":"От слабого к сильному","value":752}],"inputType":FilterInputs.Checkbox}]; + exports["protected"] = false; + + const baseUrl = site; + const domain = baseUrl.split('//')[1]; + + export const popularNovels: Plugin.popularNovels = async function ( + page, + { filters } + ) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: + 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: filters?.tags?.length ?? 0 > 0 ? { tags: filters?.tags } : {}, + hostname: domain, + limit: 40, + page, + }, + }), + }); + + const json: any = await result.json(); + const novels: Novel.Item[] = json.data?.projects?.content?.map((novel: any) => ({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: novel.covers[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + })); + + return novels; + }; + + + export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Book', + query: + 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}', + variables: { + hostname: domain, + project: novelUrl, + url: novelUrl, + }, + }), + }); + const json: any = await result.json(); + const novel: Novel.instance = { + url: novelUrl, + name: json.data.project.title, + cover: json.data.project.covers[0]?.url + ? baseUrl + json.data.project.covers[0].url + : defaultCover, + summary: parseHTML(json.data.project.annotation?.text).text(), + author: + json.data.project.persons[0].name.firstName + + ' ' + + json.data.project.persons[0].name.lastName, + status: + json.data.project.translationStatus === 'active' + ? NovelStatus.Ongoing + : NovelStatus.Completed, + }; + + let genres = [] + .concat(json.data.project.tags, json.data.project.genres) + .map((item: any) => item?.nameRu || item?.nameEng) + .filter(item => item); + + if (genres.length > 0) { + novel.genres = genres.join(','); + } + + let novelChapters: Chapter.Item[] = []; + json.data.project.subprojects.content.forEach((work: any) => + work.volumes.content.forEach((volume: any) => + volume.chapters.forEach( + (chapter: any) => + chapter?.published && + novelChapters.push({ + name: volume.shortName + ' ' + chapter.title, + url: novelUrl + '/' + volume.url + '/' + chapter.url, + releaseTime: dayjs(chapter.publishDate).format('LLL'), + }), + ), + ), + ); + novel.chapters = novelChapters; + return novel; + }; + + export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'EReaderData', + query: + 'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}', + variables: { + chapterSelector: { + fullUrl: chapterUrl, + }, + url: chapterUrl, + }, + }), + }); + const json: any = await result.json(); + + return parseHTML(json.data.chapter.text.text).html(); + }; + + export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: + 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: { + query: searchTerm, + }, + hostname: domain, + limit: 40, + page: 1, + }, + }), + }); + const json: any = await result.json(); + const novels: Novel.Item[] = []; + json?.data?.projects?.content?.forEach((novel: any) => + novels.push({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: novel.covers[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + }), + ); + return novels; + }; + + export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url, {}); + }; + \ No newline at end of file diff --git a/plugins/spanish/hasutl.js b/plugins/spanish/hasutl.js deleted file mode 100644 index 0cf9d76..0000000 --- a/plugins/spanish/hasutl.js +++ /dev/null @@ -1,139 +0,0 @@ -const cheerio = require('cheerio'); -const fetchApi = require('@libs/fetchApi'); -const fetchFile = require('@libs/fetchFile'); -const languages = require('@libs/languages'); - -const baseUrl = 'https://hasutl.wordpress.com/'; - -async function popularNovels (page) { - let url = baseUrl + 'light-novels-activas/'; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('div.wp-block-columns').each(function () { - const novelName = loadedCheerio(this).find('.wp-block-button').text(); - const novelCover = loadedCheerio(this).find('img').attr('src'); - - let novelUrl = loadedCheerio(this) - .find('.wp-block-button > a') - .attr('href'); - - const novel = { - name: novelName, - cover: novelCover, - url: novelUrl, - }; - - novels.push(novel); - }); - - return novels; -}; - -async function parseNovelAndChapters (novelUrl) { - const url = novelUrl - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novel = {}; - - novel.url = url; - - novel.url = novelUrl; - - novel.name = loadedCheerio('.post-header').text(); - - novel.cover = loadedCheerio('.featured-media > img').attr('src'); - - let novelSummary = loadedCheerio('.post-content').find('p').html(); - novel.summary = novelSummary; - - let novelChapters = []; - - loadedCheerio('.wp-block-media-text__content') - .find('a') - .each(function () { - const chapterName = loadedCheerio(this).text().trim(); - - const releaseDate = null; - - let chapterUrl = loadedCheerio(this).attr('href'); - - const chapter = { name: chapterName, releaseTime: releaseDate, url: chapterUrl }; - - novelChapters.push(chapter); - }); - - novel.chapters = novelChapters; - - return novel; -}; - -async function parseChapter (chapterUrl) { - const url = chapterUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let chapterText = loadedCheerio('.post-content').html(); - - return chapterText; -}; - -async function searchNovels (searchTerm) { - const url = `${baseUrl}?s=${searchTerm}&post_type=wp-manga`; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('.post-container ').each(function () { - const novelName = loadedCheerio(this).find('.post-header').text(); - if ( - !novelName.includes('Cap') && - !novelName.includes('Vol') && - !novelName.includes('Light Novels') - ) { - const novelCover = loadedCheerio(this).find('img').attr('src'); - - let novelUrl = loadedCheerio(this).find('a').attr('href'); - - const novel = { - name: novelName, - cover: novelCover, - url: novelUrl, - }; - - novels.push(novel); - } - }); - - return novels; -}; - -module.exports = { - id: "HasuTL", - name: 'Hasu Translations', - site: baseUrl, - version: '1.0.0', - lang: languages.Spanish, - icon: 'src/es/hasutl/icon.png', - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; diff --git a/plugins/spanish/hasutl.ts b/plugins/spanish/hasutl.ts new file mode 100644 index 0000000..c976ece --- /dev/null +++ b/plugins/spanish/hasutl.ts @@ -0,0 +1,142 @@ +import { load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "HasuTL"; +export const name = "Hasu Translations"; +export const site = "https://hasutl.wordpress.com/"; +export const version = "1.0.0"; +export const icon = "src/es/hasutl/icon.png"; + +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function (page) { + let url = baseUrl + "light-novels-activas/"; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio("div.wp-block-columns").each(function () { + const novelName = loadedCheerio(this).find(".wp-block-button").text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + + let novelUrl = loadedCheerio(this) + .find(".wp-block-button > a") + .attr("href"); + + if (!novelUrl) return; + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + let novel: Novel.instance = { + url, + }; + + novel.url = novelUrl; + + novel.name = loadedCheerio(".post-header").text(); + + novel.cover = loadedCheerio(".featured-media > img").attr("src"); + + let novelSummary = loadedCheerio(".post-content").find("p").html()!; + novel.summary = novelSummary; + + let novelChapters: Chapter.Item[] = []; + + loadedCheerio(".wp-block-media-text__content") + .find("a") + .each(function () { + const chapterName = loadedCheerio(this).text().trim(); + + const releaseDate = null; + + let chapterUrl = loadedCheerio(this).attr("href"); + + if (!chapterUrl) return; + + const chapter = { + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }; + + novelChapters.push(chapter); + }); + + novel.chapters = novelChapters; + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const url = chapterUrl; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + let chapterText = loadedCheerio(".post-content").html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = `${baseUrl}?s=${searchTerm}&post_type=wp-manga`; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".post-container ").each(function () { + const novelName = loadedCheerio(this).find(".post-header").text(); + if ( + !novelName.includes("Cap") && + !novelName.includes("Vol") && + !novelName.includes("Light Novels") + ) { + const novelCover = loadedCheerio(this).find("img").attr("src"); + + let novelUrl = loadedCheerio(this).find("a").attr("href"); + + if (!novelUrl) return; + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + } + }); + + return novels; +}; +export const fetchImage: Plugin.fetchImage = fetchFile; diff --git a/plugins/spanish/novelasligera.js b/plugins/spanish/novelasligera.js deleted file mode 100644 index bb36648..0000000 --- a/plugins/spanish/novelasligera.js +++ /dev/null @@ -1,177 +0,0 @@ -const cheerio = require('cheerio'); -const fetchApi = require('@libs/fetchApi'); -const fetchFile = require('@libs/fetchFile'); -const languages = require('@libs/languages'); - -const baseUrl = 'https://novelasligera.com/'; - -async function popularNovels (page) { - let url = baseUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('.elementor-column').each(function () { - const novelName = loadedCheerio(this) - .find('.widget-image-caption.wp-caption-text') - .text(); - if (novelName) { - const novelCover = loadedCheerio(this).find('a > img').attr('data-lazy-src'); - - let novelUrl = loadedCheerio(this).find('a').attr('href'); - - const novel = { - name: novelName, - cover: novelCover, - url: novelUrl, - }; - - novels.push(novel); - } - }); - - return novels; -}; - -async function parseNovelAndChapters ( novelUrl) { - const url = baseUrl + 'novela/' + novelUrl; - - // console.log(url); - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novel = {}; - - novel.url = url; - - novel.name = loadedCheerio('h1').text(); - - novel.cover = loadedCheerio('.elementor-widget-container') - .find('img') - .attr('src'); - - loadedCheerio('.elementor-row') - .find('p') - .each(function () { - if (loadedCheerio(this).text().includes('Autor:')) { - novel.author = loadedCheerio(this).text().replace('Autor:', '').trim(); - } - if (loadedCheerio(this).text().includes('Estado:')) { - novel.status = loadedCheerio(this) - .text() - .replace('Estado: ', '') - .trim(); - } - - if (loadedCheerio(this).text().includes('Género:')) { - loadedCheerio(this).find('span').remove(); - novel.genres = loadedCheerio(this).text().replace(/,\s/g, ','); - } - }); - - novel.artist = null; - - novel.summary = loadedCheerio( - '.elementor-text-editor.elementor-clearfix', - ).text(); - - let novelChapters = []; - - loadedCheerio('.elementor-accordion-item').remove(); - - loadedCheerio('.elementor-tab-content') - .find('li') - .each(function () { - const chapterName = loadedCheerio(this).text(); - const releaseDate = null; - const chapterUrl = loadedCheerio(this) - .find('a') - .attr('href') - - const chapter = { name: chapterName, releaseTime: releaseDate, url: chapterUrl }; - - novelChapters.push(chapter); - }); - - novel.chapters = novelChapters; - - return novel; -}; - -async function parseChapter (chapterUrl) { - const url = chapterUrl; - // console.log(url); - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - loadedCheerio('.osny-nightmode.osny-nightmode--left').remove(); - loadedCheerio('.code-block.code-block-1').remove(); - loadedCheerio('.adsb30').remove(); - loadedCheerio('.saboxplugin-wrap').remove(); - loadedCheerio('.wp-post-navigation').remove(); - - let chapterText = loadedCheerio('.entry-content').html(); - return chapterText; -}; - -async function searchNovels (searchTerm) { - const url = baseUrl + '?s=' + searchTerm + '&post_type=wp-manga'; - // console.log(url); - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('.inside-article').each(function () { - const novelCover = loadedCheerio(this).find('img').attr('src'); - let novelUrl = loadedCheerio(this).find('a').attr('href'); - - let novelName; - - if (novelUrl) { - novelName = novelUrl.replace(/-/g, ' ').replace(/^./, function (x) { - return x.toUpperCase(); - }); - } - - novelUrl += '/'; - - const novel = { - name: novelName, - cover: novelCover, - url: novelUrl, - }; - - novels.push(novel); - }); - - novels = [{ ...novels[1] }]; - - return novels; -}; - -module.exports = { - id: "ligera.com", - name: "Novelas Ligera", - site: baseUrl, - version: '1.0.0', - lang: languages.Spanish, - icon: 'src/es/novelasligera/icon.png', - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; diff --git a/plugins/spanish/novelasligera.ts b/plugins/spanish/novelasligera.ts new file mode 100644 index 0000000..03b5a0c --- /dev/null +++ b/plugins/spanish/novelasligera.ts @@ -0,0 +1,177 @@ +import { load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "ligera.com"; +export const name = "Novelas Ligera"; +export const site = "https://novelasligera.com/"; +export const version = "1.0.0"; +export const icon = "src/es/novelasligera/icon.png"; + +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function (page) { + let url = baseUrl; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".elementor-column").each(function () { + const novelName = loadedCheerio(this) + .find(".widget-image-caption.wp-caption-text") + .text(); + if (novelName) { + const novelCover = loadedCheerio(this) + .find("a > img") + .attr("data-lazy-src"); + + let novelUrl = loadedCheerio(this).find("a").attr("href"); + if (!novelUrl) return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + } + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = baseUrl + "novela/" + novelUrl; + + // console.log(url); + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + let novel: Novel.instance = { url }; + + novel.name = loadedCheerio("h1").text(); + + novel.cover = loadedCheerio(".elementor-widget-container") + .find("img") + .attr("src"); + + loadedCheerio(".elementor-row") + .find("p") + .each(function () { + if (loadedCheerio(this).text().includes("Autor:")) { + novel.author = loadedCheerio(this) + .text() + .replace("Autor:", "") + .trim(); + } + if (loadedCheerio(this).text().includes("Estado:")) { + novel.status = loadedCheerio(this) + .text() + .replace("Estado: ", "") + .trim(); + } + + if (loadedCheerio(this).text().includes("Género:")) { + loadedCheerio(this).find("span").remove(); + novel.genres = loadedCheerio(this) + .text() + .replace(/,\s/g, ","); + } + }); + + novel.summary = loadedCheerio( + ".elementor-text-editor.elementor-clearfix" + ).text(); + + let novelChapters: Chapter.Item[] = []; + + loadedCheerio(".elementor-accordion-item").remove(); + + loadedCheerio(".elementor-tab-content") + .find("li") + .each(function () { + const chapterName = loadedCheerio(this).text(); + const releaseDate = null; + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + if (!chapterUrl) return; + const chapter = { + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }; + + novelChapters.push(chapter); + }); + + novel.chapters = novelChapters; + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const url = chapterUrl; + // console.log(url); + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + loadedCheerio(".osny-nightmode.osny-nightmode--left").remove(); + loadedCheerio(".code-block.code-block-1").remove(); + loadedCheerio(".adsb30").remove(); + loadedCheerio(".saboxplugin-wrap").remove(); + loadedCheerio(".wp-post-navigation").remove(); + + let chapterText = loadedCheerio(".entry-content").html(); + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + // console.log(url); + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".inside-article").each(function () { + const novelCover = loadedCheerio(this).find("img").attr("src"); + let novelUrl = loadedCheerio(this).find("a").attr("href"); + + let novelName; + + if (novelUrl) { + novelName = novelUrl.replace(/-/g, " ").replace(/^./, function (x) { + return x.toUpperCase(); + }); + } + + novelUrl += "/"; + + if (!novelName || !novelUrl) return; + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + novels = [{ ...novels[1] }]; + + return novels; +}; +export const fetchImage: Plugin.fetchImage = fetchFile; diff --git a/plugins/spanish/novelawuxia.broken.ts b/plugins/spanish/novelawuxia.broken.ts new file mode 100644 index 0000000..3059f99 --- /dev/null +++ b/plugins/spanish/novelawuxia.broken.ts @@ -0,0 +1,214 @@ +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; +import cheerio from "cheerio"; +import { defaultCover } from "@libs/defaultCover"; + +const pluginId = "awuxia.com"; +const baseUrl = "http://www.reinowuxia.com/"; + +function getNovelName(y: string | undefined) { + return y?.replace(/-/g, " ").replace(/(?:^|\s)\S/g, (a) => a.toUpperCase()); +} + +export const popularNovels: Plugin.popularNovels = async function (page) { + let url = baseUrl + "p/todas-las-novelas.html"; + + let headers = { + "User-Agent": + "'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36", + }; + + const result = await fetchApi(url, { + method: "GET", + headers: headers, + }); + const body = await result.text(); + + let loadedCheerio = cheerio.load(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".post-body.entry-content") + .find("a") + .each(function () { + let novelName = loadedCheerio(this) + .attr("href") + ?.split("/") + .pop() + ?.replace(".html", ""); + novelName = getNovelName(novelName); + const novelCover = loadedCheerio(this).find("img").attr("src"); + + let novelUrl = loadedCheerio(this).attr("href"); + + if (!novelName || !novelUrl) return; + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = cheerio.load(body); + + let novel: Novel.instance = { url }; + + novel.name = loadedCheerio("h1.post-title").text().trim(); + + novel.cover = loadedCheerio("div.separator").find("a").attr("href"); + + novel.status = ""; + + loadedCheerio("div > b").each(function () { + const detailName = loadedCheerio(this).text(); + let detail = loadedCheerio(this)[0].nextSibling; + + if (detailName && detail) { + detail = detail.nodeValue; + + if (detailName.includes("Autor")) { + novel.author = detail.replace("Autor:", ""); + } + + if (detailName.includes("Estatus")) { + novel.status = detail.replace("Estatus: ", ""); + } + if (detailName.includes("Géneros:")) { + novel.genres = detail + .replace("Géneros: ", "") + .replace(/,\s/g, ","); + } + } + }); + + let novelChapters: Chapter.Item[] = []; + + loadedCheerio("div").each(function () { + const detailName = loadedCheerio(this).text(); + if (detailName.includes("Sinopsis")) { + novel.summary = + loadedCheerio(this).next().text() !== "" + ? loadedCheerio(this) + .next() + .text() + .replace("Sinopsis", "") + .trim() + : loadedCheerio(this) + .next() + .next() + .text() + .replace("Sinopsis", "") + .trim(); + } + + if (detailName.includes("Lista de Capítulos")) { + loadedCheerio(this) + .find("a") + .each(function (res) { + const chapterName = loadedCheerio(this).text(); + let chapterUrl = loadedCheerio(this).attr("href"); + const releaseDate = null; + + if ( + chapterName && + chapterUrl && + !novelChapters.some( + (chap) => chap.name === chapterName + ) + ) { + const chapter = { + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }; + + novelChapters.push(chapter); + } + }); + } + }); + + novel.chapters = novelChapters; + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const url = chapterUrl; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = cheerio.load(body); + + let chapterText = loadedCheerio(".post-body.entry-content").html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const url = `${baseUrl}search?q=${searchTerm}`; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = cheerio.load(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".date-outer").each(function () { + let novelName = loadedCheerio(this) + .find("a") + .attr("href") + ?.split("/") + .pop() + ?.replace(/-capitulo(.*?).html/, ""); + + const novelUrl = novelName + ".html/"; + + novelName = getNovelName(novelName); + + const exists = novels.some((novel) => novel.name === novelName); + + if (!exists) { + const novelCover = defaultCover; + + if (!novelUrl || !novelName) return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + } + }); + + return novels; +}; + +module.exports = { + id: pluginId, + name: "ReinoWuxia", + site: baseUrl, + version: "1.0.0", + icon: "src/es/novelawuxia/icon.png", + popularNovels, + parseNovelAndChapters, + parseChapter, + searchNovels, + fetchImage: fetchFile, +}; diff --git a/plugins/spanish/novelawuxia.js b/plugins/spanish/novelawuxia.js deleted file mode 100644 index 54b4c79..0000000 --- a/plugins/spanish/novelawuxia.js +++ /dev/null @@ -1,205 +0,0 @@ -const fetchApi = require('@libs/fetchApi'); -const languages = require('@libs/languages'); -const fetchFile = require('@libs/fetchFile'); -const cheerio = require('cheerio'); - -const pluginId = "awuxia.com" -const baseUrl = 'http://www.reinowuxia.com/'; - -function getNovelName(y) { - return y.replace(/-/g, ' ').replace(/(?:^|\s)\S/g, a => a.toUpperCase()); -} - -async function popularNovels (page) { - let url = baseUrl + 'p/todas-las-novelas.html'; - - let headers ={ - 'User-Agent': - "'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36", - }; - - const result = await fetchApi(url, { - method: 'GET', - headers: headers, - }); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('.post-body.entry-content') - .find('a') - .each(function () { - let novelName = loadedCheerio(this) - .attr('href') - .split('/') - .pop() - .replace('.html', ''); - novelName = getNovelName(novelName); - const novelCover = loadedCheerio(this).find('img').attr('src'); - - let novelUrl = loadedCheerio(this).attr('href'); - - const novel = { - name: novelName, - cover: novelCover, - url: novelUrl, - }; - - novels.push(novel); - }); - - return novels; -}; - -async function parseNovelAndChapters (novelUrl) { - const url = novelUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novel = {}; - - novel.url = url; - - novel.name = loadedCheerio('h1.post-title').text().trim(); - - novel.cover = loadedCheerio('div.separator').find('a').attr('href'); - - novel.artist = ''; - novel.status = ''; - - loadedCheerio('div > b').each(function () { - const detailName = loadedCheerio(this).text(); - let detail = loadedCheerio(this)[0].nextSibling; - - if (detailName && detail) { - detail = detail.nodeValue; - - if (detailName.includes('Autor')) { - novel.author = detail.replace('Autor:', ''); - } - - if (detailName.includes('Estatus')) { - novel.status = detail.replace('Estatus: ', ''); - } - if (detailName.includes('Géneros:')) { - novel.genres = detail.replace('Géneros: ', '').replace(/,\s/g, ','); - } - } - }); - - let novelChapters = []; - - loadedCheerio('div').each(function () { - const detailName = loadedCheerio(this).text(); - if (detailName.includes('Sinopsis')) { - novel.summary = - loadedCheerio(this).next().text() !== '' - ? loadedCheerio(this).next().text().replace('Sinopsis', '').trim() - : loadedCheerio(this) - .next() - .next() - .text() - .replace('Sinopsis', '') - .trim(); - } - - if (detailName.includes('Lista de Capítulos')) { - loadedCheerio(this) - .find('a') - .each(function (res) { - const chapterName = loadedCheerio(this).text(); - let chapterUrl = loadedCheerio(this).attr('href'); - const releaseDate = null; - - if ( - chapterName && - chapterUrl && - !novelChapters.some(chap => chap.name === chapterName) - ) { - const chapter = { - name: chapterName, - releaseTime: releaseDate, - url: chapterUrl, - }; - - novelChapters.push(chapter); - } - }); - } - }); - - novel.chapters = novelChapters; - - return novel; -}; - -async function parseChapter (chapterUrl) { - const url = chapterUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let chapterText = loadedCheerio('.post-body.entry-content').html(); - - return chapterText; -}; - -const searchNovels = async searchTerm => { - const url = `${baseUrl}search?q=${searchTerm}`; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('.date-outer').each(function () { - let novelName = loadedCheerio(this) - .find('a') - .attr('href') - .split('/') - .pop() - .replace(/-capitulo(.*?).html/, ''); - - const novelUrl = novelName + '.html/'; - - novelName = getNovelName(novelName); - - const exists = novels.some(novel => novel.novelName === novelName); - - if (!exists) { - const novelCover = null; - const novel = { - name: novelName, - cover: novelCover, - url: novelUrl, - }; - - novels.push(novel); - } - }); - - return novels; -}; - -module.exports = { - id: pluginId, - name: "ReinoWuxia", - site: baseUrl, - version: '1.0.0', - icon: 'src/es/novelawuxia/icon.png', - lang: languages.Spanish, - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; \ No newline at end of file diff --git a/plugins/spanish/oasistranslations.broken.ts b/plugins/spanish/oasistranslations.broken.ts new file mode 100644 index 0000000..2ad57ec --- /dev/null +++ b/plugins/spanish/oasistranslations.broken.ts @@ -0,0 +1,162 @@ +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; +import cheerio from "cheerio"; + +export const id = "oasisTL.wp"; +export const name = "Oasis Translations"; +export const site = "https://oasistranslations.wordpress.com/"; +export const version = "1.0.0"; +export const icon = "src/es/oasistranslations/icon.png"; + +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function (page) { + let url = baseUrl; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = cheerio.load(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".menu-item-1819") + .find(".sub-menu > li") + .each(function () { + const novelName = loadedCheerio(this).text(); + if (!novelName.match(/Activas|Finalizadas|Dropeadas/)) { + const novelCover = loadedCheerio(this).find("img").attr("src"); + + let novelUrl = loadedCheerio(this).find("a").attr("href"); + + if (!novelUrl) return; + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + } + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = cheerio.load(body); + + let novel: Novel.instance = { url }; + + novel.name = loadedCheerio("h1.entry-title") + .text() + .replace(/[\t\n]/g, "") + .trim(); + + novel.cover = loadedCheerio('img[loading="lazy"]').attr("src"); + + loadedCheerio(".entry-content > p").each(function (res) { + if (loadedCheerio(this).text().includes("Autor")) { + let details = loadedCheerio(this).html()!; + details = details.match(/<\/strong>(.|\n)*?
/g); + details = details.map((detail) => + detail.replace(/|<\/strong>|
|:\s/g, "") + ); + + novel.genres = ""; + + novel.author = details[2]; + novel.genres = details[4].replace(/\s| /g, ""); + } + }); + + // let novelSummary = $(this).next().html(); + novel.summary = ""; + + let novelChapters: Chapter.Item[] = []; + + // if ($(".entry-content").find("li").length) { + loadedCheerio(".entry-content") + .find("a") + .each(function () { + let chapterUrl = loadedCheerio(this).attr("href"); + + if (chapterUrl && chapterUrl.includes(baseUrl)) { + const chapterName = loadedCheerio(this).text(); + const releaseDate = null; + + const chapter = { + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }; + + novelChapters.push(chapter); + } + }); + + novel.chapters = novelChapters; + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const url = chapterUrl; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = cheerio.load(body); + + loadedCheerio("div#jp-post-flair").remove(); + + let chapterText = loadedCheerio(".entry-content").html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + searchTerm = searchTerm.toLowerCase(); + + let url = baseUrl; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = cheerio.load(body); + + let novels: Novel.Item[] = []; + loadedCheerio(".menu-item-1819") + .find(".sub-menu > li") + .each(function () { + const novelName = loadedCheerio(this).text(); + if (!novelName.match(/Activas|Finalizadas|Dropeadas/)) { + const novelCover = loadedCheerio(this).find("img").attr("src"); + + let novelUrl = loadedCheerio(this).find("a").attr("href"); + + if (!novelUrl) return; + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + } + }); + + novels = novels.filter((novel) => + novel.name.toLowerCase().includes(searchTerm) + ); + + return novels; +}; diff --git a/plugins/spanish/oasistranslations.js b/plugins/spanish/oasistranslations.js deleted file mode 100644 index aea7afb..0000000 --- a/plugins/spanish/oasistranslations.js +++ /dev/null @@ -1,167 +0,0 @@ -const fetchApi = require('@libs/fetchApi'); -const languages = require('@libs/languages'); -const fetchFile = require('@libs/fetchFile'); -const cheerio = require('cheerio'); - -const pluginId = 'oasisTL.wp'; -const sourceName = 'Oasis Translations'; -const baseUrl = 'https://oasistranslations.wordpress.com/'; - -async function popularNovels (page) { - let url = baseUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('.menu-item-1819') - .find('.sub-menu > li') - .each(function () { - const novelName = loadedCheerio(this).text(); - if (!novelName.match(/Activas|Finalizadas|Dropeadas/)) { - const novelCover = loadedCheerio(this).find('img').attr('src'); - - let novelUrl = loadedCheerio(this).find('a').attr('href'); - - const novel = { - name: novelName, - cover: novelCover, - url: novelUrl, - }; - - novels.push(novel); - } - }); - - return novels; -}; - -async function parseNovelAndChapters (novelUrl) { - const url = novelUrl - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novel = {}; - - novel.url = url; - - novel.name = loadedCheerio('h1.entry-title') - .text() - .replace(/[\t\n]/g, '') - .trim(); - - novel.cover = loadedCheerio('img[loading="lazy"]').attr('src'); - - loadedCheerio('.entry-content > p').each(function (res) { - if (loadedCheerio(this).text().includes('Autor')) { - let details = loadedCheerio(this).html(); - details = details.match(/<\/strong>(.|\n)*?
/g); - details = details.map(detail => - detail.replace(/|<\/strong>|
|:\s/g, ''), - ); - - novel.genres = ''; - - novel.author = details[2]; - novel.genres = details[4].replace(/\s| /g, ''); - novel.artist = details[3]; - } - }); - - // let novelSummary = $(this).next().html(); - novel.summary = ''; - - let novelChapters = []; - - // if ($(".entry-content").find("li").length) { - loadedCheerio('.entry-content') - .find('a') - .each(function () { - let chapterUrl = loadedCheerio(this).attr('href'); - - if (chapterUrl && chapterUrl.includes(baseUrl)) { - const chapterName = loadedCheerio(this).text(); - const releaseDate = null; - - const chapter = { name: chapterName, releaseTime: releaseDate, url: chapterUrl }; - - novelChapters.push(chapter); - } - }); - - novel.chapters = novelChapters; - - return novel; -}; - -async function parseChapter (chapterUrl) { - const url = chapterUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - loadedCheerio('div#jp-post-flair').remove(); - - let chapterText = loadedCheerio('.entry-content').html(); - - return chapterText; -}; - -async function searchNovels (searchTerm) { - searchTerm = searchTerm.toLowerCase(); - - let url = baseUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novels = []; - loadedCheerio('.menu-item-1819') - .find('.sub-menu > li') - .each(function () { - const novelName = loadedCheerio(this).text(); - if (!novelName.match(/Activas|Finalizadas|Dropeadas/)) { - const novelCover = loadedCheerio(this).find('img').attr('src'); - - let novelUrl = loadedCheerio(this).find('a').attr('href'); - - const novel = { - name: novelName, - cover: novelCover, - url: novelUrl, - }; - - novels.push(novel); - } - }); - - novels = novels.filter(novel => - novel.name.toLowerCase().includes(searchTerm), - ); - - return novels; -}; - -module.exports = { - id: pluginId, - name: sourceName, - site: baseUrl, - version: '1.0.0', - icon: 'src/es/oasistranslations/icon.png', - lang: languages.Spanish, - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; \ No newline at end of file diff --git a/plugins/spanish/skynovels.broken.ts b/plugins/spanish/skynovels.broken.ts new file mode 100644 index 0000000..27bde39 --- /dev/null +++ b/plugins/spanish/skynovels.broken.ts @@ -0,0 +1,129 @@ +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "skynovels.net"; +export const name = "SkyNovels"; +export const site = "https://www.skynovels.net/"; +export const version = "1.0.0"; +export const icon = "src/es/skynovels/icon.png"; + +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function (page) { + const url = "https://api.skynovels.net/api/novels?&q"; + + const result = await fetchApi(url); + const body = await result.json(); + + const novels: Novel.Item[] = []; + + body.novels.map((res) => { + const novelName = res.nvl_title; + const novelCover = + "https://api.skynovels.net/api/get-image/" + + res.image + + "/novels/false"; + const novelUrl = + baseUrl + "novelas/" + res.id + "/" + res.nvl_name + "/"; + + const novel = { name: novelName, url: novelUrl, cover: novelCover }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novUrl) { + let novelId = novUrl.substring().split("/")[4]; + const url = + "https://api.skynovels.net/api/novel/" + novelId + "/reading?&q"; + + const result = await fetchApi(url); + const body = await result.json(); + + const item = body.novel[0]; + + let novel: Novel.instance = { url: novUrl }; + + novel.name = item.nvl_title; + + novel.cover = + "https://api.skynovels.net/api/get-image/" + + item.image + + "/novels/false"; + + let genres: string[] = []; + item.genres.map((genre) => genres.push(genre.genre_name)); + novel.genres = genres.join(","); + novel.author = item.nvl_writer; + novel.summary = item.nvl_content; + novel.status = item.nvl_status; + + let novelChapters: Chapter.Item[] = []; + + item.volumes.map((volume) => { + volume.chapters.map((chapter) => { + const chapterName = chapter.chp_index_title; + const releaseDate = new Date(chapter.createdAt).toDateString(); + const chapterUrl = novUrl + chapter.id + "/" + chapter.chp_name; + + const chap = { + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }; + + novelChapters.push(chap); + }); + }); + + novel.chapters = novelChapters; + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapUrl) { + let chapterId = chapUrl.split("/")[6]; + const url = `https://api.skynovels.net/api/novel-chapter/${chapterId}`; + + const result = await fetchApi(url); + const body = await result.json(); + + const item = body.chapter[0]; + + let chapterText = item.chp_content; + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + searchTerm = searchTerm.toLowerCase(); + const url = "https://api.skynovels.net/api/novels?&q"; + + const result = await fetchApi(url); + const body = await result.json(); + + let results = body.novels.filter((novel) => + novel.nvl_title.toLowerCase().includes(searchTerm) + ); + + const novels: Novel.Item[] = []; + + results.map((res) => { + const novelName = res.nvl_title; + const novelCover = + "https://api.skynovels.net/api/get-image/" + + res.image + + "/novels/false"; + const novelUrl = + baseUrl + "novelas/" + res.id + "/" + res.nvl_name + "/"; + + const novel = { name: novelName, url: novelUrl, cover: novelCover }; + + novels.push(novel); + }); + + return novels; +}; diff --git a/plugins/spanish/skynovels.js b/plugins/spanish/skynovels.js deleted file mode 100644 index b38cc96..0000000 --- a/plugins/spanish/skynovels.js +++ /dev/null @@ -1,126 +0,0 @@ -const fetchApi = require('@libs/fetchApi'); -const languages = require('@libs/languages'); -const fetchFile = require('@libs/fetchFile'); - -const baseUrl = 'https://www.skynovels.net/'; - -async function popularNovels (page) { - const url = 'https://api.skynovels.net/api/novels?&q'; - - const result = await fetchApi(url); - const body = await result.json(); - - const novels = []; - - body.novels.map(res => { - const novelName = res.nvl_title; - const novelCover = - 'https://api.skynovels.net/api/get-image/' + res.image + '/novels/false'; - const novelUrl = baseUrl + 'novelas/' + res.id + '/' + res.nvl_name + '/'; - - const novel = { name: novelName, url: novelUrl, cover: novelCover }; - - novels.push(novel); - }); - - return novels ; -}; - -async function parseNovelAndChapters (novUrl) { - let novelId = novUrl.substring().split('/')[4]; - const url = 'https://api.skynovels.net/api/novel/' + novelId + '/reading?&q'; - - const result = await fetchApi(url); - const body = await result.json(); - - const item = body.novel[0]; - - let novel = {}; - - novel.url = novUrl; - - novel.name = item.nvl_title; - - novel.cover = - 'https://api.skynovels.net/api/get-image/' + item.image + '/novels/false'; - - let genres = []; - item.genres.map(genre => genres.push(genre.genre_name)); - novel.genres = genres.join(','); - novel.author = item.nvl_writer; - novel.summary = item.nvl_content; - novel.status = item.nvl_status; - - let novelChapters = []; - - item.volumes.map(volume => { - volume.chapters.map(chapter => { - const chapterName = chapter.chp_index_title; - const releaseDate = new Date(chapter.createdAt).toDateString(); - const chapterUrl = novUrl + chapter.id + '/' + chapter.chp_name; - - const chap = { name: chapterName, releaseTime: releaseDate, url: chapterUrl }; - - novelChapters.push(chap); - }); - }); - - novel.chapters = novelChapters; - - return novel; -}; - -async function parseChapter (chapUrl) { - let chapterId = chapUrl.split('/')[6]; - const url = `https://api.skynovels.net/api/novel-chapter/${chapterId}`; - - const result = await fetchApi(url); - const body = await result.json(); - - const item = body.chapter[0]; - - let chapterText = item.chp_content; - - return chapterText; -}; - -async function searchNovels (searchTerm) { - searchTerm = searchTerm.toLowerCase(); - const url = 'https://api.skynovels.net/api/novels?&q'; - - const result = await fetchApi(url); - const body = await result.json(); - - let results = body.novels.filter(novel => - novel.nvl_title.toLowerCase().includes(searchTerm), - ); - - const novels = []; - - results.map(res => { - const novelName = res.nvl_title; - const novelCover = - 'https://api.skynovels.net/api/get-image/' + res.image + '/novels/false'; - const novelUrl = baseUrl + 'novelas/' + res.id + '/' + res.nvl_name + '/'; - - const novel = { name: novelName, url: novelUrl, cover: novelCover }; - - novels.push(novel); - }); - - return novels; -}; - -module.exports = { - id: 'skynovels.net', - name: "SkyNovels", - site: baseUrl, - version: '1.0.0', - icon: 'src/es/skynovels/icon.png', - lang: languages.Spanish, - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; \ No newline at end of file diff --git a/plugins/spanish/tunovelaligera.js b/plugins/spanish/tunovelaligera.js deleted file mode 100644 index 1128293..0000000 --- a/plugins/spanish/tunovelaligera.js +++ /dev/null @@ -1,164 +0,0 @@ -const fetchApi = require('@libs/fetchApi'); -const languages = require('@libs/languages'); -const fetchFile = require('@libs/fetchFile'); -const cheerio = require('cheerio'); -const Status = require('@libs/novelStatus'); -const defaultCoverUri = - 'https://github.com/LNReader/lnreader-sources/blob/main/icons/src/coverNotAvailable.jpg?raw=true'; - -const pluginId = "TNL.com"; -const sourceName = 'TuNovelaLigera'; - -const baseUrl = 'https://tunovelaligera.com/'; - -async function popularNovels (page) { - let url = baseUrl + 'novelas/page/' + page + '/?m_orderby=rating'; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('.page-item-detail').each(function () { - const novelName = loadedCheerio(this).find('.h5 > a').text(); - const novelCover = loadedCheerio(this).find('img').attr('src'); - - let novelUrl = loadedCheerio(this) - .find('.h5 > a') - .attr('href') - - const novel = { - name: novelName, - cover: novelCover, - url: novelUrl, - }; - - novels.push(novel); - }); - - return novels; -}; - -async function parseNovelAndChapters (novelUrl) { - const url = novelUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novel = { - url, - }; - - loadedCheerio('.manga-title-badges').remove(); - - novel.name = loadedCheerio('.post-title > h1').text().trim(); - - let novelCover = loadedCheerio('.summary_image > a > img'); - - novel.cover = - novelCover.attr('data-src') || novelCover.attr('src') || defaultCoverUri; - - loadedCheerio('.post-content_item').each(function () { - const detailName = loadedCheerio(this) - .find('.summary-heading > h5') - .text() - .trim(); - const detail = loadedCheerio(this).find('.summary-content').text().trim(); - - switch (detailName) { - case 'Generos': - novel.genres = detail.replace(/, /g, ','); - break; - case 'Autores': - novel.author = detail; - break; - case 'Estado': - novel.status = - detail.includes('OnGoing') || detail.includes('Updating') - ? Status.Ongoing - : Status.Completed; - break; - } - }); - - novel.summary = loadedCheerio('div.summary__content > p').text().trim(); - - let novelChapters = []; - - loadedCheerio('.lcp_catlist li').each(function () { - const chapterName = loadedCheerio(this) - .find('a') - .text() - .replace(/[\t\n]/g, '') - .trim(); - - const releaseDate = loadedCheerio(this).find('span').text().trim(); - - let chapterUrl = loadedCheerio(this).find('a').attr('href'); - - novelChapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); - }); - - novel.chapters = novelChapters.reverse(); - - return novel; -}; - -async function parseChapter (chapterUrl) { - const url = chapterUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let chapterText = loadedCheerio('#hola_siguiente').next().text(); - - return chapterText; -}; - -async function searchNovels (searchTerm) { - const url = `${baseUrl}?s=${searchTerm}&post_type=wp-manga`; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('.c-tabs-item__content').each(function () { - const novelName = loadedCheerio(this).find('.h4 > a').text(); - const novelCover = loadedCheerio(this).find('img').attr('src'); - - let novelUrl = loadedCheerio(this).find('.h4 > a').attr('href'); - - const novel = { - name: novelName, - cover: novelCover, - url: novelUrl, - }; - - novels.push(novel); - }); - - return novels; -}; - -module.exports = { - id: pluginId, - name: sourceName, - site: baseUrl, - version: '1.0.0', - icon: 'src/es/tunovelaligera/icon.png', - lang: languages.Spanish, - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; \ No newline at end of file diff --git a/plugins/spanish/tunovelaligera.ts b/plugins/spanish/tunovelaligera.ts new file mode 100644 index 0000000..6134a7e --- /dev/null +++ b/plugins/spanish/tunovelaligera.ts @@ -0,0 +1,161 @@ +import { fetchApi, fetchFile } from "@libs/fetch"; +import { load as parseHTML } from "cheerio"; +import { NovelStatus } from "@libs/novelStatus"; +import { defaultCover } from "@libs/defaultCover"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "TNL.com"; +export const name = "TuNovelaLigera"; +export const site = "https://tunovelaligera.com/"; +export const version = "1.0.0"; +export const icon = "src/es/tunovelaligera/icon.png"; + +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function (page) { + let url = baseUrl + "novelas/page/" + page + "/?m_orderby=rating"; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".page-item-detail").each(function () { + const novelName = loadedCheerio(this).find(".h5 > a").text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + + let novelUrl = loadedCheerio(this).find(".h5 > a").attr("href"); + if (!novelUrl) return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + let novel: Novel.instance = { + url, + }; + + loadedCheerio(".manga-title-badges").remove(); + + novel.name = loadedCheerio(".post-title > h1").text().trim(); + + let novelCover = loadedCheerio(".summary_image > a > img"); + + novel.cover = + novelCover.attr("data-src") || + novelCover.attr("src") || + defaultCover; + + loadedCheerio(".post-content_item").each(function () { + const detailName = loadedCheerio(this) + .find(".summary-heading > h5") + .text() + .trim(); + const detail = loadedCheerio(this) + .find(".summary-content") + .text() + .trim(); + + switch (detailName) { + case "Generos": + novel.genres = detail.replace(/, /g, ","); + break; + case "Autores": + novel.author = detail; + break; + case "Estado": + novel.status = + detail.includes("OnGoing") || + detail.includes("Updating") + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + novel.summary = loadedCheerio("div.summary__content > p").text().trim(); + + let novelChapters: Chapter.Item[] = []; + + loadedCheerio(".lcp_catlist li").each(function () { + const chapterName = loadedCheerio(this) + .find("a") + .text() + .replace(/[\t\n]/g, "") + .trim(); + + const releaseDate = loadedCheerio(this).find("span").text().trim(); + + let chapterUrl = loadedCheerio(this).find("a").attr("href"); + if (!chapterUrl) return; + novelChapters.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + + novel.chapters = novelChapters.reverse(); + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const url = chapterUrl; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + let chapterText = loadedCheerio("#hola_siguiente").next().text(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = `${baseUrl}?s=${searchTerm}&post_type=wp-manga`; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".c-tabs-item__content").each(function () { + const novelName = loadedCheerio(this).find(".h4 > a").text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + + let novelUrl = loadedCheerio(this).find(".h4 > a").attr("href"); + if (!novelUrl) return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; +export const fetchImage: Plugin.fetchImage = fetchFile; diff --git a/plugins/spanish/yukitls.js b/plugins/spanish/yukitls.js deleted file mode 100644 index 1222025..0000000 --- a/plugins/spanish/yukitls.js +++ /dev/null @@ -1,183 +0,0 @@ -const fetchApi = require('@libs/fetchApi'); -const languages = require('@libs/languages'); -const fetchFile = require('@libs/fetchFile'); -const cheerio = require('cheerio'); - -const pluginId = "yuukitls.com"; -const baseUrl = 'https://yuukitls.com/'; - -async function popularNovels (page) { - let url = baseUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('.quadmenu-navbar-collapse ul li:nth-child(2)') - .find('li') - .each(function () { - const novelName = loadedCheerio(this).text().replace(/[\s\n]+/g,' '); - const novelCover = loadedCheerio(this).find('img').attr('src'); - - let novelUrl = loadedCheerio(this).find('a').attr('href'); - - const novel = { - name: novelName, - cover: novelCover, - url: novelUrl, - }; - - novels.push(novel); - }); - - return novels ; -}; - -async function parseNovelAndChapters (novelUrl) { - const url = novelUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novel = {}; - - novel.url = url; - - novel.name = loadedCheerio('h1.entry-title') - .text() - .replace(/[\t\n]/g, '') - .trim(); - - novel.cover = loadedCheerio('img[loading="lazy"]').attr('src'); - - loadedCheerio('.entry-content') - .find('div') - .each(function () { - if (loadedCheerio(this).text().includes('Escritor:')) { - novel.author = loadedCheerio(this) - .text() - .replace('Escritor: ', '') - .trim(); - } - if (loadedCheerio(this).text().includes('Ilustrador:')) { - novel.artist = loadedCheerio(this) - .text() - .replace('Ilustrador: ', '') - .trim(); - } - - if (loadedCheerio(this).text().includes('Género:')) { - novel.genres = loadedCheerio(this) - .text() - .replace(/Género: |\s/g, ''); - } - - if (loadedCheerio(this).text().includes('Sinopsis:')) { - novel.summary = loadedCheerio(this).next().text(); - } - }); - - let novelChapters = []; - - if (loadedCheerio('.entry-content').find('li').length) { - loadedCheerio('.entry-content') - .find('li') - .each(function () { - let chapterUrl = loadedCheerio(this).find('a').attr('href'); - - if (chapterUrl && chapterUrl.includes(baseUrl)) { - const chapterName = loadedCheerio(this).text(); - const releaseDate = null; - - const chapter = { name: chapterName, releaseTime: releaseDate, url: chapterUrl }; - - novelChapters.push(chapter); - } - }); - } else { - loadedCheerio('.entry-content') - .find('p') - .each(function () { - let chapterUrl = loadedCheerio(this).find('a').attr('href'); - - if (chapterUrl && chapterUrl.includes(baseUrl)) { - const chapterName = loadedCheerio(this).text(); - const releaseDate = null; - - const chapter = { name: chapterName, releaseTime: releaseDate, url: chapterUrl }; - - novelChapters.push(chapter); - } - }); - } - - novel.chapters = novelChapters; - - return novel; -}; - -async function parseChapter (chapterUrl) { - const url = chapterUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - let chapterText = loadedCheerio('.entry-content').html(); - - return chapterText; -}; - -async function searchNovels (searchTerm) { - searchTerm = searchTerm.toLowerCase(); - - let url = baseUrl; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('.menu-item-2869') - .find('.menu-item.menu-item-type-post_type.menu-item-object-post') - .each(function () { - const novelName = loadedCheerio(this).text(); - const novelCover = loadedCheerio(this).find('img').attr('src'); - - let novelUrl = loadedCheerio(this).find('a').attr('href'); - const novel = { - name: novelName, - cover: novelCover, - url: novelUrl, - }; - - novels.push(novel); - }); - - novels = novels.filter(novel => - novel.novelName.toLowerCase().includes(searchTerm), - ); - - return novels; -}; - -module.exports = { - id: pluginId, - name: "Yuuki Tls", - site: baseUrl, - version: '1.0.0', - icon: 'src/es/yuukitls/icon.png', - lang: languages.Spanish, - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; \ No newline at end of file diff --git a/plugins/spanish/yukitls.ts b/plugins/spanish/yukitls.ts new file mode 100644 index 0000000..5277459 --- /dev/null +++ b/plugins/spanish/yukitls.ts @@ -0,0 +1,180 @@ +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; +import { load as parseHTML } from "cheerio"; + +export const id = "yuukitls.com"; +export const name = "Yuuki Tls"; +export const site = "https://yuukitls.com/"; +export const version = "1.0.0"; +export const icon = "src/es/yuukitls/icon.png"; + +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function (page) { + let url = baseUrl; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".quadmenu-navbar-collapse ul li:nth-child(2)") + .find("li") + .each(function () { + const novelName = loadedCheerio(this) + .text() + .replace(/[\s\n]+/g, " "); + const novelCover = loadedCheerio(this).find("img").attr("src"); + + let novelUrl = loadedCheerio(this).find("a").attr("href"); + if (!novelUrl) return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + let novel: Novel.instance = { + url, + }; + + novel.name = loadedCheerio("h1.entry-title") + .text() + .replace(/[\t\n]/g, "") + .trim(); + + novel.cover = loadedCheerio('img[loading="lazy"]').attr("src"); + + loadedCheerio(".entry-content") + .find("div") + .each(function () { + if (loadedCheerio(this).text().includes("Escritor:")) { + novel.author = loadedCheerio(this) + .text() + .replace("Escritor: ", "") + .trim(); + } + if (loadedCheerio(this).text().includes("Género:")) { + novel.genres = loadedCheerio(this) + .text() + .replace(/Género: |\s/g, ""); + } + + if (loadedCheerio(this).text().includes("Sinopsis:")) { + novel.summary = loadedCheerio(this).next().text(); + } + }); + + let novelChapters: Chapter.Item[] = []; + + if (loadedCheerio(".entry-content").find("li").length) { + loadedCheerio(".entry-content") + .find("li") + .each(function () { + let chapterUrl = loadedCheerio(this).find("a").attr("href"); + + if (chapterUrl && chapterUrl.includes(baseUrl)) { + const chapterName = loadedCheerio(this).text(); + const releaseDate = null; + + const chapter = { + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }; + + novelChapters.push(chapter); + } + }); + } else { + loadedCheerio(".entry-content") + .find("p") + .each(function () { + let chapterUrl = loadedCheerio(this).find("a").attr("href"); + + if (chapterUrl && chapterUrl.includes(baseUrl)) { + const chapterName = loadedCheerio(this).text(); + const releaseDate = null; + + const chapter = { + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }; + + novelChapters.push(chapter); + } + }); + } + + novel.chapters = novelChapters; + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const url = chapterUrl; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + let chapterText = loadedCheerio(".entry-content").html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + searchTerm = searchTerm.toLowerCase(); + + let url = baseUrl; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio(".menu-item-2869") + .find(".menu-item.menu-item-type-post_type.menu-item-object-post") + .each(function () { + const novelName = loadedCheerio(this).text(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + + let novelUrl = loadedCheerio(this).find("a").attr("href"); + if (!novelUrl) return; + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + novels = novels.filter((novel) => + novel.name.toLowerCase().includes(searchTerm) + ); + + return novels; +}; +export const fetchImage: Plugin.fetchImage = fetchFile; diff --git a/plugins/turkish/TurkceLightNovels[madara].ts b/plugins/turkish/TurkceLightNovels[madara].ts new file mode 100644 index 0000000..c2e9a00 --- /dev/null +++ b/plugins/turkish/TurkceLightNovels[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "turkcelightnovels"; +export const name = "TurkceLightNovels [madara]"; +export const icon = "multisrc/madara/icons/turkcelightnovels.png"; +export const version = "1.0.0"; +export const site = "https://turkcelightnovels.com/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "light-novel" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (false) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "light-novel" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/turkish/WebNovelOku[madara].ts b/plugins/turkish/WebNovelOku[madara].ts new file mode 100644 index 0000000..d14b8c0 --- /dev/null +++ b/plugins/turkish/WebNovelOku[madara].ts @@ -0,0 +1,205 @@ + +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "webnoveloku"; +export const name = "WebNovelOku [madara]"; +export const icon = "multisrc/madara/icons/webnoveloku.png"; +export const version = "1.0.0"; +export const site = "https://www.webnoveloku.com/"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "novel" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\t\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (false) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "novel" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +}; \ No newline at end of file diff --git a/plugins/turkish/epiknovel.js b/plugins/turkish/epiknovel.js deleted file mode 100644 index c36f3d4..0000000 --- a/plugins/turkish/epiknovel.js +++ /dev/null @@ -1,167 +0,0 @@ -const cheerio = require('cheerio'); -const fetchApi = require('@libs/fetchApi'); -const fetchFile = require('@libs/fetchFile'); -const languages = require('@libs/languages'); - -const pluginId = 'epiknovel.com'; - -const sourceName = 'EpikNovel'; - -const baseUrl = 'https://www.epiknovel.com/'; - -async function popularNovels (page) { - let url = baseUrl + 'seri-listesi?Sayfa=' + page; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('div.col-lg-12.col-md-12').each(function () { - const novelName = loadedCheerio(this).find('h3').text(); - const novelCover = loadedCheerio(this).find('img').attr('data-src'); - const novelUrl = loadedCheerio(this).find('h3 > a').attr('href'); - - const novel = { - name: novelName, - cover: novelCover, - url: novelUrl, - }; - - novels.push(novel); - }); - - // console.log(novels); - - return novels; -}; - -async function parseNovelAndChapters (novelUrl) { - const url = novelUrl; - // console.log(url); - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novel = {url}; - - novel.name = loadedCheerio('h1#tables').text().trim(); - - novel.cover = loadedCheerio('img.manga-cover').attr('src'); - - novel.summary = loadedCheerio( - '#wrapper > div.row > div.col-md-9 > div:nth-child(6) > p:nth-child(3)', - ) - .text() - .trim(); - - novel.status = loadedCheerio( - '#wrapper > div.row > div.col-md-9 > div.row > div.col-md-9 > h4:nth-child(3) > a', - ) - .text() - .trim(); - - novel.author = loadedCheerio('#NovelInfo > p:nth-child(4)') - .text() - .replace(/Publisher:|\s/g, '') - .trim(); - - let novelChapters = []; - - loadedCheerio('table').find('tr').first().remove(); - - loadedCheerio('table') - .find('tr') - .each(function () { - const releaseDate = loadedCheerio(this).find('td:nth-child(3)').text(); - - let chapterName = loadedCheerio(this).find('td:nth-child(1) > a').text(); - - if (loadedCheerio(this).find('td:nth-child(1) > span').length > 0) { - chapterName = '🔒 ' + chapterName; - } - - const chapterUrl = loadedCheerio(this) - .find(' td:nth-child(1) > a') - .attr('href'); - - novelChapters.push({ - name: chapterName, - url: chapterUrl, - releaseTime: releaseDate, - }); - }); - - novel.chapters = novelChapters; - // console.log(novel); - - return novel; -}; - -async function parseChapter (chapterUrl) { - const url = chapterUrl; - - // console.log(url); - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let chapterName, chapterText; - - if (result.url === 'https://www.epiknovel.com/login') { - chapterName = ''; - chapterText = 'Premium Chapter'; - } else { - chapterName = loadedCheerio('#icerik > center > h4 > b').text(); - chapterText = loadedCheerio('div#icerik').html(); - } - - return chapterText; -}; - -async function searchNovels (searchTerm) { - const url = baseUrl + 'seri-listesi?q=' + searchTerm; - - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('div.col-lg-12.col-md-12').each(function () { - const novelName = loadedCheerio(this).find('h3').text(); - const novelCover = loadedCheerio(this).find('img').attr('data-src'); - const novelUrl = loadedCheerio(this).find('h3 > a').attr('href'); - - const novel = { - name: novelName, - cover: novelCover, - url: novelUrl, - }; - - novels.push(novel); - }); - - return novels; -}; - -module.exports = { - id: pluginId, - name: sourceName, - site: baseUrl, - version: '1.0.0', - icon: 'src/tr/epiknovel/icon.png', - lang: languages.Turkish, - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, - fetchImage: fetchFile, -}; \ No newline at end of file diff --git a/plugins/turkish/epiknovel.ts b/plugins/turkish/epiknovel.ts new file mode 100644 index 0000000..93c3589 --- /dev/null +++ b/plugins/turkish/epiknovel.ts @@ -0,0 +1,169 @@ +import { load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "epiknovel.com"; +export const name = "EpikNovel"; +export const site = "https://www.epiknovel.com/"; +export const version = "1.0.0"; +export const icon = "src/tr/epiknovel/icon.png"; + +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function (page) { + let url = baseUrl + "seri-listesi?Sayfa=" + page; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio("div.col-lg-12.col-md-12").each(function () { + const novelName = loadedCheerio(this).find("h3").text(); + const novelCover = loadedCheerio(this).find("img").attr("data-src"); + const novelUrl = loadedCheerio(this).find("h3 > a").attr("href"); + + if (!novelUrl) return; + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + // console.log(novels); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + // console.log(url); + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + let novel: Novel.instance = { url }; + + novel.name = loadedCheerio("h1#tables").text().trim(); + + novel.cover = loadedCheerio("img.manga-cover").attr("src"); + + novel.summary = loadedCheerio( + "#wrapper > div.row > div.col-md-9 > div:nth-child(6) > p:nth-child(3)" + ) + .text() + .trim(); + + novel.status = loadedCheerio( + "#wrapper > div.row > div.col-md-9 > div.row > div.col-md-9 > h4:nth-child(3) > a" + ) + .text() + .trim(); + + novel.author = loadedCheerio("#NovelInfo > p:nth-child(4)") + .text() + .replace(/Publisher:|\s/g, "") + .trim(); + + let novelChapters: Chapter.Item[] = []; + + loadedCheerio("table").find("tr").first().remove(); + + loadedCheerio("table") + .find("tr") + .each(function () { + const releaseDate = loadedCheerio(this) + .find("td:nth-child(3)") + .text(); + + let chapterName = loadedCheerio(this) + .find("td:nth-child(1) > a") + .text(); + + if ( + loadedCheerio(this).find("td:nth-child(1) > span").length > + 0 + ) { + chapterName = "🔒 " + chapterName; + } + + const chapterUrl = loadedCheerio(this) + .find(" td:nth-child(1) > a") + .attr("href"); + + if (!chapterUrl) return; + + novelChapters.push({ + name: chapterName, + url: chapterUrl, + releaseTime: releaseDate, + }); + }); + + novel.chapters = novelChapters; + // console.log(novel); + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const url = chapterUrl; + + // console.log(url); + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + let chapterName, chapterText; + + if (result.url === "https://www.epiknovel.com/login") { + chapterName = ""; + chapterText = "Premium Chapter"; + } else { + chapterName = loadedCheerio("#icerik > center > h4 > b").text(); + chapterText = loadedCheerio("div#icerik").html(); + } + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = baseUrl + "seri-listesi?q=" + searchTerm; + + const result = await fetchApi(url); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + let novels: Novel.Item[] = []; + + loadedCheerio("div.col-lg-12.col-md-12").each(function () { + const novelName = loadedCheerio(this).find("h3").text(); + const novelCover = loadedCheerio(this).find("img").attr("data-src"); + const novelUrl = loadedCheerio(this).find("h3 > a").attr("href"); + + if (!novelUrl) return; + + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; +export const fetchImage: Plugin.fetchImage = fetchFile; diff --git a/plugins/turkish/lshnovel.ts b/plugins/turkish/lshnovel.ts new file mode 100644 index 0000000..b22e95e --- /dev/null +++ b/plugins/turkish/lshnovel.ts @@ -0,0 +1,313 @@ +import { load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { FilterInputs } from "@libs/filterInputs"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; + +export const id = "lshnovel"; +export const name = "Liebe Schnee Hiver Novel"; +export const version = "1.0.0"; +export const icon = "multisrc/wpmangastream/icons/lshnovel.png"; +export const site = "https://lshnovel.com/"; +exports.protected = false; + +const pluginId = id; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async function ( + page, + { filters } +) { + let link = `${baseUrl}series/?page=${page}`; + + if (filters) { + if (Array.isArray(filters.genres) && filters.genres.length) { + link += filters.genres.map((i) => `&genre[]=${i}`).join(""); + } + + if (Array.isArray(filters.type) && filters.type.length) { + link += filters.type.map((i) => `&lang[]=${i}`).join(""); + } + } + + link += "&status=" + (filters?.status ? filters?.status : ""); + + link += "&order=" + (filters?.order ? filters?.order : "popular"); + + const body = await fetchApi(link, {}, pluginId).then((result) => + result.text() + ); + + const loadedCheerio = parseHTML(body); + + const novels: Novel.Item[] = []; + + loadedCheerio("article.bs").each(function () { + const novelName = loadedCheerio(this).find(".ntitle").text().trim(); + let image = loadedCheerio(this).find("img"); + const novelCover = image.attr("data-src") || image.attr("src"); + + const novelUrl = loadedCheerio(this).find("a").attr("href"); + if (!novelUrl) return; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const url = novelUrl; + + const result = await fetchApi(url, {}, pluginId); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + const novel: Novel.instance = { + url, + chapters: [], + }; + + novel.name = loadedCheerio(".entry-title").text(); + + novel.cover = + loadedCheerio("img.wp-post-image").attr("data-src") || + loadedCheerio("img.wp-post-image").attr("src"); + + loadedCheerio("div.spe > span").each(function () { + const detailName = loadedCheerio(this).find("b").text().trim(); + const detail = loadedCheerio(this) + .find("b") + .remove() + .end() + .text() + .trim(); + + switch (detailName) { + case "المؤلف:": + case "Yazar:": + case "Autor:": + case "Author:": + novel.author = detail; + break; + case "Status:": + case "Seviye:": + novel.status = detail; + break; + } + }); + + novel.genres = loadedCheerio(".genxed") + .text() + .trim() + .replace(/\s/g, ","); + + loadedCheerio('div[itemprop="description"] h3,p.a,strong').remove(); + novel.summary = loadedCheerio('div[itemprop="description"]') + .find("br") + .replaceWith("\n") + .end() + .text(); + + let chapter: Chapter.Item[] = []; + + loadedCheerio(".eplister") + .find("li") + .each(function () { + const chapterName = + loadedCheerio(this).find(".epl-num").text() + + " - " + + loadedCheerio(this).find(".epl-title").text(); + + const releaseDate = loadedCheerio(this) + .find(".epl-date") + .text() + .trim(); + + const chapterUrl = loadedCheerio(this).find("a").attr("href"); + if (!chapterUrl) return; + chapter.push({ + name: chapterName, + releaseTime: releaseDate, + url: chapterUrl, + }); + }); + + novel.chapters = chapter.reverse(); + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi(chapterUrl, {}, pluginId); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + let chapterText = loadedCheerio("div.epcontent").html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const url = `${baseUrl}?s=${searchTerm}`; + + const result = await fetchApi(url, {}, pluginId); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + const novels: Novel.Item[] = []; + + loadedCheerio("article.bs").each(function () { + const novelName = loadedCheerio(this).find(".ntitle").text().trim(); + const novelCover = loadedCheerio(this).find("img").attr("src"); + const novelUrl = loadedCheerio(this).find("a").attr("href"); + if (!novelUrl) return; + novels.push({ + name: novelName, + url: novelUrl, + cover: novelCover, + }); + }); + + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async function (url) { + const headers = { + Referer: baseUrl, + }; + return await fetchFile(url, { headers: headers }); +}; + +export const filters = [ + { + key: "order", + label: "Önerilen", + values: [ + { label: "Varsayılan", value: "" }, + + { label: "A-Z", value: "title" }, + + { label: "Z-A", value: "titlereverse" }, + + { label: "Son Yüklemeler", value: "update" }, + + { label: "Son Eklenenler", value: "latest" }, + + { label: "Bestimiz", value: "popular" }, + ], + inputType: FilterInputs.Picker, + }, + { + key: "status", + label: "Statü", + values: [ + { label: "Tümü", value: "" }, + + { label: "Ongoing", value: "ongoing" }, + + { label: "Hiatus", value: "hiatus" }, + + { label: "Completed", value: "completed" }, + ], + inputType: FilterInputs.Picker, + }, + { + key: "type", + label: "Tür", + values: [ + { label: "Çeviri Novel", value: "ceviri-novel" }, + + { label: "Liz-Chan", value: "liz-chan" }, + + { label: "Manhwa", value: "manhwa" }, + + { label: "Orijinal Novel", value: "orijinal-novel" }, + + { label: "Web Novel", value: "web-novel" }, + ], + inputType: FilterInputs.Checkbox, + }, + { + key: "genres", + label: "Kategori", + values: [ + { label: "+18", value: "18" }, + + { label: "Action", value: "action" }, + + { label: "Adult", value: "adult" }, + + { label: "Aksiyon", value: "aksiyon" }, + + { label: "BL", value: "bl" }, + + { label: "Comedy", value: "comedy" }, + + { label: "Doğaüstü", value: "dogaustu" }, + + { label: "Dram", value: "dram" }, + + { label: "Drama", value: "drama" }, + + { label: "Ecchi", value: "ecchi" }, + + { label: "Fantastik", value: "fantastik" }, + + { label: "Fantasy", value: "fantasy" }, + + { label: "Gizem", value: "gizem" }, + + { label: "Harem", value: "harem" }, + + { label: "Historical", value: "historical" }, + + { label: "Josei", value: "josei" }, + + { label: "Macera", value: "macera" }, + + { label: "Manhwa", value: "manhwa" }, + + { label: "Martial Arts", value: "martial-arts" }, + + { label: "Mature", value: "mature" }, + + { label: "Novel", value: "novel" }, + + { label: "Okul", value: "okul" }, + + { label: "Psikolojik", value: "psikolojik" }, + + { label: "Psychological", value: "psychological" }, + + { label: "Reverse Harem", value: "reverse-harem" }, + + { label: "Romance", value: "romance" }, + + { label: "Romantik", value: "romantik" }, + + { label: "Shoujo", value: "shoujo" }, + + { label: "Slice Of Life", value: "slice-of-life" }, + + { label: "Smut", value: "smut" }, + + { label: "Supernatural", value: "supernatural" }, + + { label: "Tarihi", value: "tarihi" }, + + { label: "Tragedy", value: "tragedy" }, + + { label: "Yaoi", value: "yaoi" }, + ], + inputType: FilterInputs.Checkbox, + }, +]; diff --git a/plugins/vietnamese/LNHako.js b/plugins/vietnamese/LNHako.js deleted file mode 100644 index e08462a..0000000 --- a/plugins/vietnamese/LNHako.js +++ /dev/null @@ -1,199 +0,0 @@ -const cheerio = require('cheerio'); -const isUrlAbsolute = require('@libs/isAbsoluteUrl'); -const languages = require('@libs/languages'); -const fetchApi = require('@libs/fetchApi'); -const fetchFile = require('@libs/fetchFile'); - -const pluginId = 'ln.hako'; -const baseUrl = 'https://ln.hako.vn'; - -async function popularNovels (page) { - const link = baseUrl + '/danh-sach?truyendich=1&sapxep=topthang&page=' + page; - const result = await fetchApi(link); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - const novels = []; - - loadedCheerio('main.row > .thumb-item-flow').each(function () { - let url = loadedCheerio(this) - .find('div.thumb_attr.series-title > a') - .attr('href'); - - if (url && !isUrlAbsolute(url)) { - url = baseUrl + url; - } - - if (url) { - const name = loadedCheerio(this).find('.series-title').text().trim(); - let cover = loadedCheerio(this) - .find('.img-in-ratio') - .attr('data-bg'); - - if (cover && !isUrlAbsolute(cover)) { - cover = baseUrl + cover; - } - - const novel = { - name, - url, - cover, - }; - - novels.push(novel); - } - }); - - return novels; -}; - -async function parseNovelAndChapters (novelUrl){ - const url = novelUrl; - const result = await fetchApi(url); - const body = await result.text(); - - let loadedCheerio = cheerio.load(body); - - const novel = { - url, - chapters: [], - }; - - novel.name = loadedCheerio('.series-name').text(); - - const background = loadedCheerio('.series-cover > .a6-ratio > div').attr( - 'style', - ); - const novelCover = background.substring( - background.indexOf('http'), - background.length - 2, - ); - - novel.cover = novelCover - ? isUrlAbsolute(novelCover) - ? novelCover - : baseUrl + novelCover - : ''; - - novel.summary = loadedCheerio('.summary-content').text().trim(); - - novel.author = loadedCheerio( - '#mainpart > div:nth-child(2) > div > div:nth-child(1) > section > main > div.top-part > div > div.col-12.col-md-9 > div.series-information > div:nth-child(2) > span.info-value > a', - ) - .text() - .trim(); - - novel.genres = loadedCheerio('.series-gernes') - .text() - .trim() - .replaceAll(/ +/g," ") - .split('\n') - .filter(e => e.trim()) - .join(', '); - - novel.status = loadedCheerio( - '#mainpart > div:nth-child(2) > div > div:nth-child(1) > section > main > div.top-part > div > div.col-12.col-md-9 > div.series-information > div:nth-child(4) > span.info-value > a', - ) - .text() - .trim(); - - loadedCheerio('.list-chapters li').each(function () { - let chapterUrl = loadedCheerio(this).find('a').attr('href'); - - if (chapterUrl && !isUrlAbsolute(chapterUrl)) { - chapterUrl = baseUrl + chapterUrl; - } - - if (chapterUrl) { - const chapterName = loadedCheerio(this) - .find('.chapter-name') - .text() - .trim(); - const releaseTime = loadedCheerio(this).find('.chapter-time').text(); - - const chapter = { - name: chapterName, - releaseTime: releaseTime, - url: chapterUrl, - }; - - novel.chapters.push(chapter); - } - }); - - return novel; -}; - -async function parseChapter (chapterUrl){ - const result = await fetchApi(chapterUrl); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - const chapterText = loadedCheerio('#chapter-content').html() || ''; - - return chapterText; -}; - -async function searchNovels (searchTerm) { - const url = baseUrl + '/tim-kiem?keywords=' + searchTerm; - const result = await fetchApi(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - const novels = []; - - loadedCheerio('div.row > .thumb-item-flow').each(function () { - let novelUrl = loadedCheerio(this) - .find('div.thumb_attr.series-title > a') - .attr('href'); - - if (novelUrl && !isUrlAbsolute(novelUrl)) { - novelUrl = baseUrl + novelUrl; - } - - if (novelUrl) { - const novelName = loadedCheerio(this).find('.series-title').text(); - let novelCover = loadedCheerio(this) - .find('.img-in-ratio') - .attr('data-bg'); - - if (novelCover && !isUrlAbsolute(novelCover)) { - novelCover = baseUrl + novelCover; - } - - novels.push({ - name: novelName, - url: novelUrl, - cover: novelCover, - }); - } - }); - - return novels; -}; - -async function fetchImage (url){ - const headers = { - Referer: 'https://ln.hako.vn', - } - return await fetchFile(url, {headers: headers}); -}; - -module.exports = { - id: pluginId, - name: 'Hako', - version: '1.0.0', - icon: 'src/vi/hakolightnovel/icon.png', - site: baseUrl, - lang: languages.Vietnamese, - description: 'Hầm trú ẩn Hako', - protected: true, - fetchImage, - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, -}; \ No newline at end of file diff --git a/plugins/vietnamese/LNHako.ts b/plugins/vietnamese/LNHako.ts new file mode 100644 index 0000000..caf19bb --- /dev/null +++ b/plugins/vietnamese/LNHako.ts @@ -0,0 +1,197 @@ +import { load as parseHTML } from "cheerio"; +import { fetchApi, fetchFile } from "@libs/fetch"; +import { Chapter, Novel, Plugin } from "@typings/plugin"; +// import dayjs from "dayjs"; +// import { FilterInputs } from "@libs/filterInputs"; +// import { NovelStatus } from "@libs/novelStatus"; +import { isUrlAbsolute } from "@libs/isAbsoluteUrl"; +// import { parseMadaraDate } from "@libs/parseMadaraDate"; + +const pluginId = "ln.hako"; +const baseUrl = "https://ln.hako.vn"; + +export const id = pluginId; // string and must be unique +export const name = "Hako"; +export const icon = "src/vi/hakolightnovel/icon.png"; // The relative path to the icon without @icons . For example: 'src/vi/hakolightnovel/icon.png' +export const version = "1.0.0"; // xx.xx.xx +export const site = baseUrl; // the link to the site +exports["protected"] = true; + +export const popularNovels: Plugin.popularNovels = async (pageNo) => { + const novels: Novel.Item[] = []; + const link = + baseUrl + "/danh-sach?truyendich=1&sapxep=topthang&page=" + pageNo; + const result = await fetchApi(link); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + loadedCheerio("main.row > .thumb-item-flow").each(function () { + let url = loadedCheerio(this) + .find("div.thumb_attr.series-title > a") + .attr("href"); + + if (url && !isUrlAbsolute(url)) { + url = baseUrl + url; + } + + if (url) { + const name = loadedCheerio(this) + .find(".series-title") + .text() + .trim(); + let cover = loadedCheerio(this) + .find(".img-in-ratio") + .attr("data-bg"); + + if (cover && !isUrlAbsolute(cover)) { + cover = baseUrl + cover; + } + + const novel = { + name, + url, + cover, + }; + + novels.push(novel); + } + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + const result = await fetchApi(novelUrl); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + novel.name = loadedCheerio(".series-name").text(); + + const background = + loadedCheerio(".series-cover > .a6-ratio > div").attr("style") || ""; + const novelCover = background.substring( + background.indexOf("http"), + background.length - 2 + ); + + novel.cover = novelCover + ? isUrlAbsolute(novelCover) + ? novelCover + : baseUrl + novelCover + : ""; + + novel.summary = loadedCheerio(".summary-content").text().trim(); + + novel.author = loadedCheerio( + "#mainpart > div:nth-child(2) > div > div:nth-child(1) > section > main > div.top-part > div > div.col-12.col-md-9 > div.series-information > div:nth-child(2) > span.info-value > a" + ) + .text() + .trim(); + + novel.genres = loadedCheerio(".series-gernes") + .text() + .trim() + .replace(/ +/g, " ") + .split("\n") + .filter((e) => e.trim()) + .join(", "); + + novel.status = loadedCheerio( + "#mainpart > div:nth-child(2) > div > div:nth-child(1) > section > main > div.top-part > div > div.col-12.col-md-9 > div.series-information > div:nth-child(4) > span.info-value > a" + ) + .text() + .trim(); + + const chapters: Chapter.Item[] = []; + loadedCheerio(".list-chapters li").each(function () { + let chapterUrl = loadedCheerio(this).find("a").attr("href"); + + if (chapterUrl && !isUrlAbsolute(chapterUrl)) { + chapterUrl = baseUrl + chapterUrl; + } + + if (chapterUrl) { + const chapterName = loadedCheerio(this) + .find(".chapter-name") + .text() + .trim(); + const releaseTime = loadedCheerio(this) + .find(".chapter-time") + .text(); + + const chapter = { + name: chapterName, + releaseTime: releaseTime, + url: chapterUrl, + }; + + chapters.push(chapter); + } + }); + novel.chapters = chapters; + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const result = await fetchApi(chapterUrl); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + const chapterText = loadedCheerio("#chapter-content").html() || ""; + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + + const url = baseUrl + "/tim-kiem?keywords=" + searchTerm; + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + loadedCheerio("div.row > .thumb-item-flow").each(function () { + let novelUrl = loadedCheerio(this) + .find("div.thumb_attr.series-title > a") + .attr("href"); + + if (novelUrl && !isUrlAbsolute(novelUrl)) { + novelUrl = baseUrl + novelUrl; + } + + if (novelUrl) { + const novelName = loadedCheerio(this).find(".series-title").text(); + let novelCover = loadedCheerio(this) + .find(".img-in-ratio") + .attr("data-bg"); + + if (novelCover && !isUrlAbsolute(novelCover)) { + novelCover = baseUrl + novelCover; + } + + novels.push({ + name: novelName, + url: novelUrl, + cover: novelCover, + }); + } + }); + + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + const headers = { + Referer: "https://ln.hako.vn", + }; + return await fetchFile(url, { headers: headers }); +}; diff --git a/scripts/json_plugins.js b/scripts/json_plugins.js deleted file mode 100644 index 9ccee81..0000000 --- a/scripts/json_plugins.js +++ /dev/null @@ -1,49 +0,0 @@ -require('module-alias/register'); -const fs = require('fs'); -const languages = require('@libs/languages'); -const path = require('path') -const root = path.dirname(__dirname); -const config = fs.existsSync(path.join(root, "config.json")) ? require("../config.json") : {}; -const username = config.githubUsername; -const repo = config.githubRepository; -const branch = config.githubBranch; - -if(!username || !repo || !branch){ - process.exit(); -} -if(!fs.existsSync(path.join(root, "dist", username))){ - fs.mkdirSync(path.join(root, "dist", username)); -} -const githubIconsLink = `https://raw.githubusercontent.com/${username}/${repo}/${branch}/icons`; -const githubPluginsLink = `https://raw.githubusercontent.com/${username}/${repo}/${branch}/plugins`; - -const json = {}; -const jsonPath = path.join(root, 'dist', username, 'plugins.json'); -const jsonMinPath = path.join(root, 'dist', username, 'plugins.min.json'); - -for (let language in languages) { // language with English name - const langPath = path.join(root, 'plugins', language.toLowerCase()); - if (!fs.existsSync(langPath)) continue; - const plugins = fs.readdirSync(langPath); - json[languages[language]] = []; - plugins.forEach(plugin => { - if(plugin.startsWith('.')) return; - const instance = require(`@plugins/${language.toLowerCase()}/${plugin.split('.')[0]}`); - - const { id, name, site, lang, version, icon, description } = instance; - // lang: language with native name - const info = { id, name, site, lang, version, description } - info.url = `${githubPluginsLink}/${language.toLowerCase()}/${plugin}`; - info.iconUrl = `${githubIconsLink}/${icon}`; - - json[lang].push(info); - - console.log('Collected', name); - }); -} - -for(let lang in json) json[lang].sort((a, b) => a.id.localeCompare(b.id)) - -fs.writeFileSync(jsonMinPath, JSON.stringify(json)); -fs.writeFileSync(jsonPath, JSON.stringify(json, null, '\t')); -console.log('Done ✅'); diff --git a/scripts/json_plugins.ts b/scripts/json_plugins.ts new file mode 100644 index 0000000..dd8ebda --- /dev/null +++ b/scripts/json_plugins.ts @@ -0,0 +1,106 @@ +require("module-alias/register"); + +import * as fs from "fs"; +import { languages } from "@libs/languages"; +import * as path from "path"; +import { Plugin, isPlugin } from "@typings/plugin"; + +import config from "./../config.json"; + +const root = path.dirname(__dirname); +const outRoot = path.join(root, ".."); +const username = config.githubUsername; +const repo = config.githubRepository; +const branch = config.githubBranch; +if (!username || !repo || !branch) { + console.error("config.json not provided!"); + process.exit(1); +} +if (!fs.existsSync(path.join(outRoot, "dist", username))) { + fs.mkdirSync(path.join(outRoot, "dist", username)); +} +const githubIconsLink = `https://raw.githubusercontent.com/${username}/${repo}/${branch}/icons`; +const githubPluginsLink = `https://raw.githubusercontent.com/${username}/${repo}/${branch}/js/plugins`; + +const json: { + [key: string]: { + id: string; + name: string; + site: string; + lang: string; + version: string; + url: string; + iconUrl: string; + }[]; +} = {}; +const jsonPath = path.join(outRoot, "dist", username, "plugins.json"); +const jsonMinPath = path.join(outRoot, "dist", username, "plugins.min.json"); +const pluginSet = new Set(); + +for (let language in languages) { + // language with English name + const languageNative = languages[language as keyof typeof languages]; + const langPath = path.join(root, "plugins", language.toLowerCase()); + if (!fs.existsSync(langPath)) continue; + const plugins = fs.readdirSync(langPath); + json[languageNative] = []; + plugins.forEach((plugin) => { + if (plugin.startsWith(".")) return; + const instance: + | Plugin.instance + | unknown = require(`../plugins/${language.toLowerCase()}/${ + plugin.split(".")[0] + }`); + + if (!isPlugin(instance)) { + console.log(plugin); + return; + } + + const { id, name, site, version, icon } = instance; + const info = { + id, + name, + site, + lang: languageNative, + version, + url: `${githubPluginsLink}/${language.toLowerCase()}/${plugin}`, + iconUrl: `${githubIconsLink}/${icon}`, + } as const; + + if (pluginSet.has(id)) { + console.log("There's already a plugin with id:", id); + throw new Error("2 or more plugins have the same id"); + } else { + pluginSet.add(id); + } + + json[languageNative].push(info); + console.log(name, "✅"); + }); +} + +for (let lang in json) json[lang].sort((a, b) => a.id.localeCompare(b.id)); + +fs.writeFileSync(jsonMinPath, JSON.stringify(json)); +fs.writeFileSync(jsonPath, JSON.stringify(json, null, "\t")); + +// check for broken plugins +for (let language in languages) { + const tsFiles = fs.readdirSync( + path.join(root, "..", "plugins", language.toLocaleLowerCase()) + ); + tsFiles + .filter((f) => f.endsWith(".broken.ts")) + .forEach((fn) => { + console.error( + language.toLocaleLowerCase() + + "/" + + fn.replace(".broken.ts", "") + + " ❌" + ); + }); +} + +console.log(jsonPath); +console.log("Done ✅"); diff --git a/scripts/multisrc/generate.ts b/scripts/multisrc/generate.ts new file mode 100644 index 0000000..b0c6f5c --- /dev/null +++ b/scripts/multisrc/generate.ts @@ -0,0 +1,52 @@ +import path from "path"; +import fs from "fs"; + +type GeneratedScript = { + lang: string; + filename: string; + pluginScript: string; +}; + +export type ScrpitGeneratorFunction = () => GeneratedScript[]; + +const isScriptGenerator = (s: unknown): s is ScrpitGeneratorFunction => { + return !!s && typeof s === "function"; +}; + +const generate = async (name: string): Promise => { + const generateAll: ScrpitGeneratorFunction | unknown = + require(`./${name}/generator`).generateAll; + if (!isScriptGenerator(generateAll)) return false; + + const sources = generateAll(); + + for (let source of sources) { + const { lang, filename, pluginScript } = source; + if(!lang || !filename || !pluginScript){ + console.warn(name, ": lang, filename, pluginScript are required!"); + continue; + } + const pluginsDir = path.join( + path.dirname(path.dirname(__dirname)), + "plugins" + ); + const filePath = path.join(pluginsDir, lang.toLowerCase(), filename.replace(/[\s-\.]+/g, "") + `[${name}].ts`); + fs.writeFileSync(filePath, pluginScript, { encoding: "utf-8" }); + } + return true; +}; + +const run = async () => { + const sources = fs + .readdirSync(__dirname) + .filter((name) => + fs.lstatSync(path.join(__dirname, name)).isDirectory() + ); + + for (let name of sources) { + const success = await generate(name); + if (success) console.log(name + " OK"); + } +}; + +run(); diff --git a/scripts/multisrc/madara/generator.ts b/scripts/multisrc/madara/generator.ts new file mode 100644 index 0000000..09428cb --- /dev/null +++ b/scripts/multisrc/madara/generator.ts @@ -0,0 +1,234 @@ +import { ScrpitGeneratorFunction } from "../generate"; +import list from "./sources.json"; + +type sourceData = (typeof list)[number]; + +export const generateAll: ScrpitGeneratorFunction = function () { + return list.map((source) => { + return generator(source); + }); +}; + +const generator = function generator(sourceJson: sourceData) { + const { pluginId, baseUrl, sourceName, options } = sourceJson; + const lang = options?.lang || "English"; + const path = options?.path || { + novels: "novel", + novel: "novel", + chapter: "novel", + }; + const useNewChapterEndpoint = options?.useNewChapterEndpoint || false; + const iconFileName = sourceName.replace(/\s+/g, "").toLowerCase(); + + const pluginScript = ` +import { load as parseHTML } from "cheerio"; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +import { defaultCover } from "@libs/defaultCover"; +import { NovelStatus } from "@libs/novelStatus" +import { parseMadaraDate } from "@libs/parseMadaraDate"; +import dayjs from "dayjs"; + +export const id = "${pluginId}"; +export const name = "${sourceName} [madara]"; +export const icon = "multisrc/madara/icons/${iconFileName}.png"; +export const version = "1.0.0"; +export const site = "${baseUrl}"; +const baseUrl = site; + +export const popularNovels: Plugin.popularNovels = async (pageNo, {showLatestNovels}) => { + const novels: Novel.Item[] = []; + const sortOrder = showLatestNovels + ? '?m_orderby=latest' + : '/?m_orderby=rating'; + + let url = site + "${path.novels}" + '/page/' + pageNo + sortOrder; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.manga-title-badges').remove(); + + loadedCheerio('.page-item-detail').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this).find('.post-title') + .find('a') + .attr('href') || ''; + const novel: Novel.Item = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = async ( + novelUrl +) => { + const novel: Novel.instance = { + url: novelUrl, + }; + + const body = await fetchApi(novelUrl).then(res => res.text()); + + let loadedCheerio = parseHTML(body); + + loadedCheerio('.manga-title-badges').remove(); + + novel.name = loadedCheerio('.post-title h1').text().trim(); + + novel.cover = + loadedCheerio('.summary_image > a > img').attr('data-lazy-src') || + loadedCheerio('.summary_image > a > img').attr('data-src') || + loadedCheerio('.summary_image > a > img').attr('src') || + defaultCover; + + loadedCheerio('.post-content_item, .post-content').each(function () { + const detailName = loadedCheerio(this).find('h5').text().trim(); + const detail = loadedCheerio(this).find('.summary-content').text().trim(); + + switch (detailName) { + case 'Genre(s)': + case 'التصنيفات': + novel.genres = detail.replace(/[\\t\\n]/g, ','); + break; + case 'Author(s)': + case 'المؤلف': + case 'المؤلف (ين)': + novel.author = detail; + break; + case 'Status': + case 'الحالة': + novel.status = + detail.includes('OnGoing') || detail.includes('مستمرة') + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + } + }); + + loadedCheerio('div.summary__content .code-block,script').remove(); + novel.summary = loadedCheerio('div.summary__content').text().trim(); + + + let html; + + if (${useNewChapterEndpoint}) { + const novelId = + loadedCheerio('.rating-post-id').attr('value') || + loadedCheerio('#manga-chapters-holder').attr('data-id') || ''; + + const body = { + action: "manga_get_chapters", + manga: novelId, + } + html = await fetchApi( + baseUrl + 'wp-admin/admin-ajax.php', + { + method: 'POST', + body: JSON.stringify(body), + }) + .then(res => res.text()); + } else { + html = await fetchApi( + baseUrl + 'ajax/chapters/', + { method: 'POST' }) + .then(res => res.text()); + } + + if (html !== '0') { + loadedCheerio = parseHTML(html); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('.wp-manga-chapter').each(function () { + const chapterName = loadedCheerio(this).find('a').text().trim(); + + let releaseDate = null; + releaseDate = loadedCheerio(this) + .find('span.chapter-release-date') + .text() + .trim(); + + if (releaseDate) { + releaseDate = parseMadaraDate(releaseDate); + } else { + /** + * Insert current date + */ + + releaseDate = dayjs().format('LL'); + } + + let chapterUrl = loadedCheerio(this).find('a').attr('href') || ''; + + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + }); + + novel.chapters = chapters.reverse(); + return novel; +}; + +export const parseChapter: Plugin.parseChapter = async (chapterUrl) => { + const url = baseUrl + "/" + "${path.chapter}" + "/" + chapterUrl; + + const body = await fetchApi(chapterUrl).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + const chapterText = + loadedCheerio('.text-left').html() || + loadedCheerio('.text-right').html() || + loadedCheerio('.entry-content').html(); + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const url = baseUrl + "?s=" + searchTerm + "&post_type=wp-manga"; + + const body = await fetchApi(url).then(res => res.text()); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('.c-tabs-item__content').each(function () { + const novelName = loadedCheerio(this).find('.post-title').text().trim(); + + let image = loadedCheerio(this).find('img'); + const novelCover = image.attr('data-src') || image.attr('src'); + + let novelUrl = loadedCheerio(this) + .find('.post-title') + .find('a') + .attr('href') || ''; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url); +};`; + + return { + lang, + filename: sourceName, + pluginScript, + }; +}; diff --git a/scripts/multisrc/madara/sources.json b/scripts/multisrc/madara/sources.json new file mode 100644 index 0000000..eb28b9a --- /dev/null +++ b/scripts/multisrc/madara/sources.json @@ -0,0 +1,360 @@ +[ + { + "pluginId": "boxnovel", + "baseUrl": "https://boxnovel.com/", + "sourceName": "BoxNovel", + "options": { + "useNewChapterEndpoint": true + } + }, + { + "pluginId": "zinnovel", + "baseUrl": "https://zinnovel.com/", + "sourceName": "ZinnNovel", + "options": { + "path": { "novels": "manga", "novel": "manga", "chapter": "manga" } + } + }, + { + "pluginId": "novelTL", + "baseUrl": "https://noveltranslate.com/", + "sourceName": "NovelTranslate", + "options": { + "path": { "novels": "all-novels", "novel": "manga", "chapter": "manga" } + } + }, + { + "pluginId": "lunarletters", + "baseUrl": "https://www.lunarletters.com/", + "sourceName": "LunarLetters", + "options": { + "path": { "novels": "series", "novel": "series", "chapter": "series" }, + "useNewChapterEndpoint": true + } + }, + { + "pluginId": "sleeptTLS", + "baseUrl": "https://sleepytranslations.com/", + "sourceName": "SleepyTranslations", + "options": { + "path": { "novels": "series", "novel": "series", "chapter": "series" }, + "useNewChapterEndpoint": true + } + }, + { + "pluginId": "freenovel.me", + "baseUrl": "https://freenovel.me/", + "sourceName": "FreeNovelMe" + }, + { + "pluginId": "1stkissnovel", + "baseUrl": "https://1stkissnovel.org/", + "sourceName": "FirstKissNovel", + "options": { + "useNewChapterEndpoint": true + } + }, + { + "pluginId": "daonovel", + "baseUrl": "https://daonovel.com/", + "sourceName": "DaoNovel", + "options": { + "path": { + "novels": "novels-list", + "novel": "novels", + "chapter": "novels" + }, + "useNewChapterEndpoint": true + } + }, + { + "pluginId": "mostnovel", + "baseUrl": "https://mostnovel.com/", + "sourceName": "MostNovel", + "options": { + "path": { "novels": "manga", "novel": "manga", "chapter": "manga" } + } + }, + { + "pluginId": "novelmultiverse", + "baseUrl": "https://www.novelmultiverse.com/", + "sourceName": "NovelMultiverse" + }, + { + "pluginId": "lnheaven", + "baseUrl": "https://lightnovelheaven.com/", + "sourceName": "LightNovelHeaven", + "options": { + "path": { "novels": "series", "novel": "series", "chapter": "series" }, + "useNewChapterEndpoint": true + } + }, + { + "pluginId": "arnovel", + "baseUrl": "https://arnovel.me/", + "sourceName": "ArNovel", + "options": { + "useNewChapterEndpoint": true, + "lang": "Arabic" + } + }, + { + "pluginId": "meionovel", + "baseUrl": "https://meionovel.id/", + "sourceName": "MeioNovel", + "options": { + "useNewChapterEndpoint": true, + "lang": "Indonesian" + } + }, + { + "pluginId": "webnovelover", + "baseUrl": "https://www.webnovelover.com/", + "sourceName": "WebNovelLover" + }, + { + "pluginId": "cratenovel", + "baseUrl": "https://cratenovel.com/", + "sourceName": "CrateNovel", + "options": { + "useNewChapterEndpoint": true + } + }, + { + "pluginId": "clicknovel", + "baseUrl": "https://clicknovel.net/", + "sourceName": "ClickNovel" + }, + { + "pluginId": "readwebnovels", + "baseUrl": "https://readwebnovels.net/", + "sourceName": "ReadWebNovels" + }, + { + "pluginId": "wbnovel", + "baseUrl": "https://wbnovel.com/", + "sourceName": "WBNovel", + "options": { "lang": "Indonesian" } + }, + { + "pluginId": "wuxiaworld.site", + "baseUrl": "https://wuxiaworld.site/", + "sourceName": "WuxiaWorld.Site", + "options": { + "useNewChapterEndpoint": true + } + }, + { + "pluginId": "mysticalmerries", + "baseUrl": "https://mysticalmerries.com/", + "sourceName": "MysticalSeries", + "options": { + "path": { "novels": "series", "novel": "series", "chapter": "series" }, + "useNewChapterEndpoint": true + } + }, + { + "pluginId": "morenovel", + "baseUrl": "https://morenovel.net/", + "sourceName": "MoreNovel", + "options": { + "useNewChapterEndpoint": true, + "lang": "Indonesian" + } + }, + { + "pluginId": "onlymtl", + "baseUrl": "https://www.onlymtl.com/", + "sourceName": "OnlyMTL", + "options": { + "useNewChapterEndpoint": true + } + }, + { + "pluginId": "hizomanga", + "baseUrl": "https://hizomanga.com/", + "sourceName": "HizoManga", + "options": { + "path": { "novels": "serie", "novel": "serie", "chapter": "serie" }, + "useNewChapterEndpoint": true, + "lang": "Arabic" + } + }, + { + "pluginId": "ar-mtl", + "baseUrl": "https://ar-mtl.club/", + "sourceName": "ArMTL", + "options": { + "useNewChapterEndpoint": true, + "lang": "Arabic" + } + }, + { + "pluginId": "novel4up", + "baseUrl": "https://novel4up.com/", + "sourceName": "Novel4Up", + "options": { + "lang": "Arabic" + } + }, + { + "pluginId": "teamxnovel", + "baseUrl": "https://teamxnovel.com/", + "sourceName": "TeamXNovel", + "options": { + "useNewChapterEndpoint": true, + "lang": "Arabic" + } + }, + { + "pluginId": "turkcelightnovels", + "baseUrl": "https://turkcelightnovels.com/", + "sourceName": "TurkceLightNovels", + "options": { + "path": { + "novels": "light-novel", + "novel": "light-novel", + "chapter": "light-novel" + }, + "lang": "Turkish" + } + }, + { + "pluginId": "sonicmtl", + "baseUrl": "https://www.sonicmtl.com/", + "sourceName": "SonicMTL", + "options": { + "useNewChapterEndpoint": true + } + }, + { + "pluginId": "mtlnovel.club", + "baseUrl": "https://mtlnovel.club/", + "sourceName": "MTLNovel.Club", + "options": { + "useNewChapterEndpoint": true + } + }, + { + "pluginId": "noobchanTL", + "baseUrl": "https://noobchan.xyz/", + "sourceName": "Noobchan Translation", + "options": { + "useNewChapterEndpoint": true + } + }, + { + "pluginId": "guavaread", + "baseUrl": "https://guavaread.com/", + "sourceName": "Guavaread", + "options": { + "useNewChapterEndpoint": true + } + }, + { + "pluginId": "sweetEscapeTL", + "baseUrl": "https://sweetescapetranslations.com/", + "sourceName": "Sweet Escape Translations", + "options": { + "useNewChapterEndpoint": false, + "path": { "novels": "manga", "novel": "manga", "chapter": "manga" } + } + }, + { + "pluginId": "fanTLS", + "baseUrl": "https://fanstranslations.com/", + "sourceName": "Fans Translations", + "options": { + "useNewChapterEndpoint": true + } + }, + { + "pluginId": "riwyat", + "baseUrl": "https://riwyat.com/", + "sourceName": "Riwyat", + "options": { + "useNewChapterEndpoint": true, + "lang": "Arabic" + } + }, + { + "pluginId": "novelstic", + "baseUrl": "https://novelstic.com/", + "sourceName": "Novelstic", + "options": { + "useNewChapterEndpoint": true, + "lang": "English" + } + }, + { + "pluginId": "neosekaiTLS", + "baseUrl": "https://www.neosekaitranslations.com/", + "sourceName": "NeoSekai Translations", + "options": { + "lang": "English" + } + }, + { + "pluginId": "mtl-novel", + "baseUrl": "https://mtl-novel.com/", + "sourceName": "MTL-Novel", + "options": { + "useNewChapterEndpoint": true, + "lang": "English", + "orderBy": "latest" + } + }, + { + "pluginId": "zetroTL", + "baseUrl": "https://zetrotranslation.com/", + "sourceName": "Zetro Translation", + "options": { + "lang": "English" + } + }, + { + "pluginId": "nocturneTLS", + "baseUrl": "https://nocturnetls.net/", + "sourceName": "Nocturne Translations", + "options": { + "lang": "English", + "path": { "novels": "novels", "novel": "novels", "chapter": "novels" } + } + }, + { + "pluginId": "sugarbbscan", + "baseUrl": "https://sugarbbscan.com/", + "sourceName": "Sugar Babies", + "options": { + "lang": "English", + "path": { "novels": "series", "novel": "series", "chapter": "series" } + } + }, + { + "pluginId": "novelroom", + "baseUrl": "https://novelroom.net/", + "sourceName": "Novelroom.net", + "options": { + "lang": "English", + "path": { "novels": "manga", "novel": "manga", "chapter": "manga" } + } + }, + { + "pluginId": "novelr18", + "baseUrl": "https://novelr18.com/", + "sourceName": "NovelR18 ", + "options": { + "lang": "English", + "path": { "novels": "novel", "novel": "manga", "chapter": "manga" }, + "useNewChapterEndpoint": true + } + }, + { + "pluginId": "webnoveloku", + "baseUrl": "https://www.webnoveloku.com/", + "sourceName": "WebNovelOku ", + "options": { + "lang": "Turkish" + } + } + ] diff --git a/scripts/multisrc/noveltl/generator.ts b/scripts/multisrc/noveltl/generator.ts new file mode 100644 index 0000000..8bded11 --- /dev/null +++ b/scripts/multisrc/noveltl/generator.ts @@ -0,0 +1,243 @@ +import { ScrpitGeneratorFunction } from "../generate"; +import list from "./sources.json"; + +type sourceData = (typeof list)[number]; + +export const generateAll: ScrpitGeneratorFunction = function () { + return list.map((source) => { + return generator(source); + }); +}; + +const generator = function generator(sourceJson: sourceData) { + const pluginId = sourceJson.id; + const sourceName = sourceJson.sourceName; + const lang = "Russian"; + const site = sourceJson.sourceSite; + const filters = sourceJson.filters || []; + const filetersString = JSON.stringify(filters).replace(/(\"inputType\":)\s*\"([^\"]+)\"/g, "$1FilterInputs.$2"); + const pluginScript = ` + import { load as parseHTML } from "cheerio"; + import dayjs from 'dayjs'; + import { fetchFile, fetchApi } from "@libs/fetch"; + import { Novel, Plugin, Chapter } from "@typings/plugin"; + // import { parseMadaraDate } from "@libs/parseMadaraDate"; + // import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; + // import { showToast } from "@libs/showToast"; + import { Filter, FilterInputs } from "@libs/filterInputs"; + import { NovelStatus } from '@libs/novelStatus'; + import { defaultCover } from "@libs/defaultCover"; + + + export const id = "${pluginId}"; + export const name = "${sourceName}"; + export const icon = ""; + export const version = "1.0.0"; + export const site = "${site}"; + export const filters: Filter[] = ${filetersString}; + exports["protected"] = false; + + const baseUrl = site; + const domain = baseUrl.split('//')[1]; + + export const popularNovels: Plugin.popularNovels = async function ( + page, + { filters } + ) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: + 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: filters?.tags?.length ?? 0 > 0 ? { tags: filters?.tags } : {}, + hostname: domain, + limit: 40, + page, + }, + }), + }); + + const json: any = await result.json(); + const novels: Novel.Item[] = json.data?.projects?.content?.map((novel: any) => ({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: novel.covers[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + })); + + return novels; + }; + + + export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Book', + query: + 'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}', + variables: { + hostname: domain, + project: novelUrl, + url: novelUrl, + }, + }), + }); + const json: any = await result.json(); + const novel: Novel.instance = { + url: novelUrl, + name: json.data.project.title, + cover: json.data.project.covers[0]?.url + ? baseUrl + json.data.project.covers[0].url + : defaultCover, + summary: parseHTML(json.data.project.annotation?.text).text(), + author: + json.data.project.persons[0].name.firstName + + ' ' + + json.data.project.persons[0].name.lastName, + status: + json.data.project.translationStatus === 'active' + ? NovelStatus.Ongoing + : NovelStatus.Completed, + }; + + let genres = [] + .concat(json.data.project.tags, json.data.project.genres) + .map((item: any) => item?.nameRu || item?.nameEng) + .filter(item => item); + + if (genres.length > 0) { + novel.genres = genres.join(','); + } + + let novelChapters: Chapter.Item[] = []; + json.data.project.subprojects.content.forEach((work: any) => + work.volumes.content.forEach((volume: any) => + volume.chapters.forEach( + (chapter: any) => + chapter?.published && + novelChapters.push({ + name: volume.shortName + ' ' + chapter.title, + url: novelUrl + '/' + volume.url + '/' + chapter.url, + releaseTime: dayjs(chapter.publishDate).format('LLL'), + }), + ), + ), + ); + novel.chapters = novelChapters; + return novel; + }; + + export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'EReaderData', + query: + 'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}', + variables: { + chapterSelector: { + fullUrl: chapterUrl, + }, + url: chapterUrl, + }, + }), + }); + const json: any = await result.json(); + + return parseHTML(json.data.chapter.text.text).html(); + }; + + export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const result = await fetchApi('https://api.novel.tl/api/site/v2/graphql', { + method: 'post', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', + 'Content-Type': 'application/json', + 'Alt-Used': 'api.novel.tl', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + }, + referrer: baseUrl, + body: JSON.stringify({ + operationName: 'Projects', + query: + 'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}', + variables: { + filter: { + query: searchTerm, + }, + hostname: domain, + limit: 40, + page: 1, + }, + }), + }); + const json: any = await result.json(); + const novels: Novel.Item[] = []; + json?.data?.projects?.content?.forEach((novel: any) => + novels.push({ + name: novel.title, + url: baseUrl + '/r/' + novel.url, + cover: novel.covers[0]?.url + ? baseUrl + novel.covers[0].url + : defaultCover, + }), + ); + return novels; + }; + + export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url, {}); + }; + ` + return { + lang, + filename: site.split('//')[1].replace(/\./g, ''), + pluginScript, + }; +}; diff --git a/scripts/multisrc/noveltl/sources.json b/scripts/multisrc/noveltl/sources.json new file mode 100644 index 0000000..d141287 --- /dev/null +++ b/scripts/multisrc/noveltl/sources.json @@ -0,0 +1,1089 @@ +[ + { + "id": "ruranobe", + "sourceName": "РуРанобэ", + "sourceSite": "https://ruranobe.ru", + "filters": [ + { + "key": "tags", + "label": "Тэги", + "values": [ + { + "label": "Автоматы", + "value": 64 + }, + { + "label": "Агрессивные персонажи", + "value": 26 + }, + { + "label": "Ад", + "value": 338 + }, + { + "label": "Адаптация манги", + "value": 8 + }, + { + "label": "Академия", + "value": 5 + }, + { + "label": "Алхимия", + "value": 27 + }, + { + "label": "Альтернативная реальность", + "value": 30 + }, + { + "label": "Амнезия", + "value": 31 + }, + { + "label": "Анабиоз", + "value": 170 + }, + { + "label": "Ангелы", + "value": 38 + }, + { + "label": "Андроиды", + "value": 37 + }, + { + "label": "Антигерой", + "value": 43 + }, + { + "label": "Антимагия", + "value": 41 + }, + { + "label": "Апатичный главный герой", + "value": 46 + }, + { + "label": "Апокалипсис", + "value": 47 + }, + { + "label": "Аристократия", + "value": 51 + }, + { + "label": "Артефакты", + "value": 58 + }, + { + "label": "Асоциальный главный герой", + "value": 42 + }, + { + "label": "R-15", + "value": 570 + }, + { + "label": "R-18", + "value": 571 + } + ], + "inputType": "Checkbox" + } + ] + }, + { + "id": "ukr.novel", + "sourceName": "Переклади українською мовою", + "sourceSite": "https://ukr.novel.tl", + "filters": [ + { + "key": "tags", + "label": "Тэги", + "values": [ + { + "label": "Альтернативная реальность", + "value": 30 + }, + { + "label": "Апатичный главный герой", + "value": 46 + }, + { + "label": "Асоциальный главный герой", + "value": 42 + }, + { + "label": "Владелец уникального оружия", + "value": 736 + }, + { + "label": "Гарем рабов", + "value": 656 + }, + { + "label": "Герои", + "value": 341 + }, + { + "label": "Героиня красавица", + "value": 81 + }, + { + "label": "Герой использует щит", + "value": 636 + }, + { + "label": "Герой-трудяга", + "value": 330 + }, + { + "label": "Главный герой — мужчина", + "value": 419 + }, + { + "label": "Глухой к любви герой", + "value": 194 + }, + { + "label": "Демоны", + "value": 193 + }, + { + "label": "Друзья детства", + "value": 128 + }, + { + "label": "Есть аниме-адаптация", + "value": 10 + }, + { + "label": "Есть манга-адаптация", + "value": 14 + }, + { + "label": "Есть CD дорама-адаптация", + "value": 12 + }, + { + "label": "Зверолюди", + "value": 39 + }, + { + "label": "Магия", + "value": 412 + }, + { + "label": "Магия призыва", + "value": 694 + }, + { + "label": "От слабого к сильному", + "value": 752 + } + ], + "inputType": "Checkbox" + } + ] + }, + { + "id": "nightnovel", + "sourceName": "Nightnovel", + "sourceSite": "https://nightnovel.online", + "filters": [ + { + "key": "tags", + "label": "Тэги", + "values": [ + { + "label": "Альтернативная реальность", + "value": 30 + }, + { + "label": "Амнезия", + "value": 31 + }, + { + "label": "Анал", + "value": 33 + }, + { + "label": "Ангелы", + "value": 38 + }, + { + "label": "Аристократия", + "value": 51 + }, + { + "label": "Артефакты", + "value": 58 + }, + { + "label": "Ассасины", + "value": 61 + }, + { + "label": "Банды", + "value": 302 + }, + { + "label": "БДСМ", + "value": 77 + }, + { + "label": "Беременность", + "value": 551 + }, + { + "label": "Боги", + "value": 318 + }, + { + "label": "Богини", + "value": 316 + }, + { + "label": "Боевая академия", + "value": 75 + }, + { + "label": "Божественная сила", + "value": 317 + }, + { + "label": "Влияние прошлого", + "value": 517 + }, + { + "label": "Враги становятся союзниками", + "value": 238 + }, + { + "label": "Вынужденные условия проживания", + "value": 287 + }, + { + "label": "Герои", + "value": 341 + }, + { + "label": "Героиня красавица", + "value": 81 + }, + { + "label": "R-18", + "value": 571 + } + ], + "inputType": "Checkbox" + } + ] + }, + { + "id": "kg.novel", + "sourceName": "Карманная галактика", + "sourceSite": "https://kg.novel.tl", + "filters": [ + { + "key": "tags", + "label": "Тэги", + "values": [ + { + "label": "Алхимия", + "value": 27 + }, + { + "label": "Андроиды", + "value": 37 + }, + { + "label": "Анти-рост персонажа", + "value": 25 + }, + { + "label": "Антигерой", + "value": 43 + }, + { + "label": "Апатичный главный герой", + "value": 46 + }, + { + "label": "Аристократия", + "value": 51 + }, + { + "label": "Асоциальный главный герой", + "value": 42 + }, + { + "label": "Ассасины", + "value": 61 + }, + { + "label": "Беззаботный герой", + "value": 115 + }, + { + "label": "Безработные", + "value": 377 + }, + { + "label": "Бессмертные", + "value": 359 + }, + { + "label": "Библиотека", + "value": 393 + }, + { + "label": "Боги", + "value": 318 + }, + { + "label": "Боевая академия", + "value": 75 + }, + { + "label": "Бывший герой", + "value": 290 + }, + { + "label": "Ведьмы", + "value": 756 + }, + { + "label": "Везучий главный герой", + "value": 411 + }, + { + "label": "Верные подданные", + "value": 410 + }, + { + "label": "Взросление персонажа", + "value": 24 + }, + { + "label": "R-15", + "value": 570 + } + ], + "inputType": "Checkbox" + } + ] + }, + { + "id": "axel.znovel", + "sourceName": "Аксел", + "sourceSite": "https://axel.znovel.space", + "filters": [ + { + "key": "tags", + "label": "Тэги", + "values": [ + { + "label": "Богини", + "value": 316 + }, + { + "label": "Везучий главный герой", + "value": 411 + }, + { + "label": "Виртуальная реальность", + "value": 744 + }, + { + "label": "Высокомерный герой", + "value": 56 + }, + { + "label": "Герой-извращенец", + "value": 521 + }, + { + "label": "Главная героиня — женщина", + "value": 277 + }, + { + "label": "Главный герой — мужчина", + "value": 419 + }, + { + "label": "Дворяне", + "value": 487 + }, + { + "label": "Девочки-волшебницы", + "value": 415 + }, + { + "label": "Демоны", + "value": 193 + }, + { + "label": "Дружба", + "value": 293 + }, + { + "label": "Есть аниме-адаптация", + "value": 10 + }, + { + "label": "Есть видеоигра", + "value": 13 + }, + { + "label": "Есть манга-адаптация", + "value": 14 + }, + { + "label": "Игра на выживание", + "value": 696 + }, + { + "label": "Из грязи в князи", + "value": 542 + }, + { + "label": "Королевская особа", + "value": 596 + }, + { + "label": "Ленивый главный герой", + "value": 389 + }, + { + "label": "Маги", + "value": 757 + }, + { + "label": "Магические технологии", + "value": 417 + } + ], + "inputType": "Checkbox" + } + ] + }, + { + "id": "sferdrakon.novel", + "sourceName": "Уголок дракона", + "sourceSite": "https://sferdrakon.novel.tl", + "filters": [ + { + "key": "tags", + "label": "Тэги", + "values": [ + { + "label": "Главный герой — мужчина", + "value": 419 + }, + { + "label": "Демоны", + "value": 193 + }, + { + "label": "Драконы", + "value": 218 + }, + { + "label": "Есть аниме-адаптация", + "value": 10 + }, + { + "label": "Есть видеоигра", + "value": 13 + }, + { + "label": "Есть манга-адаптация", + "value": 14 + }, + { + "label": "Звероподобные", + "value": 79 + }, + { + "label": "Изнасилование", + "value": 574 + }, + { + "label": "Королевская особа", + "value": 596 + }, + { + "label": "Магия", + "value": 412 + }, + { + "label": "Магия призыва", + "value": 694 + }, + { + "label": "Наездники на драконах", + "value": 216 + }, + { + "label": "Рыцари", + "value": 383 + }, + { + "label": "Цундере", + "value": 728 + }, + { + "label": "Эльфы", + "value": 235 + } + ], + "inputType": "Checkbox" + } + ] + }, + { + "id": "original.novel", + "sourceName": "Авторские работы", + "sourceSite": "https://original.novel.tl", + "filters": [ + { + "key": "tags", + "label": "Тэги", + "values": [ + { + "label": "Агрессивные персонажи", + "value": 26 + }, + { + "label": "Академия", + "value": 5 + }, + { + "label": "Андроиды", + "value": 37 + }, + { + "label": "Антимагия", + "value": 41 + }, + { + "label": "Аристократия", + "value": 51 + }, + { + "label": "Артефакты", + "value": 58 + }, + { + "label": "Брошенные дети", + "value": 1 + }, + { + "label": "Вероломные герои/плетение интриг", + "value": 424 + }, + { + "label": "Взросление персонажа", + "value": 24 + }, + { + "label": "Владелец уникального оружия", + "value": 736 + }, + { + "label": "Герой-извращенец", + "value": 521 + }, + { + "label": "Главный герой не человек", + "value": 488 + }, + { + "label": "Государственные интриги", + "value": 212 + }, + { + "label": "Душещипательная история", + "value": 335 + }, + { + "label": "Жестокие персонажи", + "value": 4 + }, + { + "label": "Зверолюди", + "value": 39 + }, + { + "label": "Знания из прошлой жизни", + "value": 553 + }, + { + "label": "Искусственный интеллект", + "value": 59 + }, + { + "label": "Конфликт в семье", + "value": 261 + }, + { + "label": "Королевства", + "value": 382 + } + ], + "inputType": "Checkbox" + } + ] + }, + { + "id": "snailulitka.novel", + "sourceName": "Snailulitka", + "sourceSite": "https://snailulitka.novel.tl", + "filters": [ + { + "key": "tags", + "label": "Тэги", + "values": [ + { + "label": "Алхимия", + "value": 27 + }, + { + "label": "Ассасины", + "value": 61 + }, + { + "label": "Бедный главный герой", + "value": 541 + }, + { + "label": "Богачи", + "value": 753 + }, + { + "label": "Виртуальная реальность", + "value": 744 + }, + { + "label": "Война", + "value": 750 + }, + { + "label": "Геймеры", + "value": 301 + }, + { + "label": "Героиня красавица", + "value": 81 + }, + { + "label": "Герой красавец", + "value": 329 + }, + { + "label": "Гильдии", + "value": 324 + }, + { + "label": "Главный герой - гений", + "value": 308 + }, + { + "label": "Главный герой — знаменитость", + "value": 263 + }, + { + "label": "Главный герой — мужчина", + "value": 419 + }, + { + "label": "Дворфы", + "value": 224 + }, + { + "label": "Драконы", + "value": 218 + }, + { + "label": "Есть маньхуа-адаптация", + "value": 15 + }, + { + "label": "Игровая рейтинговая система", + "value": 300 + }, + { + "label": "Книги навыков", + "value": 654 + }, + { + "label": "Лекари", + "value": 334 + }, + { + "label": "Месть", + "value": 588 + } + ], + "inputType": "Checkbox" + } + ] + }, + { + "id": "tsundoku.novel", + "sourceName": "TsundokuTrans", + "sourceSite": "https://tsundoku.novel.tl", + "filters": [ + { + "key": "tags", + "label": "Тэги", + "values": [ + { + "label": "Боги", + "value": 318 + }, + { + "label": "Боевая академия", + "value": 75 + }, + { + "label": "Вампиры", + "value": 742 + }, + { + "label": "Влияние прошлого", + "value": 517 + }, + { + "label": "Война", + "value": 750 + }, + { + "label": "Главная героиня — женщина", + "value": 277 + }, + { + "label": "Главный герой — мужчина", + "value": 419 + }, + { + "label": "Демоны", + "value": 193 + }, + { + "label": "Драконы", + "value": 218 + }, + { + "label": "Королевская особа", + "value": 596 + }, + { + "label": "Коррупция", + "value": 157 + }, + { + "label": "Магия", + "value": 412 + }, + { + "label": "Мастер подземелий", + "value": 222 + }, + { + "label": "Мечи и магия", + "value": 697 + }, + { + "label": "Мечники", + "value": 698 + }, + { + "label": "Монстры", + "value": 454 + }, + { + "label": "Недооцененный главный герой", + "value": 734 + }, + { + "label": "От слабого к сильному", + "value": 752 + }, + { + "label": "R-15", + "value": 570 + }, + { + "label": "R-18", + "value": 571 + } + ], + "inputType": "Checkbox" + } + ] + }, + { + "id": "japit.novel", + "sourceName": "Japit Comics", + "sourceSite": "https://japit.novel.tl", + "filters": [ + { + "key": "tags", + "label": "Тэги", + "values": [ + { + "label": "Академия", + "value": 5 + }, + { + "label": "Андроиды", + "value": 37 + }, + { + "label": "Беззаботный герой", + "value": 115 + }, + { + "label": "Боги", + "value": 318 + }, + { + "label": "Богини", + "value": 316 + }, + { + "label": "Вампиры", + "value": 742 + }, + { + "label": "Везучий главный герой", + "value": 411 + }, + { + "label": "Влияние прошлого", + "value": 517 + }, + { + "label": "Внезапное обогащение", + "value": 691 + }, + { + "label": "Военные хроники", + "value": 749 + }, + { + "label": "Война", + "value": 750 + }, + { + "label": "Воспоминания прошлого", + "value": 283 + }, + { + "label": "Герои", + "value": 341 + }, + { + "label": "Героиня красавица", + "value": 81 + }, + { + "label": "Герой влюбляется первым", + "value": 561 + }, + { + "label": "Герой красавец", + "value": 329 + }, + { + "label": "Герой-бесстыдник", + "value": 632 + }, + { + "label": "Герой-извращенец", + "value": 521 + }, + { + "label": "Герой-трудяга", + "value": 330 + }, + { + "label": "R-15", + "value": 570 + } + ], + "inputType": "Checkbox" + } + ] + }, + { + "id": "bel.novel", + "sourceName": "Белманга", + "sourceSite": "https://bel.novel.tl" + }, + { + "id": "red.novel", + "sourceName": "Север и Юг", + "sourceSite": "https://red.novel.tl", + "filters": [ + { + "key": "tags", + "label": "Тэги", + "values": [ + { + "label": "Героиня красавица", + "value": 81 + }, + { + "label": "Герой влюбляется первым", + "value": 561 + }, + { + "label": "Главный герой — мужчина", + "value": 419 + }, + { + "label": "Дружба", + "value": 293 + }, + { + "label": "Жизнь в одиночку", + "value": 396 + }, + { + "label": "Сложные семейные отношения", + "value": 149 + } + ], + "inputType": "Checkbox" + } + ] + }, + { + "id": "si.novel", + "sourceName": "Сказочные истории", + "sourceSite": "https://si.novel.tl", + "filters": [ + { + "key": "tags", + "label": "Тэги", + "values": [ + { + "label": "Аристократия", + "value": 51 + }, + { + "label": "Война", + "value": 750 + }, + { + "label": "Главная героиня — женщина", + "value": 277 + }, + { + "label": "Драконы", + "value": 218 + }, + { + "label": "Древний Китай", + "value": 34 + }, + { + "label": "Жестокие персонажи", + "value": 4 + }, + { + "label": "Злобные благородные девы", + "value": 743 + }, + { + "label": "Комедийный подтекст", + "value": 147 + }, + { + "label": "Культивация", + "value": 171 + }, + { + "label": "Магия", + "value": 412 + }, + { + "label": "Отомэ-игры", + "value": 504 + }, + { + "label": "Перерождение в другом мире", + "value": 580 + }, + { + "label": "Перерождение в другом мире", + "value": 720 + }, + { + "label": "Повелитель демонов", + "value": 191 + }, + { + "label": "Попаданец в другой мир", + "value": 723 + }, + { + "label": "Смерть", + "value": 185 + }, + { + "label": "Средневековье", + "value": 436 + }, + { + "label": "Фэнтези", + "value": 267 + }, + { + "label": "Харизматичный протагонист", + "value": 120 + }, + { + "label": "Хитрый главный герой", + "value": 173 + } + ], + "inputType": "Checkbox" + } + ] + }, + { + "id": "kod.novel", + "sourceName": "K.O.D.", + "sourceSite": "https://kod.novel.tl", + "filters": [ + { + "key": "tags", + "label": "Тэги", + "values": [ + { + "label": "Актёрское искусство", + "value": 7 + }, + { + "label": "Забота о детях", + "value": 127 + }, + { + "label": "Управление бизнесом", + "value": 109 + }, + { + "label": "Шоу-бизнес", + "value": 642 + } + ], + "inputType": "Checkbox" + } + ] + } + ] diff --git a/scripts/multisrc/readwn/generator.ts b/scripts/multisrc/readwn/generator.ts new file mode 100644 index 0000000..7bd356a --- /dev/null +++ b/scripts/multisrc/readwn/generator.ts @@ -0,0 +1,220 @@ +import { ScrpitGeneratorFunction } from "../generate"; +import list from "./sources.json"; + +type sourceData = (typeof list)[number]; + +export const generateAll: ScrpitGeneratorFunction = function () { + return list.map((source) => { + return generator(source); + }); +}; + +const generator = function generator(sourceJson: sourceData) { + const pluginId = sourceJson.id; + const sourceName = sourceJson.sourceName; + const site = sourceJson.sourceSite; + const iconFileName = sourceName.split('.')[0].toLowerCase() + '.png'; + const pluginScript = ` + import { load as parseHTML } from "cheerio"; + // import dayjs from 'dayjs'; + import { fetchFile, fetchApi } from "@libs/fetch"; + import { Novel, Plugin, Chapter } from "@typings/plugin"; + // import { parseMadaraDate } from "@libs/parseMadaraDate"; + // import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; + // import { showToast } from "@libs/showToast"; + // import { Filter, FilterInputs } from "@libs/filterInputs"; + // import { NovelStatus } from '@libs/novelStatus'; + // import { defaultCover } from "@libs/defaultCover"; + + + export const id = "${pluginId}"; + export const name = "${sourceName}"; + export const icon = "multisrc/readwn/icon/${iconFileName}"; + export const version = "1.0.0"; + export const site = "${site}"; + // export const filters: Filter[] = []; + exports["protected"] = false; + + const baseUrl = site; + + export const popularNovels: Plugin.popularNovels = async function ( + page, + { filters, showLatestNovels } + ) { + const novels: Novel.Item[] = []; + const pageNo = page - 1; + + const url = baseUrl + 'list/all/all-onclick-' + pageNo +'.html'; + + const result = await fetchApi(url); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + + loadedCheerio('li.novel-item').each(function () { + const novelName = loadedCheerio(this).find('h4').text(); + const novelUrl = baseUrl + loadedCheerio(this).find('a').attr('href'); + + const coverUri = loadedCheerio(this) + .find('.novel-cover > img') + .attr('data-src'); + + const novelCover = baseUrl + coverUri; + + const novel = { name: novelName, cover: novelCover, url: novelUrl }; + + novels.push(novel); + }); + + return novels; + }; + + + export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const result = await fetchApi(novelUrl); + const body = await result.text(); + + let loadedCheerio = parseHTML(body); + + const novel: Novel.instance = { + url: novelUrl, + chapters: [], + }; + + novel.name = loadedCheerio('h1.novel-title').text(); + + const coverUri = loadedCheerio('figure.cover > img').attr('data-src'); + novel.cover = baseUrl + coverUri; + + novel.summary = loadedCheerio('.summary') + .text() + .replace('Summary', '') + .trim(); + + novel.genres = ''; + + loadedCheerio('div.categories > ul > li').each(function () { + novel.genres += loadedCheerio(this).text().trim() + ','; + }); + + loadedCheerio('div.header-stats > span').each(function () { + if (loadedCheerio(this).find('small').text() === 'Status') { + novel.status = loadedCheerio(this).find('strong').text(); + } + }); + + novel.genres = novel.genres.slice(0, -1); + + novel.author = loadedCheerio('span[itemprop=author]').text(); + + let novelChapters: Chapter.Item[] = []; + + const novelId = novelUrl.replace('.html', '').replace(baseUrl, ''); + + const latestChapterNo = parseInt(loadedCheerio('.header-stats') + .find('span > strong') + .first() + .text() + .trim()); + + let lastChapterNo = 1; + loadedCheerio('.chapter-list li').each(function () { + const chapterName = loadedCheerio(this) + .find('a .chapter-title') + .text() + .trim(); + + const chapterUrl = baseUrl + loadedCheerio(this).find('a').attr('href')?.trim(); + + const releaseDate = loadedCheerio(this) + .find('a .chapter-update') + .text() + .trim(); + + lastChapterNo = parseInt(loadedCheerio(this).find('a .chapter-no').text().trim()); + + const chapter = { name: chapterName, releaseTime: releaseDate, url: chapterUrl }; + + novelChapters.push(chapter); + }); + + // Itterate once more before loop to finish off + lastChapterNo++; + for (let i = lastChapterNo; i <= latestChapterNo; i++) { + const chapterName = 'Chapter ' + i; + const chapterUrl = baseUrl + novelId+'_'+ i +'.html'; + const releaseDate = null; + + const chapter = { name: chapterName, releaseTime: releaseDate,url: chapterUrl }; + + novelChapters.push(chapter); + } + + novel.chapters = novelChapters; + return novel; + }; + + export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const result = await fetchApi(chapterUrl); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + const chapterText = loadedCheerio('.chapter-content').html(); + return chapterText; + }; + + export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const searchUrl = baseUrl + 'e/search/index.php'; + + const result = await fetchApi(searchUrl, { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Referer: baseUrl + 'search.html', + Origin: baseUrl, + 'user-agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36', + }, + method: 'POST', + body: JSON.stringify({ + show: 'title', + tempid: 1, + tbname: 'news', + keyboard: searchTerm, + }), + }); + const body = await result.text(); + + const loadedCheerio = parseHTML(body); + + loadedCheerio('li.novel-item').each(function () { + const novelName = loadedCheerio(this).find('h4').text(); + const novelUrl = baseUrl + loadedCheerio(this).find('a').attr('href'); + + const coverUri = baseUrl + loadedCheerio(this).find('img').attr('data-src'); + const novelCover = baseUrl + coverUri; + const novel = { + name: novelName, + cover: novelCover, + url: novelUrl, + }; + + novels.push(novel); + }); + return novels; + }; + + export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url, {}); + }; + + ` + return { + lang: "English", + filename: sourceName, + pluginScript, + }; +}; diff --git a/scripts/multisrc/readwn/sources.json b/scripts/multisrc/readwn/sources.json new file mode 100644 index 0000000..e01623b --- /dev/null +++ b/scripts/multisrc/readwn/sources.json @@ -0,0 +1,17 @@ +[ + { + "id": "Readwn.com", + "sourceSite": "https://www.readwn.com/", + "sourceName": "Readwn.com" + }, + { + "id": "Novelmt.com", + "sourceSite": "https://www.novelmt.com/", + "sourceName": "Novelmt.com" + }, + { + "id": "Ltnovel.com", + "sourceSite": "https://www.ltnovel.com/", + "sourceName": "Ltnovel.com" + } +] diff --git a/scripts/multisrc/rulate/generator.ts b/scripts/multisrc/rulate/generator.ts new file mode 100644 index 0000000..b85df08 --- /dev/null +++ b/scripts/multisrc/rulate/generator.ts @@ -0,0 +1,294 @@ +import { ScrpitGeneratorFunction } from "../generate"; +import list from "./sources.json"; + +type sourceData = (typeof list)[number]; + +export const generateAll: ScrpitGeneratorFunction = function () { + return list.map((source) => { + return generator(source); + }); +}; + +const generator = function generator(sourceJson: sourceData) { + const pluginId = sourceJson.id; + const souceName = sourceJson.sourceName; + const site = sourceJson.sourceSite; + const filters = sourceJson.filters; + const filetersString = JSON.stringify(filters).replace(/(\"inputType\":)\s*\"([^\"]+)\"/g, "$1FilterInputs.$2"); + const pluginScript = ` + import { load as parseHTML } from "cheerio"; + // import dayjs from 'dayjs'; + import { fetchFile, fetchApi } from "@libs/fetch"; + import { Novel, Plugin, Chapter } from "@typings/plugin"; + // import { parseMadaraDate } from "@libs/parseMadaraDate"; + // import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; + // import { showToast } from "@libs/showToast"; + import { Filter, FilterInputs } from "@libs/filterInputs"; + import { NovelStatus } from '@libs/novelStatus'; + // import { defaultCover } from "@libs/defaultCover"; + + + export const id = "${pluginId}"; + export const name = "${souceName}"; + export const icon = ""; + export const version = "1.0.0"; + export const site = "${site}"; + exports["protected"] = false; + + const baseUrl = site; + + const customFilters: Filter[] = ${filetersString}; + + export const filters = [ + { + key: 'sort', + label: 'Сортировка', + values: [ + { label: 'По рейтингу', value: '6' }, + { label: 'По степени готовности', value: '0' }, + { label: 'По названию на языке оригинала', value: '1' }, + { label: 'По названию на языке перевода', value: '2' }, + { label: 'По дате создания', value: '3' }, + { label: 'По дате последней активности', value: '4' }, + { label: 'По просмотрам', value: '5' }, + { label: 'По кол-ву переведённых глав', value: '7' }, + { label: 'По кол-ву лайков', value: '8' }, + { label: 'По кол-ву страниц', value: '10' }, + { label: 'По кол-ву бесплатных глав', value: '11' }, + { label: 'По кол-ву рецензий', value: '12' }, + { label: 'По кол-ву в закладках', value: '13' }, + { label: 'По кол-ву в избранном', value: '14' }, + ], + inputType: FilterInputs.Picker, + }, + { + key: 'type', + label: 'Тип', + values: [ + { label: 'Неважно', value: '0' }, + { label: 'Только переводы', value: '1' }, + { label: 'Только авторские', value: '2' }, + ], + inputType: FilterInputs.Picker, + }, + { + key: 'atmosphere', + label: 'Атмосфера', + values: [ + { label: 'Неважно', value: '0' }, + { label: 'Позитивная', value: '1' }, + { label: 'Dark', value: '2' }, + ], + inputType: FilterInputs.Picker, + }, + { + key: 'trash', + label: 'Другое', + values: [ + { label: 'Готовые на 100%', value: 'ready' }, + { label: 'Доступные для перевода', value: 'tr' }, + { label: 'Доступные для скачивания', value: 'gen' }, + { label: 'Завершённые проекты', value: 'wealth' }, + { label: 'Распродажа', value: 'discount' }, + { label: 'Только онгоинги', value: 'ongoings' }, + { label: 'Убрать машинный', value: 'remove_machinelate' }, + ], + inputType: FilterInputs.Checkbox, + }, + ...customFilters, + { + key: 'adult', + label: 'Возрастные ограничения', + values: [ + { label: 'Все', value: '0' }, + { label: 'Убрать 18+', value: '1' }, + { label: 'Только 18+', value: '2' }, + ], + inputType: FilterInputs.Picker, + }, + ]; + + export const popularNovels: Plugin.popularNovels = async function ( + page, + { filters, showLatestNovels } + ) { + let url = baseUrl + '/search?t=&cat=2'; + url += '&sort=' + showLatestNovels ? '4' : filters?.sort || '6'; + url += '&type=' + filters?.type || '0'; + url += '&atmosphere=' + filters?.atmosphere || '0'; + url += '&adult=' + filters?.adult || '0'; + + if (filters?.genres instanceof Array) { + url += filters.genres.map(i => '&genres[]='+i).join(''); + } + + if (filters?.tags instanceof Array) { + url += filters.tags.map(i => '&tags[]='+i).join(''); + } + + if (filters?.trash instanceof Array) { + url += filters.trash.map(i => '&' + i + '=1'); + } + + url += '&Book_page=' + page; + + const result = await fetchApi(url); + const body = await result.text(); + const loadedCheerio = parseHTML(body); + + const novels: Novel.Item[] = []; + + loadedCheerio( + 'ul[class="search-results"] > li:not([class="ad_type_catalog"])', + ).each(function () { + novels.push({ + name: loadedCheerio(this).find('p > a').text(), + cover: baseUrl + loadedCheerio(this).find('img').attr('src'), + url: baseUrl + loadedCheerio(this).find('p > a').attr('href') || '', + }); + }); + + return novels; + }; + + + export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const novel: Novel.instance = { + url: novelUrl, + chapters: [], + }; + let result = await fetchApi(novelUrl); + if (result.url.includes('mature?path=')) { + const formData = new FormData(); + formData.append('path', novelUrl); + formData.append('ok', 'Да'); + + await fetch(result.url, { + method: 'POST', + body: formData, + }); + + result = await fetchApi(novelUrl); + } + const body = await result.text(); + const loadedCheerio = parseHTML(body); + + novel.name = loadedCheerio('div[class="container"] > div > div > h1') + .text() + .trim(); + novel.cover = + baseUrl + loadedCheerio('div[class="images"] > div img').attr('src'); + novel.summary = loadedCheerio('#Info > div:nth-child(3)').text(); + let genres: string[] = []; + + loadedCheerio('div[class="span5"] > p').each(function () { + switch (loadedCheerio(this).find('strong').text()) { + case 'Автор:': + novel.author = loadedCheerio(this).find('em > a').text().trim(); + break; + case 'Выпуск:': + novel.status = + loadedCheerio(this).find('em').text().trim() === 'продолжается' + ? NovelStatus.Ongoing + : NovelStatus.Completed; + break; + case 'Тэги:': + loadedCheerio(this) + .find('em > a') + .each(function () { + genres.push(loadedCheerio(this).text()); + }); + break; + case 'Жанры:': + loadedCheerio(this) + .find('em > a') + .each(function () { + genres.push(loadedCheerio(this).text()); + }); + break; + } + }); + + if (genres.length > 0) { + novel.genres = genres.reverse().join(','); + } + + const chapters: Chapter.Item[] = []; + loadedCheerio('table > tbody > tr.chapter_row').each(function () { + const chapterName = loadedCheerio(this) + .find('td[class="t"] > a') + .text() + .trim(); + const releaseDate = loadedCheerio(this) + .find('td > span') + .attr('title') + ?.trim(); + const chapterUrl = baseUrl + loadedCheerio(this) + .find('td[class="t"] > a') + .attr('href'); + + if ( + loadedCheerio(this).find('td > span[class="disabled"]').length < 1 && + releaseDate + ) { + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + } + }); + + novel.chapters = chapters; + return novel; + }; + + export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + let result = await fetchApi(chapterUrl); + if (result.url.includes('mature?path=')) { + const formData = new FormData(); + formData.append('ok', 'Да'); + + await fetch(result.url, { + method: 'POST', + body: formData, + }); + + result = await fetchApi(chapterUrl); + } + const body = await result.text(); + const loadedCheerio = parseHTML(body); + + + const chapterText = loadedCheerio('.content-text').html(); + + + return chapterText; + }; + + export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + const result = await fetchApi( + baseUrl + '/search/autocomplete?query=' + searchTerm, + ); + let json: any = await result.json(); + + json.forEach((item: any) => { + const novelName = item.title_one + ' / ' + item.title_two; + const novelCover = baseUrl + item.img; + const novelUrl = baseUrl + item.url; + + novels.push({ name: novelName, cover: novelCover, url: novelUrl }); + }); + + return novels; + }; + + export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url, {}); + }; + + ` + return { + lang: "Russian", + filename: souceName, + pluginScript, + }; +}; diff --git a/scripts/multisrc/rulate/sources.json b/scripts/multisrc/rulate/sources.json new file mode 100644 index 0000000..7837ac2 --- /dev/null +++ b/scripts/multisrc/rulate/sources.json @@ -0,0 +1,6388 @@ +[ + { + "id": "tl.rulate", + "sourceName": "Rulate", + "sourceSite": "https://tl.rulate.ru", + "filters": [ + { + "key": "genres", + "label": "Жанры", + "values": [ + { + "label": "18+", + "value": "52" + }, + { + "label": "Арт", + "value": "1" + }, + { + "label": "Боевик", + "value": "2" + }, + { + "label": "Боевые искусства", + "value": "3" + }, + { + "label": "Гарем", + "value": "5" + }, + { + "label": "Героическое фэнтези", + "value": "7" + }, + { + "label": "Детектив", + "value": "8" + }, + { + "label": "Дзёсэй", + "value": "9" + }, + { + "label": "Додзинси", + "value": "10" + }, + { + "label": "Драма", + "value": "11" + }, + { + "label": "История", + "value": "13" + }, + { + "label": "Киберпанк", + "value": "46" + }, + { + "label": "Кодомо", + "value": "14" + }, + { + "label": "Комедия", + "value": "15" + }, + { + "label": "Литрпг", + "value": "48" + }, + { + "label": "Махо-сёдзё", + "value": "16" + }, + { + "label": "Мелодрама", + "value": "49" + }, + { + "label": "Меха", + "value": "17" + }, + { + "label": "Мистика", + "value": "18" + }, + { + "label": "Научная фантастика", + "value": "19" + }, + { + "label": "Повседневность", + "value": "20" + }, + { + "label": "Постапокалиптика", + "value": "21" + }, + { + "label": "Приключения", + "value": "22" + }, + { + "label": "Психология", + "value": "23" + }, + { + "label": "Романтика", + "value": "24" + }, + { + "label": "Самурайский боевик", + "value": "25" + }, + { + "label": "Сверхъестественное", + "value": "26" + }, + { + "label": "Сёдзё", + "value": "27" + }, + { + "label": "Сёнэн", + "value": "29" + }, + { + "label": "Смат", + "value": "45" + }, + { + "label": "Спорт", + "value": "31" + }, + { + "label": "Сэйнэн", + "value": "32" + }, + { + "label": "Сюаньхуа", + "value": "44" + }, + { + "label": "Сюаньхуань", + "value": "47" + }, + { + "label": "Сянься (XianXia)", + "value": "42" + }, + { + "label": "Трагедия", + "value": "33" + }, + { + "label": "Триллер", + "value": "34" + }, + { + "label": "Ужасы", + "value": "35" + }, + { + "label": "Уся (wuxia)", + "value": "41" + }, + { + "label": "Фантастика", + "value": "36" + }, + { + "label": "Фанфик", + "value": "50" + }, + { + "label": "Фэнтези", + "value": "37" + }, + { + "label": "Школа", + "value": "38" + }, + { + "label": "Этти", + "value": "39" + }, + { + "label": "Bl", + "value": "43" + }, + { + "label": "Gl", + "value": "40" + } + ], + "inputType": "Checkbox" + }, + { + "key": "tags", + "label": "Тэги", + "values": [ + { + "label": "12+", + "value": "3316" + }, + { + "label": "16+", + "value": "83" + }, + { + "label": "18+", + "value": "1749" + }, + { + "label": "21+", + "value": "3191" + }, + { + "label": "Абсурд", + "value": "7973" + }, + { + "label": "Авантюристы", + "value": "222" + }, + { + "label": "Автомат", + "value": "5272" + }, + { + "label": "Авторский мир", + "value": "1062" + }, + { + "label": "Агрессивные персонажи", + "value": "3249" + }, + { + "label": "Ад", + "value": "2623" + }, + { + "label": "Ад и рай", + "value": "817" + }, + { + "label": "Адам дженсен", + "value": "1649" + }, + { + "label": "Адаптация произведения", + "value": "192" + }, + { + "label": "Адвокат", + "value": "6340" + }, + { + "label": "Адекватные главные герои", + "value": "1953" + }, + { + "label": "Азартные игры", + "value": "5301" + }, + { + "label": "Азгард", + "value": "3494" + }, + { + "label": "Азула", + "value": "5550" + }, + { + "label": "Аид", + "value": "7641" + }, + { + "label": "Айдолы", + "value": "4081" + }, + { + "label": "Академия", + "value": "316" + }, + { + "label": "Акамэ", + "value": "5059" + }, + { + "label": "Аквакинез", + "value": "5952" + }, + { + "label": "Акира", + "value": "2639" + }, + { + "label": "Акнология", + "value": "4634" + }, + { + "label": "Актерское мастерство", + "value": "4798" + }, + { + "label": "Актёры", + "value": "7506" + }, + { + "label": "Активные герои", + "value": "6376" + }, + { + "label": "Акутагава рюноске", + "value": "4113" + }, + { + "label": "Аладдин", + "value": "6489" + }, + { + "label": "Алая ведьма", + "value": "4767" + }, + { + "label": "Алиса", + "value": "4761" + }, + { + "label": "Алкоголь", + "value": "5508" + }, + { + "label": "Аллегория", + "value": "271" + }, + { + "label": "Алукард", + "value": "4914" + }, + { + "label": "Алхимики", + "value": "1164" + }, + { + "label": "Алхимия", + "value": "1473" + }, + { + "label": "Алчность", + "value": "6897" + }, + { + "label": "Альбус дамблдор", + "value": "5784" + }, + { + "label": "Альтернативное развитие событий", + "value": "5330" + }, + { + "label": "Альянсы", + "value": "328" + }, + { + "label": "Амбициозный главный герой", + "value": "3751" + }, + { + "label": "Америка", + "value": "4082" + }, + { + "label": "Амнезия", + "value": "2207" + }, + { + "label": "Аморальный герой", + "value": "18" + }, + { + "label": "Анальный секс", + "value": "1942" + }, + { + "label": "Анбу", + "value": "8129" + }, + { + "label": "Ангелы", + "value": "857" + }, + { + "label": "Ангелы и демоны", + "value": "1327" + }, + { + "label": "Ангст", + "value": "4638" + }, + { + "label": "Андроиды", + "value": "1993" + }, + { + "label": "Анимализм", + "value": "5249" + }, + { + "label": "Аниме", + "value": "1294" + }, + { + "label": "Анко митараши", + "value": "5768" + }, + { + "label": "Антагонист", + "value": "2144" + }, + { + "label": "Антигерой", + "value": "7725" + }, + { + "label": "Антиквариат", + "value": "3552" + }, + { + "label": "Антимаг", + "value": "1348" + }, + { + "label": "Антиутопия", + "value": "662" + }, + { + "label": "Апокалипсис", + "value": "1498" + }, + { + "label": "Аристократия", + "value": "371" + }, + { + "label": "Аристократы", + "value": "7338" + }, + { + "label": "Армия", + "value": "1723" + }, + { + "label": "Артас", + "value": "6700" + }, + { + "label": "Артефакты", + "value": "2296" + }, + { + "label": "Археология", + "value": "6523" + }, + { + "label": "Асгард", + "value": "3635" + }, + { + "label": "Ассасин", + "value": "1415" + }, + { + "label": "Асуна", + "value": "5044" + }, + { + "label": "Аугментации", + "value": "7751" + }, + { + "label": "Ацуши накаджима", + "value": "4109" + }, + { + "label": "Аянокоджи киетака", + "value": "6862" + }, + { + "label": "Бандиты", + "value": "2188" + }, + { + "label": "Басейн", + "value": "6847" + }, + { + "label": "Баскетбол", + "value": "7933" + }, + { + "label": "Бастарды", + "value": "76" + }, + { + "label": "Бдсм", + "value": "444" + }, + { + "label": "Бедность", + "value": "1207" + }, + { + "label": "Без культивации", + "value": "668" + }, + { + "label": "Без любовной линий", + "value": "2225" + }, + { + "label": "Без перерождений", + "value": "262" + }, + { + "label": "Без попаданца", + "value": "8065" + }, + { + "label": "Без системы", + "value": "747" + }, + { + "label": "Без цензуры", + "value": "7525" + }, + { + "label": "Без юмора", + "value": "763" + }, + { + "label": "Бездна", + "value": "1026" + }, + { + "label": "Безжалостное домашнее животное", + "value": "1245" + }, + { + "label": "Безжалостные персонажи", + "value": "2986" + }, + { + "label": "Беззаботные персонажи ", + "value": "3123" + }, + { + "label": "Безответная любовь", + "value": "7815" + }, + { + "label": "Безработный", + "value": "2202" + }, + { + "label": "Безумие", + "value": "37" + }, + { + "label": "Безумные персонажи", + "value": "2049" + }, + { + "label": "Бейсбол", + "value": "7623" + }, + { + "label": "Беременность", + "value": "2122" + }, + { + "label": "Бесконечный поток", + "value": "7556" + }, + { + "label": "Беспечные персонажи", + "value": "742" + }, + { + "label": "Бесплатно", + "value": "8403" + }, + { + "label": "Бессмертные персонажи", + "value": "566" + }, + { + "label": "Бесстрашные персонажи", + "value": "6921" + }, + { + "label": "Бесстыдные персонажи", + "value": "1257" + }, + { + "label": "Бесстыдный главный герой", + "value": "8190" + }, + { + "label": "Библиотека", + "value": "4210" + }, + { + "label": "Библия", + "value": "488" + }, + { + "label": "Бизнес", + "value": "704" + }, + { + "label": "Биология", + "value": "2220" + }, + { + "label": "Биомехи", + "value": "1968" + }, + { + "label": "Биседзе", + "value": "777" + }, + { + "label": "Битва за веру", + "value": "721" + }, + { + "label": "Битва за трон", + "value": "7211" + }, + { + "label": "Битва королевств", + "value": "1151" + }, + { + "label": "Битва рас", + "value": "6989" + }, + { + "label": "Битвы", + "value": "1740" + }, + { + "label": "Благородные персонажи", + "value": "2201" + }, + { + "label": "Близнецы", + "value": "2931" + }, + { + "label": "Богатство", + "value": "6522" + }, + { + "label": "Богатые персонажи", + "value": "520" + }, + { + "label": "Боги", + "value": "173" + }, + { + "label": "Богини", + "value": "30" + }, + { + "label": "Богиня", + "value": "8324" + }, + { + "label": "Богоубийца", + "value": "1253" + }, + { + "label": "Богохульство", + "value": "95" + }, + { + "label": "Боевая академия", + "value": "2108" + }, + { + "label": "Боевик", + "value": "529" + }, + { + "label": "Боевые искусства", + "value": "2090" + }, + { + "label": "Большая грудь", + "value": "4717" + }, + { + "label": "Большой член", + "value": "5299" + }, + { + "label": "Борос", + "value": "6533" + }, + { + "label": "Борьба", + "value": "1925" + }, + { + "label": "Борьба за власть", + "value": "8072" + }, + { + "label": "Босс и подчиненный", + "value": "7157" + }, + { + "label": "Бояръ", + "value": "2307" + }, + { + "label": "Брак", + "value": "1558" + }, + { + "label": "Брак по расчету", + "value": "1389" + }, + { + "label": "Брат и сестра", + "value": "615" + }, + { + "label": "Братский комплекс", + "value": "2321" + }, + { + "label": "Броманс", + "value": "1103" + }, + { + "label": "Брошенные дети", + "value": "2846" + }, + { + "label": "Буддизм", + "value": "1871" + }, + { + "label": "Будущее", + "value": "1820" + }, + { + "label": "Бывший муж", + "value": "8080" + }, + { + "label": "Быстрое развитие", + "value": "1443" + }, + { + "label": "Бэтгерл", + "value": "2584" + }, + { + "label": "Бэтмен", + "value": "4668" + }, + { + "label": "В первый раз", + "value": "6212" + }, + { + "label": "В этот же мир", + "value": "49" + }, + { + "label": "Вагинальный секс", + "value": "4742" + }, + { + "label": "Ваканда", + "value": "8331" + }, + { + "label": "Валькирии", + "value": "4047" + }, + { + "label": "Вампиры", + "value": "2206" + }, + { + "label": "Веб камера", + "value": "5664" + }, + { + "label": "Веб новелла", + "value": "2349" + }, + { + "label": "Ведьмак", + "value": "843" + }, + { + "label": "Ведьмы", + "value": "643" + }, + { + "label": "Везучие персонажи", + "value": "1879" + }, + { + "label": "Веном", + "value": "3994" + }, + { + "label": "Вестерн", + "value": "2131" + }, + { + "label": "Вестерос", + "value": "8171" + }, + { + "label": "Взросление", + "value": "624" + }, + { + "label": "Взрослый главный герой", + "value": "2416" + }, + { + "label": "Видео игры", + "value": "2630" + }, + { + "label": "Викинги", + "value": "267" + }, + { + "label": "Вино", + "value": "6972" + }, + { + "label": "Вирт", + "value": "6213" + }, + { + "label": "Виртуальная реальность", + "value": "325" + }, + { + "label": "Вирусы", + "value": "3327" + }, + { + "label": "Владыка демонов", + "value": "555" + }, + { + "label": "Власть", + "value": "756" + }, + { + "label": "Влюбленность", + "value": "6132" + }, + { + "label": "Внешний вид отличается от личности", + "value": "1938" + }, + { + "label": "Внучка", + "value": "8399" + }, + { + "label": "Военные", + "value": "1096" + }, + { + "label": "Возрождение", + "value": "1160" + }, + { + "label": "Воины", + "value": "287" + }, + { + "label": "Война", + "value": "443" + }, + { + "label": "Войны", + "value": "2431" + }, + { + "label": "Воландеморт", + "value": "4278" + }, + { + "label": "Волейбол", + "value": "6415" + }, + { + "label": "Волки", + "value": "1166" + }, + { + "label": "Волшебники", + "value": "122" + }, + { + "label": "Волшебные существа", + "value": "1689" + }, + { + "label": "Волшебство", + "value": "719" + }, + { + "label": "Вор", + "value": "1399" + }, + { + "label": "Воровка", + "value": "6948" + }, + { + "label": "Ворон", + "value": "848" + }, + { + "label": "Воскрешение", + "value": "6915" + }, + { + "label": "Воспоминания из другого мира", + "value": "1366" + }, + { + "label": "Воспоминания из прошлого", + "value": "1372" + }, + { + "label": "Воссоединие семьи", + "value": "8130" + }, + { + "label": "Восхождение", + "value": "380" + }, + { + "label": "Враги становятся любовниками", + "value": "978" + }, + { + "label": "Врата", + "value": "40" + }, + { + "label": "Врата в другой мир", + "value": "1957" + }, + { + "label": "Врач", + "value": "1243" + }, + { + "label": "Временная петля", + "value": "2852" + }, + { + "label": "Врмморпг", + "value": "2043" + }, + { + "label": "Все дозволено", + "value": "96" + }, + { + "label": "Всемогущий главный герой", + "value": "1414" + }, + { + "label": "Второй шанс", + "value": "2372" + }, + { + "label": "Вуайеризм", + "value": "2498" + }, + { + "label": "Выживание", + "value": "501" + }, + { + "label": "Вымышленные существа", + "value": "3466" + }, + { + "label": "Вынужденный брак", + "value": "7705" + }, + { + "label": "Высокие технологии", + "value": "653" + }, + { + "label": "Высокомерные персонажи", + "value": "3250" + }, + { + "label": "Галустян", + "value": "5199" + }, + { + "label": "Гангстеры", + "value": "7704" + }, + { + "label": "Гарем", + "value": "440" + }, + { + "label": "Гарри поттер", + "value": "226" + }, + { + "label": "Геймеры", + "value": "2130" + }, + { + "label": "Генетические модификации", + "value": "1750" + }, + { + "label": "Гениальный главный герой", + "value": "1500" + }, + { + "label": "Гений", + "value": "1650" + }, + { + "label": "Гермиона грейнджер", + "value": "4280" + }, + { + "label": "Герои", + "value": "185" + }, + { + "label": "Героическое фэнтези", + "value": "1148" + }, + { + "label": "Гигаполис", + "value": "8393" + }, + { + "label": "Гильгамеш", + "value": "6031" + }, + { + "label": "Гильдии", + "value": "900" + }, + { + "label": "Гильдия авантюристов", + "value": "1858" + }, + { + "label": "Гинтама", + "value": "4544" + }, + { + "label": "Гипноз", + "value": "358" + }, + { + "label": "Главная героиня влюбляется первой", + "value": "973" + }, + { + "label": "Главный герой бог", + "value": "164" + }, + { + "label": "Главный герой влюбляется первым", + "value": "3629" + }, + { + "label": "Главный герой выживальщик", + "value": "120" + }, + { + "label": "Главный герой гонг", + "value": "7877" + }, + { + "label": "Главный герой девушка", + "value": "257" + }, + { + "label": "Главный герой демон", + "value": "136" + }, + { + "label": "Главный герой женщина", + "value": "7329" + }, + { + "label": "Главный герой жестокий", + "value": "1553" + }, + { + "label": "Главный герой извращенец", + "value": "94" + }, + { + "label": "Главный герой имба", + "value": "2421" + }, + { + "label": "Главный герой киборг", + "value": "7780" + }, + { + "label": "Главный герой монстр", + "value": "869" + }, + { + "label": "Главный герой мужчина", + "value": "1997" + }, + { + "label": "Главный герой на стороне сил зла", + "value": "1514" + }, + { + "label": "Главный герой не адекватный", + "value": "5" + }, + { + "label": "Главный герой не психопат", + "value": "7804" + }, + { + "label": "Главный герой не человек", + "value": "2172" + }, + { + "label": "Главный герой не эмоционален", + "value": "1150" + }, + { + "label": "Главный герой подросток", + "value": "2215" + }, + { + "label": "Главный герой позитивный", + "value": "7787" + }, + { + "label": "Главный герой психопат", + "value": "7803" + }, + { + "label": "Главный герой ребенок", + "value": "2347" + }, + { + "label": "Главный герой русский", + "value": "2417" + }, + { + "label": "Главный герой сильный с самого начала", + "value": "88" + }, + { + "label": "Главный герой скрывает свою силу", + "value": "1059" + }, + { + "label": "Главный герой эгоист", + "value": "8326" + }, + { + "label": "Глотание спермы", + "value": "4491" + }, + { + "label": "Гномы", + "value": "1870" + }, + { + "label": "Гоблины", + "value": "2029" + }, + { + "label": "Годзилла", + "value": "6990" + }, + { + "label": "Големы", + "value": "1690" + }, + { + "label": "Голливуд", + "value": "7754" + }, + { + "label": "Головоломка", + "value": "1697" + }, + { + "label": "Горничные", + "value": "8091" + }, + { + "label": "Гробница", + "value": "388" + }, + { + "label": "Групповой секс", + "value": "1314" + }, + { + "label": "Дазай осаму", + "value": "4112" + }, + { + "label": "Даньмэй", + "value": "3411" + }, + { + "label": "Даосизм", + "value": "258" + }, + { + "label": "Дарк соулс", + "value": "6057" + }, + { + "label": "Дарт вейдер", + "value": "6826" + }, + { + "label": "Дафна гринграсс", + "value": "5192" + }, + { + "label": "Дворяне", + "value": "7204" + }, + { + "label": "Девушки", + "value": "7668" + }, + { + "label": "Девушки монстры", + "value": "2231" + }, + { + "label": "Дедушка и внучка", + "value": "8400" + }, + { + "label": "Дейви джонс", + "value": "8061" + }, + { + "label": "Действие", + "value": "1425" + }, + { + "label": "Демон лорд", + "value": "153" + }, + { + "label": "Демонология", + "value": "2024" + }, + { + "label": "Демоны", + "value": "1406" + }, + { + "label": "Детектив", + "value": "1265" + }, + { + "label": "Дети", + "value": "580" + }, + { + "label": "Джарвис", + "value": "933" + }, + { + "label": "Джедай", + "value": "4442" + }, + { + "label": "Джин", + "value": "4024" + }, + { + "label": "Джокер", + "value": "3351" + }, + { + "label": "Джон сноу", + "value": "7412" + }, + { + "label": "Джоффри баратеон", + "value": "3936" + }, + { + "label": "Дзесэй", + "value": "678" + }, + { + "label": "Дин винчестер", + "value": "6953" + }, + { + "label": "Династии", + "value": "2982" + }, + { + "label": "Динозавры", + "value": "3666" + }, + { + "label": "Директор", + "value": "1417" + }, + { + "label": "Для девушек", + "value": "1441" + }, + { + "label": "Дневник", + "value": "298" + }, + { + "label": "Добби", + "value": "6762" + }, + { + "label": "Добрый мир", + "value": "1685" + }, + { + "label": "Договоры с богами", + "value": "2152" + }, + { + "label": "Договоры с демонами", + "value": "1922" + }, + { + "label": "Доктор", + "value": "1991" + }, + { + "label": "Доктор стрэндж", + "value": "7634" + }, + { + "label": "Доминирование", + "value": "2054" + }, + { + "label": "Дота", + "value": "6965" + }, + { + "label": "Доудзюцу", + "value": "7133" + }, + { + "label": "Дочь", + "value": "3291" + }, + { + "label": "Драко малфой", + "value": "46" + }, + { + "label": "Драконы", + "value": "282" + }, + { + "label": "Драконы оборотни", + "value": "8082" + }, + { + "label": "Драма", + "value": "611" + }, + { + "label": "Древние времена", + "value": "7781" + }, + { + "label": "Древний египет", + "value": "7614" + }, + { + "label": "Древний китай", + "value": "473" + }, + { + "label": "Древний мир", + "value": "4" + }, + { + "label": "Дрочка", + "value": "4565" + }, + { + "label": "Другая вселенная", + "value": "39" + }, + { + "label": "Другие планеты", + "value": "2371" + }, + { + "label": "Другой мир", + "value": "2248" + }, + { + "label": "Дружба", + "value": "1903" + }, + { + "label": "Друзья детства", + "value": "1153" + }, + { + "label": "Духи", + "value": "114" + }, + { + "label": "Духовные силы", + "value": "1955" + }, + { + "label": "Души", + "value": "1212" + }, + { + "label": "Дьявол", + "value": "1094" + }, + { + "label": "Дьявольские фрукты", + "value": "1564" + }, + { + "label": "Дэдпул", + "value": "4399" + }, + { + "label": "Евнух", + "value": "6293" + }, + { + "label": "Европейская атмосфера", + "value": "5837" + }, + { + "label": "Египет", + "value": "528" + }, + { + "label": "Еда", + "value": "2056" + }, + { + "label": "Единороги", + "value": "437" + }, + { + "label": "Екаи", + "value": "655" + }, + { + "label": "Жалкий главный герой", + "value": "97" + }, + { + "label": "Жена", + "value": "5729" + }, + { + "label": "Женское доминирование", + "value": "4622" + }, + { + "label": "Женщина в теле мужчины", + "value": "2748" + }, + { + "label": "Женщина кошка", + "value": "7513" + }, + { + "label": "Жесткий секс", + "value": "2238" + }, + { + "label": "Жестокие персонажи ", + "value": "3153" + }, + { + "label": "Жестокий мир", + "value": "1532" + }, + { + "label": "Жестокое обращение с детьми", + "value": "84" + }, + { + "label": "Жестокость", + "value": "7880" + }, + { + "label": "Животные", + "value": "7488" + }, + { + "label": "Животные компаньоны", + "value": "2061" + }, + { + "label": "Жизнь и смерть", + "value": "2014" + }, + { + "label": "Жнец", + "value": "6288" + }, + { + "label": "Жойен рид", + "value": "3926" + }, + { + "label": "Заботливый главный герой", + "value": "338" + }, + { + "label": "Закалка тела", + "value": "148" + }, + { + "label": "Заклинания", + "value": "558" + }, + { + "label": "Заключенные", + "value": "3052" + }, + { + "label": "Занпакто", + "value": "1102" + }, + { + "label": "Запретная любовь", + "value": "8033" + }, + { + "label": "Запутанный сюжет", + "value": "1041" + }, + { + "label": "Захват мира", + "value": "315" + }, + { + "label": "Зачарованные", + "value": "4169" + }, + { + "label": "Звери", + "value": "1638" + }, + { + "label": "Зверодевочки", + "value": "2006" + }, + { + "label": "Зверолюди", + "value": "1018" + }, + { + "label": "Земля", + "value": "82" + }, + { + "label": "Злая организация", + "value": "7768" + }, + { + "label": "Зло", + "value": "1512" + }, + { + "label": "Злодей", + "value": "1060" + }, + { + "label": "Злодейка", + "value": "2376" + }, + { + "label": "Злой главный герой", + "value": "616" + }, + { + "label": "Злые боги", + "value": "7109" + }, + { + "label": "Злые персонажи", + "value": "3243" + }, + { + "label": "Змей", + "value": "5721" + }, + { + "label": "Знаменитости", + "value": "1341" + }, + { + "label": "Знание медицины", + "value": "1411" + }, + { + "label": "Золотой дождь", + "value": "1828" + }, + { + "label": "Золушка", + "value": "2980" + }, + { + "label": "Зомби", + "value": "1743" + }, + { + "label": "Зомби апокалипсис", + "value": "912" + }, + { + "label": "Зона 51", + "value": "3051" + }, + { + "label": "Зооморфы", + "value": "375" + }, + { + "label": "Зоофилия", + "value": "1165" + }, + { + "label": "Зорро", + "value": "6736" + }, + { + "label": "Зрелые женщины", + "value": "1458" + }, + { + "label": "Игра", + "value": "663" + }, + { + "label": "Игра на выживание", + "value": "2182" + }, + { + "label": "Игровая система", + "value": "384" + }, + { + "label": "Игровые элементы", + "value": "720" + }, + { + "label": "Извращения", + "value": "651" + }, + { + "label": "Измена", + "value": "1874" + }, + { + "label": "Изменение характера", + "value": "1113" + }, + { + "label": "Изменения внешности", + "value": "259" + }, + { + "label": "Изменения личности", + "value": "1080" + }, + { + "label": "Изнасилование", + "value": "1283" + }, + { + "label": "Изобретения", + "value": "126" + }, + { + "label": "Изуку мидория", + "value": "6967" + }, + { + "label": "Император", + "value": "867" + }, + { + "label": "Империи", + "value": "712" + }, + { + "label": "Империя", + "value": "1716" + }, + { + "label": "Инвалидность", + "value": "2073" + }, + { + "label": "Инквизитор", + "value": "143" + }, + { + "label": "Инкуб", + "value": "3264" + }, + { + "label": "Инопланетное вторжение", + "value": "209" + }, + { + "label": "Инопланетяне", + "value": "866" + }, + { + "label": "Интерактив с читателями", + "value": "472" + }, + { + "label": "Интересный сюжет", + "value": "7374" + }, + { + "label": "Интернет", + "value": "739" + }, + { + "label": "Интимные сцены", + "value": "3450" + }, + { + "label": "Интрига", + "value": "317" + }, + { + "label": "Интриги и заговоры", + "value": "1009" + }, + { + "label": "Инцест", + "value": "1934" + }, + { + "label": "Иные миры", + "value": "35" + }, + { + "label": "Исекай", + "value": "2979" + }, + { + "label": "Искусственный интеллект", + "value": "1900" + }, + { + "label": "Исследование", + "value": "1626" + }, + { + "label": "Исторический роман", + "value": "573" + }, + { + "label": "Итачи учиха", + "value": "4556" + }, + { + "label": "Ичиго", + "value": "7254" + }, + { + "label": "Йода", + "value": "6829" + }, + { + "label": "Йоруичи", + "value": "6755" + }, + { + "label": "Калипсо", + "value": "8062" + }, + { + "label": "Каннибализм", + "value": "2249" + }, + { + "label": "Капитан америка", + "value": "6301" + }, + { + "label": "Каракура", + "value": "7473" + }, + { + "label": "Карточная игра", + "value": "1557" + }, + { + "label": "Кастиэль", + "value": "6954" + }, + { + "label": "Кафе", + "value": "3934" + }, + { + "label": "Кацуки бакуго", + "value": "7696" + }, + { + "label": "Квесты", + "value": "1707" + }, + { + "label": "Киберпанк", + "value": "1614" + }, + { + "label": "Киберспорт", + "value": "1984" + }, + { + "label": "Кино", + "value": "7126" + }, + { + "label": "Китай", + "value": "3762" + }, + { + "label": "Кицунэ", + "value": "241" + }, + { + "label": "Кланы", + "value": "834" + }, + { + "label": "Клоны", + "value": "1624" + }, + { + "label": "Книги", + "value": "1068" + }, + { + "label": "Книжный червь", + "value": "521" + }, + { + "label": "Колдовство", + "value": "2681" + }, + { + "label": "Колледж", + "value": "5058" + }, + { + "label": "Коллекционер", + "value": "2588" + }, + { + "label": "Кольцо", + "value": "1628" + }, + { + "label": "Комедия", + "value": "1430" + }, + { + "label": "Коммунисты", + "value": "1042" + }, + { + "label": "Коноха", + "value": "2827" + }, + { + "label": "Контракт", + "value": "4304" + }, + { + "label": "Контроль разума", + "value": "485" + }, + { + "label": "Конфеты", + "value": "341" + }, + { + "label": "Кончил внутрь", + "value": "4589" + }, + { + "label": "Копрофилия", + "value": "2568" + }, + { + "label": "Корейцы", + "value": "6535" + }, + { + "label": "Корея", + "value": "2443" + }, + { + "label": "Королевская власть", + "value": "55" + }, + { + "label": "Королевская семья", + "value": "276" + }, + { + "label": "Королевство", + "value": "1695" + }, + { + "label": "Короткий рассказ", + "value": "239" + }, + { + "label": "Коррупция", + "value": "831" + }, + { + "label": "Космическая опера", + "value": "2245" + }, + { + "label": "Космические рейнджеры", + "value": "5860" + }, + { + "label": "Космос", + "value": "329" + }, + { + "label": "Кот", + "value": "5171" + }, + { + "label": "Кража навыков", + "value": "1159" + }, + { + "label": "Красивая главная героиня", + "value": "186" + }, + { + "label": "Красивые женщины", + "value": "66" + }, + { + "label": "Красивые персонажи ", + "value": "5787" + }, + { + "label": "Красивый главный герой", + "value": "895" + }, + { + "label": "Крафт", + "value": "1599" + }, + { + "label": "Кровь и расчлененка", + "value": "7752" + }, + { + "label": "Кроссовер", + "value": "75" + }, + { + "label": "Ксенофилия", + "value": "2265" + }, + { + "label": "Ктулху", + "value": "920" + }, + { + "label": "Кузнец", + "value": "853" + }, + { + "label": "Кукла", + "value": "3085" + }, + { + "label": "Кулинария", + "value": "2230" + }, + { + "label": "Культивация", + "value": "1901" + }, + { + "label": "Культивирование", + "value": "1860" + }, + { + "label": "Кунилингус", + "value": "4651" + }, + { + "label": "Курама", + "value": "5709" + }, + { + "label": "Курилин", + "value": "6134" + }, + { + "label": "Кушина узумаки", + "value": "5222" + }, + { + "label": "Лавкрафт", + "value": "6063" + }, + { + "label": "Лара крофт", + "value": "6435" + }, + { + "label": "Легенда о зельде", + "value": "5707" + }, + { + "label": "Ленивый главный герой", + "value": "1516" + }, + { + "label": "Леон золдик", + "value": "7469" + }, + { + "label": "Лес", + "value": "6645" + }, + { + "label": "Лечебная магия", + "value": "562" + }, + { + "label": "Литрпг", + "value": "505" + }, + { + "label": "Локи", + "value": "1725" + }, + { + "label": "Лоли", + "value": "1576" + }, + { + "label": "Лопата", + "value": "4989" + }, + { + "label": "Лорд", + "value": "8404" + }, + { + "label": "Лориен", + "value": "7397" + }, + { + "label": "Лошади", + "value": "6110" + }, + { + "label": "Лунатизм", + "value": "3520" + }, + { + "label": "Луффи", + "value": "274" + }, + { + "label": "Лучник", + "value": "1038" + }, + { + "label": "Любовный интерес влюбляется первым", + "value": "7753" + }, + { + "label": "Любовный треугольник", + "value": "2109" + }, + { + "label": "Любовь", + "value": "2297" + }, + { + "label": "Любовь с первого взгляда", + "value": "7748" + }, + { + "label": "Любящие родители", + "value": "2699" + }, + { + "label": "Люди", + "value": "3572" + }, + { + "label": "Люцифер", + "value": "5883" + }, + { + "label": "Маг", + "value": "780" + }, + { + "label": "Магазин", + "value": "4762" + }, + { + "label": "Маги", + "value": "58" + }, + { + "label": "Магическая академия", + "value": "6788" + }, + { + "label": "Магические звери", + "value": "684" + }, + { + "label": "Магические предметы", + "value": "1648" + }, + { + "label": "Магический мир", + "value": "578" + }, + { + "label": "Магия", + "value": "150" + }, + { + "label": "Магия воды", + "value": "6450" + }, + { + "label": "Магия воздуха", + "value": "7627" + }, + { + "label": "Магия земли", + "value": "7626" + }, + { + "label": "Магия и технология", + "value": "107" + }, + { + "label": "Магия крови", + "value": "7976" + }, + { + "label": "Магия льда", + "value": "1379" + }, + { + "label": "Магия огня", + "value": "7625" + }, + { + "label": "Магия призыва", + "value": "885" + }, + { + "label": "Магия природы", + "value": "6226" + }, + { + "label": "Магия стихий", + "value": "7673" + }, + { + "label": "Магия тьмы", + "value": "3596" + }, + { + "label": "Мадара", + "value": "2929" + }, + { + "label": "Мазохизм", + "value": "1961" + }, + { + "label": "Мама", + "value": "12" + }, + { + "label": "Мама и дочь", + "value": "7894" + }, + { + "label": "Мама и сын", + "value": "7895" + }, + { + "label": "Манга", + "value": "2464" + }, + { + "label": "Манипуляция временем", + "value": "932" + }, + { + "label": "Маньяк", + "value": "2026" + }, + { + "label": "Маргери тирелл", + "value": "3935" + }, + { + "label": "Марионетки", + "value": "3175" + }, + { + "label": "Марс", + "value": "4013" + }, + { + "label": "Марти стью", + "value": "594" + }, + { + "label": "Мастер на все руки", + "value": "3189" + }, + { + "label": "Мастурбация", + "value": "3078" + }, + { + "label": "Матриархат", + "value": "7197" + }, + { + "label": "Мать и сестры", + "value": "7946" + }, + { + "label": "Мафия", + "value": "353" + }, + { + "label": "Мачеха", + "value": "4664" + }, + { + "label": "Мегаполис", + "value": "3322" + }, + { + "label": "Медицина", + "value": "441" + }, + { + "label": "Медленная романтика", + "value": "8383" + }, + { + "label": "Медленное развитие истории", + "value": "7462" + }, + { + "label": "Межрасовый секс", + "value": "3489" + }, + { + "label": "Мелиодас", + "value": "7224" + }, + { + "label": "Мелодрама", + "value": "1507" + }, + { + "label": "Менеджмент", + "value": "368" + }, + { + "label": "Мерлин", + "value": "3977" + }, + { + "label": "Месть", + "value": "1218" + }, + { + "label": "Меха", + "value": "2303" + }, + { + "label": "Меч", + "value": "48" + }, + { + "label": "Мечники", + "value": "969" + }, + { + "label": "Мечта", + "value": "527" + }, + { + "label": "Мизуки", + "value": "4269" + }, + { + "label": "Микото", + "value": "3862" + }, + { + "label": "Милая главная героиня", + "value": "134" + }, + { + "label": "Милф", + "value": "1987" + }, + { + "label": "Милый главный герой", + "value": "2678" + }, + { + "label": "Мимик", + "value": "5706" + }, + { + "label": "Мина ашидо", + "value": "7695" + }, + { + "label": "Минато намикадзе", + "value": "5223" + }, + { + "label": "Минет", + "value": "3289" + }, + { + "label": "Мир будущего", + "value": "1770" + }, + { + "label": "Мир земли", + "value": "8394" + }, + { + "label": "Мир меча и магии", + "value": "1271" + }, + { + "label": "Мистер пропер", + "value": "565" + }, + { + "label": "Мистика", + "value": "510" + }, + { + "label": "Мифические существа", + "value": "1878" + }, + { + "label": "Мифы и легенды", + "value": "4463" + }, + { + "label": "Младшая сестра", + "value": "1523" + }, + { + "label": "Мморпг", + "value": "620" + }, + { + "label": "Много главных героев", + "value": "1191" + }, + { + "label": "Много крови", + "value": "5318" + }, + { + "label": "Много персонажей", + "value": "1539" + }, + { + "label": "Множество королевств", + "value": "32" + }, + { + "label": "Модерн", + "value": "5457" + }, + { + "label": "Мокутон", + "value": "4438" + }, + { + "label": "Молоко", + "value": "5469" + }, + { + "label": "Монархия", + "value": "3590" + }, + { + "label": "Монстры", + "value": "307" + }, + { + "label": "Море", + "value": "922" + }, + { + "label": "Моритика ринносукэ", + "value": "1142" + }, + { + "label": "Мортал комбат", + "value": "5861" + }, + { + "label": "Мошенник", + "value": "959" + }, + { + "label": "Мрачный мир", + "value": "1" + }, + { + "label": "Мужчина протагонист", + "value": "31" + }, + { + "label": "Музыка", + "value": "1183" + }, + { + "label": "Мультивселенная", + "value": "7410" + }, + { + "label": "Мурим", + "value": "7342" + }, + { + "label": "Мутанты", + "value": "2392" + }, + { + "label": "Мутация", + "value": "29" + }, + { + "label": "Мясо", + "value": "1503" + }, + { + "label": "На реальных событиях", + "value": "26" + }, + { + "label": "Навыки", + "value": "415" + }, + { + "label": "Нагато узумаки", + "value": "5221" + }, + { + "label": "Наемники", + "value": "1668" + }, + { + "label": "Назарик", + "value": "5369" + }, + { + "label": "Наивный главный герой", + "value": "159" + }, + { + "label": "Наложница", + "value": "4867" + }, + { + "label": "Наноботы", + "value": "2332" + }, + { + "label": "Наркотики", + "value": "1436" + }, + { + "label": "Наруто", + "value": "7177" + }, + { + "label": "Наруто и хината", + "value": "5766" + }, + { + "label": "Насилие и жестокость", + "value": "1440" + }, + { + "label": "Наука", + "value": "2150" + }, + { + "label": "Научная фантастика", + "value": "2244" + }, + { + "label": "Национализм", + "value": "5556" + }, + { + "label": "Нацу", + "value": "5826" + }, + { + "label": "Нацуки субару", + "value": "6656" + }, + { + "label": "Начальная школа", + "value": "3920" + }, + { + "label": "Наши дни", + "value": "1814" + }, + { + "label": "Не всесильный главный герой", + "value": "42" + }, + { + "label": "Не по канону", + "value": "1611" + }, + { + "label": "Неверность", + "value": "5735" + }, + { + "label": "Невеста", + "value": "6011" + }, + { + "label": "Неджи хьюга", + "value": "3099" + }, + { + "label": "Недооценённый главный герой", + "value": "43" + }, + { + "label": "Нежить", + "value": "1949" + }, + { + "label": "Некромаг", + "value": "7441" + }, + { + "label": "Некромант", + "value": "1914" + }, + { + "label": "Некромантия", + "value": "754" + }, + { + "label": "Некрофилия", + "value": "1285" + }, + { + "label": "Ненормативная лексика", + "value": "160" + }, + { + "label": "Необычные герои", + "value": "67" + }, + { + "label": "Нет людей", + "value": "7486" + }, + { + "label": "Нетораре", + "value": "403" + }, + { + "label": "Нетори", + "value": "5393" + }, + { + "label": "Нефилимы", + "value": "7764" + }, + { + "label": "Нецензурная лексика", + "value": "4912" + }, + { + "label": "Нечисть", + "value": "4559" + }, + { + "label": "Ник фьюри", + "value": "6304" + }, + { + "label": "Ниндзюцу", + "value": "2354" + }, + { + "label": "Ниндзя", + "value": "1330" + }, + { + "label": "Новелла", + "value": "2105" + }, + { + "label": "Нудизм", + "value": "2233" + }, + { + "label": "Нэко", + "value": "147" + }, + { + "label": "Оби ван кеноби", + "value": "6830" + }, + { + "label": "Обито учиха", + "value": "2814" + }, + { + "label": "Оборотни", + "value": "53" + }, + { + "label": "Обратный гарем", + "value": "3303" + }, + { + "label": "Овощ", + "value": "6098" + }, + { + "label": "Огнестрельное оружие", + "value": "3155" + }, + { + "label": "Одержимость", + "value": "708" + }, + { + "label": "Один в поле воин", + "value": "6498" + }, + { + "label": "Онлайн игра", + "value": "1565" + }, + { + "label": "Ооками теппей", + "value": "849" + }, + { + "label": "Оральный секс", + "value": "605" + }, + { + "label": "Оргия", + "value": "974" + }, + { + "label": "Оригинальные персонажи", + "value": "7741" + }, + { + "label": "Орки", + "value": "1597" + }, + { + "label": "Оружие", + "value": "125" + }, + { + "label": "Основано на видео игре", + "value": "602" + }, + { + "label": "Особые способности", + "value": "2904" + }, + { + "label": "От бедности к богатству", + "value": "901" + }, + { + "label": "От слабого до сильного", + "value": "118" + }, + { + "label": "Отель", + "value": "2879" + }, + { + "label": "Отец", + "value": "3290" + }, + { + "label": "Отношения", + "value": "398" + }, + { + "label": "Отношения человек/нечеловек", + "value": "7406" + }, + { + "label": "Офис", + "value": "3460" + }, + { + "label": "Охотники", + "value": "2263" + }, + { + "label": "Оцуцуки", + "value": "5101" + }, + { + "label": "Ошейник", + "value": "2362" + }, + { + "label": "Ояш", + "value": "8048" + }, + { + "label": "Падчерица", + "value": "7023" + }, + { + "label": "Падший ангел", + "value": "184" + }, + { + "label": "Пайзури", + "value": "3560" + }, + { + "label": "Палач", + "value": "852" + }, + { + "label": "Палочка", + "value": "2682" + }, + { + "label": "Пандорум", + "value": "4360" + }, + { + "label": "Папа и дочь", + "value": "7647" + }, + { + "label": "Паразит", + "value": "1213" + }, + { + "label": "Параллельный мир", + "value": "2092" + }, + { + "label": "Парень", + "value": "619" + }, + { + "label": "Паркур", + "value": "3391" + }, + { + "label": "Пародия", + "value": "898" + }, + { + "label": "Пасхалки", + "value": "455" + }, + { + "label": "Паук", + "value": "2209" + }, + { + "label": "Пекарь", + "value": "5073" + }, + { + "label": "Первая книга", + "value": "269" + }, + { + "label": "Первая любовь", + "value": "640" + }, + { + "label": "Первый раз", + "value": "1465" + }, + { + "label": "Переводы", + "value": "24" + }, + { + "label": "Перевоплощение", + "value": "1596" + }, + { + "label": "Перевоплощение в игровом мире", + "value": "92" + }, + { + "label": "Перемещение в другой мир", + "value": "1288" + }, + { + "label": "Перемещение во времени", + "value": "146" + }, + { + "label": "Перерождение", + "value": "894" + }, + { + "label": "Перерождение в демона", + "value": "1111" + }, + { + "label": "Перерождение в дракона", + "value": "6867" + }, + { + "label": "Перерождение в злодейку", + "value": "5619" + }, + { + "label": "Перерождение в злодея", + "value": "749" + }, + { + "label": "Перерождение в ином мире", + "value": "109" + }, + { + "label": "Перерождение в монстра", + "value": "917" + }, + { + "label": "Перерождений", + "value": "335" + }, + { + "label": "Переселение", + "value": "813" + }, + { + "label": "Переселение души", + "value": "1864" + }, + { + "label": "Перси джексон", + "value": "6329" + }, + { + "label": "Пилот", + "value": "5521" + }, + { + "label": "Пилюли", + "value": "1531" + }, + { + "label": "Пират", + "value": "1579" + }, + { + "label": "Пирог", + "value": "80" + }, + { + "label": "Писатель", + "value": "734" + }, + { + "label": "Пистолеты", + "value": "4916" + }, + { + "label": "Письмо", + "value": "840" + }, + { + "label": "Питомцы", + "value": "3122" + }, + { + "label": "Планомерное развитие событий", + "value": "62" + }, + { + "label": "Племянник", + "value": "8303" + }, + { + "label": "Пленница", + "value": "4897" + }, + { + "label": "Плети", + "value": "4839" + }, + { + "label": "Пляж", + "value": "4243" + }, + { + "label": "Повар", + "value": "658" + }, + { + "label": "Повествование от первого лица", + "value": "1054" + }, + { + "label": "Повествование от разных лиц", + "value": "600" + }, + { + "label": "Повествование от третьего лица", + "value": "2116" + }, + { + "label": "Повседневность", + "value": "802" + }, + { + "label": "Подавитель", + "value": "497" + }, + { + "label": "Подземелье", + "value": "798" + }, + { + "label": "Подростки", + "value": "636" + }, + { + "label": "Подруга", + "value": "5804" + }, + { + "label": "Подчинение", + "value": "8343" + }, + { + "label": "Подчинение и унижение", + "value": "4500" + }, + { + "label": "Покемоны", + "value": "2840" + }, + { + "label": "Полиамория", + "value": "1300" + }, + { + "label": "Полигамия", + "value": "1336" + }, + { + "label": "Политика", + "value": "1313" + }, + { + "label": "Полицейский", + "value": "1907" + }, + { + "label": "Полубог", + "value": "7170" + }, + { + "label": "Полукровка", + "value": "999" + }, + { + "label": "Полулюди", + "value": "2273" + }, + { + "label": "Поношенные трусики", + "value": "1204" + }, + { + "label": "Попаданец", + "value": "2396" + }, + { + "label": "Попаданец в другой мир", + "value": "685" + }, + { + "label": "Попаданец в игру", + "value": "6466" + }, + { + "label": "Попадание в книгу", + "value": "6957" + }, + { + "label": "Порно", + "value": "4023" + }, + { + "label": "Постапокалипсис", + "value": "1079" + }, + { + "label": "Потеря девственности", + "value": "5879" + }, + { + "label": "Похищение", + "value": "7452" + }, + { + "label": "Поэзия", + "value": "1120" + }, + { + "label": "Правитель", + "value": "2261" + }, + { + "label": "Превращения в животных", + "value": "3410" + }, + { + "label": "Преданный любовный интерес", + "value": "2055" + }, + { + "label": "Предательство", + "value": "70" + }, + { + "label": "Президент", + "value": "4334" + }, + { + "label": "Преступник", + "value": "4049" + }, + { + "label": "Преступный мир", + "value": "965" + }, + { + "label": "Призрак", + "value": "511" + }, + { + "label": "Призыв", + "value": "332" + }, + { + "label": "Призыв в другой мир", + "value": "781" + }, + { + "label": "Призыв существ", + "value": "412" + }, + { + "label": "Приключения", + "value": "964" + }, + { + "label": "Приложение на телефоне", + "value": "5345" + }, + { + "label": "Принудительный брак", + "value": "2954" + }, + { + "label": "Принуждение", + "value": "1442" + }, + { + "label": "Принц", + "value": "362" + }, + { + "label": "Принцесса", + "value": "2403" + }, + { + "label": "Приручение мостров", + "value": "2338" + }, + { + "label": "Прислуга", + "value": "5185" + }, + { + "label": "Пришельцы", + "value": "273" + }, + { + "label": "Проза", + "value": "2012" + }, + { + "label": "Прокачка", + "value": "1070" + }, + { + "label": "Проклятия", + "value": "1347" + }, + { + "label": "Проработанный мир", + "value": "1711" + }, + { + "label": "Пророчество", + "value": "7762" + }, + { + "label": "Псайкеры", + "value": "3659" + }, + { + "label": "Псионика", + "value": "5216" + }, + { + "label": "Психбольница", + "value": "6969" + }, + { + "label": "Психические расстройства", + "value": "2445" + }, + { + "label": "Психологические травмы", + "value": "3418" + }, + { + "label": "Психология", + "value": "17" + }, + { + "label": "Публичный дом", + "value": "7860" + }, + { + "label": "Публичный секс", + "value": "6647" + }, + { + "label": "Путешествие в другой мир", + "value": "10" + }, + { + "label": "Пух", + "value": "4286" + }, + { + "label": "Пушечное мясо", + "value": "7234" + }, + { + "label": "Пытки", + "value": "1756" + }, + { + "label": "Пьяный мастер", + "value": "1655" + }, + { + "label": "Пэнси паркинсон", + "value": "7411" + }, + { + "label": "Раб", + "value": "977" + }, + { + "label": "Рабы", + "value": "1240" + }, + { + "label": "Разведение животных", + "value": "3990" + }, + { + "label": "Развитие", + "value": "208" + }, + { + "label": "Развитие персонажа", + "value": "1780" + }, + { + "label": "Развитие поселения", + "value": "7203" + }, + { + "label": "Развитие технологий", + "value": "1875" + }, + { + "label": "Разврат", + "value": "1924" + }, + { + "label": "Раздвоение личности", + "value": "2062" + }, + { + "label": "Разные расы", + "value": "1188" + }, + { + "label": "Разорванная помолвка", + "value": "2057" + }, + { + "label": "Разработчик", + "value": "2333" + }, + { + "label": "Ранги", + "value": "1568" + }, + { + "label": "Ранобэ", + "value": "1764" + }, + { + "label": "Расизм", + "value": "3554" + }, + { + "label": "Растение", + "value": "407" + }, + { + "label": "Расчленение", + "value": "7125" + }, + { + "label": "Реализм", + "value": "1180" + }, + { + "label": "Ребенок", + "value": "2881" + }, + { + "label": "Ревность", + "value": "293" + }, + { + "label": "Революция", + "value": "3996" + }, + { + "label": "Реинкарнация", + "value": "1302" + }, + { + "label": "Реинкарнация в другом мире", + "value": "2180" + }, + { + "label": "Реинкарнация в игровой мир", + "value": "304" + }, + { + "label": "Рейджер", + "value": "5147" + }, + { + "label": "Религия", + "value": "2184" + }, + { + "label": "Рем", + "value": "6655" + }, + { + "label": "Ремесленник", + "value": "1483" + }, + { + "label": "Ремонт", + "value": "5703" + }, + { + "label": "Репликаторы", + "value": "3495" + }, + { + "label": "Ресторан", + "value": "7519" + }, + { + "label": "Риас гремори", + "value": "6950" + }, + { + "label": "Рим", + "value": "612" + }, + { + "label": "Риннеган", + "value": "4230" + }, + { + "label": "Ритуал", + "value": "1233" + }, + { + "label": "Робот", + "value": "4011" + }, + { + "label": "Роботы", + "value": "467" + }, + { + "label": "Рок ли", + "value": "7002" + }, + { + "label": "Рокси", + "value": "7380" + }, + { + "label": "Романтика", + "value": "1025" + }, + { + "label": "Рон уизли", + "value": "4279" + }, + { + "label": "Ророноа зоро", + "value": "1590" + }, + { + "label": "Росомаха", + "value": "1035" + }, + { + "label": "Российская империя", + "value": "2146" + }, + { + "label": "Россия", + "value": "4246" + }, + { + "label": "Рудеус", + "value": "7379" + }, + { + "label": "Руны", + "value": "2393" + }, + { + "label": "Русалки", + "value": "3127" + }, + { + "label": "Русь", + "value": "2016" + }, + { + "label": "Рыцари", + "value": "1902" + }, + { + "label": "Рэо хатаке", + "value": "2823" + }, + { + "label": "Сабли", + "value": "6136" + }, + { + "label": "Сайян", + "value": "6802" + }, + { + "label": "Сакура", + "value": "2515" + }, + { + "label": "Самец", + "value": "1592" + }, + { + "label": "Самоубийство", + "value": "4312" + }, + { + "label": "Самураи", + "value": "576" + }, + { + "label": "Санса старк", + "value": "3925" + }, + { + "label": "Сарказм", + "value": "4658" + }, + { + "label": "Саске", + "value": "7237" + }, + { + "label": "Сатира", + "value": "215" + }, + { + "label": "Сборник", + "value": "7367" + }, + { + "label": "Свадьба", + "value": "1524" + }, + { + "label": "Сверхсила", + "value": "1055" + }, + { + "label": "Сверхъестественное", + "value": "1074" + }, + { + "label": "Свингеры", + "value": "514" + }, + { + "label": "Свинцовые книги", + "value": "5322" + }, + { + "label": "Связывание", + "value": "4506" + }, + { + "label": "Священик", + "value": "61" + }, + { + "label": "Северус снейп", + "value": "5783" + }, + { + "label": "Сёдзе", + "value": "2219" + }, + { + "label": "Секс", + "value": "2472" + }, + { + "label": "Секс без проникновения", + "value": "151" + }, + { + "label": "Секс игрушки", + "value": "1124" + }, + { + "label": "Секс по принуждению", + "value": "8335" + }, + { + "label": "Секс по согласию", + "value": "7307" + }, + { + "label": "Секс рабыня", + "value": "7174" + }, + { + "label": "Секс с близнецами", + "value": "7457" + }, + { + "label": "Секс с монстрами", + "value": "6073" + }, + { + "label": "Секс с учителем", + "value": "2570" + }, + { + "label": "Секса будет много", + "value": "7265" + }, + { + "label": "Сексуальные и психологические отклонения", + "value": "888" + }, + { + "label": "Секты", + "value": "1234" + }, + { + "label": "Селфцест", + "value": "7498" + }, + { + "label": "Сельское хозяйство", + "value": "787" + }, + { + "label": "Семейный конфликт", + "value": "935" + }, + { + "label": "Семья", + "value": "728" + }, + { + "label": "Сенджу", + "value": "6069" + }, + { + "label": "Сёнэн", + "value": "2058" + }, + { + "label": "Серийный убийца", + "value": "261" + }, + { + "label": "Сестра", + "value": "2342" + }, + { + "label": "Сила глаза", + "value": "1019" + }, + { + "label": "Сильная героиня", + "value": "7292" + }, + { + "label": "Сильная главная героиня", + "value": "1994" + }, + { + "label": "Сильный главный герой", + "value": "570" + }, + { + "label": "Сильфиетта", + "value": "7381" + }, + { + "label": "Симбиот", + "value": "2648" + }, + { + "label": "Сириус блэк", + "value": "7655" + }, + { + "label": "Сирота", + "value": "297" + }, + { + "label": "Система", + "value": "251" + }, + { + "label": "Система уровней", + "value": "2174" + }, + { + "label": "Сиськи", + "value": "16" + }, + { + "label": "Ситх", + "value": "1618" + }, + { + "label": "Сказка", + "value": "3570" + }, + { + "label": "Скайрим", + "value": "4032" + }, + { + "label": "Скайуокер", + "value": "6828" + }, + { + "label": "Сквиб", + "value": "4101" + }, + { + "label": "Сквирт", + "value": "4526" + }, + { + "label": "Скелет", + "value": "1315" + }, + { + "label": "Скрытые способности", + "value": "3417" + }, + { + "label": "Скульптор", + "value": "1178" + }, + { + "label": "Слабая главная героиня", + "value": "5805" + }, + { + "label": "Слабый главный герой", + "value": "731" + }, + { + "label": "Славяне", + "value": "2168" + }, + { + "label": "Слизерин", + "value": "6979" + }, + { + "label": "Слизь", + "value": "1095" + }, + { + "label": "Сломанная игрушка", + "value": "8396" + }, + { + "label": "Служанка", + "value": "3486" + }, + { + "label": "Смартфон", + "value": "457" + }, + { + "label": "Смена сущности", + "value": "8391" + }, + { + "label": "Смерть", + "value": "1779" + }, + { + "label": "Смерть основного персонажа", + "value": "1530" + }, + { + "label": "Собаки", + "value": "6109" + }, + { + "label": "Соблазн", + "value": "7994" + }, + { + "label": "Соблазнение", + "value": "2290" + }, + { + "label": "Современность", + "value": "568" + }, + { + "label": "Современные знания", + "value": "988" + }, + { + "label": "Сожительство", + "value": "3026" + }, + { + "label": "Создание армии", + "value": "1992" + }, + { + "label": "Создание королевства", + "value": "1497" + }, + { + "label": "Создание пилюль", + "value": "816" + }, + { + "label": "Создатель", + "value": "1045" + }, + { + "label": "Сокровища", + "value": "6521" + }, + { + "label": "Сокрытие истинной личности", + "value": "2894" + }, + { + "label": "Сома и эрина", + "value": "5737" + }, + { + "label": "Соперничество", + "value": "7699" + }, + { + "label": "Соседи", + "value": "4075" + }, + { + "label": "Сотрудник", + "value": "6339" + }, + { + "label": "Социология", + "value": "7618" + }, + { + "label": "Спокойная главная героиня", + "value": "236" + }, + { + "label": "Спокойный главный герой", + "value": "1656" + }, + { + "label": "Спор", + "value": "3484" + }, + { + "label": "Спорт", + "value": "1387" + }, + { + "label": "Спортивное тело", + "value": "8317" + }, + { + "label": "Способность кражи", + "value": "7784" + }, + { + "label": "Сражения", + "value": "23" + }, + { + "label": "Средневековье", + "value": "1022" + }, + { + "label": "Сталкер", + "value": "1932" + }, + { + "label": "Становление государства", + "value": "111" + }, + { + "label": "Старшая школа", + "value": "1658" + }, + { + "label": "Стёб", + "value": "454" + }, + { + "label": "Стекло", + "value": "7453" + }, + { + "label": "Стеснительный персонаж", + "value": "152" + }, + { + "label": "Стимпанк", + "value": "1894" + }, + { + "label": "Стихи", + "value": "1309" + }, + { + "label": "Стратегия", + "value": "569" + }, + { + "label": "Стриптиз", + "value": "6914" + }, + { + "label": "Строительство", + "value": "921" + }, + { + "label": "Студент", + "value": "1464" + }, + { + "label": "Студентка", + "value": "7518" + }, + { + "label": "Суккубы", + "value": "2662" + }, + { + "label": "Супер зрение", + "value": "6574" + }, + { + "label": "Супер удача", + "value": "489" + }, + { + "label": "Супергерои", + "value": "1481" + }, + { + "label": "Суперзлодеи", + "value": "2125" + }, + { + "label": "Супермен", + "value": "6997" + }, + { + "label": "Суперсилы", + "value": "2276" + }, + { + "label": "Существа", + "value": "6033" + }, + { + "label": "Сферы", + "value": "1077" + }, + { + "label": "Схемы и заговоры", + "value": "7116" + }, + { + "label": "Сценарий", + "value": "7489" + }, + { + "label": "Счастливый конец", + "value": "3293" + }, + { + "label": "Сын", + "value": "3297" + }, + { + "label": "Сэйнэн", + "value": "345" + }, + { + "label": "Сэм винчестер", + "value": "7461" + }, + { + "label": "Сюаньхуа", + "value": "2560" + }, + { + "label": "Сюжетные повороты", + "value": "7202" + }, + { + "label": "Сянься", + "value": "1126" + }, + { + "label": "Таблетки для развития", + "value": "589" + }, + { + "label": "Таинственное прошлое", + "value": "769" + }, + { + "label": "Тайная личность", + "value": "8096" + }, + { + "label": "Тайная любовь", + "value": "113" + }, + { + "label": "Тайны", + "value": "7481" + }, + { + "label": "Тайные отношения", + "value": "7450" + }, + { + "label": "Тайцы", + "value": "6413" + }, + { + "label": "Танки", + "value": "1787" + }, + { + "label": "Твинцест", + "value": "2450" + }, + { + "label": "Творчество", + "value": "20" + }, + { + "label": "Тейлор эберт", + "value": "3526" + }, + { + "label": "Телекинез", + "value": "2203" + }, + { + "label": "Телепортация", + "value": "914" + }, + { + "label": "Телохранитель", + "value": "723" + }, + { + "label": "Темари", + "value": "4810" + }, + { + "label": "Темная магия", + "value": "4788" + }, + { + "label": "Темное фэнтези", + "value": "1675" + }, + { + "label": "Темные эльфы", + "value": "7198" + }, + { + "label": "Тенсейган", + "value": "4367" + }, + { + "label": "Тентакли", + "value": "3236" + }, + { + "label": "Тетя", + "value": "4194" + }, + { + "label": "Техномагия", + "value": "508" + }, + { + "label": "Тиран", + "value": "1174" + }, + { + "label": "Титаны", + "value": "3308" + }, + { + "label": "Тодороки шото", + "value": "5105" + }, + { + "label": "Токио", + "value": "5791" + }, + { + "label": "Том реддл", + "value": "6652" + }, + { + "label": "Тони старк", + "value": "1338" + }, + { + "label": "Тор", + "value": "1800" + }, + { + "label": "Тор и локи", + "value": "2645" + }, + { + "label": "Торговля", + "value": "1637" + }, + { + "label": "Травничество", + "value": "221" + }, + { + "label": "Трагедия", + "value": "470" + }, + { + "label": "Трагическое прошлое", + "value": "644" + }, + { + "label": "Трактир", + "value": "1918" + }, + { + "label": "Трансмиграция", + "value": "1071" + }, + { + "label": "Трансформация", + "value": "156" + }, + { + "label": "Трансформеры", + "value": "4616" + }, + { + "label": "Тревор филипс", + "value": "5427" + }, + { + "label": "Триллер", + "value": "1676" + }, + { + "label": "Тройняшки", + "value": "7169" + }, + { + "label": "Троли", + "value": "4250" + }, + { + "label": "Трудолюбивый главный герой", + "value": "248" + }, + { + "label": "Тупой главный герой", + "value": "3710" + }, + { + "label": "Тьма", + "value": "1919" + }, + { + "label": "Тюрьма", + "value": "5597" + }, + { + "label": "Тяжёлое детство", + "value": "2253" + }, + { + "label": "Убийства", + "value": "378" + }, + { + "label": "Убийцы", + "value": "1766" + }, + { + "label": "Убийцы драконов", + "value": "1484" + }, + { + "label": "Увечья", + "value": "3266" + }, + { + "label": "Удача", + "value": "6575" + }, + { + "label": "Ужасы", + "value": "1747" + }, + { + "label": "Узумаки", + "value": "7579" + }, + { + "label": "Укротитель", + "value": "367" + }, + { + "label": "Умения", + "value": "531" + }, + { + "label": "Умная главная героиня", + "value": "1381" + }, + { + "label": "Умные персонажи", + "value": "2756" + }, + { + "label": "Умный главный герой", + "value": "2093" + }, + { + "label": "Университет", + "value": "2102" + }, + { + "label": "Унижение", + "value": "8342" + }, + { + "label": "Уничтожение мира", + "value": "81" + }, + { + "label": "Управление временем", + "value": "5648" + }, + { + "label": "Уровни", + "value": "8" + }, + { + "label": "Усиление", + "value": "3549" + }, + { + "label": "Усопп", + "value": "6737" + }, + { + "label": "Устроенный брак", + "value": "2223" + }, + { + "label": "Усыновление", + "value": "395" + }, + { + "label": "Уся", + "value": "1172" + }, + { + "label": "Утопия", + "value": "4677" + }, + { + "label": "Уход за детьми", + "value": "7609" + }, + { + "label": "Учеба в университете", + "value": "2797" + }, + { + "label": "Учебное заведение", + "value": "1817" + }, + { + "label": "Ученые", + "value": "1843" + }, + { + "label": "Учитель", + "value": "845" + }, + { + "label": "Учиха", + "value": "5348" + }, + { + "label": "Фамильяры", + "value": "1471" + }, + { + "label": "Фанатичная любовь", + "value": "6721" + }, + { + "label": "Фанаты", + "value": "7739" + }, + { + "label": "Фантастика", + "value": "286" + }, + { + "label": "Фантастический мир", + "value": "2414" + }, + { + "label": "Фанфик", + "value": "767" + }, + { + "label": "Фармацевт", + "value": "1706" + }, + { + "label": "Фашисты", + "value": "3118" + }, + { + "label": "Феи", + "value": "7185" + }, + { + "label": "Фелляция", + "value": "5880" + }, + { + "label": "Феминизм", + "value": "6527" + }, + { + "label": "Феникс", + "value": "2413" + }, + { + "label": "Фенрир", + "value": "2740" + }, + { + "label": "Феодализм", + "value": "280" + }, + { + "label": "Ферма", + "value": "285" + }, + { + "label": "Фетиш", + "value": "2398" + }, + { + "label": "Фехтовальщик", + "value": "4685" + }, + { + "label": "Филиппины", + "value": "128" + }, + { + "label": "Философия", + "value": "1859" + }, + { + "label": "Фистинг", + "value": "4206" + }, + { + "label": "Флафф", + "value": "5048" + }, + { + "label": "Флер делакур", + "value": "5191" + }, + { + "label": "Флэш", + "value": "4667" + }, + { + "label": "Фокусник", + "value": "7602" + }, + { + "label": "Фольклор", + "value": "4086" + }, + { + "label": "Фугаку", + "value": "6161" + }, + { + "label": "Фурри", + "value": "6631" + }, + { + "label": "Футбол", + "value": "1121" + }, + { + "label": "Фэнтези", + "value": "174" + }, + { + "label": "Фэнтезийный мир", + "value": "1556" + }, + { + "label": "Хакер", + "value": "475" + }, + { + "label": "Халк", + "value": "1196" + }, + { + "label": "Хан нуньен синг", + "value": "4817" + }, + { + "label": "Ханма юджиро", + "value": "4369" + }, + { + "label": "Хаос", + "value": "3117" + }, + { + "label": "Харизматичный главный герой", + "value": "2350" + }, + { + "label": "Хатаке какаши", + "value": "2820" + }, + { + "label": "Хентай", + "value": "1682" + }, + { + "label": "Хиддлстон", + "value": "5903" + }, + { + "label": "Химавари", + "value": "2583" + }, + { + "label": "Хината", + "value": "2513" + }, + { + "label": "Хитрый главный герой", + "value": "2036" + }, + { + "label": "Хоббит", + "value": "6698" + }, + { + "label": "Хогвартс", + "value": "5954" + }, + { + "label": "Холодная главная героиня", + "value": "6507" + }, + { + "label": "Холодное оружие", + "value": "4889" + }, + { + "label": "Холодный главный герой", + "value": "2686" + }, + { + "label": "Храм", + "value": "2135" + }, + { + "label": "Христианство", + "value": "877" + }, + { + "label": "Художественная литература", + "value": "2380" + }, + { + "label": "Художница", + "value": "518" + }, + { + "label": "Хулиганы", + "value": "854" + }, + { + "label": "Цветок", + "value": "902" + }, + { + "label": "Цезарь", + "value": "1705" + }, + { + "label": "Целители", + "value": "4523" + }, + { + "label": "Церковь", + "value": "1980" + }, + { + "label": "Цундере", + "value": "1078" + }, + { + "label": "Чакра", + "value": "1393" + }, + { + "label": "Черный юмор", + "value": "2868" + }, + { + "label": "Честная главная героиня", + "value": "5855" + }, + { + "label": "Честный главный герой", + "value": "129" + }, + { + "label": "Читы", + "value": "451" + }, + { + "label": "Шантаж", + "value": "7175" + }, + { + "label": "Шаринган", + "value": "5165" + }, + { + "label": "Шинигами", + "value": "4661" + }, + { + "label": "Шиноби", + "value": "1227" + }, + { + "label": "Школьная жизнь", + "value": "284" + }, + { + "label": "Школьники", + "value": "218" + }, + { + "label": "Школьницы", + "value": "5946" + }, + { + "label": "Шлюха", + "value": "5822" + }, + { + "label": "Шоу-бизнес", + "value": "7119" + }, + { + "label": "Шпионы", + "value": "4262" + }, + { + "label": "Эволюция", + "value": "2040" + }, + { + "label": "Экзорцизм", + "value": "676" + }, + { + "label": "Экономика", + "value": "121" + }, + { + "label": "Эксгибиционизм", + "value": "1643" + }, + { + "label": "Экспансия", + "value": "110" + }, + { + "label": "Эксперименты над людьми", + "value": "7408" + }, + { + "label": "Экшен", + "value": "2859" + }, + { + "label": "Элементальная магия", + "value": "1264" + }, + { + "label": "Элементы бдсм", + "value": "1323" + }, + { + "label": "Эльфы", + "value": "1362" + }, + { + "label": "Эротика", + "value": "2053" + }, + { + "label": "Этти", + "value": "1317" + }, + { + "label": "Юмор", + "value": "7360" + }, + { + "label": "Яды", + "value": "201" + }, + { + "label": "Якудза", + "value": "4599" + }, + { + "label": "Яндере", + "value": "7111" + }, + { + "label": "Bl", + "value": "8282" + }, + { + "label": "Gl", + "value": "8284" + }, + { + "label": "Rpg", + "value": "90" + }, + { + "label": "Star wars", + "value": "1985" + }, + { + "label": "Time skip", + "value": "824" + }, + { + "label": "Warrior of light", + "value": "5289" + } + ], + "inputType": "Checkbox" + } + ] + }, + { + "id": "erolate", + "sourceName": "Erolate", + "sourceSite": "https://erolate.com", + "filters": [ + { + "key": "genres", + "label": "Жанры", + "values": [ + { + "label": "Анал", + "value": "2" + }, + { + "label": "Бдсм", + "value": "3" + }, + { + "label": "Большая грудь", + "value": "5" + }, + { + "label": "Большая попка", + "value": "6" + }, + { + "label": "Большой член", + "value": "7" + }, + { + "label": "Бондаж", + "value": "8" + }, + { + "label": "В первый раз", + "value": "9" + }, + { + "label": "В цвете", + "value": "10" + }, + { + "label": "Гарем", + "value": "11" + }, + { + "label": "Гендарная интрига", + "value": "12" + }, + { + "label": "Групповой секс", + "value": "13" + }, + { + "label": "Детектив", + "value": "39" + }, + { + "label": "Драма", + "value": "14" + }, + { + "label": "Зрелые женщины (milf)", + "value": "15" + }, + { + "label": "Измена", + "value": "16" + }, + { + "label": "Изнасилование", + "value": "17" + }, + { + "label": "Инцест", + "value": "18" + }, + { + "label": "Исторический", + "value": "19" + }, + { + "label": "Комедия", + "value": "20" + }, + { + "label": "Контроль над разумом", + "value": "35" + }, + { + "label": "Маленькая грудь", + "value": "21" + }, + { + "label": "Мистика", + "value": "40" + }, + { + "label": "Научная фантастика", + "value": "22" + }, + { + "label": "Нетораре", + "value": "23" + }, + { + "label": "Оральный секс", + "value": "24" + }, + { + "label": "Повседневность", + "value": "41" + }, + { + "label": "Приключения", + "value": "38" + }, + { + "label": "Публичный секс", + "value": "33" + }, + { + "label": "Романтика", + "value": "25" + }, + { + "label": "С изображениями ", + "value": "36" + }, + { + "label": "Сверхъестественное", + "value": "34" + }, + { + "label": "Смат", + "value": "37" + }, + { + "label": "Тентакли", + "value": "26" + }, + { + "label": "Трагедия", + "value": "27" + }, + { + "label": "Ужасы", + "value": "28" + }, + { + "label": "Фантастика", + "value": "32" + }, + { + "label": "Фэнтези", + "value": "29" + }, + { + "label": "Чикан", + "value": "30" + }, + { + "label": "Этти", + "value": "31" + }, + { + "label": "Ahegao", + "value": "1" + } + ], + "inputType": "Checkbox" + }, + { + "key": "tags", + "label": "Тэги", + "values": [ + { + "label": "+18", + "value": "141" + }, + { + "label": "18+", + "value": "200" + }, + { + "label": "21+", + "value": "334" + }, + { + "label": "Адекватные главные герои", + "value": "253" + }, + { + "label": "Азартные игры", + "value": "210" + }, + { + "label": "Айдолы", + "value": "364" + }, + { + "label": "Акула", + "value": "53" + }, + { + "label": "Альтернативное развитие событий", + "value": "338" + }, + { + "label": "Аморальный главный герой", + "value": "219" + }, + { + "label": "Анал", + "value": "7" + }, + { + "label": "Анальный секс", + "value": "67" + }, + { + "label": "Ангелы", + "value": "114" + }, + { + "label": "Антигерой", + "value": "226" + }, + { + "label": "Аристократия", + "value": "260" + }, + { + "label": "Артефакт", + "value": "379" + }, + { + "label": "Афродизиак", + "value": "276" + }, + { + "label": "Ахегао", + "value": "240" + }, + { + "label": "Бабушка беременна от внука", + "value": "393" + }, + { + "label": "Бабушка и внук", + "value": "380" + }, + { + "label": "Бдсм", + "value": "131" + }, + { + "label": "Беременность", + "value": "148" + }, + { + "label": "Бесплатно", + "value": "79" + }, + { + "label": "Бесстрашные персонажи", + "value": "339" + }, + { + "label": "Библиотека", + "value": "265" + }, + { + "label": "Бистиалити", + "value": "391" + }, + { + "label": "Близнецы", + "value": "198" + }, + { + "label": "Блондинка", + "value": "91" + }, + { + "label": "Богатые персонажи", + "value": "248" + }, + { + "label": "Боги", + "value": "115" + }, + { + "label": "Боевик", + "value": "252" + }, + { + "label": "Большая грудь", + "value": "33" + }, + { + "label": "Большая попка", + "value": "328" + }, + { + "label": "Большой член", + "value": "76" + }, + { + "label": "Большой член сына", + "value": "396" + }, + { + "label": "Босс и подчиненный", + "value": "330" + }, + { + "label": "Брак", + "value": "182" + }, + { + "label": "Брак по расчёту", + "value": "346" + }, + { + "label": "Брат", + "value": "29" + }, + { + "label": "Брат и сестра", + "value": "30" + }, + { + "label": "Брюнетка", + "value": "28" + }, + { + "label": "Бэтмен", + "value": "369" + }, + { + "label": "Бэтмен", + "value": "170" + }, + { + "label": "В первый раз", + "value": "179" + }, + { + "label": "Вагинальный секс", + "value": "57" + }, + { + "label": "Вагинальный секс", + "value": "171" + }, + { + "label": "Вайурист", + "value": "9" + }, + { + "label": "Вампиры", + "value": "133" + }, + { + "label": "Ван-пис", + "value": "335" + }, + { + "label": "Веб камера", + "value": "289" + }, + { + "label": "Ведьмы", + "value": "232" + }, + { + "label": "Викинги", + "value": "307" + }, + { + "label": "Виртуальная реальность", + "value": "382" + }, + { + "label": "Вирус", + "value": "203" + }, + { + "label": "Внучка", + "value": "189" + }, + { + "label": "Война", + "value": "387" + }, + { + "label": "Время", + "value": "81" + }, + { + "label": "Второй шанс", + "value": "244" + }, + { + "label": "Вуайеризм", + "value": "62" + }, + { + "label": "Выживание", + "value": "135" + }, + { + "label": "Гарем", + "value": "107" + }, + { + "label": "Гарри поттер", + "value": "97" + }, + { + "label": "Гг имба", + "value": "87" + }, + { + "label": "Генетические модификации", + "value": "314" + }, + { + "label": "Гипноз", + "value": "143" + }, + { + "label": "Главная героиня девушка", + "value": "147" + }, + { + "label": "Главный герой женщина", + "value": "213" + }, + { + "label": "Главный герой извращенец", + "value": "116" + }, + { + "label": "Главный герой мужчина", + "value": "117" + }, + { + "label": "Глубокое горло", + "value": "94" + }, + { + "label": "Гоблины", + "value": "230" + }, + { + "label": "Город", + "value": "139" + }, + { + "label": "Городское фэнтези", + "value": "297" + }, + { + "label": "Грудное молоко", + "value": "225" + }, + { + "label": "Групповой секс", + "value": "72" + }, + { + "label": "Гэнг-бэнг", + "value": "235" + }, + { + "label": "Гяру", + "value": "222" + }, + { + "label": "Двойное проникновение", + "value": "42" + }, + { + "label": "Двойной анал", + "value": "47" + }, + { + "label": "Дворяне", + "value": "246" + }, + { + "label": "Деамоны старший школы", + "value": "291" + }, + { + "label": "Девственница", + "value": "119" + }, + { + "label": "Девушки-монстры", + "value": "223" + }, + { + "label": "Демоны", + "value": "98" + }, + { + "label": "Детектив", + "value": "249" + }, + { + "label": "Дзёсэй", + "value": "259" + }, + { + "label": "Доктор", + "value": "326" + }, + { + "label": "Дом", + "value": "138" + }, + { + "label": "Доминирование", + "value": "18" + }, + { + "label": "Дочь", + "value": "153" + }, + { + "label": "Дочь беременна от отца", + "value": "348" + }, + { + "label": "Древние времена", + "value": "383" + }, + { + "label": "Древний китай", + "value": "261" + }, + { + "label": "Другой мир", + "value": "365" + }, + { + "label": "Дружба", + "value": "325" + }, + { + "label": "Друзья детства", + "value": "183" + }, + { + "label": "Дядя", + "value": "160" + }, + { + "label": "Жена", + "value": "70" + }, + { + "label": "Жена и муж", + "value": "99" + }, + { + "label": "Жена шлюха", + "value": "236" + }, + { + "label": "Женское доминирование", + "value": "100" + }, + { + "label": "Жесткий секс", + "value": "283" + }, + { + "label": "Жёсткий секс", + "value": "101" + }, + { + "label": "Жестокие персонажи", + "value": "254" + }, + { + "label": "Жестокий мир", + "value": "336" + }, + { + "label": "Жестокость", + "value": "123" + }, + { + "label": "Заботливый главный герой", + "value": "340" + }, + { + "label": "Зависимость", + "value": "370" + }, + { + "label": "Заключение", + "value": "196" + }, + { + "label": "Заключённые", + "value": "384" + }, + { + "label": "Замок", + "value": "140" + }, + { + "label": "Запретная любовь", + "value": "273" + }, + { + "label": "Заражение", + "value": "206" + }, + { + "label": "Звёздные войны", + "value": "120" + }, + { + "label": "Зверодевочки", + "value": "158" + }, + { + "label": "Зло", + "value": "343" + }, + { + "label": "Золовка", + "value": "77" + }, + { + "label": "Золотой дождь", + "value": "146" + }, + { + "label": "Зомби апокалипсис", + "value": "241" + }, + { + "label": "Зоофилия", + "value": "144" + }, + { + "label": "Игровые элементы", + "value": "385" + }, + { + "label": "Извращения", + "value": "192" + }, + { + "label": "Измена", + "value": "71" + }, + { + "label": "Изменения внешности", + "value": "315" + }, + { + "label": "Изменения личности", + "value": "316" + }, + { + "label": "Изнасилование", + "value": "275" + }, + { + "label": "Изуку мидория", + "value": "337" + }, + { + "label": "Ино", + "value": "127" + }, + { + "label": "Инопланетяне", + "value": "304" + }, + { + "label": "Интересный сюжет", + "value": "349" + }, + { + "label": "Интимные сцены", + "value": "322" + }, + { + "label": "Интриги и заговоры", + "value": "262" + }, + { + "label": "Интроверт", + "value": "38" + }, + { + "label": "Инфекция", + "value": "207" + }, + { + "label": "Инцест", + "value": "35" + }, + { + "label": "Камера", + "value": "92" + }, + { + "label": "Колледж", + "value": "82" + }, + { + "label": "Комикс", + "value": "286" + }, + { + "label": "Контроль", + "value": "19" + }, + { + "label": "Контроль над разумом", + "value": "181" + }, + { + "label": "Контроль разума", + "value": "65" + }, + { + "label": "Кончил внутрь", + "value": "221" + }, + { + "label": "Королевская семья", + "value": "358" + }, + { + "label": "Коррупция", + "value": "49" + }, + { + "label": "Космос", + "value": "204" + }, + { + "label": "Красивая главная героиня", + "value": "255" + }, + { + "label": "Красивые женщины", + "value": "321" + }, + { + "label": "Красивый главный герой", + "value": "266" + }, + { + "label": "Ксенофилия", + "value": "386" + }, + { + "label": "Кулинария", + "value": "362" + }, + { + "label": "Культвация", + "value": "86" + }, + { + "label": "Кунилингус", + "value": "152" + }, + { + "label": "Лактация", + "value": "11" + }, + { + "label": "Лишение девственности", + "value": "212" + }, + { + "label": "Лоли", + "value": "218" + }, + { + "label": "Лунатизм", + "value": "279" + }, + { + "label": "Любовь", + "value": "89" + }, + { + "label": "Магический мир", + "value": "357" + }, + { + "label": "Магия", + "value": "80" + }, + { + "label": "Мама", + "value": "3" + }, + { + "label": "Мама беременна от сына", + "value": "274" + }, + { + "label": "Мама и дочь", + "value": "163" + }, + { + "label": "Мама и сын", + "value": "1" + }, + { + "label": "Мама супермена", + "value": "39" + }, + { + "label": "Манипуляция временем", + "value": "292" + }, + { + "label": "Марвел", + "value": "290" + }, + { + "label": "Марвел", + "value": "173" + }, + { + "label": "Марти стью", + "value": "102" + }, + { + "label": "Массаж", + "value": "224" + }, + { + "label": "Мастурбация", + "value": "63" + }, + { + "label": "Мать", + "value": "239" + }, + { + "label": "Мать и дочь", + "value": "381" + }, + { + "label": "Мать и сын", + "value": "237" + }, + { + "label": "Мафия", + "value": "256" + }, + { + "label": "Мачеха", + "value": "197" + }, + { + "label": "Мачеха беременна от сына", + "value": "395" + }, + { + "label": "Мачеха и сын", + "value": "394" + }, + { + "label": "Медсестра", + "value": "341" + }, + { + "label": "Межрассовый", + "value": "54" + }, + { + "label": "Месть", + "value": "306" + }, + { + "label": "Меха", + "value": "268" + }, + { + "label": "Ми", + "value": "243" + }, + { + "label": "Милф", + "value": "34" + }, + { + "label": "Милф", + "value": "180" + }, + { + "label": "Минет", + "value": "52" + }, + { + "label": "Мистика", + "value": "250" + }, + { + "label": "Младшая сестра", + "value": "154" + }, + { + "label": "Модель", + "value": "324" + }, + { + "label": "Монстры", + "value": "333" + }, + { + "label": "Мошеничество", + "value": "44" + }, + { + "label": "Мужская беременность", + "value": "363" + }, + { + "label": "Мужчина протагонист", + "value": "108" + }, + { + "label": "Музыка", + "value": "331" + }, + { + "label": "Мэри съюха", + "value": "309" + }, + { + "label": "Наруто", + "value": "46" + }, + { + "label": "Насилие", + "value": "128" + }, + { + "label": "Насилие и жестокость", + "value": "193" + }, + { + "label": "Научный эксперимент", + "value": "313" + }, + { + "label": "Нежить", + "value": "373" + }, + { + "label": "Некромантия", + "value": "298" + }, + { + "label": "Ненормативная лексика", + "value": "124" + }, + { + "label": "Нетораре", + "value": "69" + }, + { + "label": "Нетори", + "value": "284" + }, + { + "label": "Нижнее бельё", + "value": "392" + }, + { + "label": "Ниндзя", + "value": "296" + }, + { + "label": "Ношеные трусики", + "value": "166" + }, + { + "label": "Нудизм", + "value": "156" + }, + { + "label": "Няня", + "value": "88" + }, + { + "label": "Обмен женами", + "value": "78" + }, + { + "label": "Оборотни", + "value": "136" + }, + { + "label": "Обратный гарем", + "value": "214" + }, + { + "label": "Одержимость", + "value": "345" + }, + { + "label": "Омегаверс", + "value": "368" + }, + { + "label": "Оплодотворение", + "value": "157" + }, + { + "label": "Оральный секс", + "value": "10" + }, + { + "label": "Оргия", + "value": "356" + }, + { + "label": "Орки", + "value": "388" + }, + { + "label": "От слабого к сильному", + "value": "351" + }, + { + "label": "Отец", + "value": "2" + }, + { + "label": "Отец делится с сыном", + "value": "6" + }, + { + "label": "Отец и дочь", + "value": "205" + }, + { + "label": "Отец куколд", + "value": "272" + }, + { + "label": "Офис", + "value": "234" + }, + { + "label": "Падчерица", + "value": "271" + }, + { + "label": "Пайзури", + "value": "195" + }, + { + "label": "Папа и дочь", + "value": "360" + }, + { + "label": "Паразиты", + "value": "208" + }, + { + "label": "Параллельный мир", + "value": "320" + }, + { + "label": "Пародия", + "value": "371" + }, + { + "label": "Первый раз", + "value": "159" + }, + { + "label": "Перемещение в другой мир", + "value": "347" + }, + { + "label": "Перемещение во времени", + "value": "245" + }, + { + "label": "Перерождение", + "value": "109" + }, + { + "label": "Перерождение в злодея", + "value": "202" + }, + { + "label": "Пирсинг", + "value": "145" + }, + { + "label": "Писатель", + "value": "267" + }, + { + "label": "Планомерное развитие событий", + "value": "350" + }, + { + "label": "Повседневность", + "value": "247" + }, + { + "label": "Подглядывание", + "value": "280" + }, + { + "label": "Подчинение", + "value": "106" + }, + { + "label": "Подчинение и унижение", + "value": "211" + }, + { + "label": "Поедание киски", + "value": "26" + }, + { + "label": "Покемоны", + "value": "132" + }, + { + "label": "Покорная", + "value": "8" + }, + { + "label": "Полигамия", + "value": "352" + }, + { + "label": "Половое воспитание", + "value": "37" + }, + { + "label": "Полулюди", + "value": "231" + }, + { + "label": "Попаданец", + "value": "103" + }, + { + "label": "Попадание в книгу", + "value": "361" + }, + { + "label": "Порка", + "value": "20" + }, + { + "label": "Порно", + "value": "278" + }, + { + "label": "Постапокалипсис", + "value": "332" + }, + { + "label": "Потеря девственности", + "value": "142" + }, + { + "label": "Похищение", + "value": "161" + }, + { + "label": "Приключения", + "value": "134" + }, + { + "label": "Принуждение", + "value": "66" + }, + { + "label": "Психология", + "value": "238" + }, + { + "label": "Публично", + "value": "85" + }, + { + "label": "Публичный секс", + "value": "233" + }, + { + "label": "Раб", + "value": "353" + }, + { + "label": "Рабы", + "value": "308" + }, + { + "label": "Разват", + "value": "58" + }, + { + "label": "Разврат", + "value": "64" + }, + { + "label": "Райзен", + "value": "95" + }, + { + "label": "Раса инопланетных космических лесбиянок", + "value": "294" + }, + { + "label": "Рестленг", + "value": "84" + }, + { + "label": "Риас гремори", + "value": "389" + }, + { + "label": "Романтика", + "value": "178" + }, + { + "label": "Рыжий", + "value": "90" + }, + { + "label": "Сакура", + "value": "126" + }, + { + "label": "Самолет", + "value": "93" + }, + { + "label": "Санса старк", + "value": "277" + }, + { + "label": "Санта", + "value": "372" + }, + { + "label": "Свадьба", + "value": "73" + }, + { + "label": "Сверхсила", + "value": "228" + }, + { + "label": "Сверхъестественное", + "value": "319" + }, + { + "label": "Свингеры", + "value": "201" + }, + { + "label": "Свободные отношения", + "value": "186" + }, + { + "label": "Связывание", + "value": "137" + }, + { + "label": "Сёдзе", + "value": "264" + }, + { + "label": "Секс", + "value": "21" + }, + { + "label": "Секс без проникновения", + "value": "68" + }, + { + "label": "Секс втроем", + "value": "43" + }, + { + "label": "Секс игрушки", + "value": "59" + }, + { + "label": "Секс рабыня", + "value": "55" + }, + { + "label": "Секс с монстрами", + "value": "56" + }, + { + "label": "Секс с учителем", + "value": "184" + }, + { + "label": "Секса будет много", + "value": "151" + }, + { + "label": "Сексуальное желание", + "value": "209" + }, + { + "label": "Селфцест", + "value": "397" + }, + { + "label": "Семья", + "value": "5" + }, + { + "label": "Сестра", + "value": "31" + }, + { + "label": "Сестра беременна от брата", + "value": "355" + }, + { + "label": "Сильный с самого начала", + "value": "112" + }, + { + "label": "Симбиоз", + "value": "50" + }, + { + "label": "Сирена", + "value": "51" + }, + { + "label": "Система", + "value": "104" + }, + { + "label": "Сиськи", + "value": "285" + }, + { + "label": "Сквирт", + "value": "164" + }, + { + "label": "Скрытый секс", + "value": "287" + }, + { + "label": "Служанка", + "value": "215" + }, + { + "label": "Соб", + "value": "281" + }, + { + "label": "Соблазнение", + "value": "162" + }, + { + "label": "Современность", + "value": "217" + }, + { + "label": "Соколиный Глаз", + "value": "176" + }, + { + "label": "Соперничество", + "value": "216" + }, + { + "label": "Соседи", + "value": "61" + }, + { + "label": "Соседка", + "value": "312" + }, + { + "label": "Сперма", + "value": "229" + }, + { + "label": "Сперма на лицо", + "value": "199" + }, + { + "label": "Спящие", + "value": "187" + }, + { + "label": "Старшая сестра", + "value": "311" + }, + { + "label": "Стеб", + "value": "111" + }, + { + "label": "Студенты", + "value": "177" + }, + { + "label": "Суккуб", + "value": "390" + }, + { + "label": "Супергерои", + "value": "305" + }, + { + "label": "Суперспособности", + "value": "300" + }, + { + "label": "Сын", + "value": "4" + }, + { + "label": "Твинцест", + "value": "188" + }, + { + "label": "Темное фэнтези", + "value": "293" + }, + { + "label": "Тентакли", + "value": "303" + }, + { + "label": "Тетя", + "value": "113" + }, + { + "label": "Тетя беременна от племянника", + "value": "376" + }, + { + "label": "Тетя и племянник", + "value": "377" + }, + { + "label": "Техника", + "value": "302" + }, + { + "label": "Технологии", + "value": "301" + }, + { + "label": "Тёща", + "value": "270" + }, + { + "label": "Тиран", + "value": "310" + }, + { + "label": "Трагическое прошлое", + "value": "251" + }, + { + "label": "Трансмиграция", + "value": "269" + }, + { + "label": "Трансформация", + "value": "317" + }, + { + "label": "Триллер", + "value": "344" + }, + { + "label": "Трусики", + "value": "165" + }, + { + "label": "Убийства", + "value": "258" + }, + { + "label": "Удача", + "value": "299" + }, + { + "label": "Ужасы", + "value": "375" + }, + { + "label": "Улучшение тела", + "value": "288" + }, + { + "label": "Умная главная героиня", + "value": "242" + }, + { + "label": "Умные персонажи", + "value": "257" + }, + { + "label": "Умный главный герой", + "value": "354" + }, + { + "label": "Университет", + "value": "323" + }, + { + "label": "Ученик", + "value": "83" + }, + { + "label": "Ф", + "value": "149" + }, + { + "label": "Фанатичная любовь", + "value": "366" + }, + { + "label": "Фанаты", + "value": "367" + }, + { + "label": "Фантастика", + "value": "295" + }, + { + "label": "Фанфик", + "value": "40" + }, + { + "label": "Фелляция", + "value": "22" + }, + { + "label": "Ферма", + "value": "191" + }, + { + "label": "Фетиш", + "value": "60" + }, + { + "label": "Флэш", + "value": "121" + }, + { + "label": "Футанария", + "value": "150" + }, + { + "label": "Фэнтези", + "value": "359" + }, + { + "label": "Хентай", + "value": "75" + }, + { + "label": "Хината", + "value": "110" + }, + { + "label": "Холодная главная героиня", + "value": "263" + }, + { + "label": "Черная вдова", + "value": "220" + }, + { + "label": "Черная Вдова", + "value": "175" + }, + { + "label": "Черный юмор", + "value": "374" + }, + { + "label": "Читерство", + "value": "45" + }, + { + "label": "Чтение мыслей", + "value": "227" + }, + { + "label": "Чудовища", + "value": "378" + }, + { + "label": "Шантаж", + "value": "129" + }, + { + "label": "Школа", + "value": "118" + }, + { + "label": "Школьная жизнь", + "value": "318" + }, + { + "label": "Шлюха", + "value": "32" + }, + { + "label": "Шоу-бизнес", + "value": "329" + }, + { + "label": "Шоу-бизнес", + "value": "172" + }, + { + "label": "Эксгибиционизм", + "value": "74" + }, + { + "label": "Элементы бдсм", + "value": "185" + }, + { + "label": "Эмма уотсон", + "value": "342" + }, + { + "label": "Эмма Уотсон", + "value": "169" + }, + { + "label": "Эротика", + "value": "36" + }, + { + "label": "Этти", + "value": "327" + }, + { + "label": "Юмор", + "value": "105" + }, + { + "label": "Яндере", + "value": "155" + }, + { + "label": "Bl", + "value": "125" + }, + { + "label": "Gl", + "value": "96" + }, + { + "label": "Harry potter", + "value": "190" + }, + { + "label": "Harry potter", + "value": "168" + }, + { + "label": "Hentai", + "value": "282" + }, + { + "label": "Marvel", + "value": "194" + }, + { + "label": "Marvel", + "value": "174" + }, + { + "label": "Milf", + "value": "167" + } + ], + "inputType": "Checkbox" + } + ] + } + ] diff --git a/template.js b/template.js deleted file mode 100644 index 3bab5e2..0000000 --- a/template.js +++ /dev/null @@ -1,97 +0,0 @@ -const cheerio = require('cheerio'); -const languages = require('@libs/languages'); -const fetchApi = require('@libs/fetchApi'); -const fetchFile = require('@libs/fetchFile'); -// const dayjs = require('dayjs'); -// const FilterInputs = require('@libs/filterInputs'); -// const novelStatus = require('@libs/novelStatus'); -// const isUrlAbsolute = require('@libs/isAbsoluteUrl'); -// const parseDate = require('@libs/parseDate'); - - -async function popularNovels (pageNo) { - const novels = []; - /* - Do something.... - novel = { - name: '', - url: '', must be absolute - cover: '', - } - novels.push(novel); - */ - return novels; -}; - -async function parseNovelAndChapters (novelUrl) { - const novel = { - url: novelUrl, - chapters: [], - }; - - /** - * novel.name = ''; - * novel.cover = ''; - * novel.summary = ''; - * novel.author = ''; - * novel.artist = ''; - * novel.status = ''; - * novel.genres = ''; join by commas. For example: 'romcom, action, school' - */ - - /* - Do something.... - chapter = { - name: '', - url: '', must be absoulute - releaseTime: '', - } - novel.chapters.push(chapter); - */ - - return novel; -}; - -async function parseChapter (chapterUrl) { - // Do something... - const chapterText = ''; - return chapterText; -}; - - -async function searchNovels (searchTerm, pageNo) { - const novels = []; - /* - Do something.... - novel = { - name: '', - url: '', must be absolute - cover: '', - } - novels.push(novel); - */ - return novels; -}; - -async function fetchImage (url) { - // Some site cant fetch images normally (maybe need some headers) - // Must return base64 of image - return await fetchFile(url, {}); -}; - -module.exports = { - id, // string and must be unique - name: 'Source name', - icon, /** The relative path to the icon without @icons . For example: 'src/vi/hakolightnovel/icon.png' */ - version, // xx.xx.xx - site, - lang: languages.English, // must be included in languages - description: 'This is descriptions', - protected: false, // true if this site protect its resources (images) and you have to define headers or smt to bypass - filters, // optional - fetchImage, - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, -} \ No newline at end of file diff --git a/template.min.js b/template.min.js deleted file mode 100644 index 964c413..0000000 --- a/template.min.js +++ /dev/null @@ -1,60 +0,0 @@ -const cheerio = require('cheerio'); -const languages = require('@libs/languages'); -const fetchApi = require('@libs/fetchApi'); -const fetchFile = require('@libs/fetchFile'); -// const dayjs = require('dayjs'); -// const FilterInputs = require('@libs/filterInputs'); -// const novelStatus = require('@libs/novelStatus'); -// const isUrlAbsolute = require('@libs/isAbsoluteUrl'); -// const parseDate = require('@libs/parseDate'); - - -async function popularNovels (pageNo) { - const novels = []; - - return novels; -}; - -async function parseNovelAndChapters (novelUrl) { - const novel = { - url: novelUrl, - chapters: [], - }; - - return novel; -}; - -async function parseChapter (chapterUrl) { - const chapterText = ''; - - return chapterText; -}; - - -async function searchNovels (searchTerm, pageNo) { - const novels = []; - - return novels; -}; - -async function fetchImage (url) { - - return await fetchFile(url, {}); -}; - -module.exports = { - id, - name, - icon, - version, - site, - lang: languages.English, - // description, - // protected, - // filters, - fetchImage, - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, -} \ No newline at end of file diff --git a/template.min.ts b/template.min.ts new file mode 100644 index 0000000..c00557b --- /dev/null +++ b/template.min.ts @@ -0,0 +1,55 @@ +// import { load as parseHTML } from "cheerio"; +// import dayjs from 'dayjs'; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +// import { parseMadaraDate } from "@libs/parseMadaraDate"; +// import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; +// import { showToast } from "@libs/showToast"; +// import { Filter, FilterInputs } from "@libs/filterInputs"; +// import { NovelStatus } from '@libs/novelStatus'; +// import { defaultCover } from "@libs/defaultCover"; + + +export const id = ""; +export const name = "Source name"; +export const icon = ""; +export const version = "0.0.0"; +export const site = ""; +// export const filters: Filter[] = []; +exports["protected"] = false; + +export const popularNovels: Plugin.popularNovels = async function ( + page, + { filters, showLatestNovels } +) { + const novels: Novel.Item[] = []; + + return novels; +}; + + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const novel: Novel.instance = { + url: novelUrl, + chapters: [], + }; + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + const chapterText = ""; + + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async (searchTerm) => { + const novels: Novel.Item[] = []; + + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async (url) => { + return await fetchFile(url, {}); +}; diff --git a/template.ts b/template.ts new file mode 100644 index 0000000..17cac30 --- /dev/null +++ b/template.ts @@ -0,0 +1,91 @@ +// import { load as parseHTML } from "cheerio"; +// import dayjs from 'dayjs'; +import { fetchFile, fetchApi } from "@libs/fetch"; +import { Novel, Plugin, Chapter } from "@typings/plugin"; +// import { parseMadaraDate } from "@libs/parseMadaraDate"; +// import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; +// import { showToast } from "@libs/showToast"; +// import { Filter, FilterInputs } from "@libs/filterInputs"; +// import { NovelStatus } from '@libs/novelStatus'; +// import { defaultCover } from "@libs/defaultCover"; + +export const id = ""; // string and must be unique +export const name = "Source name"; +export const icon = ""; // The relative path to the icon without @icons . For example: 'src/vi/hakolightnovel/icon.png' +export const version = "0.0.0"; // xx.xx.xx +export const site = ""; // the link to the site +// export const filters: Filter[] = []; +exports["protected"] = false; // true if this site protect its resources (images) and you have to define headers or smt to bypass + +export const popularNovels: Plugin.popularNovels = async function ( + page, + { filters, showLatestNovels } +) { + const novels: Novel.Item[] = []; + /* + Do something.... + novel = { + name: '', + url: '', must be absolute + cover: '', + } + novels.push(novel); + */ + return novels; +}; + +export const parseNovelAndChapters: Plugin.parseNovelAndChapters = + async function (novelUrl) { + const novel: Novel.instance = { + url: novelUrl, + chapters: [], + }; + + /** + * novel.name = ''; + * novel.cover = ''; + * novel.summary = ''; + * novel.author = ''; + * novel.artist = ''; + * novel.status = ''; + * novel.genres = ''; join by commas. For example: 'romcom, action, school' + */ + + /* + Do something.... + chapter = { + name: '', chapter name should contain vol, chap num (eg: vol 1 chap 11.5: abcxyz) - it helps a alot in migation + url: '', must be absoulute + releaseTime: '', + } + novel.chapters.push(chapter); + */ + + return novel; + }; + +export const parseChapter: Plugin.parseChapter = async function (chapterUrl) { + // Do something... + const chapterText = ""; + return chapterText; +}; + +export const searchNovels: Plugin.searchNovels = async function (searchTerm) { + const novels: Novel.Item[] = []; + /* + Do something.... + const novel = { + name: '', + url: '', must be absolute + cover: '', + } + novels.push(novel); + */ + return novels; +}; + +export const fetchImage: Plugin.fetchImage = async function (url) { + // Some site cant fetch images normally (maybe need some headers) + // Must return base64 of image + return await fetchFile(url, {}); +}; diff --git a/test_web/api/plugins.ts b/test_web/api/plugins.ts new file mode 100644 index 0000000..3eeeb6d --- /dev/null +++ b/test_web/api/plugins.ts @@ -0,0 +1,76 @@ +import fs from "fs"; +import { languages } from "@libs/languages"; +import path from "path"; +import { PluginList } from "@typings/types"; +import { Plugin, isPlugin } from "@typings/plugin"; +const root = path.dirname(require?.main?.filename || ""); + +export const all_plugins = (): PluginList => { + const res: PluginList = {}; + for (let languageEnglish in languages) { + //language with English name + const languageNative = + languages[languageEnglish as keyof typeof languages]; + const langPath = path.join( + root, + "plugins", + languageEnglish.toLowerCase() + ); + if (!fs.existsSync(langPath)) continue; + const plugins = fs.readdirSync(langPath); + res[languageNative] = []; + plugins.forEach((plugin) => { + if (plugin.startsWith(".")) return; + const requirePath = `@plugins/${languageEnglish.toLowerCase()}/${ + plugin.split(".")[0] + }`; + const instance = require(requirePath); + const { id, name, version } = instance; + const info: Plugin.Info = { + id, + name, + lang: languageNative, + version, + requirePath, + }; // lang: language with native name + res[info.lang].push(info); + }); + } + return res; +}; + +const getPlugin = async ( + requirePath: string +): Promise => { + const plugin = await require(requirePath); + if (isPlugin(plugin)) return plugin; + return null; +}; + +export const popularNovels = async ( + pluginRequirePath: string, + options: Plugin.Options +) => { + const plugin = await getPlugin(pluginRequirePath); + if (!plugin) return null; + const popularNovels = await plugin.popularNovels(1, options); + return popularNovels; +}; + +export const searchNovels = async ( + pluginRequirePath: string, + searchTerm: string +) => (await getPlugin(pluginRequirePath))?.searchNovels(searchTerm); + +export const parseNovelAndChapters = async ( + pluginRequirePath: string, + novelUrl: string +) => (await getPlugin(pluginRequirePath))?.parseNovelAndChapters(novelUrl); + +export const parseChapter = async ( + pluginRequirePath: string, + chapterUrl: string +) => (await getPlugin(pluginRequirePath))?.parseChapter(chapterUrl); + +export const fetchImage = async (pluginRequirePath: string, url: string) => + (await getPlugin(pluginRequirePath))?.fetchImage(url); diff --git a/test_web/index.html b/test_web/index.html index 9b0d835..177f13a 100644 --- a/test_web/index.html +++ b/test_web/index.html @@ -90,7 +90,7 @@
-
diff --git a/test_web/static/js/index.js b/test_web/static/js/index.js index 0fb5e22..b9196d3 100644 --- a/test_web/static/js/index.js +++ b/test_web/static/js/index.js @@ -39,7 +39,7 @@ plugin_search.focus(e => { }); plugin_search.keyup(e => { - display_search(current_array.filter(plg => plg.name.toLowerCase().includes(plugin_search.val()))); + display_search(current_array.filter(plg => plg.name.toLowerCase().includes(plugin_search.val().toLowerCase()))); }); $('#clear-search').click(e => { diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..fccaf81 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,123 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + // "lib": [ + // "es2016", + // "dom", + // "es5" + // ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "CommonJS" /* Specify what module code is generated. */, + // "rootDir": "./", /* Specify the root folder within your source files. */ + "moduleResolution": "Node16" /* Specify how TypeScript looks up a file from a given module specifier. */, + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + "paths": { + "@libs/*": ["./libs/*"], + "@icons/*": ["./icons/*"], + "@plugins/*": ["./plugins/*"], + "@scripts/*": ["./scripts/*"], + "@typings/*": ["./types/*"] + } /* Specify a set of entries that re-map imports to additional lookup locations. */, + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + "resolveJsonModule": true /* Enable importing .json files. */, + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */, + // "checkJs": true /* Enable error reporting in type-checked JavaScript files. */, + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./js/" /* Specify an output folder for all emitted files. */, + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + "allowJs": true, + "checkJs": false, + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + + /* Type Checking */ + "strict": true /* Enable all strict type-checking options. */, + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + }, + "exclude": ["./plugins/**/*.broken.ts", "./js/**/*"] +} diff --git a/types/plugin.ts b/types/plugin.ts new file mode 100644 index 0000000..c87482e --- /dev/null +++ b/types/plugin.ts @@ -0,0 +1,111 @@ +import { Filter } from "@libs/filterInputs"; +import { languages } from "@libs/languages"; +import { NodeFetchParams } from "@libs/nodeFetch"; + +export namespace Chapter { + export interface Item { + name: string; + url: string; + releaseTime?: string | null; + } + export interface instance { + sourceId: number; + novelUrl: string; + chapterUrl: string; + name?: string; + chapterText?: string; + } +} + +export namespace Novel { + export interface Item { + name: string; + url: string; + cover?: string; + } + export interface instance { + url: string; + name?: string; + cover?: string; + genres?: string; + summary?: string; + author?: string; + status?: string; + chapters?: Chapter.Item[]; + } +} + +export namespace Plugin { + export interface Options { + showLatestNovels?: boolean; + filters?: Record; + } + export interface Info { + id: string; + name: string; + lang: (typeof languages)[keyof typeof languages]; + version: string; + requirePath: string; + } + export interface instance { + id: string; + name: string; + version: string; + icon: string; + site: string; + protected?: boolean; + popularNovels: popularNovels; + parseNovelAndChapters: parseNovelAndChapters; + parseChapter: parseChapter; + searchNovels: searchNovels; + fetchImage: fetchImage; + filters?: Filter[]; + } + export type popularNovels = ( + page: number, + options: Plugin.Options + ) => Promise; + export type PopularNovelsResponse = Novel.Item[]; + export type parseNovelAndChapters = ( + novelUrl: string + ) => Promise; + export type parseChapter = (chapterUrl: string) => Promise; + export type searchNovels = (searchTerm: string) => Promise; + export type fetchImage = ( + ...params: NodeFetchParams + ) => Promise; +} + +export const isPlugin = (p: any): p is Plugin.instance => { + const pl = p as Plugin.instance; + + const errorOut = (key: string) => { + console.error(`Plugin ${pl.name} doesn't have ${key}!`); + return false; + }; + + const required_funcs: (keyof Plugin.instance)[] = [ + "popularNovels", + "parseNovelAndChapters", + "parseChapter", + "searchNovels", + "fetchImage", + ]; + for (let i = 0; i < required_funcs.length; i++) { + const key = required_funcs[i]; + if (!pl[key] || typeof pl[key] !== "function") return errorOut(key); + } + const requireds_fields: (keyof Plugin.instance)[] = [ + "id", + "name", + "version", + "icon", + "site", + ]; + for (let i = 0; i < requireds_fields.length; i++) { + const key = requireds_fields[i]; + if (pl[key] === undefined) return errorOut(key); + } + + return true; +}; diff --git a/types/types.ts b/types/types.ts new file mode 100644 index 0000000..4692a45 --- /dev/null +++ b/types/types.ts @@ -0,0 +1,5 @@ +import { Plugin } from "@typings/plugin"; + +export type PluginList = { + [key: string]: Plugin.Info[]; +};