Files
venera-configs/zaimanhua.js
lost one 714353cf64 update zaimanhua and ikmmh (#127)
* 显示收藏状态

* 新增 再漫画

#48

* 更新 ikmmh.js

* 更新 zaimanhua.js

* Update index.json
2025-08-16 18:51:42 +08:00

491 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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";
},
};
}