diff --git a/package-lock.json b/package-lock.json index 65f832234..6afe8ea48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6771,12 +6771,13 @@ } }, "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==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7488,6 +7489,7 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -7644,6 +7646,19 @@ "node": ">=0.8" } }, + "node_modules/define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/define-lazy-prop": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", @@ -9727,23 +9742,13 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/function.prototype.name": { "version": "1.1.5", @@ -14074,8 +14079,77 @@ "resolved": "https://registry.npmjs.org/password-prompt/-/password-prompt-1.1.3.tgz", "integrity": "sha512-HkrjG2aJlvF0t2BMH0e2LB/EHf3Lcq3fNMzy4GYHcQblAvOl+QQji1Lx7WRBMqpVK8p+KR7bCg7oqAMXtdgqyw==", "dependencies": { - "ansi-escapes": "^4.3.2", - "cross-spawn": "^7.0.3" + "ansi-escapes": "^3.1.0", + "cross-spawn": "^6.0.5" + } + }, + "node_modules/password-prompt/node_modules/ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/password-prompt/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/password-prompt/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/password-prompt/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/password-prompt/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==", + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/password-prompt/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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/password-prompt/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" } }, "node_modules/path-browserify": { @@ -14103,6 +14177,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "engines": { "node": ">=8" } @@ -15989,6 +16064,20 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" }, + "node_modules/set-function-length": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "dependencies": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -16047,6 +16136,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -16058,6 +16148,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "engines": { "node": ">=8" } @@ -17711,6 +17802,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "dependencies": { "isexe": "^2.0.0" }, diff --git a/src/sources/ch/linovelib.js b/src/sources/ch/linovelib.js index 52110450f..16c3f5fb2 100644 --- a/src/sources/ch/linovelib.js +++ b/src/sources/ch/linovelib.js @@ -1,5 +1,5 @@ import { showToast } from '@hooks/showToast'; -import { fetchApi, fetchHtml } from '@utils/fetch/fetch'; +import { fetchHtml } from '@utils/fetch/fetch'; import * as cheerio from 'cheerio'; @@ -74,8 +74,8 @@ const parseNovelAndChapters = async novelUrl => { genres.push(loadedCheerio(this).text()); }); - if (genres && genres.length > 0) { - novel.genres = genres.join(', '); + if (genres.length) { + novel.genre = genres.join(', '); } // Table of Content is on a different page than the summary page diff --git a/src/sources/en/freewebnovel.js b/src/sources/en/freewebnovel.js deleted file mode 100644 index 42b972870..000000000 --- a/src/sources/en/freewebnovel.js +++ /dev/null @@ -1,187 +0,0 @@ -import * as cheerio from 'cheerio'; -const baseUrl = 'https://freewebnovel.com/'; - -const popularNovels = async page => { - let url = baseUrl + 'completed-novel/' + page; - - const result = await fetch(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 = loadedCheerio(this) - .find('h3 > a') - .attr('href') - .replace('.html', '') - .slice(1); - - const novel = { - sourceId: 13, - novelName, - novelCover, - novelUrl, - }; - - novels.push(novel); - }); - - return { novels }; -}; - -const parseNovelAndChapters = async novelUrl => { - const url = `${baseUrl}${novelUrl.replace('/', '')}.html`; - - const result = await fetch(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - let novel = {}; - - novel.sourceId = 13; - - novel.sourceName = 'FreeWebNovel'; - - novel.url = url; - - novel.novelUrl = novelUrl; - - novel.novelName = loadedCheerio('h1.tit').text(); - - novel.novelCover = loadedCheerio('.pic > img').attr('src'); - - novel.genre = 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.novelName) { - latestChapter = loadedCheerio(this) - .next() - .find('span.s3') - .text() - .match(/\d+/); - } - }); - - latestChapter = latestChapter[0]; - - for (let i = 1; i <= parseInt(latestChapter, 10); i++) { - const chapterName = 'Chapter ' + i; - - const releaseDate = null; - - const chapterUrl = 'chapter-' + i; - - const chapter = { chapterName, releaseDate, chapterUrl }; - - novelChapters.push(chapter); - } - - novel.chapters = novelChapters; - - return novel; -}; - -const parseChapter = async (novelUrl, chapterUrl) => { - let novelId = novelUrl.replace('/', ''); - - const url = `${baseUrl}${novelId}/${chapterUrl}.html`; - - const result = await fetch(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - - let chapterName = loadedCheerio('h1.tit').text(); - - let chapterText = loadedCheerio('div.txt').html(); - - const chapter = { - sourceId: 13, - novelUrl, - chapterUrl, - chapterName, - chapterText, - }; - - return chapter; -}; - -const searchNovels = async searchTerm => { - const url = baseUrl + 'search/'; - - const formData = new FormData(); - formData.append('searchkey', searchTerm); - - const result = await fetch(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 = loadedCheerio(this) - .find('h3 > a') - .attr('href') - .replace('.html', '') - .slice(1); - - novelUrl += '/'; - - const novel = { - sourceId: 13, - novelName, - novelCover, - novelUrl, - }; - - novels.push(novel); - }); - - return novels; -}; - -const FreeWebNovelScraper = { - popularNovels, - parseNovelAndChapters, - parseChapter, - searchNovels, -}; - -export default FreeWebNovelScraper; diff --git a/src/sources/en/freewebnovel.ts b/src/sources/en/freewebnovel.ts new file mode 100644 index 000000000..744a06375 --- /dev/null +++ b/src/sources/en/freewebnovel.ts @@ -0,0 +1,122 @@ +import { Status } from '../helpers/constants'; +import * as cheerio from 'cheerio'; +import { + SourceChapter, + SourceChapterItem, + SourceNovel, + SourceNovelItem, +} from '../types'; + +const sourceId = 13; +const sourceName = 'FreeWebNovel'; +const baseUrl = 'https://freewebnovel.com'; + +const popularNovels = async (page: number, { showLatestNovels }) => { + const sort = showLatestNovels + ? '/latest-release-novels/' + : '/completed-novels/'; + + const result = await fetch(baseUrl + sort + page).then(res => res.text()); + const loadedCheerio = cheerio.load(result); + + const novels: SourceNovelItem[] = loadedCheerio('.li-row') + .map((index, element) => ({ + sourceId, + novelName: loadedCheerio(element).find('.tit').text(), + novelCover: loadedCheerio(element).find('img').attr('src'), + novelUrl: baseUrl + loadedCheerio(element).find('h3 > a').attr('href'), + })) + .get(); + + return { novels }; +}; + +const parseNovelAndChapters = async (novelUrl: string) => { + const result = await fetch(novelUrl).then(res => res.text()); + const loadedCheerio = cheerio.load(result); + + const novel: SourceNovel = { + sourceId, + sourceName, + novelUrl, + url: novelUrl, + novelName: loadedCheerio('h1.tit').text(), + novelCover: loadedCheerio('.pic > img').attr('src'), + summary: loadedCheerio('.inner').text().trim(), + }; + + novel.genre = loadedCheerio('[title=Genre]') + .next() + .text() + .replace(/[\t\n]/g, ''); + + novel.author = loadedCheerio('[title=Author]') + .next() + .text() + .replace(/[\t\n]/g, ''); + + novel.status = + loadedCheerio('[title=Status]') + .next() + .text() + .replace(/[\t\n]/g, '') === 'OnGoing' + ? Status.ONGOING + : Status.COMPLETED; + + const chapters: SourceChapterItem[] = loadedCheerio('#idData > li > a') + .map((index, element) => ({ + chapterName: loadedCheerio(element).attr('title') || 'Chapter ' + index, + releaseDate: null, + chapterUrl: baseUrl + loadedCheerio(element).attr('href'), + })) + .get(); + + novel.chapters = chapters; + return novel; +}; + +const parseChapter = async (novelUrl: string, chapterUrl: string) => { + const result = await fetch(chapterUrl).then(res => res.text()); + const loadedCheerio = cheerio.load(result); + + const chapter: SourceChapter = { + sourceId, + novelUrl, + chapterUrl, + chapterName: loadedCheerio('h1.tit').text(), + chapterText: loadedCheerio('div.txt').html(), + }; + + return chapter; +}; + +const searchNovels = async (searchTerm: string) => { + const result = await fetch(baseUrl + '/search/', { + method: 'POST', + body: 'searchkey=' + encodeURIComponent(searchTerm), + headers: { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + }, + }).then(res => res.text()); + + const loadedCheerio = cheerio.load(result); + const novels: SourceNovelItem[] = loadedCheerio('.li-row > .li > .con') + .map((index, element) => ({ + sourceId, + novelName: loadedCheerio(element).find('.tit').text(), + novelCover: loadedCheerio(element).find('.pic > a > img').attr('src'), + novelUrl: baseUrl + loadedCheerio(element).find('h3 > a').attr('href'), + })) + .get(); + + return novels; +}; + +const FreeWebNovelScraper = { + popularNovels, + parseNovelAndChapters, + parseChapter, + searchNovels, +}; + +export default FreeWebNovelScraper; diff --git a/src/sources/multisrc/ifreedom/IfreedomGenerator.js b/src/sources/multisrc/ifreedom/IfreedomGenerator.js new file mode 100644 index 000000000..db39a0c95 --- /dev/null +++ b/src/sources/multisrc/ifreedom/IfreedomGenerator.js @@ -0,0 +1,106 @@ +import { FilterInputs } from '../../types/filterTypes'; +import MultiSrcScraper from './IfreedomScraper'; + +const defaultFilter = [ + { + key: 'sort', + inputType: FilterInputs.Picker, + label: 'Сортировка:', + values: [ + { label: 'По дате добавления', value: 'По дате добавления' }, + { label: 'По дате обновления', value: 'По дате обновления' }, + { label: 'По количеству глав', value: 'По количеству глав' }, + { label: 'По названию', value: 'По названию' }, + { label: 'По просмотрам', value: 'По просмотрам' }, + { label: 'По рейтингу', value: 'По рейтингу' }, + ], + }, + { + key: 'status', + inputType: FilterInputs.Checkbox, + label: 'Статус:', + values: [ + { label: 'Перевод активен', value: 'Перевод активен' }, + { label: 'Перевод приостановлен', value: 'Перевод приостановлен' }, + { label: 'Произведение завершено', value: 'Произведение завершено' }, + ], + }, + { + key: 'lang', + inputType: FilterInputs.Checkbox, + label: 'Язык:', + values: [ + { label: 'Английский', value: 'Английский' }, + { label: 'Китайский', value: 'Китайский' }, + { label: 'Корейский', value: 'Корейский' }, + { label: 'Японский', value: 'Японский' }, + ], + }, + { + key: 'genre', + inputType: FilterInputs.Checkbox, + label: 'Жанры:', + values: [ + { label: 'Боевик', value: 'Боевик' }, + { label: 'Боевые Искусства', value: 'Боевые Искусства' }, + { label: 'Вампиры', value: 'Вампиры' }, + { label: 'Виртуальный Мир', value: 'Виртуальный Мир' }, + { label: 'Гарем', value: 'Гарем' }, + { label: 'Героическое фэнтези', value: 'Героическое фэнтези' }, + { label: 'Детектив', value: 'Детектив' }, + { label: 'Дзёсэй', value: 'Дзёсэй' }, + { label: 'Драма', value: 'Драма' }, + { label: 'Игра', value: 'Игра' }, + { label: 'История', value: 'История' }, + { label: 'Киберпанк', value: 'Киберпанк' }, + { label: 'Комедия', value: 'Комедия' }, + { label: 'ЛитРПГ', value: 'ЛитРПГ' }, + { label: 'Меха', value: 'Меха' }, + { label: 'Милитари', value: 'Милитари' }, + { label: 'Мистика', value: 'Мистика' }, + { label: 'Научная Фантастика', value: 'Научная Фантастика' }, + { label: 'Повседневность', value: 'Повседневность' }, + { label: 'Постапокалипсис', value: 'Постапокалипсис' }, + { label: 'Приключения', value: 'Приключения' }, + { label: 'Психология', value: 'Психология' }, + { label: 'Романтика', value: 'Романтика' }, + { label: 'Сверхъестественное', value: 'Сверхъестественное' }, + { label: 'Сёдзё', value: 'Сёдзё' }, + { label: 'Сёнэн', value: 'Сёнэн' }, + { label: 'Сёнэн-ай', value: 'Сёнэн-ай' }, + { label: 'Спорт', value: 'Спорт' }, + { label: 'Сэйнэн', value: 'Сэйнэн' }, + { label: 'Сюаньхуа', value: 'Сюаньхуа' }, + { label: 'Трагедия', value: 'Трагедия' }, + { label: 'Триллер', value: 'Триллер' }, + { label: 'Ужасы', value: 'Ужасы' }, + { label: 'Фантастика', value: 'Фантастика' }, + { label: 'Фэнтези', value: 'Фэнтези' }, + { label: 'Школьная жизнь', value: 'Школьная жизнь' }, + { label: 'Экшн', value: 'Экшн' }, + { label: 'Эротика', value: 'Эротика' }, + { label: 'Этти', value: 'Этти' }, + { label: 'Яой', value: 'Яой' }, + { label: 'Adult', value: 'Adult' }, + { label: 'Mature', value: 'Mature' }, + { label: 'Xianxia', value: 'Xianxia' }, + { label: 'Xuanhuan', value: 'Xuanhuan' }, + ], + }, +]; + +const IfreedomScraper = new MultiSrcScraper( + 171, + 'https://ifreedom.su', + 'Свободный Мир Ранобэ', + defaultFilter, +); + +const BookhamsterScraper = new MultiSrcScraper( + 172, + 'https://bookhamster.ru', + 'Bookhamster', + defaultFilter, +); + +export { IfreedomScraper, BookhamsterScraper }; diff --git a/src/sources/multisrc/ifreedom/IfreedomScraper.js b/src/sources/multisrc/ifreedom/IfreedomScraper.js new file mode 100644 index 000000000..ba4cb94b3 --- /dev/null +++ b/src/sources/multisrc/ifreedom/IfreedomScraper.js @@ -0,0 +1,157 @@ +import { Status } from '../../helpers/constants'; +import * as cheerio from 'cheerio'; + +class IfreedomScraper { + constructor(sourceId, baseUrl, sourceName, filters) { + this.sourceId = sourceId; + this.baseUrl = baseUrl; + this.sourceName = sourceName; + this.filters = filters; + } + + async popularNovels(page, { showLatestNovels, filters }) { + let url = + this.baseUrl + + '/vse-knigi/?sort=' + + (showLatestNovels + ? 'По дате обновления' + : filters?.sort || 'По рейтингу'); + + if (filters?.status?.length) { + url += filters.status.map(i => '&status[]=' + i).join(''); + } + if (filters?.lang?.length) { + url += filters.lang.map(i => '&lang[]=' + i).join(''); + } + if (filters?.genre?.length) { + url += filters.genre.map(i => '&genre[]=' + i).join(''); + } + url += '&bpage=' + page; + + const body = await fetch(url).then(res => res.text()); + const loadedCheerio = cheerio.load(body); + + const novels = loadedCheerio('div.one-book-home > div.img-home a') + .map((index, element) => ({ + sourceId: this.sourceId, + novelName: loadedCheerio(element).attr('title'), + novelCover: loadedCheerio(element).find('img').attr('src'), + novelUrl: loadedCheerio(element).attr('href'), + })) + .get() + .filter(novel => novel.novelName && novel.novelUrl); + + return { novels }; + } + + async parseNovelAndChapters(novelUrl) { + const body = await fetch(novelUrl).then(res => res.text()); + const loadedCheerio = cheerio.load(body); + + const novel = { + sourceId: this.sourceId, + sourceName: this.sourceName, + url: novelUrl, + novelUrl, + novelName: loadedCheerio('.entry-title').text(), + novelCover: loadedCheerio('.img-ranobe > img').attr('src'), + summary: loadedCheerio('meta[name="description"]').attr('content'), + }; + + loadedCheerio('div.data-ranobe').each(function () { + switch (loadedCheerio(this).find('b').text()) { + case 'Автор': + novel.author = loadedCheerio(this) + .find('div.data-value') + .text() + .trim(); + break; + case 'Жанры': + novel.genre = loadedCheerio('div.data-value > a') + .map((index, element) => loadedCheerio(element).text()?.trim()) + .get() + .join(','); + break; + case 'Статус книги': + novel.status = loadedCheerio('div.data-value') + .text() + .includes('активен') + ? Status.ONGOING + : Status.COMPLETED; + break; + } + }); + + if (novel.author === 'Не указан') { + delete novel.author; + } + + const chapters = []; + loadedCheerio('div.li-ranobe').each(function () { + const chapterName = loadedCheerio(this).find('a').text(); + const chapterUrl = loadedCheerio(this).find('a').attr('href'); + if ( + !loadedCheerio(this).find('label.buy-ranobe').length && + chapterName && + chapterUrl + ) { + const releaseDate = loadedCheerio(this) + .find('div.li-col2-ranobe') + .text() + .trim(); + + chapters.push({ chapterName, releaseDate, chapterUrl }); + } + }); + + novel.chapters = chapters.reverse(); + return novel; + } + + async parseChapter(novelUrl, chapterUrl) { + const body = await fetch(chapterUrl).then(res => res.text()); + const loadedCheerio = cheerio.load(body); + + loadedCheerio('.entry-content img').each(function () { + const srcset = loadedCheerio(this).attr('srcset')?.split?.(' '); + if (srcset?.length) { + const bestlink = srcset + .filter(url => url.startsWith('http')) + ?.unshift(); + if (bestlink) { + loadedCheerio(this).attr('src', bestlink); + } + } + }); + + const chapter = { + sourceId: this.sourceId, + novelUrl, + chapterUrl, + chapterName: loadedCheerio('.entry-title').text(), + chapterText: loadedCheerio('.entry-content').html(), + }; + + return chapter; + } + + async searchNovels(searchTerm) { + const url = this.baseUrl + '/vse-knigi/?searchname=' + searchTerm; + const result = await fetch(url).then(res => res.text()); + const loadedCheerio = cheerio.load(result); + + const novels = loadedCheerio('div.one-book-home > div.img-home a') + .map((index, element) => ({ + sourceId: this.sourceId, + novelName: loadedCheerio(element).attr('title'), + novelCover: loadedCheerio(element).find('img').attr('src'), + novelUrl: loadedCheerio(element).attr('href'), + })) + .get() + .filter(novel => novel.novelName && novel.novelUrl); + + return novels; + } +} + +export default IfreedomScraper; diff --git a/src/sources/multisrc/readwn/ReadwnGenerator.js b/src/sources/multisrc/readwn/ReadwnGenerator.js index 029e0663c..6a4dd757f 100644 --- a/src/sources/multisrc/readwn/ReadwnGenerator.js +++ b/src/sources/multisrc/readwn/ReadwnGenerator.js @@ -7,64 +7,26 @@ const ReadwnScraper = new MultiSrcScraper( { label: 'Genre / Category', values: [ - { label: 'All', value: 'all' }, - { label: 'Action', value: 'action' }, - { label: 'Adventure', value: 'adventure' }, { label: 'Chinese', value: 'chinese' }, - { label: 'Comedy', value: 'comedy' }, - { label: 'Contemporary Romance', value: 'contemporary-romance' }, - { label: 'Drama', value: 'drama' }, - { label: 'Eastern Fantasy', value: 'eastern-fantasy' }, - { label: 'Ecchi', value: 'ecchi' }, { label: 'Erciyuan', value: 'erciyuan' }, { label: 'Faloo', value: 'faloo' }, { label: 'Fan-Fiction', value: 'fan-fiction' }, - { label: 'Fantasy', value: 'fantasy' }, - { label: 'Fantasy Romance', value: 'fantasy-romance' }, - { label: 'Game', value: 'game' }, - { label: 'Gender Bender', value: 'gender-bender' }, - { label: 'Harem', value: 'harem' }, { label: 'Hentai', value: 'hentai' }, - { label: 'Historical', value: 'historical' }, - { label: 'Horror', value: 'horror' }, { label: 'Isekai', value: 'isekai' }, { label: 'Japanese', value: 'japanese' }, - { label: 'Josei', value: 'josei' }, { label: 'Korean', value: 'korean' }, - { label: 'Lolicon', value: 'lolicon' }, { label: 'Magic', value: 'magic' }, - { label: 'Magical Realism', value: 'magical-realism' }, - { label: 'Martial Arts', value: 'martial-arts' }, - { label: 'Mecha', value: 'mecha' }, { label: 'Military', value: 'military' }, - { label: 'Mystery', value: 'mystery' }, { label: 'Official Circles', value: 'official_circles' }, - { label: 'Psychological', value: 'psychological' }, - { label: 'Romance', value: 'romance' }, - { label: 'School Life', value: 'school-life' }, - { label: 'Sci-fi', value: 'sci-fi' }, { label: 'Science Fiction', value: 'science_fiction' }, - { label: 'Seinen', value: 'seinen' }, - { label: 'Shoujo', value: 'shoujo' }, { label: 'Shoujo Ai', value: 'shoujo-ai' }, - { label: 'Shounen', value: 'shounen' }, - { label: 'Shounen Ai', value: 'shounen-ai' }, - { label: 'Slice of Life', value: 'slice-of-life' }, - { label: 'Sports', value: 'sports' }, - { label: 'Supernatural', value: 'supernatural' }, { label: 'Suspense Thriller', value: 'suspense_thriller' }, - { label: 'Tragedy', value: 'tragedy' }, { label: 'Travel Through Time', value: 'travel_through_time' }, { label: 'Two-dimensional', value: 'two-dimensional' }, { label: 'Urban', value: 'urban' }, { label: 'Urban Life', value: 'urban-life' }, - { label: 'Video Games', value: 'video-games' }, { label: 'Virtual Reality', value: 'virtual-reality' }, - { label: 'Wuxia', value: 'wuxia' }, { label: 'Wuxia Xianxia', value: 'wuxia_xianxia' }, - { label: 'Xianxia', value: 'xianxia' }, - { label: 'Xuanhuan', value: 'xuanhuan' }, - { label: 'Yaoi', value: 'yaoi' }, { label: 'Yuri', value: 'yuri' }, ], }, @@ -77,70 +39,31 @@ const NovelmtScraper = new MultiSrcScraper( { label: 'Genre / Category', values: [ - { label: 'All', value: 'all' }, - { label: 'Action', value: 'action' }, - { label: 'Adult', value: 'adult' }, - { label: 'Adventure', value: 'adventure' }, { label: 'Billionaire', value: 'billionaire' }, { label: 'CEO', value: 'ceo' }, { label: 'Chinese', value: 'chinese' }, - { label: 'Comedy', value: 'comedy' }, - { label: 'Contemporary Romance', value: 'contemporary-romance' }, - { label: 'Drama', value: 'drama' }, - { label: 'Eastern Fantasy', value: 'eastern-fantasy' }, { label: 'Ecchi', value: 'ecchi' }, { label: 'Erciyuan', value: 'erciyuan' }, { label: 'Faloo', value: 'faloo' }, { label: 'Fan-Fiction', value: 'fan-fiction' }, - { label: 'Fantasy', value: 'fantasy' }, - { label: 'Fantasy Romance', value: 'fantasy-romance' }, { label: 'Farming', value: 'farming' }, - { label: 'Game', value: 'game' }, { label: 'Games', value: 'games' }, { label: 'Gay Romance', value: 'gay-romance' }, - { label: 'Gender Bender', value: 'gender-bender' }, - { label: 'Harem', value: 'harem' }, - { label: 'Historical', value: 'historical' }, { label: 'Historical Romance', value: 'historical-romance' }, - { label: 'Horror', value: 'horror' }, { label: 'Isekai', value: 'isekai' }, { label: 'Japanese', value: 'japanese' }, - { label: 'Josei', value: 'josei' }, { label: 'Korean', value: 'korean' }, - { label: 'Lolicon', value: 'lolicon' }, { label: 'Magic', value: 'magic' }, - { label: 'Magical Realism', value: 'magical-realism' }, - { label: 'Martial Arts', value: 'martial-arts' }, - { label: 'Mature', value: 'mature' }, - { label: 'Mecha', value: 'mecha' }, { label: 'Military', value: 'military' }, { label: 'Modern Life', value: 'modern-life' }, { label: 'Modern Romance', value: 'modern-romance' }, - { label: 'Mystery', value: 'mystery' }, - { label: 'Psychological', value: 'psychological' }, - { label: 'Romance', value: 'romance' }, { label: 'Romantic', value: 'romantic' }, - { label: 'School Life', value: 'school-life' }, - { label: 'Sci-fi', value: 'sci-fi' }, - { label: 'Seinen', value: 'seinen' }, - { label: 'Shoujo', value: 'shoujo' }, { label: 'Shoujo Ai', value: 'shoujo-ai' }, - { label: 'Shounen', value: 'shounen' }, - { label: 'Shounen Ai', value: 'shounen-ai' }, - { label: 'Slice of Life', value: 'slice-of-life' }, { label: 'Smut', value: 'smut' }, - { label: 'Sports', value: 'sports' }, - { label: 'Supernatural', value: 'supernatural' }, - { label: 'Tragedy', value: 'tragedy' }, { label: 'Two-dimensional', value: 'two-dimensional' }, { label: 'Urban', value: 'urban' }, { label: 'Urban Life', value: 'urban-life' }, - { label: 'Video Games', value: 'video-games' }, { label: 'Virtual Reality', value: 'virtual-reality' }, - { label: 'Wuxia', value: 'wuxia' }, - { label: 'Xianxia', value: 'xianxia' }, - { label: 'Xuanhuan', value: 'xuanhuan' }, - { label: 'Yaoi', value: 'yaoi' }, { label: 'Yuri', value: 'yuri' }, ], }, @@ -153,47 +76,10 @@ const LtnovelScraper = new MultiSrcScraper( { label: 'Genre / Category', values: [ - { label: 'All', value: 'all' }, - { label: 'Action', value: 'action' }, { label: 'Adult', value: 'adult' }, - { label: 'Adventure', value: 'adventure' }, - { label: 'Comedy', value: 'comedy' }, - { label: 'Contemporary Romance', value: 'contemporary-romance' }, - { label: 'Drama', value: 'drama' }, - { label: 'Eastern Fantasy', value: 'eastern-fantasy' }, { label: 'Ecchi', value: 'ecchi' }, - { label: 'Fantasy', value: 'fantasy' }, - { label: 'Fantasy Romance', value: 'fantasy-romance' }, - { label: 'Game', value: 'game' }, - { label: 'Gender Bender', value: 'gender-bender' }, - { label: 'Harem', value: 'harem' }, - { label: 'Historical', value: 'historical' }, - { label: 'Horror', value: 'horror' }, - { label: 'Josei', value: 'josei' }, - { label: 'Lolicon', value: 'lolicon' }, - { label: 'Magical Realism', value: 'magical-realism' }, - { label: 'Martial Arts', value: 'martial-arts' }, { label: 'Mature', value: 'mature' }, - { label: 'Mecha', value: 'mecha' }, - { label: 'Mystery', value: 'mystery' }, - { label: 'Psychological', value: 'psychological' }, - { label: 'Romance', value: 'romance' }, - { label: 'School Life', value: 'school-life' }, - { label: 'Sci-fi', value: 'sci-fi' }, - { label: 'Seinen', value: 'seinen' }, - { label: 'Shoujo', value: 'shoujo' }, - { label: 'Shounen', value: 'shounen' }, - { label: 'Shounen Ai', value: 'shounen-ai' }, - { label: 'Slice of Life', value: 'slice-of-life' }, { label: 'Smut', value: 'smut' }, - { label: 'Sports', value: 'sports' }, - { label: 'Supernatural', value: 'supernatural' }, - { label: 'Tragedy', value: 'tragedy' }, - { label: 'Video Games', value: 'video-games' }, - { label: 'Wuxia', value: 'wuxia' }, - { label: 'Xianxia', value: 'xianxia' }, - { label: 'Xuanhuan', value: 'xuanhuan' }, - { label: 'Yaoi', value: 'yaoi' }, ], }, ); diff --git a/src/sources/multisrc/readwn/ReadwnScraper.js b/src/sources/multisrc/readwn/ReadwnScraper.js index c933cc7de..ac8632a31 100644 --- a/src/sources/multisrc/readwn/ReadwnScraper.js +++ b/src/sources/multisrc/readwn/ReadwnScraper.js @@ -1,9 +1,10 @@ import * as cheerio from 'cheerio'; import QueryString from 'qs'; -import { fetchHtml } from '@utils/fetch/fetch'; - +import { parseMadaraDate } from '../../helpers/parseDate'; import { FilterInputs } from '../../types/filterTypes'; +import { Status } from '../../helpers/constants'; +import { fetchHtml } from '@utils/fetch/fetch'; class ReadwnScraper { constructor(sourceId, baseUrl, sourceName, genres) { @@ -33,156 +34,176 @@ class ReadwnScraper { }, { key: 'genres', - label: genres.label, - values: genres.values, + label: 'Genre / Category', + values: [ + { label: 'All', value: 'all' }, + { label: 'Action', value: 'action' }, + { label: 'Adventure', value: 'adventure' }, + { label: 'Comedy', value: 'comedy' }, + { label: 'Contemporary Romance', value: 'contemporary-romance' }, + { label: 'Drama', value: 'drama' }, + { label: 'Eastern Fantasy', value: 'eastern-fantasy' }, + { label: 'Fantasy', value: 'fantasy' }, + { label: 'Fantasy Romance', value: 'fantasy-romance' }, + { label: 'Game', value: 'game' }, + { label: 'Gender Bender', value: 'gender-bender' }, + { label: 'Harem', value: 'harem' }, + { label: 'Historical', value: 'historical' }, + { label: 'Horror', value: 'horror' }, + { label: 'Josei', value: 'josei' }, + { label: 'Lolicon', value: 'lolicon' }, + { label: 'Magical Realism', value: 'magical-realism' }, + { label: 'Martial Arts', value: 'martial-arts' }, + { label: 'Mecha', value: 'mecha' }, + { label: 'Mystery', value: 'mystery' }, + { label: 'Psychological', value: 'psychological' }, + { label: 'Romance', value: 'romance' }, + { label: 'School Life', value: 'school-life' }, + { label: 'Sci-fi', value: 'sci-fi' }, + { label: 'Seinen', value: 'seinen' }, + { label: 'Shoujo', value: 'shoujo' }, + { label: 'Shounen', value: 'shounen' }, + { label: 'Shounen Ai', value: 'shounen-ai' }, + { label: 'Slice of Life', value: 'slice-of-life' }, + { label: 'Sports', value: 'sports' }, + { label: 'Supernatural', value: 'supernatural' }, + { label: 'Tragedy', value: 'tragedy' }, + { label: 'Video Games', value: 'video-games' }, + { label: 'Wuxia', value: 'wuxia' }, + { label: 'Xianxia', value: 'xianxia' }, + { label: 'Xuanhuan', value: 'xuanhuan' }, + { label: 'Yaoi', value: 'yaoi' }, + ...genres.values, + ], inputType: FilterInputs.Picker, }, ]; } async popularNovels(page, { showLatestNovels, filters }) { - const baseUrl = this.baseUrl; - const sourceId = this.sourceId; - - const pageNo = page - 1; - - let url = baseUrl + 'list/'; + let url = this.baseUrl + 'list/'; url += (filters?.genres || 'all') + '/'; url += (filters?.status || 'all') + '-'; url += (showLatestNovels ? 'lastdotime' : filters?.sort || 'newstime') + '-'; - url += pageNo + '.html'; + url += page - 1 + '.html'; - const body = await fetchHtml({ url, sourceId }); + const body = await fetchHtml({ url, sourceId: this.sourceId }); const loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('li.novel-item').each(function () { - const novelName = loadedCheerio(this).find('h4').text(); - const novelUrl = baseUrl + loadedCheerio(this).find('a').attr('href'); - - const coverUri = loadedCheerio(this) - .find('.novel-cover > img') - .attr('data-src'); - - const novelCover = baseUrl + coverUri; - - const novel = { sourceId, novelName, novelCover, novelUrl }; - - novels.push(novel); - }); + const novels = loadedCheerio('li.novel-item') + .map((index, element) => ({ + sourceId: this.sourceId, + novelName: loadedCheerio(element).find('h4').text(), + novelCover: + this.baseUrl + + loadedCheerio(element).find('.novel-cover > img').attr('data-src'), + novelUrl: this.baseUrl + loadedCheerio(element).find('a').attr('href'), + })) + .get(); return { novels }; } async parseNovelAndChapters(novelUrl) { - const sourceId = this.sourceId; - const baseUrl = this.baseUrl; - const sourceName = this.sourceName; - - const url = novelUrl; - - const body = await fetchHtml({ url, sourceId }); - - let loadedCheerio = cheerio.load(body); + const body = await fetchHtml({ url: novelUrl, sourceId: this.sourceId }); + const loadedCheerio = cheerio.load(body); - let novel = { - sourceId: sourceId, - sourceName: sourceName, - url, + const novel = { + sourceId: this.sourceId, + sourceName: this.sourceName, + url: novelUrl, novelUrl, }; novel.novelName = loadedCheerio('h1.novel-title').text(); - - const coverUri = loadedCheerio('figure.cover > img').attr('data-src'); - novel.novelCover = baseUrl + coverUri; + novel.novelCover = + this.baseUrl + loadedCheerio('figure.cover > img').attr('data-src'); + novel.author = loadedCheerio('span[itemprop=author]').text(); novel.summary = loadedCheerio('.summary') .text() .replace('Summary', '') .trim(); - novel.genre = ''; - - loadedCheerio('div.categories > ul > li').each(function () { - novel.genre += loadedCheerio(this).text().trim() + ','; - }); + novel.genre = loadedCheerio('div.categories > ul > li') + .map((index, element) => loadedCheerio(element).text()?.trim()) + .get() + .join(','); loadedCheerio('div.header-stats > span').each(function () { if (loadedCheerio(this).find('small').text() === 'Status') { - novel.status = loadedCheerio(this).find('strong').text(); + novel.status = + loadedCheerio(this).find('strong').text() === 'Ongoing' + ? Status.ONGOING + : Status.COMPLETED; } }); - novel.genre = novel.genre.slice(0, -1); - - novel.author = loadedCheerio('span[itemprop=author]').text(); - - let novelChapters = []; - - const novelId = novelUrl.replace('.html', '').replace(baseUrl, ''); - - const latestChapterNo = loadedCheerio('.header-stats') - .find('span > strong') - .first() - .text() - .trim(); - - let lastChapterNo = 1; - loadedCheerio('.chapter-list li').each(function () { - const chapterName = loadedCheerio(this) - .find('a .chapter-title') + const latestChapterNo = parseInt( + loadedCheerio('.header-stats') + .find('span > strong') + .first() .text() - .trim(); - - const chapterUrl = loadedCheerio(this).find('a').attr('href').trim(); - - const releaseDate = loadedCheerio(this) - .find('a .chapter-update') - .text() - .trim(); - - lastChapterNo = loadedCheerio(this).find('a .chapter-no').text().trim(); - - const chapter = { chapterName, releaseDate, chapterUrl }; - - novelChapters.push(chapter); - }); - - // Itterate once more before loop to finish off - lastChapterNo++; - for (let i = lastChapterNo; i <= latestChapterNo; i++) { - const chapterName = `Chapter ${i}`; - const chapterUrl = `${novelId}_${i}.html`; - const releaseDate = null; - - const chapter = { chapterName, releaseDate, chapterUrl }; - - novelChapters.push(chapter); + .trim(), + 10, + ); + + const chapters = loadedCheerio('.chapter-list li') + .map((index, element) => { + const chapterName = loadedCheerio(element) + .find('a .chapter-title') + .text() + .trim(); + const chapterUrl = loadedCheerio(element) + .find('a') + .attr('href') + ?.trim(); + const releaseDate = loadedCheerio(element) + .find('a .chapter-update') + .text() + .trim(); + + if (chapterUrl) { + return { + chapterName, + releaseDate: parseMadaraDate(releaseDate), + chapterUrl: this.baseUrl + chapterUrl, + }; + } + }) + .get() + .filter(chapter => chapter?.chapterName); + + if (latestChapterNo > chapters.length) { + const lastChapterNo = parseInt( + chapters[chapters.length - 1].chapterUrl.match(/_(\d+)\.html/)?.[1] || + chapters.length, + 10, + ); + + for (let i = lastChapterNo + 1; i <= latestChapterNo; i++) { + chapters.push({ + chapterName: 'Chapter ' + i, + releaseDate: null, + chapterUrl: novelUrl.replace('.html', '_' + i + '.html'), + }); + } } - - novel.chapters = novelChapters; + novel.chapters = chapters; return novel; } async parseChapter(novelUrl, chapterUrl) { - const baseUrl = this.baseUrl; - const url = baseUrl + chapterUrl; - const sourceId = this.sourceId; - - const body = await fetchHtml({ url, sourceId }); + const body = await fetchHtml({ url: chapterUrl, sourceId: this.sourceId }); const loadedCheerio = cheerio.load(body); - const chapterName = loadedCheerio('.titles > h2').text(); const chapterText = loadedCheerio('.chapter-content').html(); const chapter = { - sourceId, + sourceId: this.sourceId, novelUrl, chapterUrl, chapterName, @@ -193,17 +214,14 @@ class ReadwnScraper { } async searchNovels(searchTerm) { - const baseUrl = this.baseUrl; - const sourceId = this.sourceId; - const searchUrl = `${baseUrl}e/search/index.php`; - const body = await fetchHtml({ - url: searchUrl, + url: this.baseUrl + 'e/search/index.php', + sourceId: this.sourceId, init: { headers: { 'Content-Type': 'application/x-www-form-urlencoded', - Referer: `${baseUrl}search.html`, - Origin: baseUrl, + Referer: this.baseUrl + 'search.html', + Origin: this.baseUrl, 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36', }, @@ -218,25 +236,15 @@ class ReadwnScraper { }); const loadedCheerio = cheerio.load(body); - - let novels = []; - - loadedCheerio('li.novel-item').each(function () { - const novelName = loadedCheerio(this).find('h4').text(); - const novelUrl = baseUrl + loadedCheerio(this).find('a').attr('href'); - - const coverUri = loadedCheerio(this).find('img').attr('data-src'); - const novelCover = baseUrl + coverUri; - - const novel = { - sourceId, - novelName, - novelCover, - novelUrl, - }; - - novels.push(novel); - }); + const novels = loadedCheerio('li.novel-item') + .map((index, element) => ({ + sourceId: this.sourceId, + novelName: loadedCheerio(element).find('h4').text(), + novelCover: + this.baseUrl + loadedCheerio(element).find('img').attr('data-src'), + novelUrl: this.baseUrl + loadedCheerio(element).find('a').attr('href'), + })) + .get(); return novels; } diff --git a/src/sources/multisrc/rulate/RulateGenerator.js b/src/sources/multisrc/rulate/RulateGenerator.js index af60f7177..43d66a74f 100644 --- a/src/sources/multisrc/rulate/RulateGenerator.js +++ b/src/sources/multisrc/rulate/RulateGenerator.js @@ -54,8 +54,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Фэнтези', value: '37' }, { label: 'Школа', value: '38' }, { label: 'Этти', value: '39' }, - { label: 'Bl', value: '43' }, - { label: 'Gl', value: '40' }, ], inputType: FilterInputs.Checkbox, }, @@ -311,7 +309,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Горничные', value: '8091' }, { label: 'Гробница', value: '388' }, { label: 'Дазай осаму', value: '4112' }, - { label: 'Даньмэй', value: '3411' }, { label: 'Даосизм', value: '258' }, { label: 'Дарк соулс', value: '6057' }, { label: 'Дарт вейдер', value: '6826' }, @@ -383,6 +380,7 @@ const RulateScraper = new MultiSrcScraper( { label: 'Жестокость', value: '7880' }, { label: 'Животные', value: '7488' }, { label: 'Животные компаньоны', value: '2061' }, + { label: 'Жизнь в небесах', value: '8426' }, { label: 'Жизнь и смерть', value: '2014' }, { label: 'Жнец', value: '6288' }, { label: 'Жойен рид', value: '3926' }, @@ -420,6 +418,7 @@ const RulateScraper = new MultiSrcScraper( { label: 'Игра на выживание', value: '2182' }, { label: 'Игровая система', value: '384' }, { label: 'Игровые элементы', value: '720' }, + { label: 'Избранный', value: '8425' }, { label: 'Извращения', value: '651' }, { label: 'Измена', value: '1874' }, { label: 'Изменение характера', value: '1113' }, @@ -1061,6 +1060,7 @@ const RulateScraper = new MultiSrcScraper( { label: 'Церковь', value: '1980' }, { label: 'Цундере', value: '1078' }, { label: 'Чакра', value: '1393' }, + { label: 'Чат-система', value: '8407' }, { label: 'Черный юмор', value: '2868' }, { label: 'Честная главная героиня', value: '5855' }, { label: 'Честный главный герой', value: '129' }, @@ -1083,6 +1083,7 @@ const RulateScraper = new MultiSrcScraper( { label: 'Элементальная магия', value: '1264' }, { label: 'Эльфы', value: '1362' }, { label: 'Эротика', value: '2053' }, + { label: 'Это история без главной героини', value: '8417' }, { label: 'Юмор', value: '7360' }, { label: 'Яды', value: '201' }, { label: 'Якудза', value: '4599' }, @@ -1161,21 +1162,26 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Айдолы', value: '364' }, { label: 'Акула', value: '53' }, { label: 'Альтернативное развитие событий', value: '338' }, + { label: 'Альфа самец', value: '453' }, { label: 'Аморальный главный герой', value: '219' }, { label: 'Анал', value: '7' }, { label: 'Анальный секс', value: '67' }, { label: 'Ангелы', value: '114' }, { label: 'Антигерой', value: '226' }, { label: 'Аристократия', value: '260' }, + { label: 'Армия', value: '415' }, { label: 'Артефакт', value: '379' }, { label: 'Афродизиак', value: '276' }, { label: 'Ахегао', value: '240' }, { label: 'Бабушка беременна от внука', value: '393' }, { label: 'Бабушка и внук', value: '380' }, + { label: 'Бандиты', value: '441' }, + { label: 'Бдс', value: '420' }, { label: 'Бдсм', value: '131' }, { label: 'Беременность', value: '148' }, { label: 'Бесплатно', value: '79' }, { label: 'Бесстрашные персонажи', value: '339' }, + { label: 'Бесстыдный главный герой', value: '433' }, { label: 'Библиотека', value: '265' }, { label: 'Бистиалити', value: '391' }, { label: 'Битва за трон', value: '404' }, @@ -1184,6 +1190,7 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Богатые персонажи', value: '248' }, { label: 'Боги', value: '115' }, { label: 'Боевик', value: '252' }, + { label: 'Боевые искусства', value: '430' }, { label: 'Большая грудь', value: '33' }, { label: 'Большая попка', value: '328' }, { label: 'Большой член', value: '76' }, @@ -1193,7 +1200,9 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Брак по расчёту', value: '346' }, { label: 'Брат', value: '29' }, { label: 'Брат и сестра', value: '30' }, + { label: 'Братский комплекс', value: '466' }, { label: 'Брюнетка', value: '28' }, + { label: 'Будущее', value: '457' }, { label: 'Бэтмен', value: '369' }, { label: 'Бэтмен', value: '170' }, { label: 'В первый раз', value: '179' }, @@ -1208,7 +1217,9 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Виртуальная реальность', value: '382' }, { label: 'Вирус', value: '203' }, { label: 'Внучка', value: '189' }, + { label: 'Возвращение домой', value: '448' }, { label: 'Война', value: '387' }, + { label: 'Волшебство', value: '438' }, { label: 'Воспоминания из прошлого', value: '405' }, { label: 'Враги становятся любовниками', value: '410' }, { label: 'Время', value: '81' }, @@ -1220,7 +1231,9 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Гвен', value: '402' }, { label: 'Гг имба', value: '87' }, { label: 'Генетические модификации', value: '314' }, + { label: 'Гениальный главный герой', value: '471' }, { label: 'Гипноз', value: '143' }, + { label: 'Гл', value: '427' }, { label: 'Главная героиня девушка', value: '147' }, { label: 'Главный герой женщина', value: '213' }, { label: 'Главный герой извращенец', value: '116' }, @@ -1269,6 +1282,7 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Замок', value: '140' }, { label: 'Запретная любовь', value: '273' }, { label: 'Заражение', value: '206' }, + { label: 'Зачарованные', value: '467' }, { label: 'Звёздные войны', value: '120' }, { label: 'Зверодевочки', value: '158' }, { label: 'Зло', value: '343' }, @@ -1276,6 +1290,7 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Золотой дождь', value: '146' }, { label: 'Зомби апокалипсис', value: '241' }, { label: 'Зоофилия', value: '144' }, + { label: 'Зрелы', value: '419' }, { label: 'Зрелые женщины', value: '398' }, { label: 'Игровые элементы', value: '385' }, { label: 'Извращения', value: '192' }, @@ -1284,26 +1299,35 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Изменения личности', value: '316' }, { label: 'Изнасилование', value: '275' }, { label: 'Изуку мидория', value: '337' }, + { label: 'Инвалидность', value: '458' }, { label: 'Ино', value: '127' }, { label: 'Инопланетяне', value: '304' }, { label: 'Интересный сюжет', value: '349' }, { label: 'Интимные сцены', value: '322' }, + { label: 'Интрига', value: '435' }, { label: 'Интриги и заговоры', value: '262' }, { label: 'Интроверт', value: '38' }, { label: 'Инфекция', value: '207' }, { label: 'Инцест', value: '35' }, { label: 'Исторический роман', value: '406' }, { label: 'Камера', value: '92' }, + { label: 'Киберспорт', value: '426' }, + { label: 'Китай', value: '445' }, { label: 'Колледж', value: '82' }, + { label: 'Кольца', value: '455' }, { label: 'Комикс', value: '286' }, { label: 'Контроль', value: '19' }, { label: 'Контроль над разумом', value: '181' }, { label: 'Контроль разума', value: '65' }, { label: 'Кончил внутрь', value: '221' }, + { label: 'Корея', value: '446' }, + { label: 'Кормление грудью', value: '440' }, { label: 'Королевская семья', value: '358' }, + { label: 'Королевство', value: '465' }, { label: 'Коррупция', value: '49' }, { label: 'Космос', value: '204' }, { label: 'Красивая главная героиня', value: '255' }, + { label: 'Красивы', value: '462' }, { label: 'Красивые женщины', value: '321' }, { label: 'Красивый главный герой', value: '266' }, { label: 'Ксенофилия', value: '386' }, @@ -1316,9 +1340,13 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Лишение девственности', value: '212' }, { label: 'Лоли', value: '218' }, { label: 'Лунатизм', value: '279' }, + { label: 'Любовный треугольник', value: '473' }, { label: 'Любовь', value: '89' }, + { label: 'Любовь с первого взгляда', value: '436' }, + { label: 'Магические предметы', value: '454' }, { label: 'Магический мир', value: '357' }, { label: 'Магия', value: '80' }, + { label: 'Магия природы', value: '437' }, { label: 'Мама', value: '3' }, { label: 'Мама беременна от сына', value: '274' }, { label: 'Мама и дочь', value: '163' }, @@ -1337,6 +1365,7 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Мачеха', value: '197' }, { label: 'Мачеха беременна от сына', value: '395' }, { label: 'Мачеха и сын', value: '394' }, + { label: 'Медленная романтика', value: '431' }, { label: 'Медсестра', value: '341' }, { label: 'Межрассовый', value: '54' }, { label: 'Месть', value: '306' }, @@ -1349,8 +1378,10 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Младшая сестра', value: '154' }, { label: 'Много спермы', value: '409' }, { label: 'Модель', value: '324' }, + { label: 'Монахиня', value: '470' }, { label: 'Монстры', value: '333' }, { label: 'Мошеничество', value: '44' }, + { label: 'Муж куколд', value: '452' }, { label: 'Мужская беременность', value: '363' }, { label: 'Мужчина протагонист', value: '108' }, { label: 'Музыка', value: '331' }, @@ -1360,6 +1391,7 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Насилие', value: '128' }, { label: 'Насилие и жестокость', value: '193' }, { label: 'Научный эксперимент', value: '313' }, + { label: 'Невеста', value: '421' }, { label: 'Нежить', value: '373' }, { label: 'Некромантия', value: '298' }, { label: 'Ненормативная лексика', value: '124' }, @@ -1367,6 +1399,7 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Нетори', value: '284' }, { label: 'Нижнее бельё', value: '392' }, { label: 'Ниндзя', value: '296' }, + { label: 'Новый год', value: '459' }, { label: 'Ношеные трусики', value: '166' }, { label: 'Нудизм', value: '156' }, { label: 'Няня', value: '88' }, @@ -1379,11 +1412,13 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Оральный секс', value: '10' }, { label: 'Оргия', value: '356' }, { label: 'Орки', value: '388' }, + { label: 'От бедности к богатству', value: '444' }, { label: 'От слабого к сильному', value: '351' }, { label: 'Отец', value: '2' }, { label: 'Отец делится с сыном', value: '6' }, { label: 'Отец и дочь', value: '205' }, { label: 'Отец куколд', value: '272' }, + { label: 'Отчим', value: '417' }, { label: 'Офис', value: '234' }, { label: 'Падчерица', value: '271' }, { label: 'Пайзури', value: '195' }, @@ -1391,6 +1426,7 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Паразиты', value: '208' }, { label: 'Параллельный мир', value: '320' }, { label: 'Пародия', value: '371' }, + { label: 'Первая любовь', value: '434' }, { label: 'Первый раз', value: '159' }, { label: 'Перемещение в другой мир', value: '347' }, { label: 'Перемещение во времени', value: '245' }, @@ -1417,8 +1453,11 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Потеря девственности', value: '142' }, { label: 'Похищение', value: '161' }, { label: 'Преданные любовный интерес', value: '407' }, + { label: 'Призраки', value: '412' }, { label: 'Приключения', value: '134' }, { label: 'Принуждение', value: '66' }, + { label: 'Проклятие', value: '456' }, + { label: 'Психические расстройства', value: '463' }, { label: 'Психология', value: '238' }, { label: 'Публично', value: '85' }, { label: 'Публичный секс', value: '233' }, @@ -1428,11 +1467,15 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Разврат', value: '64' }, { label: 'Райзен', value: '95' }, { label: 'Раса инопланетных космических лесбиянок', value: '294' }, + { label: 'Ревность', value: '414' }, + { label: 'Реинкарнация', value: '428' }, { label: 'Религия', value: '403' }, { label: 'Рестленг', value: '84' }, { label: 'Риас гремори', value: '389' }, + { label: 'Рождество', value: '460' }, { label: 'Романтика', value: '178' }, { label: 'Рыжий', value: '90' }, + { label: 'С', value: '413' }, { label: 'Сакура', value: '126' }, { label: 'Самолет', value: '93' }, { label: 'Санса старк', value: '277' }, @@ -1454,6 +1497,7 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Секса будет много', value: '151' }, { label: 'Сексуальное желание', value: '209' }, { label: 'Селфцест', value: '397' }, + { label: 'Семейные традиции', value: '449' }, { label: 'Семья', value: '5' }, { label: 'Сестра', value: '31' }, { label: 'Сестра беременна от брата', value: '355' }, @@ -1474,14 +1518,24 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Соседка', value: '312' }, { label: 'Сперма', value: '229' }, { label: 'Сперма на лицо', value: '199' }, + { label: 'Спорт', value: '450' }, { label: 'Спящие', value: '187' }, + { label: 'Средневековье', value: '443' }, + { label: 'Сталкер', value: '418' }, { label: 'Старшая сестра', value: '311' }, { label: 'Стеб', value: '111' }, { label: 'Студенты', value: '177' }, { label: 'Суккуб', value: '390' }, { label: 'Супергерои', value: '305' }, { label: 'Суперспособности', value: '300' }, + { label: 'Счастливый конец', value: '461' }, { label: 'Сын', value: '4' }, + { label: 'Сын солдат', value: '416' }, + { label: 'Сюаньхуа', value: '425' }, + { label: 'Сянься', value: '424' }, + { label: 'Таблетки для развития', value: '429' }, + { label: 'Тайная любовь', value: '432' }, + { label: 'Тайные отношения', value: '451' }, { label: 'Твинцест', value: '188' }, { label: 'Темное фэнтези', value: '293' }, { label: 'Тентакли', value: '303' }, @@ -1505,6 +1559,8 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Умные персонажи', value: '257' }, { label: 'Умный главный герой', value: '354' }, { label: 'Университет', value: '323' }, + { label: 'Уся', value: '423' }, + { label: 'Учеба в университете', value: '472' }, { label: 'Ученик', value: '83' }, { label: 'Ф', value: '149' }, { label: 'Фанатичная любовь', value: '366' }, @@ -1516,16 +1572,22 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Фетиш', value: '60' }, { label: 'Флэш', value: '121' }, { label: 'Футанария', value: '150' }, + { label: 'Фэн', value: '422' }, { label: 'Фэнтези', value: '359' }, + { label: 'Фэнтезийный мир', value: '464' }, { label: 'Хентай', value: '75' }, { label: 'Хината', value: '110' }, { label: 'Холодная главная героиня', value: '263' }, + { label: 'Христианство', value: '468' }, + { label: 'Хулиганы', value: '442' }, + { label: 'Церковь', value: '469' }, { label: 'Черная вдова', value: '220' }, { label: 'Черная Вдова', value: '175' }, { label: 'Черный юмор', value: '374' }, { label: 'Читерство', value: '45' }, { label: 'Чтение мыслей', value: '227' }, { label: 'Чудовища', value: '378' }, + { label: 'Чулки', value: '439' }, { label: 'Шантаж', value: '129' }, { label: 'Школа', value: '118' }, { label: 'Школьная жизнь', value: '318' }, @@ -1540,6 +1602,7 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Этти', value: '327' }, { label: 'Юмор', value: '105' }, { label: 'Яндере', value: '155' }, + { label: 'Япония', value: '447' }, { label: 'Bl', value: '125' }, { label: 'Dc', value: '411' }, { label: 'Gl', value: '96' }, diff --git a/src/sources/multisrc/rulate/RulateScraper.js b/src/sources/multisrc/rulate/RulateScraper.js index 300ee7d3c..e24fa1422 100644 --- a/src/sources/multisrc/rulate/RulateScraper.js +++ b/src/sources/multisrc/rulate/RulateScraper.js @@ -88,11 +88,11 @@ class RulateScraper { url += '&adult=' + (filters?.adult || '0'); if (filters?.genres?.length) { - url += filters.genres.map(i => `&genres[]=${i}`).join(''); + url += filters.genres.map(i => '&genres[]=' + i).join(''); } if (filters?.tags?.length) { - url += filters.tags.map(i => `&tags[]=${i}`).join(''); + url += filters.tags.map(i => '&tags[]=' + i).join(''); } if (filters?.trash?.length) { @@ -150,6 +150,9 @@ class RulateScraper { ) .text() .trim(); + if (novel.novelName?.includes?.('[')) { + novel.novelName = novel.novelName.split('[')[0].trim(); + } novel.novelCover = this.baseUrl + loadedCheerio('div[class="images"] > div img').attr('src'); novel.summary = loadedCheerio( @@ -191,7 +194,7 @@ class RulateScraper { }, ); - if (genre.length > 0) { + if (genre.length) { novel.genre = genre.reverse().join(','); } @@ -210,7 +213,7 @@ class RulateScraper { .attr('href'); if ( - loadedCheerio(this).find('td > span[class="disabled"]').length < 1 && + !loadedCheerio(this).find('td > span[class="disabled"]').length && releaseDate ) { chapters.push({ chapterName, releaseDate, chapterUrl }); diff --git a/src/sources/ru/freedlit.ts b/src/sources/ru/freedlit.ts index 6dcf71ec2..177d2748f 100644 --- a/src/sources/ru/freedlit.ts +++ b/src/sources/ru/freedlit.ts @@ -13,17 +13,18 @@ const sourceName = 'LitSpace'; const baseUrl = 'https://freedlit.space'; const popularNovels = async (page, { showLatestNovels, filters }) => { - let url = baseUrl + '/books/'; - url += (filters?.genre || 'all') + '?sort='; + let url = baseUrl + '/get-books/all/list/' + page + '?sort='; url += showLatestNovels ? 'recent' : filters?.sort || 'popular'; url += '&status=' + (filters?.status || 'all'); url += '&access=' + (filters?.access || 'all'); url += '&adult=' + (filters?.adult || 'hide'); - url += '&page=' + page; - const result = await fetch(url); - const body = await result.text(); - const loadedCheerio = cheerio.load(body); + if (filters?.genre?.length) { + url += filters.genre.map(id => '&genres_included[]=' + id).join(''); + } + + const result = await fetch(url).then(res => res.text()); + const loadedCheerio = cheerio.load(result); const novels: SourceNovelItem[] = []; loadedCheerio('#bookListBlock > div > div').each(function () { @@ -44,31 +45,30 @@ const popularNovels = async (page, { showLatestNovels, filters }) => { }; const parseNovelAndChapters = async novelUrl => { - const result = await fetch(novelUrl); - const body = await result.text(); - const loadedCheerio = cheerio.load(body); + const result = await fetch(novelUrl).then(res => res.text()); + const loadedCheerio = cheerio.load(result); const novel: SourceNovel = { sourceId, sourceName, novelUrl, url: novelUrl, - novelName: loadedCheerio('.book-info > h4').text(), + novelName: loadedCheerio('.book-info h4').text(), novelCover: loadedCheerio('.book-cover > div > img').attr('src')?.trim(), summary: loadedCheerio('#nav-home').text()?.trim(), - author: loadedCheerio('.book-info > h5 > a').text(), + author: loadedCheerio('.book-info h5 > a').text(), genre: loadedCheerio('.genre-list > a') .map((index, element) => loadedCheerio(element).text()) .get() .join(','), }; - let chapters: SourceChapterItem[] = []; + const chapters: SourceChapterItem[] = []; - loadedCheerio('#nav-contents > div').each(function () { - const chapterName = loadedCheerio(this).find('a').text(); + loadedCheerio('a.chapter-line').each(function () { + const chapterName = loadedCheerio(this).find('h6').text(); const releaseDate = loadedCheerio(this).find('span[class="date"]').text(); - const chapterUrl = loadedCheerio(this).find('a').attr('href'); + const chapterUrl = loadedCheerio(this).attr('href'); if (chapterName && chapterUrl) { chapters.push({ chapterName, releaseDate, chapterUrl }); } @@ -83,8 +83,8 @@ const parseChapter = async (novelUrl, chapterUrl) => { const body = await result.text(); const loadedCheerio = cheerio.load(body); - loadedCheerio('div[class="standart-block"]').remove(); - loadedCheerio('div[class="mobile-block"]').remove(); + loadedCheerio('div.mobile-block').remove(); + loadedCheerio('div.standart-block').remove(); const chapter: SourceChapter = { sourceId, @@ -99,11 +99,10 @@ const parseChapter = async (novelUrl, chapterUrl) => { const searchNovels = async searchTerm => { const url = `${baseUrl}/search?query=${searchTerm}&type=all`; - const result = await fetch(url); - const body = await result.text(); - const loadedCheerio = cheerio.load(body); + const result = await fetch(url).then(res => res.text()); + const loadedCheerio = cheerio.load(result); - let novels: Novel.Item[] = []; + const novels: SourceNovelItem[] = []; loadedCheerio('#bookListBlock > div').each(function () { const novelName = loadedCheerio(this).find('h4 > a').text()?.trim(); const novelCover = loadedCheerio(this).find('a > img').attr('src')?.trim(); @@ -132,7 +131,6 @@ const filters = [ key: 'genre', label: 'Жанры:', values: [ - { label: 'Любой жанр', value: 'all' }, { label: 'Альтернативная история', value: 'alternative-history' }, { label: 'Антиутопия', value: 'dystopia' }, { label: 'Бизнес-литература', value: 'business-literature' }, @@ -210,7 +208,7 @@ const filters = [ { label: 'Юмористическое фэнтези', value: 'humor-fantasy' }, { label: 'RPS', value: 'rps' }, ], - inputType: FilterInputs.Picker, + inputType: FilterInputs.Checkbox, }, { key: 'status', diff --git a/src/sources/ru/jaomix.js b/src/sources/ru/jaomix.js index 290680367..9359081e2 100644 --- a/src/sources/ru/jaomix.js +++ b/src/sources/ru/jaomix.js @@ -10,15 +10,15 @@ const baseUrl = 'https://jaomix.ru'; const popularNovels = async (page, { showLatestNovels, filters }) => { let url = baseUrl + '/?searchrn'; - if (filters?.lang instanceof Array) { + if (filters?.lang?.length) { url += filters.lang.map((lang, idx) => `&lang[${idx}]=${lang}`).join(''); } - if (filters?.genre instanceof Array) { + if (filters?.genre?.length) { url += filters.genre .map((genre, idx) => `&genre[${idx}]=${genre}`) .join(''); } - if (filters?.delgenre instanceof Array) { + if (filters?.delgenre?.length) { url += filters.delgenre .map((genre, idx) => `&delgenre[${idx}]=del ${genre}`) .join(''); diff --git a/src/sources/ru/ranobelib.js b/src/sources/ru/ranobelib.js index 8908d4229..a14edacde 100644 --- a/src/sources/ru/ranobelib.js +++ b/src/sources/ru/ranobelib.js @@ -16,27 +16,27 @@ const popularNovels = async (page, { showLatestNovels, filters }) => { url += '&page=' + page; if (filters?.type?.length) { - url += filters.type.map(i => `&types[]=${i}`).join(''); + url += filters.type.map(i => '&types[]=' + i).join(''); } if (filters?.format?.length) { - url += filters.format.map(i => `&format[include][]=${i}`).join(''); + url += filters.format.map(i => '&format[include][]=' + i).join(''); } if (filters?.status?.length) { - url += filters.status.map(i => `&status[]=${i}`).join(''); + url += filters.status.map(i => '&status[]=' + i).join(''); } if (filters?.statuss?.length) { - url += filters.statuss.map(i => `&manga_status[]=${i}`).join(''); + url += filters.statuss.map(i => '&manga_status[]=' + i).join(''); } if (filters?.genres?.length) { - url += filters.genres.map(i => `&genres[include][]=${i}`).join(''); + url += filters.genres.map(i => '&genres[include][]=' + i).join(''); } if (filters?.tags?.length) { - url += filters.tags.map(i => `&tags[include][]=${i}`).join(''); + url += filters.tags.map(i => '&tags[include][]=' + i).join(''); } const result = await fetch(url); diff --git a/src/sources/ru/renovels.js b/src/sources/ru/renovels.js index 11a09747f..16db550e5 100644 --- a/src/sources/ru/renovels.js +++ b/src/sources/ru/renovels.js @@ -13,24 +13,24 @@ const popularNovels = async (page, { showLatestNovels, filters }) => { url += filters?.order ? filters?.order?.replace('+', '') : '-'; url += showLatestNovels ? 'chapter_date' : filters?.sort || 'rating'; - if (filters?.genres instanceof Array) { - url += filters.genres.map(i => `&genres=${i}`).join(''); + if (filters?.genres?.length) { + url += filters.genres.map(i => '&genres=' + i).join(''); } - if (filters?.status instanceof Array) { - url += filters?.status.map(i => `&status=${i}`).join(''); + if (filters?.status?.length) { + url += filters.status.map(i => '&status=' + i).join(''); } - if (filters?.types instanceof Array) { - url += filters.types.map(i => `&types=${i}`).join(''); + if (filters?.types?.length) { + url += filters.types.map(i => '&types=' + i).join(''); } - if (filters?.categories instanceof Array) { - url += filters.categories.map(i => `&categories=${i}`).join(''); + if (filters?.categories?.length) { + url += filters.categories.map(i => '&categories=' + i).join(''); } - if (filters?.age_limit instanceof Array) { - url += filters.age_limit.map(i => `&age_limit=${i}`).join(''); + if (filters?.age_limit?.length) { + url += filters.age_limit.map(i => '&age_limit=' + i).join(''); } url += '&page=' + page; diff --git a/src/sources/sourceManager.ts b/src/sources/sourceManager.ts index e0c168669..dc93fb963 100644 --- a/src/sources/sourceManager.ts +++ b/src/sources/sourceManager.ts @@ -145,6 +145,10 @@ import { RulateScraper, ErolateScraper, } from './multisrc/rulate/RulateGenerator'; +import { + IfreedomScraper, + BookhamsterScraper, +} from './multisrc/ifreedom/IfreedomGenerator'; import { RuRanobeScraper, UkrRanobeScraper, @@ -341,6 +345,8 @@ export const sourceManager = (sourceId: number): Scraper => { 168: LitSpaceScraper, // @ts-ignore 169: AsuraLightNovelScraper, // @ts-ignore 170: ICantReadJPTLScraper, // @ts-ignore + 171: IfreedomScraper, // @ts-ignore + 172: BookhamsterScraper, // @ts-ignore 173: mtlNovelEsScraper, // @ts-ignore }; diff --git a/src/sources/sources.json b/src/sources/sources.json index b4caef053..f068206b8 100644 --- a/src/sources/sources.json +++ b/src/sources/sources.json @@ -1042,6 +1042,20 @@ "icon": "https://icantreadjapanese.files.wordpress.com/2021/03/cropped-cropped-site-icon-1.png?w=16", "lang": "English" }, + { + "sourceId": 171, + "url": "https://ifreedom.su", + "sourceName": "Свободный Мир Ранобэ", + "icon": "https://ifreedom.su/wp-content/uploads/2021/03/logo.png", + "lang": "Russian" + }, + { + "sourceId": 172, + "url": "https://bookhamster.ru", + "sourceName": "Bookhamster", + "icon": "https://bookhamster.ru/wp-content/uploads/2023/01/log.jpg", + "lang": "Russian" + }, { "sourceId": 173, "url": "https://es.mtlnovel.com",