Skip to content

Commit db01b23

Browse files
authored
fix: Fiction Zone (#1935)
* Fictionzone progress * Fix fixtion zone * Fix novel covers * Fix image attempt 2 * Fix image attempt 3 * api for novel parse * Maybe fix novel parsing * Maybe fix novel parsing 2 * Maybe fix novel parsing 3 * Debug * Maybe fix novel parsing 4
1 parent e35fffd commit db01b23

1 file changed

Lines changed: 67 additions & 155 deletions

File tree

plugins/english/fictionzone.ts

Lines changed: 67 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,15 @@
11
import { fetchApi } from '@libs/fetch';
22
import { Plugin } from '@/types/plugin';
33
import { Filters } from '@libs/filterInputs';
4-
import { load as loadCheerio } from 'cheerio';
54
import { NovelStatus } from '@libs/novelStatus';
6-
import dayjs from 'dayjs';
75

8-
class FictionZonePlugin implements Plugin.PagePlugin {
6+
class FictionZonePlugin implements Plugin.PluginBase {
97
id = 'fictionzone';
108
name = 'Fiction Zone';
119
icon = 'src/en/fictionzone/icon.png';
1210
site = 'https://fictionzone.net';
13-
version = '1.0.1';
11+
version = '1.0.2';
1412
filters: Filters | undefined = undefined;
15-
imageRequestInit?: Plugin.ImageRequestInit | undefined = undefined;
16-
17-
//flag indicates whether access to LocalStorage, SesesionStorage is required.
18-
webStorageUtilized?: boolean;
19-
20-
cachedNovelIds = new Map<string, string>();
2113

2214
async popularNovels(
2315
pageNo: number,
@@ -26,176 +18,96 @@ class FictionZonePlugin implements Plugin.PagePlugin {
2618
filters,
2719
}: Plugin.PopularNovelsOptions<typeof this.filters>,
2820
): Promise<Plugin.NovelItem[]> {
29-
return await this.getPage(this.site + '/library?page=' + pageNo);
30-
}
31-
32-
async getPage(url: string) {
33-
const req = await fetchApi(url);
34-
const body = await req.text();
35-
const loadedCheerio = loadCheerio(body);
36-
37-
return loadedCheerio('div.novel-card')
38-
.map((i, el) => {
39-
const novelName = loadedCheerio(el).find('a > div.title > h1').text();
40-
const novelCover = loadedCheerio(el).find('img').attr('src');
41-
const novelUrl = loadedCheerio(el).find('a').attr('href');
42-
43-
return {
44-
name: novelName,
45-
cover: novelCover,
46-
path: novelUrl!.replace(/^\//, '').replace(/\/$/, ''),
47-
};
48-
})
49-
.toArray();
50-
}
51-
52-
async parseNovel(
53-
novelPath: string,
54-
): Promise<Plugin.SourceNovel & { totalPages: number }> {
55-
const req = await fetchApi(this.site + '/' + novelPath);
56-
const body = await req.text();
57-
const loadedCheerio = loadCheerio(body);
58-
59-
const novel: Plugin.SourceNovel & { totalPages: number } = {
60-
path: novelPath,
61-
name: loadedCheerio('div.novel-title > h1').text(),
62-
totalPages: 1,
63-
};
64-
65-
// novel.artist = '';
66-
novel.author = loadedCheerio('div.novel-author > content').text();
67-
novel.cover = loadedCheerio('div.novel-img > img').attr('src');
68-
novel.genres = [
69-
...loadedCheerio('div.genres > .items > span')
70-
.map((i, el) => loadedCheerio(el).text())
71-
.toArray(),
72-
...loadedCheerio('div.tags > .items > a')
73-
.map((i, el) => loadedCheerio(el).text())
74-
.toArray(),
75-
].join(',');
76-
const status = loadedCheerio('div.novel-status > div.content')
77-
.text()
78-
.trim();
79-
if (status === 'Ongoing') novel.status = NovelStatus.Ongoing;
80-
novel.summary = loadedCheerio('#synopsis > div.content').text();
81-
82-
const nuxtData = loadedCheerio('script#__NUXT_DATA__').html();
83-
const parsed = JSON.parse(nuxtData!);
84-
let last = null;
85-
for (const a of parsed) {
86-
if (typeof a === 'string' && a.startsWith('novel_covers/')) break;
87-
last = a;
88-
}
89-
this.cachedNovelIds.set(novelPath, last.toString());
90-
// @ts-ignore
91-
novel.chapters = loadedCheerio(
92-
'div.chapters > div.list-wrapper > div.items > a.chapter',
93-
)
94-
.map((i, el) => {
95-
const chapterName = loadedCheerio(el).find('span.chapter-title').text();
96-
const chapterUrl = loadedCheerio(el)
97-
.attr('href')
98-
?.replace(/^\//, '')
99-
.replace(/\/$/, '');
100-
const uploadTime = this.parseAgoDate(
101-
loadedCheerio(el).find('span.update-date').text(),
102-
);
103-
104-
return {
105-
name: chapterName,
106-
releaseTime: uploadTime,
107-
path: chapterUrl?.replace(/^\//, '').replace(/\/$/, ''),
108-
};
109-
})
110-
.toArray()
111-
.filter(chap => !!chap.path);
112-
novel.totalPages = parseInt(
113-
loadedCheerio('div.chapters ul.el-pager > li:last-child').text(),
21+
return await this.getPage(
22+
`/platform/browse?page=${pageNo}&page_size=20&sort_by=${showLatestNovels ? 'created_at' : 'bookmark_count'}&sort_order=desc&include_genres=true`,
11423
);
115-
116-
return novel;
11724
}
11825

119-
async parsePage(novelPath: string, page: string): Promise<Plugin.SourcePage> {
120-
let id = this.cachedNovelIds.get(novelPath);
121-
if (!id) {
122-
await this.parseNovel(novelPath);
123-
id = this.cachedNovelIds.get(novelPath);
124-
}
125-
126-
const data = await fetchApi(this.site + '/api/__api_party/api-v1', {
26+
async getData(url: string) {
27+
return await fetchApi(this.site + '/api/__api_party/fictionzone', {
12728
method: 'POST',
12829
headers: {
12930
'Content-Type': 'application/json',
31+
'Accept': 'application/json',
13032
},
13133
body: JSON.stringify({
132-
'path': '/chapter/all/' + id,
133-
'query': { 'page': parseInt(page) },
134-
'headers': { 'content-type': 'application/json' },
135-
'method': 'get',
34+
'path': url,
35+
'headers': [
36+
['content-type', 'application/json'],
37+
['x-request-time', new Date().toISOString()],
38+
],
39+
'method': 'GET',
13640
}),
13741
}).then(r => r.json());
42+
}
43+
44+
async getPage(url: string) {
45+
const data = await this.getData(url);
46+
47+
return data.data.novels.map((n: any) => ({
48+
name: n.title,
49+
cover: `https://cdn.fictionzone.net/insecure/rs:fill:165:250/${n.image}.webp`,
50+
path: `novel/${n.slug}`,
51+
}));
52+
}
53+
54+
async getChapterPage(id: string, novelPath: string) {
55+
const data = await this.getData('/platform/chapter-lists?novel_id=' + id);
56+
57+
return data.data.chapters.map((n: any) => ({
58+
name: n.title,
59+
number: n.chapter_number,
60+
date: n.published_date
61+
? new Date(n.published_date).toISOString()
62+
: undefined,
63+
path: `${novelPath}/${n.chapter_id}|/platform/chapter-content?novel_id=${id}&chapter_id=${n.chapter_id}`,
64+
}));
65+
}
66+
67+
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
68+
const novelSlug = novelPath.replace('novel/', '');
69+
const data = await this.getData(
70+
`/platform/novel-details?slug=${novelSlug}`,
71+
);
72+
13873
return {
139-
chapters: data._data.map((c: any) => ({
140-
name: c.title,
141-
releaseTime: new Date(c.created_at).toISOString(),
142-
path: novelPath + '/' + c.slug,
143-
})),
74+
path: novelPath,
75+
name: data.data.title,
76+
cover: `https://cdn.fictionzone.net/insecure/rs:fill:165:250/${data.data.image}.webp`,
77+
genres: [
78+
...data.data.genres.map((g: any) => g.name),
79+
...data.data.tags.map((g: any) => g.name),
80+
].join(','),
81+
status:
82+
data.data.status == 1
83+
? NovelStatus.Ongoing
84+
: data.data.status == 0
85+
? NovelStatus.Completed
86+
: NovelStatus.Unknown,
87+
author:
88+
data.data.contributors.filter((c: any) => c.role == 'author')[0]
89+
?.display_name || '',
90+
summary: data.data.synopsis,
91+
chapters: await this.getChapterPage(data.data.id, novelPath),
14492
};
14593
}
14694

14795
async parseChapter(chapterPath: string): Promise<string> {
148-
const req = await fetchApi(this.site + '/' + chapterPath);
149-
const body = await req.text();
150-
const loadedCheerio = loadCheerio(body);
151-
return loadedCheerio('div.chapter-content').html() || '';
96+
const data = await this.getData(chapterPath.split('|')[1]);
97+
return '<p>' + data.data.content.replaceAll('\n', '</p><p>') + '</p>';
15298
}
15399

154100
async searchNovels(
155101
searchTerm: string,
156102
pageNo: number,
157103
): Promise<Plugin.NovelItem[]> {
158104
return await this.getPage(
159-
this.site +
160-
'/library?query=' +
161-
encodeURIComponent(searchTerm) +
162-
'&page=' +
163-
pageNo +
164-
'&sort=views-all',
105+
`/platform/browse?search=${encodeURIComponent(searchTerm)}&page=${pageNo}&page_size=20&search_in_synopsis=true&sort_by=bookmark_count&sort_order=desc&include_genres=true`,
165106
);
166107
}
167108

168-
parseAgoDate(date: string | undefined) {
169-
//parseMadaraDate
170-
if (date?.includes('ago')) {
171-
const dayJSDate = dayjs(new Date()); // today
172-
const timeAgo = date.match(/\d+/)?.[0] || '';
173-
const timeAgoInt = parseInt(timeAgo, 10);
174-
175-
if (!timeAgo) return null; // there is no number!
176-
177-
if (date.includes('hours ago') || date.includes('hour ago')) {
178-
dayJSDate.subtract(timeAgoInt, 'hours'); // go back N hours
179-
}
180-
181-
if (date.includes('days ago') || date.includes('day ago')) {
182-
dayJSDate.subtract(timeAgoInt, 'days'); // go back N days
183-
}
184-
185-
if (date.includes('months ago') || date.includes('month ago')) {
186-
dayJSDate.subtract(timeAgoInt, 'months'); // go back N months
187-
}
188-
189-
if (date.includes('years ago') || date.includes('year ago')) {
190-
dayJSDate.subtract(timeAgoInt, 'years'); // go back N years
191-
}
192-
193-
return dayJSDate.toISOString();
194-
}
195-
return null; // there is no "ago" so give up
196-
}
197-
198-
resolveUrl = (path: string, isNovel?: boolean) => this.site + '/' + path;
109+
resolveUrl = (path: string, isNovel?: boolean) =>
110+
this.site + '/' + path.split('|')[0];
199111
}
200112

201113
export default new FictionZonePlugin();

0 commit comments

Comments
 (0)