Full TS for Plugins (#538)
* remove lang, descrription in plugin script * remove languages import * Update README.md * fix: missing lang * clean: remove description * feat(Add Sources) (#15) * add many sources * add showToast * feat(Add Source): (#16) * add source * remove descriptions * Early Novel & Fast Novel (#1) * some more plugin * plugin * try something new * yes * try yes * genres is plural * fastnovel full fix * testings * early check * earlynovel fixed * showToast check * earlynovel done * fastnovel fixe * again * showToasts again * fix for fastnovel * done * for pr * Update freenovelupdates.js * MTL Reader and MTL Novel (#2) * first * test * wops * mtlreaders * ok * for PR * Nitro Scans and Novel Hold (#3) * start feature * more feature * something * eslint * yes * maybe * wa * asdasd * amazing * change it up * for pr * WPmangastream and Novelfull (#4) (#17) * source generation * fix json parse error * update plguins * delete fetchApi, nobleMTL (.js) * delete showToast.js * Typescript Support (#18) * Initial TS Config * Initial TS Config * Fix most errors * Fix test webserver * Fix webserver isPlugin not working * Fix typos * Add check if plugins have all the fields * Move exports.protected to the bottom of the plugin * Change username, reponame and branchname * Fix no exports error * Fix type * Fix typo in tsc_fix * Remove const from exports to not re-define consts * Readd const becuause exports may not exist. * Fix plugins to work in-app * Fix NobleMTL * Remove fix_ts.ts * Change template.ts * delete cheerio_space * move api into ./testweb * remove js dir * remove js dir (again) * remove ./js (again) * init multisrc generator (test) * remove module.exports in libs * madara generator * update template * rename generator, TS for hako * fix icon url for madara * fix icon for madara (again) * update Readme, nodemon * export default for all libs * fix madara novelurl * update: pluginId, name, filename for madara * feat: check same id error * update: precommit, fix: require.default * Revert "update: precommit, fix: require.default" This reverts commit c642c5c72241f13eccfe417210c22272c4739ffa. * Port all plugins to ts (#19) - fix cheerio - remove export default - fix spelling * update template * add multisrc noveltl, update generator param, fix web search * fix undefined id for Snailulitka * add rulate multisrc, fix max length textarea * add readwn multisrc * delete: erolate and rulate are in multisrc * Change broken extension and show error on commit for those files (#20) * Change to .broken.ts and show error on commit for those files * remove novelStatus * fix: wrong id in github action * remove mangastream multisrc * update icon for readwn multisrc --------- Co-authored-by: K1ngfish3r <trulyihu@gmail.com> Co-authored-by: Marcin Majewski <skilletssgames@gmail.com>
This commit is contained in:
@@ -1,205 +0,0 @@
|
||||
const cheerio = require('cheerio');
|
||||
const languages = require('@libs/languages');
|
||||
const fetchApi = require('@libs/fetchApi');
|
||||
const fetchFile = require('@libs/fetchFile');
|
||||
// const novelStatus = require('@libs/novelStatus');
|
||||
// const isUrlAbsolute = require('@libs/isAbsoluteUrl');
|
||||
// const parseDate = require('@libs/parseDate');
|
||||
|
||||
const pluginId = 'yomou.syosetu';
|
||||
|
||||
const defaultCoverUri =
|
||||
'https://github.com/LNReader/lnreader-sources/blob/main/icons/src/coverNotAvailable.jpg?raw=true';
|
||||
|
||||
const searchUrl = (pagenum, order) => {
|
||||
return `https://yomou.syosetu.com/search.php?order=${order || 'hyoka'}${!isNaN((pagenum = parseInt(pagenum, 10))) // check if pagenum is a number
|
||||
? `&p=${pagenum <= 1 || pagenum > 100 ? '1' : pagenum}` // check if pagenum is between 1 and 100
|
||||
: '' // if isn't don't set ?p
|
||||
}`;
|
||||
};
|
||||
|
||||
const novelCover = defaultCoverUri;
|
||||
|
||||
async function popularNovels(pageNo) {
|
||||
let novels = [];
|
||||
|
||||
async function getNovelsFromPage(pagenumber) {
|
||||
// load page
|
||||
const result = await fetchApi(searchUrl(pagenumber || null));
|
||||
const body = await result.text();
|
||||
// Cheerio it!
|
||||
const cheerioQuery = cheerio.load(body, { decodeEntities: false });
|
||||
let pageNovels = [];
|
||||
// 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: novelCover, // TODO: IDK what to do about covers... On Syo they don't have them
|
||||
});
|
||||
});
|
||||
// return all novels from this page
|
||||
return pageNovels;
|
||||
};
|
||||
|
||||
novels = await getNovelsFromPage(pageNo);
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
async function parseNovelAndChapters(novelUrl) {
|
||||
let chapters = [];
|
||||
const result = await fetchApi(novelUrl);
|
||||
const body = await result.text();
|
||||
const cheerioQuery = cheerio.load(body, { decodeEntities: false });
|
||||
|
||||
// create novel object
|
||||
let novel = {
|
||||
url: novelUrl,
|
||||
name: cheerioQuery('.novel_title').text(),
|
||||
author: cheerioQuery('.novel_writername').text().replace('作者:', ''),
|
||||
cover: novelCover,
|
||||
};
|
||||
|
||||
// Get all the chapters
|
||||
const cqGetChapters = cheerioQuery('.novel_sublist2');
|
||||
if (cqGetChapters.length !== 0) {
|
||||
// has more than 1 chapter
|
||||
novel.summary = cheerioQuery('#novel_ex')
|
||||
.text()
|
||||
.replace(/<\s*br.*?>/g, '\n');
|
||||
cqGetChapters.each(function (i, e) {
|
||||
const chapterA = cheerioQuery(this).find('a');
|
||||
const [chapterName, releaseDate, chapterUrl] = [
|
||||
// set the variables
|
||||
chapterA.text(),
|
||||
cheerioQuery(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 fetchApi(searchUrl() + `&word=${novel.name}`);
|
||||
const nameBody = await nameResult.text();
|
||||
const summaryQuery = cheerio.load(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: cheerioQuery('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 function parseChapter(chapterUrl) {
|
||||
const result = await fetchApi(chapterUrl);
|
||||
const body = await result.text();
|
||||
|
||||
// create cheerioQuery
|
||||
const cheerioQuery = cheerio.load(body, {
|
||||
decodeEntities: false,
|
||||
});
|
||||
|
||||
let chapterText = cheerioQuery('#novel_honbun') // get chapter text
|
||||
.html();
|
||||
return chapterText;
|
||||
};
|
||||
|
||||
|
||||
async function searchNovels(searchTerm, pageNo) {
|
||||
let novels = [];
|
||||
|
||||
// returns list of novels from given page
|
||||
async function getNovelsFromPage (pagenumber) {
|
||||
// load page
|
||||
const result = await fetchApi(
|
||||
searchUrl(pagenumber || null, null) + `&word=${searchTerm}`
|
||||
);
|
||||
const body = await result.text();
|
||||
// Cheerio it!
|
||||
const cheerioQuery = cheerio.load(body, { decodeEntities: false });
|
||||
|
||||
let pageNovels = [];
|
||||
// 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: novelCover, // TODO: IDK what to do about covers... On Syo they don't have them
|
||||
});
|
||||
});
|
||||
// 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 function fetchImage(url) {
|
||||
|
||||
return await fetchFile(url);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
id: pluginId,
|
||||
name: 'Syosetu',
|
||||
icon: 'src/jp/syosetu/icon.png',
|
||||
version: '1.0.0',
|
||||
site: 'https://yomou.syosetu.com/',
|
||||
lang: languages.Japanese,
|
||||
description: '小説を読もう!は「小説家になろう」に投稿された',
|
||||
protected: false,
|
||||
fetchImage,
|
||||
popularNovels,
|
||||
parseNovelAndChapters,
|
||||
parseChapter,
|
||||
searchNovels,
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
import { load as loadCheerio } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Chapter, Novel, Plugin } from "@typings/plugin";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
// 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/";
|
||||
exports.protected = false;
|
||||
|
||||
const 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
|
||||
}`;
|
||||
};
|
||||
|
||||
export const popularNovels: Plugin.popularNovels = async function (pageNo) {
|
||||
async function getNovelsFromPage(pagenumber: number) {
|
||||
// load page
|
||||
const result = await fetchApi(searchUrl(pagenumber));
|
||||
const body = await result.text();
|
||||
// Cheerio it!
|
||||
const cheerioQuery = loadCheerio(body, { decodeEntities: false });
|
||||
let pageNovels: Novel.Item[] = [];
|
||||
// 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;
|
||||
};
|
||||
|
||||
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
|
||||
async function (novelUrl) {
|
||||
let chapters: Chapter.Item[] = [];
|
||||
const result = await fetchApi(novelUrl);
|
||||
const body = await result.text();
|
||||
const loadedCheerio = loadCheerio(body, { decodeEntities: false });
|
||||
|
||||
// create novel object
|
||||
let novel: Novel.instance = {
|
||||
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 fetchApi(
|
||||
searchUrl() + `&word=${novel.name}`
|
||||
);
|
||||
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;
|
||||
};
|
||||
|
||||
export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
|
||||
const result = await fetchApi(chapterUrl);
|
||||
const body = await result.text();
|
||||
|
||||
// create cheerioQuery
|
||||
const cheerioQuery = loadCheerio(body, {
|
||||
decodeEntities: false,
|
||||
});
|
||||
|
||||
let chapterText = cheerioQuery("#novel_honbun") // get chapter text
|
||||
.html();
|
||||
return chapterText;
|
||||
};
|
||||
|
||||
export const searchNovels: Plugin.searchNovels = async function (searchTerm) {
|
||||
let novels = [];
|
||||
|
||||
// returns list of novels from given page
|
||||
async function getNovelsFromPage(pagenumber: number) {
|
||||
// load page
|
||||
const result = await fetchApi(
|
||||
searchUrl(pagenumber) + `&word=${searchTerm}`
|
||||
);
|
||||
const body = await result.text();
|
||||
// Cheerio it!
|
||||
const cheerioQuery = loadCheerio(body, { decodeEntities: false });
|
||||
|
||||
let pageNovels: Novel.Item[] = [];
|
||||
// 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;
|
||||
};
|
||||
|
||||
export const fetchImage: Plugin.fetchImage = async function (url) {
|
||||
return await fetchFile(url);
|
||||
};
|
||||
Reference in New Issue
Block a user