mirror of
https://github.com/venera-app/venera-configs.git
synced 2025-09-27 08:27:24 +00:00
Compare commits
48 Commits
c55657f0a2
...
main
Author | SHA1 | Date | |
---|---|---|---|
![]() |
603fefe9be | ||
![]() |
cd941b92ef | ||
62fbe9294b | |||
91823846a0 | |||
ef87d90e89 | |||
![]() |
a991dac6d6 | ||
![]() |
c9fdc8367a | ||
![]() |
aafc7078ba | ||
![]() |
edebc0c430 | ||
![]() |
b6448c2055 | ||
![]() |
ca2f626483 | ||
![]() |
8a26cff469 | ||
![]() |
c281495cee | ||
![]() |
170eb738b9 | ||
![]() |
0d2ec4a85a | ||
![]() |
714353cf64 | ||
![]() |
65bb0d244d | ||
![]() |
2b8c532817 | ||
ee0a98ec33 | |||
ccc157b4f2 | |||
f2aacf2baa | |||
![]() |
f4bc304d1b | ||
![]() |
08756ee659 | ||
![]() |
6e52854782 | ||
![]() |
b5ba37794a | ||
![]() |
7dce35fd5a | ||
![]() |
ad91da8e0f | ||
![]() |
4a18a7de3a | ||
![]() |
5ff8254dd5 | ||
![]() |
fb20c68024 | ||
![]() |
631298ce1b | ||
![]() |
f812964e55 | ||
![]() |
2e13f5fce9 | ||
![]() |
0976105138 | ||
![]() |
b1b8b8cab9 | ||
![]() |
fd59c132a2 | ||
![]() |
a5b1fd6ca2 | ||
![]() |
2174c13e16 | ||
![]() |
8bfb3fdf2e | ||
![]() |
b8a7966aa7 | ||
![]() |
215e539def | ||
![]() |
499180b6ea | ||
![]() |
94e3e99877 | ||
![]() |
afea7a6c99 | ||
![]() |
891b81ae16 | ||
![]() |
e7e8324e94 | ||
![]() |
20917d8e47 | ||
c03f1c41d4 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
test/
|
@@ -1,4 +1,19 @@
|
|||||||
/** @type {import('./_venera_.js')} */
|
/** @type {import('./_venera_.js')} */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} PageJumpTarget
|
||||||
|
* @Property {string} page - The page name (search, category)
|
||||||
|
* @Property {Object} attributes - The attributes of the page
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {
|
||||||
|
* page: "search",
|
||||||
|
* attributes: {
|
||||||
|
* keyword: "example",
|
||||||
|
* },
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
|
||||||
class NewComicSource extends ComicSource {
|
class NewComicSource extends ComicSource {
|
||||||
// Note: The fields which are marked as [Optional] should be removed if not used
|
// Note: The fields which are marked as [Optional] should be removed if not used
|
||||||
|
|
||||||
@@ -256,20 +271,42 @@ class NewComicSource extends ComicSource {
|
|||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
},
|
},
|
||||||
// provide options for category comic loading
|
// [Optional] provide options for category comic loading
|
||||||
optionList: [
|
optionList: [
|
||||||
{
|
{
|
||||||
|
// [Optional] The label will not be displayed if it is empty.
|
||||||
|
label: "",
|
||||||
// For a single option, use `-` to separate the value and text, left for value, right for text
|
// For a single option, use `-` to separate the value and text, left for value, right for text
|
||||||
options: [
|
options: [
|
||||||
"newToOld-New to Old",
|
"newToOld-New to Old",
|
||||||
"oldToNew-Old to New"
|
"oldToNew-Old to New"
|
||||||
],
|
],
|
||||||
// [Optional] {string[]} - show this option only when the value not in the list
|
// [Optional] {string[]} - show this option only when the category not in the list
|
||||||
notShowWhen: null,
|
notShowWhen: null,
|
||||||
// [Optional] {string[]} - show this option only when the value in the list
|
// [Optional] {string[]} - show this option only when the category in the list
|
||||||
showWhen: null
|
showWhen: null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
/**
|
||||||
|
* [Optional] load options dynamically. If `optionList` is provided, this will be ignored.
|
||||||
|
* @since 1.5.0
|
||||||
|
* @param category {string}
|
||||||
|
* @param param {string?}
|
||||||
|
* @return {Promise<{options: string[], label?: string}[]>} - return a list of option group, each group contains a list of options
|
||||||
|
*/
|
||||||
|
optionLoader: async (category, param) => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
// [Optional] The label will not be displayed if it is empty.
|
||||||
|
label: "",
|
||||||
|
// For a single option, use `-` to separate the value and text, left for value, right for text
|
||||||
|
options: [
|
||||||
|
"newToOld-New to Old",
|
||||||
|
"oldToNew-Old to New"
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
ranking: {
|
ranking: {
|
||||||
// For a single option, use `-` to separate the value and text, left for value, right for text
|
// For a single option, use `-` to separate the value and text, left for value, right for text
|
||||||
options: [
|
options: [
|
||||||
|
75
_venera_.js
75
_venera_.js
@@ -4,6 +4,18 @@ Venera JavaScript Library
|
|||||||
This library provides a set of APIs for interacting with the Venera app.
|
This library provides a set of APIs for interacting with the Venera app.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function sendMessage
|
||||||
|
* @global
|
||||||
|
* @param {Object} message
|
||||||
|
* @returns {any}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a timeout to execute a callback function after a specified delay.
|
||||||
|
* @param callback {Function}
|
||||||
|
* @param delay {number} - delay in milliseconds
|
||||||
|
*/
|
||||||
function setTimeout(callback, delay) {
|
function setTimeout(callback, delay) {
|
||||||
sendMessage({
|
sendMessage({
|
||||||
method: 'delay',
|
method: 'delay',
|
||||||
@@ -42,8 +54,6 @@ let Convert = {
|
|||||||
/**
|
/**
|
||||||
* @param str {string}
|
* @param str {string}
|
||||||
* @returns {ArrayBuffer}
|
* @returns {ArrayBuffer}
|
||||||
*
|
|
||||||
* @since 1.4.3
|
|
||||||
*/
|
*/
|
||||||
encodeGbk: (str) => {
|
encodeGbk: (str) => {
|
||||||
return sendMessage({
|
return sendMessage({
|
||||||
@@ -57,8 +67,6 @@ let Convert = {
|
|||||||
/**
|
/**
|
||||||
* @param value {ArrayBuffer}
|
* @param value {ArrayBuffer}
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*
|
|
||||||
* @since 1.4.3
|
|
||||||
*/
|
*/
|
||||||
decodeGbk: (value) => {
|
decodeGbk: (value) => {
|
||||||
return sendMessage({
|
return sendMessage({
|
||||||
@@ -1042,20 +1050,6 @@ function ImageLoadingConfig({url, method, data, headers, onResponse, modifyImage
|
|||||||
this.onLoadFailed = onLoadFailed;
|
this.onLoadFailed = onLoadFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} PageJumpTarget
|
|
||||||
* @Property {string} page - The page name (search, category)
|
|
||||||
* @Property {Object} attributes - The attributes of the page
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* {
|
|
||||||
* page: "search",
|
|
||||||
* attributes: {
|
|
||||||
* keyword: "example",
|
|
||||||
* },
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
|
|
||||||
class ComicSource {
|
class ComicSource {
|
||||||
name = ""
|
name = ""
|
||||||
|
|
||||||
@@ -1340,13 +1334,15 @@ let UI = {
|
|||||||
* Show an input dialog
|
* Show an input dialog
|
||||||
* @param title {string}
|
* @param title {string}
|
||||||
* @param validator {(string) => string | null | undefined} - A function that validates the input. If the function returns a string, the dialog will show the error message.
|
* @param validator {(string) => string | null | undefined} - A function that validates the input. If the function returns a string, the dialog will show the error message.
|
||||||
|
* @param image {string?} - Available since 1.4.6. An optional image to show in the dialog. You can use this to show a captcha.
|
||||||
* @returns {Promise<string | null>} - The input value. If the dialog is canceled, return null.
|
* @returns {Promise<string | null>} - The input value. If the dialog is canceled, return null.
|
||||||
*/
|
*/
|
||||||
showInputDialog: (title, validator) => {
|
showInputDialog: (title, validator, image) => {
|
||||||
return sendMessage({
|
return sendMessage({
|
||||||
method: 'UI',
|
method: 'UI',
|
||||||
function: 'showInputDialog',
|
function: 'showInputDialog',
|
||||||
title: title,
|
title: title,
|
||||||
|
image: image,
|
||||||
validator: validator
|
validator: validator
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -1402,3 +1398,44 @@ let APP = {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set clipboard text
|
||||||
|
* @param text {string}
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*
|
||||||
|
* @since 1.3.4
|
||||||
|
*/
|
||||||
|
function setClipboard(text) {
|
||||||
|
return sendMessage({
|
||||||
|
method: 'setClipboard',
|
||||||
|
text: text
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get clipboard text
|
||||||
|
* @returns {Promise<string>}
|
||||||
|
*
|
||||||
|
* @since 1.3.4
|
||||||
|
*/
|
||||||
|
function getClipboard() {
|
||||||
|
return sendMessage({
|
||||||
|
method: 'getClipboard'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute a function with arguments. The function will be executed in the engine pool which is not in the main thread.
|
||||||
|
* @param func {string} - A js code string which can be evaluated to a function. The function will receive the args as its only argument.
|
||||||
|
* @param args {any[]} - The arguments to pass to the function.
|
||||||
|
* @returns {Promise<any>} - The result of the function.
|
||||||
|
* @since 1.5.0
|
||||||
|
*/
|
||||||
|
function compute(func, ...args) {
|
||||||
|
return sendMessage({
|
||||||
|
method: 'compute',
|
||||||
|
function: func,
|
||||||
|
args: args
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@@ -13,7 +13,7 @@ class Baihehui extends ComicSource {
|
|||||||
minAppVersion = "1.4.0"
|
minAppVersion = "1.4.0"
|
||||||
|
|
||||||
// update url
|
// update url
|
||||||
url = "https://cdn.jsdelivr.net/gh/venera-app/venera-configs@main/baihehui.js"
|
url = "https://git.nyne.dev/nyne/venera-configs/raw/branch/main/baihehui.js"
|
||||||
|
|
||||||
settings = {
|
settings = {
|
||||||
domains: {
|
domains: {
|
||||||
|
836
baozi.js
836
baozi.js
@@ -1,386 +1,494 @@
|
|||||||
class Baozi extends ComicSource {
|
class Baozi extends ComicSource {
|
||||||
// 此漫画源的名称
|
// 此漫画源的名称
|
||||||
name = "包子漫画"
|
name = "包子漫画";
|
||||||
|
|
||||||
// 唯一标识符
|
// 唯一标识符
|
||||||
key = "baozi"
|
key = "baozi";
|
||||||
|
|
||||||
version = "1.0.5"
|
version = "1.1.0";
|
||||||
|
|
||||||
minAppVersion = "1.0.0"
|
minAppVersion = "1.0.0";
|
||||||
|
|
||||||
// 更新链接
|
// 更新链接
|
||||||
url = "https://git.nyne.dev/nyne/venera-configs/raw/branch/main/baozi.js"
|
url = "https://git.nyne.dev/nyne/venera-configs/raw/branch/main/baozi.js";
|
||||||
|
|
||||||
settings = {
|
settings = {
|
||||||
language: {
|
language: {
|
||||||
title: "简繁切换",
|
title: "简繁切换",
|
||||||
type: "select",
|
type: "select",
|
||||||
options: [
|
options: [
|
||||||
{ value: "cn", text: "简体" },
|
{ value: "cn", text: "简体" },
|
||||||
{ value: "tw", text: "繁體" }
|
{ value: "tw", text: "繁體" },
|
||||||
],
|
],
|
||||||
default: "cn"
|
default: "cn",
|
||||||
|
},
|
||||||
|
domains: {
|
||||||
|
title: "主域名",
|
||||||
|
type: "select",
|
||||||
|
options: [
|
||||||
|
{ value: "baozimhcn.com" },
|
||||||
|
{ value: "webmota.com" },
|
||||||
|
{ value: "kukuc.co" },
|
||||||
|
{ value: "twmanga.com" },
|
||||||
|
{ value: "dinnerku.com" },
|
||||||
|
],
|
||||||
|
default: "baozimhcn.com",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// 动态生成完整域名
|
||||||
|
get lang() {
|
||||||
|
return this.loadSetting("language") || this.settings.language.default;
|
||||||
|
}
|
||||||
|
get baseUrl() {
|
||||||
|
let domain = this.loadSetting("domains") || this.settings.domains.default;
|
||||||
|
return `https://${this.lang}.${domain}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 账号
|
||||||
|
/// 设置为null禁用账号功能
|
||||||
|
account = {
|
||||||
|
/// 登录
|
||||||
|
/// 返回任意值表示登录成功
|
||||||
|
login: async (account, pwd) => {
|
||||||
|
let res = await Network.post(
|
||||||
|
`${this.baseUrl}/api/bui/signin`,
|
||||||
|
{
|
||||||
|
"content-type":
|
||||||
|
"multipart/form-data; boundary=----WebKitFormBoundaryFUNUxpOwyUaDop8s",
|
||||||
},
|
},
|
||||||
domains: {
|
'------WebKitFormBoundaryFUNUxpOwyUaDop8s\r\nContent-Disposition: form-data; name="username"\r\n\r\n' +
|
||||||
title: "主域名",
|
account +
|
||||||
type: "select",
|
'\r\n------WebKitFormBoundaryFUNUxpOwyUaDop8s\r\nContent-Disposition: form-data; name="password"\r\n\r\n' +
|
||||||
options: [
|
pwd +
|
||||||
{ value: "baozimhcn.com" },
|
"\r\n------WebKitFormBoundaryFUNUxpOwyUaDop8s--\r\n"
|
||||||
{ value: "webmota.com" },
|
);
|
||||||
{ value: "kukuc.co" },
|
if (res.status !== 200) {
|
||||||
{ value: "twmanga.com" },
|
throw "Invalid status code: " + res.status;
|
||||||
{ value: "dinnerku.com" }
|
}
|
||||||
],
|
let json = JSON.parse(res.body);
|
||||||
default: "baozimhcn.com"
|
let token = json.data;
|
||||||
|
Network.setCookies(this.baseUrl, [
|
||||||
|
new Cookie({
|
||||||
|
name: "TSID",
|
||||||
|
value: token,
|
||||||
|
domain: this.loadSetting("domains") || this.settings.domains.default,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
return "ok";
|
||||||
|
},
|
||||||
|
|
||||||
|
// 退出登录时将会调用此函数
|
||||||
|
logout: function () {
|
||||||
|
Network.deleteCookies(
|
||||||
|
this.loadSetting("domains") || this.settings.domains.default
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
get registerWebsite() {
|
||||||
|
return `${this.baseUrl}/user/signup`;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 解析漫画列表
|
||||||
|
parseComic(e) {
|
||||||
|
let url = e.querySelector("a").attributes["href"];
|
||||||
|
let id = url.split("/").pop();
|
||||||
|
let title = e.querySelector("h3").text.trim();
|
||||||
|
let cover = e.querySelector("a > amp-img").attributes["src"];
|
||||||
|
let tags = e.querySelectorAll("div.tabs > span").map((e) => e.text.trim());
|
||||||
|
let description = e.querySelector("small").text.trim();
|
||||||
|
return {
|
||||||
|
id: id,
|
||||||
|
title: title,
|
||||||
|
cover: cover,
|
||||||
|
tags: tags,
|
||||||
|
description: description,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
parseJsonComic(e) {
|
||||||
|
return {
|
||||||
|
id: e.comic_id,
|
||||||
|
title: e.name,
|
||||||
|
subTitle: e.author,
|
||||||
|
cover: `https://static-tw.baozimh.com/cover/${e.topic_img}?w=285&h=375&q=100`,
|
||||||
|
tags: e.type_names,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 探索页面
|
||||||
|
/// 一个漫画源可以有多个探索页面
|
||||||
|
explore = [
|
||||||
|
{
|
||||||
|
/// 标题
|
||||||
|
/// 标题同时用作标识符, 不能重复
|
||||||
|
title: "包子漫画",
|
||||||
|
|
||||||
|
/// singlePageWithMultiPart 或者 multiPageComicList
|
||||||
|
type: "singlePageWithMultiPart",
|
||||||
|
|
||||||
|
load: async () => {
|
||||||
|
var res = await Network.get(this.baseUrl);
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw "Invalid status code: " + res.status;
|
||||||
}
|
}
|
||||||
}
|
let document = new HtmlDocument(res.body);
|
||||||
|
let parts = document.querySelectorAll("div.index-recommend-items");
|
||||||
// 动态生成完整域名
|
let result = {};
|
||||||
get lang() {
|
for (let part of parts) {
|
||||||
return this.loadSetting('language') || this.settings.language.default;
|
let title = part.querySelector("div.catalog-title").text.trim();
|
||||||
}
|
let comics = part
|
||||||
get baseUrl() {
|
.querySelectorAll("div.comics-card")
|
||||||
let domain = this.loadSetting('domains') || this.settings.domains.default;
|
.map((e) => this.parseComic(e));
|
||||||
return `https://${this.lang}.${domain}`;
|
if (comics.length > 0) {
|
||||||
}
|
result[title] = comics;
|
||||||
|
}
|
||||||
/// 账号
|
|
||||||
/// 设置为null禁用账号功能
|
|
||||||
account = {
|
|
||||||
/// 登录
|
|
||||||
/// 返回任意值表示登录成功
|
|
||||||
login: async (account, pwd) => {
|
|
||||||
let res = await Network.post(`${this.baseUrl}/api/bui/signin`, {
|
|
||||||
'content-type': 'multipart/form-data; boundary=----WebKitFormBoundaryFUNUxpOwyUaDop8s'
|
|
||||||
}, "------WebKitFormBoundaryFUNUxpOwyUaDop8s\r\nContent-Disposition: form-data; name=\"username\"\r\n\r\n" + account + "\r\n------WebKitFormBoundaryFUNUxpOwyUaDop8s\r\nContent-Disposition: form-data; name=\"password\"\r\n\r\n" + pwd + "\r\n------WebKitFormBoundaryFUNUxpOwyUaDop8s--\r\n")
|
|
||||||
if (res.status !== 200) {
|
|
||||||
throw "Invalid status code: " + res.status
|
|
||||||
}
|
|
||||||
let json = JSON.parse(res.body)
|
|
||||||
let token = json.data
|
|
||||||
Network.setCookies(this.baseUrl, [
|
|
||||||
new Cookie({
|
|
||||||
name: 'TSID',
|
|
||||||
value: token,
|
|
||||||
domain: this.loadSetting('domains') || this.settings.domains.default
|
|
||||||
}),
|
|
||||||
])
|
|
||||||
return 'ok'
|
|
||||||
},
|
|
||||||
|
|
||||||
// 退出登录时将会调用此函数
|
|
||||||
logout: function () {
|
|
||||||
Network.deleteCookies(this.loadSetting('domains') || this.settings.domains.default)
|
|
||||||
},
|
|
||||||
|
|
||||||
get registerWebsite() {
|
|
||||||
return `${this.baseUrl}/user/signup`
|
|
||||||
}
|
}
|
||||||
}
|
return result;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/// 分类页面
|
||||||
|
/// 一个漫画源只能有一个分类页面, 也可以没有, 设置为null禁用分类页面
|
||||||
|
category = {
|
||||||
|
/// 标题, 同时为标识符, 不能与其他漫画源的分类页面重复
|
||||||
|
title: "包子漫画",
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
name: "类型",
|
||||||
|
|
||||||
|
// fixed 或者 random
|
||||||
|
// random用于分类数量相当多时, 随机显示其中一部分
|
||||||
|
type: "fixed",
|
||||||
|
|
||||||
|
// 如果类型为random, 需要提供此字段, 表示同时显示的数量
|
||||||
|
// randomNumber: 5,
|
||||||
|
|
||||||
|
categories: [
|
||||||
|
"全部",
|
||||||
|
"恋爱",
|
||||||
|
"纯爱",
|
||||||
|
"古风",
|
||||||
|
"异能",
|
||||||
|
"悬疑",
|
||||||
|
"剧情",
|
||||||
|
"科幻",
|
||||||
|
"奇幻",
|
||||||
|
"玄幻",
|
||||||
|
"穿越",
|
||||||
|
"冒险",
|
||||||
|
"推理",
|
||||||
|
"武侠",
|
||||||
|
"格斗",
|
||||||
|
"战争",
|
||||||
|
"热血",
|
||||||
|
"搞笑",
|
||||||
|
"大女主",
|
||||||
|
"都市",
|
||||||
|
"总裁",
|
||||||
|
"后宫",
|
||||||
|
"日常",
|
||||||
|
"韩漫",
|
||||||
|
"少年",
|
||||||
|
"其它",
|
||||||
|
],
|
||||||
|
|
||||||
|
// category或者search
|
||||||
|
// 如果为category, 点击后将进入分类漫画页面, 使用下方的`categoryComics`加载漫画
|
||||||
|
// 如果为search, 将进入搜索页面
|
||||||
|
itemType: "category",
|
||||||
|
|
||||||
|
// 若提供, 数量需要和`categories`一致, `categoryComics.load`方法将会收到此参数
|
||||||
|
categoryParams: [
|
||||||
|
"all",
|
||||||
|
"lianai",
|
||||||
|
"chunai",
|
||||||
|
"gufeng",
|
||||||
|
"yineng",
|
||||||
|
"xuanyi",
|
||||||
|
"juqing",
|
||||||
|
"kehuan",
|
||||||
|
"qihuan",
|
||||||
|
"xuanhuan",
|
||||||
|
"chuanyue",
|
||||||
|
"mouxian",
|
||||||
|
"tuili",
|
||||||
|
"wuxia",
|
||||||
|
"gedou",
|
||||||
|
"zhanzheng",
|
||||||
|
"rexie",
|
||||||
|
"gaoxiao",
|
||||||
|
"danuzhu",
|
||||||
|
"dushi",
|
||||||
|
"zongcai",
|
||||||
|
"hougong",
|
||||||
|
"richang",
|
||||||
|
"hanman",
|
||||||
|
"shaonian",
|
||||||
|
"qita",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
enableRankingPage: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 分类漫画页面, 即点击分类标签后进入的页面
|
||||||
|
categoryComics = {
|
||||||
|
load: async (category, param, options, page) => {
|
||||||
|
let res = await Network.get(
|
||||||
|
`${this.baseUrl}/api/bzmhq/amp_comic_list?type=${param}®ion=${options[0]}&state=${options[1]}&filter=%2a&page=${page}&limit=36&language=${this.lang}&__amp_source_origin=${this.baseUrl}`
|
||||||
|
);
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw "Invalid status code: " + res.status;
|
||||||
|
}
|
||||||
|
let maxPage = null;
|
||||||
|
let json = JSON.parse(res.body);
|
||||||
|
if (!json.next) {
|
||||||
|
maxPage = page;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
comics: json.items.map((e) => this.parseJsonComic(e)),
|
||||||
|
maxPage: maxPage,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
// 提供选项
|
||||||
|
optionList: [
|
||||||
|
{
|
||||||
|
options: ["all-全部", "cn-国漫", "jp-日本", "kr-韩国", "en-欧美"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
options: ["all-全部", "serial-连载中", "pub-已完结"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 搜索
|
||||||
|
search = {
|
||||||
|
load: async (keyword, options, page) => {
|
||||||
|
let res = await Network.get(`${this.baseUrl}/search?q=${keyword}`);
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw "Invalid status code: " + res.status;
|
||||||
|
}
|
||||||
|
let document = new HtmlDocument(res.body);
|
||||||
|
let comics = document
|
||||||
|
.querySelectorAll("div.comics-card")
|
||||||
|
.map((e) => this.parseComic(e));
|
||||||
|
return {
|
||||||
|
comics: comics,
|
||||||
|
maxPage: 1,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
// 提供选项
|
||||||
|
optionList: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 收藏
|
||||||
|
favorites = {
|
||||||
|
/// 是否为多收藏夹
|
||||||
|
multiFolder: false,
|
||||||
|
/// 添加或者删除收藏
|
||||||
|
addOrDelFavorite: async (comicId, folderId, isAdding) => {
|
||||||
|
if (!isAdding) {
|
||||||
|
let res = await Network.post(
|
||||||
|
`${this.baseUrl}/user/operation_v2?op=del_bookmark&comic_id=${comicId}`
|
||||||
|
);
|
||||||
|
if (!res.status || res.status >= 400) {
|
||||||
|
throw "Invalid status code: " + res.status;
|
||||||
|
}
|
||||||
|
return "ok";
|
||||||
|
} else {
|
||||||
|
let res = await Network.post(
|
||||||
|
`${this.baseUrl}/user/operation_v2?op=set_bookmark&comic_id=${comicId}&chapter_slot=0`
|
||||||
|
);
|
||||||
|
if (!res.status || res.status >= 400) {
|
||||||
|
throw "Invalid status code: " + res.status;
|
||||||
|
}
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 加载收藏夹, 仅当multiFolder为true时有效
|
||||||
|
// 当comicId不为null时, 需要同时返回包含该漫画的收藏夹
|
||||||
|
loadFolders: null,
|
||||||
|
/// 加载漫画
|
||||||
|
loadComics: async (page, folder) => {
|
||||||
|
let res = await Network.get(`${this.baseUrl}/user/my_bookshelf`);
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw "Invalid status code: " + res.status;
|
||||||
|
}
|
||||||
|
let document = new HtmlDocument(res.body);
|
||||||
|
function parseComic(e) {
|
||||||
|
let title = e.querySelector("h4 > a").text.trim();
|
||||||
|
let url = e.querySelector("h4 > a").attributes["href"];
|
||||||
|
let id = url.split("/").pop();
|
||||||
|
let author = e
|
||||||
|
.querySelector("div.info > ul")
|
||||||
|
.children[1].text.split(":")[1]
|
||||||
|
.trim();
|
||||||
|
let description = e
|
||||||
|
.querySelector("div.info > ul")
|
||||||
|
.children[4].children[0].text.trim();
|
||||||
|
|
||||||
/// 解析漫画列表
|
|
||||||
parseComic(e) {
|
|
||||||
let url = e.querySelector("a").attributes['href']
|
|
||||||
let id = url.split("/").pop()
|
|
||||||
let title = e.querySelector("h3").text.trim()
|
|
||||||
let cover = e.querySelector("a > amp-img").attributes["src"]
|
|
||||||
let tags = e.querySelectorAll("div.tabs > span").map(e => e.text.trim())
|
|
||||||
let description = e.querySelector("small").text.trim()
|
|
||||||
return {
|
return {
|
||||||
|
id: id,
|
||||||
|
title: title,
|
||||||
|
subTitle: author,
|
||||||
|
description: description,
|
||||||
|
cover: e.querySelector("amp-img").attributes["src"],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let comics = document
|
||||||
|
.querySelectorAll("div.bookshelf-items")
|
||||||
|
.map((e) => parseComic(e));
|
||||||
|
return {
|
||||||
|
comics: comics,
|
||||||
|
maxPage: 1,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 单个漫画相关
|
||||||
|
comic = {
|
||||||
|
// 加载漫画信息
|
||||||
|
loadInfo: async (id) => {
|
||||||
|
let res = await Network.get(`${this.baseUrl}/comic/${id}`);
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw "Invalid status code: " + res.status;
|
||||||
|
}
|
||||||
|
let document = new HtmlDocument(res.body);
|
||||||
|
|
||||||
|
let title = document.querySelector("h1.comics-detail__title").text.trim();
|
||||||
|
let cover = document.querySelector("div.l-content > div > div > amp-img")
|
||||||
|
.attributes["src"];
|
||||||
|
let author = document
|
||||||
|
.querySelector("h2.comics-detail__author")
|
||||||
|
.text.trim();
|
||||||
|
let tags = document
|
||||||
|
.querySelectorAll("div.tag-list > span")
|
||||||
|
.map((e) => e.text.trim());
|
||||||
|
tags = [...tags.filter((e) => e !== "")];
|
||||||
|
let updateTime = document
|
||||||
|
.querySelector("div.supporting-text > div > span > em")
|
||||||
|
?.text.trim()
|
||||||
|
.replace("(", "")
|
||||||
|
.replace(")", "");
|
||||||
|
if (!updateTime) {
|
||||||
|
const getLastChapterText = () => {
|
||||||
|
// 合并所有章节容器(处理可能存在多个列表的情况)
|
||||||
|
const containers = [
|
||||||
|
...document.querySelectorAll(
|
||||||
|
"#chapter-items, #chapters_other_list"
|
||||||
|
),
|
||||||
|
];
|
||||||
|
let allChapters = [];
|
||||||
|
containers.forEach((container) => {
|
||||||
|
const chapters = container.querySelectorAll(".comics-chapters > a");
|
||||||
|
allChapters.push(...Array.from(chapters));
|
||||||
|
});
|
||||||
|
const lastChapter = allChapters[allChapters.length - 1];
|
||||||
|
return (
|
||||||
|
lastChapter?.querySelector("div > span")?.text.trim() ||
|
||||||
|
"暂无更新信息"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
updateTime = getLastChapterText();
|
||||||
|
}
|
||||||
|
let description = document
|
||||||
|
.querySelector("p.comics-detail__desc")
|
||||||
|
.text.trim();
|
||||||
|
let chapters = new Map();
|
||||||
|
let i = 0;
|
||||||
|
for (let c of document.querySelectorAll(
|
||||||
|
"div#chapter-items > div.comics-chapters > a > div > span"
|
||||||
|
)) {
|
||||||
|
chapters.set(i.toString(), c.text.trim());
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
for (let c of document.querySelectorAll(
|
||||||
|
"div#chapters_other_list > div.comics-chapters > a > div > span"
|
||||||
|
)) {
|
||||||
|
chapters.set(i.toString(), c.text.trim());
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if (i === 0) {
|
||||||
|
// 将倒序的最新章节反转
|
||||||
|
const spans = Array.from(
|
||||||
|
document.querySelectorAll("div.comics-chapters > a > div > span")
|
||||||
|
).reverse();
|
||||||
|
for (let c of spans) {
|
||||||
|
chapters.set(i.toString(), c.text.trim());
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let recommend = [];
|
||||||
|
for (let c of document.querySelectorAll("div.recommend--item")) {
|
||||||
|
if (c.querySelectorAll("div.tag-comic").length > 0) {
|
||||||
|
let title = c.querySelector("span").text.trim();
|
||||||
|
let cover = c.querySelector("amp-img").attributes["src"];
|
||||||
|
let url = c.querySelector("a").attributes["href"];
|
||||||
|
let id = url.split("/").pop();
|
||||||
|
recommend.push({
|
||||||
id: id,
|
id: id,
|
||||||
title: title,
|
title: title,
|
||||||
cover: cover,
|
cover: cover,
|
||||||
tags: tags,
|
});
|
||||||
description: description
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// updateTime 将 Y年 M月 D日 转化为 Y-M-D
|
||||||
|
let updateDate = updateTime
|
||||||
|
.replace(/年/g, "-")
|
||||||
|
.replace(/月/g, "-")
|
||||||
|
.replace(/日/g, "");
|
||||||
|
|
||||||
parseJsonComic(e) {
|
return new ComicDetails({
|
||||||
return {
|
title: title,
|
||||||
id: e.comic_id,
|
cover: cover,
|
||||||
title: e.name,
|
description: description,
|
||||||
subTitle: e.author,
|
tags: {
|
||||||
cover: `https://static-tw.baozimh.com/cover/${e.topic_img}?w=285&h=375&q=100`,
|
作者: [author],
|
||||||
tags: e.type_names,
|
标签: tags,
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 探索页面
|
|
||||||
/// 一个漫画源可以有多个探索页面
|
|
||||||
explore = [{
|
|
||||||
/// 标题
|
|
||||||
/// 标题同时用作标识符, 不能重复
|
|
||||||
title: "包子漫画",
|
|
||||||
|
|
||||||
/// singlePageWithMultiPart 或者 multiPageComicList
|
|
||||||
type: "singlePageWithMultiPart",
|
|
||||||
|
|
||||||
load: async () => {
|
|
||||||
var res = await Network.get(this.baseUrl)
|
|
||||||
if (res.status !== 200) {
|
|
||||||
throw "Invalid status code: " + res.status
|
|
||||||
}
|
|
||||||
let document = new HtmlDocument(res.body)
|
|
||||||
let parts = document.querySelectorAll("div.index-recommend-items")
|
|
||||||
let result = {}
|
|
||||||
for (let part of parts) {
|
|
||||||
let title = part.querySelector("div.catalog-title").text.trim()
|
|
||||||
let comics = part.querySelectorAll("div.comics-card").map(e => this.parseComic(e))
|
|
||||||
if (comics.length > 0) {
|
|
||||||
result[title] = comics
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
/// 分类页面
|
|
||||||
/// 一个漫画源只能有一个分类页面, 也可以没有, 设置为null禁用分类页面
|
|
||||||
category = {
|
|
||||||
/// 标题, 同时为标识符, 不能与其他漫画源的分类页面重复
|
|
||||||
title: "包子漫画",
|
|
||||||
parts: [{
|
|
||||||
name: "类型",
|
|
||||||
|
|
||||||
// fixed 或者 random
|
|
||||||
// random用于分类数量相当多时, 随机显示其中一部分
|
|
||||||
type: "fixed",
|
|
||||||
|
|
||||||
// 如果类型为random, 需要提供此字段, 表示同时显示的数量
|
|
||||||
// randomNumber: 5,
|
|
||||||
|
|
||||||
categories: ['全部', '恋爱', '纯爱', '古风', '异能', '悬疑', '剧情', '科幻', '奇幻', '玄幻', '穿越', '冒险', '推理', '武侠', '格斗', '战争', '热血', '搞笑', '大女主', '都市', '总裁', '后宫', '日常', '韩漫', '少年', '其它'],
|
|
||||||
|
|
||||||
// category或者search
|
|
||||||
// 如果为category, 点击后将进入分类漫画页面, 使用下方的`categoryComics`加载漫画
|
|
||||||
// 如果为search, 将进入搜索页面
|
|
||||||
itemType: "category",
|
|
||||||
|
|
||||||
// 若提供, 数量需要和`categories`一致, `categoryComics.load`方法将会收到此参数
|
|
||||||
categoryParams: ['all', 'lianai', 'chunai', 'gufeng', 'yineng', 'xuanyi', 'juqing', 'kehuan', 'qihuan', 'xuanhuan', 'chuanyue', 'mouxian', 'tuili', 'wuxia', 'gedou', 'zhanzheng', 'rexie', 'gaoxiao', 'danuzhu', 'dushi', 'zongcai', 'hougong', 'richang', 'hanman', 'shaonian', 'qita']
|
|
||||||
}
|
|
||||||
],
|
|
||||||
enableRankingPage: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 分类漫画页面, 即点击分类标签后进入的页面
|
|
||||||
categoryComics = {
|
|
||||||
load: async (category, param, options, page) => {
|
|
||||||
let res = await Network.get(`${this.baseUrl}/api/bzmhq/amp_comic_list?type=${param}®ion=${options[0]}&state=${options[1]}&filter=%2a&page=${page}&limit=36&language=${this.lang}&__amp_source_origin=${this.baseUrl}`)
|
|
||||||
if (res.status !== 200) {
|
|
||||||
throw "Invalid status code: " + res.status
|
|
||||||
}
|
|
||||||
let maxPage = null
|
|
||||||
let json = JSON.parse(res.body)
|
|
||||||
if (!json.next) {
|
|
||||||
maxPage = page
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
comics: json.items.map(e => this.parseJsonComic(e)),
|
|
||||||
maxPage: maxPage
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
// 提供选项
|
chapters: chapters,
|
||||||
optionList: [{
|
recommend: recommend,
|
||||||
options: [
|
updateTime: updateDate,
|
||||||
"all-全部",
|
});
|
||||||
"cn-国漫",
|
},
|
||||||
"jp-日本",
|
loadEp: async (comicId, epId) => {
|
||||||
"kr-韩国",
|
const images = [];
|
||||||
"en-欧美",
|
let currentPageUrl = `${this.baseUrl}/comic/chapter/${comicId}/0_${epId}.html`;
|
||||||
],
|
let maxAttempts = 100;
|
||||||
}, {
|
|
||||||
options: [
|
|
||||||
"all-全部",
|
|
||||||
"serial-连载中",
|
|
||||||
"pub-已完结",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 搜索
|
while (maxAttempts > 0) {
|
||||||
search = {
|
const res = await Network.get(currentPageUrl);
|
||||||
load: async (keyword, options, page) => {
|
if (res.status !== 200) break;
|
||||||
let res = await Network.get(`${this.baseUrl}/search?q=${keyword}`)
|
|
||||||
if (res.status !== 200) {
|
|
||||||
throw "Invalid status code: " + res.status
|
|
||||||
}
|
|
||||||
let document = new HtmlDocument(res.body)
|
|
||||||
let comics = document.querySelectorAll("div.comics-card").map(e => this.parseComic(e))
|
|
||||||
return {
|
|
||||||
comics: comics,
|
|
||||||
maxPage: 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// 提供选项
|
// 解析当前页图片
|
||||||
optionList: []
|
const doc = new HtmlDocument(res.body);
|
||||||
}
|
doc
|
||||||
|
.querySelectorAll("ul.comic-contain > div > amp-img")
|
||||||
|
.forEach((img) => {
|
||||||
|
const src = img?.attributes?.["src"];
|
||||||
|
if (typeof src === "string") images.push(src);
|
||||||
|
});
|
||||||
|
|
||||||
/// 收藏
|
// 查找下一页链接
|
||||||
favorites = {
|
const nextLink = doc.querySelector("a#next-chapter");
|
||||||
/// 是否为多收藏夹
|
if (nextLink?.text?.match(/下一页|下一頁/)) {
|
||||||
multiFolder: false,
|
currentPageUrl = nextLink.attributes["href"];
|
||||||
/// 添加或者删除收藏
|
} else {
|
||||||
addOrDelFavorite: async (comicId, folderId, isAdding) => {
|
break;
|
||||||
if (!isAdding) {
|
|
||||||
let res = await Network.post(`${this.baseUrl}/user/operation_v2?op=del_bookmark&comic_id=${comicId}`)
|
|
||||||
if (!res.status || res.status >= 400) {
|
|
||||||
throw "Invalid status code: " + res.status
|
|
||||||
}
|
|
||||||
return 'ok'
|
|
||||||
} else {
|
|
||||||
let res = await Network.post(`${this.baseUrl}/user/operation_v2?op=set_bookmark&comic_id=${comicId}&chapter_slot=0`)
|
|
||||||
if (!res.status || res.status >= 400) {
|
|
||||||
throw "Invalid status code: " + res.status
|
|
||||||
}
|
|
||||||
return 'ok'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 加载收藏夹, 仅当multiFolder为true时有效
|
|
||||||
// 当comicId不为null时, 需要同时返回包含该漫画的收藏夹
|
|
||||||
loadFolders: null,
|
|
||||||
/// 加载漫画
|
|
||||||
loadComics: async (page, folder) => {
|
|
||||||
let res = await Network.get(`${this.baseUrl}/user/my_bookshelf`)
|
|
||||||
if (res.status !== 200) {
|
|
||||||
throw "Invalid status code: " + res.status
|
|
||||||
}
|
|
||||||
let document = new HtmlDocument(res.body)
|
|
||||||
function parseComic(e) {
|
|
||||||
let title = e.querySelector("h4 > a").text.trim()
|
|
||||||
let url = e.querySelector("h4 > a").attributes['href']
|
|
||||||
let id = url.split("/").pop()
|
|
||||||
let author = e.querySelector("div.info > ul").children[1].text.split(":")[1].trim()
|
|
||||||
let description = e.querySelector("div.info > ul").children[4].children[0].text.trim()
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: id,
|
|
||||||
title: title,
|
|
||||||
subTitle: author,
|
|
||||||
description: description,
|
|
||||||
cover: e.querySelector("amp-img").attributes['src']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let comics = document.querySelectorAll("div.bookshelf-items").map(e => parseComic(e))
|
|
||||||
return {
|
|
||||||
comics: comics,
|
|
||||||
maxPage: 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
maxAttempts--;
|
||||||
|
}
|
||||||
/// 单个漫画相关
|
// 代理后图片水印更少
|
||||||
comic = {
|
let mobileImages = images.map((e) => {
|
||||||
// 加载漫画信息
|
const regex = /scomic\/.*/;
|
||||||
loadInfo: async (id) => {
|
const match = e.match(regex);
|
||||||
let res = await Network.get(`${this.baseUrl}/comic/${id}`)
|
return `https://as-rsa1-usla.baozicdn.com/w640/${match[0]}`;
|
||||||
if (res.status !== 200) {
|
});
|
||||||
throw "Invalid status code: " + res.status
|
return { images: mobileImages };
|
||||||
}
|
},
|
||||||
let document = new HtmlDocument(res.body)
|
};
|
||||||
|
|
||||||
let title = document.querySelector("h1.comics-detail__title").text.trim()
|
|
||||||
let cover = document.querySelector("div.l-content > div > div > amp-img").attributes['src']
|
|
||||||
let author = document.querySelector("h2.comics-detail__author").text.trim()
|
|
||||||
let tags = document.querySelectorAll("div.tag-list > span").map(e => e.text.trim())
|
|
||||||
tags = [...tags.filter(e => e !== "")]
|
|
||||||
let updateTime = document.querySelector("div.supporting-text > div > span > em")?.text.trim().replace('(', '').replace(')', '')
|
|
||||||
if (!updateTime) {
|
|
||||||
const getLastChapterText = () => {
|
|
||||||
// 合并所有章节容器(处理可能存在多个列表的情况)
|
|
||||||
const containers = [
|
|
||||||
...document.querySelectorAll("#chapter-items, #chapters_other_list")
|
|
||||||
];
|
|
||||||
let allChapters = [];
|
|
||||||
containers.forEach(container => {
|
|
||||||
const chapters = container.querySelectorAll(".comics-chapters > a");
|
|
||||||
allChapters.push(...Array.from(chapters));
|
|
||||||
});
|
|
||||||
const lastChapter = allChapters[allChapters.length - 1];
|
|
||||||
return lastChapter?.querySelector("div > span")?.text.trim() || "暂无更新信息";
|
|
||||||
};
|
|
||||||
updateTime = getLastChapterText();
|
|
||||||
}
|
|
||||||
let description = document.querySelector("p.comics-detail__desc").text.trim()
|
|
||||||
let chapters = new Map()
|
|
||||||
let i = 0
|
|
||||||
for (let c of document.querySelectorAll("div#chapter-items > div.comics-chapters > a > div > span")) {
|
|
||||||
chapters.set(i.toString(), c.text.trim())
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
for (let c of document.querySelectorAll("div#chapters_other_list > div.comics-chapters > a > div > span")) {
|
|
||||||
chapters.set(i.toString(), c.text.trim())
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
if (i === 0) {
|
|
||||||
// 将倒序的最新章节反转
|
|
||||||
const spans = Array.from(document.querySelectorAll("div.comics-chapters > a > div > span")).reverse();
|
|
||||||
for (let c of spans) {
|
|
||||||
chapters.set(i.toString(), c.text.trim());
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let recommend = []
|
|
||||||
for (let c of document.querySelectorAll("div.recommend--item")) {
|
|
||||||
if (c.querySelectorAll("div.tag-comic").length > 0) {
|
|
||||||
let title = c.querySelector("span").text.trim()
|
|
||||||
let cover = c.querySelector("amp-img").attributes['src']
|
|
||||||
let url = c.querySelector("a").attributes['href']
|
|
||||||
let id = url.split("/").pop()
|
|
||||||
recommend.push({
|
|
||||||
id: id,
|
|
||||||
title: title,
|
|
||||||
cover: cover
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// updateTime 将 Y年 M月 D日 转化为 Y-M-D
|
|
||||||
let updateDate = updateTime.replace(/年/g, '-').replace(/月/g, '-').replace(/日/g, '');
|
|
||||||
|
|
||||||
return new ComicDetails({
|
|
||||||
title: title,
|
|
||||||
cover: cover,
|
|
||||||
description: description,
|
|
||||||
tags: {
|
|
||||||
"作者": [author],
|
|
||||||
"标签": tags
|
|
||||||
},
|
|
||||||
chapters: chapters,
|
|
||||||
recommend: recommend,
|
|
||||||
updateTime: updateDate,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
loadEp: async (comicId, epId) => {
|
|
||||||
const images = [];
|
|
||||||
let currentPageUrl = `${this.baseUrl}/comic/chapter/${comicId}/0_${epId}.html`;
|
|
||||||
let maxAttempts = 100;
|
|
||||||
|
|
||||||
while (maxAttempts > 0) {
|
|
||||||
const res = await Network.get(currentPageUrl);
|
|
||||||
if (res.status !== 200) break;
|
|
||||||
|
|
||||||
// 解析当前页图片
|
|
||||||
const doc = new HtmlDocument(res.body);
|
|
||||||
doc.querySelectorAll("ul.comic-contain > div > amp-img").forEach(img => {
|
|
||||||
const src = img?.attributes?.['src'];
|
|
||||||
if (typeof src === 'string') images.push(src);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 查找下一页链接
|
|
||||||
const nextLink = doc.querySelector("a#next-chapter");
|
|
||||||
if (nextLink?.text?.match(/下一页|下一頁/)) {
|
|
||||||
currentPageUrl = nextLink.attributes['href'];
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
maxAttempts--;
|
|
||||||
}
|
|
||||||
// 代理后图片水印更少
|
|
||||||
return { images };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
165
comick.js
165
comick.js
@@ -1,10 +1,10 @@
|
|||||||
class Comick extends ComicSource {
|
class Comick extends ComicSource {
|
||||||
name = "comick"
|
name = "comick"
|
||||||
key = "comick"
|
key = "comick"
|
||||||
version = "1.0.1"
|
version = "1.1.1"
|
||||||
minAppVersion = "1.4.0"
|
minAppVersion = "1.4.0"
|
||||||
// update url
|
// update url
|
||||||
url = "https://cdn.jsdelivr.net/gh/venera-app/venera-configs@main/comick.js"
|
url = "https://git.nyne.dev/nyne/venera-configs/raw/branch/main/comick.js"
|
||||||
|
|
||||||
settings = {
|
settings = {
|
||||||
domains: {
|
domains: {
|
||||||
@@ -340,11 +340,28 @@ class Comick extends ComicSource {
|
|||||||
'拉脱维亚文': 'lv'
|
'拉脱维亚文': 'lv'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getRandomHeaders() {
|
||||||
|
const userAgents = [
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1",
|
||||||
|
"Mozilla/5.0 (Linux; Android 10; SM-G973F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
||||||
|
];
|
||||||
|
|
||||||
|
return {
|
||||||
|
"User-Agent": userAgents[Math.floor(Math.random() * userAgents.length)],
|
||||||
|
"Accept": "application/json, text/plain, */*",
|
||||||
|
"Accept-Language": "en-US,en;q=0.9",
|
||||||
|
"Connection": "keep-alive"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
transReformBookList(bookList, descriptionPrefix = "更新至:") {
|
transReformBookList(bookList, descriptionPrefix = "更新至:") {
|
||||||
return bookList.map(book => ({
|
return bookList.map(book => ({
|
||||||
id: `${book.relates.slug}//${book.relates.title}`,
|
id: `${book.relates?.slug || 'unknown'}//${book.relates?.title || '未知标题'}`,
|
||||||
title: book.relates.title,
|
title: book.relates?.title || '未知标题',
|
||||||
cover: book.relates.md_covers?.[0]?.b2key
|
cover: book.relates?.md_covers?.[0]?.b2key
|
||||||
? `https://meo.comick.pictures/${book.relates.md_covers[0].b2key}`
|
? `https://meo.comick.pictures/${book.relates.md_covers[0].b2key}`
|
||||||
: 'w7xqzd.jpg',
|
: 'w7xqzd.jpg',
|
||||||
}));
|
}));
|
||||||
@@ -352,8 +369,8 @@ class Comick extends ComicSource {
|
|||||||
|
|
||||||
transformBookList(bookList, descriptionPrefix = "更新至:") {
|
transformBookList(bookList, descriptionPrefix = "更新至:") {
|
||||||
return bookList.map(book => ({
|
return bookList.map(book => ({
|
||||||
id: `${book.slug}//${book.title}`,
|
id: `${book.slug || 'unknown'}//${book.title || '未知标题'}`,
|
||||||
title: book.title,
|
title: book.title || '未知标题',
|
||||||
cover: book.md_covers?.[0]?.b2key
|
cover: book.md_covers?.[0]?.b2key
|
||||||
? `https://meo.comick.pictures/${book.md_covers[0].b2key}`
|
? `https://meo.comick.pictures/${book.md_covers[0].b2key}`
|
||||||
: 'w7xqzd.jpg',
|
: 'w7xqzd.jpg',
|
||||||
@@ -364,12 +381,14 @@ class Comick extends ComicSource {
|
|||||||
|
|
||||||
getFormattedManga(manga) {
|
getFormattedManga(manga) {
|
||||||
return {
|
return {
|
||||||
id: `${manga.slug}//${manga.title}`,
|
id: `${manga.slug || 'unknown'}//${manga.title || '未知标题'}`,
|
||||||
title: manga.title || "无标题",
|
title: manga.title || "无标题",
|
||||||
cover: manga.md_covers?.[0]?.b2key
|
cover: manga.md_covers?.[0]?.b2key
|
||||||
? `https://meo.comick.pictures/${manga.md_covers[0].b2key}`
|
? `https://meo.comick.pictures/${manga.md_covers[0].b2key}`
|
||||||
: 'w7xqzd.jpg',
|
: 'w7xqzd.jpg',
|
||||||
tags: [],
|
tags: [
|
||||||
|
`更新时间: ${manga.uploaded_at ? new Date(manga.uploaded_at).toISOString().split('T')[0] : ''}`
|
||||||
|
],
|
||||||
description: manga.desc || "暂无描述"
|
description: manga.desc || "暂无描述"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -424,12 +443,16 @@ class Comick extends ComicSource {
|
|||||||
`page=${encodeURIComponent(page)}`
|
`page=${encodeURIComponent(page)}`
|
||||||
];
|
];
|
||||||
|
|
||||||
if (options[0] && options[0] !== "-全部") {
|
if (options[0]) {
|
||||||
params.push(`country=${encodeURIComponent(options[0].split("-")[0])}`);
|
params.push(`sort=${encodeURIComponent(options[0].split("-")[0])}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options[1]) {
|
if (options[1] && options[1] !== "-全部") {
|
||||||
params.push(`status=${encodeURIComponent(options[1].split("-")[0])}`);
|
params.push(`country=${encodeURIComponent(options[1].split("-")[0])}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options[2]) {
|
||||||
|
params.push(`status=${encodeURIComponent(options[2].split("-")[0])}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
url += params.join('&');
|
url += params.join('&');
|
||||||
@@ -446,6 +469,7 @@ class Comick extends ComicSource {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
optionList: [
|
optionList: [
|
||||||
|
{options: ["uploaded-更新排序","user_follow_count-关注排序", "rating-评分排序", "created_at-创建排序"]},
|
||||||
{options: ["-全部", "cn-国漫", "jp-日本", "kr-韩国", "others-欧美"]},
|
{options: ["-全部", "cn-国漫", "jp-日本", "kr-韩国", "others-欧美"]},
|
||||||
{options: ["1-连载", "2-完结", "3-休刊", "4-暂停更新"]}
|
{options: ["1-连载", "2-完结", "3-休刊", "4-暂停更新"]}
|
||||||
]
|
]
|
||||||
@@ -475,12 +499,15 @@ class Comick extends ComicSource {
|
|||||||
buildId: null,
|
buildId: null,
|
||||||
|
|
||||||
loadInfo: async (id) => {
|
loadInfo: async (id) => {
|
||||||
|
let headers = Comick.getRandomHeaders();
|
||||||
|
|
||||||
|
|
||||||
const [cId, cTitle] = id.split("//");
|
const [cId, cTitle] = id.split("//");
|
||||||
if (!cId) {
|
if (!cId) {
|
||||||
throw "ID error: ";
|
throw "ID error: ";
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = await Network.get(`${this.baseUrl}/comic/${cId}`)
|
let res = await Network.get(`${this.baseUrl}/comic/${cId}`, { headers });
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
throw "Invalid status code: " + res.status
|
throw "Invalid status code: " + res.status
|
||||||
}
|
}
|
||||||
@@ -511,32 +538,46 @@ class Comick extends ComicSource {
|
|||||||
const result = {};
|
const result = {};
|
||||||
let updateTime = "";
|
let updateTime = "";
|
||||||
let i = 1;
|
let i = 1;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
for (const lang of langs) {
|
for (const lang of langs) {
|
||||||
|
// 随机生成请求头
|
||||||
|
let headers = Comick.getRandomHeaders();
|
||||||
|
|
||||||
let first = langMap[lang];
|
let first = langMap[lang];
|
||||||
if (first.vol == null && first.chap == null) {
|
if (first.vol == null && first.chap == null) {
|
||||||
const chapters = new Map();
|
const chapters = new Map();
|
||||||
chapters.set(`${first.hid}//no//-1//${first.lang}`, '无标卷');
|
chapters.set(`${first.hid || 'unknown'}//no//-1//${first.lang || 'unknown'}`, '无标卷');
|
||||||
result[Comick.language_dict[lang] || lang] = chapters;
|
result[Comick.language_dict[lang] || lang] = chapters;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 构造章节请求 URL
|
// 3. 构造章节请求 URL
|
||||||
const url =
|
const url =
|
||||||
`${this.baseUrl}/_next/data/${buildId}/comic/${id}/${first.hid}` +
|
`${this.baseUrl}/_next/data/${buildId}/comic/${id}/${first.hid || 'unknown'}` +
|
||||||
(first.chap != null
|
(first.chap != null
|
||||||
? `-chapter-${first.chap}`
|
? `-chapter-${first.chap}`
|
||||||
: `-volume-${first.vol}`) +
|
: `-volume-${first.vol}`) +
|
||||||
`-${lang}.json`;
|
`-${lang}.json?slug=${id}&` +
|
||||||
|
(first.chap != null
|
||||||
|
? `chapter=${first.hid || 'unknown'}`
|
||||||
|
: `volume=${first.hid || 'unknown'}`)
|
||||||
|
+
|
||||||
|
(first.chap != null
|
||||||
|
? `-chapter-${first.chap}`
|
||||||
|
: `-volume-${first.vol}`) + `-${lang}`
|
||||||
|
;
|
||||||
|
|
||||||
const res = await Network.get(url);
|
const res = await Network.get(url, { headers });
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
throw `Invalid status code: ${res.status}`;
|
throw `Invalid status code: ${res.status}`;
|
||||||
}
|
}
|
||||||
const raw = JSON.parse(res.body);
|
const raw = JSON.parse(res.body);
|
||||||
if(i==1){
|
if(i==1){
|
||||||
//获得更新时间:
|
//获得更新时间:
|
||||||
updateTime = raw.pageProps.chapter.updated_at
|
updateTime = raw.pageProps?.chapter?.updated_at
|
||||||
? raw.pageProps.chapter.updated_at.split('T')[0] : comicData.last_chapter
|
? raw.pageProps.chapter.updated_at.split('T')[0] : comicData?.last_chapter
|
||||||
? `第${comicData.last_chapter}话`: " ";
|
? `第${comicData.last_chapter}话`: " ";
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
@@ -548,13 +589,13 @@ class Comick extends ComicSource {
|
|||||||
list.forEach(ch => {
|
list.forEach(ch => {
|
||||||
let key, label;
|
let key, label;
|
||||||
if (ch.chap == null && ch.vol == null) {
|
if (ch.chap == null && ch.vol == null) {
|
||||||
key = `${ch.hid}//no//-1//${first.lang}`;
|
key = `${ch.hid || 'unknown'}//no//-1//${first.lang || 'unknown'}`;
|
||||||
label = '无标卷';
|
label = '无标卷';
|
||||||
} else if (ch.chap != null) {
|
} else if (ch.chap != null) {
|
||||||
key = `${ch.hid}//chapter//${ch.chap}//${first.lang}`;
|
key = `${ch.hid || 'unknown'}//chapter//${ch.chap}//${first.lang || 'unknown'}`;
|
||||||
label = `第${ch.chap}话`;
|
label = `第${ch.chap}话`;
|
||||||
} else {
|
} else {
|
||||||
key = `${ch.hid}//volume//${ch.vol}//${first.lang}`;
|
key = `${ch.hid || 'unknown'}//volume//${ch.vol}//${first.lang || 'unknown'}`;
|
||||||
label = `第${ch.vol}卷`;
|
label = `第${ch.vol}卷`;
|
||||||
}
|
}
|
||||||
chapters.set(key, label);
|
chapters.set(key, label);
|
||||||
@@ -571,20 +612,23 @@ class Comick extends ComicSource {
|
|||||||
this.comic.id = id;
|
this.comic.id = id;
|
||||||
let document = new HtmlDocument(res.body)
|
let document = new HtmlDocument(res.body)
|
||||||
let jsonData = JSON.parse(document.getElementById('__NEXT_DATA__').text);
|
let jsonData = JSON.parse(document.getElementById('__NEXT_DATA__').text);
|
||||||
let comicData = jsonData.props.pageProps.comic;
|
let comicData = jsonData.props?.pageProps?.comic;
|
||||||
let authorData = jsonData.props.pageProps.authors;
|
let authorData = jsonData.props?.pageProps?.authors || [];
|
||||||
let title = cTitle? cTitle:comicData?.title|| "未知标题";
|
let title = cTitle || comicData?.title || "未知标题";
|
||||||
let status = comicData?.status || "1"; // 默认连载
|
let status = comicData?.status || "1"; // 默认连载
|
||||||
let cover = comicData.md_covers?.[0]?.b2key ? `https://meo.comick.pictures/${comicData.md_covers[0].b2key}` : 'w7xqzd.jpg';
|
let cover = comicData?.md_covers?.[0]?.b2key ? `https://meo.comick.pictures/${comicData.md_covers[0].b2key}` : 'w7xqzd.jpg';
|
||||||
let author = authorData[0]?.name || "未知作者";
|
let author = authorData[0]?.name || "未知作者";
|
||||||
|
|
||||||
// 提取标签的slug数组的代码
|
// 提取标签的slug数组的代码
|
||||||
let extractSlugs = (comicData) => {
|
let extractSlugs = (comicData) => {
|
||||||
try {
|
try {
|
||||||
// 获取md_comic_md_genres数组
|
// 获取md_comic_md_genres数组
|
||||||
const genres = comicData.md_comic_md_genres;
|
const genres = comicData?.md_comic_md_genres;
|
||||||
|
if (!genres || !Array.isArray(genres)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
// 使用map提取每个md_genres中的slug
|
// 使用map提取每个md_genres中的slug
|
||||||
const slugs = genres.map(genre => genre.md_genres.slug);
|
const slugs = genres.map(genre => genre?.md_genres?.slug).filter(slug => slug != null);
|
||||||
return slugs;
|
return slugs;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return []; // 返回空数组作为容错处理
|
return []; // 返回空数组作为容错处理
|
||||||
@@ -596,17 +640,17 @@ class Comick extends ComicSource {
|
|||||||
const translatedTags = tags.map(tag => {
|
const translatedTags = tags.map(tag => {
|
||||||
return Comick.category_param_dict[tag] || tag; // 如果字典里没有,就返回原值
|
return Comick.category_param_dict[tag] || tag; // 如果字典里没有,就返回原值
|
||||||
});
|
});
|
||||||
let description = comicData.desc || "暂无描述";
|
let description = comicData?.desc || "暂无描述";
|
||||||
|
|
||||||
//处理推荐列表
|
//处理推荐列表
|
||||||
let recommends = this.transReformBookList(comicData.recommendations!=null?comicData.recommendations:[]);
|
let recommends = this.transReformBookList(comicData?.recommendations || []);
|
||||||
//只要recommends数组前面十个,不够十个则就是recommends的长度
|
//只要recommends数组前面十个,不够十个则就是recommends的长度
|
||||||
recommends = recommends.slice(0, Math.min(recommends.length, 10));
|
recommends = recommends.slice(0, Math.min(recommends.length, 10));
|
||||||
|
|
||||||
//处理空漫画
|
//处理空漫画
|
||||||
let firstChapters = jsonData.props.pageProps.firstChapters;
|
let firstChapters = jsonData.props?.pageProps?.firstChapters || [];
|
||||||
|
|
||||||
if(comicData.chapter_count == 0 && (firstChapters==null||firstChapters.length==0)){
|
if((comicData?.chapter_count == 0 || !comicData?.chapter_count) && firstChapters.length == 0){
|
||||||
let chapters = new Map()
|
let chapters = new Map()
|
||||||
return {
|
return {
|
||||||
title: title,
|
title: title,
|
||||||
@@ -624,9 +668,28 @@ class Comick extends ComicSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// let updateTime = comicData.last_chapter ? "第" + comicData.last_chapter + "话" : " "; //这里目前还无法实现更新时间
|
// let updateTime = comicData.last_chapter ? "第" + comicData.last_chapter + "话" : " "; //这里目前还无法实现更新时间
|
||||||
let buildId = jsonData.buildId;
|
let buildId = jsonData?.buildId;
|
||||||
let slug = jsonData.query.slug;
|
let slug = jsonData?.query?.slug;
|
||||||
let firstChapter = jsonData.props.pageProps.firstChapters[0];
|
let firstChapter = firstChapters.length > 0 ? firstChapters[0] : null;
|
||||||
|
|
||||||
|
// 处理无章节的情况
|
||||||
|
if (!firstChapter) {
|
||||||
|
let chapters = new Map();
|
||||||
|
let updateTime = comicData?.last_chapter ? "第" + comicData.last_chapter + "话" : "暂无更新";
|
||||||
|
return {
|
||||||
|
title: title,
|
||||||
|
cover: cover,
|
||||||
|
description: description,
|
||||||
|
tags: {
|
||||||
|
"作者": [author],
|
||||||
|
"更新": [updateTime],
|
||||||
|
"标签": translatedTags,
|
||||||
|
"状态": [Comick.comic_status[status]]
|
||||||
|
},
|
||||||
|
chapters: chapters,
|
||||||
|
recommend: recommends || []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 处理无标卷和无标话的情况
|
// 处理无标卷和无标话的情况
|
||||||
if(firstChapter.vol == null && firstChapter.chap == null){
|
if(firstChapter.vol == null && firstChapter.chap == null){
|
||||||
@@ -639,7 +702,8 @@ class Comick extends ComicSource {
|
|||||||
// 如果处理完成之后依然章节没有卷和话信息,直接返回无标卷
|
// 如果处理完成之后依然章节没有卷和话信息,直接返回无标卷
|
||||||
if(firstChapter.vol == null && firstChapter.chap == null){
|
if(firstChapter.vol == null && firstChapter.chap == null){
|
||||||
let chapters = new Map()
|
let chapters = new Map()
|
||||||
chapters.set(firstChapter.hid + "//no//-1//" + firstChapter.lang, "无标卷")
|
let updateTime = comicData?.last_chapter ? "第" + comicData.last_chapter + "话" : "暂无更新";
|
||||||
|
chapters.set((firstChapter.hid || 'unknown') + "//no//-1//" + (firstChapter.lang || 'unknown'), "无标卷")
|
||||||
return {
|
return {
|
||||||
title: title,
|
title: title,
|
||||||
cover: cover,
|
cover: cover,
|
||||||
@@ -651,6 +715,7 @@ class Comick extends ComicSource {
|
|||||||
"状态": [Comick.comic_status[status]]
|
"状态": [Comick.comic_status[status]]
|
||||||
},
|
},
|
||||||
chapters: chapters,
|
chapters: chapters,
|
||||||
|
recommend: recommends || []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -671,7 +736,7 @@ class Comick extends ComicSource {
|
|||||||
"状态": [Comick.comic_status[status]],
|
"状态": [Comick.comic_status[status]],
|
||||||
},
|
},
|
||||||
chapters: chapters,
|
chapters: chapters,
|
||||||
recommend: recommends!=null?recommends:[]
|
recommend: recommends || []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
loadEp: async (comicId, epId) => {
|
loadEp: async (comicId, epId) => {
|
||||||
@@ -694,7 +759,7 @@ class Comick extends ComicSource {
|
|||||||
// 如果是无标卷, 只看第一个
|
// 如果是无标卷, 只看第一个
|
||||||
url = `${this.baseUrl}/comic/${cId}/${hid}`;
|
url = `${this.baseUrl}/comic/${cId}/${hid}`;
|
||||||
}else{
|
}else{
|
||||||
url = `${this.baseUrl}/comic/${cId}/${hid}-${type}-${chapter}-${lang}.json`;
|
url = `${this.baseUrl}/comic/${cId}/${hid}-${type}-${chapter}-${lang}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
let maxAttempts = 100;
|
let maxAttempts = 100;
|
||||||
@@ -706,19 +771,31 @@ class Comick extends ComicSource {
|
|||||||
let document = new HtmlDocument(res.body)
|
let document = new HtmlDocument(res.body)
|
||||||
|
|
||||||
let jsonData = JSON.parse(document.getElementById('__NEXT_DATA__').text); //json解析方式
|
let jsonData = JSON.parse(document.getElementById('__NEXT_DATA__').text); //json解析方式
|
||||||
let imagesData = jsonData.props.pageProps.chapter.md_images;
|
let imagesData = jsonData.props?.pageProps?.chapter?.md_images;
|
||||||
|
|
||||||
|
// 检查图片数据是否存在
|
||||||
|
if (!imagesData || !Array.isArray(imagesData)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// 解析当前页图片
|
// 解析当前页图片
|
||||||
imagesData.forEach(image => {
|
imagesData.forEach(image => {
|
||||||
// 处理图片链接
|
if (image?.b2key) {
|
||||||
let imageUrl = `https://meo.comick.pictures/${image.b2key}`;
|
// 处理图片链接
|
||||||
images.push(imageUrl);
|
let imageUrl = `https://meo.comick.pictures/${image.b2key}`;
|
||||||
|
images.push(imageUrl);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 查找下一页链接
|
// 查找下一页链接
|
||||||
const nextLink = document.querySelector("a#next-chapter");
|
const nextLink = document.querySelector("a#next-chapter");
|
||||||
if (nextLink?.text?.match(/下一页|下一頁/)) {
|
if (nextLink?.text?.match(/下一页|下一頁/)) {
|
||||||
url = nextLink.attributes['href'];
|
const nextUrl = nextLink.attributes?.['href'];
|
||||||
|
if (nextUrl) {
|
||||||
|
url = nextUrl;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
136
copy_manga.js
136
copy_manga.js
@@ -4,7 +4,7 @@ class CopyManga extends ComicSource {
|
|||||||
|
|
||||||
key = "copy_manga"
|
key = "copy_manga"
|
||||||
|
|
||||||
version = "1.3.5"
|
version = "1.3.8"
|
||||||
|
|
||||||
minAppVersion = "1.2.1"
|
minAppVersion = "1.2.1"
|
||||||
|
|
||||||
@@ -12,19 +12,42 @@ class CopyManga extends ComicSource {
|
|||||||
|
|
||||||
get headers() {
|
get headers() {
|
||||||
let token = this.loadData("token");
|
let token = this.loadData("token");
|
||||||
|
let secret = "M2FmMDg1OTAzMTEwMzJlZmUwNjYwNTUwYTA1NjNhNTM="
|
||||||
|
|
||||||
|
let now = new Date(Date.now());
|
||||||
|
let year = now.getFullYear();
|
||||||
|
let month = (now.getMonth() + 1).toString().padStart(2, '0');
|
||||||
|
let day = now.getDate().toString().padStart(2, '0');
|
||||||
|
let ts = Math.floor(now.getTime() / 1000).toString()
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
token = "";
|
token = "";
|
||||||
} else {
|
} else {
|
||||||
token = " " + token;
|
token = " " + token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let sig = Convert.hmacString(
|
||||||
|
Convert.decodeBase64(secret),
|
||||||
|
Convert.encodeUtf8(ts),
|
||||||
|
"sha256"
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/114.0",
|
"User-Agent": "COPY/3.0.0",
|
||||||
"Origin": `https://${this.loadSetting('base_url')}`,
|
"source": "copyApp",
|
||||||
|
"deviceinfo": this.deviceinfo,
|
||||||
|
"dt": `${year}.${month}.${day}`,
|
||||||
|
"platform": "3",
|
||||||
|
"referer": `com.copymanga.app-3.0.0`,
|
||||||
|
"version": "3.0.0",
|
||||||
|
"device": this.device,
|
||||||
|
"pseudoid": this.pseudoid,
|
||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
"platform": "1",
|
|
||||||
"version": "2025.05.09",
|
|
||||||
"region": this.copyRegion,
|
"region": this.copyRegion,
|
||||||
"authorization": `Token${token}`,
|
"authorization": `Token${token}`,
|
||||||
|
"umstring": "b4c89ca4104ea9a97750314d791520ac",
|
||||||
|
"x-auth-timestamp": ts,
|
||||||
|
"x-auth-signature": sig,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,10 +59,37 @@ class CopyManga extends ComicSource {
|
|||||||
|
|
||||||
static defaultImageQuality = "1500"
|
static defaultImageQuality = "1500"
|
||||||
|
|
||||||
static defaultApiUrl = 'mapi.copy20.com'
|
static defaultApiUrl = 'api.copy2000.online'
|
||||||
|
|
||||||
static searchApi = "/api/kb/web/searchb/comics"
|
static searchApi = "/api/kb/web/searchb/comics"
|
||||||
|
|
||||||
|
get deviceinfo() {
|
||||||
|
let info = this.loadData("_deviceinfo");
|
||||||
|
if (!info) {
|
||||||
|
info = CopyManga.generateDeviceInfo();
|
||||||
|
this.saveData("_deviceinfo", info);
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
get device() {
|
||||||
|
let dev = this.loadData("_device");
|
||||||
|
if (!dev) {
|
||||||
|
dev = CopyManga.generateDevice();
|
||||||
|
this.saveData("_device", dev);
|
||||||
|
}
|
||||||
|
return dev;
|
||||||
|
}
|
||||||
|
|
||||||
|
get pseudoid() {
|
||||||
|
let pid = this.loadData("_pseudoid");
|
||||||
|
if (!pid) {
|
||||||
|
pid = CopyManga.generatePseudoid();
|
||||||
|
this.saveData("_pseudoid", pid);
|
||||||
|
}
|
||||||
|
return pid;
|
||||||
|
}
|
||||||
|
|
||||||
// get copyVersion() {
|
// get copyVersion() {
|
||||||
// return this.loadSetting('version')
|
// return this.loadSetting('version')
|
||||||
// }
|
// }
|
||||||
@@ -48,6 +98,43 @@ class CopyManga extends ComicSource {
|
|||||||
// return this.loadSetting('platform')
|
// return this.loadSetting('platform')
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
static generateDeviceInfo() {
|
||||||
|
return `${randomInt(1000000, 9999999)}V-${randomInt(1000, 9999)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static generateDevice() {
|
||||||
|
function randCharA() {
|
||||||
|
return String.fromCharCode(65 + randomInt(0, 25));
|
||||||
|
}
|
||||||
|
function randDigit() {
|
||||||
|
return String.fromCharCode(48 + randomInt(0, 9));
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
randCharA() +
|
||||||
|
randCharA() +
|
||||||
|
randDigit() +
|
||||||
|
randCharA() + "." +
|
||||||
|
randDigit() +
|
||||||
|
randDigit() +
|
||||||
|
randDigit() +
|
||||||
|
randDigit() +
|
||||||
|
randDigit() +
|
||||||
|
randDigit() + "." +
|
||||||
|
randDigit() +
|
||||||
|
randDigit() +
|
||||||
|
randDigit()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static generatePseudoid() {
|
||||||
|
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||||
|
let pseudoid = '';
|
||||||
|
for (let i = 0; i < 16; i++) {
|
||||||
|
pseudoid += chars.charAt(randomInt(0, chars.length - 1));
|
||||||
|
}
|
||||||
|
return pseudoid;
|
||||||
|
}
|
||||||
|
|
||||||
get apiUrl() {
|
get apiUrl() {
|
||||||
return `https://${this.loadSetting('base_url')}`
|
return `https://${this.loadSetting('base_url')}`
|
||||||
}
|
}
|
||||||
@@ -64,6 +151,7 @@ class CopyManga extends ComicSource {
|
|||||||
// 用于储存 { 作者名 : 英文参数 }
|
// 用于储存 { 作者名 : 英文参数 }
|
||||||
this.author_path_word_dict = {}
|
this.author_path_word_dict = {}
|
||||||
this.refreshSearchApi()
|
this.refreshSearchApi()
|
||||||
|
this.refreshAppApi()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// account
|
/// account
|
||||||
@@ -445,7 +533,7 @@ class CopyManga extends ComicSource {
|
|||||||
let is_collect = isAdding ? 1 : 0
|
let is_collect = isAdding ? 1 : 0
|
||||||
let token = this.loadData("token");
|
let token = this.loadData("token");
|
||||||
let comicData = await Network.get(
|
let comicData = await Network.get(
|
||||||
`${this.apiUrl}/api/v3/comic2/${comicId}`,
|
`${this.apiUrl}/api/v3/comic2/${comicId}?in_mainland=true&request_id=&platform=3`,
|
||||||
this.headers
|
this.headers
|
||||||
)
|
)
|
||||||
if (comicData.status !== 200) {
|
if (comicData.status !== 200) {
|
||||||
@@ -520,7 +608,7 @@ class CopyManga extends ComicSource {
|
|||||||
let getChapters = async (id, groups) => {
|
let getChapters = async (id, groups) => {
|
||||||
let fetchSingle = async (id, path) => {
|
let fetchSingle = async (id, path) => {
|
||||||
let res = await Network.get(
|
let res = await Network.get(
|
||||||
`${this.apiUrl}/api/v3/comic/${id}/group/${path}/chapters?limit=500&offset=0`,
|
`${this.apiUrl}/api/v3/comic/${id}/group/${path}/chapters?limit=100&offset=0&in_mainland=true&request_id=`,
|
||||||
this.headers
|
this.headers
|
||||||
);
|
);
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
@@ -534,11 +622,11 @@ class CopyManga extends ComicSource {
|
|||||||
eps.set(id, title);
|
eps.set(id, title);
|
||||||
});
|
});
|
||||||
let maxChapter = data.results.total;
|
let maxChapter = data.results.total;
|
||||||
if (maxChapter > 500) {
|
if (maxChapter > 100) {
|
||||||
let offset = 500;
|
let offset = 100;
|
||||||
while (offset < maxChapter) {
|
while (offset < maxChapter) {
|
||||||
res = await Network.get(
|
res = await Network.get(
|
||||||
`${this.apiUrl}/api/v3/comic/${id}/group/${path}/chapters?limit=500&offset=${offset}`,
|
`${this.apiUrl}/api/v3/comic/${id}/group/${path}/chapters?limit=100&offset=${offset}`,
|
||||||
this.headers
|
this.headers
|
||||||
);
|
);
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
@@ -550,7 +638,7 @@ class CopyManga extends ComicSource {
|
|||||||
let id = e.uuid;
|
let id = e.uuid;
|
||||||
eps.set(id, title)
|
eps.set(id, title)
|
||||||
});
|
});
|
||||||
offset += 500;
|
offset += 100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return eps;
|
return eps;
|
||||||
@@ -595,7 +683,7 @@ class CopyManga extends ComicSource {
|
|||||||
|
|
||||||
let results = await Promise.all([
|
let results = await Promise.all([
|
||||||
Network.get(
|
Network.get(
|
||||||
`${this.apiUrl}/api/v3/comic2/${id}`,
|
`${this.apiUrl}/api/v3/comic2/${id}?in_mainland=true&request_id=&platform=3`,
|
||||||
this.headers
|
this.headers
|
||||||
),
|
),
|
||||||
getFavoriteStatus.bind(this)(id)
|
getFavoriteStatus.bind(this)(id)
|
||||||
@@ -647,7 +735,7 @@ class CopyManga extends ComicSource {
|
|||||||
while (attempt < maxAttempts) {
|
while (attempt < maxAttempts) {
|
||||||
try {
|
try {
|
||||||
res = await Network.get(
|
res = await Network.get(
|
||||||
`${this.apiUrl}/api/v3/comic/${comicId}/chapter2/${epId}`,
|
`${this.apiUrl}/api/v3/comic/${comicId}/chapter2/${epId}?in_mainland=true&request_id=`,
|
||||||
{
|
{
|
||||||
...this.headers
|
...this.headers
|
||||||
}
|
}
|
||||||
@@ -854,6 +942,17 @@ class CopyManga extends ComicSource {
|
|||||||
type: "input",
|
type: "input",
|
||||||
validator: '^(?!:\\/\\/)(?=.{1,253})([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,}$',
|
validator: '^(?!:\\/\\/)(?=.{1,253})([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,}$',
|
||||||
default: CopyManga.defaultApiUrl,
|
default: CopyManga.defaultApiUrl,
|
||||||
|
},
|
||||||
|
clear_device_info: {
|
||||||
|
title: "清除设备信息",
|
||||||
|
type: "callback",
|
||||||
|
buttonText: "点击清除设备信息",
|
||||||
|
callback: () => {
|
||||||
|
this.deleteData("_deviceinfo");
|
||||||
|
this.deleteData("_device");
|
||||||
|
this.deleteData("_pseudoid");
|
||||||
|
this.refreshAppApi();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// version: {
|
// version: {
|
||||||
// title: "拷贝版本(重启APP生效)",
|
// title: "拷贝版本(重启APP生效)",
|
||||||
@@ -897,4 +996,13 @@ class CopyManga extends ComicSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async refreshAppApi() {
|
||||||
|
const url = "https://api.copy-manga.com/api/v3/system/network2?platform=3"
|
||||||
|
const res = await fetch(url, { headers: this.headers });
|
||||||
|
if (res.status === 200) {
|
||||||
|
let data = await res.json();
|
||||||
|
this.settings.base_url= data.results.api[0][0];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,7 +7,7 @@ class Ehentai extends ComicSource {
|
|||||||
// unique id of the source
|
// unique id of the source
|
||||||
key = "ehentai"
|
key = "ehentai"
|
||||||
|
|
||||||
version = "1.1.3"
|
version = "1.1.4"
|
||||||
|
|
||||||
minAppVersion = "1.0.0"
|
minAppVersion = "1.0.0"
|
||||||
|
|
||||||
@@ -1182,7 +1182,7 @@ class Ehentai extends ComicSource {
|
|||||||
if(url.includes('?')) {
|
if(url.includes('?')) {
|
||||||
url = url.split('?')[0]
|
url = url.split('?')[0]
|
||||||
}
|
}
|
||||||
let reg = RegExp("https?://(e-|ex)hentai.org/g/(\\d+)/(\\w+)/")
|
let reg = RegExp("https?://(e-|ex)hentai.org/g/(\\d+)/(\\w+)/?$")
|
||||||
let match = reg.exec(url)
|
let match = reg.exec(url)
|
||||||
if(match) {
|
if(match) {
|
||||||
return `${this.baseUrl}/g/${match[2]}/${match[3]}/`
|
return `${this.baseUrl}/g/${match[2]}/${match[3]}/`
|
||||||
|
160
hitomi.js
160
hitomi.js
@@ -995,16 +995,16 @@ class Hitomi extends ComicSource {
|
|||||||
// unique id of the source
|
// unique id of the source
|
||||||
key = "hitomi";
|
key = "hitomi";
|
||||||
|
|
||||||
version = "1.0.0";
|
version = "1.1.2";
|
||||||
|
|
||||||
minAppVersion = "1.4.0";
|
minAppVersion = "1.4.6";
|
||||||
|
|
||||||
// update url
|
// update url
|
||||||
url = "https://git.nyne.dev/nyne/venera-configs/raw/branch/main/hitomi.js";
|
url = "https://git.nyne.dev/nyne/venera-configs/raw/branch/main/hitomi.js";
|
||||||
|
|
||||||
galleryCache = [];
|
galleryCache = [];
|
||||||
categoryResultCache = undefined;
|
categoryResultCache = undefined;
|
||||||
searchResultCache = undefined;
|
searchResultCaches = new Map();
|
||||||
|
|
||||||
_mapGalleryBlockInfoToComic(n) {
|
_mapGalleryBlockInfoToComic(n) {
|
||||||
return new Comic({
|
return new Comic({
|
||||||
@@ -1088,95 +1088,24 @@ class Hitomi extends ComicSource {
|
|||||||
title: "hitomi.la",
|
title: "hitomi.la",
|
||||||
parts: [
|
parts: [
|
||||||
{
|
{
|
||||||
name: "Language",
|
name: "语言",
|
||||||
type: "fixed",
|
type: "fixed",
|
||||||
categories: [
|
categories: ["汉语", "英语"],
|
||||||
{
|
itemType: "category",
|
||||||
label: "Chinese",
|
categoryParams: ["language:chinese", "language:english"],
|
||||||
target: {
|
|
||||||
page: "category",
|
|
||||||
attributes: {
|
|
||||||
category: "language",
|
|
||||||
param: "chinese",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "English",
|
|
||||||
target: {
|
|
||||||
page: "category",
|
|
||||||
attributes: {
|
|
||||||
category: "language",
|
|
||||||
param: "english",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "类别",
|
name: "类别",
|
||||||
type: "fixed",
|
type: "fixed",
|
||||||
categories: [
|
categories: ["同人志", "漫画", "画师CG", "游戏CG", "图集", "动画"],
|
||||||
{
|
itemType: "category",
|
||||||
label: "doujinshi",
|
categoryParams: [
|
||||||
target: {
|
"type:doujinshi",
|
||||||
page: "category",
|
"type:manga",
|
||||||
attributes: {
|
"type:artistcg",
|
||||||
category: "type",
|
"type:gamecg",
|
||||||
param: "doujinshi",
|
"type:imageset",
|
||||||
},
|
"type:anime",
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "manga",
|
|
||||||
target: {
|
|
||||||
page: "category",
|
|
||||||
attributes: {
|
|
||||||
category: "type",
|
|
||||||
param: "manga",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "artistcg",
|
|
||||||
target: {
|
|
||||||
page: "category",
|
|
||||||
attributes: {
|
|
||||||
category: "type",
|
|
||||||
param: "artistcg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "gamecg",
|
|
||||||
target: {
|
|
||||||
page: "category",
|
|
||||||
attributes: {
|
|
||||||
category: "type",
|
|
||||||
param: "gamecg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "imageset",
|
|
||||||
target: {
|
|
||||||
page: "category",
|
|
||||||
attributes: {
|
|
||||||
category: "type",
|
|
||||||
param: "imageset",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "anime",
|
|
||||||
target: {
|
|
||||||
page: "category",
|
|
||||||
attributes: {
|
|
||||||
category: "type",
|
|
||||||
param: "anime",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -1195,9 +1124,11 @@ class Hitomi extends ComicSource {
|
|||||||
* @returns {Promise<{comics: Comic[], maxPage: number}>}
|
* @returns {Promise<{comics: Comic[], maxPage: number}>}
|
||||||
*/
|
*/
|
||||||
load: async (category, param, options, page) => {
|
load: async (category, param, options, page) => {
|
||||||
|
const term = param;
|
||||||
|
if (!term.includes(":"))
|
||||||
|
throw new Error("不合法的标签,请使用namespace:tag的格式");
|
||||||
if (page === 1) {
|
if (page === 1) {
|
||||||
const option = parseInt(options[0]);
|
const option = parseInt(options[0]);
|
||||||
const term = category + ":" + param;
|
|
||||||
const searchOptions = {
|
const searchOptions = {
|
||||||
term,
|
term,
|
||||||
orderby: "date",
|
orderby: "date",
|
||||||
@@ -1351,6 +1282,7 @@ class Hitomi extends ComicSource {
|
|||||||
* @returns {Promise<{comics: Comic[], maxPage: number}>}
|
* @returns {Promise<{comics: Comic[], maxPage: number}>}
|
||||||
*/
|
*/
|
||||||
load: async (keyword, options, page) => {
|
load: async (keyword, options, page) => {
|
||||||
|
const cacheKey = (keyword || "") + "|" + options.join(",");
|
||||||
if (page === 1) {
|
if (page === 1) {
|
||||||
const option = parseInt(options[0]);
|
const option = parseInt(options[0]);
|
||||||
const term = keyword;
|
const term = keyword;
|
||||||
@@ -1392,11 +1324,11 @@ class Hitomi extends ComicSource {
|
|||||||
const comics = (await get_galleryblocks(result.gids)).map((n) =>
|
const comics = (await get_galleryblocks(result.gids)).map((n) =>
|
||||||
this._mapGalleryBlockInfoToComic(n)
|
this._mapGalleryBlockInfoToComic(n)
|
||||||
);
|
);
|
||||||
this.searchResultCache = {
|
this.searchResultCaches.set(cacheKey, {
|
||||||
type: "single",
|
type: "single",
|
||||||
state: result.state,
|
state: result.state,
|
||||||
count: result.count,
|
count: result.count,
|
||||||
};
|
});
|
||||||
return {
|
return {
|
||||||
comics,
|
comics,
|
||||||
maxPage: Math.ceil(result.count / 25),
|
maxPage: Math.ceil(result.count / 25),
|
||||||
@@ -1407,20 +1339,21 @@ class Hitomi extends ComicSource {
|
|||||||
result.gids.slice(25 * page - 25, 25 * page)
|
result.gids.slice(25 * page - 25, 25 * page)
|
||||||
)
|
)
|
||||||
).map((n) => this._mapGalleryBlockInfoToComic(n));
|
).map((n) => this._mapGalleryBlockInfoToComic(n));
|
||||||
this.searchResultCache = {
|
this.searchResultCaches.set(cacheKey, {
|
||||||
type: "all",
|
type: "all",
|
||||||
gids: result.gids,
|
gids: result.gids,
|
||||||
count: result.count,
|
count: result.count,
|
||||||
};
|
});
|
||||||
return {
|
return {
|
||||||
comics,
|
comics,
|
||||||
maxPage: Math.ceil(result.count / 25),
|
maxPage: Math.ceil(result.count / 25),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (this.searchResultCache.type === "single") {
|
const searchResultCache = this.searchResultCaches.get(cacheKey);
|
||||||
|
if (searchResultCache.type === "single") {
|
||||||
const result = await getSingleTagSearchPage({
|
const result = await getSingleTagSearchPage({
|
||||||
state: this.searchResultCache.state,
|
state: searchResultCache.state,
|
||||||
page: page - 1,
|
page: page - 1,
|
||||||
});
|
});
|
||||||
const comics = (await get_galleryblocks(result.galleryids)).map((n) =>
|
const comics = (await get_galleryblocks(result.galleryids)).map((n) =>
|
||||||
@@ -1428,17 +1361,17 @@ class Hitomi extends ComicSource {
|
|||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
comics,
|
comics,
|
||||||
maxPage: Math.ceil(this.searchResultCache.count / 25),
|
maxPage: Math.ceil(searchResultCache.count / 25),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const comics = (
|
const comics = (
|
||||||
await get_galleryblocks(
|
await get_galleryblocks(
|
||||||
this.searchResultCache.gids.slice(25 * page - 25, 25 * page)
|
searchResultCache.gids.slice(25 * page - 25, 25 * page)
|
||||||
)
|
)
|
||||||
).map((n) => this._mapGalleryBlockInfoToComic(n));
|
).map((n) => this._mapGalleryBlockInfoToComic(n));
|
||||||
return {
|
return {
|
||||||
comics,
|
comics,
|
||||||
maxPage: Math.ceil(this.searchResultCache.count / 25),
|
maxPage: Math.ceil(searchResultCache.count / 25),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1482,6 +1415,34 @@ class Hitomi extends ComicSource {
|
|||||||
|
|
||||||
// enable tags suggestions
|
// enable tags suggestions
|
||||||
enableTagsSuggestions: true,
|
enableTagsSuggestions: true,
|
||||||
|
onTagSuggestionSelected: (namespace, tag) => {
|
||||||
|
let fixedNamespace = undefined;
|
||||||
|
switch (namespace) {
|
||||||
|
case "reclass":
|
||||||
|
fixedNamespace = "type";
|
||||||
|
break;
|
||||||
|
case "parody":
|
||||||
|
fixedNamespace = "series";
|
||||||
|
break;
|
||||||
|
case "other":
|
||||||
|
fixedNamespace = "tag";
|
||||||
|
break;
|
||||||
|
case "mixed":
|
||||||
|
fixedNamespace = "tag";
|
||||||
|
break;
|
||||||
|
case "temp":
|
||||||
|
fixedNamespace = "tag";
|
||||||
|
break;
|
||||||
|
case "cosplayer":
|
||||||
|
fixedNamespace = "tag";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fixedNamespace = namespace;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// return the text to insert into search box
|
||||||
|
return `${fixedNamespace}:${tag.replaceAll(" ", "_")}`;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// single comic related
|
/// single comic related
|
||||||
@@ -1495,10 +1456,11 @@ 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);
|
||||||
|
203
ikmmh.js
203
ikmmh.js
@@ -1,22 +1,56 @@
|
|||||||
|
/** @type {import('./_venera_.js')} */
|
||||||
|
|
||||||
|
function getValidatorCookie(htmlString) {
|
||||||
|
// 正则表达式匹配 document.cookie 设置语句
|
||||||
|
const cookieRegex = /document\.cookie\s*=\s*"([^"]+)"/;
|
||||||
|
const match = htmlString.match(cookieRegex);
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
return null; // 没有找到 cookie 设置语句
|
||||||
|
}
|
||||||
|
|
||||||
|
const cookieSetting = match[1];
|
||||||
|
const cookies = cookieSetting.split(';');
|
||||||
|
if (cookies.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const nameValuePart = cookies[0].trim();
|
||||||
|
const equalsIndex = nameValuePart.indexOf('=');
|
||||||
|
|
||||||
|
const name = nameValuePart.substring(0, equalsIndex);
|
||||||
|
const value = nameValuePart.substring(equalsIndex + 1);
|
||||||
|
|
||||||
|
return new Cookie({ name, value, domain: "www.ikmmh.com" })
|
||||||
|
}
|
||||||
|
|
||||||
|
function needPassValidator(htmlString) {
|
||||||
|
var cookie = getValidatorCookie(htmlString)
|
||||||
|
if (cookie != null) {
|
||||||
|
Network.setCookies(Ikm.baseUrl, [cookie])
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
class Ikm extends ComicSource {
|
class Ikm extends ComicSource {
|
||||||
// 基础配置
|
// 基础配置
|
||||||
name = "爱看漫";
|
name = "爱看漫";
|
||||||
key = "ikmmh";
|
key = "ikmmh";
|
||||||
version = "1.0.2";
|
version = "1.0.5";
|
||||||
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://www.ikmmh.com";
|
||||||
static Mobile_UA = "Mozilla/5.0 (Linux; Android) Mobile";
|
static Mobile_UA = "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1 Edg/140.0.0.0";
|
||||||
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",
|
||||||
};
|
};
|
||||||
@@ -24,7 +58,7 @@ class Ikm extends ComicSource {
|
|||||||
static thumbConfig = (url) => ({
|
static thumbConfig = (url) => ({
|
||||||
headers: {
|
headers: {
|
||||||
...Ikm.webHeaders,
|
...Ikm.webHeaders,
|
||||||
Referer: Ikm.baseUrl,
|
"referer": Ikm.baseUrl,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
// 账号系统
|
// 账号系统
|
||||||
@@ -38,14 +72,26 @@ class Ikm extends ComicSource {
|
|||||||
);
|
);
|
||||||
if (res.status !== 200)
|
if (res.status !== 200)
|
||||||
throw new Error(`登录失败,状态码:${res.status}`);
|
throw new Error(`登录失败,状态码:${res.status}`);
|
||||||
|
|
||||||
|
if (needPassValidator(res.body)) {
|
||||||
|
// rePost
|
||||||
|
res = await Network.post(
|
||||||
|
`${Ikm.baseUrl}/api/user/userarr/login`,
|
||||||
|
Ikm.jsonHead,
|
||||||
|
`user=${account}&pass=${pwd}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
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("www.ikmmh.com"),
|
||||||
registerWebsite: `${Ikm.baseUrl}/user/register/`,
|
registerWebsite: `${Ikm.baseUrl}/user/register/`,
|
||||||
};
|
};
|
||||||
// 探索页面
|
// 探索页面
|
||||||
@@ -58,6 +104,12 @@ class Ikm extends ComicSource {
|
|||||||
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}`);
|
||||||
|
|
||||||
|
if (needPassValidator(res.body)) {
|
||||||
|
// rePost
|
||||||
|
res = await Network.get(`${Ikm.baseUrl}/`, Ikm.webHeaders);
|
||||||
|
}
|
||||||
|
|
||||||
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];
|
||||||
@@ -72,10 +124,10 @@ class Ikm extends ComicSource {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
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),
|
||||||
};
|
};
|
||||||
@@ -90,6 +142,21 @@ class Ikm extends ComicSource {
|
|||||||
category = {
|
category = {
|
||||||
title: "爱看漫",
|
title: "爱看漫",
|
||||||
parts: [
|
parts: [
|
||||||
|
{
|
||||||
|
name: "更新",
|
||||||
|
type: "fixed",
|
||||||
|
categories: [
|
||||||
|
"星期一",
|
||||||
|
"星期二",
|
||||||
|
"星期三",
|
||||||
|
"星期四",
|
||||||
|
"星期五",
|
||||||
|
"星期六",
|
||||||
|
"星期日",
|
||||||
|
],
|
||||||
|
itemType: "category",
|
||||||
|
categoryParams: ["1", "2", "3", "4", "5", "6", "7"],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "分类",
|
name: "分类",
|
||||||
// fixed 或者 random
|
// fixed 或者 random
|
||||||
@@ -139,38 +206,13 @@ class Ikm extends ComicSource {
|
|||||||
"历史",
|
"历史",
|
||||||
"战争",
|
"战争",
|
||||||
"恐怖",
|
"恐怖",
|
||||||
"霸总",
|
"霸总"
|
||||||
"全部",
|
|
||||||
"连载中",
|
|
||||||
"已完结",
|
|
||||||
"全部",
|
|
||||||
"日漫",
|
|
||||||
"港台",
|
|
||||||
"美漫",
|
|
||||||
"国漫",
|
|
||||||
"韩漫",
|
|
||||||
"未分类",
|
|
||||||
],
|
],
|
||||||
// category或者search
|
// category或者search
|
||||||
// 如果为category, 点击后将进入分类漫画页面, 使用下方的`categoryComics`加载漫画
|
// 如果为category, 点击后将进入分类漫画页面, 使用下方的`categoryComics`加载漫画
|
||||||
// 如果为search, 将进入搜索页面
|
// 如果为search, 将进入搜索页面
|
||||||
itemType: "category",
|
itemType: "category",
|
||||||
},
|
}
|
||||||
{
|
|
||||||
name: "更新",
|
|
||||||
type: "fixed",
|
|
||||||
categories: [
|
|
||||||
"星期一",
|
|
||||||
"星期二",
|
|
||||||
"星期三",
|
|
||||||
"星期四",
|
|
||||||
"星期五",
|
|
||||||
"星期六",
|
|
||||||
"星期日",
|
|
||||||
],
|
|
||||||
itemType: "category",
|
|
||||||
categoryParams: ["1", "2", "3", "4", "5", "6", "7"],
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
enableRankingPage: false,
|
enableRankingPage: false,
|
||||||
};
|
};
|
||||||
@@ -186,6 +228,15 @@ class Ikm extends ComicSource {
|
|||||||
);
|
);
|
||||||
if (res.status !== 200)
|
if (res.status !== 200)
|
||||||
throw new Error(`分类请求失败,状态码:${res.status}`);
|
throw new Error(`分类请求失败,状态码:${res.status}`);
|
||||||
|
|
||||||
|
if (needPassValidator(res.body)) {
|
||||||
|
// rePost
|
||||||
|
res = await Network.get(
|
||||||
|
`${Ikm.baseUrl}/update/${param}.html`,
|
||||||
|
Ikm.webHeaders
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let document = new HtmlDocument(res.body);
|
let document = new HtmlDocument(res.body);
|
||||||
let comics = document.querySelectorAll("li.comic-item").map((e) => ({
|
let comics = document.querySelectorAll("li.comic-item").map((e) => ({
|
||||||
title: e.querySelector("p.title").text.split("~")[0],
|
title: e.querySelector("p.title").text.split("~")[0],
|
||||||
@@ -195,7 +246,7 @@ class Ikm extends ComicSource {
|
|||||||
}));
|
}));
|
||||||
return {
|
return {
|
||||||
comics,
|
comics,
|
||||||
maxPage: 1,
|
maxPage: 1
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
res = await Network.post(
|
res = await Network.post(
|
||||||
@@ -205,6 +256,17 @@ class Ikm extends ComicSource {
|
|||||||
options[0]
|
options[0]
|
||||||
}&page=${page}`
|
}&page=${page}`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (needPassValidator(res.body)) {
|
||||||
|
// rePost
|
||||||
|
res = await Network.post(
|
||||||
|
`${Ikm.baseUrl}/api/comic/index/lists`,
|
||||||
|
Ikm.jsonHead,
|
||||||
|
`area=${options[1]}&tags=${encodeURIComponent(category)}&full=${options[0]
|
||||||
|
}&page=${page}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let resData = JSON.parse(res.body);
|
let resData = JSON.parse(res.body);
|
||||||
return {
|
return {
|
||||||
comics: resData.data.map((e) => ({
|
comics: resData.data.map((e) => ({
|
||||||
@@ -270,6 +332,15 @@ class Ikm extends ComicSource {
|
|||||||
`${Ikm.baseUrl}/search?searchkey=${encodeURIComponent(keyword)}`,
|
`${Ikm.baseUrl}/search?searchkey=${encodeURIComponent(keyword)}`,
|
||||||
Ikm.webHeaders
|
Ikm.webHeaders
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (needPassValidator(res.body)) {
|
||||||
|
// rePost
|
||||||
|
res = await Network.get(
|
||||||
|
`${Ikm.baseUrl}/search?searchkey=${encodeURIComponent(keyword)}`,
|
||||||
|
Ikm.webHeaders
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let document = new HtmlDocument(res.body);
|
let document = new HtmlDocument(res.body);
|
||||||
return {
|
return {
|
||||||
comics: document.querySelectorAll("li.comic-item").map((e) => ({
|
comics: document.querySelectorAll("li.comic-item").map((e) => ({
|
||||||
@@ -296,6 +367,12 @@ class Ikm extends ComicSource {
|
|||||||
if (isAdding) {
|
if (isAdding) {
|
||||||
// 获取漫画信息
|
// 获取漫画信息
|
||||||
let infoRes = await Network.get(comicId, Ikm.webHeaders);
|
let infoRes = await Network.get(comicId, Ikm.webHeaders);
|
||||||
|
|
||||||
|
if (needPassValidator(infoRes.body)) {
|
||||||
|
// rePost
|
||||||
|
infoRes = await Network.get(comicId, Ikm.webHeaders);
|
||||||
|
}
|
||||||
|
|
||||||
let name = new HtmlDocument(infoRes.body).querySelector(
|
let name = new HtmlDocument(infoRes.body).querySelector(
|
||||||
"meta[property='og:title']"
|
"meta[property='og:title']"
|
||||||
).attributes["content"];
|
).attributes["content"];
|
||||||
@@ -315,6 +392,16 @@ class Ikm extends ComicSource {
|
|||||||
Ikm.jsonHead,
|
Ikm.jsonHead,
|
||||||
`articleid=${id}`
|
`articleid=${id}`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (needPassValidator(res.body)) {
|
||||||
|
// rePost
|
||||||
|
res = await Network.post(
|
||||||
|
`${Ikm.baseUrl}/api/user/bookcase/del`,
|
||||||
|
Ikm.jsonHead,
|
||||||
|
`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";
|
||||||
@@ -332,6 +419,15 @@ class Ikm extends ComicSource {
|
|||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
throw "加载收藏失败:" + res.status;
|
throw "加载收藏失败:" + res.status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (needPassValidator(res.body)) {
|
||||||
|
// rePost
|
||||||
|
res = await Network.get(
|
||||||
|
`${Ikm.baseUrl}/user/bookcase`,
|
||||||
|
Ikm.webHeaders
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let document = new HtmlDocument(res.body);
|
let document = new HtmlDocument(res.body);
|
||||||
return {
|
return {
|
||||||
comics: document.querySelectorAll("div.bookrack-item").map((e) => ({
|
comics: document.querySelectorAll("div.bookrack-item").map((e) => ({
|
||||||
@@ -348,7 +444,21 @@ class Ikm extends ComicSource {
|
|||||||
// 漫画详情
|
// 漫画详情
|
||||||
comic = {
|
comic = {
|
||||||
loadInfo: async (id) => {
|
loadInfo: async (id) => {
|
||||||
|
// 加载收藏页并判断是否收藏
|
||||||
|
let isFavorite = false;
|
||||||
|
try {
|
||||||
|
let favorites = await this.favorites.loadComics(1, null);
|
||||||
|
isFavorite = favorites.comics.some((comic) => comic.id === id);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("加载收藏页失败:", error);
|
||||||
|
}
|
||||||
let res = await Network.get(id, Ikm.webHeaders);
|
let res = await Network.get(id, Ikm.webHeaders);
|
||||||
|
|
||||||
|
if (needPassValidator(res.body)) {
|
||||||
|
// rePost
|
||||||
|
res = await Network.get(id, Ikm.webHeaders);
|
||||||
|
}
|
||||||
|
|
||||||
let document = new HtmlDocument(res.body);
|
let document = new HtmlDocument(res.body);
|
||||||
let comicId = id.match(/\d+/)[0];
|
let comicId = id.match(/\d+/)[0];
|
||||||
// 获取章节数据
|
// 获取章节数据
|
||||||
@@ -356,7 +466,7 @@ class Ikm extends ComicSource {
|
|||||||
`${Ikm.baseUrl}/api/comic/zyz/chapterlink?id=${comicId}`,
|
`${Ikm.baseUrl}/api/comic/zyz/chapterlink?id=${comicId}`,
|
||||||
{
|
{
|
||||||
...Ikm.jsonHead,
|
...Ikm.jsonHead,
|
||||||
Referer: id,
|
"referer": id,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
let epData = JSON.parse(epRes.body);
|
let epData = JSON.parse(epRes.body);
|
||||||
@@ -393,13 +503,13 @@ class Ikm extends ComicSource {
|
|||||||
cover: thumb,
|
cover: thumb,
|
||||||
description: intro,
|
description: intro,
|
||||||
tags: {
|
tags: {
|
||||||
作者: [
|
"作者": [
|
||||||
document
|
document
|
||||||
.querySelector("div.book-container__author")
|
.querySelector("div.book-container__author")
|
||||||
.text.split("作者:")[1],
|
.text.split("作者:")[1],
|
||||||
],
|
],
|
||||||
更新: [document.querySelector("div.update > a > em").text],
|
"更新": [document.querySelector("div.update > a > em").text],
|
||||||
标签: document
|
"标签": document
|
||||||
.querySelectorAll("div.book-hero__detail > div.tags > a")
|
.querySelectorAll("div.book-hero__detail > div.tags > a")
|
||||||
.map((e) => e.text.trim())
|
.map((e) => e.text.trim())
|
||||||
.filter((text) => text),
|
.filter((text) => text),
|
||||||
@@ -412,12 +522,19 @@ class Ikm extends ComicSource {
|
|||||||
cover: e.querySelector("div.thumb_img").attributes["data-src"],
|
cover: e.querySelector("div.thumb_img").attributes["data-src"],
|
||||||
id: `${Ikm.baseUrl}${e.querySelector("a").attributes["href"]}`,
|
id: `${Ikm.baseUrl}${e.querySelector("a").attributes["href"]}`,
|
||||||
})),
|
})),
|
||||||
|
isFavorite: isFavorite,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
onThumbnailLoad: Ikm.thumbConfig,
|
onThumbnailLoad: Ikm.thumbConfig,
|
||||||
loadEp: async (comicId, epId) => {
|
loadEp: async (comicId, epId) => {
|
||||||
try {
|
try {
|
||||||
let res = await Network.get(epId, Ikm.webHeaders);
|
let res = await Network.get(epId, Ikm.webHeaders);
|
||||||
|
|
||||||
|
if (needPassValidator(res.body)) {
|
||||||
|
// rePost
|
||||||
|
res = await Network.get(epId, Ikm.webHeaders);
|
||||||
|
}
|
||||||
|
|
||||||
let document = new HtmlDocument(res.body);
|
let document = new HtmlDocument(res.body);
|
||||||
return {
|
return {
|
||||||
images: document
|
images: document
|
||||||
@@ -433,7 +550,7 @@ class Ikm extends ComicSource {
|
|||||||
url,
|
url,
|
||||||
headers: {
|
headers: {
|
||||||
...Ikm.webHeaders,
|
...Ikm.webHeaders,
|
||||||
Referer: epId,
|
"referer": epId,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
56
index.json
56
index.json
@@ -3,7 +3,7 @@
|
|||||||
"name": "拷贝漫画",
|
"name": "拷贝漫画",
|
||||||
"fileName": "copy_manga.js",
|
"fileName": "copy_manga.js",
|
||||||
"key": "copy_manga",
|
"key": "copy_manga",
|
||||||
"version": "1.3.5"
|
"version": "1.3.8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Komiic",
|
"name": "Komiic",
|
||||||
@@ -15,19 +15,19 @@
|
|||||||
"name": "包子漫画",
|
"name": "包子漫画",
|
||||||
"fileName": "baozi.js",
|
"fileName": "baozi.js",
|
||||||
"key": "baozi",
|
"key": "baozi",
|
||||||
"version": "1.0.5"
|
"version": "1.1.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"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",
|
||||||
"fileName": "nhentai.js",
|
"fileName": "nhentai.js",
|
||||||
"key": "nhentai",
|
"key": "nhentai",
|
||||||
"version": "1.0.4"
|
"version": "1.0.6"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "紳士漫畫",
|
"name": "紳士漫畫",
|
||||||
@@ -40,13 +40,13 @@
|
|||||||
"name": "ehentai",
|
"name": "ehentai",
|
||||||
"fileName": "ehentai.js",
|
"fileName": "ehentai.js",
|
||||||
"key": "ehentai",
|
"key": "ehentai",
|
||||||
"version": "1.1.3"
|
"version": "1.1.4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "禁漫天堂",
|
"name": "禁漫天堂",
|
||||||
"fileName": "jm.js",
|
"fileName": "jm.js",
|
||||||
"key": "jm",
|
"key": "jm",
|
||||||
"version": "1.1.4",
|
"version": "1.3.0",
|
||||||
"description": "禁漫天堂漫畫源, 不能使用時請嘗試切換分流"
|
"description": "禁漫天堂漫畫源, 不能使用時請嘗試切換分流"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -60,24 +60,60 @@
|
|||||||
"name": "爱看漫",
|
"name": "爱看漫",
|
||||||
"fileName": "ikmmh.js",
|
"fileName": "ikmmh.js",
|
||||||
"key": "ikmmh",
|
"key": "ikmmh",
|
||||||
"version": "1.0.2"
|
"version": "1.0.5"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "少年ジャンプ+",
|
"name": "少年ジャンプ+",
|
||||||
"fileName": "shonenjumpplus.js",
|
"fileName": "shonen_jump_plus.js",
|
||||||
"key": "shonen_jump_plus",
|
"key": "shonen_jump_plus",
|
||||||
"version": "1.0.1"
|
"version": "1.1.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "hitomi.la",
|
"name": "hitomi.la",
|
||||||
"fileName": "hitomi.js",
|
"fileName": "hitomi.js",
|
||||||
"key": "hitomi",
|
"key": "hitomi",
|
||||||
"version": "1.0.0"
|
"version": "1.1.2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "comick",
|
"name": "comick",
|
||||||
"fileName": "comick.js",
|
"fileName": "comick.js",
|
||||||
"key": "comick",
|
"key": "comick",
|
||||||
|
"version": "1.1.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "优酷漫画",
|
||||||
|
"fileName": "ykmh.js",
|
||||||
|
"key": "ykmh",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "再漫画",
|
||||||
|
"fileName": "zaimanhua.js",
|
||||||
|
"key": "zaimanhua",
|
||||||
"version": "1.0.1"
|
"version": "1.0.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "漫画柜",
|
||||||
|
"fileName": "manhuagui.js",
|
||||||
|
"key": "ManHuaGui",
|
||||||
|
"version": "1.1.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "优酷漫画",
|
||||||
|
"fileName": "ykmh.js",
|
||||||
|
"key": "ykmh",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "漫蛙吧",
|
||||||
|
"fileName": "manwaba.js",
|
||||||
|
"key": "manwaba",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Lanraragi",
|
||||||
|
"fileName": "lanraragi.js",
|
||||||
|
"key": "lanraragi",
|
||||||
|
"version": "1.1.0"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
219
jm.js
219
jm.js
@@ -7,25 +7,31 @@ class JM extends ComicSource {
|
|||||||
// unique id of the source
|
// unique id of the source
|
||||||
key = "jm"
|
key = "jm"
|
||||||
|
|
||||||
version = "1.1.4"
|
version = "1.3.0"
|
||||||
|
|
||||||
minAppVersion = "1.2.5"
|
minAppVersion = "1.5.0"
|
||||||
|
|
||||||
|
static jmVersion = "2.0.6"
|
||||||
|
|
||||||
|
static jmPkgName = "com.example.app"
|
||||||
|
|
||||||
// update url
|
// update url
|
||||||
url = "https://git.nyne.dev/nyne/venera-configs/raw/branch/main/jm.js"
|
url = "https://git.nyne.dev/nyne/venera-configs/raw/branch/main/jm.js"
|
||||||
|
|
||||||
static apiDomains = [
|
static fallbackServers = [
|
||||||
"www.jmapiproxyxxx.vip",
|
"www.cdntwice.org",
|
||||||
"www.cdnblackmyth.club",
|
"www.cdnsha.org",
|
||||||
"www.cdnmhws.cc",
|
"www.cdnaspa.cc",
|
||||||
"www.cdnmhwscc.org"
|
"www.cdnntr.cc",
|
||||||
];
|
];
|
||||||
|
|
||||||
static imageUrl = "https://cdn-msp.jmapinodeudzn.net"
|
static imageUrl = "https://cdn-msp.jmapinodeudzn.net"
|
||||||
|
|
||||||
static apiUa = "Mozilla/5.0 (Linux; Android 10; K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.0.0 Mobile Safari/537.36"
|
static ua = "Mozilla/5.0 (Linux; Android 10; K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.0.0 Mobile Safari/537.36"
|
||||||
|
|
||||||
static imgUa = "okhttp/3.12.1"
|
get ua() {
|
||||||
|
return JM.ua;
|
||||||
|
}
|
||||||
|
|
||||||
get baseUrl() {
|
get baseUrl() {
|
||||||
let index = parseInt(this.loadSetting('apiDomain')) - 1
|
let index = parseInt(this.loadSetting('apiDomain')) - 1
|
||||||
@@ -48,12 +54,49 @@ class JM extends ComicSource {
|
|||||||
return /^\d+$/.test(str)
|
return /^\d+$/.test(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
get apiUa() {
|
get baseHeaders() {
|
||||||
return JM.apiUa;
|
return {
|
||||||
|
"Accept": "*/*",
|
||||||
|
"Accept-Encoding": "gzip, deflate, br, zstd",
|
||||||
|
"Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
|
||||||
|
"Connection": "keep-alive",
|
||||||
|
"Origin": "https://localhost",
|
||||||
|
"Referer": "https://localhost/",
|
||||||
|
"Sec-Fetch-Dest": "empty",
|
||||||
|
"Sec-Fetch-Mode": "cors",
|
||||||
|
"Sec-Fetch-Site": "cross-site",
|
||||||
|
"X-Requested-With": JM.jmPkgName,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get imgUa() {
|
getApiHeaders(time) {
|
||||||
return JM.imgUa;
|
const jmAuthKey = "18comicAPPContent"
|
||||||
|
let token = Convert.md5(Convert.encodeUtf8(`${time}${jmAuthKey}`))
|
||||||
|
|
||||||
|
return {
|
||||||
|
...this.baseHeaders,
|
||||||
|
"Authorization": "Bearer",
|
||||||
|
"Sec-Fetch-Storage-Access": "active",
|
||||||
|
"token": Convert.hexEncode(token),
|
||||||
|
"tokenparam": `${time},${JM.jmVersion}`,
|
||||||
|
"User-Agent": this.ua,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getImgHeaders() {
|
||||||
|
return {
|
||||||
|
"Accept": "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8",
|
||||||
|
"Accept-Encoding": "gzip, deflate, br, zstd",
|
||||||
|
"Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
|
||||||
|
"Connection": "keep-alive",
|
||||||
|
"Referer": "https://localhost/",
|
||||||
|
"Sec-Fetch-Dest": "image",
|
||||||
|
"Sec-Fetch-Mode": "no-cors",
|
||||||
|
"Sec-Fetch-Site": "cross-site",
|
||||||
|
"Sec-Fetch-Storage-Access": "active",
|
||||||
|
"User-Agent": this.ua,
|
||||||
|
"X-Requested-With": JM.jmPkgName,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getCoverUrl(id) {
|
getCoverUrl(id) {
|
||||||
@@ -78,32 +121,33 @@ class JM extends ComicSource {
|
|||||||
* @param showConfirmDialog {boolean}
|
* @param showConfirmDialog {boolean}
|
||||||
*/
|
*/
|
||||||
async refreshApiDomains(showConfirmDialog) {
|
async refreshApiDomains(showConfirmDialog) {
|
||||||
let today = new Date();
|
let url = "https://rup4a04-c02.tos-cn-hongkong.bytepluses.com/newsvr-2025.txt"
|
||||||
let url = "https://jmappc01-1308024008.cos.ap-guangzhou.myqcloud.com/server-2024.txt"
|
|
||||||
let domainSecret = "diosfjckwpqpdfjkvnqQjsik"
|
let domainSecret = "diosfjckwpqpdfjkvnqQjsik"
|
||||||
let title = ""
|
let title = ""
|
||||||
let message = ""
|
let message = ""
|
||||||
|
let servers = []
|
||||||
let domains = []
|
let domains = []
|
||||||
let res = await fetch(
|
let res = await fetch(
|
||||||
`${url}?time=${today.getFullYear()}${today.getMonth() + 1}${today.getDate()}`,
|
url,
|
||||||
{headers: {"User-Agent": this.imgUa}}
|
{headers: this.baseHeaders}
|
||||||
)
|
)
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
let data = this.convertData(await res.text(), domainSecret)
|
let data = this.convertData(await res.text(), domainSecret)
|
||||||
let json = JSON.parse(data)
|
let json = JSON.parse(data)
|
||||||
if (json["Server"]) {
|
if (json["Server"]) {
|
||||||
title = "Update Success"
|
title = "Update Success"
|
||||||
message = "New domains:\n\n"
|
message = "\n"
|
||||||
domains = json["Server"]
|
servers = json["Server"].slice(0, 4)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (domains.length === 0) {
|
if (servers.length === 0) {
|
||||||
title = "Update Failed"
|
title = "Update Failed"
|
||||||
message = `Using built-in domains:\n\n`
|
message = `Using built-in domains:\n\n`
|
||||||
domains = JM.apiDomains
|
servers = JM.fallbackServers
|
||||||
}
|
}
|
||||||
for (let i = 0; i < domains.length; i++) {
|
for (let i = 0; i < servers.length; i++) {
|
||||||
message = message + `Stream ${i + 1}: ${domains[i]}\n`
|
message = message + `線路${i + 1}: ${servers[i]}\n\n`
|
||||||
|
domains.push(servers[i])
|
||||||
}
|
}
|
||||||
if (showConfirmDialog) {
|
if (showConfirmDialog) {
|
||||||
UI.showDialog(
|
UI.showDialog(
|
||||||
@@ -135,7 +179,7 @@ class JM extends ComicSource {
|
|||||||
async refreshImgUrl(showMessage) {
|
async refreshImgUrl(showMessage) {
|
||||||
let index = this.loadSetting('imageStream')
|
let index = this.loadSetting('imageStream')
|
||||||
let res = await this.get(
|
let res = await this.get(
|
||||||
`${this.baseUrl}/setting?app_img_shunt=${index}`
|
`${this.baseUrl}/setting?app_img_shunt=${index}?express=`
|
||||||
)
|
)
|
||||||
let setting = JSON.parse(res)
|
let setting = JSON.parse(res)
|
||||||
if (setting["img_host"]) {
|
if (setting["img_host"]) {
|
||||||
@@ -174,19 +218,6 @@ class JM extends ComicSource {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
getHeaders(time) {
|
|
||||||
const jmVersion = "1.7.6"
|
|
||||||
const jmAuthKey = "18comicAPPContent"
|
|
||||||
let token = Convert.md5(Convert.encodeUtf8(`${time}${jmAuthKey}`))
|
|
||||||
|
|
||||||
return {
|
|
||||||
"token": Convert.hexEncode(token),
|
|
||||||
"tokenparam": `${time},${jmVersion}`,
|
|
||||||
"Accept-Encoding": "gzip",
|
|
||||||
"User-Agent": this.apiUa,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param input {string}
|
* @param input {string}
|
||||||
@@ -213,7 +244,7 @@ class JM extends ComicSource {
|
|||||||
async get(url) {
|
async get(url) {
|
||||||
let time = Math.floor(Date.now() / 1000)
|
let time = Math.floor(Date.now() / 1000)
|
||||||
let kJmSecret = "185Hcomic3PAPP7R"
|
let kJmSecret = "185Hcomic3PAPP7R"
|
||||||
let res = await Network.get(url, this.getHeaders(time))
|
let res = await Network.get(url, this.getApiHeaders(time))
|
||||||
if(res.status !== 200) {
|
if(res.status !== 200) {
|
||||||
if(res.status === 401) {
|
if(res.status === 401) {
|
||||||
let json = JSON.parse(res.body)
|
let json = JSON.parse(res.body)
|
||||||
@@ -237,7 +268,7 @@ class JM extends ComicSource {
|
|||||||
let time = Math.floor(Date.now() / 1000)
|
let time = Math.floor(Date.now() / 1000)
|
||||||
let kJmSecret = "185Hcomic3PAPP7R"
|
let kJmSecret = "185Hcomic3PAPP7R"
|
||||||
let res = await Network.post(url, {
|
let res = await Network.post(url, {
|
||||||
...this.getHeaders(time),
|
...this.getApiHeaders(time),
|
||||||
"Content-Type": "application/x-www-form-urlencoded"
|
"Content-Type": "application/x-www-form-urlencoded"
|
||||||
}, body)
|
}, body)
|
||||||
if(res.status !== 200) {
|
if(res.status !== 200) {
|
||||||
@@ -339,6 +370,12 @@ class JM extends ComicSource {
|
|||||||
/// title of the category page, used to identify the page, it should be unique
|
/// title of the category page, used to identify the page, it should be unique
|
||||||
title: "禁漫天堂",
|
title: "禁漫天堂",
|
||||||
parts: [
|
parts: [
|
||||||
|
{
|
||||||
|
name: "每週必看",
|
||||||
|
type: "fixed",
|
||||||
|
categories: ["每週必看"],
|
||||||
|
itemType: "category",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "成人A漫",
|
name: "成人A漫",
|
||||||
type: "fixed",
|
type: "fixed",
|
||||||
@@ -449,33 +486,74 @@ class JM extends ComicSource {
|
|||||||
* @returns {Promise<{comics: Comic[], maxPage: number}>}
|
* @returns {Promise<{comics: Comic[], maxPage: number}>}
|
||||||
*/
|
*/
|
||||||
load: async (category, param, options, page) => {
|
load: async (category, param, options, page) => {
|
||||||
param ??= category
|
if (category !== "每週必看") {
|
||||||
param = encodeURIComponent(param)
|
param ??= category
|
||||||
let res = await this.get(`${this.baseUrl}/categories/filter?o=${options[0]}&c=${param}&page=${page}`)
|
param = encodeURIComponent(param)
|
||||||
let data = JSON.parse(res)
|
let res = await this.get(`${this.baseUrl}/categories/filter?o=${options[0]}&c=${param}&page=${page}`)
|
||||||
let total = data.total
|
let data = JSON.parse(res)
|
||||||
let maxPage = Math.ceil(total / 80)
|
let total = data.total
|
||||||
let comics = data.content.map((e) => this.parseComic(e))
|
let maxPage = Math.ceil(total / 80)
|
||||||
return {
|
let comics = data.content.map((e) => this.parseComic(e))
|
||||||
comics: comics,
|
return {
|
||||||
maxPage: maxPage
|
comics: comics,
|
||||||
|
maxPage: maxPage
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let res = await this.get(`${this.baseUrl}/week/filter?id=${options[0]}&page=1&type=${options[1]}&page=0`)
|
||||||
|
let data = JSON.parse(res)
|
||||||
|
let comics = data.list.map((e) => this.parseComic(e))
|
||||||
|
return {
|
||||||
|
comics: comics,
|
||||||
|
maxPage: 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// provide options for category comic loading
|
/**
|
||||||
optionList: [
|
* [Optional] load options dynamically. If `optionList` is provided, this will be ignored.
|
||||||
{
|
* @param category {string}
|
||||||
// For a single option, use `-` to separate the value and text, left for value, right for text
|
* @param param {string?}
|
||||||
options: [
|
* @return {Promise<{options: string[], label?: string}[]>} - return a list of option group, each group contains a list of options
|
||||||
"mr-最新",
|
*/
|
||||||
"mv-總排行",
|
optionLoader: async (category, param) => {
|
||||||
"mv_m-月排行",
|
if (category !== "每週必看") {
|
||||||
"mv_w-周排行",
|
return [
|
||||||
"mv_t-日排行",
|
{
|
||||||
"mp-最多圖片",
|
label: "排序",
|
||||||
"tf-最多喜歡",
|
// For a single option, use `-` to separate the value and text, left for value, right for text
|
||||||
],
|
options: [
|
||||||
|
"mr-最新",
|
||||||
|
"mv-總排行",
|
||||||
|
"mv_m-月排行",
|
||||||
|
"mv_w-周排行",
|
||||||
|
"mv_t-日排行",
|
||||||
|
"mp-最多圖片",
|
||||||
|
"tf-最多喜歡",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
let res = await this.get(`${this.baseUrl}/week`)
|
||||||
|
let data = JSON.parse(res)
|
||||||
|
let options = []
|
||||||
|
for (let e of data["categories"]) {
|
||||||
|
options.push(`${e["id"]}-${e["time"]}`)
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: "時間",
|
||||||
|
options: options,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "類型",
|
||||||
|
options: [
|
||||||
|
"manga-日漫",
|
||||||
|
"hanman-韓漫",
|
||||||
|
"another-其他",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
},
|
||||||
ranking: {
|
ranking: {
|
||||||
// For a single option, use `-` to separate the value and text, left for value, right for text
|
// For a single option, use `-` to separate the value and text, left for value, right for text
|
||||||
options: [
|
options: [
|
||||||
@@ -634,7 +712,7 @@ class JM extends ComicSource {
|
|||||||
if (id.startsWith('jm')) {
|
if (id.startsWith('jm')) {
|
||||||
id = id.substring(2)
|
id = id.substring(2)
|
||||||
}
|
}
|
||||||
let res = await this.get(`${this.baseUrl}/album?comicName=&id=${id}`);
|
let res = await this.get(`${this.baseUrl}/album?id=${id}`);
|
||||||
let data = JSON.parse(res)
|
let data = JSON.parse(res)
|
||||||
let author = data.author ?? []
|
let author = data.author ?? []
|
||||||
let chapters = new Map()
|
let chapters = new Map()
|
||||||
@@ -735,11 +813,9 @@ class JM extends ComicSource {
|
|||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
headers: {
|
headers: this.getImgHeaders(),
|
||||||
"Accept-Encoding": "gzip",
|
// gif 图片不需要修改
|
||||||
"User-Agent": this.imgUa,
|
modifyImage: url.endsWith(".gif") ? null : `
|
||||||
},
|
|
||||||
modifyImage: `
|
|
||||||
let modifyImage = (image) => {
|
let modifyImage = (image) => {
|
||||||
const num = ${num}
|
const num = ${num}
|
||||||
let blockSize = Math.floor(image.height / num)
|
let blockSize = Math.floor(image.height / num)
|
||||||
@@ -773,10 +849,7 @@ class JM extends ComicSource {
|
|||||||
*/
|
*/
|
||||||
onThumbnailLoad: (url) => {
|
onThumbnailLoad: (url) => {
|
||||||
return {
|
return {
|
||||||
headers: {
|
headers: this.getImgHeaders()
|
||||||
"Accept-Encoding": "gzip",
|
|
||||||
"User-Agent": this.imgUa,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
294
lanraragi.js
Normal file
294
lanraragi.js
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
/** @type {import('./_venera_.js')} */
|
||||||
|
class Lanraragi extends ComicSource {
|
||||||
|
name = "Lanraragi"
|
||||||
|
key = "lanraragi"
|
||||||
|
version = "1.1.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" },
|
||||||
|
apiKey: { title: "APIKEY", type: "input", default: "" }
|
||||||
|
}
|
||||||
|
|
||||||
|
get baseUrl() {
|
||||||
|
const api = this.loadSetting('api') || this.settings.api.default
|
||||||
|
|
||||||
|
return api.replace(/\/$/, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
get headers() {
|
||||||
|
let apiKey = this.loadSetting('apiKey')
|
||||||
|
if (apiKey) apiKey = "Bearer " + Convert.encodeBase64(Convert.encodeUtf8(apiKey))
|
||||||
|
|
||||||
|
return {
|
||||||
|
"Authorization": `${apiKey}`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
try {
|
||||||
|
const url = `${this.baseUrl}/api/categories`
|
||||||
|
const res = await Network.get(url, this.headers)
|
||||||
|
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, this.headers)
|
||||||
|
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, this.headers)
|
||||||
|
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, this.headers)
|
||||||
|
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, this.headers)
|
||||||
|
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, this.headers)
|
||||||
|
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, this.headers)
|
||||||
|
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) => {
|
||||||
|
return {
|
||||||
|
headers: this.headers
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onThumbnailLoad: (url) => {
|
||||||
|
return {
|
||||||
|
headers: this.headers
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 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,
|
||||||
|
}
|
||||||
|
}
|
1349
manhuagui.js
Normal file
1349
manhuagui.js
Normal file
File diff suppressed because it is too large
Load Diff
387
manwaba.js
Normal file
387
manwaba.js
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
/** @type {import('./_venera_.js')} */
|
||||||
|
class ManWaBa extends ComicSource {
|
||||||
|
// Note: The fields which are marked as [Optional] should be removed if not used
|
||||||
|
|
||||||
|
// name of the source
|
||||||
|
name = "漫蛙吧";
|
||||||
|
|
||||||
|
// unique id of the source
|
||||||
|
key = "manwaba";
|
||||||
|
|
||||||
|
version = "1.0.0";
|
||||||
|
|
||||||
|
minAppVersion = "1.4.0";
|
||||||
|
|
||||||
|
// update url
|
||||||
|
url = "https://git.nyne.dev/nyne/venera-configs/raw/branch/main/manwaba.js";
|
||||||
|
|
||||||
|
api = "https://www.manwaba.com/api/v1";
|
||||||
|
|
||||||
|
init() {
|
||||||
|
/**
|
||||||
|
* Sends an HTTP request.
|
||||||
|
* @param {string} url - The URL to send the request to.
|
||||||
|
* @param {string} method - The HTTP method (e.g., GET, POST, PUT, PATCH, DELETE).
|
||||||
|
* @param {Object} params - The query parameters to include in the request.
|
||||||
|
* @param {Object} headers - The headers to include in the request.
|
||||||
|
* @param {string} payload - The payload to include in the request.
|
||||||
|
* @returns {Promise<Object>} The response from the request.
|
||||||
|
*/
|
||||||
|
this.fetchJson = async (
|
||||||
|
url,
|
||||||
|
{ method = "GET", params, headers, payload }
|
||||||
|
) => {
|
||||||
|
if (params) {
|
||||||
|
let params_str = Object.keys(params)
|
||||||
|
.map((key) => `${key}=${params[key]}`)
|
||||||
|
.join("&");
|
||||||
|
url += `?${params_str}`;
|
||||||
|
}
|
||||||
|
let res = await Network.sendRequest(method, url, headers, payload);
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw `Invalid status code: ${res.status}, body: ${res.body}`;
|
||||||
|
}
|
||||||
|
let json = JSON.parse(res.body);
|
||||||
|
return json;
|
||||||
|
};
|
||||||
|
this.logger = {
|
||||||
|
error: (msg) => {
|
||||||
|
log("error", this.name, msg);
|
||||||
|
},
|
||||||
|
info: (msg) => {
|
||||||
|
log("info", this.name, msg);
|
||||||
|
},
|
||||||
|
warn: (msg) => {
|
||||||
|
log("warning", this.name, msg);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// explore page list
|
||||||
|
explore = [
|
||||||
|
{
|
||||||
|
// title of the page.
|
||||||
|
// title is used to identify the page, it should be unique
|
||||||
|
title: this.name,
|
||||||
|
|
||||||
|
/// multiPartPage or multiPageComicList or mixed
|
||||||
|
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) => {
|
||||||
|
let params = {
|
||||||
|
page: 1,
|
||||||
|
pageSize: 6,
|
||||||
|
type: "",
|
||||||
|
flag: false,
|
||||||
|
};
|
||||||
|
const url = `${this.api}/json/home`;
|
||||||
|
const data = await this.fetchJson(url, { params }).then(
|
||||||
|
(res) => res.data
|
||||||
|
);
|
||||||
|
let magnaList = {
|
||||||
|
热门: data.comicList,
|
||||||
|
古风: data.gufengList,
|
||||||
|
玄幻: data.xuanhuanList,
|
||||||
|
校园: data.xiaoyuanList,
|
||||||
|
};
|
||||||
|
function parseComic(comic) {
|
||||||
|
return new Comic({
|
||||||
|
id: comic.id.toString(),
|
||||||
|
title: comic.title,
|
||||||
|
subTitle: comic.author,
|
||||||
|
cover: comic.pic,
|
||||||
|
tags: comic.tags.split(","),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let result = {};
|
||||||
|
for (let key in magnaList) {
|
||||||
|
result[key] = magnaList[key].map(parseComic);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// categories
|
||||||
|
category = {
|
||||||
|
/// title of the category page, used to identify the page, it should be unique
|
||||||
|
title: this.name,
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
// title of the part
|
||||||
|
name: "类型",
|
||||||
|
|
||||||
|
// fixed or random or dynamic
|
||||||
|
// if random, need to provide `randomNumber` field, which indicates the number of comics to display at the same time
|
||||||
|
// if dynamic, need to provide `loader` field, which indicates the function to load comics
|
||||||
|
type: "fixed",
|
||||||
|
|
||||||
|
// Remove this if type is dynamic
|
||||||
|
categories: [
|
||||||
|
"全部",
|
||||||
|
"热血",
|
||||||
|
"玄幻",
|
||||||
|
"恋爱",
|
||||||
|
"冒险",
|
||||||
|
"古风",
|
||||||
|
"都市",
|
||||||
|
"穿越",
|
||||||
|
"奇幻",
|
||||||
|
"其他",
|
||||||
|
"搞笑",
|
||||||
|
"少男",
|
||||||
|
"战斗",
|
||||||
|
"重生",
|
||||||
|
"逆袭",
|
||||||
|
"爆笑",
|
||||||
|
"少年",
|
||||||
|
"后宫",
|
||||||
|
"系统",
|
||||||
|
"BL",
|
||||||
|
"韩漫",
|
||||||
|
"完整版",
|
||||||
|
"19r",
|
||||||
|
"台版",
|
||||||
|
],
|
||||||
|
|
||||||
|
itemType: "category",
|
||||||
|
categoryParams: [
|
||||||
|
"",
|
||||||
|
"热血",
|
||||||
|
"玄幻",
|
||||||
|
"恋爱",
|
||||||
|
"冒险",
|
||||||
|
"古风",
|
||||||
|
"都市",
|
||||||
|
"穿越",
|
||||||
|
"奇幻",
|
||||||
|
"其他",
|
||||||
|
"搞笑",
|
||||||
|
"少男",
|
||||||
|
"战斗",
|
||||||
|
"重生",
|
||||||
|
"逆袭",
|
||||||
|
"爆笑",
|
||||||
|
"少年",
|
||||||
|
"后宫",
|
||||||
|
"系统",
|
||||||
|
"BL",
|
||||||
|
"韩漫",
|
||||||
|
"完整版",
|
||||||
|
"19r",
|
||||||
|
"台版",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// enable ranking page
|
||||||
|
enableRankingPage: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// category comic loading related
|
||||||
|
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) => {
|
||||||
|
let url = `${this.api}/json/cate`;
|
||||||
|
let payload = JSON.stringify({
|
||||||
|
page: {
|
||||||
|
page: page,
|
||||||
|
pageSize: 10,
|
||||||
|
},
|
||||||
|
category: "comic",
|
||||||
|
sort: parseInt(options[2]),
|
||||||
|
comic: {
|
||||||
|
status: parseInt(options[0] == "2" ? -1 : options[0]),
|
||||||
|
day: parseInt(options[1]),
|
||||||
|
tag: param,
|
||||||
|
},
|
||||||
|
video: {
|
||||||
|
year: 0,
|
||||||
|
typeId: 0,
|
||||||
|
typeId1: 0,
|
||||||
|
area: "",
|
||||||
|
lang: "",
|
||||||
|
status: -1,
|
||||||
|
day: 0,
|
||||||
|
},
|
||||||
|
novel: {
|
||||||
|
status: -1,
|
||||||
|
day: 0,
|
||||||
|
sortId: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let data = await this.fetchJson(url, {
|
||||||
|
method: "POST",
|
||||||
|
payload,
|
||||||
|
}).then((res) => res.data);
|
||||||
|
|
||||||
|
function parseComic(comic) {
|
||||||
|
return new Comic({
|
||||||
|
id: comic.url.split("/").pop(),
|
||||||
|
title: comic.title,
|
||||||
|
subTitle: comic.author,
|
||||||
|
cover: comic.pic,
|
||||||
|
tags: comic.tags.split(","),
|
||||||
|
description: comic.intro,
|
||||||
|
status: comic.status == 0 ? "连载中" : "已完结",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
comics: data.map(parseComic),
|
||||||
|
maxPage: 100,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
// provide options for category comic loading
|
||||||
|
optionList: [
|
||||||
|
{
|
||||||
|
options: ["2-全部", "0-连载中", "1-已完结"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
options: [
|
||||||
|
"0-全部",
|
||||||
|
"1-周一",
|
||||||
|
"2-周二",
|
||||||
|
"3-周三",
|
||||||
|
"4-周四",
|
||||||
|
"5-周五",
|
||||||
|
"6-周六",
|
||||||
|
"7-周日",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
options: ["0-更新", "1-新作", "2-畅销", "3-热门", "4-收藏"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// search related
|
||||||
|
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) => {
|
||||||
|
const pageSize = 20;
|
||||||
|
let url = `${this.api}/json/search`;
|
||||||
|
let params = {
|
||||||
|
keyword,
|
||||||
|
type: "mh",
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
};
|
||||||
|
let data = await this.fetchJson(url, { params }).then((res) => res.data);
|
||||||
|
let total = data.total;
|
||||||
|
let comics = data.list.map((item) => {
|
||||||
|
return new Comic({
|
||||||
|
id: item.id.toString(),
|
||||||
|
title: item.title,
|
||||||
|
subTitle: item.author,
|
||||||
|
cover: item.cover,
|
||||||
|
tags: item.tags.split(","),
|
||||||
|
description: item.description,
|
||||||
|
status: item.status == 0 ? "连载中" : "已完结",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
let maxPage = Math.ceil(total / pageSize);
|
||||||
|
return {
|
||||||
|
comics,
|
||||||
|
maxPage,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// single comic related
|
||||||
|
comic = {
|
||||||
|
/**
|
||||||
|
* load comic info
|
||||||
|
* @param id {string}
|
||||||
|
* @returns {Promise<ComicDetails>}s
|
||||||
|
*/
|
||||||
|
loadInfo: async (id) => {
|
||||||
|
let url = `${this.api}/json/comic/${id}`;
|
||||||
|
let data = await this.fetchJson(url, { payload: undefined }).then(
|
||||||
|
(res) => res.data
|
||||||
|
);
|
||||||
|
this.logger.warn(`loadInfo: ${data}`);
|
||||||
|
let chapterId = data.id;
|
||||||
|
let chapterApi = `${this.api}/json/comic/chapter`;
|
||||||
|
let params = {
|
||||||
|
comicId: chapterId,
|
||||||
|
page: 1,
|
||||||
|
pageSize: 1,
|
||||||
|
};
|
||||||
|
let pageRes = await this.fetchJson(chapterApi, { params });
|
||||||
|
let total = pageRes.pagination.total;
|
||||||
|
|
||||||
|
let chapterRes = await this.fetchJson(chapterApi, {
|
||||||
|
params: {
|
||||||
|
...params,
|
||||||
|
pageSize: total,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let chapterList = chapterRes.data;
|
||||||
|
let chapters = new Map();
|
||||||
|
chapterList.forEach((item) => {
|
||||||
|
chapters.set(item.id.toString(), item.title.toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
return new ComicDetails({
|
||||||
|
title: data.title.toString(),
|
||||||
|
subTitle: data.author.toString(),
|
||||||
|
cover: data.cover,
|
||||||
|
tags: {
|
||||||
|
类型: data.tags.split(","),
|
||||||
|
状态: data.status == 0 ? "连载中" : "已完结",
|
||||||
|
},
|
||||||
|
chapters,
|
||||||
|
description: data.intro,
|
||||||
|
updateTime: new Date(data.editTime * 1000).toLocaleDateString(),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* load images of a chapter
|
||||||
|
* @param comicId {string}
|
||||||
|
* @param epId {string?}
|
||||||
|
* @returns {Promise<{images: string[]}>}
|
||||||
|
*/
|
||||||
|
loadEp: async (comicId, epId) => {
|
||||||
|
let imgApi = `${this.api}/comic/image/${epId}`;
|
||||||
|
let params = {
|
||||||
|
page: 1,
|
||||||
|
pageSize: 1,
|
||||||
|
imageSource: "https://tu.mhttu.cc",
|
||||||
|
};
|
||||||
|
let pageNum = await this.fetchJson(imgApi, {
|
||||||
|
params,
|
||||||
|
}).then((res) => res.data.pagination.total);
|
||||||
|
let imageRes = await this.fetchJson(imgApi, {
|
||||||
|
params: {
|
||||||
|
...params,
|
||||||
|
pageSize: pageNum,
|
||||||
|
},
|
||||||
|
}).then((res) => res.data.images);
|
||||||
|
let images = imageRes.map((item) => item.url);
|
||||||
|
return {
|
||||||
|
images,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
23
nhentai.js
23
nhentai.js
@@ -7,7 +7,7 @@ class Nhentai extends ComicSource {
|
|||||||
// unique id of the source
|
// unique id of the source
|
||||||
key = "nhentai"
|
key = "nhentai"
|
||||||
|
|
||||||
version = "1.0.4"
|
version = "1.0.6"
|
||||||
|
|
||||||
minAppVersion = "1.0.0"
|
minAppVersion = "1.0.0"
|
||||||
|
|
||||||
@@ -328,6 +328,25 @@ class Nhentai extends ComicSource {
|
|||||||
|
|
||||||
/// single comic related
|
/// single comic related
|
||||||
comic = {
|
comic = {
|
||||||
|
/**
|
||||||
|
* [Optional] provide configs for a thumbnail loading
|
||||||
|
* @param url {string}
|
||||||
|
* @returns {ImageLoadingConfig | Promise<ImageLoadingConfig>}
|
||||||
|
*
|
||||||
|
* `ImageLoadingConfig.modifyImage` and `ImageLoadingConfig.onLoadFailed` will be ignored.
|
||||||
|
* They are not supported for thumbnails.
|
||||||
|
*/
|
||||||
|
onThumbnailLoad: (url) => {
|
||||||
|
if(url.startsWith("//")) {
|
||||||
|
url = "https:" + url
|
||||||
|
} else if(!url.startsWith("http")) {
|
||||||
|
url = "https://" + url
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: url,
|
||||||
|
}
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* load comic info
|
* load comic info
|
||||||
* @param id {string}
|
* @param id {string}
|
||||||
@@ -504,7 +523,7 @@ class Nhentai extends ComicSource {
|
|||||||
'nhentai.net',
|
'nhentai.net',
|
||||||
],
|
],
|
||||||
linkToId: (url) => {
|
linkToId: (url) => {
|
||||||
let regex = /\/g\/(\d+)\//g
|
let regex = /\/g\/(\d+)\/?$/g
|
||||||
let match = regex.exec(url)
|
let match = regex.exec(url)
|
||||||
if(match) {
|
if(match) {
|
||||||
return match[1]
|
return match[1]
|
||||||
|
155
picacg.js
155
picacg.js
@@ -3,13 +3,13 @@ class Picacg extends ComicSource {
|
|||||||
|
|
||||||
key = "picacg"
|
key = "picacg"
|
||||||
|
|
||||||
version = "1.0.3"
|
version = "1.0.5"
|
||||||
|
|
||||||
minAppVersion = "1.0.0"
|
minAppVersion = "1.0.0"
|
||||||
|
|
||||||
url = "https://git.nyne.dev/nyne/venera-configs/raw/branch/main/picacg.js"
|
url = "https://git.nyne.dev/nyne/venera-configs/raw/branch/main/picacg.js"
|
||||||
|
|
||||||
api = "https://picaapi.picacomic.com"
|
static defaultApiUrl = "https://picaapi.picacomic.com"
|
||||||
|
|
||||||
apiKey = "C69BAF41DA5ABD1FFEDC6D2FEA56B";
|
apiKey = "C69BAF41DA5ABD1FFEDC6D2FEA56B";
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ class Picacg extends ComicSource {
|
|||||||
},
|
},
|
||||||
login: async (account, pwd) => {
|
login: async (account, pwd) => {
|
||||||
let res = await Network.post(
|
let res = await Network.post(
|
||||||
`${this.api}/auth/sign-in`,
|
`${this.loadSetting('base_url')}/auth/sign-in`,
|
||||||
this.buildHeaders('POST', 'auth/sign-in'),
|
this.buildHeaders('POST', 'auth/sign-in'),
|
||||||
{
|
{
|
||||||
email: account,
|
email: account,
|
||||||
@@ -111,13 +111,13 @@ class Picacg extends ComicSource {
|
|||||||
throw 'Not logged in'
|
throw 'Not logged in'
|
||||||
}
|
}
|
||||||
let res = await Network.get(
|
let res = await Network.get(
|
||||||
`${this.api}/comics/random`,
|
`${this.loadSetting('base_url')}/comics/random`,
|
||||||
this.buildHeaders('GET', 'comics/random', this.loadData('token'))
|
this.buildHeaders('GET', 'comics/random', this.loadData('token'))
|
||||||
)
|
)
|
||||||
if(res.status === 401) {
|
if(res.status === 401) {
|
||||||
await this.account.reLogin()
|
await this.account.reLogin()
|
||||||
res = await Network.get(
|
res = await Network.get(
|
||||||
`${this.api}/comics/random`,
|
`${this.loadSetting('base_url')}/comics/random`,
|
||||||
this.buildHeaders('GET', 'comics/random', this.loadData('token'))
|
this.buildHeaders('GET', 'comics/random', this.loadData('token'))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -142,13 +142,13 @@ class Picacg extends ComicSource {
|
|||||||
throw 'Not logged in'
|
throw 'Not logged in'
|
||||||
}
|
}
|
||||||
let res = await Network.get(
|
let res = await Network.get(
|
||||||
`${this.api}/comics?page=${page}&s=dd`,
|
`${this.loadSetting('base_url')}/comics?page=${page}&s=dd`,
|
||||||
this.buildHeaders('GET', `comics?page=${page}&s=dd`, this.loadData('token'))
|
this.buildHeaders('GET', `comics?page=${page}&s=dd`, this.loadData('token'))
|
||||||
)
|
)
|
||||||
if(res.status === 401) {
|
if(res.status === 401) {
|
||||||
await this.account.reLogin()
|
await this.account.reLogin()
|
||||||
res = await Network.get(
|
res = await Network.get(
|
||||||
`${this.api}/comics?page=${page}&s=dd`,
|
`${this.loadSetting('base_url')}/comics?page=${page}&s=dd`,
|
||||||
this.buildHeaders('GET', `comics?page=${page}&s=dd`, this.loadData('token'))
|
this.buildHeaders('GET', `comics?page=${page}&s=dd`, this.loadData('token'))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -229,13 +322,13 @@ class Picacg extends ComicSource {
|
|||||||
load: async (category, param, options, page) => {
|
load: async (category, param, options, page) => {
|
||||||
let type = param ?? 'c'
|
let type = param ?? 'c'
|
||||||
let res = await Network.get(
|
let res = await Network.get(
|
||||||
`${this.api}/comics?page=${page}&${type}=${encodeURIComponent(category)}&s=${options[0]}`,
|
`${this.loadSetting('base_url')}/comics?page=${page}&${type}=${encodeURIComponent(category)}&s=${options[0]}`,
|
||||||
this.buildHeaders('GET', `comics?page=${page}&${type}=${encodeURIComponent(category)}&s=${options[0]}`, this.loadData('token'))
|
this.buildHeaders('GET', `comics?page=${page}&${type}=${encodeURIComponent(category)}&s=${options[0]}`, this.loadData('token'))
|
||||||
)
|
)
|
||||||
if(res.status === 401) {
|
if(res.status === 401) {
|
||||||
await this.account.reLogin()
|
await this.account.reLogin()
|
||||||
res = await Network.get(
|
res = await Network.get(
|
||||||
`${this.api}/comics?page=${page}&${type}=${encodeURIComponent(category)}&s=${options[0]}`,
|
`${this.loadSetting('base_url')}/comics?page=${page}&${type}=${encodeURIComponent(category)}&s=${options[0]}`,
|
||||||
this.buildHeaders('GET', `comics?page=${page}&${type}=${encodeURIComponent(category)}&s=${options[0]}`, this.loadData('token'))
|
this.buildHeaders('GET', `comics?page=${page}&${type}=${encodeURIComponent(category)}&s=${options[0]}`, this.loadData('token'))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -271,13 +364,13 @@ class Picacg extends ComicSource {
|
|||||||
],
|
],
|
||||||
load: async (option, page) => {
|
load: async (option, page) => {
|
||||||
let res = await Network.get(
|
let res = await Network.get(
|
||||||
`${this.api}/comics/leaderboard?tt=${option}&ct=VC`,
|
`${this.loadSetting('base_url')}/comics/leaderboard?tt=${option}&ct=VC`,
|
||||||
this.buildHeaders('GET', `comics/leaderboard?tt=${option}&ct=VC`, this.loadData('token'))
|
this.buildHeaders('GET', `comics/leaderboard?tt=${option}&ct=VC`, this.loadData('token'))
|
||||||
)
|
)
|
||||||
if(res.status === 401) {
|
if(res.status === 401) {
|
||||||
await this.account.reLogin()
|
await this.account.reLogin()
|
||||||
res = await Network.get(
|
res = await Network.get(
|
||||||
`${this.api}/comics/leaderboard?tt=${option}&ct=VC`,
|
`${this.loadSetting('base_url')}/comics/leaderboard?tt=${option}&ct=VC`,
|
||||||
this.buildHeaders('GET', `comics/leaderboard?tt=${option}&ct=VC`, this.loadData('token'))
|
this.buildHeaders('GET', `comics/leaderboard?tt=${option}&ct=VC`, this.loadData('token'))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -301,7 +394,7 @@ class Picacg extends ComicSource {
|
|||||||
search = {
|
search = {
|
||||||
load: async (keyword, options, page) => {
|
load: async (keyword, options, page) => {
|
||||||
let res = await Network.post(
|
let res = await Network.post(
|
||||||
`${this.api}/comics/advanced-search?page=${page}`,
|
`${this.loadSetting('base_url')}/comics/advanced-search?page=${page}`,
|
||||||
this.buildHeaders('POST', `comics/advanced-search?page=${page}`, this.loadData('token')),
|
this.buildHeaders('POST', `comics/advanced-search?page=${page}`, this.loadData('token')),
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
keyword: keyword,
|
keyword: keyword,
|
||||||
@@ -311,7 +404,7 @@ class Picacg extends ComicSource {
|
|||||||
if(res.status === 401) {
|
if(res.status === 401) {
|
||||||
await this.account.reLogin()
|
await this.account.reLogin()
|
||||||
res = await Network.post(
|
res = await Network.post(
|
||||||
`${this.api}/comics/advanced-search?page=${page}`,
|
`${this.loadSetting('base_url')}/comics/advanced-search?page=${page}`,
|
||||||
this.buildHeaders('POST', `comics/advanced-search?page=${page}`, this.loadData('token')),
|
this.buildHeaders('POST', `comics/advanced-search?page=${page}`, this.loadData('token')),
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
keyword: keyword,
|
keyword: keyword,
|
||||||
@@ -351,7 +444,7 @@ class Picacg extends ComicSource {
|
|||||||
/// 添加或者删除收藏
|
/// 添加或者删除收藏
|
||||||
addOrDelFavorite: async (comicId, folderId, isAdding) => {
|
addOrDelFavorite: async (comicId, folderId, isAdding) => {
|
||||||
let res = await Network.post(
|
let res = await Network.post(
|
||||||
`${this.api}/comics/${comicId}/favourite`,
|
`${this.loadSetting('base_url')}/comics/${comicId}/favourite`,
|
||||||
this.buildHeaders('POST', `comics/${comicId}/favourite`, this.loadData('token')),
|
this.buildHeaders('POST', `comics/${comicId}/favourite`, this.loadData('token')),
|
||||||
'{}'
|
'{}'
|
||||||
)
|
)
|
||||||
@@ -367,7 +460,7 @@ class Picacg extends ComicSource {
|
|||||||
loadComics: async (page, folder) => {
|
loadComics: async (page, folder) => {
|
||||||
let sort = this.loadSetting('favoriteSort')
|
let sort = this.loadSetting('favoriteSort')
|
||||||
let res = await Network.get(
|
let res = await Network.get(
|
||||||
`${this.api}/users/favourite?page=${page}&s=${sort}`,
|
`${this.loadSetting('base_url')}/users/favourite?page=${page}&s=${sort}`,
|
||||||
this.buildHeaders('GET', `users/favourite?page=${page}&s=${sort}`, this.loadData('token'))
|
this.buildHeaders('GET', `users/favourite?page=${page}&s=${sort}`, this.loadData('token'))
|
||||||
)
|
)
|
||||||
if(res.status === 401) {
|
if(res.status === 401) {
|
||||||
@@ -394,7 +487,7 @@ class Picacg extends ComicSource {
|
|||||||
loadInfo: async (id) => {
|
loadInfo: async (id) => {
|
||||||
let infoLoader = async () => {
|
let infoLoader = async () => {
|
||||||
let res = await Network.get(
|
let res = await Network.get(
|
||||||
`${this.api}/comics/${id}`,
|
`${this.loadSetting('base_url')}/comics/${id}`,
|
||||||
this.buildHeaders('GET', `comics/${id}`, this.loadData('token'))
|
this.buildHeaders('GET', `comics/${id}`, this.loadData('token'))
|
||||||
)
|
)
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
@@ -410,7 +503,7 @@ class Picacg extends ComicSource {
|
|||||||
let allEps = [];
|
let allEps = [];
|
||||||
while (true) {
|
while (true) {
|
||||||
let res = await Network.get(
|
let res = await Network.get(
|
||||||
`${this.api}/comics/${id}/eps?page=${i}`,
|
`${this.loadSetting('base_url')}/comics/${id}/eps?page=${i}`,
|
||||||
this.buildHeaders('GET', `comics/${id}/eps?page=${i}`, this.loadData('token'))
|
this.buildHeaders('GET', `comics/${id}/eps?page=${i}`, this.loadData('token'))
|
||||||
);
|
);
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
@@ -432,7 +525,7 @@ class Picacg extends ComicSource {
|
|||||||
}
|
}
|
||||||
let relatedLoader = async () => {
|
let relatedLoader = async () => {
|
||||||
let res = await Network.get(
|
let res = await Network.get(
|
||||||
`${this.api}/comics/${id}/recommendation`,
|
`${this.loadSetting('base_url')}/comics/${id}/recommendation`,
|
||||||
this.buildHeaders('GET', `comics/${id}/recommendation`, this.loadData('token'))
|
this.buildHeaders('GET', `comics/${id}/recommendation`, this.loadData('token'))
|
||||||
)
|
)
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
@@ -491,7 +584,7 @@ class Picacg extends ComicSource {
|
|||||||
let i = 1
|
let i = 1
|
||||||
while(true) {
|
while(true) {
|
||||||
let res = await Network.get(
|
let res = await Network.get(
|
||||||
`${this.api}/comics/${comicId}/order/${epId}/pages?page=${i}`,
|
`${this.loadSetting('base_url')}/comics/${comicId}/order/${epId}/pages?page=${i}`,
|
||||||
this.buildHeaders('GET', `comics/${comicId}/order/${epId}/pages?page=${i}`, this.loadData('token'))
|
this.buildHeaders('GET', `comics/${comicId}/order/${epId}/pages?page=${i}`, this.loadData('token'))
|
||||||
)
|
)
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
@@ -512,7 +605,7 @@ class Picacg extends ComicSource {
|
|||||||
},
|
},
|
||||||
likeComic: async (id, isLike) => {
|
likeComic: async (id, isLike) => {
|
||||||
var res = await Network.post(
|
var res = await Network.post(
|
||||||
`${this.api}/comics/${id}/like`,
|
`${this.loadSetting('base_url')}/comics/${id}/like`,
|
||||||
this.buildHeaders('POST', `comics/${id}/like`, this.loadData('token')),
|
this.buildHeaders('POST', `comics/${id}/like`, this.loadData('token')),
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
@@ -541,7 +634,7 @@ class Picacg extends ComicSource {
|
|||||||
|
|
||||||
if(replyTo) {
|
if(replyTo) {
|
||||||
let res = await Network.get(
|
let res = await Network.get(
|
||||||
`${this.api}/comments/${replyTo}/childrens?page=${page}`,
|
`${this.loadSetting('base_url')}/comments/${replyTo}/childrens?page=${page}`,
|
||||||
this.buildHeaders('GET', `comments/${replyTo}/childrens?page=${page}`, this.loadData('token'))
|
this.buildHeaders('GET', `comments/${replyTo}/childrens?page=${page}`, this.loadData('token'))
|
||||||
)
|
)
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
@@ -554,7 +647,7 @@ class Picacg extends ComicSource {
|
|||||||
maxPage = data.data.comments.pages
|
maxPage = data.data.comments.pages
|
||||||
} else {
|
} else {
|
||||||
let res = await Network.get(
|
let res = await Network.get(
|
||||||
`${this.api}/comics/${comicId}/comments?page=${page}`,
|
`${this.loadSetting('base_url')}/comics/${comicId}/comments?page=${page}`,
|
||||||
this.buildHeaders('GET', `comics/${comicId}/comments?page=${page}`, this.loadData('token'))
|
this.buildHeaders('GET', `comics/${comicId}/comments?page=${page}`, this.loadData('token'))
|
||||||
)
|
)
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
@@ -575,7 +668,7 @@ class Picacg extends ComicSource {
|
|||||||
sendComment: async (comicId, subId, content, replyTo) => {
|
sendComment: async (comicId, subId, content, replyTo) => {
|
||||||
if(replyTo) {
|
if(replyTo) {
|
||||||
let res = await Network.post(
|
let res = await Network.post(
|
||||||
`${this.api}/comments/${replyTo}`,
|
`${this.loadSetting('base_url')}/comments/${replyTo}`,
|
||||||
this.buildHeaders('POST', `/comments/${replyTo}`, this.loadData('token')),
|
this.buildHeaders('POST', `/comments/${replyTo}`, this.loadData('token')),
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
content: content
|
content: content
|
||||||
@@ -586,7 +679,7 @@ class Picacg extends ComicSource {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let res = await Network.post(
|
let res = await Network.post(
|
||||||
`${this.api}/comics/${comicId}/comments`,
|
`${this.loadSetting('base_url')}/comics/${comicId}/comments`,
|
||||||
this.buildHeaders('POST', `/comics/${comicId}/comments`, this.loadData('token')),
|
this.buildHeaders('POST', `/comics/${comicId}/comments`, this.loadData('token')),
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
content: content
|
content: content
|
||||||
@@ -600,7 +693,7 @@ class Picacg extends ComicSource {
|
|||||||
},
|
},
|
||||||
likeComment: async (comicId, subId, commentId, isLike) => {
|
likeComment: async (comicId, subId, commentId, isLike) => {
|
||||||
let res = await Network.post(
|
let res = await Network.post(
|
||||||
`${this.api}/comments/${commentId}/like`,
|
`${this.loadSetting('base_url')}/comments/${commentId}/like`,
|
||||||
this.buildHeaders('POST', `/comments/${commentId}/like`, this.loadData('token')),
|
this.buildHeaders('POST', `/comments/${commentId}/like`, this.loadData('token')),
|
||||||
'{}'
|
'{}'
|
||||||
)
|
)
|
||||||
@@ -632,6 +725,12 @@ class Picacg extends ComicSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
settings = {
|
settings = {
|
||||||
|
base_url: {
|
||||||
|
title: "API地址(地址末尾不要添加斜杠)",
|
||||||
|
type: "input",
|
||||||
|
validator: null,
|
||||||
|
default: Picacg.defaultApiUrl,
|
||||||
|
},
|
||||||
'imageQuality': {
|
'imageQuality': {
|
||||||
type: 'select',
|
type: 'select',
|
||||||
title: 'Image quality',
|
title: 'Image quality',
|
||||||
@@ -685,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': "最多喜欢",
|
||||||
@@ -704,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': "最多喜歡",
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
class ShonenJumpPlus extends ComicSource {
|
class ShonenJumpPlus extends ComicSource {
|
||||||
name = "少年ジャンプ+";
|
name = "少年ジャンプ+";
|
||||||
key = "shonen_jump_plus";
|
key = "shonen_jump_plus";
|
||||||
version = "1.0.1";
|
version = "1.1.0";
|
||||||
minAppVersion = "1.2.1";
|
minAppVersion = "1.2.1";
|
||||||
url =
|
url =
|
||||||
"https://git.nyne.dev/nyne/venera-configs/raw/branch/main/shonen_jump_plus.js";
|
"https://git.nyne.dev/nyne/venera-configs/raw/branch/main/shonen_jump_plus.js";
|
||||||
@@ -10,13 +10,14 @@ class ShonenJumpPlus extends ComicSource {
|
|||||||
bearerToken = null;
|
bearerToken = null;
|
||||||
userAccountId = null;
|
userAccountId = null;
|
||||||
tokenExpiry = 0;
|
tokenExpiry = 0;
|
||||||
|
latestVersion = "4.0.21";
|
||||||
|
|
||||||
get headers() {
|
get headers() {
|
||||||
return {
|
return {
|
||||||
"Origin": "https://shonenjumpplus.com",
|
"Origin": "https://shonenjumpplus.com",
|
||||||
"Referer": "https://shonenjumpplus.com/",
|
"Referer": "https://shonenjumpplus.com/",
|
||||||
"X-Giga-Device-Id": this.deviceId,
|
"X-Giga-Device-Id": this.deviceId,
|
||||||
"User-Agent": "ShonenJumpPlus-Android/4.0.19",
|
"User-Agent": `ShonenJumpPlus-Android/${this.latestVersion}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +31,14 @@ class ShonenJumpPlus extends ComicSource {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
init() { }
|
async init() {
|
||||||
|
const url = "https://apps.apple.com/jp/app/少年ジャンプ-人気漫画が読める雑誌アプリ/id875750302";
|
||||||
|
const resp = await Network.get(url);
|
||||||
|
const match = resp.body.match(/":\[\{\\"versionDisplay\\":\\"([\d.]+)\\",\\"rele/);
|
||||||
|
if (match) {
|
||||||
|
this.latestVersion = match[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
explore = [
|
explore = [
|
||||||
{
|
{
|
||||||
@@ -85,8 +93,9 @@ class ShonenJumpPlus extends ComicSource {
|
|||||||
? cover.replace("{height}", "500").replace("{width}", "500")
|
? cover.replace("{height}", "500").replace("{width}", "500")
|
||||||
: "",
|
: "",
|
||||||
tags: [],
|
tags: [],
|
||||||
description: `Ranking: ${item.rank} · Views: ${item.viewCount || "Unknown"
|
description: `Ranking: ${item.rank} · Views: ${
|
||||||
}`,
|
item.viewCount || "Unknown"
|
||||||
|
}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,6 +125,9 @@ class ShonenJumpPlus extends ComicSource {
|
|||||||
const pageInfo = response?.data?.search?.pageInfo || {};
|
const pageInfo = response?.data?.search?.pageInfo || {};
|
||||||
|
|
||||||
const comics = edges.map(({ node }) => {
|
const comics = edges.map(({ node }) => {
|
||||||
|
const authors = (node.author?.name || "").split(/\s*\/\s*/).filter(
|
||||||
|
Boolean,
|
||||||
|
);
|
||||||
const cover = node.latestIssue?.thumbnailUriTemplate ||
|
const cover = node.latestIssue?.thumbnailUriTemplate ||
|
||||||
node.thumbnailUriTemplate;
|
node.thumbnailUriTemplate;
|
||||||
if (node.__typename === "Series") {
|
if (node.__typename === "Series") {
|
||||||
@@ -123,9 +135,8 @@ class ShonenJumpPlus extends ComicSource {
|
|||||||
id: node.databaseId,
|
id: node.databaseId,
|
||||||
title: node.title || "",
|
title: node.title || "",
|
||||||
cover: this.replaceCoverUrl(cover),
|
cover: this.replaceCoverUrl(cover),
|
||||||
extra: {
|
description: node.description || "",
|
||||||
author: node.author?.name || "",
|
tags: authors,
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (node.__typename === "MagazineLabel") {
|
if (node.__typename === "MagazineLabel") {
|
||||||
@@ -150,16 +161,40 @@ class ShonenJumpPlus extends ComicSource {
|
|||||||
loadInfo: async (id) => {
|
loadInfo: async (id) => {
|
||||||
await this.ensureAuth();
|
await this.ensureAuth();
|
||||||
const seriesData = await this.fetchSeriesDetail(id);
|
const seriesData = await this.fetchSeriesDetail(id);
|
||||||
const chapters = await this.fetchEpisodes(id);
|
const episodes = await this.fetchEpisodes(id);
|
||||||
|
|
||||||
|
const { chapters, latestPublishAt } = episodes.reduce(
|
||||||
|
(acc, ep) => ({
|
||||||
|
chapters: {
|
||||||
|
...acc.chapters,
|
||||||
|
[ep.databaseId]: ep.title || "",
|
||||||
|
},
|
||||||
|
latestPublishAt:
|
||||||
|
ep.publishedAt && ep.publishedAt > acc.latestPublishAt
|
||||||
|
? ep.publishedAt
|
||||||
|
: acc.latestPublishAt,
|
||||||
|
}),
|
||||||
|
{ chapters: {}, latestPublishAt: "" },
|
||||||
|
);
|
||||||
|
|
||||||
|
const maxDate = latestPublishAt > seriesData.openAt
|
||||||
|
? latestPublishAt
|
||||||
|
: seriesData.openAt;
|
||||||
|
const updateDate = new Date(new Date(maxDate) - 60 * 60 * 1000);
|
||||||
|
const authors = (seriesData.author?.name || "").split(/\s*\/\s*/).filter(
|
||||||
|
Boolean,
|
||||||
|
);
|
||||||
|
|
||||||
return new ComicDetails({
|
return new ComicDetails({
|
||||||
title: seriesData.title || "",
|
title: seriesData.title || "",
|
||||||
subtitle: seriesData.author?.name || "",
|
subtitle: authors.join(" / "),
|
||||||
cover: this.replaceCoverUrl(seriesData.thumbnailUriTemplate),
|
cover: this.replaceCoverUrl(seriesData.thumbnailUriTemplate),
|
||||||
description: seriesData.descriptionBanner?.text || "",
|
description: seriesData.description || "",
|
||||||
tags: {
|
tags: {
|
||||||
"Author": [seriesData.author?.name || ""],
|
"Author": authors,
|
||||||
|
"Update": [updateDate.toISOString().slice(0, 10)],
|
||||||
},
|
},
|
||||||
|
url: `https://shonenjumpplus.com/app/episode/${seriesData.publisherId}`,
|
||||||
chapters,
|
chapters,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -264,11 +299,10 @@ class ShonenJumpPlus extends ComicSource {
|
|||||||
"SeriesDetailEpisodeList",
|
"SeriesDetailEpisodeList",
|
||||||
{ id, episodeOffset: 0, episodeFirst: 1500, episodeSort: "NUMBER_ASC" },
|
{ id, episodeOffset: 0, episodeFirst: 1500, episodeSort: "NUMBER_ASC" },
|
||||||
);
|
);
|
||||||
const episodes = response?.data?.series?.episodes?.edges || [];
|
const episodes = (response?.data?.series?.episodes?.edges || []).map(
|
||||||
return episodes.reduce((chapters, { node }) => ({
|
(edge) => edge.node
|
||||||
...chapters,
|
);
|
||||||
[node.databaseId]: node.title || "",
|
return episodes;
|
||||||
}), {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchEpisodePages(episodeId) {
|
async fetchEpisodePages(episodeId) {
|
||||||
@@ -352,7 +386,7 @@ const GraphQLQueries = {
|
|||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
__typename
|
__typename
|
||||||
... on Series { id databaseId title thumbnailUriTemplate author { name } }
|
... on Series { id databaseId title thumbnailUriTemplate author { name } description }
|
||||||
... on MagazineLabel { id databaseId title thumbnailUriTemplate latestIssue { thumbnailUriTemplate } }
|
... on MagazineLabel { id databaseId title thumbnailUriTemplate latestIssue { thumbnailUriTemplate } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -361,15 +395,18 @@ const GraphQLQueries = {
|
|||||||
"SeriesDetail": `query SeriesDetail($id: String!) {
|
"SeriesDetail": `query SeriesDetail($id: String!) {
|
||||||
series(databaseId: $id) {
|
series(databaseId: $id) {
|
||||||
id databaseId title thumbnailUriTemplate
|
id databaseId title thumbnailUriTemplate
|
||||||
author { name } descriptionBanner { text }
|
author { name }
|
||||||
|
description
|
||||||
hashtags serialUpdateScheduleLabel
|
hashtags serialUpdateScheduleLabel
|
||||||
|
openAt
|
||||||
|
publisherId
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
"SeriesDetailEpisodeList":
|
"SeriesDetailEpisodeList":
|
||||||
`query SeriesDetailEpisodeList($id: String!, $episodeOffset: Int, $episodeFirst: Int, $episodeSort: ReadableProductSorting) {
|
`query SeriesDetailEpisodeList($id: String!, $episodeOffset: Int, $episodeFirst: Int, $episodeSort: ReadableProductSorting) {
|
||||||
series(databaseId: $id) {
|
series(databaseId: $id) {
|
||||||
episodes: readableProducts(types: [EPISODE,SPECIAL_CONTENT], first: $episodeFirst, offset: $episodeOffset, sort: $episodeSort) {
|
episodes: readableProducts(types: [EPISODE], first: $episodeFirst, offset: $episodeOffset, sort: $episodeSort) {
|
||||||
edges { node { databaseId title } }
|
edges { node { databaseId title publishedAt } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
883
ykmh.js
Normal file
883
ykmh.js
Normal file
@@ -0,0 +1,883 @@
|
|||||||
|
/** @type {import('./_venera_.js')} */
|
||||||
|
class YKMHSource extends ComicSource {
|
||||||
|
name = "优酷漫画"
|
||||||
|
key = "ykmh"
|
||||||
|
version = "1.0.0"
|
||||||
|
minAppVersion = "1.4.0"
|
||||||
|
url = "https://git.nyne.dev/nyne/venera-configs/raw/branch/main/ykmh.js"
|
||||||
|
|
||||||
|
get baseUrl() {
|
||||||
|
return "https://www.ykmh.net";
|
||||||
|
}
|
||||||
|
|
||||||
|
explore = [
|
||||||
|
{
|
||||||
|
title: "优酷漫画",
|
||||||
|
type: "multiPartPage",
|
||||||
|
|
||||||
|
load: async (page) => {
|
||||||
|
let res = await Network.get("https://www.ykmh.net")
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw `Invalid status code: ${res.status}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseHotCarousel(html) {
|
||||||
|
let hotComics = []
|
||||||
|
let carouselPattern = /<div class="sub-item">\s*<a href="([^"]+)" target="_blank"><img src="([^"]+)" alt="[^"]*"><\/a>\s*<div class="carousel-caption">\s*([^<]+)\s*<\/div>/g
|
||||||
|
let match
|
||||||
|
while ((match = carouselPattern.exec(html)) !== null) {
|
||||||
|
let cover = match[2]
|
||||||
|
if (!cover.startsWith('http')) {
|
||||||
|
cover = 'https://www.ykmh.net' + (cover.startsWith('/') ? cover : '/' + cover)
|
||||||
|
}
|
||||||
|
|
||||||
|
hotComics.push(new Comic({
|
||||||
|
id: match[1],
|
||||||
|
title: match[3].trim(),
|
||||||
|
cover: cover,
|
||||||
|
tags: [`热门推荐`],
|
||||||
|
description: "热门推荐漫画"
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
if (hotComics.length === 0) {
|
||||||
|
let keywordPattern = /<li data-key="(\d+)"><a href="(https:\/\/www\.ykmh\.net\/manhua\/[^"]+)"[^>]*>([^<]+)<\/a><\/li>/g
|
||||||
|
while ((match = keywordPattern.exec(html)) !== null) {
|
||||||
|
hotComics.push(new Comic({
|
||||||
|
id: match[2],
|
||||||
|
title: match[3],
|
||||||
|
cover: "https://www.ykmh.net/images/default/cover.png",
|
||||||
|
tags: [`热门关键词`],
|
||||||
|
description: ""
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hotComics.slice(0, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseLatestComics(html) {
|
||||||
|
let latestComics = []
|
||||||
|
let comicPattern = /<li data-key="(\d+)"><a class="image-link" href="([^"]+)" title="([^"]+)"><img src="([^"]+)"[^>]*><span class="tip"><p>([^<]*)<\/p><\/span><\/a><p><a href="[^"]*" title="[^"]*">([^<]+)<\/a><\/p>/g
|
||||||
|
let match
|
||||||
|
while ((match = comicPattern.exec(html)) !== null) {
|
||||||
|
let cover = match[4]
|
||||||
|
if (!cover.startsWith('http')) {
|
||||||
|
cover = 'https://www.ykmh.net' + (cover.startsWith('/') ? cover : '/' + cover)
|
||||||
|
}
|
||||||
|
|
||||||
|
latestComics.push(new Comic({
|
||||||
|
id: match[2],
|
||||||
|
title: match[3],
|
||||||
|
cover: cover,
|
||||||
|
tags: [match[5]],
|
||||||
|
description: `更新至:${match[5]}`
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
return latestComics.slice(0, 15)
|
||||||
|
}
|
||||||
|
|
||||||
|
let hotComics = parseHotCarousel(res.body)
|
||||||
|
let latestComics = parseLatestComics(res.body)
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: "热门推荐",
|
||||||
|
comics: hotComics
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "最新更新",
|
||||||
|
comics: latestComics
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
static category_param_dict = {
|
||||||
|
"全部": "",
|
||||||
|
"爱情": "aiqing",
|
||||||
|
"剧情": "juqing",
|
||||||
|
"欢乐向": "huanlexiang",
|
||||||
|
"格斗": "gedou",
|
||||||
|
"科幻": "kehuan",
|
||||||
|
"伪娘": "weiniang",
|
||||||
|
"节操": "jiecao",
|
||||||
|
"恐怖": "kongbu",
|
||||||
|
"悬疑": "xuanyi",
|
||||||
|
"冒险": "maoxian",
|
||||||
|
"校园": "xiaoyuan",
|
||||||
|
"治愈": "zhiyu",
|
||||||
|
"恋爱": "lianai",
|
||||||
|
"奇幻": "qihuan",
|
||||||
|
"热血": "rexue",
|
||||||
|
"限制级": "xianzhiji",
|
||||||
|
"魔法": "mofa",
|
||||||
|
"后宫": "hougong",
|
||||||
|
"魔幻": "mohuan",
|
||||||
|
"轻小说": "qingxiaoshuo",
|
||||||
|
"震撼": "zhenhan",
|
||||||
|
"纯爱": "chunai",
|
||||||
|
"少女": "shaonv",
|
||||||
|
"战争": "zhanzheng",
|
||||||
|
"武侠": "wuxia",
|
||||||
|
"搞笑": "gaoxiao",
|
||||||
|
"神鬼": "shengui",
|
||||||
|
"竞技": "jingji",
|
||||||
|
"幻想": "huanxiang",
|
||||||
|
"神魔": "shenmo",
|
||||||
|
"灵异": "lingyi",
|
||||||
|
"百合": "baihe",
|
||||||
|
"运动": "yundong",
|
||||||
|
"体育": "tiyu",
|
||||||
|
"惊悚": "jingsong",
|
||||||
|
"日常": "richang",
|
||||||
|
"绅士": "shenshi",
|
||||||
|
"颜艺": "yanyi",
|
||||||
|
"生活": "shenghuo",
|
||||||
|
"四格": "sige",
|
||||||
|
"萌系": "mengxi",
|
||||||
|
"都市": "dushi",
|
||||||
|
"同人": "tongren",
|
||||||
|
"推理": "tuili",
|
||||||
|
"耽美": "danmei",
|
||||||
|
"卖肉": "mairou",
|
||||||
|
"职场": "zhichang",
|
||||||
|
"侦探": "zhentan",
|
||||||
|
"战斗": "zhandou",
|
||||||
|
"爆笑": "baoxiao",
|
||||||
|
"总裁": "zongcai",
|
||||||
|
"美食": "meishi",
|
||||||
|
"性转换": "xingzhuanhuan",
|
||||||
|
"励志": "lizhi",
|
||||||
|
"西方魔幻": "xifangmohuan",
|
||||||
|
"改编": "gaibian",
|
||||||
|
"其他": "qita",
|
||||||
|
"宅系": "zhaixi",
|
||||||
|
"机战": "jizhan",
|
||||||
|
"乙女": "yinv",
|
||||||
|
"秀吉": "xiuji",
|
||||||
|
"舰娘": "jianniang",
|
||||||
|
"历史": "lishi",
|
||||||
|
"猎奇": "lieqi",
|
||||||
|
"社会": "shehui",
|
||||||
|
"青春": "qingchun",
|
||||||
|
"高清单行": "gaoqingdanxing",
|
||||||
|
"东方": "dongfang",
|
||||||
|
"橘味": "juwei",
|
||||||
|
"音乐舞蹈": "yinyuewudao",
|
||||||
|
"家庭": "jiating",
|
||||||
|
"ゆり": "unknown",
|
||||||
|
"彩虹": "caihong",
|
||||||
|
"少年": "shaonian",
|
||||||
|
"泡泡": "paopao",
|
||||||
|
"宫斗": "gongdou",
|
||||||
|
"动作": "dongzuo",
|
||||||
|
"青年": "qingnian",
|
||||||
|
"虐心": "nuexin",
|
||||||
|
"泛爱": "fanai",
|
||||||
|
"机甲": "jijia",
|
||||||
|
"装逼": "zhuangbi",
|
||||||
|
"#愛情": "aiqing2",
|
||||||
|
"#長條": "zhangtiao",
|
||||||
|
"#穿越": "chuanyue",
|
||||||
|
"#生活": "shenghuo2",
|
||||||
|
"TS": "TS",
|
||||||
|
"#耽美": "danmei2",
|
||||||
|
"#后宫": "hougong2",
|
||||||
|
"#节操": "jiecao2",
|
||||||
|
"#轻小说": "qingxiaoshuo2",
|
||||||
|
"#奇幻": "qihuan2",
|
||||||
|
"#悬疑": "xuanyi2",
|
||||||
|
"#校园": "xiaoyuan2",
|
||||||
|
"#爱情": "aiqing3",
|
||||||
|
"#百合": "baihe2",
|
||||||
|
"#长条": "changtiao",
|
||||||
|
"#冒险": "maoxian2",
|
||||||
|
"#搞笑": "gaoxiao2",
|
||||||
|
"#欢乐向": "huanlexiang2",
|
||||||
|
"#职场": "zhichang2",
|
||||||
|
"#神鬼": "shengui2",
|
||||||
|
"#生存": "shengcun",
|
||||||
|
"#治愈": "zhiyu2",
|
||||||
|
"#竞技": "jingji2",
|
||||||
|
"#美食": "meishi2",
|
||||||
|
"#其他": "qita2",
|
||||||
|
"#机战": "jizhan2",
|
||||||
|
"#战争": "zhanzheng2",
|
||||||
|
"#科幻": "kehuan2",
|
||||||
|
"#四格": "sige2",
|
||||||
|
"#武侠": "wuxia2",
|
||||||
|
"#重生": "zhongsheng",
|
||||||
|
"#性转换": "xingzhuanhuan2",
|
||||||
|
"#热血": "rexue2",
|
||||||
|
"#伪娘": "weiniang2",
|
||||||
|
"#异世界": "yishijie",
|
||||||
|
"#萌系": "mengxi2",
|
||||||
|
"#格斗": "gedou2",
|
||||||
|
"#励志": "lizhi2",
|
||||||
|
"#都市": "dushi2",
|
||||||
|
"#惊悚": "jingsong2",
|
||||||
|
"#侦探": "zhentan2",
|
||||||
|
"#舰娘": "jianniang2",
|
||||||
|
"#音乐舞蹈": "yinyuewudao2",
|
||||||
|
"#TL": "TL",
|
||||||
|
"#AA": "AA",
|
||||||
|
"#转生": "zhuansheng",
|
||||||
|
"#魔幻": "mohuan2",
|
||||||
|
"---": "unknown2",
|
||||||
|
"#彩色": "caise",
|
||||||
|
"福瑞": "furui",
|
||||||
|
"#FATE": "FATE",
|
||||||
|
"西幻": "xihuan",
|
||||||
|
"#C99": "C99",
|
||||||
|
"#C101": "C101",
|
||||||
|
"#历史": "lishi2",
|
||||||
|
"#C102": "C102",
|
||||||
|
"#无修正": "wuxiuzheng",
|
||||||
|
"#C103": "C103",
|
||||||
|
"#东方": "dongfang2",
|
||||||
|
"栏目": "lanmu",
|
||||||
|
"异世界": "yishijie2",
|
||||||
|
"恶搞": "egao",
|
||||||
|
"霸总": "bazong",
|
||||||
|
"古风": "gufeng",
|
||||||
|
"穿越": "chuanyue2",
|
||||||
|
"玄幻": "xuanhuan",
|
||||||
|
"日更": "rigeng",
|
||||||
|
"吸血": "xixie",
|
||||||
|
"萝莉": "luoli",
|
||||||
|
"漫改": "mangai",
|
||||||
|
"唯美": "weimei",
|
||||||
|
"宅男腐女": "zhainanfunv",
|
||||||
|
"老师": "laoshi",
|
||||||
|
"诱惑": "youhuo",
|
||||||
|
"杂志": "zazhi",
|
||||||
|
"脑洞": "naodong",
|
||||||
|
"其它": "qita3",
|
||||||
|
"#恐怖": "kongbu2",
|
||||||
|
"#C105": "C105",
|
||||||
|
"权谋": "quanmou",
|
||||||
|
"大陆": "dalu",
|
||||||
|
"日本": "riben",
|
||||||
|
"香港": "hongkong",
|
||||||
|
"台湾": "taiwan",
|
||||||
|
"欧美": "oumei",
|
||||||
|
"韩国": "hanguo",
|
||||||
|
"其它": "qita",
|
||||||
|
"儿童漫画": "ertong",
|
||||||
|
"少年漫画": "shaonian",
|
||||||
|
"少女漫画": "shaonv",
|
||||||
|
"青年漫画": "qingnian",
|
||||||
|
"已完结": "wanjie",
|
||||||
|
"连载中": "lianzai"
|
||||||
|
}
|
||||||
|
|
||||||
|
static comic_status = {
|
||||||
|
"连载中": "ongoing",
|
||||||
|
"已完结": "completed",
|
||||||
|
"暂停": "paused",
|
||||||
|
"完结": "completed",
|
||||||
|
"连载": "ongoing",
|
||||||
|
"休刊": "paused",
|
||||||
|
"未知状态": "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
category = {
|
||||||
|
title: "优酷漫画",
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
name: "主题",
|
||||||
|
type: "fixed",
|
||||||
|
categories: Object.keys(YKMHSource.category_param_dict),
|
||||||
|
itemType: "category",
|
||||||
|
categoryParams: Object.values(YKMHSource.category_param_dict),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
enableRankingPage: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
categoryComics = {
|
||||||
|
load: async (category, param, options, page) => {
|
||||||
|
let sort = "";
|
||||||
|
temp = options[1].split("-")[0]
|
||||||
|
if(temp==0){sort=""}else{sort="-"}
|
||||||
|
sort = sort + options[0].split("-")[0]
|
||||||
|
let url;
|
||||||
|
if (param === "" || param === undefined) {
|
||||||
|
url = `https://www.ykmh.net/list/${sort}/?page=${page}`;
|
||||||
|
} else {
|
||||||
|
url = `https://www.ykmh.net/list/${param}/${sort}/${page}/`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = await Network.get(url);
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw `Invalid status code: ${res.status}`;
|
||||||
|
}
|
||||||
|
function parseComicsList(html) {
|
||||||
|
let comics = []
|
||||||
|
let comicPattern = /<li class="list-comic" data-key="(\d+)">\s*<a class="comic_img"\s+href="([^"]+)"><img src="([^"]+)" alt="([^"]*)"[^>]*><\/a>\s*<span class="comic_list_det"[^>]*>\s*<h3><a href="[^"]*">([^<]+)<\/a><\/h3>/g
|
||||||
|
|
||||||
|
let match
|
||||||
|
while ((match = comicPattern.exec(html)) !== null) {
|
||||||
|
let cover = match[3]
|
||||||
|
if (!cover.startsWith('http')) {
|
||||||
|
cover = 'https://www.ykmh.net' + (cover.startsWith('/') ? cover : '/' + cover)
|
||||||
|
}
|
||||||
|
|
||||||
|
comics.push(new Comic({
|
||||||
|
id: match[2],
|
||||||
|
title: match[5] || match[4],
|
||||||
|
cover: cover,
|
||||||
|
tags: [],
|
||||||
|
description: ""
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
return comics
|
||||||
|
}
|
||||||
|
|
||||||
|
let comics = parseComicsList(res.body)
|
||||||
|
|
||||||
|
let maxPage = 1
|
||||||
|
let pagePattern = /<li class="last"><a href="[^"]*\/(\d+)\/" data-page="\d+">尾页<\/a><\/li>/
|
||||||
|
let pageMatch = res.body.match(pagePattern)
|
||||||
|
if (pageMatch) {
|
||||||
|
maxPage = parseInt(pageMatch[1])
|
||||||
|
} else {
|
||||||
|
let altPagePattern = /<li class="last"><a href="[^"]*" data-page="(\d+)">尾页<\/a><\/li>/
|
||||||
|
let altMatch = res.body.match(altPagePattern)
|
||||||
|
if (altMatch) {
|
||||||
|
maxPage = parseInt(altMatch[1]) + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
comics: comics,
|
||||||
|
maxPage: maxPage
|
||||||
|
};
|
||||||
|
},
|
||||||
|
optionList: [
|
||||||
|
{options:[
|
||||||
|
"update-更新时间",
|
||||||
|
"post-发布时间",
|
||||||
|
"click-点击量"
|
||||||
|
]},
|
||||||
|
{options:[
|
||||||
|
"0-降序",
|
||||||
|
"1-升序"
|
||||||
|
]},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// search related
|
||||||
|
search = {
|
||||||
|
load: async (keyword, options, page) => {
|
||||||
|
let encodedKeyword = encodeURIComponent(keyword);
|
||||||
|
let url;
|
||||||
|
if (page && page > 1) {
|
||||||
|
url = `https://www.ykmh.net/search/?keywords=${encodedKeyword}&page=${page}`;
|
||||||
|
} else {
|
||||||
|
url = `https://www.ykmh.net/search/?keywords=${encodedKeyword}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = await Network.get(url);
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw `Request Error: ${res.status}`;
|
||||||
|
}
|
||||||
|
function parseSearchResults(html) {
|
||||||
|
let comics = []
|
||||||
|
let comicPattern = /<li class="list-comic" data-key="(\d+)"><a class="image-link"\s+href="([^"]+)"\s+title="([^"]+)"><img src="([^"]+)"[^>]*><\/a>\s*<p><a href="[^"]*"[^>]*>([^<]+)<\/a><\/p>\s*<p class="auth"><a href="[^"]*">([^<]*)<\/a><\/p>\s*<p class="newPage">([^<]*)<\/p>/g
|
||||||
|
|
||||||
|
let match
|
||||||
|
while ((match = comicPattern.exec(html)) !== null) {
|
||||||
|
let cover = match[4]
|
||||||
|
if (!cover.startsWith('http')) {
|
||||||
|
cover = 'https://www.ykmh.net' + (cover.startsWith('/') ? cover : '/' + cover)
|
||||||
|
}
|
||||||
|
comics.push(new Comic({
|
||||||
|
id: match[2],
|
||||||
|
title: match[3],
|
||||||
|
cover: cover,
|
||||||
|
tags: [match[6] || "未知作者", match[7] || ""],
|
||||||
|
description: `作者:${match[6] || "未知作者"} | 更新至:${match[7] || "未知"}`
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
return comics
|
||||||
|
}
|
||||||
|
|
||||||
|
let comics = parseSearchResults(res.body)
|
||||||
|
let maxPage = 1
|
||||||
|
let pagePattern = /<li class="last"><a href="[^"]*page=(\d+)"[^>]*>尾页<\/a><\/li>/
|
||||||
|
let pageMatch = res.body.match(pagePattern)
|
||||||
|
if (pageMatch) {
|
||||||
|
maxPage = parseInt(pageMatch[1])
|
||||||
|
} else {
|
||||||
|
let altPagePattern = /<a href="[^"]*page=(\d+)"[^>]*>\s*(\d+)\s*<\/a>/g
|
||||||
|
let matches = [...res.body.matchAll(altPagePattern)]
|
||||||
|
if (matches.length > 0) {
|
||||||
|
maxPage = Math.max(...matches.map(m => parseInt(m[2])))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
comics: comics,
|
||||||
|
maxPage: maxPage
|
||||||
|
};
|
||||||
|
},
|
||||||
|
optionList: []
|
||||||
|
}
|
||||||
|
|
||||||
|
/// single comic related
|
||||||
|
comic = {
|
||||||
|
id: null,
|
||||||
|
buildId: null,
|
||||||
|
|
||||||
|
loadInfo: async (id) => {
|
||||||
|
if (!id || typeof id !== 'string') {
|
||||||
|
throw "ID不能为空";
|
||||||
|
}
|
||||||
|
let targetUrl = id;
|
||||||
|
if (id.startsWith('https://www.ykmh.net/')) {
|
||||||
|
targetUrl = id.replace('https://www.ykmh.net/', 'https://m.ykmh.net/');
|
||||||
|
}
|
||||||
|
else if (id.startsWith('/')) {
|
||||||
|
targetUrl = 'https://m.ykmh.net' + id;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
targetUrl = 'https://m.ykmh.net/' + id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targetUrl.endsWith('/')) {
|
||||||
|
targetUrl += '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = await Network.get(targetUrl, {
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Mobile Safari/537.36 Edg/139.0.0.0'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw `请求失败,状态码: ${res.status},URL: ${targetUrl}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseComicInfo(html) {
|
||||||
|
try {
|
||||||
|
if (!html || typeof html !== 'string') {
|
||||||
|
return {
|
||||||
|
title: "未知标题",
|
||||||
|
cover: "https://m.ykmh.net/images/default/cover.png",
|
||||||
|
author: "未知作者",
|
||||||
|
status: "未知状态",
|
||||||
|
tags: [],
|
||||||
|
description: "暂无描述"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let title = "未知标题";
|
||||||
|
try {
|
||||||
|
let titleMatch = html.match(/<div class="BarTit" id="comicName">([^<]+)<\/div>/);
|
||||||
|
if (titleMatch && titleMatch[1]) {
|
||||||
|
title = titleMatch[1].trim();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("解析标题失败:", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
let cover = "https://m.ykmh.net/images/default/cover.png";
|
||||||
|
try {
|
||||||
|
let coverMatch = html.match(/<div class="pic" id="Cover">\s*<mip-img src="([^"]+)"/);
|
||||||
|
if (coverMatch && coverMatch[1]) {
|
||||||
|
cover = coverMatch[1];
|
||||||
|
} else {
|
||||||
|
let backupMatch = html.match(/<mip-img src="([^"]+)"/);
|
||||||
|
if (backupMatch && backupMatch[1]) {
|
||||||
|
cover = backupMatch[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("解析封面失败:", e);
|
||||||
|
}
|
||||||
|
let author = "未知作者";
|
||||||
|
try {
|
||||||
|
let authorMatch = html.match(/<p class="txtItme">\s*<span class="icon icon01"><\/span>\s*<a href="[^"]*">([^<]+)<\/a>\s*<\/p>/);
|
||||||
|
if (authorMatch && authorMatch[1]) {
|
||||||
|
author = authorMatch[1].trim();
|
||||||
|
} else {
|
||||||
|
let authorMatch2 = html.match(/<span class="icon icon01"><\/span>\s*<a href="[^"]*">([^<]+)<\/a>/);
|
||||||
|
if (authorMatch2 && authorMatch2[1]) {
|
||||||
|
author = authorMatch2[1].trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("解析到作者:", author);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("解析作者失败:", e);
|
||||||
|
}
|
||||||
|
let status = "未知状态";
|
||||||
|
let tags = [];
|
||||||
|
try {
|
||||||
|
let txtItems = html.match(/<p class="txtItme">[\s\S]*?<\/p>/g);
|
||||||
|
if (txtItems) {
|
||||||
|
console.log("找到txtItme元素数量:", txtItems.length);
|
||||||
|
for (let item of txtItems) {
|
||||||
|
if (item.includes('icon icon02')) {
|
||||||
|
let tagMatches = item.matchAll(/<a href="[^"]*\/list\/[^"]*\/">([^<]+)<\/a>/g);
|
||||||
|
if (tagMatches) {
|
||||||
|
for (let tagMatch of tagMatches) {
|
||||||
|
if (tagMatch && tagMatch[1]) {
|
||||||
|
let tagText = tagMatch[1].trim();
|
||||||
|
if (tagText && !tags.includes(tagText)) {
|
||||||
|
tags.push(tagText);
|
||||||
|
if (tagText === '连载中' || tagText === '已完结' || tagText === '完结' ||
|
||||||
|
tagText === '连载' || tagText === '暂停' || tagText === '休刊') {
|
||||||
|
status = tagText;
|
||||||
|
console.log("找到状态标签:", tagText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("解析到状态:", status, "标签:", tags);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("解析标签失败:", e);
|
||||||
|
tags = [];
|
||||||
|
}
|
||||||
|
let description = "暂无描述";
|
||||||
|
try {
|
||||||
|
let descMatch = html.match(/<mip-showmore[^>]*id="showmore-des">\s*([^<]+(?:<[^>]+>[^<]*<\/[^>]+>[^<]*)*?)\s*<\/mip-showmore>/);
|
||||||
|
if (descMatch && descMatch[1]) {
|
||||||
|
description = descMatch[1].replace(/<[^>]+>/g, '').replace(/^\s*介绍:\s*/, '').trim();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("解析描述失败:", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: title || "未知标题",
|
||||||
|
cover: cover || "https://m.ykmh.net/images/default/cover.png",
|
||||||
|
author: author || "未知作者",
|
||||||
|
status: status || "未知状态",
|
||||||
|
tags: tags || [],
|
||||||
|
description: description || "暂无描述"
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("parseComicInfo 总体错误:", error);
|
||||||
|
return {
|
||||||
|
title: "未知标题",
|
||||||
|
cover: "https://m.ykmh.net/images/default/cover.png",
|
||||||
|
author: "未知作者",
|
||||||
|
status: "未知状态",
|
||||||
|
tags: [],
|
||||||
|
description: "暂无描述"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseChapters(html) {
|
||||||
|
let allChaptersMap = new Map();
|
||||||
|
let groupedChapters = new Map();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!html || typeof html !== 'string') {
|
||||||
|
return allChaptersMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
let chapterGroupsPattern = /<div class="comic-chapters">[\s\S]*?<span class="Title">([^<]+)<\/span>[\s\S]*?<ul id="chapter-list-(\d+)"[^>]*>([\s\S]*?)<\/ul>/g;
|
||||||
|
|
||||||
|
let groupMatch;
|
||||||
|
while ((groupMatch = chapterGroupsPattern.exec(html)) !== null) {
|
||||||
|
try {
|
||||||
|
if (groupMatch && groupMatch[1] && groupMatch[3]) {
|
||||||
|
let groupTitle = groupMatch[1].trim();
|
||||||
|
let groupContent = groupMatch[3];
|
||||||
|
let groupChapters = new Map();
|
||||||
|
|
||||||
|
let chapterPattern = /<li>\s*<a href="([^"]+)"[^>]*>\s*<span>([^<]+)<\/span>\s*<\/a>\s*<\/li>/g;
|
||||||
|
|
||||||
|
let chapterMatch;
|
||||||
|
while ((chapterMatch = chapterPattern.exec(groupContent)) !== null) {
|
||||||
|
try {
|
||||||
|
if (chapterMatch && chapterMatch[1] && chapterMatch[2]) {
|
||||||
|
let chapterUrl = chapterMatch[1];
|
||||||
|
let chapterTitle = chapterMatch[2].trim();
|
||||||
|
if (!chapterUrl.startsWith('http')) {
|
||||||
|
chapterUrl = 'https://m.ykmh.net' + (chapterUrl.startsWith('/') ? chapterUrl : '/' + chapterUrl);
|
||||||
|
}
|
||||||
|
groupChapters.set(chapterUrl, chapterTitle);
|
||||||
|
let finalChapterTitle = chapterTitle;
|
||||||
|
if (groupTitle !== "连载列表") {
|
||||||
|
finalChapterTitle = `[${groupTitle}] ${chapterTitle}`;
|
||||||
|
}
|
||||||
|
allChaptersMap.set(chapterUrl, finalChapterTitle);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("解析单个章节失败:", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (groupChapters.size > 0) {
|
||||||
|
groupedChapters.set(groupTitle, groupChapters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("解析章节组别失败:", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allChaptersMap.size === 0) {
|
||||||
|
console.warn("使用备用章节解析方法");
|
||||||
|
let allChapterPattern = /<li>\s*<a href="([^"]+)"[^>]*>\s*<span>([^<]+)<\/span>\s*<\/a>\s*<\/li>/g;
|
||||||
|
|
||||||
|
let match;
|
||||||
|
while ((match = allChapterPattern.exec(html)) !== null) {
|
||||||
|
try {
|
||||||
|
if (match && match[1] && match[2]) {
|
||||||
|
let chapterUrl = match[1];
|
||||||
|
let chapterTitle = match[2].trim();
|
||||||
|
if (!chapterUrl.startsWith('http')) {
|
||||||
|
chapterUrl = 'https://m.ykmh.net' + (chapterUrl.startsWith('/') ? chapterUrl : '/' + chapterUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
allChaptersMap.set(chapterUrl, chapterTitle);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("解析单个章节失败:", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (groupedChapters.size > 1) {
|
||||||
|
console.log("使用多分组模式");
|
||||||
|
return groupedChapters;
|
||||||
|
}
|
||||||
|
console.log("使用合并模式");
|
||||||
|
return allChaptersMap;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("parseChapters 总体错误:", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return allChaptersMap;
|
||||||
|
}
|
||||||
|
function parseRecommends(html) {
|
||||||
|
let recommends = [];
|
||||||
|
try {
|
||||||
|
if (!html || typeof html !== 'string') {
|
||||||
|
return recommends;
|
||||||
|
}
|
||||||
|
|
||||||
|
let recommendPattern = /<li class="list-comic" data-key="[^"]*">\s*<a class="ImgA" href="([^"]+)"><mip-img src="([^"]+)"[^>]*alt="([^"]*)"[^>]*><\/mip-img><\/a>\s*<a class="txtA" href="[^"]+">([^<]+)<\/a>/g;
|
||||||
|
|
||||||
|
let match;
|
||||||
|
let count = 0;
|
||||||
|
while ((match = recommendPattern.exec(html)) !== null && count < 10) {
|
||||||
|
try {
|
||||||
|
if (match && match[1] && match[2]) {
|
||||||
|
let recUrl = match[1];
|
||||||
|
let recCover = match[2];
|
||||||
|
let recTitle = (match[4] && match[4].trim()) || (match[3] && match[3].trim()) || "未知标题";
|
||||||
|
recommends.push(new Comic({
|
||||||
|
id: recUrl,
|
||||||
|
title: recTitle,
|
||||||
|
cover: recCover
|
||||||
|
}));
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("解析单个推荐漫画失败:", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("parseRecommends 总体错误:", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return recommends;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.comic.id = id;
|
||||||
|
if (!res.body || typeof res.body !== 'string') {
|
||||||
|
throw "响应内容为空或格式错误";
|
||||||
|
}
|
||||||
|
|
||||||
|
let comicInfo = parseComicInfo(res.body);
|
||||||
|
let chapters = parseChapters(res.body);
|
||||||
|
let recommends = parseRecommends(res.body);
|
||||||
|
comicInfo = comicInfo || {};
|
||||||
|
if (!comicInfo.title) comicInfo.title = "未知标题";
|
||||||
|
if (!comicInfo.author) comicInfo.author = "未知作者";
|
||||||
|
if (!comicInfo.status) comicInfo.status = "未知状态";
|
||||||
|
if (!comicInfo.description) comicInfo.description = "暂无描述";
|
||||||
|
if (!comicInfo.cover) comicInfo.cover = "https://m.ykmh.net/images/default/cover.png";
|
||||||
|
if (!comicInfo.tags || !Array.isArray(comicInfo.tags)) comicInfo.tags = [];
|
||||||
|
let updateInfo = "暂无更新";
|
||||||
|
try {
|
||||||
|
if (chapters && chapters.size > 0) {
|
||||||
|
let latestChapter = null;
|
||||||
|
let firstValue = Array.from(chapters.values())[0];
|
||||||
|
if (firstValue instanceof Map) {
|
||||||
|
for (let groupChapters of chapters.values()) {
|
||||||
|
if (groupChapters.size > 0) {
|
||||||
|
latestChapter = Array.from(groupChapters.values())[0];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
latestChapter = firstValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (latestChapter) {
|
||||||
|
updateInfo = `更新至:${latestChapter}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("获取更新信息失败:", e);
|
||||||
|
}
|
||||||
|
let mappedStatus = comicInfo.status;
|
||||||
|
try {
|
||||||
|
if (YKMHSource.comic_status && comicInfo.status) {
|
||||||
|
mappedStatus = YKMHSource.comic_status[comicInfo.status];
|
||||||
|
if (!mappedStatus) {
|
||||||
|
if (comicInfo.status.includes('连载')) {
|
||||||
|
mappedStatus = "ongoing";
|
||||||
|
} else if (comicInfo.status.includes('完结')) {
|
||||||
|
mappedStatus = "completed";
|
||||||
|
} else if (comicInfo.status.includes('暂停') || comicInfo.status.includes('休刊')) {
|
||||||
|
mappedStatus = "paused";
|
||||||
|
} else {
|
||||||
|
mappedStatus = comicInfo.status; // 保持原状态
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("状态映射失败:", e);
|
||||||
|
mappedStatus = comicInfo.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: comicInfo.title,
|
||||||
|
cover: comicInfo.cover,
|
||||||
|
description: comicInfo.description,
|
||||||
|
tags: {
|
||||||
|
"作者": [comicInfo.author],
|
||||||
|
"状态": [mappedStatus],
|
||||||
|
"更新": [updateInfo],
|
||||||
|
"标签": comicInfo.tags
|
||||||
|
},
|
||||||
|
chapters: chapters || new Map(),
|
||||||
|
recommend: recommends || []
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("loadInfo详细错误:", error);
|
||||||
|
throw `解析漫画信息失败: ${error.message || error}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loadEp: async (comicId, epId) => {
|
||||||
|
if (!comicId || typeof comicId !== 'string') {
|
||||||
|
throw "漫画ID不能为空";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!epId || typeof epId !== 'string') {
|
||||||
|
throw "章节ID不能为空";
|
||||||
|
}
|
||||||
|
let chapterUrl = epId;
|
||||||
|
if (!chapterUrl.startsWith('http')) {
|
||||||
|
chapterUrl = 'https://m.ykmh.net' + (chapterUrl.startsWith('/') ? chapterUrl : '/' + chapterUrl);
|
||||||
|
} else if (chapterUrl.startsWith('https://www.ykmh.net/')) {
|
||||||
|
chapterUrl = chapterUrl.replace('https://www.ykmh.net/', 'https://m.ykmh.net/');
|
||||||
|
}
|
||||||
|
let res = await Network.get(chapterUrl, {
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw `请求章节失败,状态码: ${res.status}`;
|
||||||
|
}
|
||||||
|
function parseChapterImages(html) {
|
||||||
|
let images = [];
|
||||||
|
let scriptMatch = html.match(/var\s+chapterImages\s*=\s*(\[.*?\]);/);
|
||||||
|
if (scriptMatch) {
|
||||||
|
try {
|
||||||
|
let imageList = JSON.parse(scriptMatch[1]);
|
||||||
|
images = imageList.map(img => {
|
||||||
|
if (!img.startsWith('http')) {
|
||||||
|
return 'https://m.ykmh.net' + (img.startsWith('/') ? img : '/' + img);
|
||||||
|
}
|
||||||
|
return img;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (images.length === 0) {
|
||||||
|
let imgPattern = /<img[^>]+src="([^"]+)"[^>]*>/g;
|
||||||
|
let match;
|
||||||
|
while ((match = imgPattern.exec(html)) !== null) {
|
||||||
|
let imgSrc = match[1];
|
||||||
|
if (imgSrc.includes('cover') || imgSrc.includes('avatar') ||
|
||||||
|
imgSrc.includes('logo') || imgSrc.includes('icon') ||
|
||||||
|
imgSrc.includes('banner')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!imgSrc.startsWith('http')) {
|
||||||
|
imgSrc = 'https://m.ykmh.net' + (imgSrc.startsWith('/') ? imgSrc : '/' + imgSrc);
|
||||||
|
}
|
||||||
|
|
||||||
|
images.push(imgSrc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (images.length === 0) {
|
||||||
|
let containerPattern = /<div[^>]*class="[^"]*chapter[^"]*"[^>]*>(.*?)<\/div>/gs;
|
||||||
|
let containerMatch = html.match(containerPattern);
|
||||||
|
|
||||||
|
if (containerMatch) {
|
||||||
|
let containerHtml = containerMatch[1];
|
||||||
|
let imgPattern = /<img[^>]+src="([^"]+)"/g;
|
||||||
|
let match;
|
||||||
|
while ((match = imgPattern.exec(containerHtml)) !== null) {
|
||||||
|
let imgSrc = match[1];
|
||||||
|
if (!imgSrc.startsWith('http')) {
|
||||||
|
imgSrc = 'https://m.ykmh.net' + (imgSrc.startsWith('/') ? imgSrc : '/' + imgSrc);
|
||||||
|
}
|
||||||
|
images.push(imgSrc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return images;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
let images = parseChapterImages(res.body);
|
||||||
|
if (images.length === 0) {
|
||||||
|
console.warn("未找到章节图片,可能需要进一步的页面解析");
|
||||||
|
}
|
||||||
|
|
||||||
|
return { images: images || [] };
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
throw `解析章节图片失败: ${error.message || error}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClickTag: (namespace, tag) => {
|
||||||
|
if (namespace === "标签") {
|
||||||
|
let r_tag = YKMHSource.category_param_dict[tag];
|
||||||
|
return {
|
||||||
|
action: 'category',
|
||||||
|
keyword: `${tag}`,
|
||||||
|
param: `${r_tag}`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw "未支持此类Tag检索"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
490
zaimanhua.js
Normal file
490
zaimanhua.js
Normal file
@@ -0,0 +1,490 @@
|
|||||||
|
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";
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
Reference in New Issue
Block a user