diff --git a/.env b/.env new file mode 100644 index 0000000..b78a910 --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +GITHUB_USERNAME='nyagami' +GITHUB_REPOSITORY='plugins' +GITHUB_BRANCH='main' \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6c02cb0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules/**/* +.vscode diff --git a/README.md b/README.md index c71dd2b..5d4dd33 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,58 @@ -# LNReader Sources + + +# LNReader Plugins

GitHub issues by-label GitHub issues by-label

-Repository to host source related issues, and requests for [LNReader](https://github.com/LNReader/lnreader). +Repository to host plugins and related issues, and requests for [LNReader](https://github.com/LNReader/lnreader). +## Contributing +1. Please use this [template](./template.js) or [template.min](./template.min.js) +2. Choose your language in [./plugins](./plugins) ++ If your language doenst exist, please request us. We will add it soon. +3. Write your scripts ++ See example: [Hako](./plugins/vietnamese/LNHako.js) + +## Test your script +We use expressjs to make an UI web for testing. +1. Installing + ``` + npm install + ``` +2. Running + ``` + nodemon index.js + ``` + + Then open http://localhost:3000 +3. Testing + + Find you plugin + + Check if all functions of your plugin work properly +#### 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) +``` +GITHUB_USERNAME='nyagami' +GITHUB_REPOSITORY='plugins' +GITHUB_BRANCH='main' +``` + +in [pluginManager.ts](https://github.com/nyagami/lnreader/blob/install_sources/src/plugins/pluginManager.ts) +```ts +const fetchPlugins = async () => { + const availablePlugins: Record> = await fetch( + 'https://raw.githubusercontent.com/nyagami/plugins/main/plugins/plugins.min.json', + ).then(res => res.json()); + return availablePlugins; +}; +``` + +---------- The developer of this application does not have any affiliation with the content providers available. diff --git a/api/plugins.js b/api/plugins.js new file mode 100644 index 0000000..a020c3f --- /dev/null +++ b/api/plugins.js @@ -0,0 +1,60 @@ +const fs = require('fs'); +const languages = require('@libs/languages'); +const path = require('path') +const root = path.dirname(require.main.filename); + +const githubIconsLink = 'https://raw.githubusercontent.com/nyagami/plugins/main/icons'; +const githubIconSuffix = '?raw=true'; +const githubPluginsLink = 'https://raw.githubusercontent.com/nyagami/plugins/main/plugins'; +const githubPluginSuffix = '?newtest=true'; + +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, icon, description } = instance; + const info = { id, name, lang, version, description } // lang: language with native name + info.url = `${githubPluginsLink}/${language.toLowerCase()}/${plugin}${githubPluginSuffix}`; + info.iconUrl = `${githubIconsLink}/${icon}${githubIconSuffix}`; + 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 new file mode 100644 index 0000000..5132f98 --- /dev/null +++ b/cheerio_space/source.html @@ -0,0 +1,14 @@ + + + + + + + + + + + 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 new file mode 100644 index 0000000..925eb83 --- /dev/null +++ b/cheerio_space/test.js @@ -0,0 +1,6 @@ +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/icons/src/kr/agitoon/agit.png b/icons/src/kr/agitoon/agit.png new file mode 100644 index 0000000..9c623d9 Binary files /dev/null and b/icons/src/kr/agitoon/agit.png differ diff --git a/icons/src/pt/reaperscans/webicon.png b/icons/src/pt/reaperscans/webicon.png new file mode 100644 index 0000000..bff0874 Binary files /dev/null and b/icons/src/pt/reaperscans/webicon.png differ diff --git a/index.js b/index.js new file mode 100644 index 0000000..34cae07 --- /dev/null +++ b/index.js @@ -0,0 +1,47 @@ +require('module-alias/register'); + +const path = require('path'); +const express = require('express'); +const bodyParser = require('body-parser'); +const pluginApi = require('@api/plugins'); + +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"); +}); \ No newline at end of file diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..a6b1ab6 --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@api/*": [ + "./api/*", + ], + "@libs/*": [ + "./libs/*", + ], + "@icons/*": [ + "./icons/*", + ], + "@plugins/*": [ + "./plugins/*", + ], + "@scripts/*": [ + "./scripts/*", + ] + }, + }, +} \ No newline at end of file diff --git a/libs/fetchApi.js b/libs/fetchApi.js new file mode 100644 index 0000000..78b564e --- /dev/null +++ b/libs/fetchApi.js @@ -0,0 +1,14 @@ +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 new file mode 100644 index 0000000..86c9d3f --- /dev/null +++ b/libs/fetchFile.js @@ -0,0 +1,15 @@ +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 new file mode 100644 index 0000000..06b6427 --- /dev/null +++ b/libs/filterInputs.js @@ -0,0 +1,7 @@ +const FilterInputs = { + TextInput: 0, + Picker: 1, + Checkbox: 2, +} + +module.exports = FilterInputs diff --git a/libs/isAbsoluteUrl.js b/libs/isAbsoluteUrl.js new file mode 100644 index 0000000..11d6435 --- /dev/null +++ b/libs/isAbsoluteUrl.js @@ -0,0 +1,25 @@ +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/languages.js b/libs/languages.js new file mode 100644 index 0000000..1e91d33 --- /dev/null +++ b/libs/languages.js @@ -0,0 +1,14 @@ +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/novelStatus.js b/libs/novelStatus.js new file mode 100644 index 0000000..69a9a03 --- /dev/null +++ b/libs/novelStatus.js @@ -0,0 +1,9 @@ +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/parseDate.js b/libs/parseDate.js new file mode 100644 index 0000000..5f481a3 --- /dev/null +++ b/libs/parseDate.js @@ -0,0 +1,30 @@ +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/nodemon.json b/nodemon.json new file mode 100644 index 0000000..d7f117e --- /dev/null +++ b/nodemon.json @@ -0,0 +1,3 @@ +{ + "ignore": ["plugins/plugins.json", "plugins/plugins.min.json", "cheerio_space/*"] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c5ce302 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2897 @@ +{ + "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", + "dotenv": "^16.0.3", + "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/dotenv": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", + "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", + "engines": { + "node": ">=12" + } + }, + "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" + }, + { + "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" + }, + { + "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" + }, + { + "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/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" + } + }, + "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==", + "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" + } + }, + "dotenv": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", + "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==" + }, + "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": { + "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==" + } + } + }, + "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==" + } + } + } + } + }, + "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": { + "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==", + "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 new file mode 100644 index 0000000..2bd9b28 --- /dev/null +++ b/package.json @@ -0,0 +1,37 @@ +{ + "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 ." + }, + "author": "", + "license": "MIT", + "dependencies": { + "body-parser": "^1.20.2", + "cheerio": "^1.0.0-rc.10", + "cli-color": "^2.0.3", + "dayjs": "^1.11.4", + "dotenv": "^16.0.3", + "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" + ] +} diff --git a/plugins/chinese/.chinese b/plugins/chinese/.chinese new file mode 100644 index 0000000..e69de29 diff --git a/plugins/english/.english b/plugins/english/.english new file mode 100644 index 0000000..e69de29 diff --git a/plugins/english/LightNovelReader.js b/plugins/english/LightNovelReader.js new file mode 100644 index 0000000..91e4126 --- /dev/null +++ b/plugins/english/LightNovelReader.js @@ -0,0 +1,178 @@ +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/NovelFullMe.js b/plugins/english/NovelFullMe.js new file mode 100644 index 0000000..018a10e --- /dev/null +++ b/plugins/english/NovelFullMe.js @@ -0,0 +1,167 @@ +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/NovelOnline.js b/plugins/english/NovelOnline.js new file mode 100644 index 0000000..698ea01 --- /dev/null +++ b/plugins/english/NovelOnline.js @@ -0,0 +1,120 @@ +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/allnovelfull.js b/plugins/english/allnovelfull.js new file mode 100644 index 0000000..b1240a5 --- /dev/null +++ b/plugins/english/allnovelfull.js @@ -0,0 +1,171 @@ +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/bestlightnovel.js b/plugins/english/bestlightnovel.js new file mode 100644 index 0000000..c88e0cd --- /dev/null +++ b/plugins/english/bestlightnovel.js @@ -0,0 +1,143 @@ +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/comrademao.js b/plugins/english/comrademao.js new file mode 100644 index 0000000..0d323e1 --- /dev/null +++ b/plugins/english/comrademao.js @@ -0,0 +1,149 @@ +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/divinedaolibrary.js b/plugins/english/divinedaolibrary.js new file mode 100644 index 0000000..4f6870d --- /dev/null +++ b/plugins/english/divinedaolibrary.js @@ -0,0 +1,155 @@ +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/foxaholic.js b/plugins/english/foxaholic.js new file mode 100644 index 0000000..6ad359c --- /dev/null +++ b/plugins/english/foxaholic.js @@ -0,0 +1,162 @@ +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/freewebnovel.js b/plugins/english/freewebnovel.js new file mode 100644 index 0000000..f93cd68 --- /dev/null +++ b/plugins/english/freewebnovel.js @@ -0,0 +1,173 @@ +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/novelpub.js b/plugins/english/novelpub.js new file mode 100644 index 0000000..6e6c9e0 --- /dev/null +++ b/plugins/english/novelpub.js @@ -0,0 +1,182 @@ +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/french/.french b/plugins/french/.french new file mode 100644 index 0000000..e69de29 diff --git a/plugins/french/chireads.js b/plugins/french/chireads.js new file mode 100644 index 0000000..42d012b --- /dev/null +++ b/plugins/french/chireads.js @@ -0,0 +1,126 @@ +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/noveldeglace.js b/plugins/french/noveldeglace.js new file mode 100644 index 0000000..562aaf8 --- /dev/null +++ b/plugins/french/noveldeglace.js @@ -0,0 +1,147 @@ +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/.indonesian b/plugins/indonesian/.indonesian new file mode 100644 index 0000000..e69de29 diff --git a/plugins/indonesian/indowebnovel.js b/plugins/indonesian/indowebnovel.js new file mode 100644 index 0000000..053de72 --- /dev/null +++ b/plugins/indonesian/indowebnovel.js @@ -0,0 +1,142 @@ +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/novelringan.js b/plugins/indonesian/novelringan.js new file mode 100644 index 0000000..bcc7183 --- /dev/null +++ b/plugins/indonesian/novelringan.js @@ -0,0 +1,162 @@ +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/sakuranovel.js b/plugins/indonesian/sakuranovel.js new file mode 100644 index 0000000..f0a6365 --- /dev/null +++ b/plugins/indonesian/sakuranovel.js @@ -0,0 +1,151 @@ +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/japanese/.japanese b/plugins/japanese/.japanese new file mode 100644 index 0000000..e69de29 diff --git a/plugins/japanese/Syosetu.js b/plugins/japanese/Syosetu.js new file mode 100644 index 0000000..4825255 --- /dev/null +++ b/plugins/japanese/Syosetu.js @@ -0,0 +1,205 @@ +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/korean/Agitoon.js b/plugins/korean/Agitoon.js new file mode 100644 index 0000000..d099189 --- /dev/null +++ b/plugins/korean/Agitoon.js @@ -0,0 +1,155 @@ +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/plugins.json b/plugins/plugins.json new file mode 100644 index 0000000..293022f --- /dev/null +++ b/plugins/plugins.json @@ -0,0 +1,287 @@ +{ + "中文, 汉语, 漢語": [], + "English": [ + { + "id": "LNR.org", + "name": "LightNovelReader", + "site": "https://lightnovelreader.org", + "lang": "English", + "version": "1.0.0", + "description": "This is description for plugin", + "url": "https://raw.githubusercontent.com/nyagami/plugins/main/plugins/english/LightNovelReader.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/en/lightnovelreader/icon.png" + }, + { + "id": "NF.me", + "name": "NovelFull.me", + "site": "https://novelfull.me/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/main/plugins/english/NovelFullMe.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/en/novelfullme/icon.png" + }, + { + "id": "NO.net", + "name": "novelsOnline", + "site": "https://novelsonline.net", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/main/plugins/english/NovelOnline.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/coverNotAvailable.jpg" + }, + { + "id": "ANF.com", + "name": "AllNovelFull", + "site": "https://allnovelfull.com", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/main/plugins/english/allnovelfull.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/main/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/main/plugins/english/bestlightnovel.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/main/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/nyagami/plugins/main/plugins/english/comrademao.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/main/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/nyagami/plugins/main/plugins/english/divinedaolibrary.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/en/divinedaolibrary/icon.png" + }, + { + "id": "foxaholic", + "name": "Foxaholic", + "site": "https://www.foxaholic.com/", + "lang": "English", + "version": "1.0.0", + "description": "Digging Pits", + "url": "https://raw.githubusercontent.com/nyagami/plugins/main/plugins/english/foxaholic.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/en/foxaholic/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/main/plugins/english/freewebnovel.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/en/freewebnovel/icon.png" + }, + { + "id": "novelpub.com", + "name": "NovelPub", + "site": "https://www.novelpub.com/", + "lang": "English", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/main/plugins/english/novelpub.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/en/novelpub/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/nyagami/plugins/main/plugins/french/chireads.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/main/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/nyagami/plugins/main/plugins/french/noveldeglace.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/main/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/nyagami/plugins/main/plugins/indonesian/indowebnovel.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/main/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/nyagami/plugins/main/plugins/indonesian/novelringan.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/main/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/main/plugins/indonesian/sakuranovel.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/id/sakuranovel/icon.png" + } + ], + "日本語": [ + { + "id": "yomou.syosetu", + "name": "Syosetu", + "site": "https://yomou.syosetu.com/", + "lang": "日本語", + "version": "1.0.0", + "description": "小説を読もう!は「小説家になろう」に投稿された", + "url": "https://raw.githubusercontent.com/nyagami/plugins/main/plugins/japanese/Syosetu.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/main/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/nyagami/plugins/main/plugins/korean/Agitoon.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/kr/agitoon/agit.png" + } + ], + "Português": [], + "русский язык": [ + { + "id": "jaomix.ru", + "name": "Jaomix", + "site": "https://jaomix.ru", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/main/plugins/russian/jaomix.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/ru/jaomix/icon.png" + }, + { + "id": "RNBH.org", + "name": "RanobeHub", + "site": "https://ranobehub.org/", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/main/plugins/russian/ranobehub.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/ru/ranobehub/icon.png" + }, + { + "id": "RNRF", + "name": "РанобэРФ", + "site": "https://ранобэ.рф", + "lang": "русский язык", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/main/plugins/russian/ranoberf.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/ru/ranoberf/icon.png" + } + ], + "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/main/plugins/spanish/hasutl.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/main/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/main/plugins/spanish/novelasligera.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/es/novelasligera/icon.png" + }, + { + "id": "awuxia.com", + "name": "ReinoWuxia", + "site": "http://www.reinowuxia.com/", + "lang": "español, castellano", + "version": "1.0.0", + "url": "https://raw.githubusercontent.com/nyagami/plugins/main/plugins/spanish/novelawuxia.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/es/novelawuxia/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/nyagami/plugins/main/plugins/spanish/oasistranslations.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/main/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/nyagami/plugins/main/plugins/spanish/skynovels.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/main/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/nyagami/plugins/main/plugins/spanish/tunovelaligera.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/main/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/main/plugins/spanish/yukitls.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/main/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/main/plugins/turkish/epiknovel.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/tr/epiknovel/icon.png" + } + ], + "Tiếng Việt": [ + { + "id": "ln.hako", + "name": "Hako", + "site": "https://ln.hako.vn", + "lang": "Tiếng Việt", + "version": "1.0.0", + "description": "Cổng LightNovel", + "url": "https://raw.githubusercontent.com/nyagami/plugins/main/plugins/vietnamese/LNHako.js", + "iconUrl": "https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/vi/hakolightnovel/icon.png" + } + ] +} \ No newline at end of file diff --git a/plugins/plugins.min.json b/plugins/plugins.min.json new file mode 100644 index 0000000..e465c27 --- /dev/null +++ b/plugins/plugins.min.json @@ -0,0 +1 @@ +{"中文, 汉语, 漢語":[],"English":[{"id":"LNR.org","name":"LightNovelReader","site":"https://lightnovelreader.org","lang":"English","version":"1.0.0","description":"This is description for plugin","url":"https://raw.githubusercontent.com/nyagami/plugins/main/plugins/english/LightNovelReader.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/en/lightnovelreader/icon.png"},{"id":"NF.me","name":"NovelFull.me","site":"https://novelfull.me/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/main/plugins/english/NovelFullMe.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/en/novelfullme/icon.png"},{"id":"NO.net","name":"novelsOnline","site":"https://novelsonline.net","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/main/plugins/english/NovelOnline.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/coverNotAvailable.jpg"},{"id":"ANF.com","name":"AllNovelFull","site":"https://allnovelfull.com","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/main/plugins/english/allnovelfull.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/main/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/main/plugins/english/bestlightnovel.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/main/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/nyagami/plugins/main/plugins/english/comrademao.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/main/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/nyagami/plugins/main/plugins/english/divinedaolibrary.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/en/divinedaolibrary/icon.png"},{"id":"foxaholic","name":"Foxaholic","site":"https://www.foxaholic.com/","lang":"English","version":"1.0.0","description":"Digging Pits","url":"https://raw.githubusercontent.com/nyagami/plugins/main/plugins/english/foxaholic.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/en/foxaholic/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/main/plugins/english/freewebnovel.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/en/freewebnovel/icon.png"},{"id":"novelpub.com","name":"NovelPub","site":"https://www.novelpub.com/","lang":"English","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/main/plugins/english/novelpub.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/en/novelpub/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/nyagami/plugins/main/plugins/french/chireads.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/main/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/nyagami/plugins/main/plugins/french/noveldeglace.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/main/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/nyagami/plugins/main/plugins/indonesian/indowebnovel.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/main/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/nyagami/plugins/main/plugins/indonesian/novelringan.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/main/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/main/plugins/indonesian/sakuranovel.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/id/sakuranovel/icon.png"}],"日本語":[{"id":"yomou.syosetu","name":"Syosetu","site":"https://yomou.syosetu.com/","lang":"日本語","version":"1.0.0","description":"小説を読もう!は「小説家になろう」に投稿された","url":"https://raw.githubusercontent.com/nyagami/plugins/main/plugins/japanese/Syosetu.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/main/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/nyagami/plugins/main/plugins/korean/Agitoon.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/kr/agitoon/agit.png"}],"Português":[],"русский язык":[{"id":"jaomix.ru","name":"Jaomix","site":"https://jaomix.ru","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/main/plugins/russian/jaomix.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/ru/jaomix/icon.png"},{"id":"RNBH.org","name":"RanobeHub","site":"https://ranobehub.org/","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/main/plugins/russian/ranobehub.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/ru/ranobehub/icon.png"},{"id":"RNRF","name":"РанобэРФ","site":"https://ранобэ.рф","lang":"русский язык","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/main/plugins/russian/ranoberf.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/ru/ranoberf/icon.png"}],"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/main/plugins/spanish/hasutl.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/main/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/main/plugins/spanish/novelasligera.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/es/novelasligera/icon.png"},{"id":"awuxia.com","name":"ReinoWuxia","site":"http://www.reinowuxia.com/","lang":"español, castellano","version":"1.0.0","url":"https://raw.githubusercontent.com/nyagami/plugins/main/plugins/spanish/novelawuxia.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/es/novelawuxia/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/nyagami/plugins/main/plugins/spanish/oasistranslations.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/main/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/nyagami/plugins/main/plugins/spanish/skynovels.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/main/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/nyagami/plugins/main/plugins/spanish/tunovelaligera.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/main/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/main/plugins/spanish/yukitls.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/main/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/main/plugins/turkish/epiknovel.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/tr/epiknovel/icon.png"}],"Tiếng Việt":[{"id":"ln.hako","name":"Hako","site":"https://ln.hako.vn","lang":"Tiếng Việt","version":"1.0.0","description":"Cổng LightNovel","url":"https://raw.githubusercontent.com/nyagami/plugins/main/plugins/vietnamese/LNHako.js","iconUrl":"https://raw.githubusercontent.com/nyagami/plugins/main/icons/src/vi/hakolightnovel/icon.png"}]} \ No newline at end of file diff --git a/plugins/portuguese/.portuguese b/plugins/portuguese/.portuguese new file mode 100644 index 0000000..e69de29 diff --git a/plugins/russian/.russian b/plugins/russian/.russian new file mode 100644 index 0000000..e69de29 diff --git a/plugins/russian/jaomix.js b/plugins/russian/jaomix.js new file mode 100644 index 0000000..18907ed --- /dev/null +++ b/plugins/russian/jaomix.js @@ -0,0 +1,217 @@ +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 += filters?.sort ? filters?.sort : (showLatestNovels ? 'upd' : '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 novelName = loadedCheerio(this) + .find('div[class="img-home"] > a') + .attr('title'); + const novelCover = loadedCheerio(this) + .find('div[class="img-home"] > a > img') + .attr('src') + .replace('-150x150', ''); + const novelUrl = loadedCheerio(this) + .find('div[class="img-home"] > 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); + 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 novelName = loadedCheerio(this) + .find('div[class="img-home"] > a') + .attr('title'); + const novelCover = loadedCheerio(this) + .find('div[class="img-home"] > a > img') + .attr('src') + .replace('-150x150', ''); + const novelUrl = loadedCheerio(this) + .find('div[class="img-home"] > a') + .attr('href'); + + const novel = {name: novelName, cover: novelCover, url: novelUrl }; + novels.push(novel); + }); + + 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: 'Adult', value: 'Adult' }, + { label: 'Ecchi', value: 'Ecchi' }, + { label: 'Josei', value: 'Josei' }, + { label: 'Lolicon', value: 'Lolicon' }, + { label: 'Mature', value: 'Mature' }, + { label: 'Sci-fi', value: 'Sci-fi' }, + { label: 'Shoujo', value: 'Shoujo' }, + { label: 'Wuxia', value: 'Wuxia' }, + { label: 'Xianxia', value: 'Xianxia' }, + { label: 'Xuanhuan', value: 'Xuanhuan' }, + { label: 'Yaoi', value: 'Yaoi' }, + { 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: 'Этти' }, + ], + 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, +}; \ No newline at end of file diff --git a/plugins/russian/ranobehub.js b/plugins/russian/ranobehub.js new file mode 100644 index 0000000..044f702 --- /dev/null +++ b/plugins/russian/ranobehub.js @@ -0,0 +1,1131 @@ +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 += filters?.sort ? filters?.sort : + (showLatestNovels ? 'last_chapter_at' : '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 => { + const novelName = novel.names.rus; + const novelCover = novel.poster.medium; + const novelUrl = novel.url; + + novels.push({ + name: novelName, + cover: novelCover, + url: novelUrl, + }); + }); + 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); + + let chapterText = loadedCheerio( + 'body > div.pusher.container_main > div:nth-child(5) > div: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: '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: '655' }, + { label: 'Главный герой играет роль', value: '396' }, + { label: 'Главный герой носит очки', value: '637' }, + { label: 'Главный герой пацифист', value: '675' }, + { label: 'Главный герой с несколькими телами', value: '628' }, + { label: 'Главный герой силен с самого начала', value: '45' }, + { 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: '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: '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: '664' }, + { 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: '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: '[Награжденная работа]', value: '611' }, + { label: 'Ёкаи', value: '516' }, + { label: '18+', value: '338' }, + { 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: '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: '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: '15' }, + { label: 'Сёдзё-ай', value: '23' }, + { label: 'Сёнэн', value: '189' }, + { label: 'Сёнэн-ай', value: '680' }, + { label: 'Сверхъестественное', value: '20' }, + { 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, +}; \ No newline at end of file diff --git a/plugins/russian/ranoberf.js b/plugins/russian/ranoberf.js new file mode 100644 index 0000000..eb24c41 --- /dev/null +++ b/plugins/russian/ranoberf.js @@ -0,0 +1,147 @@ +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 += filters?.sort ? filters?.sort : (showLatestNovels ? 'lastPublishedChapter' : '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(item => { + const novelName = item.title; + const novelUrl = baseUrl + '/' + item.slug; + const novelCover = item?.verticalImage?.url + ? baseUrl + item.verticalImage.url + : defaultCoverUri; + novels.push({ name: novelName, cover: novelCover, url: novelUrl }); + }); + + return novels; +}; + +async function parseNovelAndChapters (novelUrl) { + const result = await fetchApi(novelUrl); + const body = await result.text(); + + const loadedCheerio = cheerio.load(body); + let json = loadedCheerio('#__NEXT_DATA__').html(); + json = JSON.parse(json); + + let novel = { + url: novelUrl, + name: json.props.pageProps.book.title, + cover: json.props.pageProps.book?.verticalImage?.url + ? baseUrl + json.props.pageProps.book.verticalImage.url + : defaultCoverUri, + summary: json.props.pageProps.book.description, + author: json.props.pageProps.book?.author || '', + genres: json.props.pageProps.book.genres.map(item => item.title).join(','), + status: json.props.pageProps.book.additionalInfo.includes('Активен') + ? Status.Ongoing + : Status.Completed, + }; + + let chapters = []; + + json.props.pageProps.book.chapters.forEach(item => { + const chapterName = item.title; + const releaseDate = dayjs(item.publishedAt).format('LLL'); + const chapterUrl = baseUrl + item.url; + + if (!item.isDonate || item.isUserPaid) { + chapters.push({ name: chapterName, releaseTime: releaseDate, url: chapterUrl }); + } + }); + + 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')) { + let 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(item => { + const novelName = item.title; + const novelUrl = baseUrl + '/' + item.slug; + const novelCover = item?.verticalImage?.url + ? baseUrl + item.verticalImage.url + : defaultCoverUri; + novels.push({ name: novelName, cover: novelCover, url: novelUrl }); + }); + + 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, +}; \ No newline at end of file diff --git a/plugins/spanish/.spanish b/plugins/spanish/.spanish new file mode 100644 index 0000000..e69de29 diff --git a/plugins/spanish/hasutl.js b/plugins/spanish/hasutl.js new file mode 100644 index 0000000..0cf9d76 --- /dev/null +++ b/plugins/spanish/hasutl.js @@ -0,0 +1,139 @@ +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/novelasligera.js b/plugins/spanish/novelasligera.js new file mode 100644 index 0000000..bb36648 --- /dev/null +++ b/plugins/spanish/novelasligera.js @@ -0,0 +1,177 @@ +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/novelawuxia.js b/plugins/spanish/novelawuxia.js new file mode 100644 index 0000000..54b4c79 --- /dev/null +++ b/plugins/spanish/novelawuxia.js @@ -0,0 +1,205 @@ +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.js b/plugins/spanish/oasistranslations.js new file mode 100644 index 0000000..aea7afb --- /dev/null +++ b/plugins/spanish/oasistranslations.js @@ -0,0 +1,167 @@ +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.js b/plugins/spanish/skynovels.js new file mode 100644 index 0000000..b38cc96 --- /dev/null +++ b/plugins/spanish/skynovels.js @@ -0,0 +1,126 @@ +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 new file mode 100644 index 0000000..1128293 --- /dev/null +++ b/plugins/spanish/tunovelaligera.js @@ -0,0 +1,164 @@ +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/yukitls.js b/plugins/spanish/yukitls.js new file mode 100644 index 0000000..1222025 --- /dev/null +++ b/plugins/spanish/yukitls.js @@ -0,0 +1,183 @@ +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/turkish/.turkish b/plugins/turkish/.turkish new file mode 100644 index 0000000..e69de29 diff --git a/plugins/turkish/epiknovel.js b/plugins/turkish/epiknovel.js new file mode 100644 index 0000000..c36f3d4 --- /dev/null +++ b/plugins/turkish/epiknovel.js @@ -0,0 +1,167 @@ +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/vietnamese/.vietnamese b/plugins/vietnamese/.vietnamese new file mode 100644 index 0000000..e69de29 diff --git a/plugins/vietnamese/LNHako.js b/plugins/vietnamese/LNHako.js new file mode 100644 index 0000000..5682a96 --- /dev/null +++ b/plugins/vietnamese/LNHako.js @@ -0,0 +1,199 @@ +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: 'Cổng LightNovel', + protected: true, + fetchImage, + popularNovels, + parseNovelAndChapters, + parseChapter, + searchNovels, +}; \ No newline at end of file diff --git a/scripts/json_plugins.js b/scripts/json_plugins.js new file mode 100644 index 0000000..a789906 --- /dev/null +++ b/scripts/json_plugins.js @@ -0,0 +1,40 @@ +require('module-alias/register'); +require('dotenv').config(); +const fs = require('fs'); +const languages = require('@libs/languages'); +const path = require('path') +const root = path.dirname(__dirname); +const username = process.env.GITHUB_USERNAME; +const repo = process.env.GITHUB_REPOSITORY; +const branch = process.env.GITHUB_BRANCH; + +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, 'plugins', 'plugins.json'); +const jsonMinPath = path.join(root, 'plugins', '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); + }); +} +fs.writeFileSync(jsonMinPath, JSON.stringify(json)); +fs.writeFileSync(jsonPath, JSON.stringify(json, null, '\t')); +console.log('Done ✅'); diff --git a/template.js b/template.js new file mode 100644 index 0000000..3bab5e2 --- /dev/null +++ b/template.js @@ -0,0 +1,97 @@ +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 new file mode 100644 index 0000000..964c413 --- /dev/null +++ b/template.min.js @@ -0,0 +1,60 @@ +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/test_web/index.html b/test_web/index.html new file mode 100644 index 0000000..9b0d835 --- /dev/null +++ b/test_web/index.html @@ -0,0 +1,117 @@ + + + + + + + + + + + Testing plugin + + + + +
+
+
+ popularNovels + +
+ Loading... +
+
+
+
+
+
+
+ searchNovels +
+ +
+ +
+
+
+ Loading... +
+
+
+
+
+
+
+ parseNovelAndChapters +
+ +
+ +
+
+
+ Loading... +
+
+
+
+
+
+
+
+
+
+
+ parseChapter +
+ +
+ +
+
+
+ Loading... +
+
+
+ +
+
+
+
+ fetchImage +
+ +
+ +
+
+
+ Loading... +
+
+ +
+
+ + + + + \ No newline at end of file diff --git a/test_web/static/css/bootstrap.min.css b/test_web/static/css/bootstrap.min.css new file mode 100644 index 0000000..6561b6f --- /dev/null +++ b/test_web/static/css/bootstrap.min.css @@ -0,0 +1,7 @@ +/*! + * Bootstrap v4.0.0 (https://getbootstrap.com) + * Copyright 2011-2018 The Bootstrap Authors + * Copyright 2011-2018 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}@-ms-viewport{width:device-width}article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent;-webkit-text-decoration-skip:objects}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg:not(:root){overflow:hidden}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-family:inherit;font-weight:500;line-height:1.2;color:inherit}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014 \00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}code{font-size:87.5%;color:#e83e8c;word-break:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1140px}}.container-fluid{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;min-height:1px;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-sm-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-sm-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-sm-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-sm-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-sm-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-sm-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-sm-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-sm-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-sm-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-sm-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-sm-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-sm-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-sm-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-sm-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-sm-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-sm-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-md-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-md-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-md-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-md-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-md-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-md-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-md-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-md-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-md-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-md-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-md-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-md-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-md-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-md-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-md-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-md-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-lg-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-lg-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-lg-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-lg-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-lg-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-lg-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-lg-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-lg-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-lg-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-lg-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-lg-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-lg-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-lg-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-lg-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-lg-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-lg-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-xl-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-xl-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-xl-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-xl-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-xl-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-xl-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-xl-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-xl-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-xl-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-xl-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-xl-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-xl-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-xl-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-xl-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-xl-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-xl-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;max-width:100%;margin-bottom:1rem;background-color:transparent}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table .table{background-color:#fff}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#212529;border-color:#32383e}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#212529}.table-dark td,.table-dark th,.table-dark thead th{border-color:#32383e}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}select.form-control:not([size]):not([multiple]){height:calc(2.25rem + 2px)}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding-top:.375rem;padding-bottom:.375rem;margin-bottom:0;line-height:1.5;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm,.input-group-lg>.form-control-plaintext.form-control,.input-group-lg>.input-group-append>.form-control-plaintext.btn,.input-group-lg>.input-group-append>.form-control-plaintext.input-group-text,.input-group-lg>.input-group-prepend>.form-control-plaintext.btn,.input-group-lg>.input-group-prepend>.form-control-plaintext.input-group-text,.input-group-sm>.form-control-plaintext.form-control,.input-group-sm>.input-group-append>.form-control-plaintext.btn,.input-group-sm>.input-group-append>.form-control-plaintext.input-group-text,.input-group-sm>.input-group-prepend>.form-control-plaintext.btn,.input-group-sm>.input-group-prepend>.form-control-plaintext.input-group-text{padding-right:0;padding-left:0}.form-control-sm,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-sm>.input-group-append>select.btn:not([size]):not([multiple]),.input-group-sm>.input-group-append>select.input-group-text:not([size]):not([multiple]),.input-group-sm>.input-group-prepend>select.btn:not([size]):not([multiple]),.input-group-sm>.input-group-prepend>select.input-group-text:not([size]):not([multiple]),.input-group-sm>select.form-control:not([size]):not([multiple]),select.form-control-sm:not([size]):not([multiple]){height:calc(1.8125rem + 2px)}.form-control-lg,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-lg>.input-group-append>select.btn:not([size]):not([multiple]),.input-group-lg>.input-group-append>select.input-group-text:not([size]):not([multiple]),.input-group-lg>.input-group-prepend>select.btn:not([size]):not([multiple]),.input-group-lg>.input-group-prepend>select.input-group-text:not([size]):not([multiple]),.input-group-lg>select.form-control:not([size]):not([multiple]),select.form-control-lg:not([size]):not([multiple]){height:calc(2.875rem + 2px)}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.5rem;margin-top:.1rem;font-size:.875rem;line-height:1;color:#fff;background-color:rgba(40,167,69,.8);border-radius:.2rem}.custom-select.is-valid,.form-control.is-valid,.was-validated .custom-select:valid,.was-validated .form-control:valid{border-color:#28a745}.custom-select.is-valid:focus,.form-control.is-valid:focus,.was-validated .custom-select:valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-select.is-valid~.valid-feedback,.custom-select.is-valid~.valid-tooltip,.form-control.is-valid~.valid-feedback,.form-control.is-valid~.valid-tooltip,.was-validated .custom-select:valid~.valid-feedback,.was-validated .custom-select:valid~.valid-tooltip,.was-validated .form-control:valid~.valid-feedback,.was-validated .form-control:valid~.valid-tooltip{display:block}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{background-color:#71dd8a}.custom-control-input.is-valid~.valid-feedback,.custom-control-input.is-valid~.valid-tooltip,.was-validated .custom-control-input:valid~.valid-feedback,.was-validated .custom-control-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(40,167,69,.25)}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid~.custom-file-label::before,.was-validated .custom-file-input:valid~.custom-file-label::before{border-color:inherit}.custom-file-input.is-valid~.valid-feedback,.custom-file-input.is-valid~.valid-tooltip,.was-validated .custom-file-input:valid~.valid-feedback,.was-validated .custom-file-input:valid~.valid-tooltip{display:block}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.5rem;margin-top:.1rem;font-size:.875rem;line-height:1;color:#fff;background-color:rgba(220,53,69,.8);border-radius:.2rem}.custom-select.is-invalid,.form-control.is-invalid,.was-validated .custom-select:invalid,.was-validated .form-control:invalid{border-color:#dc3545}.custom-select.is-invalid:focus,.form-control.is-invalid:focus,.was-validated .custom-select:invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-select.is-invalid~.invalid-feedback,.custom-select.is-invalid~.invalid-tooltip,.form-control.is-invalid~.invalid-feedback,.form-control.is-invalid~.invalid-tooltip,.was-validated .custom-select:invalid~.invalid-feedback,.was-validated .custom-select:invalid~.invalid-tooltip,.was-validated .form-control:invalid~.invalid-feedback,.was-validated .form-control:invalid~.invalid-tooltip{display:block}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{background-color:#efa2a9}.custom-control-input.is-invalid~.invalid-feedback,.custom-control-input.is-invalid~.invalid-tooltip,.was-validated .custom-control-input:invalid~.invalid-feedback,.was-validated .custom-control-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(220,53,69,.25)}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid~.custom-file-label::before,.was-validated .custom-file-input:invalid~.custom-file-label::before{border-color:inherit}.custom-file-input.is-invalid~.invalid-feedback,.custom-file-input.is-invalid~.invalid-tooltip,.was-validated .custom-file-input:invalid~.invalid-feedback,.was-validated .custom-file-input:invalid~.invalid-tooltip{display:block}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .input-group{width:auto}.form-inline .form-check{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;text-align:center;white-space:nowrap;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}.btn:focus,.btn:hover{text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}.btn:not(:disabled):not(.disabled).active,.btn:not(:disabled):not(.disabled):active{background-image:none}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-primary{color:#007bff;background-color:transparent;background-image:none;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;background-color:transparent;background-image:none;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;background-color:transparent;background-image:none;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;background-color:transparent;background-image:none;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;background-color:transparent;background-image:none;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;background-color:transparent;background-image:none;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;background-color:transparent;background-image:none;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;background-color:transparent;background-image:none;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;background-color:transparent}.btn-link:hover{color:#0056b3;text-decoration:underline;background-color:transparent;border-color:transparent}.btn-link.focus,.btn-link:focus{text-decoration:underline;border-color:transparent;box-shadow:none}.btn-link.disabled,.btn-link:disabled{color:#6c757d}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;transition:opacity .15s linear}.fade.show{opacity:1}.collapse{display:none}.collapse.show{display:block}tr.collapse.show{display:table-row}tbody.collapse.show{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}.dropdown,.dropup{position:relative}.dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropup .dropdown-menu{margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;width:0;height:0;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#f8f9fa}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.btn-group,.btn-group-vertical{position:relative;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-webkit-box-flex:0;-ms-flex:0 1 auto;flex:0 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group,.btn-group-vertical .btn+.btn,.btn-group-vertical .btn+.btn-group,.btn-group-vertical .btn-group+.btn,.btn-group-vertical .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after{margin-left:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.btn-group-vertical .btn,.btn-group-vertical .btn-group{width:100%}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control{position:relative;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;margin-bottom:0}.input-group>.custom-file:focus,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control{margin-left:-1px}.input-group>.custom-select:not(:last-child),.input-group>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::before{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label,.input-group>.custom-file:not(:first-child) .custom-file-label::before{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-append,.input-group-prepend{display:-webkit-box;display:-ms-flexbox;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.5rem;padding-left:1.5rem}.custom-control-inline{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;z-index:-1;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;background-color:#007bff}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:active~.custom-control-label::before{color:#fff;background-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{margin-bottom:0}.custom-control-label::before{position:absolute;top:.25rem;left:0;display:block;width:1rem;height:1rem;pointer-events:none;content:"";-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:#dee2e6}.custom-control-label::after{position:absolute;top:.25rem;left:0;display:block;width:1rem;height:1rem;content:"";background-repeat:no-repeat;background-position:center center;background-size:50% 50%}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::before{background-color:#007bff}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='%23fff' d='M0 2h4'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::before{background-color:#007bff}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(2.25rem + 2px);padding:.375rem 1.75rem .375rem .75rem;line-height:1.5;color:#495057;vertical-align:middle;background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") no-repeat right .75rem center;background-size:8px 10px;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:inset 0 1px 2px rgba(0,0,0,.075),0 0 5px rgba(128,189,255,.5)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{opacity:0}.custom-select-sm{height:calc(1.8125rem + 2px);padding-top:.375rem;padding-bottom:.375rem;font-size:75%}.custom-select-lg{height:calc(2.875rem + 2px);padding-top:.375rem;padding-bottom:.375rem;font-size:125%}.custom-file{position:relative;display:inline-block;width:100%;height:calc(2.25rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(2.25rem + 2px);margin:0;opacity:0}.custom-file-input:focus~.custom-file-control{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:focus~.custom-file-control::before{border-color:#80bdff}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(2.25rem + 2px);padding:.375rem .75rem;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(calc(2.25rem + 2px) - 1px * 2);padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:1px solid #ced4da;border-radius:0 .25rem .25rem 0}.nav{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar>.container,.navbar>.container-fluid{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler:not(:disabled):not(.disabled){cursor:pointer}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .dropdown-menu-right{right:0;left:auto}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .dropup .dropdown-menu{top:auto;bottom:100%}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .dropdown-menu-right{right:0;left:auto}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .dropup .dropdown-menu{top:auto;bottom:100%}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .dropdown-menu-right{right:0;left:auto}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .dropup .dropdown-menu{top:auto;bottom:100%}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .dropdown-menu-right{right:0;left:auto}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .dropup .dropdown-menu{top:auto;bottom:100%}}.navbar-expand{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .dropdown-menu-right{right:0;left:auto}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .dropup .dropdown-menu{top:auto;bottom:100%}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group:first-child .list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card>.list-group:last-child .list-group-item:last-child{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.card-body{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img{width:100%;border-radius:calc(.25rem - 1px)}.card-img-top{width:100%;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img-bottom{width:100%;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-ms-flex:1 0 0%;flex:1 0 0%;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-webkit-box-flex:1;-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:first-child .card-header,.card-group>.card:first-child .card-img-top{border-top-right-radius:0}.card-group>.card:first-child .card-footer,.card-group>.card:first-child .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:last-child .card-header,.card-group>.card:last-child .card-img-top{border-top-left-radius:0}.card-group>.card:last-child .card-footer,.card-group>.card:last-child .card-img-bottom{border-bottom-left-radius:0}.card-group>.card:only-child{border-radius:.25rem}.card-group>.card:only-child .card-header,.card-group>.card:only-child .card-img-top{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card-group>.card:only-child .card-footer,.card-group>.card:only-child .card-img-bottom{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.card-group>.card:not(:first-child):not(:last-child):not(:only-child){border-radius:0}.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-footer,.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-header,.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-img-bottom,.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-img-top{border-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem}.card-columns .card{display:inline-block;width:100%}}.breadcrumb{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;padding-left:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:-webkit-box;display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:2;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-link:not(:disabled):not(.disabled){cursor:pointer}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:1;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}.badge-primary[href]:focus,.badge-primary[href]:hover{color:#fff;text-decoration:none;background-color:#0062cc}.badge-secondary{color:#fff;background-color:#6c757d}.badge-secondary[href]:focus,.badge-secondary[href]:hover{color:#fff;text-decoration:none;background-color:#545b62}.badge-success{color:#fff;background-color:#28a745}.badge-success[href]:focus,.badge-success[href]:hover{color:#fff;text-decoration:none;background-color:#1e7e34}.badge-info{color:#fff;background-color:#17a2b8}.badge-info[href]:focus,.badge-info[href]:hover{color:#fff;text-decoration:none;background-color:#117a8b}.badge-warning{color:#212529;background-color:#ffc107}.badge-warning[href]:focus,.badge-warning[href]:hover{color:#212529;text-decoration:none;background-color:#d39e00}.badge-danger{color:#fff;background-color:#dc3545}.badge-danger[href]:focus,.badge-danger[href]:hover{color:#fff;text-decoration:none;background-color:#bd2130}.badge-light{color:#212529;background-color:#f8f9fa}.badge-light[href]:focus,.badge-light[href]:hover{color:#212529;text-decoration:none;background-color:#dae0e5}.badge-dark{color:#fff;background-color:#343a40}.badge-dark[href]:focus,.badge-dark[href]:hover{color:#fff;text-decoration:none;background-color:#1d2124}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:-webkit-box;display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;color:#fff;text-align:center;background-color:#007bff;transition:width .6s ease}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:progress-bar-stripes 1s linear infinite;animation:progress-bar-stripes 1s linear infinite}.media{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.media-body{-webkit-box-flex:1;-ms-flex:1;flex:1}.list-group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;margin-bottom:-1px;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.list-group-item:focus,.list-group-item:hover{z-index:1;text-decoration:none}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-flush .list-group-item{border-right:0;border-left:0;border-radius:0}.list-group-flush:first-child .list-group-item:first-child{border-top:0}.list-group-flush:last-child .list-group-item:last-child{border-bottom:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:focus,.close:hover{color:#000;text-decoration:none;opacity:.75}.close:not(:disabled):not(.disabled){cursor:pointer}button.close{padding:0;background-color:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;outline:0}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.show .modal-dialog{-webkit-transform:translate(0,0);transform:translate(0,0)}.modal-dialog-centered{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;min-height:calc(100% - (.5rem * 2))}.modal-content{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding:1rem;border-bottom:1px solid #e9ecef;border-top-left-radius:.3rem;border-top-right-radius:.3rem}.modal-header .close{padding:1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;padding:1rem;border-top:1px solid #e9ecef}.modal-footer>:not(:first-child){margin-left:.25rem}.modal-footer>:not(:last-child){margin-right:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-centered{min-height:calc(100% - (1.75rem * 2))}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg{max-width:800px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top] .arrow,.bs-popover-top .arrow{bottom:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=top] .arrow::after,.bs-popover-auto[x-placement^=top] .arrow::before,.bs-popover-top .arrow::after,.bs-popover-top .arrow::before{border-width:.5rem .5rem 0}.bs-popover-auto[x-placement^=top] .arrow::before,.bs-popover-top .arrow::before{bottom:0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top] .arrow::after,.bs-popover-top .arrow::after{bottom:1px;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right] .arrow,.bs-popover-right .arrow{left:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right] .arrow::after,.bs-popover-auto[x-placement^=right] .arrow::before,.bs-popover-right .arrow::after,.bs-popover-right .arrow::before{border-width:.5rem .5rem .5rem 0}.bs-popover-auto[x-placement^=right] .arrow::before,.bs-popover-right .arrow::before{left:0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right] .arrow::after,.bs-popover-right .arrow::after{left:1px;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom] .arrow,.bs-popover-bottom .arrow{top:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=bottom] .arrow::after,.bs-popover-auto[x-placement^=bottom] .arrow::before,.bs-popover-bottom .arrow::after,.bs-popover-bottom .arrow::before{border-width:0 .5rem .5rem .5rem}.bs-popover-auto[x-placement^=bottom] .arrow::before,.bs-popover-bottom .arrow::before{top:0;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom] .arrow::after,.bs-popover-bottom .arrow::after{top:1px;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left] .arrow,.bs-popover-left .arrow{right:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left] .arrow::after,.bs-popover-auto[x-placement^=left] .arrow::before,.bs-popover-left .arrow::after,.bs-popover-left .arrow::before{border-width:.5rem 0 .5rem .5rem}.bs-popover-auto[x-placement^=left] .arrow::before,.bs-popover-left .arrow::before{right:0;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left] .arrow::after,.bs-popover-left .arrow::after{right:1px;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;color:inherit;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-item{position:relative;display:none;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:100%;transition:-webkit-transform .6s ease;transition:transform .6s ease;transition:transform .6s ease,-webkit-transform .6s ease;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.carousel-item-next,.carousel-item-prev{position:absolute;top:0}.carousel-item-next.carousel-item-left,.carousel-item-prev.carousel-item-right{-webkit-transform:translateX(0);transform:translateX(0)}@supports ((-webkit-transform-style:preserve-3d) or (transform-style:preserve-3d)){.carousel-item-next.carousel-item-left,.carousel-item-prev.carousel-item-right{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.active.carousel-item-right,.carousel-item-next{-webkit-transform:translateX(100%);transform:translateX(100%)}@supports ((-webkit-transform-style:preserve-3d) or (transform-style:preserve-3d)){.active.carousel-item-right,.carousel-item-next{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}.active.carousel-item-left,.carousel-item-prev{-webkit-transform:translateX(-100%);transform:translateX(-100%)}@supports ((-webkit-transform-style:preserve-3d) or (transform-style:preserve-3d)){.active.carousel-item-left,.carousel-item-prev{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:transparent no-repeat center center;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E")}.carousel-control-next-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E")}.carousel-indicators{position:absolute;right:0;bottom:10px;left:0;z-index:15;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{position:relative;-webkit-box-flex:0;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;background-color:rgba(255,255,255,.5)}.carousel-indicators li::before{position:absolute;top:-10px;left:0;display:inline-block;width:100%;height:10px;content:""}.carousel-indicators li::after{position:absolute;bottom:-10px;left:0;display:inline-block;width:100%;height:10px;content:""}.carousel-indicators .active{background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-circle{border-radius:50%!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.857143%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-webkit-box-orient:horizontal!important;-webkit-box-direction:normal!important;-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-webkit-box-orient:horizontal!important;-webkit-box-direction:reverse!important;-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-webkit-box-orient:vertical!important;-webkit-box-direction:reverse!important;-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.justify-content-start{-webkit-box-pack:start!important;-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-webkit-box-pack:end!important;-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-webkit-box-pack:center!important;-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-webkit-box-pack:justify!important;-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-webkit-box-align:start!important;-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-webkit-box-align:end!important;-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-webkit-box-align:center!important;-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-webkit-box-align:baseline!important;-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-webkit-box-align:stretch!important;-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-webkit-box-orient:horizontal!important;-webkit-box-direction:normal!important;-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-webkit-box-orient:horizontal!important;-webkit-box-direction:reverse!important;-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-webkit-box-orient:vertical!important;-webkit-box-direction:reverse!important;-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.justify-content-sm-start{-webkit-box-pack:start!important;-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-webkit-box-pack:end!important;-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-webkit-box-pack:center!important;-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-webkit-box-pack:justify!important;-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-webkit-box-align:start!important;-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-webkit-box-align:end!important;-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-webkit-box-align:center!important;-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-webkit-box-align:baseline!important;-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-webkit-box-align:stretch!important;-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-webkit-box-orient:horizontal!important;-webkit-box-direction:normal!important;-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-webkit-box-orient:horizontal!important;-webkit-box-direction:reverse!important;-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-webkit-box-orient:vertical!important;-webkit-box-direction:reverse!important;-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.justify-content-md-start{-webkit-box-pack:start!important;-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-webkit-box-pack:end!important;-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-webkit-box-pack:center!important;-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-webkit-box-pack:justify!important;-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-webkit-box-align:start!important;-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-webkit-box-align:end!important;-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-webkit-box-align:center!important;-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-webkit-box-align:baseline!important;-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-webkit-box-align:stretch!important;-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-webkit-box-orient:horizontal!important;-webkit-box-direction:normal!important;-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-webkit-box-orient:horizontal!important;-webkit-box-direction:reverse!important;-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-webkit-box-orient:vertical!important;-webkit-box-direction:reverse!important;-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.justify-content-lg-start{-webkit-box-pack:start!important;-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-webkit-box-pack:end!important;-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-webkit-box-pack:center!important;-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-webkit-box-pack:justify!important;-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-webkit-box-align:start!important;-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-webkit-box-align:end!important;-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-webkit-box-align:center!important;-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-webkit-box-align:baseline!important;-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-webkit-box-align:stretch!important;-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-webkit-box-orient:horizontal!important;-webkit-box-direction:normal!important;-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-webkit-box-orient:horizontal!important;-webkit-box-direction:reverse!important;-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-webkit-box-orient:vertical!important;-webkit-box-direction:reverse!important;-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.justify-content-xl-start{-webkit-box-pack:start!important;-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-webkit-box-pack:end!important;-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-webkit-box-pack:center!important;-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-webkit-box-pack:justify!important;-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-webkit-box-align:start!important;-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-webkit-box-align:end!important;-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-webkit-box-align:center!important;-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-webkit-box-align:baseline!important;-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-webkit-box-align:stretch!important;-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;-webkit-clip-path:inset(50%);clip-path:inset(50%);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal;-webkit-clip-path:none;clip-path:none}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.text-justify{text-align:justify!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0062cc!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#545b62!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#1e7e34!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#117a8b!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#d39e00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#bd2130!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#dae0e5!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#1d2124!important}.text-muted{color:#6c757d!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}} +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/test_web/static/css/index.css b/test_web/static/css/index.css new file mode 100644 index 0000000..3322063 --- /dev/null +++ b/test_web/static/css/index.css @@ -0,0 +1,120 @@ +.navbar{ + width: 1080px; + align-items: center; + padding-left: 5%; + justify-content: space-between; +} + +.input-group{ + width: 20rem; +} + +#search-results{ + position: absolute; + top: calc(100% - 0.5rem); + width: 15rem; + padding: 0.375rem 0; + border-radius: 5px; + background-color: white; +} + +.btn.disabled, .btn:disabled{ + opacity: 1; +} + +#test-container{ + align-items: center; + height: max-content; + margin-bottom: 100px; +} + + +.test-wrapper{ + margin: 1rem; + padding: 0.5rem; + background-color: aliceblue; +} + +#popularNovels, #searchNovels{ + height: 350px; + min-width: 95%; + max-width: 95%; + overflow-y: hidden; + overflow-x: scroll; +} + +#parseNovelAndChapters{ + height: 380px; + min-width: 95%; + max-width: 95%; +} + +.title{ + font-weight: bold; +} + +.spinner-border { + display: none; + width: 1rem; + height: 1rem; + vertical-align: text-bottom; + border: 0.25em solid currentColor; + border-right-color: transparent; + border-radius: 50%; + -webkit-animation: spinner-border .75s linear infinite; + animation: spinner-border .75s linear infinite; +} + +@keyframes spinner-border{ + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +.novel-list{ + display: flex; + margin-top: 0.5rem; + height: 300px; + width: 100%; +} + +.novel-item{ + display: flex; + padding: 0.5rem; +} + +.novel-item img { + max-height: 90%; +} + +.novel-info{ + width: 16rem; +} + +.info-item{ + display: flex; + margin-top: 5px; +} + +.info-value{ + width: 10rem; + padding-left: 0.25rem; + font-size: 0.65rem; + word-wrap:break-word; +} + +.btn{ + font-size: 0.75rem; + padding: 0.25rem 0.25rem; +} + +.chapter-list{ + position: relative; + display: grid; + min-width: 50%; + overflow-y: scroll; + overflow-x: scroll; + grid-template-columns: 15rem 15rem 15rem 15rem; + grid-gap: 0, 0; +} diff --git a/test_web/static/ico.png b/test_web/static/ico.png new file mode 100644 index 0000000..d2f483c Binary files /dev/null and b/test_web/static/ico.png differ diff --git a/test_web/static/js/index.js b/test_web/static/js/index.js new file mode 100644 index 0000000..3cf7825 --- /dev/null +++ b/test_web/static/js/index.js @@ -0,0 +1,263 @@ +var all_plugins = undefined; +var plugin_arr = []; +var current_array = []; +var current_plugin_requirePath = undefined; + +const language_selection = $('#language'); +const plugin_search = $('#plugin'); +const search_results = $('#search-results'); + +fetch('/all_plugins').then(res => res.json()).then(all => { + all_plugins = all; + for(let lang in all){ + language_selection.append(``); + plugin_arr = plugin_arr.concat(all[lang]); + } +}); + +const display_search = (arr) => { + search_results.hide(); + search_results.html(''); + arr.slice(0, 15).forEach(plugin => { + search_results.append(` +
+ +
+ `) + }); + search_results.show(); +} + +plugin_search.focus(e => { + if(!all_plugins) return; + if(language_selection.val()){ + current_array = all_plugins[language_selection.val()]; + }else{ + current_array = plugin_arr; + } + display_search(current_array); +}); + +plugin_search.keyup(e => { + display_search(current_array.filter(plg => plg.name.toLowerCase().includes(plugin_search.val()))); +}); + +$('#clear-search').click(e => { + search_results.hide(); + plugin_search.val(''); + plugin_search.attr('data-require', ''); +}) + +const test_plugin = (btn) => { + const ele = $(btn); + current_plugin_requirePath = ele.attr('data-require'); + plugin_search.attr('data-require', current_plugin_requirePath); + plugin_search.val(ele.text().split('/')[1]); + search_results.hide(); +} + +const popularNovels = () => { + if(!current_plugin_requirePath) return; + $('#popularNovels .spinner-border').show(); + fetch(`/popularNovels/`,{ + method: 'POST', + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({"pluginRequirePath": current_plugin_requirePath}), + }) + .then(res => res.json()) + .then(novels => { + const novel_list = $('#popularNovels .novel-list'); + novel_list.html(''); + if(novels.error){ + novel_list.html(novels.error); + }else{ + novels.forEach(novel => { + novel_list.append(` +
+ No cover found +
+
+
name
+ ${novel.name?.slice(0, 50)} +
Copy
+
+
+
url
+ ${novel.url?.slice(0, 50)} +
Copy
+
+
+
cover
+ ${novel.cover?.slice(0, 50)} +
Copy
+
+
+
+ `); + }) + } + $('#popularNovels .spinner-border').hide(); + }); +} + +const searchNovels = () => { + const searchTerm = $('#searchNovels input').val(); + if(!current_plugin_requirePath || !searchTerm) return; + $('#searchNovels .spinner-border').show(); + fetch(`/searchNovels/`,{ + method: 'POST', + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({"pluginRequirePath": current_plugin_requirePath, "searchTerm": searchTerm}), + }) + .then(res => res.json()) + .then(novels => { + const novel_list = $('#searchNovels .novel-list'); + novel_list.html(''); + if(novels.error){ + novel_list.html(novels.error); + }else{ + novels.forEach(novel => { + novel_list.append(` +
+ No cover found +
+
+
name
+ ${novel.name?.slice(0, 50)} +
Copy
+
+
+
url
+ ${novel.url?.slice(0, 50)} +
Copy
+
+
+
cover
+ ${novel.cover?.slice(0, 50)} +
Copy
+
+
+
+ `); + }) + } + $('#searchNovels .spinner-border').hide(); + }); +} + +const parseNovelAndChapters = () => { + const novelUrl = $('#parseNovelAndChapters input').val(); + if(!current_plugin_requirePath || !novelUrl) return; + $('#parseNovelAndChapters .spinner-border').show(); + fetch(`/parseNovelAndChapters/`,{ + method: 'POST', + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({"pluginRequirePath": current_plugin_requirePath, "novelUrl": novelUrl}), + }) + .then(res => res.json()) + .then(sourceNovel => { + const novel_item = $('#parseNovelAndChapters .novel-list .novel-item'); + novel_item.html(` + No cover found +
+
+
name
+ ${sourceNovel.name?.slice(0, 50)} +
Copy
+
+
+
url
+ ${sourceNovel.url?.slice(0, 50)} +
Copy
+
+
+
cover
+ ${sourceNovel.cover?.slice(0, 50)} +
Copy
+
+
+
summary
+ ${sourceNovel.summary?.slice(0, 50)} +
Copy
+
+
+
author
+ ${sourceNovel.author} +
Copy
+
+
+
artist
+ ${sourceNovel.artist} +
Copy
+
+
+
status
+ ${sourceNovel.status} +
Copy
+
+
+
genres
+ ${sourceNovel.genres?.slice(0, 50)} +
Copy
+
+
+ `); + if(sourceNovel.error){ + novel_item.html(sourceNovel.error); + }else{ + const chapter_list = $('#parseNovelAndChapters .chapter-list'); + chapter_list.html(''); + sourceNovel.chapters.forEach(chapter => { + chapter_list.append(` +
+ ${chapter.name?.slice(0, 26)} +
Copy url
+
+ `); + }) + } + $('#parseNovelAndChapters .spinner-border').hide(); + }); +} + +const parseChapter = () => { + const chapterUrl = $('#parseChapter input').val(); + if(!current_plugin_requirePath || !chapterUrl) return; + $('#parseChapter .spinner-border').show(); + fetch(`/parseChapter/`,{ + method: 'POST', + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({"pluginRequirePath": current_plugin_requirePath, "chapterUrl": chapterUrl}), + }) + .then(res => res.json()) + .then(chapterText => { + $('#parseChapter textarea').text(chapterText); + $('#parseChapter .spinner-border').hide(); + }) +} + +const fetchImage = () => { + const url = $('#fetchImage input').val(); + if(!current_plugin_requirePath || !url) return; + $('#fetchImage .spinner-border').show(); + fetch(`/fetchImage/`,{ + method: 'POST', + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({"pluginRequirePath": current_plugin_requirePath, "url": url}), + }) + .then(res => res.json()) + .then(base64 => { + $('#fetchImage img').attr("src", `data:image/jpg;base64,${base64}`); + $('#fetchImage .spinner-border').hide(); + }) +} \ No newline at end of file diff --git a/test_web/static/js/jquery-3.6.4.min.js b/test_web/static/js/jquery-3.6.4.min.js new file mode 100644 index 0000000..fde8714 --- /dev/null +++ b/test_web/static/js/jquery-3.6.4.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.4 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,y=n.hasOwnProperty,a=y.toString,l=a.call(Object),v={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.4",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&v(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!y||!y.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ve(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ye(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ve(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.cssHas=ce(function(){try{return C.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],y=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&y.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||y.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||y.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||y.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||y.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||y.push(".#.+[+~]"),e.querySelectorAll("\\\f"),y.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&y.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&y.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&y.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),y.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),d.cssHas||y.push(":has"),y=y.length&&new RegExp(y.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),v=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType&&e.documentElement||e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&v(p,e)?-1:t==C||t.ownerDocument==p&&v(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!y||!y.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),v.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",v.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",v.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ye(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),v.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(v.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return B(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=_e(v.pixelPosition,function(e,t){if(t)return t=Be(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return B(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0