mirror of
https://github.com/venera-app/venera-configs.git
synced 2025-09-27 00:27:23 +00:00
Compare commits
4 Commits
2b8c532817
...
170eb738b9
Author | SHA1 | Date | |
---|---|---|---|
![]() |
170eb738b9 | ||
![]() |
0d2ec4a85a | ||
![]() |
714353cf64 | ||
![]() |
65bb0d244d |
@@ -995,7 +995,7 @@ class Hitomi extends ComicSource {
|
|||||||
// unique id of the source
|
// unique id of the source
|
||||||
key = "hitomi";
|
key = "hitomi";
|
||||||
|
|
||||||
version = "1.1.0";
|
version = "1.1.1";
|
||||||
|
|
||||||
minAppVersion = "1.4.6";
|
minAppVersion = "1.4.6";
|
||||||
|
|
||||||
@@ -1523,10 +1523,10 @@ class Hitomi extends ComicSource {
|
|||||||
const data = await get_gallery_detail(id);
|
const data = await get_gallery_detail(id);
|
||||||
|
|
||||||
const tags = new Map();
|
const tags = new Map();
|
||||||
if ("type" in data) tags.set("type", [data.type]);
|
if ("type" in data && data.type) tags.set("type", [data.type]);
|
||||||
if (data.groups.length) tags.set("groups", data.groups);
|
if (data.groups.length) tags.set("groups", data.groups);
|
||||||
if (data.artists.length) tags.set("artists", data.artists);
|
if (data.artists.length) tags.set("artists", data.artists);
|
||||||
if ("language" in data) tags.set("language", [data.language]);
|
if ("language" in data && data.language) tags.set("language", [data.language]);
|
||||||
if (data.series.length) tags.set("series", data.series);
|
if (data.series.length) tags.set("series", data.series);
|
||||||
if (data.characters.length) tags.set("characters", data.characters);
|
if (data.characters.length) tags.set("characters", data.characters);
|
||||||
if (data.females.length) tags.set("females", data.females);
|
if (data.females.length) tags.set("females", data.females);
|
||||||
|
892
ikmmh.js
892
ikmmh.js
@@ -1,452 +1,440 @@
|
|||||||
class Ikm extends ComicSource {
|
class Ikm extends ComicSource {
|
||||||
// 基础配置
|
// 基础配置
|
||||||
name = "爱看漫";
|
name = "爱看漫";
|
||||||
key = "ikmmh";
|
key = "ikmmh";
|
||||||
version = "1.0.3";
|
version = "1.0.4";
|
||||||
minAppVersion = "1.0.0";
|
minAppVersion = "1.0.0";
|
||||||
url = "https://git.nyne.dev/nyne/venera-configs/raw/branch/main/ikmmh.js";
|
url = "https://git.nyne.dev/nyne/venera-configs/raw/branch/main/ikmmh.js";
|
||||||
// 常量定义
|
// 常量定义
|
||||||
static baseUrl = "https://ymcdnyfqdapp.ikmmh.com";
|
static baseUrl = "https://ymcdnyfqdapp.ikmmh.com";
|
||||||
static Mobile_UA = "Mozilla/5.0 (Linux; Android) Mobile";
|
static Mobile_UA = "Mozilla/5.0 (Linux; Android) Mobile";
|
||||||
static webHeaders = {
|
static webHeaders = {
|
||||||
"User-Agent": Ikm.Mobile_UA,
|
"User-Agent": Ikm.Mobile_UA,
|
||||||
Accept:
|
"Accept":
|
||||||
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
||||||
};
|
};
|
||||||
static jsonHead = {
|
static jsonHead = {
|
||||||
"User-Agent": Ikm.Mobile_UA,
|
"User-Agent": Ikm.Mobile_UA,
|
||||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||||
Accept: "application/json, text/javascript, */*; q=0.01",
|
"Accept": "application/json, text/javascript, */*; q=0.01",
|
||||||
"Accept-Encoding": "gzip",
|
"Accept-Encoding": "gzip",
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
};
|
};
|
||||||
// 统一缩略图加载配置
|
// 统一缩略图加载配置
|
||||||
static thumbConfig = (url) => ({
|
static thumbConfig = (url) => ({
|
||||||
headers: {
|
headers: {
|
||||||
...Ikm.webHeaders,
|
...Ikm.webHeaders,
|
||||||
Referer: Ikm.baseUrl,
|
"referer": Ikm.baseUrl,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
// 账号系统
|
// 账号系统
|
||||||
account = {
|
account = {
|
||||||
login: async (account, pwd) => {
|
login: async (account, pwd) => {
|
||||||
try {
|
try {
|
||||||
let res = await Network.post(
|
let res = await Network.post(
|
||||||
`${Ikm.baseUrl}/api/user/userarr/login`,
|
`${Ikm.baseUrl}/api/user/userarr/login`,
|
||||||
Ikm.jsonHead,
|
Ikm.jsonHead,
|
||||||
`user=${account}&pass=${pwd}`
|
`user=${account}&pass=${pwd}`
|
||||||
);
|
);
|
||||||
if (res.status !== 200)
|
if (res.status !== 200)
|
||||||
throw new Error(`登录失败,状态码:${res.status}`);
|
throw new Error(`登录失败,状态码:${res.status}`);
|
||||||
let data = JSON.parse(res.body);
|
let data = JSON.parse(res.body);
|
||||||
if (data.code !== 0) throw new Error(data.msg || "登录异常");
|
if (data.code !== 0) throw new Error(data.msg || "登录异常");
|
||||||
return "ok";
|
return "ok";
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new Error(`登录失败:${err.message}`);
|
throw new Error(`登录失败:${err.message}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
logout: () => Network.deleteCookies("ymcdnyfqdapp.ikmmh.com"),
|
logout: () => Network.deleteCookies("ymcdnyfqdapp.ikmmh.com"),
|
||||||
registerWebsite: `${Ikm.baseUrl}/user/register/`,
|
registerWebsite: `${Ikm.baseUrl}/user/register/`,
|
||||||
};
|
};
|
||||||
// 探索页面
|
// 探索页面
|
||||||
explore = [
|
explore = [
|
||||||
{
|
{
|
||||||
title: this.name,
|
title: this.name,
|
||||||
type: "singlePageWithMultiPart",
|
type: "singlePageWithMultiPart",
|
||||||
load: async () => {
|
load: async () => {
|
||||||
try {
|
try {
|
||||||
let res = await Network.get(`${Ikm.baseUrl}/`, Ikm.webHeaders);
|
let res = await Network.get(`${Ikm.baseUrl}/`, Ikm.webHeaders);
|
||||||
if (res.status !== 200)
|
if (res.status !== 200)
|
||||||
throw new Error(`加载探索页面失败,状态码:${res.status}`);
|
throw new Error(`加载探索页面失败,状态码:${res.status}`);
|
||||||
let document = new HtmlDocument(res.body);
|
let document = new HtmlDocument(res.body);
|
||||||
let parseComic = (e) => {
|
let parseComic = (e) => {
|
||||||
let title = e.querySelector("div.title").text.split("~")[0];
|
let title = e.querySelector("div.title").text.split("~")[0];
|
||||||
let cover = e.querySelector("div.thumb_img").attributes["data-src"];
|
let cover = e.querySelector("div.thumb_img").attributes["data-src"];
|
||||||
let link = `${Ikm.baseUrl}${
|
let link = `${Ikm.baseUrl}${
|
||||||
e.querySelector("a").attributes["href"]
|
e.querySelector("a").attributes["href"]
|
||||||
}`;
|
}`;
|
||||||
return {
|
return {
|
||||||
title,
|
title,
|
||||||
cover,
|
cover,
|
||||||
id: link,
|
id: link,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
本周推荐: document
|
"本周推荐": document
|
||||||
.querySelectorAll("div.module-good-fir > div.item")
|
.querySelectorAll("div.module-good-fir > div.item")
|
||||||
.map(parseComic),
|
.map(parseComic),
|
||||||
今日更新: document
|
"今日更新": document
|
||||||
.querySelectorAll("div.module-day-fir > div.item")
|
.querySelectorAll("div.module-day-fir > div.item")
|
||||||
.map(parseComic),
|
.map(parseComic),
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new Error(`探索页面加载失败:${err.message}`);
|
throw new Error(`探索页面加载失败:${err.message}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onThumbnailLoad: Ikm.thumbConfig,
|
onThumbnailLoad: Ikm.thumbConfig,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
// 分类页面
|
// 分类页面
|
||||||
category = {
|
category = {
|
||||||
title: "爱看漫",
|
title: "爱看漫",
|
||||||
parts: [
|
parts: [
|
||||||
{
|
{
|
||||||
name: "分类",
|
name: "更新",
|
||||||
// fixed 或者 random
|
type: "fixed",
|
||||||
// random用于分类数量相当多时, 随机显示其中一部分
|
categories: [
|
||||||
type: "fixed",
|
"星期一",
|
||||||
// 如果类型为random, 需要提供此字段, 表示同时显示的数量
|
"星期二",
|
||||||
// randomNumber: 5,
|
"星期三",
|
||||||
categories: [
|
"星期四",
|
||||||
"全部",
|
"星期五",
|
||||||
"长条",
|
"星期六",
|
||||||
"大女主",
|
"星期日",
|
||||||
"百合",
|
],
|
||||||
"耽美",
|
itemType: "category",
|
||||||
"纯爱",
|
categoryParams: ["1", "2", "3", "4", "5", "6", "7"],
|
||||||
"後宫",
|
},
|
||||||
"韩漫",
|
{
|
||||||
"奇幻",
|
name: "分类",
|
||||||
"轻小说",
|
// fixed 或者 random
|
||||||
"生活",
|
// random用于分类数量相当多时, 随机显示其中一部分
|
||||||
"悬疑",
|
type: "fixed",
|
||||||
"格斗",
|
// 如果类型为random, 需要提供此字段, 表示同时显示的数量
|
||||||
"搞笑",
|
// randomNumber: 5,
|
||||||
"伪娘",
|
categories: [
|
||||||
"竞技",
|
"全部",
|
||||||
"职场",
|
"长条",
|
||||||
"萌系",
|
"大女主",
|
||||||
"冒险",
|
"百合",
|
||||||
"治愈",
|
"耽美",
|
||||||
"都市",
|
"纯爱",
|
||||||
"霸总",
|
"後宫",
|
||||||
"神鬼",
|
"韩漫",
|
||||||
"侦探",
|
"奇幻",
|
||||||
"爱情",
|
"轻小说",
|
||||||
"古风",
|
"生活",
|
||||||
"欢乐向",
|
"悬疑",
|
||||||
"科幻",
|
"格斗",
|
||||||
"穿越",
|
"搞笑",
|
||||||
"性转换",
|
"伪娘",
|
||||||
"校园",
|
"竞技",
|
||||||
"美食",
|
"职场",
|
||||||
"悬疑",
|
"萌系",
|
||||||
"剧情",
|
"冒险",
|
||||||
"热血",
|
"治愈",
|
||||||
"节操",
|
"都市",
|
||||||
"励志",
|
"霸总",
|
||||||
"异世界",
|
"神鬼",
|
||||||
"历史",
|
"侦探",
|
||||||
"战争",
|
"爱情",
|
||||||
"恐怖",
|
"古风",
|
||||||
"霸总",
|
"欢乐向",
|
||||||
"全部",
|
"科幻",
|
||||||
"连载中",
|
"穿越",
|
||||||
"已完结",
|
"性转换",
|
||||||
"全部",
|
"校园",
|
||||||
"日漫",
|
"美食",
|
||||||
"港台",
|
"悬疑",
|
||||||
"美漫",
|
"剧情",
|
||||||
"国漫",
|
"热血",
|
||||||
"韩漫",
|
"节操",
|
||||||
"未分类",
|
"励志",
|
||||||
],
|
"异世界",
|
||||||
// category或者search
|
"历史",
|
||||||
// 如果为category, 点击后将进入分类漫画页面, 使用下方的`categoryComics`加载漫画
|
"战争",
|
||||||
// 如果为search, 将进入搜索页面
|
"恐怖",
|
||||||
itemType: "category",
|
"霸总"
|
||||||
},
|
],
|
||||||
{
|
// category或者search
|
||||||
name: "更新",
|
// 如果为category, 点击后将进入分类漫画页面, 使用下方的`categoryComics`加载漫画
|
||||||
type: "fixed",
|
// 如果为search, 将进入搜索页面
|
||||||
categories: [
|
itemType: "category",
|
||||||
"星期一",
|
}
|
||||||
"星期二",
|
],
|
||||||
"星期三",
|
enableRankingPage: false,
|
||||||
"星期四",
|
};
|
||||||
"星期五",
|
// 分类漫画加载
|
||||||
"星期六",
|
categoryComics = {
|
||||||
"星期日",
|
load: async (category, param, options, page) => {
|
||||||
],
|
try {
|
||||||
itemType: "category",
|
let res;
|
||||||
categoryParams: ["1", "2", "3", "4", "5", "6", "7"],
|
if (param) {
|
||||||
},
|
res = await Network.get(
|
||||||
],
|
`${Ikm.baseUrl}/update/${param}.html`,
|
||||||
enableRankingPage: false,
|
Ikm.webHeaders
|
||||||
};
|
);
|
||||||
// 分类漫画加载
|
if (res.status !== 200)
|
||||||
categoryComics = {
|
throw new Error(`分类请求失败,状态码:${res.status}`);
|
||||||
load: async (category, param, options, page) => {
|
let document = new HtmlDocument(res.body);
|
||||||
try {
|
let comics = document.querySelectorAll("li.comic-item").map((e) => ({
|
||||||
let res;
|
title: e.querySelector("p.title").text.split("~")[0],
|
||||||
if (param) {
|
cover: e.querySelector("img").attributes["src"],
|
||||||
res = await Network.get(
|
id: `${Ikm.baseUrl}${e.querySelector("a").attributes["href"]}`,
|
||||||
`${Ikm.baseUrl}/update/${param}.html`,
|
subTitle: e.querySelector("span.chapter").text,
|
||||||
Ikm.webHeaders
|
}));
|
||||||
);
|
return {
|
||||||
if (res.status !== 200)
|
comics,
|
||||||
throw new Error(`分类请求失败,状态码:${res.status}`);
|
maxPage: 1
|
||||||
let document = new HtmlDocument(res.body);
|
};
|
||||||
let comics = document.querySelectorAll("li.comic-item").map((e) => ({
|
} else {
|
||||||
title: e.querySelector("p.title").text.split("~")[0],
|
res = await Network.post(
|
||||||
cover: e.querySelector("img").attributes["src"],
|
`${Ikm.baseUrl}/api/comic/index/lists`,
|
||||||
id: `${Ikm.baseUrl}${e.querySelector("a").attributes["href"]}`,
|
Ikm.jsonHead,
|
||||||
subTitle: e.querySelector("span.chapter").text,
|
`area=${options[1]}&tags=${encodeURIComponent(category)}&full=${
|
||||||
}));
|
options[0]
|
||||||
return {
|
}&page=${page}`
|
||||||
comics,
|
);
|
||||||
maxPage: 1,
|
let resData = JSON.parse(res.body);
|
||||||
};
|
return {
|
||||||
} else {
|
comics: resData.data.map((e) => ({
|
||||||
res = await Network.post(
|
id: `${Ikm.baseUrl}${e.info_url}`,
|
||||||
`${Ikm.baseUrl}/api/comic/index/lists`,
|
title: e.name.split("~")[0],
|
||||||
Ikm.jsonHead,
|
subTitle: e.author,
|
||||||
`area=${options[1]}&tags=${encodeURIComponent(category)}&full=${
|
cover: e.cover,
|
||||||
options[0]
|
tags: e.tags,
|
||||||
}&page=${page}`
|
description: e.lastchapter,
|
||||||
);
|
})),
|
||||||
let resData = JSON.parse(res.body);
|
maxPage: resData.end || 1,
|
||||||
return {
|
};
|
||||||
comics: resData.data.map((e) => ({
|
}
|
||||||
id: `${Ikm.baseUrl}${e.info_url}`,
|
} catch (err) {
|
||||||
title: e.name.split("~")[0],
|
throw new Error(`分类加载失败:${err.message}`);
|
||||||
subTitle: e.author,
|
}
|
||||||
cover: e.cover,
|
},
|
||||||
tags: e.tags,
|
onThumbnailLoad: Ikm.thumbConfig,
|
||||||
description: e.lastchapter,
|
optionList: [
|
||||||
})),
|
{
|
||||||
maxPage: resData.end || 1,
|
// 对于单个选项, 使用-分割, 左侧为用于数据加载的值, 即传给load函数的options参数; 右侧为显示给用户的文本
|
||||||
};
|
|
||||||
}
|
options: ["3-全部", "4-连载中", "1-已完结"],
|
||||||
} catch (err) {
|
notShowWhen: [
|
||||||
throw new Error(`分类加载失败:${err.message}`);
|
"星期一",
|
||||||
}
|
"星期二",
|
||||||
},
|
"星期三",
|
||||||
onThumbnailLoad: Ikm.thumbConfig,
|
"星期四",
|
||||||
optionList: [
|
"星期五",
|
||||||
{
|
"星期六",
|
||||||
// 对于单个选项, 使用-分割, 左侧为用于数据加载的值, 即传给load函数的options参数; 右侧为显示给用户的文本
|
"星期日",
|
||||||
|
],
|
||||||
options: ["3-全部", "4-连载中", "1-已完结"],
|
showWhen: null,
|
||||||
notShowWhen: [
|
},
|
||||||
"星期一",
|
{
|
||||||
"星期二",
|
options: [
|
||||||
"星期三",
|
"9-全部",
|
||||||
"星期四",
|
"1-日漫",
|
||||||
"星期五",
|
"2-港台",
|
||||||
"星期六",
|
"3-美漫",
|
||||||
"星期日",
|
"4-国漫",
|
||||||
],
|
"5-韩漫",
|
||||||
showWhen: null,
|
"6-未分类",
|
||||||
},
|
],
|
||||||
{
|
notShowWhen: [
|
||||||
options: [
|
"星期一",
|
||||||
"9-全部",
|
"星期二",
|
||||||
"1-日漫",
|
"星期三",
|
||||||
"2-港台",
|
"星期四",
|
||||||
"3-美漫",
|
"星期五",
|
||||||
"4-国漫",
|
"星期六",
|
||||||
"5-韩漫",
|
"星期日",
|
||||||
"6-未分类",
|
],
|
||||||
],
|
showWhen: null,
|
||||||
notShowWhen: [
|
},
|
||||||
"星期一",
|
],
|
||||||
"星期二",
|
};
|
||||||
"星期三",
|
// 搜索功能
|
||||||
"星期四",
|
search = {
|
||||||
"星期五",
|
load: async (keyword, options, page) => {
|
||||||
"星期六",
|
try {
|
||||||
"星期日",
|
let res = await Network.get(
|
||||||
],
|
`${Ikm.baseUrl}/search?searchkey=${encodeURIComponent(keyword)}`,
|
||||||
showWhen: null,
|
Ikm.webHeaders
|
||||||
},
|
);
|
||||||
],
|
let document = new HtmlDocument(res.body);
|
||||||
};
|
return {
|
||||||
// 搜索功能
|
comics: document.querySelectorAll("li.comic-item").map((e) => ({
|
||||||
search = {
|
title: e.querySelector("p.title").text.split("~")[0],
|
||||||
load: async (keyword, options, page) => {
|
cover: e.querySelector("img").attributes["src"],
|
||||||
try {
|
id: `${Ikm.baseUrl}${e.querySelector("a").attributes["href"]}`,
|
||||||
let res = await Network.get(
|
subTitle: e.querySelector("span.chapter").text,
|
||||||
`${Ikm.baseUrl}/search?searchkey=${encodeURIComponent(keyword)}`,
|
})),
|
||||||
Ikm.webHeaders
|
maxPage: 1,
|
||||||
);
|
};
|
||||||
let document = new HtmlDocument(res.body);
|
} catch (err) {
|
||||||
return {
|
throw new Error(`搜索失败:${err.message}`);
|
||||||
comics: document.querySelectorAll("li.comic-item").map((e) => ({
|
}
|
||||||
title: e.querySelector("p.title").text.split("~")[0],
|
},
|
||||||
cover: e.querySelector("img").attributes["src"],
|
onThumbnailLoad: Ikm.thumbConfig,
|
||||||
id: `${Ikm.baseUrl}${e.querySelector("a").attributes["href"]}`,
|
optionList: [],
|
||||||
subTitle: e.querySelector("span.chapter").text,
|
};
|
||||||
})),
|
// 收藏功能
|
||||||
maxPage: 1,
|
favorites = {
|
||||||
};
|
multiFolder: false,
|
||||||
} catch (err) {
|
addOrDelFavorite: async (comicId, folderId, isAdding) => {
|
||||||
throw new Error(`搜索失败:${err.message}`);
|
try {
|
||||||
}
|
let id = comicId.match(/\d+/)[0];
|
||||||
},
|
if (isAdding) {
|
||||||
onThumbnailLoad: Ikm.thumbConfig,
|
// 获取漫画信息
|
||||||
optionList: [],
|
let infoRes = await Network.get(comicId, Ikm.webHeaders);
|
||||||
};
|
let name = new HtmlDocument(infoRes.body).querySelector(
|
||||||
// 收藏功能
|
"meta[property='og:title']"
|
||||||
favorites = {
|
).attributes["content"];
|
||||||
multiFolder: false,
|
// 添加收藏
|
||||||
addOrDelFavorite: async (comicId, folderId, isAdding) => {
|
let res = await Network.post(
|
||||||
try {
|
`${Ikm.baseUrl}/api/user/bookcase/add`,
|
||||||
let id = comicId.match(/\d+/)[0];
|
Ikm.jsonHead,
|
||||||
if (isAdding) {
|
`articleid=${id}&articlename=${encodeURIComponent(name)}`
|
||||||
// 获取漫画信息
|
);
|
||||||
let infoRes = await Network.get(comicId, Ikm.webHeaders);
|
let data = JSON.parse(res.body);
|
||||||
let name = new HtmlDocument(infoRes.body).querySelector(
|
if (data.code !== "0") throw new Error(data.msg || "收藏失败");
|
||||||
"meta[property='og:title']"
|
return "ok";
|
||||||
).attributes["content"];
|
} else {
|
||||||
// 添加收藏
|
// 删除收藏
|
||||||
let res = await Network.post(
|
let res = await Network.post(
|
||||||
`${Ikm.baseUrl}/api/user/bookcase/add`,
|
`${Ikm.baseUrl}/api/user/bookcase/del`,
|
||||||
Ikm.jsonHead,
|
Ikm.jsonHead,
|
||||||
`articleid=${id}&articlename=${encodeURIComponent(name)}`
|
`articleid=${id}`
|
||||||
);
|
);
|
||||||
let data = JSON.parse(res.body);
|
let data = JSON.parse(res.body);
|
||||||
if (data.code !== "0") throw new Error(data.msg || "收藏失败");
|
if (data.code !== "0") throw new Error(data.msg || "取消收藏失败");
|
||||||
return "ok";
|
return "ok";
|
||||||
} else {
|
}
|
||||||
// 删除收藏
|
} catch (err) {
|
||||||
let res = await Network.post(
|
throw new Error(`收藏操作失败:${err.message}`);
|
||||||
`${Ikm.baseUrl}/api/user/bookcase/del`,
|
}
|
||||||
Ikm.jsonHead,
|
},
|
||||||
`articleid=${id}`
|
//加载收藏
|
||||||
);
|
loadComics: async (page, folder) => {
|
||||||
let data = JSON.parse(res.body);
|
let res = await Network.get(
|
||||||
if (data.code !== "0") throw new Error(data.msg || "取消收藏失败");
|
`${Ikm.baseUrl}/user/bookcase`,
|
||||||
return "ok";
|
Ikm.webHeaders
|
||||||
}
|
);
|
||||||
} catch (err) {
|
if (res.status !== 200) {
|
||||||
throw new Error(`收藏操作失败:${err.message}`);
|
throw "加载收藏失败:" + res.status;
|
||||||
}
|
}
|
||||||
},
|
let document = new HtmlDocument(res.body);
|
||||||
//加载收藏
|
return {
|
||||||
loadComics: async (page, folder) => {
|
comics: document.querySelectorAll("div.bookrack-item").map((e) => ({
|
||||||
let res = await Network.get(
|
title: e.querySelector("h3").text.split("~")[0],
|
||||||
`${Ikm.baseUrl}/user/bookcase`,
|
subTitle: e.querySelector("p.desc").text,
|
||||||
Ikm.webHeaders
|
cover: e.querySelector("img").attributes["src"],
|
||||||
);
|
id: `${Ikm.baseUrl}/book/${e.attributes["data-id"]}/`,
|
||||||
if (res.status !== 200) {
|
})),
|
||||||
throw "加载收藏失败:" + res.status;
|
maxPage: 1,
|
||||||
}
|
};
|
||||||
let document = new HtmlDocument(res.body);
|
},
|
||||||
return {
|
onThumbnailLoad: Ikm.thumbConfig,
|
||||||
comics: document.querySelectorAll("div.bookrack-item").map((e) => ({
|
};
|
||||||
title: e.querySelector("h3").text.split("~")[0],
|
// 漫画详情
|
||||||
subTitle: e.querySelector("p.desc").text,
|
comic = {
|
||||||
cover: e.querySelector("img").attributes["src"],
|
loadInfo: async (id) => {
|
||||||
id: `${Ikm.baseUrl}/book/${e.attributes["data-id"]}/`,
|
// 加载收藏页并判断是否收藏
|
||||||
})),
|
let isFavorite = false;
|
||||||
maxPage: 1,
|
try {
|
||||||
};
|
let favorites = await this.favorites.loadComics(1, null);
|
||||||
},
|
isFavorite = favorites.comics.some((comic) => comic.id === id);
|
||||||
onThumbnailLoad: Ikm.thumbConfig,
|
} catch (error) {
|
||||||
};
|
console.error("加载收藏页失败:", error);
|
||||||
// 漫画详情
|
}
|
||||||
comic = {
|
let res = await Network.get(id, Ikm.webHeaders);
|
||||||
loadInfo: async (id) => {
|
let document = new HtmlDocument(res.body);
|
||||||
let res = await Network.get(id, Ikm.webHeaders);
|
let comicId = id.match(/\d+/)[0];
|
||||||
let document = new HtmlDocument(res.body);
|
// 获取章节数据
|
||||||
let comicId = id.match(/\d+/)[0];
|
let epRes = await Network.get(
|
||||||
// 获取章节数据
|
`${Ikm.baseUrl}/api/comic/zyz/chapterlink?id=${comicId}`,
|
||||||
let epRes = await Network.get(
|
{
|
||||||
`${Ikm.baseUrl}/api/comic/zyz/chapterlink?id=${comicId}`,
|
...Ikm.jsonHead,
|
||||||
{
|
"referer": id,
|
||||||
...Ikm.jsonHead,
|
}
|
||||||
Referer: id,
|
);
|
||||||
}
|
let epData = JSON.parse(epRes.body);
|
||||||
);
|
let eps = new Map();
|
||||||
let epData = JSON.parse(epRes.body);
|
if (epData.data && epData.data.length > 0 && epData.data[0].list) {
|
||||||
let eps = new Map();
|
epData.data[0].list.forEach((e) => {
|
||||||
if (epData.data && epData.data.length > 0 && epData.data[0].list) {
|
let title = e.name;
|
||||||
epData.data[0].list.forEach((e) => {
|
let id = `${Ikm.baseUrl}${e.url}`;
|
||||||
let title = e.name;
|
eps.set(id, title);
|
||||||
let id = `${Ikm.baseUrl}${e.url}`;
|
});
|
||||||
eps.set(id, title);
|
} else {
|
||||||
});
|
throw new Error(`章节数据格式异常`);
|
||||||
} else {
|
}
|
||||||
throw new Error(`章节数据格式异常`);
|
|
||||||
}
|
let title = document.querySelector(
|
||||||
|
"div.book-hero__detail > div.title"
|
||||||
let title = document.querySelector(
|
).text;
|
||||||
"div.book-hero__detail > div.title"
|
let escapedTitle = title.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
).text;
|
let thumb =
|
||||||
let escapedTitle = title.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
document
|
||||||
let thumb =
|
.querySelector("div.coverimg")
|
||||||
document
|
.attributes["style"].match(/\((.*?)\)/)?.[1] || "";
|
||||||
.querySelector("div.coverimg")
|
let desc = document
|
||||||
.attributes["style"].match(/\((.*?)\)/)?.[1] || "";
|
.querySelector("article.book-container__detail")
|
||||||
let desc = document
|
.text.match(
|
||||||
.querySelector("article.book-container__detail")
|
new RegExp(
|
||||||
.text.match(
|
`漫画名:${escapedTitle}(?:(?:[^。]*?(?:简介|漫画简介)\\s*[::]?\\s*)|(?:[^。]*?))([\\s\\S]+?)\\.\\.\\.。`
|
||||||
new RegExp(
|
)
|
||||||
`漫画名:${escapedTitle}(?:(?:[^。]*?(?:简介|漫画简介)\\s*[::]?\\s*)|(?:[^。]*?))([\\s\\S]+?)\\.\\.\\.。`
|
);
|
||||||
)
|
let intro = desc?.[1]?.trim().replace(/\s+/g, " ") || "";
|
||||||
);
|
|
||||||
let intro = desc?.[1]?.trim().replace(/\s+/g, " ") || "";
|
return {
|
||||||
|
title: title.split("~")[0],
|
||||||
// 获取更新日期
|
cover: thumb,
|
||||||
let fullDateStr = document
|
description: intro,
|
||||||
.querySelector('meta[property="og:cartoon:update_time"]')
|
tags: {
|
||||||
.attributes["content"]; // "2025-07-18 08:37:02"
|
"作者": [
|
||||||
let date = new Date(fullDateStr);
|
document
|
||||||
let year = date.getFullYear();
|
.querySelector("div.book-container__author")
|
||||||
let month = String(date.getMonth() + 1).padStart(2, "0"); // 月份从0开始,要加1
|
.text.split("作者:")[1],
|
||||||
let day = String(date.getDate()).padStart(2, "0");
|
],
|
||||||
let updateTime = `${year}-${month}-${day}`;
|
"更新": [document.querySelector("div.update > a > em").text],
|
||||||
|
"标签": document
|
||||||
return new ComicDetails({
|
.querySelectorAll("div.book-hero__detail > div.tags > a")
|
||||||
title: title.split("~")[0],
|
.map((e) => e.text.trim())
|
||||||
cover: thumb,
|
.filter((text) => text),
|
||||||
description: intro,
|
},
|
||||||
updateTime: updateTime,
|
chapters: eps,
|
||||||
tags: {
|
recommend: document
|
||||||
作者: [
|
.querySelectorAll("div.module-guessu > div.item")
|
||||||
document
|
.map((e) => ({
|
||||||
.querySelector("div.book-container__author")
|
title: e.querySelector("div.title").text.split("~")[0],
|
||||||
.text.split("作者:")[1],
|
cover: e.querySelector("div.thumb_img").attributes["data-src"],
|
||||||
],
|
id: `${Ikm.baseUrl}${e.querySelector("a").attributes["href"]}`,
|
||||||
最新章节: [document.querySelector("div.update > a > em").text],
|
})),
|
||||||
标签: document
|
isFavorite: isFavorite,
|
||||||
.querySelectorAll("div.book-hero__detail > div.tags > a")
|
};
|
||||||
.map((e) => e.text.trim())
|
},
|
||||||
.filter((text) => text),
|
onThumbnailLoad: Ikm.thumbConfig,
|
||||||
},
|
loadEp: async (comicId, epId) => {
|
||||||
chapters: eps,
|
try {
|
||||||
recommend: document
|
let res = await Network.get(epId, Ikm.webHeaders);
|
||||||
.querySelectorAll("div.module-guessu > div.item")
|
let document = new HtmlDocument(res.body);
|
||||||
.map((e) => ({
|
return {
|
||||||
title: e.querySelector("div.title").text.split("~")[0],
|
images: document
|
||||||
cover: e.querySelector("div.thumb_img").attributes["data-src"],
|
.querySelectorAll("img.lazy")
|
||||||
id: `${Ikm.baseUrl}${e.querySelector("a").attributes["href"]}`,
|
.map((e) => e.attributes["data-src"]),
|
||||||
})),
|
};
|
||||||
});
|
} catch (err) {
|
||||||
},
|
throw new Error(`加载章节失败:${err.message}`);
|
||||||
onThumbnailLoad: Ikm.thumbConfig,
|
}
|
||||||
loadEp: async (comicId, epId) => {
|
},
|
||||||
try {
|
onImageLoad: (url, comicId, epId) => {
|
||||||
let res = await Network.get(epId, Ikm.webHeaders);
|
return {
|
||||||
let document = new HtmlDocument(res.body);
|
url,
|
||||||
return {
|
headers: {
|
||||||
images: document
|
...Ikm.webHeaders,
|
||||||
.querySelectorAll("img.lazy")
|
"referer": epId,
|
||||||
.map((e) => e.attributes["data-src"]),
|
},
|
||||||
};
|
};
|
||||||
} catch (err) {
|
},
|
||||||
throw new Error(`加载章节失败:${err.message}`);
|
};
|
||||||
}
|
}
|
||||||
},
|
|
||||||
onImageLoad: (url, comicId, epId) => {
|
|
||||||
return {
|
|
||||||
url,
|
|
||||||
headers: {
|
|
||||||
...Ikm.webHeaders,
|
|
||||||
Referer: epId,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
14
index.json
14
index.json
@@ -21,7 +21,7 @@
|
|||||||
"name": "Picacg",
|
"name": "Picacg",
|
||||||
"fileName": "picacg.js",
|
"fileName": "picacg.js",
|
||||||
"key": "picacg",
|
"key": "picacg",
|
||||||
"version": "1.0.3"
|
"version": "1.0.5"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "nhentai",
|
"name": "nhentai",
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
"name": "爱看漫",
|
"name": "爱看漫",
|
||||||
"fileName": "ikmmh.js",
|
"fileName": "ikmmh.js",
|
||||||
"key": "ikmmh",
|
"key": "ikmmh",
|
||||||
"version": "1.0.3"
|
"version": "1.0.4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "少年ジャンプ+",
|
"name": "少年ジャンプ+",
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
"name": "hitomi.la",
|
"name": "hitomi.la",
|
||||||
"fileName": "hitomi.js",
|
"fileName": "hitomi.js",
|
||||||
"key": "hitomi",
|
"key": "hitomi",
|
||||||
"version": "1.1.0"
|
"version": "1.1.1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "comick",
|
"name": "comick",
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
"name": "再漫画",
|
"name": "再漫画",
|
||||||
"fileName": "zaimanhua.js",
|
"fileName": "zaimanhua.js",
|
||||||
"key": "zaimanhua",
|
"key": "zaimanhua",
|
||||||
"version": "1.0.0"
|
"version": "1.0.1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "漫画柜",
|
"name": "漫画柜",
|
||||||
@@ -109,5 +109,11 @@
|
|||||||
"fileName": "manwaba.js",
|
"fileName": "manwaba.js",
|
||||||
"key": "manwaba",
|
"key": "manwaba",
|
||||||
"version": "1.0.0"
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Lanraragi",
|
||||||
|
"fileName": "lanraragi.js",
|
||||||
|
"key": "lanraragi",
|
||||||
|
"version": "1.0.0"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
275
lanraragi.js
Normal file
275
lanraragi.js
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
/** @type {import('./_venera_.js')} */
|
||||||
|
class Lanraragi extends ComicSource {
|
||||||
|
name = "Lanraragi"
|
||||||
|
key = "lanraragi"
|
||||||
|
version = "1.0.0"
|
||||||
|
minAppVersion = "1.4.0"
|
||||||
|
url = "https://git.nyne.dev/nyne/venera-configs/raw/branch/main/lanraragi.js"
|
||||||
|
|
||||||
|
settings = {
|
||||||
|
api: { title: "API", type: "input", default: "http://lrr.tvc-16.science" }
|
||||||
|
}
|
||||||
|
get baseUrl() {
|
||||||
|
|
||||||
|
const api = this.loadSetting('api') || this.settings.api.default
|
||||||
|
return api.replace(/\/$/, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
try {
|
||||||
|
const url = `${this.baseUrl}/api/categories`
|
||||||
|
const res = await Network.get(url)
|
||||||
|
if (res.status !== 200) { this.saveData('categories', []); return }
|
||||||
|
let data = []
|
||||||
|
try { data = JSON.parse(res.body) } catch (_) { data = [] }
|
||||||
|
if (!Array.isArray(data)) data = []
|
||||||
|
this.saveData('categories', data)
|
||||||
|
this.saveData('categories_ts', Date.now())
|
||||||
|
} catch (_) { this.saveData('categories', []) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// account = {
|
||||||
|
// login: async (account, pwd) => {},
|
||||||
|
// loginWithWebview: { url: "", checkStatus: (url, title) => false, onLoginSuccess: () => {} },
|
||||||
|
// loginWithCookies: { fields: ["ipb_member_id","ipb_pass_hash","igneous","star"], validate: async (values) => false },
|
||||||
|
// logout: () => {},
|
||||||
|
// registerWebsite: null,
|
||||||
|
// }
|
||||||
|
|
||||||
|
explore = [
|
||||||
|
{ title: "Lanraragi", type: "multiPageComicList", load: async (page = 1) => {
|
||||||
|
const url = `${this.baseUrl}/api/archives`
|
||||||
|
const res = await Network.get(url)
|
||||||
|
if (res.status !== 200) throw `Invalid status code: ${res.status}`
|
||||||
|
const data = JSON.parse(res.body)
|
||||||
|
const list = data.slice((page-1)*50, page*50)
|
||||||
|
const parseComic = (item) => {
|
||||||
|
let base = this.baseUrl.replace(/\/$/, '')
|
||||||
|
if (!/^https?:\/\//.test(base)) base = 'http://' + base
|
||||||
|
const cover = `${base}/api/archives/${item.arcid}/thumbnail`
|
||||||
|
return new Comic({ id: item.arcid, title: item.title, subTitle: '', cover, tags: item.tags ? item.tags.split(',').map(t=>t.trim()).filter(Boolean) : [], description: `页数: ${item.pagecount} | 新: ${item.isnew} | 扩展: ${item.extension}` })
|
||||||
|
}
|
||||||
|
return { comics: list.map(parseComic), maxPage: Math.ceil(data.length/50) }
|
||||||
|
}}
|
||||||
|
]
|
||||||
|
|
||||||
|
category = {
|
||||||
|
title: "Lanraragi",
|
||||||
|
parts: [ { name: "ALL", type: "dynamic", loader: () => {
|
||||||
|
const data = this.loadData('categories')
|
||||||
|
if (!Array.isArray(data) || data.length === 0) throw 'Please check your API settings or categories.'
|
||||||
|
const items = []
|
||||||
|
for (const cat of data) {
|
||||||
|
if (!cat) continue
|
||||||
|
const id = cat.id ?? cat._id ?? cat.name
|
||||||
|
const label = cat.name ?? String(id)
|
||||||
|
try { items.push({ label, target: new PageJumpTarget({ page: 'category', attributes: { category: id, param: null } }) }) }
|
||||||
|
catch (_) { items.push({ label, target: { page: 'category', attributes: { category: id, param: null } } }) }
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
} } ],
|
||||||
|
enableRankingPage: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
categoryComics = {
|
||||||
|
load: async (category, param, options, page) => {
|
||||||
|
// Use /search endpoint filtered by category tag value
|
||||||
|
const base = (this.baseUrl || '').replace(/\/$/, '')
|
||||||
|
const pageSize = 100
|
||||||
|
const start = Math.max(0, (page - 1) * pageSize)
|
||||||
|
|
||||||
|
const qp = []
|
||||||
|
const add = (k, v) => qp.push(`${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)
|
||||||
|
add('draw', String(Date.now() % 1000))
|
||||||
|
add('columns[0][data]', '')
|
||||||
|
add('columns[0][name]', 'title')
|
||||||
|
add('columns[0][searchable]', 'true')
|
||||||
|
add('columns[0][orderable]', 'true')
|
||||||
|
add('columns[0][search][value]', '')
|
||||||
|
add('columns[0][search][regex]', 'false')
|
||||||
|
add('columns[1][data]', 'tags')
|
||||||
|
add('columns[1][name]', 'artist')
|
||||||
|
add('columns[1][searchable]', 'true')
|
||||||
|
add('columns[1][orderable]', 'true')
|
||||||
|
add('columns[1][search][value]', '')
|
||||||
|
add('columns[1][search][regex]', 'false')
|
||||||
|
add('columns[2][data]', 'tags')
|
||||||
|
add('columns[2][name]', 'series')
|
||||||
|
add('columns[2][searchable]', 'true')
|
||||||
|
add('columns[2][orderable]', 'true')
|
||||||
|
add('columns[2][search][value]', '')
|
||||||
|
add('columns[2][search][regex]', 'false')
|
||||||
|
add('columns[3][data]', 'tags')
|
||||||
|
add('columns[3][name]', 'tags')
|
||||||
|
add('columns[3][searchable]', 'true')
|
||||||
|
add('columns[3][orderable]', 'false')
|
||||||
|
// Filter by category identifier in tags column
|
||||||
|
add('columns[3][search][value]', category || '')
|
||||||
|
add('columns[3][search][regex]', 'false')
|
||||||
|
add('order[0][column]', '0')
|
||||||
|
add('order[0][dir]', 'asc')
|
||||||
|
add('start', String(start))
|
||||||
|
add('length', String(pageSize))
|
||||||
|
add('search[value]', '')
|
||||||
|
add('search[regex]', 'false')
|
||||||
|
|
||||||
|
const url = `${base}/search?${qp.join('&')}`
|
||||||
|
const res = await Network.get(url)
|
||||||
|
if (res.status !== 200) throw `Invalid status code: ${res.status}`
|
||||||
|
const data = JSON.parse(res.body)
|
||||||
|
const list = Array.isArray(data.data) ? data.data : []
|
||||||
|
const comics = list.map(item => {
|
||||||
|
const cover = `${base}/api/archives/${item.arcid}/thumbnail`
|
||||||
|
const tags = item.tags ? item.tags.split(',').map(t => t.trim()).filter(Boolean) : []
|
||||||
|
return new Comic({
|
||||||
|
id: item.arcid,
|
||||||
|
title: item.title || item.filename || item.arcid,
|
||||||
|
subTitle: '',
|
||||||
|
cover,
|
||||||
|
tags,
|
||||||
|
description: `页数: ${item.pagecount} | 新: ${item.isnew} | 扩展: ${item.extension}`
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const total = typeof data.recordsFiltered === 'number' && data.recordsFiltered >= 0
|
||||||
|
? data.recordsFiltered
|
||||||
|
: (list.length < pageSize ? start + list.length : start + pageSize)
|
||||||
|
const maxPage = Math.max(1, Math.ceil(total / pageSize))
|
||||||
|
return { comics, maxPage }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
search = {
|
||||||
|
load: async (keyword, options, page = 1) => {
|
||||||
|
const base = (this.baseUrl || '').replace(/\/$/, '')
|
||||||
|
|
||||||
|
// Fetch all results once (start=-1), then page locally for consistent UX across servers
|
||||||
|
const qp = []
|
||||||
|
const add = (k, v) => qp.push(`${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)
|
||||||
|
const pick = (key, def) => {
|
||||||
|
let v = options && (options[key])
|
||||||
|
if (typeof v === 'string') {
|
||||||
|
const idx = v.indexOf('-');
|
||||||
|
if (idx > 0) v = v.slice(0, idx)
|
||||||
|
}
|
||||||
|
return (v === undefined || v === null || v === '') ? def : v
|
||||||
|
}
|
||||||
|
const sortby = pick(0, 'title')
|
||||||
|
const order = pick(1, 'asc')
|
||||||
|
const newonly = String(pick(2, 'false'))
|
||||||
|
const untaggedonly = String(pick(3, 'false'))
|
||||||
|
const groupby = String(pick(4, 'true'))
|
||||||
|
|
||||||
|
add('filter', (keyword || '').trim())
|
||||||
|
add('start', '-1')
|
||||||
|
add('sortby', sortby)
|
||||||
|
add('order', order)
|
||||||
|
add('newonly', newonly)
|
||||||
|
add('untaggedonly', untaggedonly)
|
||||||
|
add('groupby_tanks', groupby)
|
||||||
|
|
||||||
|
const url = `${base}/api/search?${qp.join('&')}`
|
||||||
|
const res = await Network.get(url)
|
||||||
|
if (res.status !== 200) throw `Invalid status code: ${res.status}`
|
||||||
|
const data = JSON.parse(res.body)
|
||||||
|
const all = Array.isArray(data.data) ? data.data : []
|
||||||
|
|
||||||
|
const pageSize = 100
|
||||||
|
const start = Math.max(0, (page - 1) * pageSize)
|
||||||
|
const slice = all.slice(start, start + pageSize)
|
||||||
|
|
||||||
|
const comics = slice.map(item => {
|
||||||
|
const cover = `${base}/api/archives/${item.arcid}/thumbnail`
|
||||||
|
const tags = item.tags ? item.tags.split(',').map(t => t.trim()).filter(Boolean) : []
|
||||||
|
return new Comic({
|
||||||
|
id: item.arcid,
|
||||||
|
title: item.title || item.filename || item.arcid,
|
||||||
|
subTitle: '',
|
||||||
|
cover,
|
||||||
|
tags,
|
||||||
|
description: `页数: ${item.pagecount ?? ''} | 新: ${item.isnew ?? ''} | 扩展: ${item.extension ?? ''}`
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const total = (typeof data.recordsFiltered === 'number' && data.recordsFiltered >= 0)
|
||||||
|
? data.recordsFiltered
|
||||||
|
: all.length
|
||||||
|
const maxPage = Math.max(1, Math.ceil(total / pageSize))
|
||||||
|
return { comics, maxPage }
|
||||||
|
},
|
||||||
|
loadNext: async (keyword, options, next) => {
|
||||||
|
const page = (typeof next === 'number' && next > 0) ? next : 1
|
||||||
|
return await this.search.load(keyword, options, page)
|
||||||
|
},
|
||||||
|
optionList: [
|
||||||
|
{ type: "select", options: ["title-按标题","lastread-最近阅读"], label: "sortby", default: "title" },
|
||||||
|
{ type: "select", options: ["asc-升序","desc-降序"], label: "order", default: "asc" },
|
||||||
|
{ type: "select", options: ["false-全部","true-仅新"], label: "newonly", default: "false" },
|
||||||
|
{ type: "select", options: ["false-全部","true-仅未打标签"], label: "untaggedonly", default: "false" },
|
||||||
|
{ type: "select", options: ["true-启用","false-禁用"], label: "groupby_tanks", default: "true" }
|
||||||
|
],
|
||||||
|
enableTagsSuggestions: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
// favorites = {
|
||||||
|
// multiFolder: false,
|
||||||
|
// addOrDelFavorite: async (comicId, folderId, isAdding, favoriteId) => {},
|
||||||
|
// loadFolders: async (comicId) => {},
|
||||||
|
// addFolder: async (name) => {},
|
||||||
|
// deleteFolder: async (folderId) => {},
|
||||||
|
// loadComics: async (page, folder) => {},
|
||||||
|
// loadNext: async (next, folder) => {},
|
||||||
|
// singleFolderForSingleComic: false,
|
||||||
|
// }
|
||||||
|
|
||||||
|
comic = {
|
||||||
|
loadInfo: async (id) => {
|
||||||
|
const url = `${this.baseUrl}/api/archives/${id}/metadata`
|
||||||
|
const res = await Network.get(url)
|
||||||
|
if (res.status !== 200) throw `Invalid status code: ${res.status}`
|
||||||
|
const data = JSON.parse(res.body)
|
||||||
|
const cover = `${this.baseUrl}/api/archives/${id}/thumbnail`
|
||||||
|
let tags = data.tags ? data.tags.split(',').map(t=>t.trim()).filter(Boolean) : []
|
||||||
|
const rating = tags.find(t=>t.startsWith('rating:'))
|
||||||
|
if (rating) tags = tags.filter(t=>!t.startsWith('rating:'))
|
||||||
|
const chapters = new Map(); chapters.set(id, data.title || 'Local manga')
|
||||||
|
return { title: data.title || data.filename || id, cover, description: data.summary || '', tags: { "Tags": tags, "Extension": [data.extension], "Rating": rating ? [rating.replace('rating:', '')] : [], "Page": [String(data.pagecount)] }, chapters }
|
||||||
|
},
|
||||||
|
loadThumbnails: async (id, next) => {
|
||||||
|
const metaUrl = `${this.baseUrl}/api/archives/${id}/metadata`
|
||||||
|
const res = await Network.get(metaUrl)
|
||||||
|
if (res.status !== 200) throw `Invalid status code: ${res.status}`
|
||||||
|
const data = JSON.parse(res.body)
|
||||||
|
const pagecount = data.pagecount || 1
|
||||||
|
const thumbnails = []
|
||||||
|
for (let i = 1; i <= pagecount; i++) thumbnails.push(`${this.baseUrl}/api/archives/${id}/thumbnail?page=${i}`)
|
||||||
|
return { thumbnails, next: null }
|
||||||
|
},
|
||||||
|
starRating: async (id, rating) => {},
|
||||||
|
loadEp: async (comicId, epId) => {
|
||||||
|
const base = (this.baseUrl || '').replace(/\/$/, '')
|
||||||
|
const url = `${base}/api/archives/${comicId}/files?force=false`
|
||||||
|
const res = await Network.get(url)
|
||||||
|
if (res.status !== 200) throw `Invalid status code: ${res.status}`
|
||||||
|
const data = JSON.parse(res.body)
|
||||||
|
const images = (data.pages || []).map(p => {
|
||||||
|
if (!p) return null
|
||||||
|
const s = String(p)
|
||||||
|
if (/^https?:\/\//i.test(s)) return s
|
||||||
|
return `${base}${s.startsWith('/') ? s : '/' + s}`
|
||||||
|
}).filter(Boolean)
|
||||||
|
return { images }
|
||||||
|
},
|
||||||
|
// onImageLoad: (url, comicId, epId) => ({}),
|
||||||
|
// onThumbnailLoad: (url) => ({}),
|
||||||
|
// likeComic: async (id, isLike) => {},
|
||||||
|
// loadComments: async (comicId, subId, page, replyTo) => {},
|
||||||
|
// sendComment: async (comicId, subId, content, replyTo) => {},
|
||||||
|
// likeComment: async (comicId, subId, commentId, isLike) => {},
|
||||||
|
// voteComment: async (id, subId, commentId, isUp, isCancel) => {},
|
||||||
|
// idMatch: null,
|
||||||
|
// onClickTag: (namespace, tag) => {},
|
||||||
|
// link: { domains: ['example.com'], linkToId: (url) => null },
|
||||||
|
enableTagsTranslate: false,
|
||||||
|
}
|
||||||
|
}
|
103
picacg.js
103
picacg.js
@@ -3,7 +3,7 @@ class Picacg extends ComicSource {
|
|||||||
|
|
||||||
key = "picacg"
|
key = "picacg"
|
||||||
|
|
||||||
version = "1.0.4"
|
version = "1.0.5"
|
||||||
|
|
||||||
minAppVersion = "1.0.0"
|
minAppVersion = "1.0.0"
|
||||||
|
|
||||||
@@ -164,6 +164,99 @@ class Picacg extends ComicSource {
|
|||||||
comics: comics
|
comics: comics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Picacg H24",
|
||||||
|
type: "multiPageComicList",
|
||||||
|
load: async (page) => {
|
||||||
|
if (!this.isLogged) {
|
||||||
|
throw 'Not logged in'
|
||||||
|
}
|
||||||
|
let res = await Network.get(
|
||||||
|
`${this.loadSetting('base_url')}/comics/leaderboard?tt=H24&ct=VC`,
|
||||||
|
this.buildHeaders('GET', 'comics/leaderboard?tt=H24&ct=VC', this.loadData('token'))
|
||||||
|
)
|
||||||
|
if (res.status === 401) {
|
||||||
|
await this.account.reLogin()
|
||||||
|
res = await Network.get(
|
||||||
|
`${this.loadSetting('base_url')}/comics/leaderboard?tt=H24&ct=VC`,
|
||||||
|
this.buildHeaders('GET', 'comics/leaderboard?tt=H24&ct=VC', this.loadData('token'))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw 'Invalid status code: ' + res.status
|
||||||
|
}
|
||||||
|
let data = JSON.parse(res.body)
|
||||||
|
let comics = []
|
||||||
|
data.data.comics.forEach(c => {
|
||||||
|
comics.push(this.parseComic(c))
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
comics: comics
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Picacg D7",
|
||||||
|
type: "multiPageComicList",
|
||||||
|
load: async (page) => {
|
||||||
|
if (!this.isLogged) {
|
||||||
|
throw 'Not logged in'
|
||||||
|
}
|
||||||
|
let res = await Network.get(
|
||||||
|
`${this.loadSetting('base_url')}/comics/leaderboard?tt=D7&ct=VC`,
|
||||||
|
this.buildHeaders('GET', 'comics/leaderboard?tt=D7&ct=VC', this.loadData('token'))
|
||||||
|
)
|
||||||
|
if (res.status === 401) {
|
||||||
|
await this.account.reLogin()
|
||||||
|
res = await Network.get(
|
||||||
|
`${this.loadSetting('base_url')}/comics/leaderboard?tt=D7&ct=VC`,
|
||||||
|
this.buildHeaders('GET', 'comics/leaderboard?tt=D7&ct=VC', this.loadData('token'))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw 'Invalid status code: ' + res.status
|
||||||
|
}
|
||||||
|
let data = JSON.parse(res.body)
|
||||||
|
let comics = []
|
||||||
|
data.data.comics.forEach(c => {
|
||||||
|
comics.push(this.parseComic(c))
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
comics: comics
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Picacg D30",
|
||||||
|
type: "multiPageComicList",
|
||||||
|
load: async (page) => {
|
||||||
|
if (!this.isLogged) {
|
||||||
|
throw 'Not logged in'
|
||||||
|
}
|
||||||
|
let res = await Network.get(
|
||||||
|
`${this.loadSetting('base_url')}/comics/leaderboard?tt=D30&ct=VC`,
|
||||||
|
this.buildHeaders('GET', 'comics/leaderboard?tt=D30&ct=VC', this.loadData('token'))
|
||||||
|
)
|
||||||
|
if (res.status === 401) {
|
||||||
|
await this.account.reLogin()
|
||||||
|
res = await Network.get(
|
||||||
|
`${this.loadSetting('base_url')}/comics/leaderboard?tt=D30&ct=VC`,
|
||||||
|
this.buildHeaders('GET', 'comics/leaderboard?tt=D30&ct=VC', this.loadData('token'))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw 'Invalid status code: ' + res.status
|
||||||
|
}
|
||||||
|
let data = JSON.parse(res.body)
|
||||||
|
let comics = []
|
||||||
|
data.data.comics.forEach(c => {
|
||||||
|
comics.push(this.parseComic(c))
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
comics: comics
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -691,6 +784,9 @@ class Picacg extends ComicSource {
|
|||||||
'zh_CN': {
|
'zh_CN': {
|
||||||
'Picacg Random': "哔咔随机",
|
'Picacg Random': "哔咔随机",
|
||||||
'Picacg Latest': "哔咔最新",
|
'Picacg Latest': "哔咔最新",
|
||||||
|
'Picacg H24': "哔咔日榜",
|
||||||
|
'Picacg D7': "哔咔周榜",
|
||||||
|
'Picacg D30': "哔咔月榜",
|
||||||
'New to old': "新到旧",
|
'New to old': "新到旧",
|
||||||
'Old to new': "旧到新",
|
'Old to new': "旧到新",
|
||||||
'Most likes': "最多喜欢",
|
'Most likes': "最多喜欢",
|
||||||
@@ -710,6 +806,9 @@ class Picacg extends ComicSource {
|
|||||||
'zh_TW': {
|
'zh_TW': {
|
||||||
'Picacg Random': "哔咔隨機",
|
'Picacg Random': "哔咔隨機",
|
||||||
'Picacg Latest': "哔咔最新",
|
'Picacg Latest': "哔咔最新",
|
||||||
|
'Picacg H24': "哔咔日榜",
|
||||||
|
'Picacg D7': "哔咔周榜",
|
||||||
|
'Picacg D30': "哔咔月榜",
|
||||||
'New to old': "新到舊",
|
'New to old': "新到舊",
|
||||||
'Old to new': "舊到新",
|
'Old to new': "舊到新",
|
||||||
'Most likes': "最多喜歡",
|
'Most likes': "最多喜歡",
|
||||||
@@ -727,4 +826,4 @@ class Picacg extends ComicSource {
|
|||||||
'Sort': "排序",
|
'Sort': "排序",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
798
zaimanhua.js
798
zaimanhua.js
@@ -1,418 +1,490 @@
|
|||||||
/** @type {import('./_venera_.js')} */
|
class Zaimanhua extends ComicSource {
|
||||||
class ZaiManHua extends ComicSource {
|
// 基础信息
|
||||||
// Note: The fields which are marked as [Optional] should be removed if not used
|
|
||||||
|
|
||||||
// name of the source
|
|
||||||
name = "再漫画";
|
name = "再漫画";
|
||||||
|
|
||||||
// unique id of the source
|
|
||||||
key = "zaimanhua";
|
key = "zaimanhua";
|
||||||
|
version = "1.0.1";
|
||||||
|
minAppVersion = "1.0.0";
|
||||||
|
url =
|
||||||
|
"https://git.nyne.dev/nyne/venera-configs/raw/branch/main/zaimanhua.js";
|
||||||
|
|
||||||
version = "1.0.0";
|
// 初始化请求头
|
||||||
|
|
||||||
minAppVersion = "1.4.0";
|
|
||||||
|
|
||||||
// update url
|
|
||||||
url = "https://git.nyne.dev/nyne/venera-configs/raw/branch/main/zaimanhua.js";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* fetch html content
|
|
||||||
* @param url {string}
|
|
||||||
* @param headers {object?}
|
|
||||||
* @returns {Promise<{document:HtmlDocument}>}
|
|
||||||
*/
|
|
||||||
async fetchHtml(url, headers = {}) {
|
|
||||||
let res = await Network.get(url, headers);
|
|
||||||
if (res.status !== 200) {
|
|
||||||
throw "Invalid status code: " + res.status;
|
|
||||||
}
|
|
||||||
let document = new HtmlDocument(res.body);
|
|
||||||
|
|
||||||
return document;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* fetch json content
|
|
||||||
* @param url {string}
|
|
||||||
* @param headers {object?}
|
|
||||||
* @returns {Promise<{data:object}>}
|
|
||||||
*/
|
|
||||||
async fetchJson(url, headers = {}) {
|
|
||||||
let res = await Network.get(url, headers);
|
|
||||||
return JSON.parse(res.body).data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* parse json content
|
|
||||||
* @param e object
|
|
||||||
* @returns {Comic}
|
|
||||||
*/
|
|
||||||
parseJsonComic(e) {
|
|
||||||
let id = e.comic_py;
|
|
||||||
if (!id) {
|
|
||||||
id = id.comicPy;
|
|
||||||
}
|
|
||||||
let title = e?.name;
|
|
||||||
if (!title) {
|
|
||||||
title = e?.title;
|
|
||||||
}
|
|
||||||
return new Comic({
|
|
||||||
id: id.toString(),
|
|
||||||
title: title.toString(),
|
|
||||||
subtitle: e?.authors,
|
|
||||||
tags: e?.types?.split("/"),
|
|
||||||
cover: e?.cover,
|
|
||||||
description: e?.last_update_chapter_name.toString(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [Optional] init function
|
|
||||||
*/
|
|
||||||
init() {
|
init() {
|
||||||
this.domain = "https://www.zaimanhua.com";
|
this.headers = {
|
||||||
this.imgBase = "https://images.zaimanhua.com";
|
"User-Agent": "Mozilla/5.0 (Linux; Android) Mobile",
|
||||||
this.baseUrl = "https://manhua.zaimanhua.com";
|
"authorization": `Bearer ${this.loadData("token") || ""}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// 构建 URL
|
||||||
|
buildUrl(path) {
|
||||||
|
return `https://v4api.zaimanhua.com/app/v1/${path}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// explore page list
|
//账户管理
|
||||||
|
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 = [
|
explore = [
|
||||||
{
|
{
|
||||||
// title of the page.
|
title: "再漫画 更新",
|
||||||
// title is used to identify the page, it should be unique
|
type: "multiPageComicList",
|
||||||
title: this.name,
|
|
||||||
|
|
||||||
/// TODO multiPartPage
|
|
||||||
type: "singlePageWithMultiPart",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* load function
|
|
||||||
* @param page {number | null} - page number, null for `singlePageWithMultiPart` type
|
|
||||||
* @returns {{}}
|
|
||||||
* - for `multiPartPage` type, return [{title: string, comics: Comic[], viewMore: PageJumpTarget}]
|
|
||||||
* - for `multiPageComicList` type, for each page(1-based), return {comics: Comic[], maxPage: number}
|
|
||||||
* - for `mixed` type, use param `page` as index. for each index(0-based), return {data: [], maxPage: number?}, data is an array contains Comic[] or {title: string, comics: Comic[], viewMore: string?}
|
|
||||||
*/
|
|
||||||
load: async (page) => {
|
load: async (page) => {
|
||||||
let result = {};
|
const res = await Network.get(
|
||||||
// https://manhua.zaimanhua.com/api/v1/comic1/recommend/list?
|
this.buildUrl(`comic/update/list/0/${page}`),
|
||||||
// channel=pc&app_name=zmh&version=1.0.0×tamp=1753547675981&uid=0
|
this.headers
|
||||||
let api = `${this.baseUrl}/api/v1/comic1/recommend/list`;
|
);
|
||||||
let params = {
|
const data = JSON.parse(res.body).data;
|
||||||
channel: "pc",
|
return {
|
||||||
app_name: "zmh",
|
comics: data.map((item) => this.parseComic(item)),
|
||||||
version: "1.0.0",
|
|
||||||
timestamp: Date.now(),
|
|
||||||
uid: 0,
|
|
||||||
};
|
};
|
||||||
let params_str = Object.keys(params)
|
|
||||||
.map((key) => `${key}=${params[key]}`)
|
|
||||||
.join("&");
|
|
||||||
let url = `${api}?${params_str}`;
|
|
||||||
const json = await this.fetchJson(url);
|
|
||||||
let data = json.list;
|
|
||||||
data.shift(); // 去掉第一个
|
|
||||||
data.pop(); // 去掉最后一个
|
|
||||||
data.map((arr) => {
|
|
||||||
let title = arr.name;
|
|
||||||
let comic_list = arr.list.map((item) => this.parseJsonComic(item));
|
|
||||||
result[title] = comic_list;
|
|
||||||
});
|
|
||||||
|
|
||||||
log("error", "再看漫画", result);
|
|
||||||
return result;
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// categories
|
static categoryParamMap = {
|
||||||
// categories
|
"全部": "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 = {
|
category = {
|
||||||
/// title of the category page, used to identify the page, it should be unique
|
title: "再漫画",
|
||||||
title: this.name,
|
|
||||||
parts: [
|
parts: [
|
||||||
{
|
{
|
||||||
name: "类型",
|
name: "排行榜",
|
||||||
type: "fixed",
|
type: "fixed",
|
||||||
categories: [
|
categories: ["日排行", "周排行", "月排行", "总排行"],
|
||||||
"全部",
|
itemType: "category",
|
||||||
"冒险",
|
categoryParams: ["0", "1", "2", "3"],
|
||||||
"搞笑",
|
},
|
||||||
"格斗",
|
{
|
||||||
"科幻",
|
name: "分类",
|
||||||
"爱情",
|
type: "fixed",
|
||||||
"侦探",
|
categories: Object.keys(Zaimanhua.categoryParamMap),
|
||||||
"竞技",
|
categoryParams: Object.values(Zaimanhua.categoryParamMap),
|
||||||
"魔法",
|
|
||||||
"校园",
|
|
||||||
"百合",
|
|
||||||
"耽美",
|
|
||||||
"历史",
|
|
||||||
"战争",
|
|
||||||
"宅系",
|
|
||||||
"治愈",
|
|
||||||
"仙侠",
|
|
||||||
"武侠",
|
|
||||||
"职场",
|
|
||||||
"神鬼",
|
|
||||||
"奇幻",
|
|
||||||
"生活",
|
|
||||||
"其他",
|
|
||||||
],
|
|
||||||
itemType: "category",
|
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",
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
// enable ranking page
|
|
||||||
enableRankingPage: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// category comic loading related
|
//分类漫画加载
|
||||||
categoryComics = {
|
categoryComics = {
|
||||||
/**
|
|
||||||
* load comics of a category
|
|
||||||
* @param category {string} - category name
|
|
||||||
* @param param {string?} - category param
|
|
||||||
* @param options {string[]} - options from optionList
|
|
||||||
* @param page {number} - page number
|
|
||||||
* @returns {Promise<{comics: Comic[], maxPage: number}>}
|
|
||||||
*/
|
|
||||||
load: async (category, param, options, page) => {
|
load: async (category, param, options, page) => {
|
||||||
let fil = `${this.baseUrl}/api/v1/comic1/filter`;
|
if (category.includes("排行")) {
|
||||||
let params = {
|
let res = await Network.get(
|
||||||
timestamp: Date.now(),
|
this.buildUrl(
|
||||||
sortType: 0,
|
`comic/rank/list?page=${page}&rank_type=${options}&by_time=${param}`
|
||||||
page: page,
|
),
|
||||||
size: 20,
|
this.headers
|
||||||
status: options[1],
|
);
|
||||||
audience: options[0],
|
return {
|
||||||
theme: param,
|
comics: JSON.parse(res.body).data.map((item) =>
|
||||||
cate: options[2],
|
this.parseComic(item)
|
||||||
};
|
),
|
||||||
// 拼接url
|
maxPage: 10,
|
||||||
let params_str = Object.keys(params)
|
};
|
||||||
.map((key) => `${key}=${params[key]}`)
|
} else {
|
||||||
.join("&");
|
param = Zaimanhua.categoryParamMap[category] || "0";
|
||||||
// log("error", "再漫画", params_str);
|
let res = await Network.get(
|
||||||
let url = `${fil}?${params_str}&firstLetter`;
|
this.buildUrl(
|
||||||
// log("error", "再漫画", url);
|
`comic/filter/list?status=${options[2]}&theme=${param}&zone=${options[3]}&cate=${options[1]}&sortType=${options[0]}&page=${page}&size=20`
|
||||||
|
),
|
||||||
const json = await this.fetchJson(url);
|
this.headers
|
||||||
let comics = json.comicList.map((e) => this.parseJsonComic(e));
|
);
|
||||||
let maxPage = Math.ceil(json.totalNum / params.size);
|
const data = JSON.parse(res.body).data;
|
||||||
// log("error", "再漫画", comics);
|
return {
|
||||||
return {
|
comics: data.comicList.map((item) => this.parseComic(item)),
|
||||||
comics,
|
maxPage: Math.ceil(data.totalNum / 20),
|
||||||
maxPage,
|
};
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
// provide options for category comic loading
|
|
||||||
optionList: [
|
optionList: [
|
||||||
{
|
{
|
||||||
options: ["0-全部", "3262-少年", "3263-少女", "3264-青年"],
|
options: ["1-更新", "2-人气"],
|
||||||
|
notShowWhen: null,
|
||||||
|
showWhen: Object.keys(Zaimanhua.categoryParamMap),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
options: ["0-全部", "1-故事漫画", "2-四格多格"],
|
options: [
|
||||||
|
"0-全部",
|
||||||
|
"3262-少年漫画",
|
||||||
|
"3263-少女漫画",
|
||||||
|
"3264-青年漫画",
|
||||||
|
"13626-女青漫画",
|
||||||
|
],
|
||||||
|
notShowWhen: null,
|
||||||
|
showWhen: Object.keys(Zaimanhua.categoryParamMap),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
options: ["0-全部", "1-连载", "2-完结"],
|
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 related
|
//搜索
|
||||||
search = {
|
search = {
|
||||||
/**
|
|
||||||
* load search result
|
|
||||||
* @param keyword {string}
|
|
||||||
* @param options {string[]} - options from optionList
|
|
||||||
* @param page {number}
|
|
||||||
* @returns {Promise<{comics: Comic[], maxPage: number}>}
|
|
||||||
*/
|
|
||||||
load: async (keyword, options, page) => {
|
load: async (keyword, options, page) => {
|
||||||
let url = `${this.baseUrl}/app/v1/search/index?keyword=${keyword}&source=0&page=${page}&size=20`;
|
const res = await Network.get(
|
||||||
const json = await this.fetchJson(url);
|
this.buildUrl(
|
||||||
let comics = json.comicList.map((e) => this.parseJsonComic(e));
|
`search/index?keyword=${encodeURIComponent(
|
||||||
let maxPage = Math.ceil(json.totalNum / params.size);
|
keyword
|
||||||
// log("error", "再漫画", comics);
|
)}&page=${page}&sort=0&size=20`
|
||||||
|
),
|
||||||
|
this.headers
|
||||||
|
);
|
||||||
|
const data = JSON.parse(res.body).data.list;
|
||||||
return {
|
return {
|
||||||
comics,
|
comics: data.map((item) => this.parseComic(item)),
|
||||||
maxPage,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
// provide options for search
|
|
||||||
optionList: [],
|
optionList: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
/// single comic related
|
//收藏
|
||||||
|
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 = {
|
comic = {
|
||||||
/**
|
|
||||||
* load comic info
|
|
||||||
* @param id {string}
|
|
||||||
* @returns {Promise<ComicDetails>}
|
|
||||||
*/
|
|
||||||
loadInfo: async (id) => {
|
loadInfo: async (id) => {
|
||||||
const api = `${this.domain}/api/v1/comic1/comic/detail`;
|
const getFavoriteStatus = async (id) => {
|
||||||
let params = {
|
let res = await Network.get(
|
||||||
channel: "pc",
|
this.buildUrl(`comic/sub/checkIsSub?objId=${id}&source=1`),
|
||||||
app_name: "zmh",
|
this.headers
|
||||||
version: "1.0.0",
|
);
|
||||||
timestamp: Date.now(),
|
this.checkResponseStatus(res);
|
||||||
uid: 0,
|
return JSON.parse(res.body).data.isSub;
|
||||||
comic_py: id,
|
|
||||||
};
|
};
|
||||||
let params_str = Object.keys(params)
|
let results = await Promise.all([
|
||||||
.map((key) => `${key}=${params[key]}`)
|
Network.get(
|
||||||
.join("&");
|
this.buildUrl(`comic/detail/${id}?channel=android`),
|
||||||
let url = `${api}?${params_str}`;
|
this.headers
|
||||||
const json = await this.fetchJson(url);
|
),
|
||||||
const info = json.comicInfo;
|
getFavoriteStatus.bind(this)(id),
|
||||||
const comic_id = info.id;
|
]);
|
||||||
let title = info.title;
|
const response = JSON.parse(results[0].body);
|
||||||
let author = info.authorInfo.authorName;
|
if (response.errno !== 0) throw new Error(response.errmsg || "加载失败");
|
||||||
|
const data = response.data.data;
|
||||||
|
|
||||||
// 修复时间戳转换问题
|
function processChapters(groups) {
|
||||||
let lastUpdateTime = new Date(info.lastUpdateTime * 1000);
|
return (groups || []).reduce((result, group) => {
|
||||||
let updateTime = `${lastUpdateTime.getFullYear()}-${
|
const groupTitle = group.title || "默认";
|
||||||
lastUpdateTime.getMonth() + 1
|
const chapters = (group.data || [])
|
||||||
}-${lastUpdateTime.getDate()}`;
|
.reverse()
|
||||||
|
.map((ch) => [
|
||||||
let description = info.description;
|
String(ch.chapter_id),
|
||||||
let cover = info.cover;
|
`${ch.chapter_title.replace(
|
||||||
|
/^(?:连载版?)?(\d+\.?\d*)([话卷])?$/,
|
||||||
let chapters = new Map();
|
(_, n, t) => `第${n}${t || "话"}`
|
||||||
info.chapterList[0].data.forEach((e) => {
|
)}`,
|
||||||
chapters.set(e.chapter_id.toString(), e.chapter_title);
|
]);
|
||||||
});
|
result.set(groupTitle, new Map(chapters));
|
||||||
// chapters 按照key排序
|
return result;
|
||||||
let chaptersSorted = new Map([...chapters].sort((a, b) => a[0] - b[0]));
|
}, new Map());
|
||||||
|
}
|
||||||
// 获取推荐漫画
|
// 分类标签
|
||||||
const api2 = `${this.baseUrl}/api/v1/comic1/comic/same_list`;
|
const { authors, status, types } = data;
|
||||||
let params2 = {
|
const tagMapper = (arr) => arr.map((t) => t.tag_name);
|
||||||
channel: "pc",
|
|
||||||
app_name: "zmh",
|
|
||||||
version: "1.0.0",
|
|
||||||
timestamp: Date.now(),
|
|
||||||
uid: 0,
|
|
||||||
comic_id: comic_id,
|
|
||||||
};
|
|
||||||
let params2_str = Object.keys(params2)
|
|
||||||
.map((key) => `${key}=${params2[key]}`)
|
|
||||||
.join("&");
|
|
||||||
let url2 = `${api2}?${params2_str}`;
|
|
||||||
const json2 = await this.fetchJson(url2);
|
|
||||||
let recommend = json2.data.comicList.map((e) => this.parseJsonComic(e));
|
|
||||||
let tags = {
|
|
||||||
状态: [info.status],
|
|
||||||
类型: [info.readerGroup, ...info.types.split("/")],
|
|
||||||
点击: [info.hitNumStr.toString()],
|
|
||||||
订阅: [info.subNumStr],
|
|
||||||
};
|
|
||||||
|
|
||||||
return new ComicDetails({
|
|
||||||
title,
|
|
||||||
subtitle: author,
|
|
||||||
cover,
|
|
||||||
description,
|
|
||||||
tags,
|
|
||||||
chapters: chaptersSorted,
|
|
||||||
recommend,
|
|
||||||
updateTime,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* load images of a chapter
|
|
||||||
* @param comicId {string}
|
|
||||||
* @param epId {string?}
|
|
||||||
* @returns {Promise<{images: string[]}>}
|
|
||||||
*/
|
|
||||||
loadEp: async (comicId, epId) => {
|
|
||||||
const api_ = `${this.domain}/api/v1/comic1/comic/detail`;
|
|
||||||
// log("error", "再漫画", id);
|
|
||||||
let params_ = {
|
|
||||||
channel: "pc",
|
|
||||||
app_name: "zmh",
|
|
||||||
version: "1.0.0",
|
|
||||||
timestamp: Date.now(),
|
|
||||||
uid: 0,
|
|
||||||
comic_py: comicId,
|
|
||||||
};
|
|
||||||
let params_str_ = Object.keys(params_)
|
|
||||||
.map((key) => `${key}=${params_[key]}`)
|
|
||||||
.join("&");
|
|
||||||
let url_ = `${api_}?${params_str_}`;
|
|
||||||
const json_ = await this.fetchJson(url_);
|
|
||||||
const info_ = json_.comicInfo;
|
|
||||||
const comic_id = info_.id;
|
|
||||||
|
|
||||||
const api = `${this.baseUrl}/api/v1/comic1/chapter/detail`;
|
|
||||||
// comic_id=18114&chapter_id=36227
|
|
||||||
let params = {
|
|
||||||
channel: "pc",
|
|
||||||
app_name: "zmh",
|
|
||||||
version: "1.0.0",
|
|
||||||
timestamp: Date.now(),
|
|
||||||
uid: 0,
|
|
||||||
comic_id: comic_id,
|
|
||||||
chapter_id: epId,
|
|
||||||
};
|
|
||||||
let params_str = Object.keys(params)
|
|
||||||
.map((key) => `${key}=${params[key]}`)
|
|
||||||
.join("&");
|
|
||||||
let url = `${api}?${params_str}`;
|
|
||||||
const json = await this.fetchJson(url);
|
|
||||||
const info = json.chapterInfo;
|
|
||||||
return {
|
return {
|
||||||
images: info.page_url,
|
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) => {
|
||||||
* [Optional] provide configs for an image loading
|
const res = await Network.get(
|
||||||
* @param url
|
this.buildUrl(`comic/chapter/${comicId}/${epId}`)
|
||||||
* @param comicId
|
);
|
||||||
* @param epId
|
const data = JSON.parse(res.body).data.data;
|
||||||
* @returns {ImageLoadingConfig | Promise<ImageLoadingConfig>}
|
return { images: data.page_url_hd || data.page_url };
|
||||||
*/
|
|
||||||
onImageLoad: (url, comicId, epId) => {
|
|
||||||
return {};
|
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
* [Optional] provide configs for a thumbnail loading
|
loadComments: async (comicId, subId, page, replyTo) => {
|
||||||
* @param url {string}
|
try {
|
||||||
* @returns {ImageLoadingConfig | Promise<ImageLoadingConfig>}
|
// 构建请求URL
|
||||||
*
|
const url = this.buildUrl(
|
||||||
* `ImageLoadingConfig.modifyImage` and `ImageLoadingConfig.onLoadFailed` will be ignored.
|
`comment/list?page=${page}&size=30&type=4&objId=${
|
||||||
* They are not supported for thumbnails.
|
subId || comicId
|
||||||
*/
|
}&sortBy=1`
|
||||||
onThumbnailLoad: (url) => {
|
);
|
||||||
return {};
|
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";
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user