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[];
+};