Skip to content

Commit e1f3caf

Browse files
authored
refactor: Update Read From Net to Use Desktop UA, URL Method, and Inline Tags in parseChapter (#2117)
1 parent eb992eb commit e1f3caf

1 file changed

Lines changed: 67 additions & 61 deletions

File tree

plugins/english/readfrom.ts

Lines changed: 67 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,22 @@ import { Filters } from '@libs/filterInputs';
44
import { CheerioAPI, load as loadCheerio } from 'cheerio';
55
import { defaultCover } from '@libs/defaultCover';
66

7+
const pluginHeaders = {
8+
'User-Agent':
9+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36',
10+
};
11+
712
class ReadFromPlugin implements Plugin.PluginBase {
813
id = 'readfrom';
914
name = 'Read From Net';
1015
icon = 'src/en/readfrom/icon.png';
11-
site = 'https://readfrom.net';
12-
version = '1.0.2';
16+
site = 'https://readfrom.net/';
17+
version = '1.1.0';
1318
filters: Filters | undefined = undefined;
14-
imageRequestInit?: Plugin.ImageRequestInit | undefined = undefined;
19+
headers = new Headers(pluginHeaders);
20+
imageRequestInit: Plugin.ImageRequestInit = {
21+
headers: pluginHeaders,
22+
};
1523

1624
//flag indicates whether access to LocalStorage, SesesionStorage is required.
1725
webStorageUtilized?: boolean;
@@ -34,18 +42,17 @@ class ReadFromPlugin implements Plugin.PluginBase {
3442
(isSearch ? 'div.text' : '#dle-content') + ' > article.box',
3543
)
3644
.map((i, el) => {
45+
const $el = loadedCheerio(el);
46+
const novelPath = $el.find('h2.title a').attr('href');
47+
if (!novelPath) return;
3748
const summary = loadedCheerio(el).find(
3849
isSearch ? 'div.text5' : 'div.text3',
3950
)[0];
4051
loadedCheerio(summary).find('.coll-ellipsis').remove();
4152
loadedCheerio(summary).find('a').remove();
4253
return {
4354
name: loadedCheerio(el).find('h2.title').text().trim(),
44-
path: loadedCheerio(el)
45-
.find('h2.title > a')
46-
.attr('href')!
47-
.replace('https://readfrom.net/', '')
48-
.replace(/^\//, ''),
55+
path: new URL(novelPath, this.site).pathname.substring(1),
4956
cover: loadedCheerio(el).find('img').attr('src') || defaultCover,
5057
summary:
5158
loadedCheerio(summary).text().trim() +
@@ -78,21 +85,20 @@ class ReadFromPlugin implements Plugin.PluginBase {
7885

7986
async popularNovels(
8087
pageNo: number,
81-
{
82-
showLatestNovels,
83-
filters,
84-
}: Plugin.PopularNovelsOptions<typeof this.filters>,
88+
{ showLatestNovels }: Plugin.PopularNovelsOptions<typeof this.filters>,
8589
) {
8690
const type = showLatestNovels ? 'last_added_books' : 'allbooks';
87-
const res = await fetchApi(
88-
'https://readfrom.net/' + type + '/page/' + pageNo + '/',
89-
);
91+
const res = await fetchApi(this.site + type + '/page/' + pageNo, {
92+
headers: this.headers,
93+
});
9094
const text = await res.text();
9195
return this.parseNovels(loadCheerio(text));
9296
}
9397

9498
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
95-
const data = await fetchApi('https://readfrom.net/' + novelPath);
99+
const data = await fetchApi(this.site + novelPath, {
100+
headers: this.headers,
101+
});
96102
const text = await data.text();
97103
const loadedCheerio = loadCheerio(text);
98104

@@ -109,25 +115,22 @@ class ReadFromPlugin implements Plugin.PluginBase {
109115
loadedCheerio('article.box > div > center > div > a > img').attr('src') ||
110116
defaultCover;
111117

112-
novel.chapters = loadedCheerio(loadedCheerio('div.pages').get()[0])
118+
const rawChapters = loadedCheerio('div.pages')
119+
.first()
113120
.find('> a')
114-
.map((i, el) => {
115-
return {
116-
name: loadedCheerio(el).text().trim(),
117-
path: loadedCheerio(el)
118-
.attr('href')!
119-
.replace('https://readfrom.net/', '')
120-
.replace(/^\//, ''),
121-
// releaseTime: '',
122-
chapterNumber: i + 2,
123-
};
124-
})
125-
.toArray();
126-
novel.chapters.unshift({
127-
name: '1',
128-
path: novelPath,
129-
chapterNumber: 1,
130-
});
121+
.toArray()
122+
.flatMap(el => {
123+
const href = loadedCheerio(el).attr('href');
124+
if (!href) return [];
125+
126+
const path = new URL(href, this.site).pathname.substring(1);
127+
return path ? [{ name: loadedCheerio(el).text().trim(), path }] : [];
128+
});
129+
130+
novel.chapters = [
131+
{ name: '1', path: novelPath, chapterNumber: 1 },
132+
...rawChapters.map((ch, i) => ({ ...ch, chapterNumber: i + 2 })),
133+
];
131134

132135
let moreNovelInfo = this.loadedNovelCache.find(
133136
novel => novel.path === novelPath,
@@ -156,43 +159,45 @@ class ReadFromPlugin implements Plugin.PluginBase {
156159
}
157160

158161
async parseChapter(chapterPath: string): Promise<string> {
159-
const data = await fetchApi('https://readfrom.net/' + chapterPath);
160-
const text = await data.text();
161-
const loadedCheerio = loadCheerio(text);
162-
loadedCheerio('#textToRead > span:empty').remove();
163-
loadedCheerio('#textToRead > center').remove();
164-
165-
const textToRead = loadedCheerio('#textToRead');
162+
const response = await fetchApi(this.site + chapterPath, {
163+
headers: this.headers,
164+
});
165+
const $ = loadCheerio(await response.text());
166+
$('#textToRead > span:empty, #textToRead > center').remove();
166167

167-
let paragraph: string[] = [];
168168
const chapterHtml: string[] = [];
169+
let p: string[] = [];
170+
171+
const allowed = new Set(['b', 'i', 'u', 'strong', 'em', 'a']);
172+
const flush = () =>
173+
p.length && (chapterHtml.push(`<p>${p.join(' ').trim()}</p>`), (p = []));
169174

170-
textToRead.contents().each((_, element) => {
171-
switch (element.type) {
175+
for (const el of $('#textToRead').contents().toArray()) {
176+
switch (el.type) {
177+
case 'comment':
178+
continue;
172179
case 'text':
173-
const content = element.data.trim();
174-
if (content) {
175-
paragraph.push(content);
180+
if (el.data.trim()) {
181+
// Convert _text_ to <i>text</i>
182+
const jbText = el.data.trim().replace(/_([^_]+)_/g, '<i>$1</i>');
183+
p.push(jbText);
176184
}
177-
break;
178-
185+
continue;
179186
case 'tag':
180-
if (paragraph.length > 0) {
181-
chapterHtml.push(`<p>${paragraph.join(' ')}</p>`);
182-
paragraph = [];
187+
if (allowed.has(el.name)) {
188+
p.push($.html(el));
189+
continue;
183190
}
184-
if (element.tagName !== 'br') {
185-
chapterHtml.push(loadedCheerio.html(element));
191+
if (el.name === 'br') {
192+
flush();
193+
continue;
186194
}
187-
break;
188195
}
189-
});
190-
191-
// Close any remaining paragraph
192-
if (paragraph.length > 0) {
193-
chapterHtml.push(`<p>${paragraph.join(' ')}</p>`);
196+
flush();
197+
chapterHtml.push($.html(el));
194198
}
195199

200+
flush();
196201
return chapterHtml.join('');
197202
}
198203

@@ -201,12 +206,13 @@ class ReadFromPlugin implements Plugin.PluginBase {
201206
const res = await fetchApi(
202207
'https://readfrom.net/build_in_search/?q=' +
203208
encodeURIComponent(searchTerm),
209+
{ headers: this.headers },
204210
);
205211
const text = await res.text();
206212
return this.parseNovels(loadCheerio(text), true);
207213
}
208214

209-
resolveUrl = (path: string, isNovel?: boolean) => this.site + '/' + path;
215+
// resolveUrl = (path: string, isNovel?: boolean) => this.site + '/' + path;
210216
}
211217

212218
export default new ReadFromPlugin();

0 commit comments

Comments
 (0)