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, + }; + }, + }; +}