* add prettier

* lint-staged

* prettier

* upgrade husky
This commit is contained in:
nyagami
2024-03-23 22:00:29 +07:00
committed by GitHub
parent c22979b10f
commit 5f9a067ab9
113 changed files with 79244 additions and 50841 deletions
+230 -218
View File
@@ -1,283 +1,295 @@
import { load as parseHTML } from "cheerio";
import { fetchApi, fetchFile, fetchProto } from "@libs/fetch";
import { Plugin } from "@typings/plugin";
import { Filters } from "@libs/filterInputs";
import { NovelStatus } from "@libs/novelStatus";
import { load as parseHTML } from 'cheerio';
import { fetchApi, fetchFile, fetchProto } from '@libs/fetch';
import { Plugin } from '@typings/plugin';
import { Filters } from '@libs/filterInputs';
import { NovelStatus } from '@libs/novelStatus';
interface StringValue {
value: string;
value: string;
}
interface BoolValue {
value: boolean;
value: boolean;
}
interface DecimalValue {
units: bigint;
nanos: number;
units: bigint;
nanos: number;
}
interface Timestamp {
seconds: number;
nanos: number;
seconds: number;
nanos: number;
}
interface NovelEntry {
name: string;
coverUrl: string;
slug: string;
name: string;
coverUrl: string;
slug: string;
}
interface RelatedChapterUserInfo {
isChapterUnlocked?: BoolValue;
isChapterUnlocked?: BoolValue;
}
interface ChapterItem {
entityId: number;
name: string;
slug: string;
number?: DecimalValue;
content?: StringValue;
relatedUserInfo?: RelatedChapterUserInfo;
offset: number;
publishedAt?: Timestamp;
entityId: number;
name: string;
slug: string;
number?: DecimalValue;
content?: StringValue;
relatedUserInfo?: RelatedChapterUserInfo;
offset: number;
publishedAt?: Timestamp;
}
interface GetChapterResponse {
item?: ChapterItem;
item?: ChapterItem;
}
interface ChapterGroupCounts {
total: number;
advance: number;
normal: number;
total: number;
advance: number;
normal: number;
}
interface ChapterGroupItem {
id: number;
title: string;
order: number;
fromChapterNumber?: DecimalValue;
toChapterNumber?: DecimalValue;
chapterList: ChapterItem[];
counts?: ChapterGroupCounts;
id: number;
title: string;
order: number;
fromChapterNumber?: DecimalValue;
toChapterNumber?: DecimalValue;
chapterList: ChapterItem[];
counts?: ChapterGroupCounts;
}
interface GetChapterListResponse {
items: ChapterGroupItem[];
items: ChapterGroupItem[];
}
enum NovelItem_Status {
Finished = 0,
Active = 1,
Hiatus = 2,
All = -1
Finished = 0,
Active = 1,
Hiatus = 2,
All = -1,
}
interface NovelKarmaInfo {
isActive: boolean;
isFree: boolean;
maxFreeChapter?: DecimalValue;
canUnlockWithVip: boolean;
isActive: boolean;
isFree: boolean;
maxFreeChapter?: DecimalValue;
canUnlockWithVip: boolean;
}
interface NovelItem {
id: number;
name: string;
slug: string;
status: NovelItem_Status;
visible: boolean;
description?: StringValue;
synopsis?: StringValue;
coverUrl?: StringValue;
translatorName?: StringValue;
authorName?: StringValue;
karmaInfo?: NovelKarmaInfo;
genres: string[];
id: number;
name: string;
slug: string;
status: NovelItem_Status;
visible: boolean;
description?: StringValue;
synopsis?: StringValue;
coverUrl?: StringValue;
translatorName?: StringValue;
authorName?: StringValue;
karmaInfo?: NovelKarmaInfo;
genres: string[];
}
interface GetNovelResponse {
item?: NovelItem;
item?: NovelItem;
}
class WuxiaWorld implements Plugin.PluginBase {
id = "wuxiaworld";
name = "Wuxia World";
icon = "src/en/wuxiaworld/icon.png";
site = "https://www.wuxiaworld.com/";
filters?: Filters | undefined;
version = "0.5.0";
id = 'wuxiaworld';
name = 'Wuxia World';
icon = 'src/en/wuxiaworld/icon.png';
site = 'https://www.wuxiaworld.com/';
filters?: Filters | undefined;
version = '0.5.0';
parseNovels(data: {items: NovelEntry[]}){
const novels: Plugin.NovelItem[] = [];
parseNovels(data: { items: NovelEntry[] }) {
const novels: Plugin.NovelItem[] = [];
data.items.map((novel: NovelEntry) => {
const name = novel.name;
const cover = novel.coverUrl;
const path = `novel/${novel.slug}/`;
data.items.map((novel: NovelEntry) => {
const name = novel.name;
const cover = novel.coverUrl;
const path = `novel/${novel.slug}/`;
novels.push({
name,
cover,
path,
});
});
novels.push({
name,
cover,
path,
});
});
return novels;
return novels;
}
async popularNovels(
pageNo: number,
options: Plugin.PopularNovelsOptions<Filters>,
): Promise<Plugin.NovelItem[]> {
const link = `${this.site}api/novels`;
const result = await fetchApi(link);
const data = await result.json();
return this.parseNovels(data);
}
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
const data = await fetchProto<GetNovelResponse>(
{
proto: this.proto,
requestType: 'GetNovelRequest',
responseType: 'GetNovelResponse',
requestData: { slug: novelPath.split('/')[1] },
},
'https://api2.wuxiaworld.com/wuxiaworld.api.v2.Novels/GetNovel',
{
headers: {
'Content-Type': 'application/grpc-web+proto',
},
},
);
const novel: Plugin.SourceNovel = {
path: novelPath,
name: data.item?.name || 'Untitled',
cover: data.item?.coverUrl?.value,
summary: parseHTML(
data.item?.description?.value + '\n\n' + data.item?.synopsis?.value,
).text(),
author: data.item?.authorName?.value,
genres: data.item?.genres.join(','),
chapters: [],
};
const status = data.item?.status;
switch (status) {
case NovelItem_Status.Active:
novel.status = NovelStatus.Ongoing;
break;
case NovelItem_Status.Hiatus:
novel.status = NovelStatus.OnHiatus;
break;
case NovelItem_Status.All:
novel.status = NovelStatus.Unknown;
break;
default:
novel.status = NovelStatus.Completed;
}
async popularNovels(pageNo: number, options: Plugin.PopularNovelsOptions<Filters>): Promise<Plugin.NovelItem[]> {
const link = `${this.site}api/novels`;
// novel.summary = loadedCheerio('.relative > .absolute:first')
// .children('span')
// .map((i,el) => loadedCheerio(el).text().trim())
// .toArray()
// .join('\n');
const result = await fetchApi(link);
const data = await result.json();
// novel.genres = loadedCheerio("a.MuiLink-underlineNone")
// .map((i,el) => loadedCheerio(el).text())
// .toArray()
// .join(',');
return this.parseNovels(data)
}
// novel.status = loadedCheerio("div.font-set-b10").text();
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
const data = await fetchProto<GetNovelResponse>(
{
proto: this.proto,
requestType: 'GetNovelRequest',
responseType: 'GetNovelResponse',
requestData: {slug: novelPath.split('/')[1]},
const list = await fetchProto<GetChapterListResponse>(
{
proto: this.proto,
requestType: 'GetChapterListRequest',
responseType: 'GetChapterListResponse',
requestData: { novelId: data.item?.id },
},
'https://api2.wuxiaworld.com/wuxiaworld.api.v2.Chapters/GetChapterList',
{
headers: {
'Content-Type': 'application/grpc-web+proto',
},
},
);
const freeChapter =
Number(data.item?.karmaInfo?.maxFreeChapter?.units) +
(data.item?.karmaInfo?.maxFreeChapter?.nanos || 0) / 1000000000 || 50;
const chapter: Plugin.ChapterItem[] = list.items.flatMap(
(ChapterGroupItem: ChapterGroupItem) =>
ChapterGroupItem.chapterList.map((chapterItem: ChapterItem) => ({
page: ChapterGroupItem.title,
name:
chapterItem.name +
(chapterItem.relatedUserInfo?.isChapterUnlocked?.value === false ||
(!chapterItem.relatedUserInfo &&
Number(chapterItem.number?.units) +
(chapterItem.number?.nanos || 0) / 1000000000 >
freeChapter)
? ' 🔒'
: ''),
path: novelPath + chapterItem.slug,
chapterNumber: chapterItem.offset,
releaseTime: new Date(
(chapterItem.publishedAt?.seconds || 0) * 1000 +
(chapterItem.publishedAt?.nanos || 0) / 1000000,
).toISOString(),
})),
);
novel.chapters = chapter;
return novel;
}
async parseChapter(chapterPath: string): Promise<string> {
const paths = chapterPath.split('/');
const data = await fetchProto<GetChapterResponse>(
{
proto: this.proto,
requestType: 'GetChapterRequest',
responseType: 'GetChapterResponse',
requestData: {
chapterProperty: {
slugs: {
novelSlug: paths[1],
chapterSlug: paths[2],
},
'https://api2.wuxiaworld.com/wuxiaworld.api.v2.Novels/GetNovel',
{
headers: {
"Content-Type": "application/grpc-web+proto",
}
}
)
},
},
},
'https://api2.wuxiaworld.com/wuxiaworld.api.v2.Chapters/GetChapter',
{
headers: {
'Content-Type': 'application/grpc-web+proto',
},
},
);
// loadedCheerio(".chapter-nav").remove();
// loadedCheerio("#chapter-content > script").remove();
// const chapterText = loadedCheerio("#chapter-content").html() || '';
const novel: Plugin.SourceNovel = {
path: novelPath,
name: data.item?.name || "Untitled",
cover: data.item?.coverUrl?.value,
summary: parseHTML(data.item?.description?.value + '\n\n' + data.item?.synopsis?.value).text(),
author: data.item?.authorName?.value,
genres: data.item?.genres.join(','),
chapters: [],
};
const chapterText = data.item?.content?.value || '';
return chapterText;
}
const status = data.item?.status;
switch (status){
case NovelItem_Status.Active:
novel.status = NovelStatus.Ongoing;
break;
case NovelItem_Status.Hiatus:
novel.status = NovelStatus.OnHiatus;
break;
case NovelItem_Status.All:
novel.status = NovelStatus.Unknown;
break;
default: novel.status = NovelStatus.Completed;
}
async searchNovels(
searchTerm: string,
pageNo: number,
): Promise<Plugin.NovelItem[]> {
const searchUrl = 'https://www.wuxiaworld.com/api/novels/search?query=';
// novel.summary = loadedCheerio('.relative > .absolute:first')
// .children('span')
// .map((i,el) => loadedCheerio(el).text().trim())
// .toArray()
// .join('\n');
const url = searchUrl + searchTerm;
// novel.genres = loadedCheerio("a.MuiLink-underlineNone")
// .map((i,el) => loadedCheerio(el).text())
// .toArray()
// .join(',');
const result = await fetchApi(url);
const data = await result.json();
// novel.status = loadedCheerio("div.font-set-b10").text();
return this.parseNovels(data);
}
const list = await fetchProto<GetChapterListResponse>(
{
proto: this.proto,
requestType: 'GetChapterListRequest',
responseType: 'GetChapterListResponse',
requestData: {novelId: data.item?.id},
},
'https://api2.wuxiaworld.com/wuxiaworld.api.v2.Chapters/GetChapterList',
{
headers: {
"Content-Type": "application/grpc-web+proto",
}
}
)
fetchImage(url: string): Promise<string | undefined> {
return fetchFile(url);
}
const freeChapter = Number(data.item?.karmaInfo?.maxFreeChapter?.units) +
(data.item?.karmaInfo?.maxFreeChapter?.nanos || 0) / 1000000000 || 50;
const chapter: Plugin.ChapterItem[] = list.items.flatMap((ChapterGroupItem: ChapterGroupItem) =>
ChapterGroupItem.chapterList.map((chapterItem: ChapterItem) => ({
page: ChapterGroupItem.title,
name: chapterItem.name + (
(chapterItem.relatedUserInfo?.isChapterUnlocked?.value === false) ||
(!chapterItem.relatedUserInfo &&
(Number(chapterItem.number?.units) +
(chapterItem.number?.nanos || 0) / 1000000000) > freeChapter)
? ' 🔒'
: ''
),
path: novelPath + chapterItem.slug,
chapterNumber: chapterItem.offset,
releaseTime: new Date(
(chapterItem.publishedAt?.seconds || 0) * 1000 +
(chapterItem.publishedAt?.nanos || 0) / 1000000
).toISOString(),
})
));
novel.chapters = chapter;
return novel;
}
async parseChapter(chapterPath: string): Promise<string> {
const paths = chapterPath.split('/');
const data = await fetchProto<GetChapterResponse>(
{
proto: this.proto,
requestType: 'GetChapterRequest',
responseType: 'GetChapterResponse',
requestData: {
chapterProperty: {
slugs: {
novelSlug: paths[1],
chapterSlug: paths[2],
}
}
},
},
'https://api2.wuxiaworld.com/wuxiaworld.api.v2.Chapters/GetChapter',
{
headers: {
"Content-Type": "application/grpc-web+proto",
}
}
)
// loadedCheerio(".chapter-nav").remove();
// loadedCheerio("#chapter-content > script").remove();
// const chapterText = loadedCheerio("#chapter-content").html() || '';
const chapterText = data.item?.content?.value || "";
return chapterText
}
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]> {
const searchUrl = "https://www.wuxiaworld.com/api/novels/search?query=";
const url = searchUrl + searchTerm;
const result = await fetchApi(url);
const data = await result.json();
return this.parseNovels(data);
}
fetchImage(url: string): Promise<string | undefined> {
return fetchFile(url);
}
proto = `
proto = `
syntax = "proto3";
option optimize_for = CODE_SIZE;
package wuxiaworld.api.v2;
@@ -460,7 +472,7 @@ class WuxiaWorld implements Plugin.PluginBase {
service Novels {
rpc GetNovel(GetNovelRequest) returns (GetNovelResponse);
}
`
`;
}
export default new WuxiaWorld();
export default new WuxiaWorld();