diff --git a/javascript/manga/src/zh/copymanga.js b/javascript/manga/src/zh/copymanga.js new file mode 100644 index 00000000..037d8f19 --- /dev/null +++ b/javascript/manga/src/zh/copymanga.js @@ -0,0 +1,595 @@ +const mangayomiSources = [{ + "name": "拷贝漫画", + "lang": "zh", + "baseUrl": "https://www.mangacopy.com", + "apiUrl": "https://api.mangacopy.com", + "iconUrl": "https://hi77-overseas.mangafuna.xyz/static/free.ico", + "typeSource": "single", + "isManga": true, + "isNsfw": false, + "version": "0.0.1", + "dateFormat": "", + "dateFormatLocale": "", + "pkgPath": "manga/src/zh/copymanga.js" + }]; + + class DefaultExtension extends MProvider { + stringUTF8(text) { + var bytes = []; + for (var i = 0; i < text.length; i++) { + bytes.push(text.charCodeAt(i)); + } + var charCodes = []; + var i = 0; + while (i < bytes.length) { + var byte1 = bytes[i]; + var charCode; + + if (byte1 < 0x80) { + charCode = byte1; + i += 1; + } else if (byte1 < 0xE0) { + var byte2 = bytes[i + 1]; + charCode = ((byte1 & 0x1F) << 6) | (byte2 & 0x3F); + i += 2; + } else if (byte1 < 0xF0) { + var byte2 = bytes[i + 1]; + var byte3 = bytes[i + 2]; + charCode = ((byte1 & 0x0F) << 12) | ((byte2 & 0x3F) << 6) | (byte3 & 0x3F); + i += 3; + } else { + var byte2 = bytes[i + 1]; + var byte3 = bytes[i + 2]; + var byte4 = bytes[i + 3]; + charCode = ((byte1 & 0x07) << 18) | ((byte2 & 0x3F) << 12) | ((byte3 & 0x3F) << 6) | (byte4 & 0x3F); + i += 4; + } + + charCodes.push(charCode); + } + return String.fromCharCode.apply(null, charCodes); + } + + base64encode(str) { + const base64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + var out, i, len; + var c1, c2, c3; + len = str.length; + i = 0; + out = ""; + while (i < len) { + c1 = str.charCodeAt(i++) & 0xff; + if (i == len) { + out += base64EncodeChars.charAt(c1 >> 2); + out += base64EncodeChars.charAt((c1 & 0x3) << 4); + out += "=="; + break; + } + c2 = str.charCodeAt(i++); + if (i == len) { + out += base64EncodeChars.charAt(c1 >> 2); + out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4)); + out += base64EncodeChars.charAt((c2 & 0xF) << 2); + out += "="; + break; + } + c3 = str.charCodeAt(i++); + out += base64EncodeChars.charAt(c1 >> 2); + out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4)); + out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6)); + out += base64EncodeChars.charAt(c3 & 0x3F); + } + return out; + } + + decode(result) { + var iv = result.substring(0, 16); + result = result.replace(iv, ""); + const bytes = []; + for (var i = 0; i < result.length; i = i + 2) { + bytes.push(parseInt(result.substr(i, 2), 16)); + } + var charString = String.fromCharCode.apply(null, bytes); + const data = this.base64encode(charString); + const text = cryptoHandler(data, iv, "xxxmanga.woo.key", false); + return text; + } + + getHeaders(url) { + return { + Referer: this.source.baseUrl + }; + } + + async getManga(url) { + const res = await new Client().get(this.source.apiUrl + url); + const datas = JSON.parse(res.body); + const manga = []; + for (const data of datas["results"]["list"]) { + manga.push({ + name: this.stringUTF8(data["name"]), + imageUrl: data["cover"], + link: data["path_word"] + }); + } + return { + list: manga, + hasNextPage: true + }; + } + + async getPopular(page) { + return await this.getManga(`/api/v3/comics?free_type=1&limit=16&offset=${(page-1)*16}&ordering=-popular&_update=true`); + } + + async getLatestUpdates(page) { + return await this.getManga(`/api/v3/comics?free_type=1&limit=16&offset=${(page-1)*16}&ordering=-datetime_updated&_update=true`); + } + + async search(query, page, filters) { + if (query != "") { + const res = await new Client().get(this.source.apiUrl + `/api/v3/search/comic?platform=1&q=${query}&limit=16&offset=${(page -1)*16}&q_type=&_update=true`, { + "webp": '1', + "region": '1', + "platform": '1', + "version": '2022.10.20', + "accept": 'application/json', + 'content-encoding': 'gzip, compress, br' + }); + const datas = JSON.parse(res.body)["results"]["list"]; + const manga = []; + for (const data of datas) { + manga.push({ + name: this.stringUTF8(data["name"]), + imageUrl: data["cover"], + link: data["path_word"] + }); + } + return { + list: manga, + hasNextPage: true + }; + } + var type, region, sort; + for (const filter of filters) { + if (filter["type"] == "type") { + type = filter["values"][filter["state"]]["value"]; + } + if (filter["type"] == "region") { + region = filter["values"][filter["state"]]["value"]; + } + if (filter["type"] == "sort") { + sort = filter["values"][filter["state"]]["value"]; + } + } + return await this.getManga(`/api/v3/comics?free_type=1&limit=16&offset=${(page-1)*16}&theme=${type}&top=${region}&ordering=${sort}&_update=true`); + } + + async getDetail(url) { + const res = await new Client().get(this.source.apiUrl + `/api/v3/comic2/${url}`); + const data = JSON.parse(res.body)["results"]["comic"]; + const title = this.stringUTF8(data["name"]); + const cover = data["cover"]; + const desc = this.stringUTF8(data["brief"]); + const author_ = []; + for (const a of data["author"]) { + author_.push(this.stringUTF8(a["name"])); + } + const author = author_.join(","); + const status = data["status"]["value"]; + const genres = data["theme"].map(e => this.stringUTF8(e["name"])); + const chapters = []; + const res_ = await new Client().get(this.source.baseUrl + `/comicdetail/${url}/chapters`, { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36" + }); + const ch_text = JSON.parse(res_.body)["results"]; + const chapter_datas = JSON.parse(this.decode(ch_text)); + for (const ch of chapter_datas["groups"]["default"]["chapters"]) { + chapters.push({ + name: ch["name"], + url: url + "|" + ch["id"] + }); + } + return { + name: title, + imageUrl: cover, + description: desc, + author: author, + status: status, + genre: genres, + episodes: chapters, + link: this.source.baseUrl + "/comic/" + url + } + } + + async getPageList(url) { + const urls = url.split("|"); + const res = await new Client().get(this.source.baseUrl + `/comic/${urls[0]}/chapter/${urls[1]}`, { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36" + }); + const img_text = res.body.match(/contentKey="(.*)"/)[1]; + const results = JSON.parse(this.decode(img_text)); + return results.map(e => e["url"]); + } + + getFilterList() { + return [{ + type: "type", + name: "分类", + type_name: "SelectFilter", + values: [{ + type_name: "SelectOption", + name: "全部", + value: "" + }, + { + type_name: "SelectOption", + name: "愛情", + value: "aiqing" + }, + { + type_name: "SelectOption", + name: "歡樂向", + value: "huanlexiang" + }, + { + type_name: "SelectOption", + name: "冒险", + value: "maoxian" + }, + { + type_name: "SelectOption", + name: "奇幻", + value: "qihuan" + }, + { + type_name: "SelectOption", + name: "百合", + value: "baihe" + }, + { + type_name: "SelectOption", + name: "校园", + value: "xiaoyuan" + }, + { + type_name: "SelectOption", + name: "科幻", + value: "kehuan" + }, + { + type_name: "SelectOption", + name: "東方", + value: "dongfang" + }, + { + type_name: "SelectOption", + name: "生活", + value: "shenghuo" + }, + { + type_name: "SelectOption", + name: "轻小说", + value: "qingxiaoshuo" + }, + { + type_name: "SelectOption", + name: "格鬥", + value: "gedou" + }, + { + type_name: "SelectOption", + name: "耽美", + value: "danmei" + }, + { + type_name: "SelectOption", + name: "悬疑", + value: "xuanyi" + }, + { + type_name: "SelectOption", + name: "神鬼", + value: "shengui" + }, + { + type_name: "SelectOption", + name: "其他", + value: "qita" + }, + { + type_name: "SelectOption", + name: "职场", + value: "zhichang" + }, + { + type_name: "SelectOption", + name: "萌系", + value: "mengxi" + }, + { + type_name: "SelectOption", + name: "治愈", + value: "zhiyu" + }, + { + type_name: "SelectOption", + name: "長條", + value: "changtiao" + }, + { + type_name: "SelectOption", + name: "四格", + value: "sige" + }, + { + type_name: "SelectOption", + name: "舰娘", + value: "jianniang" + }, + { + type_name: "SelectOption", + name: "节操", + value: "jiecao" + }, + { + type_name: "SelectOption", + name: "TL", + value: "teenslove" + }, + { + type_name: "SelectOption", + name: "竞技", + value: "jingji" + }, + { + type_name: "SelectOption", + name: "搞笑", + value: "gaoxiao" + }, + { + type_name: "SelectOption", + name: "伪娘", + value: "weiniang" + }, + { + type_name: "SelectOption", + name: "热血", + value: "rexue" + }, + { + type_name: "SelectOption", + name: "後宮", + value: "hougong" + }, + { + type_name: "SelectOption", + name: "美食", + value: "meishi" + }, + { + type_name: "SelectOption", + name: "性转换", + value: "xingzhuanhuan" + }, + { + type_name: "SelectOption", + name: "侦探", + value: "zhentan" + }, + { + type_name: "SelectOption", + name: "励志", + value: "lizhi" + }, + { + type_name: "SelectOption", + name: "AA", + value: "aa" + }, + { + type_name: "SelectOption", + name: "彩色", + value: "COLOR" + }, + { + type_name: "SelectOption", + name: "音乐舞蹈", + value: "yinyuewudao" + }, + { + type_name: "SelectOption", + name: "异世界", + value: "yishijie" + }, + { + type_name: "SelectOption", + name: "战争", + value: "zhanzheng" + }, + { + type_name: "SelectOption", + name: "历史", + value: "lishi" + }, + { + type_name: "SelectOption", + name: "机战", + value: "jizhan" + }, + { + type_name: "SelectOption", + name: "惊悚", + value: "jingsong" + }, + { + type_name: "SelectOption", + name: "C99", + value: "comiket99" + }, + { + type_name: "SelectOption", + name: "恐怖", + value: "恐怖" + }, + { + type_name: "SelectOption", + name: "都市", + value: "dushi" + }, + { + type_name: "SelectOption", + name: "C97", + value: "comiket97" + }, + { + type_name: "SelectOption", + name: "穿越", + value: "chuanyue" + }, + { + type_name: "SelectOption", + name: "C96", + value: "comiket96" + }, + { + type_name: "SelectOption", + name: "重生", + value: "chongsheng" + }, + { + type_name: "SelectOption", + name: "魔幻", + value: "mohuan" + }, + { + type_name: "SelectOption", + name: "宅系", + value: "zhaixi" + }, + { + type_name: "SelectOption", + name: "武侠", + value: "wuxia" + }, + { + type_name: "SelectOption", + name: "C98", + value: "C98" + }, + { + type_name: "SelectOption", + name: "生存", + value: "shengcun" + }, + { + type_name: "SelectOption", + name: "C95", + value: "comiket95" + }, + { + type_name: "SelectOption", + name: "FATE", + value: "fate" + }, + { + type_name: "SelectOption", + name: "無修正", + value: "Uncensored" + }, + { + type_name: "SelectOption", + name: "转生", + value: "zhuansheng" + }, + { + type_name: "SelectOption", + name: "LoveLive", + value: "loveLive" + }, + { + type_name: "SelectOption", + name: "男同", + value: "nantong" + }, + { + type_name: "SelectOption", + name: "仙侠", + value: "xianxia" + }, + { + type_name: "SelectOption", + name: "玄幻", + value: "xuanhuan" + }, + { + type_name: "SelectOption", + name: "真人", + value: "zhenren" + }, + ], + }, + { + type: "region", + name: "地区", + type_name: "SelectFilter", + values: [{ + type_name: "SelectOption", + name: "全部", + value: "" + }, + { + type_name: "SelectOption", + name: "日本", + value: "japan" + }, + { + type_name: "SelectOption", + name: "韩国", + value: "korea" + }, + { + type_name: "SelectOption", + name: "欧美", + value: "west" + }, + { + type_name: "SelectOption", + name: "完结", + value: "finish" + }, + ], + }, + { + type: "sort", + name: "排序", + type_name: "SelectFilter", + values: [{ + type_name: "SelectOption", + name: "更新时间⬇️", + value: "-datetime_updated" + }, + { + type_name: "SelectOption", + name: "更新时间⬆️", + value: "datetime_updated" + }, + { + type_name: "SelectOption", + name: "热度⬇️", + value: "-popular" + }, + { + type_name: "SelectOption", + name: "热度⬆️", + value: "popular" + }, + ], + }, + ]; + } + + getSourcePreferences() { + throw new Error("getSourcePreferences not implemented"); + } + } \ No newline at end of file diff --git a/javascript/manga/src/zh/dmzj.js b/javascript/manga/src/zh/dmzj.js new file mode 100644 index 00000000..06b1d2ca --- /dev/null +++ b/javascript/manga/src/zh/dmzj.js @@ -0,0 +1,360 @@ +const mangayomiSources = [{ + "name": "动漫之家", + "lang": "zh", + "baseUrl": "https://www.dmzj.com", + "apiUrl": "", + "iconUrl": "https://www.dmzj.com/_nuxt/logo_dmzj.1c94014a.png", + "typeSource": "single", + "isManga": true, + "isNsfw": false, + "version": "0.0.1", + "dateFormat": "", + "dateFormatLocale": "manga/src/zh/dmzj.js", + "pkgPath": "" + }]; + + class DefaultExtension extends MProvider { + getHeaders(url) { + throw new Error("getHeaders not implemented"); + } + async getManga(url) { + const res = await new Client().get(url); + const datas = JSON.parse(res.body); + const mangas = []; + for (const data of datas) { + mangas.push({ + name: data["name"], + imageUrl: "https://images.idmzj.com/" + data["cover"], + link: data["comic_py"] + }); + } + return { + list: mangas, + hasNextPage: true + }; + } + async getPopular(page) { + return await this.getManga(`https://m.idmzj.com/classify/0-0-0-0-0-${page-1}.json`); + } + async getLatestUpdates(page) { + return await this.getManga(`https://m.idmzj.com/classify/0-0-0-0-1-${page-1}.json`); + } + async search(query, page, filters) { + if (query == "") { + var type, region, status, sort; + for (const filter of filters) { + if (filter["type"] == "type") { + type = filter["values"][filter["state"]]["value"]; + } + if (filter["type"] == "region") { + region = filter["values"][filter["state"]]["value"]; + } + if (filter["type"] == "status") { + status = filter["values"][filter["state"]]["value"]; + } + if (filter["type"] == "sort") { + sort = filter["values"][filter["state"]]["value"]; + } + } + return await this.getManga(`https://m.idmzj.com/classify/${type}-0-${status}-${region}-${sort}-${page - 1}.json`); + } + const res = await new Client().get(`http://sacg.dmzj.com/comicsum/search.php?s=${query}`); + const datas = JSON.parse(res.body.slice(20, -1)); + const mangas = []; + for (const data of datas) { + mangas.push({ + name: data["comic_name"], + imageUrl: data["comic_cover"], + link: data["comic_url"].replace("//manhua.dmzj.com/", "") + }); + } + return { + list: mangas, + hasNextPage: true + }; + } + + async getDetail(url) { + const preference = new SharedPreferences(); + const res = await new Client().get(`https://www.dmzj.com/api/v1/comic1/comic/detail?comic_py=${url}&channel=pc&app_name=dmzj&version=1.0.0&uid=${preference.get("uid")}`); + const datas = JSON.parse(res.body); + if (datas["errno"] != 0) { + return { + name: datas["errmsg"] + }; + } + const title = datas["data"]["comicInfo"]["title"]; + const status = datas["data"]["comicInfo"]["status"] == "连载中" ? 0 : 1; + const cover = datas["data"]["comicInfo"]["cover"]; + const author = datas["data"]["comicInfo"]["authorInfo"]["authorName"]; + const genres = datas["data"]["comicInfo"]["types"].split("/"); + const desc = datas["data"]["comicInfo"]["description"] + const chapters = []; + if (datas["data"]["comicInfo"]["chapterList"] != null) { + for (const chlist of datas["data"]["comicInfo"]["chapterList"]) { + for (const ch of chlist["data"]) { + chapters.push({ + name: `[[${chlist["title"]}]]` + ch["chapter_title"], + url: datas["data"]["comicInfo"]["id"].toString() + "|" + ch["chapter_id"].toString(), + dateUpload: ch["updatetime"].toString() + }); + } + } + } + chapters.reverse(); + return { + name: title, + imageUrl: cover, + author: author, + genre: genres, + description: desc, + episodes: chapters, + status: status, + link: this.source.baseUrl + "/info/" + url + }; + } + + async getPageList(url) { + const preference = new SharedPreferences(); + const ids = url.split("|"); + const res = await new Client().get(`https://www.dmzj.com/api/v1/comic1/chapter/detail?channel=pc&app_name=dmzj&version=1.0.0&comic_id=${ids[0]}&chapter_id=${ids[1]}&uid=${preference.get("uid")}`); + const datas = JSON.parse(res.body); + if (datas["errno"] != 0) { + return []; + } + if (preference.get("hd") && "page_url_hd" in datas["data"]["chapterInfo"]) { + return datas["data"]["chapterInfo"]["page_url_hd"]; + } + return datas["data"]["chapterInfo"]["page_url"]; + } + + getFilterList() { + return [{ + type: "type", + name: "分类", + type_name: "SelectFilter", + values: [{ + type_name: "SelectOption", + name: "全部", + value: "0" + }, + { + type_name: "SelectOption", + name: "冒险", + value: "1" + }, + { + type_name: "SelectOption", + name: "欢乐向", + value: "2" + }, + { + type_name: "SelectOption", + name: "格斗", + value: "3" + }, + { + type_name: "SelectOption", + name: "科幻", + value: "4" + }, + { + type_name: "SelectOption", + name: "爱情", + value: "5" + }, + { + type_name: "SelectOption", + name: "竞技", + value: "6" + }, + { + type_name: "SelectOption", + name: "魔法", + value: "7" + }, + { + type_name: "SelectOption", + name: "校园", + value: "8" + }, + { + type_name: "SelectOption", + name: "悬疑", + value: "9" + }, + { + type_name: "SelectOption", + name: "恐怖", + value: "10" + }, + { + type_name: "SelectOption", + name: "生活亲情", + value: "11" + }, + { + type_name: "SelectOption", + name: "百合", + value: "12" + }, + { + type_name: "SelectOption", + name: "伪娘", + value: "13" + }, + { + type_name: "SelectOption", + name: "耽美", + value: "14" + }, + { + type_name: "SelectOption", + name: "后宫", + value: "15" + }, + { + type_name: "SelectOption", + name: "萌系", + value: "16" + }, + { + type_name: "SelectOption", + name: "治愈", + value: "17" + }, + { + type_name: "SelectOption", + name: "武侠", + value: "18" + }, + { + type_name: "SelectOption", + name: "职场", + value: "19" + }, + { + type_name: "SelectOption", + name: "奇幻", + value: "20" + }, + { + type_name: "SelectOption", + name: "节操", + value: "21" + }, + { + type_name: "SelectOption", + name: "轻小说", + value: "22" + }, + { + type_name: "SelectOption", + name: "搞笑", + value: "23" + }, + ] + }, + { + type: "region", + name: "地区", + type_name: "SelectFilter", + values: [{ + type_name: "SelectOption", + name: "全部", + value: "0" + }, + { + type_name: "SelectOption", + name: "日本", + value: "1" + }, + { + type_name: "SelectOption", + name: "内地", + value: "2" + }, + { + type_name: "SelectOption", + name: "欧美", + value: "3" + }, + { + type_name: "SelectOption", + name: "港台", + value: "4" + }, + { + type_name: "SelectOption", + name: "韩国", + value: "5" + }, + { + type_name: "SelectOption", + name: "其他", + value: "6" + }, + ] + }, + { + type: "status", + name: "状态", + type_name: "SelectFilter", + values: [{ + type_name: "SelectOption", + name: "全部", + value: "0" + }, + { + type_name: "SelectOption", + name: "连载中", + value: "1" + }, + { + type_name: "SelectOption", + name: "已完结", + value: "2" + }, + ] + }, + { + type: "sort", + name: "排序", + type_name: "SelectFilter", + values: [{ + type_name: "SelectOption", + name: "浏览次数", + value: "0" + }, + { + type_name: "SelectOption", + name: "更新时间", + value: "1" + }, + ] + }, + ]; + } + + getSourcePreferences() { + return [{ + "key": "uid", + "editTextPreference": { + "title": "用户uid", + "summary": "设置后可以解锁部分漫画", + "value": "2665531", + "dialogTitle": "UID", + "dialogMessage": "", + } + }, + { + "key": "hd", + "switchPreferenceCompat": { + "title": "高清画质", + "summary": "启用后使用高清画质", + "value": false + } + } + ]; + } + } \ No newline at end of file diff --git a/javascript/manga/src/zh/jmcomic.js b/javascript/manga/src/zh/jmcomic.js new file mode 100644 index 00000000..740f9615 --- /dev/null +++ b/javascript/manga/src/zh/jmcomic.js @@ -0,0 +1,255 @@ +const mangayomiSources = [{ + "name": "禁漫天堂", + "lang": "zh", + "baseUrl": "https://18comic.vip", + "apiUrl": "", + "iconUrl": "https://cdn-msp.jmcomic.me/media/logo/new_logo.png?v=2024043002", + "typeSource": "single", + "isManga": true, + "isNsfw": true, + "version": "0.0.1", + "dateFormat": "", + "dateFormatLocale": "", + "pkgPath": "manga/src/zh/jmcomic.js" + }]; + + class DefaultExtension extends MProvider { + dateStringToTimestamp(dateString) { + var parts = dateString.split('-'); + var year = parseInt(parts[0]); + var month = parseInt(parts[1]) - 1; + var day = parseInt(parts[2]); + + var date = new Date(year, month, day); + var timestamp = date.getTime(); + + return timestamp; + } + + getHeaders(url) { + throw new Error("getHeaders not implemented"); + } + + async getManga(url, p) { + const res = await new Client().get(this.source.baseUrl + url, { + Referer: this.source.baseUrl + }); + const doc = new Document(res.body); + const manga = []; + const elements = doc.select(p); + for (const element of elements) { + var text = element.innerHtml; + text = text.slice(text.search("