mirror of
https://github.com/venera-app/venera-configs.git
synced 2025-09-27 08:27:24 +00:00
refactor(zaimanhua): 重构漫画源接口实现和数据结构
- 修改fetchHtml和fetchJson返回类型,增加错误处理 - 简化漫画信息解析逻辑,移除冗余字段 - 重构分类页面实现,使用固定分类选项 - 实现分类漫画加载接口,支持分页和筛选
This commit is contained in:
405
zaimanhua.js
405
zaimanhua.js
@@ -19,22 +19,27 @@ class ZaiManHua extends ComicSource {
|
|||||||
* fetch html content
|
* fetch html content
|
||||||
* @param url {string}
|
* @param url {string}
|
||||||
* @param headers {object?}
|
* @param headers {object?}
|
||||||
* @returns {Promise<{body: string, status: number, headers: object}>}
|
* @returns {Promise<{document:HtmlDocument}>}
|
||||||
*/
|
*/
|
||||||
async fetchHtml(url, headers = {}) {
|
async fetchHtml(url, headers = {}) {
|
||||||
let res = await Network.get(url, headers);
|
let res = await Network.get(url, headers);
|
||||||
return res;
|
if (res.status !== 200) {
|
||||||
|
throw "Invalid status code: " + res.status;
|
||||||
|
}
|
||||||
|
let document = new HtmlDocument(res.body);
|
||||||
|
|
||||||
|
return document;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* fetch json content
|
* fetch json content
|
||||||
* @param url {string}
|
* @param url {string}
|
||||||
* @param headers {object?}
|
* @param headers {object?}
|
||||||
* @returns {Promise<{errno:number,errmsg:string,data:object}>}
|
* @returns {Promise<{data:object}>}
|
||||||
*/
|
*/
|
||||||
async fetchJson(url, headers = {}) {
|
async fetchJson(url, headers = {}) {
|
||||||
let res = await Network.get(url, headers);
|
let res = await Network.get(url, headers);
|
||||||
return JSON.parse(res.body);
|
return JSON.parse(res.body).data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -85,7 +90,6 @@ class ZaiManHua extends ComicSource {
|
|||||||
id,
|
id,
|
||||||
subtitle,
|
subtitle,
|
||||||
tags,
|
tags,
|
||||||
url,
|
|
||||||
cover,
|
cover,
|
||||||
description,
|
description,
|
||||||
});
|
});
|
||||||
@@ -100,24 +104,17 @@ class ZaiManHua extends ComicSource {
|
|||||||
let cover = e.cover;
|
let cover = e.cover;
|
||||||
let title = e.name;
|
let title = e.name;
|
||||||
let id = e.comic_py;
|
let id = e.comic_py;
|
||||||
let url = `https://www.zaimanhua.com/info/${e.id}.html`;
|
|
||||||
|
|
||||||
let subtitle = e.authors;
|
let subtitle = e.authors;
|
||||||
|
|
||||||
let classify = e.types;
|
let classify = e.types.split("/");
|
||||||
let status = e.status;
|
|
||||||
let description = e.last_update_chapter_name;
|
let description = e.last_update_chapter_name;
|
||||||
let tags = {
|
|
||||||
类型: classify,
|
|
||||||
状态: status,
|
|
||||||
};
|
|
||||||
|
|
||||||
return new Comic({
|
return new Comic({
|
||||||
title,
|
title,
|
||||||
id,
|
id,
|
||||||
subtitle,
|
subtitle,
|
||||||
tags,
|
tags: classify,
|
||||||
url,
|
|
||||||
cover,
|
cover,
|
||||||
description,
|
description,
|
||||||
});
|
});
|
||||||
@@ -152,11 +149,7 @@ class ZaiManHua extends ComicSource {
|
|||||||
*/
|
*/
|
||||||
load: async (page) => {
|
load: async (page) => {
|
||||||
let result = {};
|
let result = {};
|
||||||
let res = await this.fetchHtml(this.domain);
|
let document = await this.fetchHtml(this.domain);
|
||||||
if (res.status !== 200) {
|
|
||||||
throw `Invalid status code: ${res.status}`;
|
|
||||||
}
|
|
||||||
let document = new HtmlDocument(res.body);
|
|
||||||
// 推荐
|
// 推荐
|
||||||
let recommend_title = document.querySelector(
|
let recommend_title = document.querySelector(
|
||||||
".new_recommend_l h2"
|
".new_recommend_l h2"
|
||||||
@@ -179,51 +172,71 @@ class ZaiManHua extends ComicSource {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// categories
|
||||||
// categories
|
// categories
|
||||||
category = {
|
category = {
|
||||||
/// title of the category page, used to identify the page, it should be unique
|
/// title of the category page, used to identify the page, it should be unique
|
||||||
title: "",
|
title: this.name,
|
||||||
parts: [
|
parts: [
|
||||||
{
|
{
|
||||||
// title of the part
|
name: "类型",
|
||||||
name: "Theme",
|
|
||||||
|
|
||||||
// fixed or random or dynamic
|
|
||||||
// if random, need to provide `randomNumber` field, which indicates the number of comics to display at the same time
|
|
||||||
// if dynamic, need to provide `loader` field, which indicates the function to load comics
|
|
||||||
type: "fixed",
|
type: "fixed",
|
||||||
|
|
||||||
// Remove this if type is dynamic
|
|
||||||
categories: [
|
categories: [
|
||||||
{
|
"全部",
|
||||||
label: "Category1",
|
"冒险",
|
||||||
/**
|
"搞笑",
|
||||||
* @type {PageJumpTarget}
|
"格斗",
|
||||||
*/
|
"科幻",
|
||||||
target: {
|
"爱情",
|
||||||
page: "category",
|
"侦探",
|
||||||
attributes: {
|
"竞技",
|
||||||
category: "category1",
|
"魔法",
|
||||||
param: null,
|
"校园",
|
||||||
|
"百合",
|
||||||
|
"耽美",
|
||||||
|
"历史",
|
||||||
|
"战争",
|
||||||
|
"宅系",
|
||||||
|
"治愈",
|
||||||
|
"仙侠",
|
||||||
|
"武侠",
|
||||||
|
"职场",
|
||||||
|
"神鬼",
|
||||||
|
"奇幻",
|
||||||
|
"生活",
|
||||||
|
"其他",
|
||||||
|
],
|
||||||
|
itemType: "category",
|
||||||
|
categoryParams: [
|
||||||
|
"0",
|
||||||
|
"1",
|
||||||
|
"2",
|
||||||
|
"3",
|
||||||
|
"4",
|
||||||
|
"5",
|
||||||
|
"6",
|
||||||
|
"7",
|
||||||
|
"8",
|
||||||
|
"9",
|
||||||
|
"11",
|
||||||
|
"13",
|
||||||
|
"14",
|
||||||
|
"15",
|
||||||
|
"16",
|
||||||
|
"17",
|
||||||
|
"18",
|
||||||
|
"19",
|
||||||
|
"20",
|
||||||
|
"21",
|
||||||
|
"22",
|
||||||
|
"23",
|
||||||
|
"24",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
// number of comics to display at the same time
|
|
||||||
// randomNumber: 5,
|
|
||||||
|
|
||||||
// load function for dynamic type
|
|
||||||
// loader: async () => {
|
|
||||||
// return [
|
|
||||||
// // ...
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
// enable ranking page
|
// enable ranking page
|
||||||
enableRankingPage: false,
|
enableRankingPage: false,
|
||||||
}
|
};
|
||||||
|
|
||||||
/// category comic loading related
|
/// category comic loading related
|
||||||
categoryComics = {
|
categoryComics = {
|
||||||
@@ -236,85 +249,47 @@ class ZaiManHua extends ComicSource {
|
|||||||
* @returns {Promise<{comics: Comic[], maxPage: number}>}
|
* @returns {Promise<{comics: Comic[], maxPage: number}>}
|
||||||
*/
|
*/
|
||||||
load: async (category, param, options, page) => {
|
load: async (category, param, options, page) => {
|
||||||
/*
|
let fil = "https://manhua.zaimanhua.com/api/v1/comic1/filter";
|
||||||
```
|
let params = {
|
||||||
let data = JSON.parse((await Network.get('...')).body)
|
timestamp: Date.now(),
|
||||||
let maxPage = data.maxPage
|
sortType: 0,
|
||||||
|
page: page,
|
||||||
function parseComic(comic) {
|
size: 20,
|
||||||
// ...
|
status: options[1],
|
||||||
|
audience: options[0],
|
||||||
return new Comic({
|
theme: param,
|
||||||
id: id,
|
cate: options[2],
|
||||||
title: title,
|
};
|
||||||
subTitle: author,
|
// 拼接url
|
||||||
cover: cover,
|
let params_str = Object.keys(params)
|
||||||
tags: tags,
|
.map((key) => `${key}=${params[key]}`)
|
||||||
description: description
|
.join("&");
|
||||||
})
|
// log("error", "再漫画", params_str);
|
||||||
}
|
let url = `${fil}?${params_str}&firstLetter`;
|
||||||
|
// log("error", "再漫画", url);
|
||||||
|
|
||||||
|
const json = await this.fetchJson(url);
|
||||||
|
let comics = json.comicList.map((e) => this.parseJsonComic(e));
|
||||||
|
let maxPage = Math.ceil(json.totalNum / params.size);
|
||||||
|
// log("error", "再漫画", comics);
|
||||||
return {
|
return {
|
||||||
comics: data.list.map(parseComic),
|
comics,
|
||||||
maxPage: maxPage
|
maxPage,
|
||||||
}
|
};
|
||||||
```
|
|
||||||
*/
|
|
||||||
},
|
},
|
||||||
// provide options for category comic loading
|
// provide options for category comic loading
|
||||||
optionList: [
|
optionList: [
|
||||||
{
|
{
|
||||||
// For a single option, use `-` to separate the value and text, left for value, right for text
|
options: ["0-全部", "3262-少年", "3263-少女", "3264-青年"],
|
||||||
options: [
|
},
|
||||||
"newToOld-New to Old",
|
{
|
||||||
"oldToNew-Old to New"
|
options: ["0-全部", "1-故事漫画", "2-四格多格"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
options: ["0-全部", "1-连载", "2-完结"],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
// [Optional] {string[]} - show this option only when the value not in the list
|
};
|
||||||
notShowWhen: null,
|
|
||||||
// [Optional] {string[]} - show this option only when the value in the list
|
|
||||||
showWhen: null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
ranking: {
|
|
||||||
// For a single option, use `-` to separate the value and text, left for value, right for text
|
|
||||||
options: [
|
|
||||||
"day-Day",
|
|
||||||
"week-Week"
|
|
||||||
],
|
|
||||||
/**
|
|
||||||
* load ranking comics
|
|
||||||
* @param option {string} - option from optionList
|
|
||||||
* @param page {number} - page number
|
|
||||||
* @returns {Promise<{comics: Comic[], maxPage: number}>}
|
|
||||||
*/
|
|
||||||
load: async (option, page) => {
|
|
||||||
/*
|
|
||||||
```
|
|
||||||
let data = JSON.parse((await Network.get('...')).body)
|
|
||||||
let maxPage = data.maxPage
|
|
||||||
|
|
||||||
function parseComic(comic) {
|
|
||||||
// ...
|
|
||||||
|
|
||||||
return new Comic({
|
|
||||||
id: id,
|
|
||||||
title: title,
|
|
||||||
subTitle: author,
|
|
||||||
cover: cover,
|
|
||||||
tags: tags,
|
|
||||||
description: description
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
comics: data.list.map(parseComic),
|
|
||||||
maxPage: maxPage
|
|
||||||
}
|
|
||||||
```
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// search related
|
/// search related
|
||||||
search = {
|
search = {
|
||||||
@@ -326,191 +301,17 @@ class ZaiManHua extends ComicSource {
|
|||||||
* @returns {Promise<{comics: Comic[], maxPage: number}>}
|
* @returns {Promise<{comics: Comic[], maxPage: number}>}
|
||||||
*/
|
*/
|
||||||
load: async (keyword, options, page) => {
|
load: async (keyword, options, page) => {
|
||||||
/*
|
let url = `https://manhua.zaimanhua.com/app/v1/search/index?keyword=${keyword}&source=0&page=${page}&size=20`;
|
||||||
```
|
const json = await this.fetchJson(url);
|
||||||
let data = JSON.parse((await Network.get('...')).body)
|
|
||||||
let maxPage = data.maxPage
|
|
||||||
|
|
||||||
function parseComic(comic) {
|
|
||||||
// ...
|
|
||||||
|
|
||||||
return new Comic({
|
|
||||||
id: id,
|
|
||||||
title: title,
|
|
||||||
subTitle: author,
|
|
||||||
cover: cover,
|
|
||||||
tags: tags,
|
|
||||||
description: description
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
comics: data.list.map(parseComic),
|
|
||||||
maxPage: maxPage
|
|
||||||
}
|
|
||||||
```
|
|
||||||
*/
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* load search result with next page token.
|
|
||||||
* The field will be ignored if `load` function is implemented.
|
|
||||||
* @param keyword {string}
|
|
||||||
* @param options {(string)[]} - options from optionList
|
|
||||||
* @param next {string | null}
|
|
||||||
* @returns {Promise<{comics: Comic[], maxPage: number}>}
|
|
||||||
*/
|
|
||||||
loadNext: async (keyword, options, next) => {},
|
|
||||||
|
|
||||||
// provide options for search
|
// provide options for search
|
||||||
optionList: [
|
optionList: [],
|
||||||
{
|
|
||||||
// [Optional] default is `select`
|
|
||||||
// type: select, multi-select, dropdown
|
|
||||||
// For select, there is only one selected value
|
|
||||||
// For multi-select, there are multiple selected values or none. The `load` function will receive a json string which is an array of selected values
|
|
||||||
// For dropdown, there is one selected value at most. If no selected value, the `load` function will receive a null
|
|
||||||
type: "select",
|
|
||||||
// For a single option, use `-` to separate the value and text, left for value, right for text
|
|
||||||
options: ["0-time", "1-popular"],
|
|
||||||
// option label
|
|
||||||
label: "sort",
|
|
||||||
// default selected options. If not set, use the first option as default
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
// enable tags suggestions
|
// enable tags suggestions
|
||||||
enableTagsSuggestions: false,
|
enableTagsSuggestions: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// favorite related
|
|
||||||
favorites = {
|
|
||||||
// whether support multi folders
|
|
||||||
multiFolder: false,
|
|
||||||
/**
|
|
||||||
* add or delete favorite.
|
|
||||||
* throw `Login expired` to indicate login expired, App will automatically re-login and re-add/delete favorite
|
|
||||||
* @param comicId {string}
|
|
||||||
* @param folderId {string}
|
|
||||||
* @param isAdding {boolean} - true for add, false for delete
|
|
||||||
* @param favoriteId {string?} - [Comic.favoriteId]
|
|
||||||
* @returns {Promise<any>} - return any value to indicate success
|
|
||||||
*/
|
|
||||||
addOrDelFavorite: async (comicId, folderId, isAdding, favoriteId) => {
|
|
||||||
/*
|
|
||||||
```
|
|
||||||
let res = await Network.post('...')
|
|
||||||
if (res.status === 401) {
|
|
||||||
throw `Login expired`;
|
|
||||||
}
|
|
||||||
return 'ok'
|
|
||||||
```
|
|
||||||
*/
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* load favorite folders.
|
|
||||||
* throw `Login expired` to indicate login expired, App will automatically re-login retry.
|
|
||||||
* if comicId is not null, return favorite folders which contains the comic.
|
|
||||||
* @param comicId {string?}
|
|
||||||
* @returns {Promise<{folders: {[p: string]: string}, favorited: string[]}>} - `folders` is a map of folder id to folder name, `favorited` is a list of folder id which contains the comic
|
|
||||||
*/
|
|
||||||
loadFolders: async (comicId) => {
|
|
||||||
/*
|
|
||||||
```
|
|
||||||
let data = JSON.parse((await Network.get('...')).body)
|
|
||||||
|
|
||||||
let folders = {}
|
|
||||||
|
|
||||||
data.folders.forEach((f) => {
|
|
||||||
folders[f.id] = f.name
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
folders: folders,
|
|
||||||
favorited: data.favorited
|
|
||||||
}
|
|
||||||
```
|
|
||||||
*/
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* add a folder
|
|
||||||
* @param name {string}
|
|
||||||
* @returns {Promise<any>} - return any value to indicate success
|
|
||||||
*/
|
|
||||||
addFolder: async (name) => {
|
|
||||||
/*
|
|
||||||
```
|
|
||||||
let res = await Network.post('...')
|
|
||||||
if (res.status === 401) {
|
|
||||||
throw `Login expired`;
|
|
||||||
}
|
|
||||||
return 'ok'
|
|
||||||
```
|
|
||||||
*/
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* delete a folder
|
|
||||||
* @param folderId {string}
|
|
||||||
* @returns {Promise<void>} - return any value to indicate success
|
|
||||||
*/
|
|
||||||
deleteFolder: async (folderId) => {
|
|
||||||
/*
|
|
||||||
```
|
|
||||||
let res = await Network.delete('...')
|
|
||||||
if (res.status === 401) {
|
|
||||||
throw `Login expired`;
|
|
||||||
}
|
|
||||||
return 'ok'
|
|
||||||
```
|
|
||||||
*/
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* load comics in a folder
|
|
||||||
* throw `Login expired` to indicate login expired, App will automatically re-login retry.
|
|
||||||
* @param page {number}
|
|
||||||
* @param folder {string?} - folder id, null for non-multi-folder
|
|
||||||
* @returns {Promise<{comics: Comic[], maxPage: number}>}
|
|
||||||
*/
|
|
||||||
loadComics: async (page, folder) => {
|
|
||||||
/*
|
|
||||||
```
|
|
||||||
let data = JSON.parse((await Network.get('...')).body)
|
|
||||||
let maxPage = data.maxPage
|
|
||||||
|
|
||||||
function parseComic(comic) {
|
|
||||||
// ...
|
|
||||||
|
|
||||||
return new Comic{
|
|
||||||
id: id,
|
|
||||||
title: title,
|
|
||||||
subTitle: author,
|
|
||||||
cover: cover,
|
|
||||||
tags: tags,
|
|
||||||
description: description
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
comics: data.list.map(parseComic),
|
|
||||||
maxPage: maxPage
|
|
||||||
}
|
|
||||||
```
|
|
||||||
*/
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* load comics with next page token
|
|
||||||
* @param next {string | null} - next page token, null for first page
|
|
||||||
* @param folder {string}
|
|
||||||
* @returns {Promise<{comics: Comic[], next: string?}>}
|
|
||||||
*/
|
|
||||||
loadNext: async (next, folder) => {},
|
|
||||||
/**
|
|
||||||
* If the comic source only allows one comic in one folder, set this to true.
|
|
||||||
*/
|
|
||||||
singleFolderForSingleComic: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// single comic related
|
/// single comic related
|
||||||
comic = {
|
comic = {
|
||||||
/**
|
/**
|
||||||
|
Reference in New Issue
Block a user