Files
lnreader-plugins/plugins/japanese/Syosetu.ts
T
nyagami 65ae9ba112 Fix: non-english broken plugins (#701)
* fix epiknovel

* fix: hasutl

* fix: Novelas Ligera

* fix: ReinoWuxia

* fix: Oasis

* fix: json script

* fix: Skynovels

* fix: Yuuki Tls

* fix: Agitoon

* fix: Syosetu

* fix: IndoWebNovel

* fix: Novel Ringan

* fix: Sakura Novel

* fix: Hako

* minor: type checking when PRing
2024-01-20 21:20:28 +07:00

208 lines
8.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { load as loadCheerio } from "cheerio";
import { fetchFile } from "@libs/fetch";
import { Plugin } from "@typings/plugin";
import { defaultCover } from "@libs/defaultCover";
import { Filters } from "@libs/filterInputs";
// const novelStatus = require('@libs/novelStatus');
// const isUrlAbsolute = require('@libs/isAbsoluteUrl');
// const parseDate = require('@libs/parseDate');
const pluginId = "yomou.syosetu";
export const id = pluginId;
export const name = "Syosetu";
export const icon = "src/jp/syosetu/icon.png";
export const version = "1.0.0";
export const site = "https://yomou.syosetu.com/";
class Syosetu implements Plugin.PluginBase {
id = "yomou.syosetu";
name = "Syosetu";
icon = "src/jp/syosetu/icon.png";
site = "https://yomou.syosetu.com/";
filters?: Filters | undefined;
version = "1.0.0";
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}
searchUrl = (pagenum?: number, order?: string) => {
return `https://yomou.syosetu.com/search.php?order=${order || "hyoka"}${
pagenum !== undefined
? `&p=${pagenum <= 1 || pagenum > 100 ? "1" : pagenum}` // check if pagenum is between 1 and 100
: "" // if isn't don't set ?p
}`;
};
async popularNovels(pageNo: number, options: Plugin.PopularNovelsOptions<Filters>): Promise<Plugin.NovelItem[]> {
const getNovelsFromPage = async (pagenumber: number) => {
// load page
const result = await fetch(this.searchUrl(pagenumber), {headers: this.headers});
const body = await result.text();
// Cheerio it!
const cheerioQuery = loadCheerio(body, { decodeEntities: false });
let pageNovels: Plugin.NovelItem[] = [];
// find class=searchkekka_box
cheerioQuery(".searchkekka_box").each(function (i, e) {
// get div with link and name
const novelDIV = cheerioQuery(this).find(".novel_h");
// get link element
const novelA = novelDIV.children()[0];
// add new novel to array
pageNovels.push({
name: novelDIV.text(), // get the name
url: novelA.attribs.href, // get last part of the link
cover: defaultCover,
});
});
// return all novels from this page
return pageNovels;
}
const novels = await getNovelsFromPage(pageNo);
return novels;
}
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
let chapters: Plugin.ChapterItem[] = [];
const result = await fetch(novelUrl, {headers: this.headers});
const body = await result.text();
const loadedCheerio = loadCheerio(body, { decodeEntities: false });
// create novel object
let novel: Plugin.SourceNovel = {
url: novelUrl,
name: loadedCheerio(".novel_title").text(),
author: loadedCheerio(".novel_writername")
.text()
.replace("作者:", ""),
cover: defaultCover,
};
// Get all the chapters
const cqGetChapters = loadedCheerio(".novel_sublist2");
if (cqGetChapters.length !== 0) {
// has more than 1 chapter
novel.summary = loadedCheerio("#novel_ex")
.text()
.replace(/<\s*br.*?>/g, "\n");
cqGetChapters.each(function (i, e) {
const chapterA = loadedCheerio(this).find("a");
const [chapterName, releaseDate, chapterUrl] = [
// set the variables
chapterA.text(),
loadedCheerio(this)
.find("dt") // get title
.text() // get text
.replace(/(.)/g, "") // remove "(edited)" mark
.trim(), // trim spaces
"https://ncode.syosetu.com" + chapterA.attr("href"),
];
chapters.push({
name: chapterName,
releaseTime: releaseDate,
url: chapterUrl,
});
});
} else {
/**
* Because there are oneshots on the site, they have to be treated with special care
* that's what pisses me off in Shosetsu app. They have this extension,
* but every oneshot is set as "there are no chapters" and all contents are thrown into the description!!
*/
// get summary for oneshot chapter
const nameResult = await fetch(
this.searchUrl() + `&word=${novel.name}`,
{headers: this.headers}
);
const nameBody = await nameResult.text();
const summaryQuery = loadCheerio(nameBody, {
decodeEntities: false,
});
const foundText = summaryQuery(".searchkekka_box")
.first()
.find(".ex")
.text()
.replace(/\s{2,}/g, "\n");
novel.summary = foundText;
// add single chapter
chapters.push({
name: "Oneshot",
releaseTime: loadedCheerio("head")
.find("meta[name='WWWC']")
.attr("content"), // get date from metadata
url: novelUrl, // set chapterUrl to oneshot so that chapterScraper knows it's a one-shot,
});
}
novel.chapters = chapters;
return novel;
}
async parseChapter(chapterUrl: string): Promise<string> {
const result = await fetch(chapterUrl, {headers: this.headers});
const body = await result.text();
// create cheerioQuery
const cheerioQuery = loadCheerio(body, {
decodeEntities: false,
});
let chapterText = cheerioQuery("#novel_honbun") // get chapter text
.html() || '';
return chapterText;
}
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]> {
let novels = [];
// returns list of novels from given page
const getNovelsFromPage = async (pagenumber: number) => {
// load page
const result = await fetch(
this.searchUrl(pagenumber) + `&word=${searchTerm}`,
{headers: this.headers}
);
const body = await result.text();
// Cheerio it!
const cheerioQuery = loadCheerio(body, { decodeEntities: false });
let pageNovels: Plugin.NovelItem[] = [];
// find class=searchkekka_box
cheerioQuery(".searchkekka_box").each(function (i, e) {
// get div with link and name
const novelDIV = cheerioQuery(this).find(".novel_h");
// get link element
const novelA = novelDIV.children()[0];
// add new novel to array
pageNovels.push({
name: novelDIV.text(), // get the name
url: novelA.attribs.href, // get last part of the link
cover: defaultCover,
});
});
// return all novels from this page
return pageNovels;
}
// counter of loaded pages
// let pagesLoaded = 0;
// do {
// // always load first one
// novels.push(...(await getNovelsFromPage(pagesLoaded + 1)));
// pagesLoaded++;
// } while (pagesLoaded < maxPageLoad && isNext); // check if we should load more
novels = await getNovelsFromPage(1);
/** Use
* novels.push(...(await getNovelsFromPage(pageNumber)))
* if you want to load more
*/
// respond with novels!
return novels;
}
async fetchImage(url: string): Promise<string | undefined> {
return fetchFile(url);
}
}
export default new Syosetu();