diff --git a/src/sources/ru/ranobelib.js b/src/sources/ru/ranobelib.js new file mode 100644 index 00000000..71d71857 --- /dev/null +++ b/src/sources/ru/ranobelib.js @@ -0,0 +1,152 @@ +import cheerio from 'react-native-cheerio'; + +const sourceId = 93; +const sourceName = 'RanobeLib'; + +const baseUrl = 'https://ranobelib.me/'; + +const popularNovels = async page => { + const totalPages = 29; + const url = `${baseUrl}manga-list?sort=rate&dir=desc&page=${page}`; + + const result = await fetch(url); + const body = await result.text(); + + const loadedCheerio = cheerio.load(body); + + let novels = []; + + loadedCheerio('.media-card-wrap').each(function () { + const novelName = loadedCheerio(this).find('.media-card__title').text(); + const novelCover = loadedCheerio(this) + .find('a.media-card') + .attr('data-src'); + const novelUrl = loadedCheerio(this).find('a.media-card').attr('href'); + + const novel = {sourceId, novelName, novelCover, novelUrl}; + + novels.push(novel); + }); + + return {totalPages, novels}; +}; + +const parseNovelAndChapters = async novelUrl => { + const result = await fetch(novelUrl); + const body = await result.text(); + + const loadedCheerio = cheerio.load(body); + + let novel = { + sourceId, + sourceName, + url: novelUrl, + novelUrl, + novelName: '', + novelCover: '', + author: null, + status: null, + genre: null, + summary: null, + chapters: [], + }; + + novel.novelName = loadedCheerio('.media-name__main').text().trim(); + + novel.novelCover = loadedCheerio('.media-sidebar__cover img').attr('src'); + + novel.summary = loadedCheerio('.media-description__text').text().trim(); + + novel.author = loadedCheerio( + '#main-page > div > div.container.container_responsive > div > div.media-sidebar > div.media-info-list.paper > a:nth-child(4) > div.media-info-list__value', + ) + .text() + .trim(); + + let chapters = []; + + let chaptersJson = body.match( + /window.__DATA__ = [\s\S]*?window._SITE_COLOR_/gm, + ); + + chaptersJson = chaptersJson?.[0] + ?.replace('window.__DATA__ = ', '') + ?.replace('window._SITE_COLOR_', '') + ?.trim() + ?.slice(0, -1); + + chaptersJson = JSON.parse(chaptersJson); + + const novelSlug = chaptersJson.manga.slug; + + chaptersJson.chapters.list.map(item => { + const chapterName = + `Том ${item.chapter_volume} Глава ${item.chapter_number} ${item.chapter_name}`?.trim(); + const releaseDate = item.chapter_created_at; + const chapterUrl = `${baseUrl}${novelSlug}/v${item.chapter_volume}/c${item.chapter_number}`; + + 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); + + const chapterName = loadedCheerio( + 'body > div.reader.reader_text > div.header.header_reader.headroom.headroom--top.headroom--not-bottom > div > a > div:nth-child(3)', + ) + .text() + .trim(); + const chapterText = loadedCheerio('.reader-container').html(); + + const chapter = { + sourceId, + novelUrl, + chapterUrl, + chapterName, + chapterText, + }; + + return chapter; +}; + +const searchNovels = async searchTerm => { + const url = `${baseUrl}manga-list?sort=rate&dir=desc&page=1&name=${searchTerm}`; + + const result = await fetch(url); + const body = await result.text(); + + let loadedCheerio = cheerio.load(body); + + let novels = []; + + loadedCheerio('.media-card-wrap').each(function () { + const novelName = loadedCheerio(this).find('.media-card__title').text(); + const novelCover = loadedCheerio(this) + .find('a.media-card') + .attr('data-src'); + const novelUrl = loadedCheerio(this).find('a.media-card').attr('href'); + + const novel = {sourceId, novelName, novelCover, novelUrl}; + + novels.push(novel); + }); + + return novels; +}; + +const RanobeLibScraper = { + popularNovels, + parseNovelAndChapters, + parseChapter, + searchNovels, +}; + +export default RanobeLibScraper; diff --git a/src/sources/sources.js b/src/sources/sources.js index 5bf8f010..ff440ce5 100644 --- a/src/sources/sources.js +++ b/src/sources/sources.js @@ -91,6 +91,7 @@ import RewayatArScraper from './ar/rewayatar'; import MTLCornerScraper from './en/mtlcorner'; import NitroScansScraper from './en/nitroscans'; import NovelForestScraper from './en/novelforest'; +import RanobeLibScraper from './ru/ranobelib'; export const getSource = sourceId => { const scrapers = { @@ -185,6 +186,7 @@ export const getSource = sourceId => { 90: NitroScansScraper, 91: OnlyMTLScraper, 92: NovelForestScraper, + 93: RanobeLibScraper, }; return scrapers[sourceId]; diff --git a/src/sources/sources.json b/src/sources/sources.json index 024a507e..cb190b56 100644 --- a/src/sources/sources.json +++ b/src/sources/sources.json @@ -635,5 +635,12 @@ "sourceName": "NovelForest", "icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/novelforest/icon.png?raw=true", "lang": "English" + }, + { + "sourceId": 93, + "url": "https://ranobelib.me/", + "sourceName": "RanobeLib", + "icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/ru/ranobelib/icon.png?raw=true", + "lang": "Russian" } ]