diff --git a/goda.js b/goda.js new file mode 100644 index 0000000..ae17e9e --- /dev/null +++ b/goda.js @@ -0,0 +1,332 @@ +/** @type {import('./_venera_.js')} */ +class Goda extends ComicSource { + // Note: The fields which are marked as [Optional] should be removed if not used + + // name of the source + name = "GoDa漫画" + + // unique id of the source + key = "goda" + + version = "1.0.0" + + minAppVersion = "1.4.0" + + // update url + url = "https://git.nyne.dev/nyne/venera-configs/raw/branch/main/goda.js" + + settings = { + domains: { + title: "域名", + type: "input", + default: "godamh.com" + }, + api: { + title: "API域名", + type: "input", + default: "api-get-v3.mgsearcher.com" + }, + image: { + title: "图片域名", + type: "input", + default: "t40-1-4.g-mh.online" + } + } + + get baseUrl() { + return `https://${this.loadSetting("domains")}`; + } + + get apiUrl() { + return `https://${this.loadSetting("api")}/api`; + } + + get imageUrl() { + return `https://${this.loadSetting("image")}`; + } + + get headers() { + return { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:144.0) Gecko/20100101 Firefox/144.0", + "Referer": this.baseUrl + }; + } + + parseComics(doc) { + console.warn(doc) + const result = []; + for (let item of doc.querySelectorAll(".pb-2")) { + result.push(new Comic({ + id: item.querySelector("a").attributes["href"], + title: item.querySelector("h3").text, + cover: item.querySelector("img").attributes["src"] + })) + } + return result; + } + + // 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: "multiPartPage", + + load: async () => { + const res = await Network.get(this.baseUrl, this.headers); + const document = new HtmlDocument(res.body); + const result = [{ title: "近期更新", comics: [], viewMore: null }]; + for (let item of document.querySelector(".pb-unit-md").querySelectorAll(".slicarda")) { + result[0].comics.push(new Comic({ + id: item.attributes["href"], + title: item.querySelector("h3").text, + cover: item.querySelector("img").attributes["src"] + })) + } + const cardlists = document.querySelectorAll(".cardlist"); + const hometitles = document.querySelectorAll(".hometitle"); + for (let i = 0; i < hometitles.length; i++) { + result.push({ + title: hometitles[i].querySelector("h2").text, + comics: this.parseComics(cardlists[i]), + viewMore: { + page: "category", + attributes: { + category: hometitles[i].querySelector("h2").text, + param: hometitles[i].attributes["href"] + }, + } + }); + } + return result; + } + } + ] + + // categories + category = { + /// title of the category page, used to identify the page, it should be unique + title: this.name, + parts: [ + { + name: "类型", + type: "fixed", + categories: [ + "全部", + "韩漫", + "热门漫画", + "国漫", + "其他", + "日漫", + "欧美" + ], + itemType: "category", + categoryParams: [ + "/manga", + "/manga-genre/kr", + "/manga-genre/hots", + "/manga-genre/cn", + "/manga-genre/qita", + "/manga-genre/jp", + "/manga-genre/ou-mei" + ], + }, + { + name: "标签", + type: "fixed", + categories: [ + "复仇", + "古风", + "奇幻", + "逆袭", + "异能", + "宅向", + "穿越", + "热血", + "纯爱", + "系统", + "重生", + "冒险", + "灵异", + "大女主", + "剧情", + "恋爱", + "玄幻", + "女神", + "科幻", + "魔幻", + "推理", + "猎奇", + "治愈", + "都市", + "异形", + "青春", + "末日", + "悬疑", + "修仙", + "战斗" + ], + itemType: "category", + categoryParams: [ + "/manga-tag/fuchou", + "/manga-tag/gufeng", + "/manga-tag/qihuan", + "/manga-tag/nixi", + "/manga-tag/yineng", + "/manga-tag/zhaixiang", + "/manga-tag/chuanyue", + "/manga-tag/rexue", + "/manga-tag/chunai", + "/manga-tag/xitong", + "/manga-tag/zhongsheng", + "/manga-tag/maoxian", + "/manga-tag/lingyi", + "/manga-tag/danvzhu", + "/manga-tag/juqing", + "/manga-tag/lianai", + "/manga-tag/xuanhuan", + "/manga-tag/nvshen", + "/manga-tag/kehuan", + "/manga-tag/mohuan", + "/manga-tag/tuili", + "/manga-tag/lieqi", + "/manga-tag/zhiyu", + "/manga-tag/doushi", + "/manga-tag/yixing", + "/manga-tag/qingchun", + "/manga-tag/mori", + "/manga-tag/xuanyi", + "/manga-tag/xiuxian", + "/manga-tag/zhandou" + ], + } + ], + // enable ranking page + enableRankingPage: false, + } + + /// category comic loading related + categoryComics = { + load: async (category, params, options, page) => { + const res = await Network.get(`${this.baseUrl}${params}/page/${page}`, this.headers); + if (res.status !== 200) { + throw `Invalid status code: ${res.status}`; + } + const document = new HtmlDocument(res.body); + let maxPage = null; + try { + maxPage = parseInt(document.querySelectorAll("button.text-small").pop().text.replaceAll("\n", "").replaceAll(" ", "")); + } catch(_) { + maxPage = 1; + } + return { + comics: this.parseComics(document), + maxPage: maxPage + }; + } + } + + /// search related + search = { + load: async (keyword, options, page) => { + const res = await Network.get(`${this.baseUrl}/s/${keyword}?page=${page}`); + if (res.status !== 200) { + throw `Invalid status code: ${res.status}`; + } + const document = new HtmlDocument(res.body); + let maxPage = null; + try { + maxPage = parseInt(document.querySelectorAll("button.text-small").pop().text.replaceAll("\n", "").replaceAll(" ", "")); + } catch(_) { + maxPage = 1; + } + return { + comics: this.parseComics(document), + maxPage: maxPage + }; + }, + // enable tags suggestions + enableTagsSuggestions: false, + } + + /// single comic related + comic = { + onThumbnailLoad: (url) => { + return { + headers: this.headers + } + }, + loadInfo: async (id) => { + const res = await Network.get(this.baseUrl + id); + if (res.status !== 200) { + throw `Invalid status code: ${res.status}`; + } + const document = new HtmlDocument(res.body); + const title = document.querySelector(".text-xl").text.trim().split(" ")[0] + const cover = document.querySelector(".object-cover").attributes["src"]; + const description = document.querySelector("p.text-medium").text; + const infos = document.querySelectorAll("div.py-1"); + const tags = { "作者": [], "类型": [], "标签": [] }; + for (let author of infos[0].querySelectorAll("a > span")) { + let author_name = author.text.trim(); + if (author_name.endsWith(",")) { + author_name = author_name.slice(0, -1).trim(); + } + tags["作者"].push(author_name); + } + for (let category of infos[1].querySelectorAll("a > span")) { + let category_name = category.text.trim(); + if (category_name.endsWith(",")) { + category_name = category_name.slice(0, -1).trim(); + } + tags["类型"].push(category_name); + } + for (let tag of infos[2].querySelectorAll("a")) { + tags["标签"].push(tag.text.replace("\n", "").replaceAll(" ", "").replace("#", "")); + } + const mangaId = document.querySelector("#mangachapters").attributes["data-mid"]; + const jsonRes = await Network.get(`${this.apiUrl}/manga/get?mid=${mangaId}&mode=all&t=${Date.now()}`, this.headers); + const jsonData = JSON.parse(jsonRes.body); + const chapters = {}; + for (let ch of jsonData["data"]["chapters"]) { + chapters[`${mangaId}@${ch["id"]}`] = ch["attributes"]["title"]; + } + const recommend = []; + for (let item of document.querySelectorAll("div.cardlist > div.pb-2")) { + recommend.push(new Comic({ + id: item.querySelector("a").attributes["href"], + title: item.querySelector("h3").text, + cover: item.querySelector("img").attributes["src"] + })); + } + return new ComicDetails({ + title: title, + cover: cover, + description: description, + tags: tags, + chapters: chapters, + recommend: recommend, + }); + }, + + loadEp: async (comicId, epId) => { + const ids = epId.split("@"); + const res = await Network.get(`${this.apiUrl}/chapter/getinfo?m=${ids[0]}&c=${ids[1]}`, this.headers); + if (res.status !== 200) { + throw `Invalid status code: ${res.status}`; + } + const jsonData = JSON.parse(res.body); + const images = []; + for (let i of jsonData["data"]["info"]["images"]["images"]) { + images.push(this.imageUrl + i["url"]); + } + return { images }; + }, + + // enable tags translate + enableTagsTranslate: false, + } +} \ No newline at end of file diff --git a/index.json b/index.json index 4171086..5c0f91a 100644 --- a/index.json +++ b/index.json @@ -133,5 +133,17 @@ "fileName": "ccc.js", "key": "ccc", "version": "1.0.0" + }, + { + "name": "GoDa漫画", + "fileName": "goda.js", + "key": "goda", + "version": "1.0.0" + }, + { + "name": "18漫画", + "fileName": "mh18.js", + "key": "mh18", + "version": "1.0.0" } ] diff --git a/mh18.js b/mh18.js new file mode 100644 index 0000000..e0b62a0 --- /dev/null +++ b/mh18.js @@ -0,0 +1,316 @@ +/** @type {import('./_venera_.js')} */ +class MH18 extends ComicSource { + // Note: The fields which are marked as [Optional] should be removed if not used + + // name of the source + name = "18漫画" + + // unique id of the source + key = "mh18" + + version = "1.0.0" + + minAppVersion = "1.4.0" + + // update url + url = "https://git.nyne.dev/nyne/venera-configs/raw/branch/main/mh18.js" + + settings = { + domains: { + title: "域名", + type: "input", + default: "18mh.org" + } + } + + get baseUrl() { + return `https://${this.loadSetting("domains")}`; + } + + get headers() { + return { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:144.0) Gecko/20100101 Firefox/144.0", + "Referer": this.baseUrl + }; + } + + parseComics(doc) { + console.warn(doc) + const result = []; + for (let item of doc.querySelectorAll(".pb-2")) { + result.push(new Comic({ + id: item.querySelector("a").attributes["href"], + title: item.querySelector("h3").text, + cover: item.querySelector("img").attributes["src"] + })) + } + return result; + } + + // 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: "multiPartPage", + + load: async () => { + const res = await Network.get(this.baseUrl, this.headers); + const document = new HtmlDocument(res.body); + const result = [{ title: "近期更新", comics: [], viewMore: null }]; + for (let item of document.querySelector(".pb-unit-md").querySelectorAll(".slicarda")) { + result[0].comics.push(new Comic({ + id: item.attributes["href"], + title: item.querySelector("h3").text, + cover: item.querySelector("img").attributes["src"] + })) + } + const cardlists = document.querySelectorAll(".cardlist"); + const hometitles = document.querySelectorAll(".hometitle"); + for (let i = 0; i < hometitles.length; i++) { + result.push({ + title: hometitles[i].querySelector("h2").text, + comics: this.parseComics(cardlists[i]), + viewMore: { + page: "category", + attributes: { + category: hometitles[i].querySelector("h2").text, + param: hometitles[i].attributes["href"] + }, + } + }); + } + return result; + } + } + ] + + // categories + category = { + /// title of the category page, used to identify the page, it should be unique + title: this.name, + parts: [ + { + name: "类型", + type: "fixed", + categories: [ + "全部", + "韓漫", + "真人寫真", + "日漫", + "AI寫真", + "熱門漫畫" + ], + itemType: "category", + categoryParams: [ + "/manga", + "/manga-genre/hanman", + "/manga-genre/zhenrenxiezhen", + "/manga-genre/riman", + "/manga-genre/aixiezhen", + "/manga-genre/hots" + ], + }, + { + name: "标签", + type: "fixed", + categories: [ + "多人", + "慾望", + "正妹", + "同居", + "女學生", + "劇情", + "偷情", + "校园", + "逆襲", + "办公室", + "誘惑", + "反转", + "熟女", + "人妻", + "初戀", + "少妇", + "刺激", + "女大学生", + "治疗", + "超能力", + "浪漫校园", + "戏剧", + "学姐", + "大学生", + "泳衣", + "暧昧", + "写真", + "女神", + "大尺度", + "纯情警察" + ], + itemType: "category", + categoryParams: [ + "/manga-tag/duoren", + "/manga-tag/yuwang", + "/manga-tag/zhengmei", + "/manga-tag/tongju", + "/manga-tag/nxuesheng", + "/manga-tag/juqing", + "/manga-tag/touqing", + "/manga-tag/xiaoyuan", + "/manga-tag/nixi", + "/manga-tag/bangongshi", + "/manga-tag/youhuo", + "/manga-tag/fanzhuan", + "/manga-tag/shun", + "/manga-tag/renqi", + "/manga-tag/chulian", + "/manga-tag/shaofu", + "/manga-tag/ciji", + "/manga-tag/ndaxuesheng", + "/manga-tag/zhiliao", + "/manga-tag/chaonengli", + "/manga-tag/langmanxiaoyuan", + "/manga-tag/xiju", + "/manga-tag/xuejie", + "/manga-tag/daxuesheng", + "/manga-tag/yongyi", + "/manga-tag/aimei", + "/manga-tag/xiezhen", + "/manga-tag/nshen", + "/manga-tag/dachidu", + "/manga-tag/chunqingjingcha" + ], + } + ], + // enable ranking page + enableRankingPage: false, + } + + /// category comic loading related + categoryComics = { + load: async (category, params, options, page) => { + const res = await Network.get(`${this.baseUrl}${params}/page/${page}`, this.headers); + if (res.status !== 200) { + throw `Invalid status code: ${res.status}`; + } + const document = new HtmlDocument(res.body); + let maxPage = null; + try { + maxPage = parseInt(document.querySelectorAll("button.text-small").pop().text.replaceAll("\n", "").replaceAll(" ", "")); + } catch (_) { + maxPage = 1; + } + return { + comics: this.parseComics(document), + maxPage: maxPage + }; + } + } + + /// search related + search = { + load: async (keyword, options, page) => { + const res = await Network.get(`${this.baseUrl}/s/${keyword}?page=${page}`); + if (res.status !== 200) { + throw `Invalid status code: ${res.status}`; + } + const document = new HtmlDocument(res.body); + let maxPage = null; + try { + maxPage = parseInt(document.querySelectorAll("button.text-small").pop().text.replaceAll("\n", "").replaceAll(" ", "")); + } catch (_) { + maxPage = 1; + } + return { + comics: this.parseComics(document), + maxPage: maxPage + }; + }, + // enable tags suggestions + enableTagsSuggestions: false, + } + + /// single comic related + comic = { + onThumbnailLoad: (url) => { + return { + headers: this.headers + } + }, + loadInfo: async (id) => { + if (!id.startsWith("http")) { + id = this.baseUrl + id; + } + const res = await Network.get(id); + if (res.status !== 200) { + throw `Invalid status code: ${res.status}`; + } + const document = new HtmlDocument(res.body); + const title = document.querySelector(".text-xl").text.trim().split(" ")[0] + const cover = document.querySelector(".object-cover").attributes["src"]; + const description = document.querySelector("p.text-medium").text; + const infos = document.querySelectorAll("div.py-1"); + const tags = { "作者": [], "类型": [], "标签": [] }; + for (let author of infos[0].querySelectorAll("a > span")) { + let author_name = author.text.trim(); + if (author_name.endsWith(",")) { + author_name = author_name.slice(0, -1).trim(); + } + tags["作者"].push(author_name); + } + for (let category of infos[1].querySelectorAll("a > span")) { + let category_name = category.text.trim(); + if (category_name.endsWith(",")) { + category_name = category_name.slice(0, -1).trim(); + } + tags["类型"].push(category_name); + } + for (let tag of infos[2].querySelectorAll("a")) { + tags["标签"].push(tag.text.replace("\n", "").replaceAll(" ", "").replace("#", "")); + } + const mangaId = document.querySelector("#mangachapters").attributes["data-mid"]; + const chapterRes = await Network.get(`${this.baseUrl}/manga/get?mid=${mangaId}&mode=all&t=${Date.now()}`, this.headers); + const chapterDoc = new HtmlDocument(chapterRes.body); + const chapters = {}; + for (let ch of chapterDoc.querySelectorAll(".chapteritem")) { + const info = ch.querySelector("a"); + chapters[`${info.attributes["data-ms"]}@${info.attributes["data-cs"]}`] = ch.querySelector(".chaptertitle").text; + } + const recommend = []; + for (let item of document.querySelectorAll("div.cardlist > div.pb-2")) { + recommend.push(new Comic({ + id: item.querySelector("a").attributes["href"], + title: item.querySelector("h3").text, + cover: item.querySelector("img").attributes["src"] + })); + } + return new ComicDetails({ + title: title, + cover: cover, + description: description, + tags: tags, + chapters: chapters, + recommend: recommend, + }); + }, + + loadEp: async (comicId, epId) => { + const ids = epId.split("@"); + const res = await Network.get(`${this.baseUrl}/chapter/getcontent?m=${ids[0]}&c=${ids[1]}`, this.headers); + if (res.status !== 200) { + throw `Invalid status code: ${res.status}`; + } + const document = new HtmlDocument(res.body); + const images = []; + for (let i of document.querySelector("#chapcontent").querySelectorAll("img")) { + images.push(i.attributes["data-src"] ? i.attributes["data-src"] : i.attributes["src"]); + } + return { images }; + }, + + // enable tags translate + enableTagsTranslate: false, + } +} \ No newline at end of file