diff --git a/README.md b/README.md index b05e604..5189ed7 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,14 @@ Sources for [LNReader](https://github.com/rajarsheechatterjee/lnreader) | https://www.royalroad.com/ | :heavy_check_mark: | | https://www.scribblehub.com/ | :heavy_check_mark: | | https://lnmtl.com/ | :heavy_check_mark: | +| https://skynovel.org/ | :heavy_check_mark: | +| https://novelcake.com/ | :heavy_check_mark: | +| https://novelsrock.com/ | :heavy_check_mark: | +| https://zinnovel.com/ | :heavy_check_mark: | +| https://noveltranslate.com/ | :heavy_check_mark: | +| https://www.lunarletters.com/ | :heavy_check_mark: | +| https://sleepytranslations.com/ | :heavy_check_mark: | +| https://freenovel.me/ | :heavy_check_mark: | | https://syosetu.com | :heavy_check_mark: | | https://tunovelaligera.com/ | :heavy_check_mark: | | https://www.skynovels.net/ | :heavy_check_mark: | diff --git a/index.js b/index.js index 0c34833..919df9d 100755 --- a/index.js +++ b/index.js @@ -60,6 +60,9 @@ app.use("/api/31/", require("./src/es/novelawuxia/NovelaWuxia")); // Japanese app.use("/api/36", require("./src/jp/syosetu/Syosetu")); +// MultiSrc +app.use("/api/", require("./multisrc/madara/MadaraGenerator")); + const PORT = process.env.PORT || 5000; app.listen(PORT, () => console.log(`Server started on port ${PORT}`)); diff --git a/multisrc/madara/MadaraGenerator.js b/multisrc/madara/MadaraGenerator.js new file mode 100644 index 0000000..c45f754 --- /dev/null +++ b/multisrc/madara/MadaraGenerator.js @@ -0,0 +1,112 @@ +const express = require("express"); +const MadaraScraper = require("./MadaraScraper"); + +const router = express.Router(); + +const getPath = (extensionId) => { + const path = { + 38: { novels: "manga", novel: "manga", chapter: "manga" }, + 39: { novels: "series", novel: "series", chapter: "series" }, + 40: { novels: "novels", novel: "novel", chapter: "novel" }, + 41: { novels: "manga", novel: "manga", chapter: "manga" }, + 42: { novels: "all-novels", novel: "manga", chapter: "manga" }, + 43: { novels: "series", novel: "series", chapter: "series" }, + 44: { novels: "series", novel: "series", chapter: "series" }, + 45: { novels: "novel", novel: "novel", chapter: "novel" }, + }; + + return path[extensionId]; +}; + +const madaraSources = [ + { + extensionId: 38, + scraper: new MadaraScraper( + 38, + "https://skynovel.org/", + "SkyNovel", + getPath(38) + ), + }, + { + extensionId: 39, + scraper: new MadaraScraper( + 39, + "https://novelcake.com/", + "NovelCake", + getPath(39) + ), + }, + { + extensionId: 40, + scraper: new MadaraScraper( + 40, + "https://novelsrock.com/", + "NovelsRock", + getPath(40) + ), + }, + { + extensionId: 41, + scraper: new MadaraScraper( + 41, + "https://zinnovel.com/", + "ZinnNovel", + getPath(41) + ), + }, + { + extensionId: 42, + scraper: new MadaraScraper( + 42, + "https://noveltranslate.com/", + "NovelTranslate", + getPath(42) + ), + }, + { + extensionId: 43, + scraper: new MadaraScraper( + 43, + "https://www.lunarletters.com/", + "LunarLetters", + getPath(43) + ), + }, + { + extensionId: 44, + scraper: new MadaraScraper( + 44, + "https://sleepytranslations.com/", + "SleepyTranslations", + getPath(44) + ), + }, + { + extensionId: 45, + scraper: new MadaraScraper( + 45, + "https://freenovel.me/", + "FreeNovelMe", + getPath(45) + ), + }, +]; + +madaraSources.map((source) => { + router.get(`/${source.extensionId}/novels/`, source.scraper.novelsScraper); + + router.get( + `/${source.extensionId}/novel/:novelUrl`, + source.scraper.novelScraper + ); + + router.get( + `/${source.extensionId}/novel/:novelUrl/:chapterUrl`, + source.scraper.chapterScraper + ); + + router.get(`/${source.extensionId}/search/`, source.scraper.searchScraper); +}); + +module.exports = router; diff --git a/multisrc/madara/MadaraScraper.js b/multisrc/madara/MadaraScraper.js new file mode 100644 index 0000000..5bf8dac --- /dev/null +++ b/multisrc/madara/MadaraScraper.js @@ -0,0 +1,220 @@ +const cheerio = require("cheerio"); +const fetch = require("node-fetch"); +const FormData = require("form-data"); +const { parseHtml } = require("../../src/helper"); + +class MadaraScraper { + constructor(extensionId, baseUrl, sourceName, path) { + this.extensionId = extensionId; + this.baseUrl = baseUrl; + this.sourceName = sourceName; + this.path = path; + + this.novelsScraper = this.novelsScraper.bind(this); + this.novelScraper = this.novelScraper.bind(this); + this.chapterScraper = this.chapterScraper.bind(this); + this.searchScraper = this.searchScraper.bind(this); + } + + async novelsScraper(req, res) { + let url = this.baseUrl + this.path.novels + "/?m_orderby=rating"; + let extensionId = this.extensionId; + + const result = await fetch(url); + const body = await result.text(); + + const $ = cheerio.load(body); + + let novels = []; + + $(".manga-title-badges").remove(); + + $(".page-item-detail").each(function (result) { + const novelName = $(this).find(".post-title").text().trim(); + let image = $(this).find("img"); + const novelCover = image.attr("data-src") || image.attr("src"); + + let novelUrl = $(this) + .find(".post-title") + .find("a") + .attr("href") + .split("/")[4]; + + const novel = { + extensionId, + novelName, + novelCover, + novelUrl, + }; + + novels.push(novel); + }); + + res.json(novels); + } + + async novelScraper(req, res) { + const novelUrl = req.params.novelUrl; + const url = `${this.baseUrl}${this.path.novel}/${novelUrl}/`; + + const result = await fetch(url); + const body = await result.text(); + + let $ = cheerio.load(body); + + let novel = {}; + + novel.extensionId = this.extensionId; + + novel.sourceName = this.sourceName; + + novel.sourceUrl = url; + + novel.novelUrl = novelUrl; + + $(".manga-title-badges").remove(); + + novel.novelName = $(".post-title > h1").text().trim(); + + novel.novelCover = + $(".summary_image > a > img").attr("data-src") || + $(".summary_image > a > img").attr("src"); + + $(".post-content_item").each(function (result) { + const detailName = $(this) + .find(".summary-heading > h5") + .text() + .trim(); + const detail = $(this).find(".summary-content").text().trim(); + + switch (detailName) { + case "Genre(s)": + novel["Genre(s)"] = detail.replace(/[\t\n]/g, ","); + break; + case "Author(s)": + novel["Author(s)"] = detail; + break; + case "Status": + novel.Status = detail; + break; + } + }); + + novel.novelSummary = $("div.summary__content").text().trim(); + + let novelChapters = []; + + const novelId = + $(".rating-post-id").attr("value") || + $("#manga-chapters-holder").attr("data-id"); + + let formData = new FormData(); + formData.append("action", "manga_get_chapters"); + formData.append("manga", novelId); + + const data = await fetch(this.baseUrl + "wp-admin/admin-ajax.php", { + method: "POST", + body: formData, + }); + const text = await data.text(); + + $ = cheerio.load(text); + + $(".wp-manga-chapter").each(function (result) { + const chapterName = $(this) + .find("a") + .text() + .replace(/[\t\n]/g, "") + .trim(); + + const releaseDate = $(this) + .find("span") + .text() + .replace("Free", "") + .trim(); + + const chapterUrl = $(this).find("a").attr("href").split("/")[5]; + + novelChapters.push({ chapterName, releaseDate, chapterUrl }); + }); + + novel.novelChapters = novelChapters.reverse(); + + res.json(novel); + } + + async chapterScraper(req, res) { + let novelUrl = req.params.novelUrl; + let chapterUrl = req.params.chapterUrl; + + let extensionId = this.extensionId; + + const url = `${this.baseUrl}${this.path.chapter}/${novelUrl}/${chapterUrl}`; + + const result = await fetch(url); + const body = await result.text(); + + const $ = cheerio.load(body); + + let chapterName = + $(".text-center").text() || $("#chapter-heading").text(); + + let chapterText = $(".text-left").html(); + chapterText = parseHtml(chapterText); + + let nextChapter = null; + let prevChapter = null; + + const chapter = { + extensionId, + novelUrl, + chapterUrl, + chapterName, + chapterText, + nextChapter, + prevChapter, + }; + + res.json(chapter); + } + + async searchScraper(req, res) { + const searchTerm = req.query.s; + + const url = `${this.baseUrl}?s=${searchTerm}&post_type=wp-manga`; + + const result = await fetch(url); + const body = await result.text(); + + const $ = cheerio.load(body); + + let novels = []; + let extensionId = this.extensionId; + + $(".c-tabs-item__content").each(function (result) { + const novelName = $(this).find(".post-title").text().trim(); + + let image = $(this).find("img"); + const novelCover = image.attr("data-src") || image.attr("src"); + + let novelUrl = $(this) + .find(".post-title") + .find("a") + .attr("href") + .split("/")[4]; + + const novel = { + extensionId, + novelName, + novelCover, + novelUrl, + }; + + novels.push(novel); + }); + + res.json(novels); + } +} + +module.exports = MadaraScraper; diff --git a/multisrc/madara/icons/freenovel.png b/multisrc/madara/icons/freenovel.png new file mode 100644 index 0000000..d5d1b30 Binary files /dev/null and b/multisrc/madara/icons/freenovel.png differ diff --git a/multisrc/madara/icons/lunarletters.png b/multisrc/madara/icons/lunarletters.png new file mode 100644 index 0000000..cd20625 Binary files /dev/null and b/multisrc/madara/icons/lunarletters.png differ diff --git a/multisrc/madara/icons/novelcake.png b/multisrc/madara/icons/novelcake.png new file mode 100644 index 0000000..2d9805b Binary files /dev/null and b/multisrc/madara/icons/novelcake.png differ diff --git a/multisrc/madara/icons/novelsrock.png b/multisrc/madara/icons/novelsrock.png new file mode 100644 index 0000000..545d3c7 Binary files /dev/null and b/multisrc/madara/icons/novelsrock.png differ diff --git a/multisrc/madara/icons/noveltranslate.png b/multisrc/madara/icons/noveltranslate.png new file mode 100644 index 0000000..3a4e47c Binary files /dev/null and b/multisrc/madara/icons/noveltranslate.png differ diff --git a/multisrc/madara/icons/skynovel.png b/multisrc/madara/icons/skynovel.png new file mode 100644 index 0000000..d0fc289 Binary files /dev/null and b/multisrc/madara/icons/skynovel.png differ diff --git a/multisrc/madara/icons/sleepytranslations.png b/multisrc/madara/icons/sleepytranslations.png new file mode 100644 index 0000000..f112559 Binary files /dev/null and b/multisrc/madara/icons/sleepytranslations.png differ diff --git a/multisrc/madara/icons/zinnnovel.png b/multisrc/madara/icons/zinnnovel.png new file mode 100644 index 0000000..8cc36f7 Binary files /dev/null and b/multisrc/madara/icons/zinnnovel.png differ diff --git a/sources.js b/sources.js index 33e5196..3a7166d 100755 --- a/sources.js +++ b/sources.js @@ -246,6 +246,78 @@ extensions = [ sourceLanguage: "English", status: 1, }, + { + sourceId: 38, + sourceUrl: "https://skynovel.org/", + sourceName: "SkyNovel", + sourceCover: + "https://github.com/rajarsheechatterjee/lnreader-extensions/blob/main/multisrc/madara/icons/skynovel.png?raw=true", + sourceLanguage: "English", + status: 1, + }, + { + sourceId: 39, + sourceUrl: "https://novelcake.com/", + sourceName: "NovelCake", + sourceCover: + "https://github.com/rajarsheechatterjee/lnreader-extensions/blob/main/multisrc/madara/icons/novelcake.png?raw=true", + sourceLanguage: "English", + status: 1, + }, + { + sourceId: 40, + sourceUrl: "https://novelsrock.com/", + sourceName: "NovelsRock", + sourceCover: + "https://github.com/rajarsheechatterjee/lnreader-extensions/blob/main/multisrc/madara/icons/novelsrock.png?raw=true", + sourceLanguage: "English", + status: 1, + }, + { + sourceId: 41, + sourceUrl: "https://zinnovel.com/", + sourceName: "ZinnNovel", + sourceCover: + "https://github.com/rajarsheechatterjee/lnreader-extensions/blob/main/multisrc/madara/icons/zinnovel.png?raw=true", + sourceLanguage: "English", + status: 1, + }, + { + sourceId: 42, + sourceUrl: "https://noveltranslate.com/", + sourceName: "NovelTranslate", + sourceCover: + "https://github.com/rajarsheechatterjee/lnreader-extensions/blob/main/multisrc/madara/icons/noveltranslate.png?raw=true", + sourceLanguage: "English", + status: 1, + }, + { + sourceId: 43, + sourceUrl: "https://www.lunarletters.com/", + sourceName: "LunarLetters", + sourceCover: + "https://github.com/rajarsheechatterjee/lnreader-extensions/blob/main/multisrc/madara/icons/lunarletters.png?raw=true", + sourceLanguage: "English", + status: 1, + }, + { + sourceId: 44, + sourceUrl: "https://sleepytranslations.com/", + sourceName: "SleepyTranslations", + sourceCover: + "https://github.com/rajarsheechatterjee/lnreader-extensions/blob/main/multisrc/madara/icons/sleepytranslations.png?raw=true", + sourceLanguage: "English", + status: 1, + }, + { + sourceId: 45, + sourceUrl: "https://freenovel.me/", + sourceName: "FreeNovelMe", + sourceCover: + "https://github.com/rajarsheechatterjee/lnreader-extensions/blob/main/multisrc/madara/icons/freenovel.png?raw=true", + sourceLanguage: "English", + status: 1, + }, /** * Spanish diff --git a/src/en/lnmtl/LNMTLScraper.js b/src/en/lnmtl/LNMTLScraper.js index 5c18c80..a740af4 100644 --- a/src/en/lnmtl/LNMTLScraper.js +++ b/src/en/lnmtl/LNMTLScraper.js @@ -133,7 +133,7 @@ const chapterScraper = async (req, res) => { $(".original").remove(); - let chapterText = $(".chapter-body ").html(); + let chapterText = $(".chapter-body").html(); chapterText = chapterName + "\n\n" + parseHtml(chapterText); let nextChapter = null;