From 6e52854782aac6b8e17a238cde327a7a9cba4301 Mon Sep 17 00:00:00 2001 From: Brooklyn Bartly Date: Mon, 28 Jul 2025 17:54:39 +0800 Subject: [PATCH] Manwaba (#118) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 添加漫蛙漫画源实现基础功能 * refactor(api): 重构网络请求和数据处理逻辑 - 将 `fetchJson` 拆分为 `getJson` 和 `postJson` 方法,增强类型安全 - 更新基础 URL 移除尾部斜杠 - 实现分类、搜索和详情页的实际 API 调用 - 完善漫画状态显示和分类选项 - 移除冗余的 `initFunc` 方法 * refactor: 移除未实现的收藏相关功能代码 清理未实现的收藏功能相关代码,包括添加/删除收藏、加载收藏夹等功能,以保持代码库整洁 * refactor(manwaba): 重构API响应处理逻辑并实现loadInfo方法 - 修改getJson方法直接返回完整JSON响应,不再处理特定code和data字段 - 重构分类列表和搜索结果的data字段处理逻辑 - 实现loadInfo方法获取漫画详情信息 * refactor(api): 重构API请求方法并更新基础URL - 将多个独立的请求方法合并为统一的fetchJson方法 - 更新基础URL为新的API端点 - 简化参数处理和请求逻辑 - 移除不再使用的工具方法 * fix: 将漫画ID转换为字符串类型以避免潜在的类型错误 * refactor: 将baseUrl重命名为api以提升代码可读性 统一将baseUrl变量名改为api,使其更符合实际用途,提高代码可读性和一致性 * refactor(ManWaBa): 优化fetchJson默认参数并添加日志功能 - 为fetchJson方法的payload参数添加默认值undefined - 新增logger对象提供error/info/warn日志方法 - 在loadInfo方法中添加日志记录 - 移除未使用的可选方法以简化代码结构 * fix: 修复fetchJson调用时payload参数未定义的问题 确保在调用fetchJson时明确传递payload为undefined,避免潜在的类型错误 * refactor(ManWaBa): 优化漫画信息加载和章节图片获取逻辑 重构漫画信息加载和章节图片获取的代码,提取重复参数为变量,简化请求逻辑 移除未使用的onImageLoad和onThumbnailLoad方法,集中处理图片获取功能 * feat: 添加漫蛙吧源到index.json --- index.json | 5 + manwaba.js | 387 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 392 insertions(+) create mode 100644 manwaba.js diff --git a/index.json b/index.json index b6ae5c7..9bea12f 100644 --- a/index.json +++ b/index.json @@ -103,5 +103,10 @@ "fileName": "ykmh.js", "key": "ykmh", "version": "1.0.0" + },{ + "name": "漫蛙吧", + "fileName": "manwaba.js", + "key": "manwaba", + "version": "1.0.0" } ] diff --git a/manwaba.js b/manwaba.js new file mode 100644 index 0000000..8a88bfe --- /dev/null +++ b/manwaba.js @@ -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://cdn.jsdelivr.net/gh/venera-app/venera-configs@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} 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}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, + }; + }, + }; +}