class Baozi extends ComicSource { // 此漫画源的名称 name = "包子漫画"; // 唯一标识符 key = "baozi"; version = "1.1.0"; minAppVersion = "1.0.0"; // 更新链接 url = "https://git.nyne.dev/nyne/venera-configs/raw/branch/main/baozi.js"; settings = { language: { title: "简繁切换", type: "select", options: [ { value: "cn", text: "简体" }, { value: "tw", text: "繁體" }, ], 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", }, '------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`; }, }; /// 解析漫画列表 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 = {}; 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, }; }, // 提供选项 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(); 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, 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--; } // 代理后图片水印更少 let mobileImages = images.map((e) => { const regex = /scomic\/.*/; const match = e.match(regex); return `https://as-rsa1-usla.baozicdn.com/w640/${match[0]}`; }); return { images: mobileImages }; }, }; }