diff --git a/src/sources/multisrc/readwn/ReadwnGenerator.js b/src/sources/multisrc/readwn/ReadwnGenerator.js index 885bbe679..029e0663c 100644 --- a/src/sources/multisrc/readwn/ReadwnGenerator.js +++ b/src/sources/multisrc/readwn/ReadwnGenerator.js @@ -4,18 +4,198 @@ const ReadwnScraper = new MultiSrcScraper( 68, 'https://www.wuxiap.com/', 'Readwn.com', + { + 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' }, + ], + }, ); const NovelmtScraper = new MultiSrcScraper( 99, 'https://www.wuxiapub.com/', 'Novelmt.com', + { + 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' }, + ], + }, ); const LtnovelScraper = new MultiSrcScraper( 100, 'https://www.ltnovel.com/', 'Ltnovel.com', + { + 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' }, + ], + }, ); export { ReadwnScraper, NovelmtScraper, LtnovelScraper }; diff --git a/src/sources/multisrc/readwn/ReadwnScraper.js b/src/sources/multisrc/readwn/ReadwnScraper.js index be6856034..96b504627 100644 --- a/src/sources/multisrc/readwn/ReadwnScraper.js +++ b/src/sources/multisrc/readwn/ReadwnScraper.js @@ -1,22 +1,54 @@ +import { FilterInputs } from '../../types/filterTypes'; import * as cheerio from 'cheerio'; import QueryString from 'qs'; class ReadwnScraper { - constructor(sourceId, baseUrl, sourceName) { + constructor(sourceId, baseUrl, sourceName, genres) { this.sourceId = sourceId; this.baseUrl = baseUrl; this.sourceName = sourceName; + this.filters = [ + { + key: 'sort', + label: 'Sort By', + values: [ + { label: 'New', value: 'newstime' }, + { label: 'Popular', value: 'onclick' }, + { label: 'Updates', value: 'lastdotime' }, + ], + inputType: FilterInputs.Picker, + }, + { + key: 'status', + label: 'Status', + values: [ + { label: 'All', value: 'all' }, + { label: 'Completed', value: 'Completed' }, + { label: 'Ongoing', value: 'Ongoing' }, + ], + inputType: FilterInputs.Picker, + }, + { + key: 'genres', + label: genres.label, + values: genres.values, + inputType: FilterInputs.Picker, + }, + ]; } - async popularNovels(page, options) { + async popularNovels(page, { showLatestNovels, filters }) { const baseUrl = this.baseUrl; const sourceId = this.sourceId; const pageNo = page - 1; - const url = `${baseUrl}list/all/${ - options?.showLatestNovels ? 'all-lastdotime' : 'all-onclick' - }-${pageNo}.html`; + let url = baseUrl + 'list/'; + url += (filters?.genres || 'all') + '/'; + url += (filters?.status || 'all') + '-'; + url += + (showLatestNovels ? 'lastdotime' : filters?.sort || 'newstime') + '-'; + url += pageNo + '.html'; const result = await fetch(url); const body = await result.text(); diff --git a/src/sources/multisrc/rulate/RulateGenerator.js b/src/sources/multisrc/rulate/RulateGenerator.js index 32c2eedfd..af60f7177 100644 --- a/src/sources/multisrc/rulate/RulateGenerator.js +++ b/src/sources/multisrc/rulate/RulateGenerator.js @@ -14,7 +14,7 @@ const RulateScraper = new MultiSrcScraper( { label: 'Арт', value: '1' }, { label: 'Боевик', value: '2' }, { label: 'Боевые искусства', value: '3' }, - { label: 'Гарем', value: '5' }, + { label: 'Гаремник', value: '5' }, { label: 'Героическое фэнтези', value: '7' }, { label: 'Детектив', value: '8' }, { label: 'Дзёсэй', value: '9' }, @@ -65,8 +65,6 @@ const RulateScraper = new MultiSrcScraper( values: [ { label: '12+', value: '3316' }, { label: '16+', value: '83' }, - { label: '18+', value: '1749' }, - { label: '21+', value: '3191' }, { label: 'Абсурд', value: '7973' }, { label: 'Авантюристы', value: '222' }, { label: 'Автомат', value: '5272' }, @@ -108,7 +106,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Америка', value: '4082' }, { label: 'Амнезия', value: '2207' }, { label: 'Аморальный герой', value: '18' }, - { label: 'Анальный секс', value: '1942' }, { label: 'Анбу', value: '8129' }, { label: 'Ангелы', value: '857' }, { label: 'Ангелы и демоны', value: '1327' }, @@ -139,19 +136,17 @@ const RulateScraper = new MultiSrcScraper( { label: 'Басейн', value: '6847' }, { label: 'Баскетбол', value: '7933' }, { label: 'Бастарды', value: '76' }, - { label: 'Бдсм', value: '444' }, { label: 'Бедность', value: '1207' }, { label: 'Без культивации', value: '668' }, { label: 'Без любовной линий', value: '2225' }, { label: 'Без перерождений', value: '262' }, { label: 'Без попаданца', value: '8065' }, { label: 'Без системы', value: '747' }, - { label: 'Без цензуры', value: '7525' }, { label: 'Без юмора', value: '763' }, { label: 'Бездна', value: '1026' }, { label: 'Безжалостное домашнее животное', value: '1245' }, { label: 'Безжалостные персонажи', value: '2986' }, - { label: 'Беззаботные персонажи ', value: '3123' }, + { label: 'Беззаботные персонажи', value: '3123' }, { label: 'Безответная любовь', value: '7815' }, { label: 'Безработный', value: '2202' }, { label: 'Безумие', value: '37' }, @@ -186,10 +181,7 @@ const RulateScraper = new MultiSrcScraper( { label: 'Богоубийца', value: '1253' }, { label: 'Богохульство', value: '95' }, { label: 'Боевая академия', value: '2108' }, - { label: 'Боевик', value: '529' }, - { label: 'Боевые искусства', value: '2090' }, { label: 'Большая грудь', value: '4717' }, - { label: 'Большой член', value: '5299' }, { label: 'Борос', value: '6533' }, { label: 'Борьба', value: '1925' }, { label: 'Борьба за власть', value: '8072' }, @@ -209,7 +201,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Бэтмен', value: '4668' }, { label: 'В первый раз', value: '6212' }, { label: 'В этот же мир', value: '49' }, - { label: 'Вагинальный секс', value: '4742' }, { label: 'Ваканда', value: '8331' }, { label: 'Валькирии', value: '4047' }, { label: 'Вампиры', value: '2206' }, @@ -278,7 +269,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Гений', value: '1650' }, { label: 'Гермиона грейнджер', value: '4280' }, { label: 'Герои', value: '185' }, - { label: 'Героическое фэнтези', value: '1148' }, { label: 'Гигаполис', value: '8393' }, { label: 'Гильгамеш', value: '6031' }, { label: 'Гильдии', value: '900' }, @@ -312,7 +302,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Главный герой сильный с самого начала', value: '88' }, { label: 'Главный герой скрывает свою силу', value: '1059' }, { label: 'Главный герой эгоист', value: '8326' }, - { label: 'Глотание спермы', value: '4491' }, { label: 'Гномы', value: '1870' }, { label: 'Гоблины', value: '2029' }, { label: 'Годзилла', value: '6990' }, @@ -321,7 +310,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Головоломка', value: '1697' }, { label: 'Горничные', value: '8091' }, { label: 'Гробница', value: '388' }, - { label: 'Групповой секс', value: '1314' }, { label: 'Дазай осаму', value: '4112' }, { label: 'Даньмэй', value: '3411' }, { label: 'Даосизм', value: '258' }, @@ -337,7 +325,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Демон лорд', value: '153' }, { label: 'Демонология', value: '2024' }, { label: 'Демоны', value: '1406' }, - { label: 'Детектив', value: '1265' }, { label: 'Дети', value: '580' }, { label: 'Джарвис', value: '933' }, { label: 'Джедай', value: '4442' }, @@ -365,12 +352,10 @@ const RulateScraper = new MultiSrcScraper( { label: 'Драко малфой', value: '46' }, { label: 'Драконы', value: '282' }, { label: 'Драконы оборотни', value: '8082' }, - { label: 'Драма', value: '611' }, { label: 'Древние времена', value: '7781' }, { label: 'Древний египет', value: '7614' }, { label: 'Древний китай', value: '473' }, { label: 'Древний мир', value: '4' }, - { label: 'Дрочка', value: '4565' }, { label: 'Другая вселенная', value: '39' }, { label: 'Другие планеты', value: '2371' }, { label: 'Другой мир', value: '2248' }, @@ -393,10 +378,8 @@ const RulateScraper = new MultiSrcScraper( { label: 'Женское доминирование', value: '4622' }, { label: 'Женщина в теле мужчины', value: '2748' }, { label: 'Женщина кошка', value: '7513' }, - { label: 'Жесткий секс', value: '2238' }, - { label: 'Жестокие персонажи ', value: '3153' }, + { label: 'Жестокие персонажи', value: '3153' }, { label: 'Жестокий мир', value: '1532' }, - { label: 'Жестокое обращение с детьми', value: '84' }, { label: 'Жестокость', value: '7880' }, { label: 'Животные', value: '7488' }, { label: 'Животные компаньоны', value: '2061' }, @@ -426,13 +409,11 @@ const RulateScraper = new MultiSrcScraper( { label: 'Змей', value: '5721' }, { label: 'Знаменитости', value: '1341' }, { label: 'Знание медицины', value: '1411' }, - { label: 'Золотой дождь', value: '1828' }, { label: 'Золушка', value: '2980' }, { label: 'Зомби', value: '1743' }, { label: 'Зомби апокалипсис', value: '912' }, { label: 'Зона 51', value: '3051' }, { label: 'Зооморфы', value: '375' }, - { label: 'Зоофилия', value: '1165' }, { label: 'Зорро', value: '6736' }, { label: 'Зрелые женщины', value: '1458' }, { label: 'Игра', value: '663' }, @@ -444,7 +425,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Изменение характера', value: '1113' }, { label: 'Изменения внешности', value: '259' }, { label: 'Изменения личности', value: '1080' }, - { label: 'Изнасилование', value: '1283' }, { label: 'Изобретения', value: '126' }, { label: 'Изуку мидория', value: '6967' }, { label: 'Император', value: '867' }, @@ -461,7 +441,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Интимные сцены', value: '3450' }, { label: 'Интрига', value: '317' }, { label: 'Интриги и заговоры', value: '1009' }, - { label: 'Инцест', value: '1934' }, { label: 'Иные миры', value: '35' }, { label: 'Исекай', value: '2979' }, { label: 'Искусственный интеллект', value: '1900' }, @@ -480,7 +459,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Кафе', value: '3934' }, { label: 'Кацуки бакуго', value: '7696' }, { label: 'Квесты', value: '1707' }, - { label: 'Киберпанк', value: '1614' }, { label: 'Киберспорт', value: '1984' }, { label: 'Кино', value: '7126' }, { label: 'Китай', value: '3762' }, @@ -490,17 +468,14 @@ const RulateScraper = new MultiSrcScraper( { label: 'Книги', value: '1068' }, { label: 'Книжный червь', value: '521' }, { label: 'Колдовство', value: '2681' }, - { label: 'Колледж', value: '5058' }, + { label: 'колледж', value: '5058' }, { label: 'Коллекционер', value: '2588' }, { label: 'Кольцо', value: '1628' }, - { label: 'Комедия', value: '1430' }, { label: 'Коммунисты', value: '1042' }, { label: 'Коноха', value: '2827' }, { label: 'Контракт', value: '4304' }, { label: 'Контроль разума', value: '485' }, { label: 'Конфеты', value: '341' }, - { label: 'Кончил внутрь', value: '4589' }, - { label: 'Копрофилия', value: '2568' }, { label: 'Корейцы', value: '6535' }, { label: 'Корея', value: '2443' }, { label: 'Королевская власть', value: '55' }, @@ -515,19 +490,16 @@ const RulateScraper = new MultiSrcScraper( { label: 'Кража навыков', value: '1159' }, { label: 'Красивая главная героиня', value: '186' }, { label: 'Красивые женщины', value: '66' }, - { label: 'Красивые персонажи ', value: '5787' }, + { label: 'Красивые персонажи', value: '5787' }, { label: 'Красивый главный герой', value: '895' }, { label: 'Крафт', value: '1599' }, - { label: 'Кровь и расчлененка', value: '7752' }, { label: 'Кроссовер', value: '75' }, - { label: 'Ксенофилия', value: '2265' }, { label: 'Ктулху', value: '920' }, { label: 'Кузнец', value: '853' }, { label: 'Кукла', value: '3085' }, { label: 'Кулинария', value: '2230' }, { label: 'Культивация', value: '1901' }, { label: 'Культивирование', value: '1860' }, - { label: 'Кунилингус', value: '4651' }, { label: 'Курама', value: '5709' }, { label: 'Курилин', value: '6134' }, { label: 'Кушина узумаки', value: '5222' }, @@ -538,7 +510,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Леон золдик', value: '7469' }, { label: 'Лес', value: '6645' }, { label: 'Лечебная магия', value: '562' }, - { label: 'Литрпг', value: '505' }, { label: 'Локи', value: '1725' }, { label: 'Лоли', value: '1576' }, { label: 'Лопата', value: '4989' }, @@ -587,7 +558,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Марс', value: '4013' }, { label: 'Марти стью', value: '594' }, { label: 'Мастер на все руки', value: '3189' }, - { label: 'Мастурбация', value: '3078' }, { label: 'Матриархат', value: '7197' }, { label: 'Мать и сестры', value: '7946' }, { label: 'Мафия', value: '353' }, @@ -596,13 +566,11 @@ const RulateScraper = new MultiSrcScraper( { label: 'Медицина', value: '441' }, { label: 'Медленная романтика', value: '8383' }, { label: 'Медленное развитие истории', value: '7462' }, - { label: 'Межрасовый секс', value: '3489' }, { label: 'Мелиодас', value: '7224' }, { label: 'Мелодрама', value: '1507' }, { label: 'Менеджмент', value: '368' }, { label: 'Мерлин', value: '3977' }, { label: 'Месть', value: '1218' }, - { label: 'Меха', value: '2303' }, { label: 'Меч', value: '48' }, { label: 'Мечники', value: '969' }, { label: 'Мечта', value: '527' }, @@ -614,12 +582,10 @@ const RulateScraper = new MultiSrcScraper( { label: 'Мимик', value: '5706' }, { label: 'Мина ашидо', value: '7695' }, { label: 'Минато намикадзе', value: '5223' }, - { label: 'Минет', value: '3289' }, { label: 'Мир будущего', value: '1770' }, { label: 'Мир земли', value: '8394' }, { label: 'Мир меча и магии', value: '1271' }, { label: 'Мистер пропер', value: '565' }, - { label: 'Мистика', value: '510' }, { label: 'Мифические существа', value: '1878' }, { label: 'Мифы и легенды', value: '4463' }, { label: 'Младшая сестра', value: '1523' }, @@ -638,7 +604,7 @@ const RulateScraper = new MultiSrcScraper( { label: 'Мортал комбат', value: '5861' }, { label: 'Мошенник', value: '959' }, { label: 'Мрачный мир', value: '1' }, - { label: 'Мужчина протагонист', value: '31' }, + { label: 'Мужчина в женском теле', value: '8405' }, { label: 'Музыка', value: '1183' }, { label: 'Мультивселенная', value: '7410' }, { label: 'Мурим', value: '7342' }, @@ -653,12 +619,10 @@ const RulateScraper = new MultiSrcScraper( { label: 'Наивный главный герой', value: '159' }, { label: 'Наложница', value: '4867' }, { label: 'Наноботы', value: '2332' }, - { label: 'Наркотики', value: '1436' }, { label: 'Наруто', value: '7177' }, { label: 'Наруто и хината', value: '5766' }, { label: 'Насилие и жестокость', value: '1440' }, { label: 'Наука', value: '2150' }, - { label: 'Научная фантастика', value: '2244' }, { label: 'Национализм', value: '5556' }, { label: 'Нацу', value: '5826' }, { label: 'Нацуки субару', value: '6656' }, @@ -674,7 +638,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Некромаг', value: '7441' }, { label: 'Некромант', value: '1914' }, { label: 'Некромантия', value: '754' }, - { label: 'Некрофилия', value: '1285' }, { label: 'Ненормативная лексика', value: '160' }, { label: 'Необычные герои', value: '67' }, { label: 'Нет людей', value: '7486' }, @@ -687,7 +650,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Ниндзюцу', value: '2354' }, { label: 'Ниндзя', value: '1330' }, { label: 'Новелла', value: '2105' }, - { label: 'Нудизм', value: '2233' }, { label: 'Нэко', value: '147' }, { label: 'Оби ван кеноби', value: '6830' }, { label: 'Обито учиха', value: '2814' }, @@ -699,8 +661,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Один в поле воин', value: '6498' }, { label: 'Онлайн игра', value: '1565' }, { label: 'Ооками теппей', value: '849' }, - { label: 'Оральный секс', value: '605' }, - { label: 'Оргия', value: '974' }, { label: 'Оригинальные персонажи', value: '7741' }, { label: 'Орки', value: '1597' }, { label: 'Оружие', value: '125' }, @@ -719,7 +679,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Ояш', value: '8048' }, { label: 'Падчерица', value: '7023' }, { label: 'Падший ангел', value: '184' }, - { label: 'Пайзури', value: '3560' }, { label: 'Палач', value: '852' }, { label: 'Палочка', value: '2682' }, { label: 'Пандорум', value: '4360' }, @@ -768,7 +727,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Повествование от первого лица', value: '1054' }, { label: 'Повествование от разных лиц', value: '600' }, { label: 'Повествование от третьего лица', value: '2116' }, - { label: 'Повседневность', value: '802' }, { label: 'Подавитель', value: '497' }, { label: 'Подземелье', value: '798' }, { label: 'Подростки', value: '636' }, @@ -788,9 +746,7 @@ const RulateScraper = new MultiSrcScraper( { label: 'Попаданец в другой мир', value: '685' }, { label: 'Попаданец в игру', value: '6466' }, { label: 'Попадание в книгу', value: '6957' }, - { label: 'Порно', value: '4023' }, { label: 'Постапокалипсис', value: '1079' }, - { label: 'Потеря девственности', value: '5879' }, { label: 'Похищение', value: '7452' }, { label: 'Поэзия', value: '1120' }, { label: 'Правитель', value: '2261' }, @@ -804,7 +760,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Призыв', value: '332' }, { label: 'Призыв в другой мир', value: '781' }, { label: 'Призыв существ', value: '412' }, - { label: 'Приключения', value: '964' }, { label: 'Приложение на телефоне', value: '5345' }, { label: 'Принудительный брак', value: '2954' }, { label: 'Принуждение', value: '1442' }, @@ -818,14 +773,13 @@ const RulateScraper = new MultiSrcScraper( { label: 'Проклятия', value: '1347' }, { label: 'Проработанный мир', value: '1711' }, { label: 'Пророчество', value: '7762' }, + { label: 'Против наркотиков', value: '8406' }, { label: 'Псайкеры', value: '3659' }, { label: 'Псионика', value: '5216' }, { label: 'Психбольница', value: '6969' }, { label: 'Психические расстройства', value: '2445' }, { label: 'Психологические травмы', value: '3418' }, - { label: 'Психология', value: '17' }, { label: 'Публичный дом', value: '7860' }, - { label: 'Публичный секс', value: '6647' }, { label: 'Путешествие в другой мир', value: '10' }, { label: 'Пух', value: '4286' }, { label: 'Пушечное мясо', value: '7234' }, @@ -839,7 +793,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Развитие персонажа', value: '1780' }, { label: 'Развитие поселения', value: '7203' }, { label: 'Развитие технологий', value: '1875' }, - { label: 'Разврат', value: '1924' }, { label: 'Раздвоение личности', value: '2062' }, { label: 'Разные расы', value: '1188' }, { label: 'Разорванная помолвка', value: '2057' }, @@ -848,7 +801,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Ранобэ', value: '1764' }, { label: 'Расизм', value: '3554' }, { label: 'Растение', value: '407' }, - { label: 'Расчленение', value: '7125' }, { label: 'Реализм', value: '1180' }, { label: 'Ребенок', value: '2881' }, { label: 'Ревность', value: '293' }, @@ -871,7 +823,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Роботы', value: '467' }, { label: 'Рок ли', value: '7002' }, { label: 'Рокси', value: '7380' }, - { label: 'Романтика', value: '1025' }, { label: 'Рон уизли', value: '4279' }, { label: 'Ророноа зоро', value: '1590' }, { label: 'Росомаха', value: '1035' }, @@ -887,7 +838,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Сайян', value: '6802' }, { label: 'Сакура', value: '2515' }, { label: 'Самец', value: '1592' }, - { label: 'Самоубийство', value: '4312' }, { label: 'Самураи', value: '576' }, { label: 'Санса старк', value: '3925' }, { label: 'Сарказм', value: '4658' }, @@ -896,31 +846,16 @@ const RulateScraper = new MultiSrcScraper( { label: 'Сборник', value: '7367' }, { label: 'Свадьба', value: '1524' }, { label: 'Сверхсила', value: '1055' }, - { label: 'Сверхъестественное', value: '1074' }, - { label: 'Свингеры', value: '514' }, { label: 'Свинцовые книги', value: '5322' }, { label: 'Связывание', value: '4506' }, { label: 'Священик', value: '61' }, { label: 'Северус снейп', value: '5783' }, { label: 'Сёдзе', value: '2219' }, - { label: 'Секс', value: '2472' }, - { label: 'Секс без проникновения', value: '151' }, - { label: 'Секс игрушки', value: '1124' }, - { label: 'Секс по принуждению', value: '8335' }, - { label: 'Секс по согласию', value: '7307' }, - { label: 'Секс рабыня', value: '7174' }, - { label: 'Секс с близнецами', value: '7457' }, - { label: 'Секс с монстрами', value: '6073' }, - { label: 'Секс с учителем', value: '2570' }, - { label: 'Секса будет много', value: '7265' }, - { label: 'Сексуальные и психологические отклонения', value: '888' }, { label: 'Секты', value: '1234' }, - { label: 'Селфцест', value: '7498' }, { label: 'Сельское хозяйство', value: '787' }, { label: 'Семейный конфликт', value: '935' }, { label: 'Семья', value: '728' }, { label: 'Сенджу', value: '6069' }, - { label: 'Сёнэн', value: '2058' }, { label: 'Серийный убийца', value: '261' }, { label: 'Сестра', value: '2342' }, { label: 'Сила глаза', value: '1019' }, @@ -933,13 +868,11 @@ const RulateScraper = new MultiSrcScraper( { label: 'Сирота', value: '297' }, { label: 'Система', value: '251' }, { label: 'Система уровней', value: '2174' }, - { label: 'Сиськи', value: '16' }, { label: 'Ситх', value: '1618' }, { label: 'Сказка', value: '3570' }, { label: 'Скайрим', value: '4032' }, { label: 'Скайуокер', value: '6828' }, { label: 'Сквиб', value: '4101' }, - { label: 'Сквирт', value: '4526' }, { label: 'Скелет', value: '1315' }, { label: 'Скрытые способности', value: '3417' }, { label: 'Скульптор', value: '1178' }, @@ -974,7 +907,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Спокойная главная героиня', value: '236' }, { label: 'Спокойный главный герой', value: '1656' }, { label: 'Спор', value: '3484' }, - { label: 'Спорт', value: '1387' }, { label: 'Спортивное тело', value: '8317' }, { label: 'Способность кражи', value: '7784' }, { label: 'Сражения', value: '23' }, @@ -988,7 +920,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Стимпанк', value: '1894' }, { label: 'Стихи', value: '1309' }, { label: 'Стратегия', value: '569' }, - { label: 'Стриптиз', value: '6914' }, { label: 'Строительство', value: '921' }, { label: 'Студент', value: '1464' }, { label: 'Студентка', value: '7518' }, @@ -1005,11 +936,9 @@ const RulateScraper = new MultiSrcScraper( { label: 'Сценарий', value: '7489' }, { label: 'Счастливый конец', value: '3293' }, { label: 'Сын', value: '3297' }, - { label: 'Сэйнэн', value: '345' }, { label: 'Сэм винчестер', value: '7461' }, { label: 'Сюаньхуа', value: '2560' }, { label: 'Сюжетные повороты', value: '7202' }, - { label: 'Сянься', value: '1126' }, { label: 'Таблетки для развития', value: '589' }, { label: 'Таинственное прошлое', value: '769' }, { label: 'Тайная личность', value: '8096' }, @@ -1018,7 +947,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Тайные отношения', value: '7450' }, { label: 'Тайцы', value: '6413' }, { label: 'Танки', value: '1787' }, - { label: 'Твинцест', value: '2450' }, { label: 'Творчество', value: '20' }, { label: 'Тейлор эберт', value: '3526' }, { label: 'Телекинез', value: '2203' }, @@ -1029,7 +957,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Темное фэнтези', value: '1675' }, { label: 'Темные эльфы', value: '7198' }, { label: 'Тенсейган', value: '4367' }, - { label: 'Тентакли', value: '3236' }, { label: 'Тетя', value: '4194' }, { label: 'Техномагия', value: '508' }, { label: 'Тиран', value: '1174' }, @@ -1042,14 +969,12 @@ const RulateScraper = new MultiSrcScraper( { label: 'Тор и локи', value: '2645' }, { label: 'Торговля', value: '1637' }, { label: 'Травничество', value: '221' }, - { label: 'Трагедия', value: '470' }, { label: 'Трагическое прошлое', value: '644' }, { label: 'Трактир', value: '1918' }, { label: 'Трансмиграция', value: '1071' }, { label: 'Трансформация', value: '156' }, { label: 'Трансформеры', value: '4616' }, { label: 'Тревор филипс', value: '5427' }, - { label: 'Триллер', value: '1676' }, { label: 'Тройняшки', value: '7169' }, { label: 'Троли', value: '4250' }, { label: 'Трудолюбивый главный герой', value: '248' }, @@ -1062,7 +987,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Убийцы драконов', value: '1484' }, { label: 'Увечья', value: '3266' }, { label: 'Удача', value: '6575' }, - { label: 'Ужасы', value: '1747' }, { label: 'Узумаки', value: '7579' }, { label: 'Укротитель', value: '367' }, { label: 'Умения', value: '531' }, @@ -1078,7 +1002,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Усопп', value: '6737' }, { label: 'Устроенный брак', value: '2223' }, { label: 'Усыновление', value: '395' }, - { label: 'Уся', value: '1172' }, { label: 'Утопия', value: '4677' }, { label: 'Уход за детьми', value: '7609' }, { label: 'Учеба в университете', value: '2797' }, @@ -1089,9 +1012,7 @@ const RulateScraper = new MultiSrcScraper( { label: 'Фамильяры', value: '1471' }, { label: 'Фанатичная любовь', value: '6721' }, { label: 'Фанаты', value: '7739' }, - { label: 'Фантастика', value: '286' }, { label: 'Фантастический мир', value: '2414' }, - { label: 'Фанфик', value: '767' }, { label: 'Фармацевт', value: '1706' }, { label: 'Фашисты', value: '3118' }, { label: 'Феи', value: '7185' }, @@ -1101,11 +1022,9 @@ const RulateScraper = new MultiSrcScraper( { label: 'Фенрир', value: '2740' }, { label: 'Феодализм', value: '280' }, { label: 'Ферма', value: '285' }, - { label: 'Фетиш', value: '2398' }, { label: 'Фехтовальщик', value: '4685' }, { label: 'Филиппины', value: '128' }, { label: 'Философия', value: '1859' }, - { label: 'Фистинг', value: '4206' }, { label: 'Флафф', value: '5048' }, { label: 'Флер делакур', value: '5191' }, { label: 'Флэш', value: '4667' }, @@ -1114,7 +1033,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Фугаку', value: '6161' }, { label: 'Фурри', value: '6631' }, { label: 'Футбол', value: '1121' }, - { label: 'Фэнтези', value: '174' }, { label: 'Фэнтезийный мир', value: '1556' }, { label: 'Хакер', value: '475' }, { label: 'Халк', value: '1196' }, @@ -1123,7 +1041,6 @@ const RulateScraper = new MultiSrcScraper( { label: 'Хаос', value: '3117' }, { label: 'Харизматичный главный герой', value: '2350' }, { label: 'Хатаке какаши', value: '2820' }, - { label: 'Хентай', value: '1682' }, { label: 'Хиддлстон', value: '5903' }, { label: 'Химавари', value: '2583' }, { label: 'Хината', value: '2513' }, @@ -1161,21 +1078,15 @@ const RulateScraper = new MultiSrcScraper( { label: 'Эволюция', value: '2040' }, { label: 'Экзорцизм', value: '676' }, { label: 'Экономика', value: '121' }, - { label: 'Эксгибиционизм', value: '1643' }, { label: 'Экспансия', value: '110' }, { label: 'Эксперименты над людьми', value: '7408' }, - { label: 'Экшен', value: '2859' }, { label: 'Элементальная магия', value: '1264' }, - { label: 'Элементы бдсм', value: '1323' }, { label: 'Эльфы', value: '1362' }, { label: 'Эротика', value: '2053' }, - { label: 'Этти', value: '1317' }, { label: 'Юмор', value: '7360' }, { label: 'Яды', value: '201' }, { label: 'Якудза', value: '4599' }, { label: 'Яндере', value: '7111' }, - { label: 'Bl', value: '8282' }, - { label: 'Gl', value: '8284' }, { label: 'Rpg', value: '90' }, { label: 'Star wars', value: '1985' }, { label: 'Time skip', value: '824' }, @@ -1224,7 +1135,7 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Приключения', value: '38' }, { label: 'Публичный секс', value: '33' }, { label: 'Романтика', value: '25' }, - { label: 'С изображениями ', value: '36' }, + { label: 'С изображениями', value: '36' }, { label: 'Сверхъестественное', value: '34' }, { label: 'Смат', value: '37' }, { label: 'Тентакли', value: '26' }, @@ -1267,6 +1178,7 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Бесстрашные персонажи', value: '339' }, { label: 'Библиотека', value: '265' }, { label: 'Бистиалити', value: '391' }, + { label: 'Битва за трон', value: '404' }, { label: 'Близнецы', value: '198' }, { label: 'Блондинка', value: '91' }, { label: 'Богатые персонажи', value: '248' }, @@ -1297,12 +1209,15 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Вирус', value: '203' }, { label: 'Внучка', value: '189' }, { label: 'Война', value: '387' }, + { label: 'Воспоминания из прошлого', value: '405' }, + { label: 'Враги становятся любовниками', value: '410' }, { label: 'Время', value: '81' }, { label: 'Второй шанс', value: '244' }, { label: 'Вуайеризм', value: '62' }, { label: 'Выживание', value: '135' }, { label: 'Гарем', value: '107' }, { label: 'Гарри поттер', value: '97' }, + { label: 'Гвен', value: '402' }, { label: 'Гг имба', value: '87' }, { label: 'Генетические модификации', value: '314' }, { label: 'Гипноз', value: '143' }, @@ -1361,6 +1276,7 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Золотой дождь', value: '146' }, { label: 'Зомби апокалипсис', value: '241' }, { label: 'Зоофилия', value: '144' }, + { label: 'Зрелые женщины', value: '398' }, { label: 'Игровые элементы', value: '385' }, { label: 'Извращения', value: '192' }, { label: 'Измена', value: '71' }, @@ -1376,6 +1292,7 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Интроверт', value: '38' }, { label: 'Инфекция', value: '207' }, { label: 'Инцест', value: '35' }, + { label: 'Исторический роман', value: '406' }, { label: 'Камера', value: '92' }, { label: 'Колледж', value: '82' }, { label: 'Комикс', value: '286' }, @@ -1393,7 +1310,9 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Кулинария', value: '362' }, { label: 'Культвация', value: '86' }, { label: 'Кунилингус', value: '152' }, + { label: 'Кушина', value: '399' }, { label: 'Лактация', value: '11' }, + { label: 'Лесби', value: '408' }, { label: 'Лишение девственности', value: '212' }, { label: 'Лоли', value: '218' }, { label: 'Лунатизм', value: '279' }, @@ -1428,6 +1347,7 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Минет', value: '52' }, { label: 'Мистика', value: '250' }, { label: 'Младшая сестра', value: '154' }, + { label: 'Много спермы', value: '409' }, { label: 'Модель', value: '324' }, { label: 'Монстры', value: '333' }, { label: 'Мошеничество', value: '44' }, @@ -1435,6 +1355,7 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Мужчина протагонист', value: '108' }, { label: 'Музыка', value: '331' }, { label: 'Мэри съюха', value: '309' }, + { label: 'Наруко', value: '400' }, { label: 'Наруто', value: '46' }, { label: 'Насилие', value: '128' }, { label: 'Насилие и жестокость', value: '193' }, @@ -1495,6 +1416,7 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Постапокалипсис', value: '332' }, { label: 'Потеря девственности', value: '142' }, { label: 'Похищение', value: '161' }, + { label: 'Преданные любовный интерес', value: '407' }, { label: 'Приключения', value: '134' }, { label: 'Принуждение', value: '66' }, { label: 'Психология', value: '238' }, @@ -1506,6 +1428,7 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Разврат', value: '64' }, { label: 'Райзен', value: '95' }, { label: 'Раса инопланетных космических лесбиянок', value: '294' }, + { label: 'Религия', value: '403' }, { label: 'Рестленг', value: '84' }, { label: 'Риас гремори', value: '389' }, { label: 'Романтика', value: '178' }, @@ -1618,6 +1541,7 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Юмор', value: '105' }, { label: 'Яндере', value: '155' }, { label: 'Bl', value: '125' }, + { label: 'Dc', value: '411' }, { label: 'Gl', value: '96' }, { label: 'Harry potter', value: '190' }, { label: 'Harry potter', value: '168' }, @@ -1625,6 +1549,7 @@ const ErolateScraper = new MultiSrcScraper( { label: 'Marvel', value: '194' }, { label: 'Marvel', value: '174' }, { label: 'Milf', value: '167' }, + { label: 'Mj', value: '401' }, ], inputType: FilterInputs.Checkbox, }, diff --git a/src/sources/multisrc/rulate/RulateScraper.js b/src/sources/multisrc/rulate/RulateScraper.js index 914519cc8..300ee7d3c 100644 --- a/src/sources/multisrc/rulate/RulateScraper.js +++ b/src/sources/multisrc/rulate/RulateScraper.js @@ -81,11 +81,11 @@ class RulateScraper { async popularNovels(page, { showLatestNovels, filters }) { const baseUrl = this.baseUrl; const sourceId = this.sourceId; - let url = baseUrl + '/search?t=&cat=2'; - url += '&sort=' + showLatestNovels ? '4' : filters?.sort || '6'; - url += '&type=' + filters?.type || '0'; - url += '&atmosphere=' + filters?.atmosphere || '0'; - url += '&adult=' + filters?.adult || '0'; + let url = baseUrl + '/search?t=&cat=0'; + url += '&type=' + (filters?.type || '0'); + url += '&sort=' + (showLatestNovels ? '4' : filters?.sort || '6'); + url += '&atmosphere=' + (filters?.atmosphere || '0'); + url += '&adult=' + (filters?.adult || '0'); if (filters?.genres?.length) { url += filters.genres.map(i => `&genres[]=${i}`).join(''); @@ -145,45 +145,51 @@ class RulateScraper { novelUrl, }; - novel.novelName = loadedCheerio('div[class="container"] > div > div > h1') + novel.novelName = loadedCheerio( + 'div[class="container"] > div > div > h1, div.span8:nth-child(1) > h1:nth-child(2)', + ) .text() .trim(); novel.novelCover = this.baseUrl + loadedCheerio('div[class="images"] > div img').attr('src'); - novel.summary = loadedCheerio('#Info > div:nth-child(3)').html(); + novel.summary = loadedCheerio( + '#Info > div:nth-child(3), .book-description', + ).html(); let genre = []; if (novel?.summary) { novel.summary = htmlToText(novel.summary); } - loadedCheerio('div[class="span5"] > p').each(function () { - switch (loadedCheerio(this).find('strong').text()) { - case 'Автор:': - novel.author = loadedCheerio(this).find('em > a').text().trim(); - break; - case 'Выпуск:': - novel.status = - loadedCheerio(this).find('em').text().trim() === 'продолжается' - ? Status.ONGOING - : Status.COMPLETED; - break; - case 'Тэги:': - loadedCheerio(this) - .find('em > a') - .each(function () { - genre.push(loadedCheerio(this).text()); - }); - break; - case 'Жанры:': - loadedCheerio(this) - .find('em > a') - .each(function () { - genre.push(loadedCheerio(this).text()); - }); - break; - } - }); + loadedCheerio('div.span5 > p, .span5 > div:nth-child(2) > p').each( + function () { + switch (loadedCheerio(this).find('strong').text()) { + case 'Автор:': + novel.author = loadedCheerio(this).find('em > a').text().trim(); + break; + case 'Выпуск:': + novel.status = + loadedCheerio(this).find('em').text().trim() === 'продолжается' + ? Status.Ongoing + : Status.Completed; + break; + case 'Тэги:': + loadedCheerio(this) + .find('em > a') + .each(function () { + genre.push(loadedCheerio(this).text()); + }); + break; + case 'Жанры:': + loadedCheerio(this) + .find('em > a') + .each(function () { + genre.push(loadedCheerio(this).text()); + }); + break; + } + }, + ); if (genre.length > 0) { novel.genre = genre.reverse().join(','); diff --git a/src/sources/ru/bookriver.js b/src/sources/ru/bookriver.js index 16b5b459c..fdf084115 100644 --- a/src/sources/ru/bookriver.js +++ b/src/sources/ru/bookriver.js @@ -91,16 +91,12 @@ const parseChapter = async (novelUrl, chapterUrl) => { }; const searchNovels = async searchTerm => { - const url = `${baseUrl}/search/books?keyword=${searchTerm}`; + const url = `https://api.bookriver.ru/api/v1/search/autocomplete?keyword=${searchTerm}&page=1&perPage=10`; const result = await fetch(url); - const body = await result.text(); - - const loadedCheerio = cheerio.load(body); - let json = loadedCheerio('#__NEXT_DATA__').html(); - json = JSON.parse(json); + const json = await result.json(); let novels = []; - json.props.pageProps.state.catalog.books.books.forEach(novel => + json?.data?.books?.forEach(novel => novels.push({ sourceId, novelName: novel.name, diff --git a/src/sources/ru/freedlit.ts b/src/sources/ru/freedlit.ts new file mode 100644 index 000000000..6dcf71ec2 --- /dev/null +++ b/src/sources/ru/freedlit.ts @@ -0,0 +1,254 @@ +import * as cheerio from 'cheerio'; +import { FilterInputs } from '../types/filterTypes'; +import { + SourceChapter, + SourceChapterItem, + SourceNovel, + SourceNovelItem, +} from '../types'; + +const sourceId = 168; +const sourceName = 'LitSpace'; + +const baseUrl = 'https://freedlit.space'; + +const popularNovels = async (page, { showLatestNovels, filters }) => { + let url = baseUrl + '/books/'; + url += (filters?.genre || 'all') + '?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); + + const novels: SourceNovelItem[] = []; + loadedCheerio('#bookListBlock > div > div').each(function () { + const novelName = loadedCheerio(this).find('div > h4 > a').text()?.trim(); + const novelCover = loadedCheerio(this) + .find('div > a > img') + .attr('src') + ?.trim(); + const novelUrl = loadedCheerio(this) + .find('div > h4 > a') + .attr('href') + ?.trim(); + + novels.push({ sourceId, novelName, novelCover, novelUrl }); + }); + + return { novels }; +}; + +const parseNovelAndChapters = async novelUrl => { + const result = await fetch(novelUrl); + const body = await result.text(); + const loadedCheerio = cheerio.load(body); + + const novel: SourceNovel = { + sourceId, + sourceName, + novelUrl, + url: novelUrl, + 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(), + genre: loadedCheerio('.genre-list > a') + .map((index, element) => loadedCheerio(element).text()) + .get() + .join(','), + }; + + let chapters: SourceChapterItem[] = []; + + loadedCheerio('#nav-contents > div').each(function () { + const chapterName = loadedCheerio(this).find('a').text(); + const releaseDate = loadedCheerio(this).find('span[class="date"]').text(); + const chapterUrl = loadedCheerio(this).find('a').attr('href'); + if (chapterName && chapterUrl) { + chapters.push({ chapterName, releaseDate, chapterUrl }); + } + }); + + novel.chapters = chapters; + return novel; +}; + +const parseChapter = async (novelUrl, chapterUrl) => { + const result = await fetch(chapterUrl); + const body = await result.text(); + const loadedCheerio = cheerio.load(body); + + loadedCheerio('div[class="standart-block"]').remove(); + loadedCheerio('div[class="mobile-block"]').remove(); + + const chapter: SourceChapter = { + sourceId, + novelUrl, + chapterUrl, + chapterName: loadedCheerio('li[class="active"]').text()?.trim(), + chapterText: loadedCheerio('div[class="chapter"]').html(), + }; + + return chapter; +}; + +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); + + let novels: Novel.Item[] = []; + loadedCheerio('#bookListBlock > div').each(function () { + const novelName = loadedCheerio(this).find('h4 > a').text()?.trim(); + const novelCover = loadedCheerio(this).find('a > img').attr('src')?.trim(); + const novelUrl = loadedCheerio(this).find('h4 > a').attr('href')?.trim(); + + novels.push({ sourceId, novelName, novelCover, novelUrl }); + }); + + return novels; +}; + +const filters = [ + { + key: 'sort', + label: 'Сортировка:', + values: [ + { label: 'По популярности', value: 'popular' }, + { label: 'По количеству комментариев', value: 'comments' }, + { label: 'По количеству лайков', value: 'likes' }, + { label: 'По новизне', value: 'recent' }, + { label: 'По просмотрам', value: 'views' }, + ], + inputType: FilterInputs.Picker, + }, + { + key: 'genre', + label: 'Жанры:', + values: [ + { label: 'Любой жанр', value: 'all' }, + { label: 'Альтернативная история', value: 'alternative-history' }, + { label: 'Антиутопия', value: 'dystopia' }, + { label: 'Бизнес-литература', value: 'business-literature' }, + { label: 'Боевая фантастика', value: 'combat-fiction' }, + { label: 'Боевик', value: 'action' }, + { label: 'Боевое фэнтези', value: 'combat-fantasy' }, + { label: 'Бояръ-Аниме', value: 'boyar-anime' }, + { label: 'Героическая фантастика', value: 'heroic-fiction' }, + { label: 'Героическое фэнтези', value: 'heroic-fantasy' }, + { label: 'Городское фэнтези', value: 'urban-fantasy' }, + { label: 'Гримдарк', value: 'grimdark' }, + { label: 'Детектив', value: 'mystery' }, + { label: 'Детская литература', value: 'kids-literature' }, + { label: 'Документальная проза', value: 'biography' }, + { label: 'Историческая проза', value: 'historical-fiction' }, + { label: 'Исторический детектив', value: 'historical-mystery' }, + { + label: 'Исторический любовный роман', + value: 'historical-romantic-novel', + }, + { label: 'Историческое фэнтези', value: 'historical-fantasy' }, + { label: 'Киберпанк', value: 'cyberpunk' }, + { label: 'Космическая фантастика', value: 'cosmic-fiction' }, + { label: 'ЛитРПГ', value: 'litrpg' }, + { label: 'Лоу / Низкое фэнтези', value: 'low-fantasy' }, + { label: 'Любовное фэнтези', value: 'romantic-fantasy' }, + { label: 'Любовный роман', value: 'romantic-novel' }, + { label: 'Мистика', value: 'mystic' }, + { label: 'Мистический детектив', value: 'mystic-detective' }, + { label: 'Научная фантастика', value: 'science-fiction' }, + { label: 'Подростковая проза', value: 'young-adult' }, + { label: 'Политический роман', value: 'political-romance' }, + { label: 'Попаданцы', value: 'accidental-travel' }, + { label: 'Попаданцы в магические миры', value: 'magic-worlds-travel' }, + { label: 'Попаданцы во времени', value: 'time-travel' }, + { label: 'Порнотика', value: 'pornotica' }, + { label: 'Постапокалипсис', value: 'post-apocalypse' }, + { label: 'Поэзия', value: 'poetry' }, + { label: 'Приключения', value: 'adventure' }, + { label: 'Публицистика', value: 'journalism' }, + { label: 'Пьеса', value: 'play' }, + { label: 'Развитие личности', value: 'how-to-book' }, + { label: 'Разное', value: 'other' }, + { label: 'Реализм', value: 'Realism' }, + { label: 'РеалРПГ', value: 'realrpg' }, + { label: 'Репликация', value: 'replication' }, + { label: 'Романтическая эротика', value: 'romantic-erotic-fiction' }, + { label: 'Сказка', value: 'fairy-tale' }, + { label: 'Слэш', value: 'slash' }, + { label: 'Современная проза', value: 'modern-prose' }, + { label: 'Современный любовный роман', value: 'modern-romantic-novel' }, + { label: 'Социальная фантастика', value: 'social-fiction' }, + { label: 'Стимпанк', value: 'steampunk' }, + { label: 'Сценарий', value: 'scenario' }, + { label: 'Сюаньхуань', value: 'xuanhuan' }, + { label: 'Сянься', value: 'xianxia' }, + { label: 'Тёмное фэнтези', value: 'dark-fantasy' }, + { label: 'Триллер', value: 'thriller' }, + { label: 'Уся', value: 'wuxia' }, + { label: 'Фантастика', value: 'fiction' }, + { label: 'Фантастический детектив', value: 'mystery-fiction' }, + { label: 'Фанфик', value: 'fan-fiction' }, + { label: 'Фемслэш', value: 'femslash' }, + { label: 'Фэнтези', value: 'fantasy' }, + { label: 'Хоррор', value: 'horror' }, + { label: 'Шпионский детектив', value: 'spy-crime' }, + { label: 'Эпическое фэнтези', value: 'epic-fantasy' }, + { label: 'Эротика', value: 'erotic-fiction' }, + { label: 'Эротическая фантастика', value: 'erotic-fiction' }, + { label: 'Эротический фанфик', value: 'erotic-fan-fiction' }, + { label: 'Эротическое фэнтези', value: 'erotic-fantasy' }, + { label: 'Этническое фэнтези', value: 'ethnic-fantasy' }, + { label: 'Юмор', value: 'humor' }, + { label: 'Юмористическая фантастика', value: 'humor-fiction' }, + { label: 'Юмористическое фэнтези', value: 'humor-fantasy' }, + { label: 'RPS', value: 'rps' }, + ], + inputType: FilterInputs.Picker, + }, + { + key: 'status', + label: 'Статус:', + values: [ + { label: 'Любой статус', value: 'all' }, + { label: 'В процессе', value: 'in-process' }, + { label: 'Завершено', value: 'finished' }, + ], + inputType: FilterInputs.Picker, + }, + { + key: 'access', + label: 'Доступ:', + values: [ + { label: 'Любой доступ', value: 'all' }, + { label: 'Бесплатные', value: 'free' }, + { label: 'Платные', value: 'paid' }, + ], + inputType: FilterInputs.Picker, + }, + { + key: 'adult', + label: 'Возрастные ограничения:', + values: [ + { label: 'Скрыть 18+', value: 'hide' }, + { label: 'Показать +18', value: 'show' }, + ], + inputType: FilterInputs.Picker, + }, +]; + +const LitSpaceScraper = { + popularNovels, + parseNovelAndChapters, + parseChapter, + searchNovels, + filters, +}; + +export default LitSpaceScraper; diff --git a/src/sources/ru/jaomix.js b/src/sources/ru/jaomix.js index 149d5cfb9..290680367 100644 --- a/src/sources/ru/jaomix.js +++ b/src/sources/ru/jaomix.js @@ -8,17 +8,26 @@ const sourceName = 'Jaomix'; const baseUrl = 'https://jaomix.ru'; const popularNovels = async (page, { showLatestNovels, filters }) => { - let url = baseUrl + '/?searchrn&sortby='; - url += showLatestNovels ? 'upd' : filters?.sort || 'count'; + let url = baseUrl + '/?searchrn'; - if (filters?.type?.length) { - url += filters.type.map(i => `&lang[]=${i}`).join(''); + if (filters?.lang instanceof Array) { + url += filters.lang.map((lang, idx) => `&lang[${idx}]=${lang}`).join(''); } - - if (filters?.genres?.length) { - url += filters.genres.map(i => `&genre[]=${i}`).join(''); + if (filters?.genre instanceof Array) { + url += filters.genre + .map((genre, idx) => `&genre[${idx}]=${genre}`) + .join(''); + } + if (filters?.delgenre instanceof Array) { + url += filters.delgenre + .map((genre, idx) => `&delgenre[${idx}]=del ${genre}`) + .join(''); } - url += `&page=${page}`; + + url += '&sortcountchapt=' + (filters?.sortcountchapt || '1'); + url += '&sortdaycreate=' + (filters?.sortdaycreate || '1'); + url += '&sortby=' + (showLatestNovels ? 'upd' : filters?.sortby || 'topweek'); + url += '&gpage=' + page; const result = await fetch(url); let body = await result.text(); @@ -136,30 +145,105 @@ const searchNovels = async searchTerm => { const filters = [ { - key: 'sort', - label: 'Сортировка', + key: 'sortby', + label: 'Сортировка:', values: [ - { label: 'Имя', value: 'alphabet' }, - { label: 'Просмотры', value: 'count' }, - { label: 'Дате добавления', value: 'new' }, - { label: 'Дате обновления', value: 'upd' }, + { label: 'Топ недели', value: 'topweek' }, + { label: 'По алфавиту', value: 'alphabet' }, + { label: 'По дате обновления', value: 'upd' }, + { label: 'По дате создания', value: 'new' }, + { label: 'По просмотрам', value: 'count' }, + { label: 'Топ года', value: 'topyear' }, + { label: 'Топ дня', value: 'topday' }, + { label: 'Топ за все время', value: 'alltime' }, + { label: 'Топ месяца', value: 'topmonth' }, ], inputType: FilterInputs.Picker, }, { - key: 'type', - label: 'Тип', + key: 'sortdaycreate', + label: 'Дата добавления:', values: [ - { label: 'Английский', value: 'Английский' }, - { label: 'Китайский', value: 'Китайский' }, - { label: 'Корейский', value: 'Корейский' }, - { label: 'Японский', value: 'Японский' }, + { label: 'Любое', value: '1' }, + { label: 'От 120 до 180 дней', value: '1218' }, + { label: 'От 180 до 365 дней', value: '1836' }, + { label: 'От 30 до 60 дней', value: '3060' }, + { label: 'От 365 дней', value: '365' }, + { label: 'От 60 до 90 дней', value: '6090' }, + { label: 'От 90 до 120 дней', value: '9012' }, + { label: 'Послед. 30 дней', value: '30' }, + ], + inputType: FilterInputs.Picker, + }, + { + key: 'sortcountchapt', + label: 'Количество глав:', + values: [ + { label: 'Любое кол-во глав', value: '1' }, + { label: 'До 500', value: '500' }, + { label: 'От 1000 до 2000', value: '1020' }, + { label: 'От 2000 до 3000', value: '2030' }, + { label: 'От 3000 до 4000', value: '3040' }, + { label: 'От 4000', value: '400' }, + { label: 'От 500 до 1000', value: '510' }, + ], + inputType: FilterInputs.Picker, + }, + { + key: 'genre', + label: 'Жанры:', + values: [ + { label: 'Боевые Искусства', value: 'Боевые Искусства' }, + { label: 'Виртуальный Мир', value: 'Виртуальный Мир' }, + { label: 'Гарем', value: 'Гарем' }, + { label: 'Детектив', value: 'Детектив' }, + { label: 'Драма', value: 'Драма' }, + { label: 'Игра', value: 'Игра' }, + { label: 'Истории из жизни', value: 'Истории из жизни' }, + { label: 'Исторический', value: 'Исторический' }, + { label: 'История', value: 'История' }, + { label: 'Исэкай', value: 'Исэкай' }, + { label: 'Комедия', value: 'Комедия' }, + { label: 'Меха', value: 'Меха' }, + { label: 'Мистика', value: 'Мистика' }, + { label: 'Научная Фантастика', value: 'Научная Фантастика' }, + { label: 'Повседневность', value: 'Повседневность' }, + { label: 'Постапокалипсис', value: 'Постапокалипсис' }, + { label: 'Приключения', value: 'Приключения' }, + { label: 'Психология', value: 'Психология' }, + { label: 'Романтика', value: 'Романтика' }, + { label: 'Сверхъестественное', value: 'Сверхъестественное' }, + { label: 'Сёнэн', value: 'Сёнэн' }, + { label: 'Сёнэн-ай', value: 'Сёнэн-ай' }, + { label: 'Спорт', value: 'Спорт' }, + { label: 'Сэйнэн', value: 'Сэйнэн' }, + { label: 'Сюаньхуа', value: 'Сюаньхуа' }, + { label: 'Трагедия', value: 'Трагедия' }, + { label: 'Триллер', value: 'Триллер' }, + { label: 'Фантастика', value: 'Фантастика' }, + { label: 'Фэнтези', value: 'Фэнтези' }, + { label: 'Хоррор', value: 'Хоррор' }, + { label: 'Школьная жизнь', value: 'Школьная жизнь' }, + { label: 'Шоунен', value: 'Шоунен' }, + { label: 'Экшн', value: 'Экшн' }, + { label: 'Этти', value: 'Этти' }, + { label: 'Юри', value: 'Юри' }, + { label: 'Adult', value: 'Adult' }, + { label: 'Ecchi', value: 'Ecchi' }, + { label: 'Josei', value: 'Josei' }, + { label: 'Lolicon', value: 'Lolicon' }, + { label: 'Mature', value: 'Mature' }, + { label: 'Shoujo', value: 'Shoujo' }, + { label: 'Wuxia', value: 'Wuxia' }, + { label: 'Xianxia', value: 'Xianxia' }, + { label: 'Xuanhuan', value: 'Xuanhuan' }, + { label: 'Yaoi', value: 'Yaoi' }, ], inputType: FilterInputs.Checkbox, }, { - key: 'genres', - label: 'Жанры', + key: 'delgenre', + label: 'Исключить жанры:', values: [ { label: 'Боевые Искусства', value: 'Боевые Искусства' }, { label: 'Виртуальный Мир', value: 'Виртуальный Мир' }, @@ -209,6 +293,17 @@ const filters = [ ], inputType: FilterInputs.Checkbox, }, + { + key: 'lang', + label: 'Выбрать языки:', + values: [ + { label: 'Английский', value: 'Английский' }, + { label: 'Китайский', value: 'Китайский' }, + { label: 'Корейский', value: 'Корейский' }, + { label: 'Японский', value: 'Японский' }, + ], + inputType: FilterInputs.Checkbox, + }, ]; const JaomixScraper = { diff --git a/src/sources/ru/renovels.js b/src/sources/ru/renovels.js index 1f1035ee1..11a09747f 100644 --- a/src/sources/ru/renovels.js +++ b/src/sources/ru/renovels.js @@ -13,22 +13,26 @@ const popularNovels = async (page, { showLatestNovels, filters }) => { url += filters?.order ? filters?.order?.replace('+', '') : '-'; url += showLatestNovels ? 'chapter_date' : filters?.sort || 'rating'; - if (filters?.type?.length) { - url += filters.type.map(i => `&types=${i}`).join(''); + if (filters?.genres instanceof Array) { + url += filters.genres.map(i => `&genres=${i}`).join(''); } - if (filters?.statuss?.length) { - url += filters?.statuss.map(i => `&status=${i}`).join(''); + if (filters?.status instanceof Array) { + url += filters?.status.map(i => `&status=${i}`).join(''); } - if (filters?.genres?.length) { - url += filters.genres.map(i => `&genres=${i}`).join(''); + if (filters?.types instanceof Array) { + url += filters.types.map(i => `&types=${i}`).join(''); } - if (filters?.categories?.length) { + if (filters?.categories instanceof Array) { url += filters.categories.map(i => `&categories=${i}`).join(''); } + if (filters?.age_limit instanceof Array) { + url += filters.age_limit.map(i => `&age_limit=${i}`).join(''); + } + url += '&page=' + page; const result = await fetch(url); @@ -38,8 +42,9 @@ const popularNovels = async (page, { showLatestNovels, filters }) => { body.content.forEach(novel => novels.push({ sourceId, - novelName: novel.rus_name, - cover: baseUrl + (novel.img?.high || novel.img?.mid || novel.img.low), + novelName: novel.main_name || novel.secondary_name, + novelCover: + baseUrl + (novel.img?.high || novel.img?.mid || novel.img.low), novelUrl: novel.dir, }), ); @@ -56,13 +61,13 @@ const parseNovelAndChapters = async novelUrl => { sourceName, url: baseUrl + '/novel/' + body.content.dir, novelUrl, - novelName: body.content.rus_name, + novelName: body.content.main_name || body.content.secondary_name, summary: htmlToText(body.content.description), novelCover: baseUrl + (body.content.img?.high || body.content.img?.mid || body.content.img.low), status: - body.content.status.name === 'Продолжается' + body.content?.status?.name === 'Продолжается' ? Status.ONGOING : Status.COMPLETED, }; @@ -75,14 +80,15 @@ const parseNovelAndChapters = async novelUrl => { novel.genre = tags.join(','); } - let all = (body.content.count_chapters / 100 + 1) ^ 0; + let all = Math.floor(body.content.count_chapters / 100 + 1); + let branch_id = body.content.branches?.[0]?.id || body.content.id; let chapters = []; for (let i = 0; i < all; i++) { let chapterResult = await fetch( baseUrl + '/api/titles/chapters/?branch_id=' + - body.content.branches[0].id + + branch_id + '&count=100&page=' + (i + 1), ); @@ -128,8 +134,9 @@ const searchNovels = async searchTerm => { body.content.forEach(novel => novels.push({ sourceId, - novelName: novel.rus_name, - cover: baseUrl + (novel.img?.high || novel.img?.mid || novel.img.low), + novelName: novel.main_name || novel.secondary_name, + novelCover: + baseUrl + (novel.img?.high || novel.img?.mid || novel.img.low), novelUrl: novel.dir, }), ); @@ -155,41 +162,14 @@ const filters = [ key: 'order', label: 'Порядок', values: [ - { label: 'По убыванию', value: '-' }, // desc - { label: 'По возрастанию', value: '+' }, // asc + { label: 'По убыванию', value: '-' }, + { label: 'По возрастанию', value: '+' }, ], inputType: FilterInputs.Picker, }, - { - key: 'type', - label: 'Тип', - values: [ - { label: 'Авторское', value: '1' }, - { label: 'Другое', value: '7' }, - { label: 'Запад', value: '5' }, - { label: 'Китай', value: '4' }, - { label: 'Корея', value: '3' }, - { label: 'Фанфики', value: '6' }, - { label: 'Япония', value: '2' }, - ], - inputType: FilterInputs.Checkbox, - }, - { - key: 'statuss', - label: 'Статус тайтла', - values: [ - { label: 'Закончен', value: '0' }, - { label: 'Продолжается', value: '1' }, - { label: 'Заморожен', value: '2' }, - { label: 'Нет переводчика', value: '3' }, - { label: 'Анонс', value: '4' }, - { label: 'Лицензировано', value: '5' }, - ], - inputType: FilterInputs.Checkbox, - }, { key: 'genres', - label: 'Жанры', + label: 'Жанры:', values: [ { label: 'Боевик', value: '112' }, { label: 'Война', value: '123' }, @@ -226,7 +206,7 @@ const filters = [ }, { key: 'categories', - label: 'Категории', + label: 'Тэги:', values: [ { label: '[Награжденная работа]', value: '648' }, { label: '18+', value: '423' }, @@ -928,6 +908,43 @@ const filters = [ ], inputType: FilterInputs.Checkbox, }, + { + key: 'types', + label: 'Типы:', + values: [ + { label: 'Авторское', value: '1' }, + { label: 'Другое', value: '7' }, + { label: 'Запад', value: '5' }, + { label: 'Китай', value: '4' }, + { label: 'Корея', value: '3' }, + { label: 'Фанфики', value: '6' }, + { label: 'Япония', value: '2' }, + ], + inputType: FilterInputs.Checkbox, + }, + { + key: 'status', + label: 'Статус проекта:', + values: [ + { label: 'Анонс', value: '4' }, + { label: 'Закончен', value: '0' }, + { label: 'Заморожен', value: '2' }, + { label: 'Лицензировано', value: '5' }, + { label: 'Нет переводчика', value: '3' }, + { label: 'Продолжается', value: '1' }, + ], + inputType: FilterInputs.Checkbox, + }, + { + key: 'age_limit', + label: 'Возрастной рейтинг:', + values: [ + { label: '16+', value: '1' }, + { label: '18+', value: '2' }, + { label: 'Для всех', value: '0' }, + ], + inputType: FilterInputs.Checkbox, + }, ]; const RenovelsScraper = { diff --git a/src/sources/sourceManager.ts b/src/sources/sourceManager.ts index e4e8e0ec9..65bfa091d 100644 --- a/src/sources/sourceManager.ts +++ b/src/sources/sourceManager.ts @@ -159,6 +159,8 @@ import { KodScraper, } from './multisrc/noveltl/NovelTlGenerator'; import NovelsOnlineScraper from './en/NovelOnline'; +import SmakolykyTlScraper from './ua/smakolykytl'; +import LitSpaceScraper from './ru/freedlit'; interface PopularNovelsResponse { novels: SourceNovelItem[]; @@ -332,6 +334,8 @@ export const sourceManager = (sourceId: number): Scraper => { 164: BookRiverScraper, // @ts-ignore 165: LinovelibScraper, // @ts-ignore 166: NOVAScraper, // @ts-ignore + 167: SmakolykyTlScraper, // @ts-ignore + 168: LitSpaceScraper, // @ts-ignore }; return scrapers[sourceId]; diff --git a/src/sources/sources.json b/src/sources/sources.json index 671f44ad2..240ab5428 100644 --- a/src/sources/sources.json +++ b/src/sources/sources.json @@ -1013,5 +1013,19 @@ "url": "https://novelasligeras.net", "lang": "Spanish", "icon": "https://pbs.twimg.com/profile_images/1659040611507351552/DeLBdFep_400x400.jpg" + }, + { + "sourceId": 167, + "sourceName": "Смаколики", + "url": "https://smakolykytl.site/", + "lang": "Ukraine", + "icon": "https://smakolykytl.site/img/favicon/apple-touch-icon.webp" + }, + { + "sourceId": 168, + "sourceName": "LitSpace", + "url": "https://freedlit.space/", + "lang": "Russian", + "icon": "https://freedlit.space/images/fav/apple-touch-icon.png" } ] diff --git a/src/sources/ua/smakolykytl.ts b/src/sources/ua/smakolykytl.ts new file mode 100644 index 000000000..8c72be92d --- /dev/null +++ b/src/sources/ua/smakolykytl.ts @@ -0,0 +1,259 @@ +import { Status } from '../helpers/constants'; +import dayjs from 'dayjs'; +import { SourceChapter, SourceNovel, SourceNovelItem } from '../types'; + +const sourceId = 167; +const sourceName = 'Смаколики'; +const baseUrl = 'https://smakolykytl.site/'; + +const popularNovels = async (page: number, { showLatestNovels }) => { + const url = showLatestNovels + ? 'https://api.smakolykytl.site/api/user/updates' + : 'https://api.smakolykytl.site/api/user/projects'; + + const result = await fetch(url); + const json = (await result.json()) as response; + + const novels: SourceNovelItem[] = []; + + (json?.projects || json?.updates)?.forEach(novel => + novels.push({ + sourceId, + novelName: novel.title, + novelCover: novel.image.url, + novelUrl: baseUrl + 'titles/' + novel.id, + }), + ); + + return { novels }; +}; + +const parseNovelAndChapters = async (novelUrl: string) => { + const id = novelUrl.split('/').pop(); + const result = await fetch( + 'https://api.smakolykytl.site/api/user/projects/' + id, + ); + const book = (await result.json()) as response; + + const novel: SourceNovel = { + sourceId, + sourceName, + novelUrl, + url: novelUrl, + novelName: book?.project?.title, + novelCover: book?.project?.image?.url, + summary: book?.project?.description, + chapters: [], + author: book?.project?.author, + status: book?.project?.status_translate.includes('Триває') + ? Status.ONGOING + : Status.COMPLETED, + }; + let genre = [book?.project?.genres, book?.project?.tags] + .flat() + .map(tags => tags?.title) + .filter(tags => tags); + + if (genre.length > 0) { + novel.genre = genre.join(', '); + } + + const res = await fetch( + 'https://api.smakolykytl.site/api/user/projects/' + id + '/books', + ); + const data = (await res.json()) as response; + + data?.books?.forEach(volume => + volume?.chapters?.map(chapter => + novel.chapters.push({ + chapterName: volume.title + ' ' + chapter.title, + releaseDate: dayjs(chapter.modifiedAt).format('LLL'), + chapterUrl: baseUrl + 'read/' + chapter.id, + }), + ), + ); + + return novel; +}; + +const parseChapter = async (novelUrl: string, chapterUrl: string) => { + const id = chapterUrl.split('/').pop(); + + const result = await fetch( + 'https://api.smakolykytl.site/api/user/chapters/' + id, + ); + const json = (await result.json()) as response; + const chapterRaw: HTML[] = JSON.parse(json?.chapter?.content || '[]'); + + const chapter: SourceChapter = { + sourceId, + novelUrl, + chapterUrl, + chapterName: json?.chapter?.title, + chapterText: jsonToHtml(chapterRaw), + }; + + return chapter; +}; + +const searchNovels = async (searchTerm: string) => { + const result = await fetch('https://api.smakolykytl.site/api/user/projects'); + const json = (await result.json()) as response; + const novels: SourceNovelItem[] = []; + + json?.projects + ?.filter( + novel => + novel.title.includes(searchTerm) || String(novel.id) === searchTerm, + ) + ?.forEach(novel => + novels.push({ + sourceId, + novelName: novel.title, + novelCover: novel.image.url, + novelUrl: baseUrl + 'titles/' + novel.id, + }), + ); + return novels; +}; + +function jsonToHtml(json: HTML[], html: string = '') { + json.forEach(element => { + switch (element.type) { + case 'hardBreak': + html += '
'; + break; + case 'horizontalRule': + html += '
'; + break; + case 'image': + if (element.attrs) { + const attrs = Object.entries(element.attrs) + .filter(attr => attr?.[1]) + .map(attr => `${attr[0]}="${attr[1]}"`); + html += ''; + } + break; + case 'paragraph': + html += + '

' + + (element.content ? jsonToHtml(element.content) : '
') + + '

'; + break; + case 'text': + html += element.text; + break; + default: + html += JSON.stringify(element, null, '\t'); //maybe I missed something. + break; + } + }); + return html; +} + +interface response { + projects?: TopLevelProject[]; + updates?: Update[]; + project?: TopLevelProject; + books?: BookElement[]; + chapter?: TopLevelChapter; +} + +interface BookElement { + id: number; + rank: number; + title: string; + chapters: PurpleChapter[]; +} + +interface PurpleChapter { + id: number; + title: string; + rank: string; + modifiedAt: Date; +} + +interface TopLevelChapter { + id: number; + title: string; + rank: string; + content: string; + modifiedAt: Date; + book: ChapterBook; +} + +interface ChapterBook { + id: number; + rank: number; + title: string; + chapters: FluffyChapter[]; + project: BookProject; +} + +interface FluffyChapter { + id: number; + rank: string; +} + +interface BookProject { + id: number; +} + +interface TopLevelProject { + id: number; + title: string; + description: string; + author: string; + translator: string; + modifiedAt: Date; + alternatives: string; + release: string; + nation: string; + status: string; + status_translate: string; + image: Image; + tags?: Genre[]; + genres?: Genre[]; +} + +interface Genre { + id: number; + title: string; +} + +interface Image { + id: number; + url: string; + name: string; +} + +interface Update { + id: number; + title: string; + author: string; + translator: string; + modifiedAt: Date; + image: Image; +} + +interface HTML { + type: string; + content?: HTML[]; + attrs?: Attrs; + text?: string; +} + +interface Attrs { + src: string; + alt: string | null; + title: string | null; +} + +const SmakolykyTlScraper = { + popularNovels, + parseNovelAndChapters, + parseChapter, + searchNovels, +}; + +export default SmakolykyTlScraper;