Files
venera-configs/baihehui.js
2025-08-03 16:58:09 +08:00

673 lines
25 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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

/** @type {import('./_venera_.js')} */
class Baihehui 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 = "baihehui"
version = "1.0.0"
minAppVersion = "1.4.0"
// update url
url = "https://git.nyne.dev/nyne/venera-configs/raw/branch/main/baihehui.js"
settings = {
domains: {
title: "主页源",
type: "select",
options: [
{ value: "yamibo.com" },
],
default: "yamibo.com"
},
}
get baseUrl() {
return `https://www.${this.loadSetting('domains')}`;
}
/**
* [Optional] init function
*/
init() {
}
account = {
login: async (username, password) => {
Network.deleteCookies("https://www.yamibo.com");
// 1. GET 登录页,保存 PHPSESSID 和 _csrf-frontend
let resGet = await Network.get("https://www.yamibo.com/user/login", {
headers: { "User-Agent": "Mozilla/5.0" }
});
if (resGet.status !== 200) throw "无法打开登录页";
// 1.1 提取并保存 GET 返回的 Set-Cookie
let sc1 = resGet.headers["set-cookie"] || resGet.headers["Set-Cookie"] || [];
let initialCookies = [];
for (let line of Array.isArray(sc1) ? sc1 : [sc1]) {
let [pair] = line.split(";");
let [name, value] = pair.split("=");
name = name.trim(); value = value.trim();
if (name === "PHPSESSID" || name === "_csrf-frontend") {
initialCookies.push(new Cookie({ name, value, domain: "www.yamibo.com" }));
}
}
Network.setCookies("https://www.yamibo.com", initialCookies);
// 2. 解析 CSRF token
let doc = new HtmlDocument(resGet.body);
let csrf = doc
.querySelector('meta[name="csrf-token"]')
.attributes.content;
doc.dispose();
// 3. 构造编码后的表单
let form = [
`_csrf-frontend=${encodeURIComponent(csrf)}`,
`LoginForm%5Busername%5D=${encodeURIComponent(username)}`,
`LoginForm%5Bpassword%5D=${encodeURIComponent(password)}`,
'LoginForm%5BrememberMe%5D=0',
'LoginForm%5BrememberMe%5D=1',
`login-button=${encodeURIComponent("登录")}`
].join("&");
// 4. POST 登录(会自动带上刚才的 Cookie
let resPost = await Network.post(
"https://www.yamibo.com/user/login",
{
"Content-Type": "application/x-www-form-urlencoded",
"Referer": "https://www.yamibo.com/user/login",
"User-Agent": "Mozilla/5.0"
},
form
);
if (resPost.status === 400) throw "登录失败";
Network.deleteCookies("https://www.yamibo.com");
// …account.login 中 POST 后提取 Cookie 部分…
let raw = resPost.headers["set-cookie"] || resPost.headers["Set-Cookie"];
if (!raw) throw "未收到任何 Cookie";
// 1. 将单条字符串按“逗号+Cookie名=”拆分
let parts = Array.isArray(raw)
? raw
: raw.split(/,(?=\s*(?:PHPSESSID|_identity-frontend|_csrf-frontend)=)/);
// 2. 提取目标 Cookie
const names = ["PHPSESSID", "_identity-frontend", "_csrf-frontend"];
let cookies = parts.map(line => {
let [pair] = line.split(";");
let [k, v] = pair.split("=");
k = k.trim(); v = v.trim();
if (names.includes(k)) return new Cookie({ name: k, value: v, domain: "www.yamibo.com" });
}).filter(Boolean);
// 3. 验证并保存
if (cookies.length !== names.length) {
throw "登录未返回完整 Cookie实际" + cookies.map(c => c.name).join(",");
}
Network.setCookies("https://www.yamibo.com", cookies);
return true;
},
logout: () => {
Network.deleteCookies("https://www.yamibo.com");
},
registerWebsite: "https://www.yamibo.com/user/signup"
}
static category_types = {
"全部作品": "manga/list@a@?",
"原创": "manga/list?q=4@a@&",
"同人": "manga/list?q=6@a@&",
}
static article_types = {
"翻页漫画": "search/type?type=3&tag=@b@翻页漫画",
"条漫": "search/type?type=3&tag=@b@条漫",
"四格": "search/type?type=3&tag=@b@四格",
"绘本": "search/type?type=3&tag=@b@绘本",
"杂志": "search/type?type=3&tag=@b@杂志",
"合志": "search/type?type=3&tag=@b@合志",
}
static relate_types = {
"编辑推荐": "manga/rcmds?type=3012@c@&",
"最近更新": "manga/latest@c@?",
"原创推荐": "manga/rcmds?type=3014@c@&",
"同人推荐": "manga/rcmds?type=3015@c@&",
}
// explore page list
explore = [
{
title: "百合会",
type: "singlePageWithMultiPart",
load: async (page) => {
// 1. 拿到 HTML
let res = await Network.get("https://www.yamibo.com/site/manga");
if (res.status !== 200) {
throw `Invalid status code: ${res.status}`;
}
// 2. 解析文档
let doc = new HtmlDocument(res.body);
// 3. 通用解析单元函数
function parseItem(el) {
let a = el.querySelector(".media-img") || el.querySelector("a.media-img");
let href = a.attributes.href;
let id = href.match(/\/manga\/(\d+)/)[1];
// 从 style 中提取 url
let style = a.attributes.style || "";
let cover = `https://www.yamibo.com/coverm/000/000/${id}.jpg`;
let title = el.querySelector("h3 a").text.trim();
return new Comic({ id, title, cover });
}
// 4. 抓「编辑推荐」
let editor = [];
let editorEls = doc.querySelectorAll(".recommend-list .media-cell.horizontal");
for (let el of editorEls) {
editor.push(parseItem(el));
}
// 5. 抓「最近更新」
let latest = [];
// 找到标题元素,再拿其后面的 <ul> 下的 .media-cell.vertical
let latestTitle = doc.querySelectorAll("h2.module-title")
.find(e => e.text.includes("最近更新"));
if (latestTitle) {
let ul = latestTitle.nextElementSibling;
if (ul) {
let items = ul.querySelectorAll(".media-cell.vertical");
for (let el of items) latest.push(parseItem(el));
}
}
// 原创推荐
let original = [];
let originalTitle = doc.querySelectorAll("h2.module-title")
.find(e => e.text.includes("原创推荐"));
if (originalTitle) {
let ul = originalTitle.nextElementSibling;
if (ul) {
let items = ul.querySelectorAll(".media-cell.vertical");
for (let el of items) original.push(parseItem(el));
}
}
// 6. 抓「同人推荐」
let fan = [];
let fanTitle = doc.querySelectorAll("h2.module-title")
.find(e => e.text.includes("同人推荐"));
if (fanTitle) {
let ul = fanTitle.nextElementSibling;
if (ul) {
let items = ul.querySelectorAll(".media-cell.vertical");
for (let el of items) fan.push(parseItem(el));
}
}
// 7. 清理并返回
doc.dispose();
return {
"编辑推荐": editor,
"最近更新": latest,
"原创推荐": original,
"同人推荐": fan
};
}
}
];
// categories
category = {
/// title of the category page, used to identify the page, it should be unique
title: "百合会",
parts: [
{
name: "分类",
type: "fixed",
categories: Object.keys(Baihehui.category_types),
itemType: "category",
categoryParams: Object.values(Baihehui.category_types),
},
{
name: "作品类型(需要登陆)",
type: "fixed",
categories: Object.keys(Baihehui.article_types),
itemType: "category",
categoryParams: Object.values(Baihehui.article_types),
},
{
name: "更多推荐",
type: "fixed",
categories: Object.keys(Baihehui.relate_types),
itemType: "category",
categoryParams: Object.values(Baihehui.relate_types),
},
],
// enable ranking page
enableRankingPage: false,
}
/// category comic loading related
categoryComics = {
load: async (category, params, options, page) => {
let param = params.split('@')[0];
let type = params.split('@')[1];
let type_options = params.split('@')[2];
let url = ""
if (type == "b") {
url = `${this.baseUrl}/${param}${encodeURIComponent(type_options)}&sort=updated_at`;
url += `&page=${page}&per-page=50`;
} else {
url = `${this.baseUrl}/${param}${type_options}sort=updated_at`;
url += `&page=${page}&per-page=50`;
}
// 发起请求
let res = await Network.get(url, {
headers: { "User-Agent": "Mozilla/5.0" }
});
if (res.status !== 200) {
throw `Invalid status code: ${res.status}`;
}
// 解析 HTML
let document = new HtmlDocument(res.body);
// 获取最大页数
let lastPageElement = document.querySelector('li.last > a');
let maxPage = lastPageElement ? parseInt(lastPageElement.attributes['data-page']) + 1 : 1;
// 分类解析、
if (type == "a") {
let mangaList = [];
// 获取所有漫画行
let rows = document.querySelectorAll('tr[data-key]');
rows.forEach(row => {
// 提取信息
let href = row.querySelector('a').attributes['href'];
// 提取最后的数字作为 id
let rawId = href.match(/\/manga\/(\d+)$/)[1];
// 补零处理 - 确保id是3位数
let id = rawId.padStart(3, '0');
let title = row.querySelector('a').text;
let author = row.querySelectorAll('td')[2].text;
// 获取标签
let tags = [
row.querySelectorAll('td')[4].text, // 作品分类(原创/同人)
row.querySelectorAll('td')[5].text // 连载状态
];
// 获取更新时间作为描述
let updateTime = row.querySelectorAll('td')[8].text;
// 构建漫画对象
let manga = {
id: id,
title: title,
cover: `https://www.yamibo.com/coverm/000/000/${id}.jpg`, // 默认封面
tags: tags,
description: `更新于: ${updateTime}`
};
mangaList.push(manga);
});
return {
comics: mangaList,
maxPage: maxPage // 从分页信息可以看出总共5页
};
} else if (type == "b") {
let mangaList = [];
// 获取所有漫画行
let rows = document.querySelectorAll('tr[data-key]');
rows.forEach(row => {
// 提取信息
let href = row.querySelector('a').attributes['href'];
// 提取最后的数字作为 id
let rawId = href.match(/\/manga\/(\d+)$/)[1];
// 补零处理 - 确保id是3位数
let id = rawId.padStart(3, '0');
let title = row.querySelector('a').text;
let author = row.querySelectorAll('td')[2].text;
// 获取标签
let tags = [
row.querySelectorAll('td')[3].text.replace(/\[|\]/g, ''), // 作品分类 (去掉方括号)
row.querySelectorAll('td')[4].text // 连载状态
];
// 获取更新时间作为描述
let updateTime = row.querySelectorAll('td')[6].text;
// 构建封面 URL
let cover = `https://www.yamibo.com/coverm/000/000/${id}.jpg`;
// 构建漫画对象
let manga = {
id: id,
title: title,
cover: cover, // 使用有效封面或默认封面
tags: tags,
description: `${updateTime}`
};
mangaList.push(manga);
});
return {
comics: mangaList,
maxPage: maxPage // 从分页信息可以看出总共8页
};
} else {
let mangaList = [];
// 获取所有漫画行
let rows = document.querySelectorAll('tr[data-key]');
rows.forEach(row => {
// 提取信息
let href = row.querySelector('a').attributes['href'];
// 提取最后的数字作为 id
let rawId = href.match(/\/manga\/(\d+)$/)[1];
// 补零处理 - 确保id是3位数
let id = rawId.padStart(3, '0');
let title = row.querySelector('a').text;
// 获取更新时间作为描述
let updateTime = row.querySelector('td:last-child').text.trim();
// 构建封面 URL
let cover = `https://www.yamibo.com/coverm/000/000/${id}.jpg`;
// 构建漫画对象
let manga = {
id: id,
title: title,
cover: cover, // 使用有效封面或默认封面
tags: [],
description: `更新于: ${updateTime}`
};
mangaList.push(manga);
});
return {
comics: mangaList,
maxPage: maxPage // 从分页信息可以看出总共8页
};
}
}
}
/// 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) => {
let url = `https://www.yamibo.com/search/manga?SearchForm%5Bkeyword%5D=${encodeURIComponent(keyword)}&page=${page}`;
let res = await Network.get(url, {
headers: {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/114.0"
}
});
if (res.status !== 200) {
throw `Invalid status code: ${res.status}`;
}
let document = new HtmlDocument(res.body);
// 获取最大页数
let lastPageElement = document.querySelector('li.last > a');
let maxPage = lastPageElement ? parseInt(lastPageElement.attributes['data-page']) + 1 : 1;
// 提取漫画列表
let mangaList = [];
// 获取所有漫画行
let rows = document.querySelectorAll('tr[data-key]');
rows.forEach(row => {
// 提取信息
let href = row.querySelector('a').attributes['href'];
// 提取最后的数字作为 id
let rawId = href.match(/\/manga\/(\d+)$/)[1];
// 补零处理 - 确保id是3位数
let id = rawId.padStart(3, '0');
let title = row.querySelector('a').text;
// 获取更新时间作为描述
let updateTime = row.querySelector('td:last-child').text.trim();
// 构建封面 URL
let cover = `https://www.yamibo.com/coverm/000/000/${id}.jpg`;
// 构建漫画对象
let manga = {
id: id,
title: title,
cover: cover, // 使用有效封面或默认封面
tags: [],
description: `更新于: ${updateTime}`
};
mangaList.push(manga);
});
return {
comics: mangaList,
maxPage: maxPage // 从分页信息可以看出总共8页
};
},
/**
* load search result with next page token.
* The field will be ignored if `load` function is implemented.
* @param keyword {string}
* @param options {(string)[]} - options from optionList
* @param next {string | null}
* @returns {Promise<{comics: Comic[], maxPage: number}>}
*/
loadNext: async (keyword, options, next) => {
},
// provide options for search
optionList: [],
// enable tags suggestions
enableTagsSuggestions: false,
}
/// single comic related
comic = {
/**
* load comic info
* @param id {string}
* @returns {Promise<ComicDetails>}
*/
loadInfo: async (id) => {
let res = await Network.get(`${this.baseUrl}/manga/${id}`);
if (res.status !== 200) {
throw `Invalid status code: ${res.status}`;
}
let document = new HtmlDocument(res.body);
// 提取漫画标题
let title = document.querySelector("h3.col-md-12").text.trim();
// 提取封面图片
let cover = "https://www.yamibo.com/coverm/000/000/" + id + ".jpg";
// 提取作者信息
let author = "";
document.querySelectorAll("p").forEach(p => {
if (p.text.includes("作者:")) {
author = p.text.replace("作者:", "").trim();
}
});
// 提取标签
let tags = [];
document.querySelectorAll("a.label.label-ntype").forEach(tag => {
tags.push(tag.text.trim());
});
// 提取更新时间
let updateTime = "";
document.querySelectorAll("p").forEach(p => {
if (p.text.includes("更新时间:")) {
updateTime = p.text.replace("更新时间:", "").trim();
}
});
// 提取简介
//let description = document.querySelector("div.panel-body > div.panel-collapse > div.panel-body").text.trim();
let description = "";
// 提取章节信息
let chapters = new Map();
document.querySelectorAll("div[data-key]").forEach(chapter => {
let chapterKey = chapter.attributes['data-key']; // 获取 data-key 值
let chapterTitle = chapter.querySelector("a").text.trim(); // 获取章节标题
chapters.set(chapterKey, chapterTitle); // 将 data-key 和章节标题存入 Map
});
return {
title: title,
cover: cover,
description: description,
tags: {
"作者": [author],
"更新": [updateTime],
"标签": tags
},
chapters: chapters
};
},
loadComments: async (comicId, subId, page, replyTo) => {
let url = `${this.baseUrl}/manga/${comicId}?dp-1-page=${page}`;
let res = await Network.get(url, {
headers: {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/114.0"
}
});
if (res.status !== 200) {
throw `Invalid status code: ${res.status}`;
}
let document = new HtmlDocument(res.body);
// 提取评论总数
let totalCommentsMatch = document.querySelector("div.panel-body").text.match(/共(\d+)篇/);
let totalComments = totalCommentsMatch ? parseInt(totalCommentsMatch[1]) : 0;
// 提取评论列表
let comments = [];
document.querySelectorAll("div.post.row").forEach(post => {
let userName = post.querySelector("span.cmt-username > a").text.trim();
let avatar = "https://www.yamibo.com/" + post.querySelector("a > img.cmt-avatar").attributes['src'];
let content = post.querySelector("div.row > p").text.trim();
let time = post.querySelector("span.description").text.replace("在 ", "").trim();
let replyCountMatch = post.querySelector("a.btn.btn-sm").text.match(/(\d+) 条回复/);
let replyCount = replyCountMatch ? parseInt(replyCountMatch[1]) : 0;
let id = post.querySelector("button.btn_reply").attributes['pid'];
comments.push({
userName: userName,
avatar: avatar,
content: content,
time: time,
replyCount: replyCount,
id: id
});
});
// 计算最大页数
let maxPageElement = document.querySelector("li.last > a");
let maxPage = maxPageElement ? parseInt(maxPageElement.attributes['data-page']) + 1 : 1;
return {
comments: comments,
totalComments: totalComments,
maxPage: maxPage
};
},
loadEp: async (comicId, epId) => {
let baseUrl = `https://www.yamibo.com/manga/view-chapter?id=${epId}`;
let res = await Network.get(`${baseUrl}&page=1`, {
headers: {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/114.0"
}
});
if (res.status !== 200) {
throw `Invalid status code: ${res.status}`;
}
let document = new HtmlDocument(res.body);
// 提取最大页数
let lastPageElement = document.querySelector("li.last > a");
let maxPage = lastPageElement ? parseInt(lastPageElement.attributes['data-page']) + 1 : 1;
let images = [];
// 循环抓取所有页面的图片
for (let page = 1; page <= maxPage; page++) {
let pageUrl = `${baseUrl}&page=${page}`;
let pageRes = await Network.get(pageUrl, {
headers: {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/114.0"
}
});
if (pageRes.status !== 200) {
throw `Invalid status code on page ${page}: ${pageRes.status}`;
}
let pageDocument = new HtmlDocument(pageRes.body);
// 提取图片 URL
let imageElement = pageDocument.querySelector("img#imgPic");
if (!imageElement) {
throw `Image not found on page ${page}.`;
}
let imageUrl = imageElement.attributes['src'];
images.push(imageUrl);
}
return {
images: images, // 所有页面的图片 URL
maxPage: maxPage
};
},
// enable tags translate
enableTagsTranslate: false,
}
}