Skip to content

Commit 5ae57b2

Browse files
committed
add: novelhi (no filter)
1 parent 5be7a59 commit 5ae57b2

File tree

2 files changed

+199
-0
lines changed

2 files changed

+199
-0
lines changed

plugins/english/novelhi.ts

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import { fetchApi } from '@libs/fetch';
2+
import { Plugin } from '@/types/plugin';
3+
import { load as parseHTML } from 'cheerio';
4+
import { NovelStatus } from '@libs/novelStatus';
5+
import { defaultCover } from '@libs/defaultCover';
6+
7+
class NovelHi implements Plugin.PluginBase {
8+
id = 'novelhi';
9+
name = 'NovelHi';
10+
icon = 'src/en/novelhi/icon.png';
11+
site = 'https://novelhi.com/';
12+
version = '1.0.0';
13+
14+
// flag indicates whether access to LocalStorage, SesesionStorage is required.
15+
webStorageUtilized?: boolean;
16+
17+
// Cache for storing extended metadata from the list API | ie: copypasta from readfrom.ts
18+
loadedNovelCache: CachedNovel[] = [];
19+
20+
parseNovels(novels: NovelData[]): CachedNovel[] {
21+
const ret: CachedNovel[] = novels.map(item => ({
22+
name: item.bookName,
23+
path: `s/${item.simpleName}`,
24+
cover: item.picUrl || defaultCover,
25+
summary: item.bookDesc,
26+
author: item.authorName,
27+
status: item.bookStatus,
28+
genres: item.genres.map(g => g.genreName).join(', '),
29+
}));
30+
31+
// Manage cache size
32+
this.loadedNovelCache.push(...ret);
33+
if (this.loadedNovelCache.length > 100) {
34+
this.loadedNovelCache = this.loadedNovelCache.slice(-100);
35+
}
36+
37+
return ret;
38+
}
39+
40+
async popularNovels(
41+
pageNo: number,
42+
{ showLatestNovels }: Plugin.PopularNovelsOptions,
43+
): Promise<Plugin.NovelItem[]> {
44+
const params = new URLSearchParams();
45+
46+
params.append('curr', `${pageNo}`);
47+
params.append('limit', '10');
48+
params.append('keyword', '');
49+
50+
const jsonUrl = `${this.site}book/searchByPageInShelf?` + params.toString();
51+
const response = await fetchApi(jsonUrl);
52+
const json: ApiResponse = await response.json();
53+
54+
return this.parseNovels(json.data.list);
55+
}
56+
57+
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
58+
const data = await fetchApi(this.site + novelPath);
59+
const text = await data.text();
60+
const loadedCheerio = parseHTML(text);
61+
62+
const translate = loadedCheerio('#translate <').html();
63+
if (translate) {
64+
throw Error('This Novel has been removed and is no longer available');
65+
}
66+
67+
const novel: Plugin.SourceNovel = {
68+
path: novelPath,
69+
name: loadedCheerio('meta[name=keywords]').attr('content') || 'Untitled',
70+
cover: loadedCheerio('.cover,.decorate-img').attr('src') || defaultCover,
71+
};
72+
73+
let moreNovelInfo = this.loadedNovelCache.find(n => n.path === novelPath);
74+
75+
if (!moreNovelInfo) {
76+
moreNovelInfo = (await this.searchNovels(novel.name, 1)).find(
77+
novel => novel.path === novelPath,
78+
);
79+
}
80+
if (moreNovelInfo) {
81+
novel.genres = moreNovelInfo.genres;
82+
novel.author = moreNovelInfo.author;
83+
novel.status =
84+
moreNovelInfo.status === '1'
85+
? NovelStatus.Completed
86+
: NovelStatus.Ongoing;
87+
const summary = moreNovelInfo.summary.replace(/<br\s*\/?>/gi, '\n');
88+
novel.summary = parseHTML(summary).text().trim();
89+
}
90+
91+
const chapters: Plugin.ChapterItem[] = [];
92+
const bookId = loadedCheerio('#bookId').attr('value');
93+
if (bookId && !translate) {
94+
const params = new URLSearchParams();
95+
params.append('bookId', bookId);
96+
params.append('curr', '1');
97+
params.append('limit', '42121');
98+
99+
const url = `${this.site}book/queryIndexList?` + params.toString();
100+
const res = await fetchApi(url);
101+
const resJson: ApiChapter = await res.json();
102+
103+
resJson?.data?.list?.forEach(chapter =>
104+
chapters.push({
105+
name: chapter.indexName,
106+
path: novelPath + '/' + chapter.indexNum,
107+
releaseTime: chapter.createTime,
108+
}),
109+
);
110+
}
111+
112+
novel.chapters = chapters.reverse();
113+
return novel;
114+
}
115+
116+
async parseChapter(chapterPath: string): Promise<string> {
117+
const url = this.site + chapterPath;
118+
const result = await fetchApi(url).then(res => res.text());
119+
120+
const loadedCheerio = parseHTML(result);
121+
loadedCheerio('#showReading script,ins').remove();
122+
const chapterText = loadedCheerio('#showReading').html();
123+
if (!chapterText) {
124+
return loadedCheerio('#translate <').html() || '';
125+
}
126+
return chapterText;
127+
}
128+
129+
async searchNovels(
130+
searchTerm: string,
131+
pageNo: number,
132+
): Promise<Plugin.NovelItem[]> {
133+
const params = new URLSearchParams();
134+
135+
params.append('curr', `${pageNo}`);
136+
params.append('limit', '10');
137+
params.append('keyword', `${searchTerm}`);
138+
139+
const jsonUrl = `${this.site}book/searchByPageInShelf?` + params.toString();
140+
const response = await fetchApi(jsonUrl);
141+
const json: ApiResponse = await response.json();
142+
143+
return this.parseNovels(json.data.list);
144+
}
145+
}
146+
147+
export default new NovelHi();
148+
149+
type CachedNovel = Plugin.NovelItem & {
150+
summary: string;
151+
genres: string;
152+
author: string;
153+
status: string;
154+
};
155+
156+
type NovelData = {
157+
id: string;
158+
bookName: string;
159+
picUrl: string;
160+
simpleName: string;
161+
authorName: string;
162+
bookDesc: string;
163+
bookStatus: string;
164+
lastIndexName: string;
165+
genres: {
166+
genreId: string;
167+
genreName: string;
168+
}[];
169+
};
170+
171+
type ChapterData = {
172+
id: string;
173+
bookId: string;
174+
indexNum: string;
175+
indexName: string;
176+
createTime: string;
177+
};
178+
179+
type ApiResponse = {
180+
code: string;
181+
msg: string;
182+
data: {
183+
pageNum: string;
184+
pageSize: string;
185+
total: string;
186+
list: NovelData[];
187+
};
188+
};
189+
190+
type ApiChapter = {
191+
code: string;
192+
msg: string;
193+
data: {
194+
pageNum: string;
195+
pageSize: string;
196+
total: string;
197+
list: ChapterData[];
198+
};
199+
};
3.04 KB
Loading

0 commit comments

Comments
 (0)