mirror of
https://github.com/venera-app/venera-configs.git
synced 2025-09-27 08:27:24 +00:00
491 lines
14 KiB
JavaScript
491 lines
14 KiB
JavaScript
class Zaimanhua extends ComicSource {
|
||
// 基础信息
|
||
name = "再漫画";
|
||
key = "zaimanhua";
|
||
version = "1.0.1";
|
||
minAppVersion = "1.0.0";
|
||
url =
|
||
"https://git.nyne.dev/nyne/venera-configs/raw/branch/main/zaimanhua.js";
|
||
|
||
// 初始化请求头
|
||
init() {
|
||
this.headers = {
|
||
"User-Agent": "Mozilla/5.0 (Linux; Android) Mobile",
|
||
"authorization": `Bearer ${this.loadData("token") || ""}`,
|
||
};
|
||
}
|
||
// 构建 URL
|
||
buildUrl(path) {
|
||
return `https://v4api.zaimanhua.com/app/v1/${path}`;
|
||
}
|
||
|
||
//账户管理
|
||
account = {
|
||
login: async (username, password) => {
|
||
try {
|
||
const encryptedPwd = Convert.hexEncode(
|
||
Convert.md5(Convert.encodeUtf8(password))
|
||
);
|
||
const res = await Network.post(
|
||
"https://account-api.zaimanhua.com/v1/login/passwd",
|
||
{ "Content-Type": "application/x-www-form-urlencoded;charset=utf-8" },
|
||
`username=${username}&passwd=${encryptedPwd}`
|
||
);
|
||
|
||
const data = JSON.parse(res.body);
|
||
if (data.errno !== 0) throw new Error(data.errmsg);
|
||
|
||
this.saveData("token", data.data.user.token);
|
||
this.headers.authorization = `Bearer ${data.data.user.token}`;
|
||
return true;
|
||
} catch (e) {
|
||
UI.showMessage(`登录失败: ${e.message}`);
|
||
throw e;
|
||
}
|
||
},
|
||
logout: () => {
|
||
this.deleteData("token");
|
||
},
|
||
};
|
||
|
||
// 状态检查
|
||
checkResponseStatus(res) {
|
||
if (res.status === 401) {
|
||
throw new Error("登录失效");
|
||
}
|
||
if (res.status !== 200) {
|
||
throw new Error(`请求失败: ${res.status}`);
|
||
}
|
||
}
|
||
|
||
// 漫画解析
|
||
parseComic(comic) {
|
||
// const safeString = (value) => (value || "").toString().trim();
|
||
const safeString = (value) => (value != null ? value.toString() : "");
|
||
const resolveId = () =>
|
||
[comic.comic_id, comic.id].find((id) => id && id !== "0") || "";
|
||
const resolveTags = () =>
|
||
[comic.status, ...safeString(comic.types).split("/")].filter(Boolean);
|
||
const resolveDescription = () => {
|
||
const candidates = [
|
||
comic.description,
|
||
comic.last_update_chapter_name,
|
||
comic.last_name,
|
||
];
|
||
return candidates.find((text) => text) || "";
|
||
};
|
||
|
||
return {
|
||
id: safeString(resolveId()),
|
||
title: comic.title || comic.name,
|
||
subTitle: comic.authors,
|
||
cover: comic.cover,
|
||
tags: resolveTags(),
|
||
description: resolveDescription(),
|
||
};
|
||
}
|
||
|
||
//探索页面
|
||
explore = [
|
||
{
|
||
title: "再漫画 更新",
|
||
type: "multiPageComicList",
|
||
load: async (page) => {
|
||
const res = await Network.get(
|
||
this.buildUrl(`comic/update/list/0/${page}`),
|
||
this.headers
|
||
);
|
||
const data = JSON.parse(res.body).data;
|
||
return {
|
||
comics: data.map((item) => this.parseComic(item)),
|
||
};
|
||
},
|
||
},
|
||
];
|
||
|
||
static categoryParamMap = {
|
||
"全部": "0",
|
||
"冒险": "4",
|
||
"欢乐向": "5",
|
||
"格斗": "6",
|
||
"科幻": "7",
|
||
"爱情": "8",
|
||
"侦探": "9",
|
||
"竞技": "10",
|
||
"魔法": "11",
|
||
"神鬼": "12",
|
||
"校园": "13",
|
||
"惊悚": "14",
|
||
"其他": "16",
|
||
"四格": "17",
|
||
"亲情": "3242",
|
||
"百合": "3243",
|
||
"秀吉": "3244",
|
||
"悬疑": "3245",
|
||
"纯爱": "3246",
|
||
"热血": "3248",
|
||
"泛爱": "3249",
|
||
"历史": "3250",
|
||
"战争": "3251",
|
||
"萌系": "3252",
|
||
"宅系": "3253",
|
||
"治愈": "3254",
|
||
"励志": "3255",
|
||
"武侠": "3324",
|
||
"机战": "3325",
|
||
"音乐舞蹈": "3326",
|
||
"美食": "3327",
|
||
"职场": "3328",
|
||
"西方魔幻": "3365",
|
||
"高清单行": "4459",
|
||
"TS": "4518",
|
||
"东方": "5077",
|
||
"魔幻": "5806",
|
||
"奇幻": "5848",
|
||
"节操": "6219",
|
||
"轻小说": "6316",
|
||
"颜艺": "6437",
|
||
"搞笑": "7568",
|
||
"仙侠": "23388",
|
||
"舰娘": "7900",
|
||
"动画": "13627",
|
||
"AA": "17192",
|
||
"福瑞": "18522",
|
||
"生存": "23323",
|
||
"日常": "23388",
|
||
"画集": "30788",
|
||
"C100": "31137",
|
||
};
|
||
|
||
//分类页面
|
||
category = {
|
||
title: "再漫画",
|
||
parts: [
|
||
{
|
||
name: "排行榜",
|
||
type: "fixed",
|
||
categories: ["日排行", "周排行", "月排行", "总排行"],
|
||
itemType: "category",
|
||
categoryParams: ["0", "1", "2", "3"],
|
||
},
|
||
{
|
||
name: "分类",
|
||
type: "fixed",
|
||
categories: Object.keys(Zaimanhua.categoryParamMap),
|
||
categoryParams: Object.values(Zaimanhua.categoryParamMap),
|
||
itemType: "category",
|
||
},
|
||
],
|
||
};
|
||
|
||
//分类漫画加载
|
||
categoryComics = {
|
||
load: async (category, param, options, page) => {
|
||
if (category.includes("排行")) {
|
||
let res = await Network.get(
|
||
this.buildUrl(
|
||
`comic/rank/list?page=${page}&rank_type=${options}&by_time=${param}`
|
||
),
|
||
this.headers
|
||
);
|
||
return {
|
||
comics: JSON.parse(res.body).data.map((item) =>
|
||
this.parseComic(item)
|
||
),
|
||
maxPage: 10,
|
||
};
|
||
} else {
|
||
param = Zaimanhua.categoryParamMap[category] || "0";
|
||
let res = await Network.get(
|
||
this.buildUrl(
|
||
`comic/filter/list?status=${options[2]}&theme=${param}&zone=${options[3]}&cate=${options[1]}&sortType=${options[0]}&page=${page}&size=20`
|
||
),
|
||
this.headers
|
||
);
|
||
const data = JSON.parse(res.body).data;
|
||
return {
|
||
comics: data.comicList.map((item) => this.parseComic(item)),
|
||
maxPage: Math.ceil(data.totalNum / 20),
|
||
};
|
||
}
|
||
},
|
||
optionList: [
|
||
{
|
||
options: ["1-更新", "2-人气"],
|
||
notShowWhen: null,
|
||
showWhen: Object.keys(Zaimanhua.categoryParamMap),
|
||
},
|
||
{
|
||
options: [
|
||
"0-全部",
|
||
"3262-少年漫画",
|
||
"3263-少女漫画",
|
||
"3264-青年漫画",
|
||
"13626-女青漫画",
|
||
],
|
||
notShowWhen: null,
|
||
showWhen: Object.keys(Zaimanhua.categoryParamMap),
|
||
},
|
||
{
|
||
options: ["0-全部", "2309-连载中", "2310-已完结", "29205-短篇"],
|
||
notShowWhen: null,
|
||
showWhen: Object.keys(Zaimanhua.categoryParamMap),
|
||
},
|
||
{
|
||
options: [
|
||
"0-全部",
|
||
"2304-日本",
|
||
"2305-韩国",
|
||
"2306-欧美",
|
||
"2307-港台",
|
||
"2308-内地",
|
||
"8435-其他",
|
||
],
|
||
notShowWhen: null,
|
||
showWhen: Object.keys(Zaimanhua.categoryParamMap),
|
||
},
|
||
{
|
||
options: ["0-人气", "1-吐槽", "2-订阅"],
|
||
notshowWhen: null,
|
||
showWhen: ["日排行", "周排行", "月排行", "总排行"],
|
||
},
|
||
],
|
||
};
|
||
|
||
//搜索
|
||
search = {
|
||
load: async (keyword, options, page) => {
|
||
const res = await Network.get(
|
||
this.buildUrl(
|
||
`search/index?keyword=${encodeURIComponent(
|
||
keyword
|
||
)}&page=${page}&sort=0&size=20`
|
||
),
|
||
this.headers
|
||
);
|
||
const data = JSON.parse(res.body).data.list;
|
||
return {
|
||
comics: data.map((item) => this.parseComic(item)),
|
||
};
|
||
},
|
||
optionList: [],
|
||
};
|
||
|
||
//收藏
|
||
favorites = {
|
||
multiFolder: false,
|
||
addOrDelFavorite: async (comicId, folderId, isAdding) => {
|
||
const path = isAdding ? "add" : "del";
|
||
const res = await Network.get(
|
||
this.buildUrl(`comic/sub/${path}?comic_id=${comicId}`),
|
||
this.headers
|
||
);
|
||
const data = JSON.parse(res.body);
|
||
if (data.errno !== 0) {
|
||
throw new Error(data.errmsg || "操作失败");
|
||
}
|
||
return "ok";
|
||
},
|
||
loadComics: async (page) => {
|
||
try {
|
||
const res = await Network.get(
|
||
this.buildUrl(`comic/sub/list?status=0&page=${page}&size=20`),
|
||
this.headers
|
||
);
|
||
const data = JSON.parse(res.body).data;
|
||
return {
|
||
comics: data.subList.map((item) => this.parseComic(item)) ?? [],
|
||
maxPage: Math.ceil(data.total / 20),
|
||
};
|
||
} catch (e) {
|
||
console.error("加载收藏失败:", e);
|
||
return { comics: [], maxPage: null };
|
||
}
|
||
},
|
||
};
|
||
|
||
// 时间戳转换
|
||
formatTimestamp(ts) {
|
||
const date = new Date(ts * 1000);
|
||
return date.toISOString().split("T")[0];
|
||
}
|
||
|
||
//漫画详情
|
||
comic = {
|
||
loadInfo: async (id) => {
|
||
const getFavoriteStatus = async (id) => {
|
||
let res = await Network.get(
|
||
this.buildUrl(`comic/sub/checkIsSub?objId=${id}&source=1`),
|
||
this.headers
|
||
);
|
||
this.checkResponseStatus(res);
|
||
return JSON.parse(res.body).data.isSub;
|
||
};
|
||
let results = await Promise.all([
|
||
Network.get(
|
||
this.buildUrl(`comic/detail/${id}?channel=android`),
|
||
this.headers
|
||
),
|
||
getFavoriteStatus.bind(this)(id),
|
||
]);
|
||
const response = JSON.parse(results[0].body);
|
||
if (response.errno !== 0) throw new Error(response.errmsg || "加载失败");
|
||
const data = response.data.data;
|
||
|
||
function processChapters(groups) {
|
||
return (groups || []).reduce((result, group) => {
|
||
const groupTitle = group.title || "默认";
|
||
const chapters = (group.data || [])
|
||
.reverse()
|
||
.map((ch) => [
|
||
String(ch.chapter_id),
|
||
`${ch.chapter_title.replace(
|
||
/^(?:连载版?)?(\d+\.?\d*)([话卷])?$/,
|
||
(_, n, t) => `第${n}${t || "话"}`
|
||
)}`,
|
||
]);
|
||
result.set(groupTitle, new Map(chapters));
|
||
return result;
|
||
}, new Map());
|
||
}
|
||
// 分类标签
|
||
const { authors, status, types } = data;
|
||
const tagMapper = (arr) => arr.map((t) => t.tag_name);
|
||
return {
|
||
title: data.title,
|
||
cover: data.cover,
|
||
description: data.description,
|
||
tags: {
|
||
"作者": tagMapper(authors),
|
||
"状态": [...tagMapper(status), data.last_update_chapter_name],
|
||
"标签": tagMapper(types),
|
||
},
|
||
updateTime: this.formatTimestamp(data.last_updatetime),
|
||
chapters: processChapters(data.chapters),
|
||
isFavorite: results[1],
|
||
subId: id,
|
||
};
|
||
},
|
||
loadEp: async (comicId, epId) => {
|
||
const res = await Network.get(
|
||
this.buildUrl(`comic/chapter/${comicId}/${epId}`)
|
||
);
|
||
const data = JSON.parse(res.body).data.data;
|
||
return { images: data.page_url_hd || data.page_url };
|
||
},
|
||
|
||
loadComments: async (comicId, subId, page, replyTo) => {
|
||
try {
|
||
// 构建请求URL
|
||
const url = this.buildUrl(
|
||
`comment/list?page=${page}&size=30&type=4&objId=${
|
||
subId || comicId
|
||
}&sortBy=1`
|
||
);
|
||
const res = await Network.get(url, this.headers);
|
||
this.checkResponseStatus(res);
|
||
|
||
const response = JSON.parse(res.body);
|
||
const data = response.data;
|
||
|
||
/* 空数据检查 */
|
||
if (!data || !data.commentIdList || !data.commentList) {
|
||
UI.showMessage("暂时没有评论,快来发表第一条吧~");
|
||
return { comments: [], maxPage: 0 };
|
||
}
|
||
|
||
/* 处理评论ID列表 */
|
||
// 标准化ID数组:处理null/字符串/数组等多种情况
|
||
const rawIds = Array.isArray(data.commentIdList)
|
||
? data.commentIdList
|
||
: [];
|
||
|
||
// 展开所有ID并过滤无效值
|
||
const allCommentIds = rawIds
|
||
.map((idStr) => `${idStr || ""}`.split(",")) // 转换为字符串再分割
|
||
.flat()
|
||
.filter((id) => id.trim() !== "");
|
||
|
||
// 最终ID处理流程
|
||
const processComments = () => {
|
||
// 去重并验证ID有效性
|
||
const validIds = [...new Set(allCommentIds)].filter((id) =>
|
||
data.commentList.hasOwnProperty(id)
|
||
);
|
||
|
||
// 过滤回复评论
|
||
const filteredIds = replyTo
|
||
? validIds.filter(
|
||
(id) => data.commentList[id]?.to_comment_id == replyTo
|
||
)
|
||
: validIds;
|
||
|
||
// 转换为评论对象
|
||
return filteredIds.map((id) => {
|
||
const comment = data.commentList[id];
|
||
return new Comment({
|
||
userName: comment.nickname || "匿名用户",
|
||
avatar: comment.photo || "",
|
||
content: comment.content || "[内容已删除]",
|
||
time: this.formatTimestamp(comment.create_time),
|
||
replyCount: comment.reply_amount || 0,
|
||
score: comment.like_amount || 0,
|
||
id: String(id),
|
||
parentId: comment.to_comment_id || null,
|
||
});
|
||
});
|
||
};
|
||
|
||
// 当没有有效评论时显示提示
|
||
const comments = processComments();
|
||
if (comments.length === 0) {
|
||
UI.showMessage(replyTo ? "该评论暂无回复" : "这里还没有评论哦~");
|
||
}
|
||
|
||
return {
|
||
comments: comments,
|
||
maxPage: Math.ceil((data.total || 0) / 30),
|
||
};
|
||
} catch (e) {
|
||
console.error("评论加载失败:", e);
|
||
UI.showMessage(`加载评论失败: ${e.message}`);
|
||
return { comments: [], maxPage: 0 };
|
||
}
|
||
},
|
||
|
||
// 发送评论, 返回任意值表示成功.
|
||
sendComment: async (comicId, subId, content, replyTo) => {
|
||
if (!replyTo) {
|
||
replyTo = 0;
|
||
}
|
||
let res = await Network.post(
|
||
this.buildUrl(`comment/add`),
|
||
{
|
||
...this.headers,
|
||
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
|
||
},
|
||
`obj_id=${subId}&content=${encodeURIComponent(
|
||
content
|
||
)}&to_comment_id=${replyTo}&type=4`
|
||
);
|
||
this.checkResponseStatus(res);
|
||
let response = JSON.parse(res.body);
|
||
if (response.errno !== 0) throw new Error(response.errmsg || "加载失败");
|
||
return "ok";
|
||
},
|
||
// 点赞
|
||
likeComment: async (comicId, subId, commentId, isLike) => {
|
||
let res = await Network.post(
|
||
this.buildUrl(`comment/addLike`),
|
||
{
|
||
...this.headers,
|
||
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
|
||
},
|
||
`commentId=${commentId}&type=4`
|
||
);
|
||
this.checkResponseStatus(res);
|
||
return "ok";
|
||
},
|
||
};
|
||
}
|