/** @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, }; }, }; }