Compare commits

...

2 Commits

Author SHA1 Message Date
Naomi
603fefe9be [ikmmh] Add pass validator (#154) 2025-09-14 16:36:53 +08:00
Gandum2077
cd941b92ef [hitomi.la] bugfix (#152)
* [hitomi.la]Fix issue that galleries without language tag cannot be loaded

* [hitomi.la] fix title error on loading by category

* [hitomi.la] Fixed a bug where results could conflict when multiple searches occur simultaneously.

* [hitomi.la] Update to version 1.1.2
2025-09-05 17:41:42 +08:00
3 changed files with 157 additions and 105 deletions

128
hitomi.js
View File

@@ -995,7 +995,7 @@ class Hitomi extends ComicSource {
// unique id of the source // unique id of the source
key = "hitomi"; key = "hitomi";
version = "1.1.1"; version = "1.1.2";
minAppVersion = "1.4.6"; minAppVersion = "1.4.6";
@@ -1004,7 +1004,7 @@ class Hitomi extends ComicSource {
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),
}; };
} }
} }
@@ -1526,7 +1459,8 @@ class Hitomi extends ComicSource {
if ("type" in data && data.type) 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 && data.language) 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);

128
ikmmh.js
View File

@@ -1,13 +1,47 @@
/** @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.4"; 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":
@@ -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];
@@ -176,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,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) => ({
@@ -260,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) => ({
@@ -286,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"];
@@ -305,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";
@@ -322,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) => ({
@@ -347,6 +453,12 @@ class Ikm extends ComicSource {
console.error("加载收藏页失败:", 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];
// 获取章节数据 // 获取章节数据
@@ -417,6 +529,12 @@ class Ikm extends ComicSource {
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

View File

@@ -60,7 +60,7 @@
"name": "爱看漫", "name": "爱看漫",
"fileName": "ikmmh.js", "fileName": "ikmmh.js",
"key": "ikmmh", "key": "ikmmh",
"version": "1.0.4" "version": "1.0.5"
}, },
{ {
"name": "少年ジャンプ+", "name": "少年ジャンプ+",
@@ -72,7 +72,7 @@
"name": "hitomi.la", "name": "hitomi.la",
"fileName": "hitomi.js", "fileName": "hitomi.js",
"key": "hitomi", "key": "hitomi",
"version": "1.1.1" "version": "1.1.2"
}, },
{ {
"name": "comick", "name": "comick",