refactor: Update Read From Net to Use Desktop UA, URL Method, and Inline Tags in parseChapter (#2117)

This commit is contained in:
K1ngfish3r
2026-04-16 23:36:34 +05:00
committed by GitHub
parent eb992eb988
commit e1f3cafe24
+74 -68
View File
@@ -4,14 +4,22 @@ import { Filters } from '@libs/filterInputs';
import { CheerioAPI, load as loadCheerio } from 'cheerio'; import { CheerioAPI, load as loadCheerio } from 'cheerio';
import { defaultCover } from '@libs/defaultCover'; import { defaultCover } from '@libs/defaultCover';
const pluginHeaders = {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36',
};
class ReadFromPlugin implements Plugin.PluginBase { class ReadFromPlugin implements Plugin.PluginBase {
id = 'readfrom'; id = 'readfrom';
name = 'Read From Net'; name = 'Read From Net';
icon = 'src/en/readfrom/icon.png'; icon = 'src/en/readfrom/icon.png';
site = 'https://readfrom.net'; site = 'https://readfrom.net/';
version = '1.0.2'; version = '1.1.0';
filters: Filters | undefined = undefined; filters: Filters | undefined = undefined;
imageRequestInit?: Plugin.ImageRequestInit | undefined = undefined; headers = new Headers(pluginHeaders);
imageRequestInit: Plugin.ImageRequestInit = {
headers: pluginHeaders,
};
//flag indicates whether access to LocalStorage, SesesionStorage is required. //flag indicates whether access to LocalStorage, SesesionStorage is required.
webStorageUtilized?: boolean; webStorageUtilized?: boolean;
@@ -34,6 +42,9 @@ class ReadFromPlugin implements Plugin.PluginBase {
(isSearch ? 'div.text' : '#dle-content') + ' > article.box', (isSearch ? 'div.text' : '#dle-content') + ' > article.box',
) )
.map((i, el) => { .map((i, el) => {
const $el = loadedCheerio(el);
const novelPath = $el.find('h2.title a').attr('href');
if (!novelPath) return;
const summary = loadedCheerio(el).find( const summary = loadedCheerio(el).find(
isSearch ? 'div.text5' : 'div.text3', isSearch ? 'div.text5' : 'div.text3',
)[0]; )[0];
@@ -41,11 +52,7 @@ class ReadFromPlugin implements Plugin.PluginBase {
loadedCheerio(summary).find('a').remove(); loadedCheerio(summary).find('a').remove();
return { return {
name: loadedCheerio(el).find('h2.title').text().trim(), name: loadedCheerio(el).find('h2.title').text().trim(),
path: loadedCheerio(el) path: new URL(novelPath, this.site).pathname.substring(1),
.find('h2.title > a')
.attr('href')!
.replace('https://readfrom.net/', '')
.replace(/^\//, ''),
cover: loadedCheerio(el).find('img').attr('src') || defaultCover, cover: loadedCheerio(el).find('img').attr('src') || defaultCover,
summary: summary:
loadedCheerio(summary).text().trim() + loadedCheerio(summary).text().trim() +
@@ -78,21 +85,20 @@ class ReadFromPlugin implements Plugin.PluginBase {
async popularNovels( async popularNovels(
pageNo: number, pageNo: number,
{ { showLatestNovels }: Plugin.PopularNovelsOptions<typeof this.filters>,
showLatestNovels,
filters,
}: Plugin.PopularNovelsOptions<typeof this.filters>,
) { ) {
const type = showLatestNovels ? 'last_added_books' : 'allbooks'; const type = showLatestNovels ? 'last_added_books' : 'allbooks';
const res = await fetchApi( const res = await fetchApi(this.site + type + '/page/' + pageNo, {
'https://readfrom.net/' + type + '/page/' + pageNo + '/', headers: this.headers,
); });
const text = await res.text(); const text = await res.text();
return this.parseNovels(loadCheerio(text)); return this.parseNovels(loadCheerio(text));
} }
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> { async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
const data = await fetchApi('https://readfrom.net/' + novelPath); const data = await fetchApi(this.site + novelPath, {
headers: this.headers,
});
const text = await data.text(); const text = await data.text();
const loadedCheerio = loadCheerio(text); const loadedCheerio = loadCheerio(text);
@@ -109,25 +115,22 @@ class ReadFromPlugin implements Plugin.PluginBase {
loadedCheerio('article.box > div > center > div > a > img').attr('src') || loadedCheerio('article.box > div > center > div > a > img').attr('src') ||
defaultCover; defaultCover;
novel.chapters = loadedCheerio(loadedCheerio('div.pages').get()[0]) const rawChapters = loadedCheerio('div.pages')
.first()
.find('> a') .find('> a')
.map((i, el) => { .toArray()
return { .flatMap(el => {
name: loadedCheerio(el).text().trim(), const href = loadedCheerio(el).attr('href');
path: loadedCheerio(el) if (!href) return [];
.attr('href')!
.replace('https://readfrom.net/', '') const path = new URL(href, this.site).pathname.substring(1);
.replace(/^\//, ''), return path ? [{ name: loadedCheerio(el).text().trim(), path }] : [];
// releaseTime: '', });
chapterNumber: i + 2,
}; novel.chapters = [
}) { name: '1', path: novelPath, chapterNumber: 1 },
.toArray(); ...rawChapters.map((ch, i) => ({ ...ch, chapterNumber: i + 2 })),
novel.chapters.unshift({ ];
name: '1',
path: novelPath,
chapterNumber: 1,
});
let moreNovelInfo = this.loadedNovelCache.find( let moreNovelInfo = this.loadedNovelCache.find(
novel => novel.path === novelPath, novel => novel.path === novelPath,
@@ -156,43 +159,45 @@ class ReadFromPlugin implements Plugin.PluginBase {
} }
async parseChapter(chapterPath: string): Promise<string> { async parseChapter(chapterPath: string): Promise<string> {
const data = await fetchApi('https://readfrom.net/' + chapterPath); const response = await fetchApi(this.site + chapterPath, {
const text = await data.text(); headers: this.headers,
const loadedCheerio = loadCheerio(text);
loadedCheerio('#textToRead > span:empty').remove();
loadedCheerio('#textToRead > center').remove();
const textToRead = loadedCheerio('#textToRead');
let paragraph: string[] = [];
const chapterHtml: string[] = [];
textToRead.contents().each((_, element) => {
switch (element.type) {
case 'text':
const content = element.data.trim();
if (content) {
paragraph.push(content);
}
break;
case 'tag':
if (paragraph.length > 0) {
chapterHtml.push(`<p>${paragraph.join(' ')}</p>`);
paragraph = [];
}
if (element.tagName !== 'br') {
chapterHtml.push(loadedCheerio.html(element));
}
break;
}
}); });
const $ = loadCheerio(await response.text());
$('#textToRead > span:empty, #textToRead > center').remove();
// Close any remaining paragraph const chapterHtml: string[] = [];
if (paragraph.length > 0) { let p: string[] = [];
chapterHtml.push(`<p>${paragraph.join(' ')}</p>`);
const allowed = new Set(['b', 'i', 'u', 'strong', 'em', 'a']);
const flush = () =>
p.length && (chapterHtml.push(`<p>${p.join(' ').trim()}</p>`), (p = []));
for (const el of $('#textToRead').contents().toArray()) {
switch (el.type) {
case 'comment':
continue;
case 'text':
if (el.data.trim()) {
// Convert _text_ to <i>text</i>
const jbText = el.data.trim().replace(/_([^_]+)_/g, '<i>$1</i>');
p.push(jbText);
}
continue;
case 'tag':
if (allowed.has(el.name)) {
p.push($.html(el));
continue;
}
if (el.name === 'br') {
flush();
continue;
}
}
flush();
chapterHtml.push($.html(el));
} }
flush();
return chapterHtml.join(''); return chapterHtml.join('');
} }
@@ -201,12 +206,13 @@ class ReadFromPlugin implements Plugin.PluginBase {
const res = await fetchApi( const res = await fetchApi(
'https://readfrom.net/build_in_search/?q=' + 'https://readfrom.net/build_in_search/?q=' +
encodeURIComponent(searchTerm), encodeURIComponent(searchTerm),
{ headers: this.headers },
); );
const text = await res.text(); const text = await res.text();
return this.parseNovels(loadCheerio(text), true); return this.parseNovels(loadCheerio(text), true);
} }
resolveUrl = (path: string, isNovel?: boolean) => this.site + '/' + path; // resolveUrl = (path: string, isNovel?: boolean) => this.site + '/' + path;
} }
export default new ReadFromPlugin(); export default new ReadFromPlugin();