From 067b386b6f064fd86183445054a19bbfbb0b5297 Mon Sep 17 00:00:00 2001 From: nyne Date: Tue, 11 Feb 2025 14:00:46 +0800 Subject: [PATCH 1/2] jm: Add account, favorites, send comments. --- index.json | 2 +- jm.js | 189 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 183 insertions(+), 8 deletions(-) diff --git a/index.json b/index.json index e0b2c2c..af420eb 100644 --- a/index.json +++ b/index.json @@ -45,6 +45,6 @@ "name": "禁漫天堂", "fileName": "jm.js", "key": "jm", - "version": "1.0.3" + "version": "1.1.0" } ] diff --git a/jm.js b/jm.js index b932162..91b03a2 100644 --- a/jm.js +++ b/jm.js @@ -7,9 +7,9 @@ class JM extends ComicSource { // unique id of the source key = "jm" - version = "1.0.3" + version = "1.1.0" - minAppVersion = "1.2.1" + minAppVersion = "1.2.5" // update url url = "https://cdn.jsdelivr.net/gh/venera-app/venera-configs@latest/jm.js" @@ -38,7 +38,7 @@ class JM extends ComicSource { } overwriteApiUrls(domains) { - if (domains.length != 0) JM.apiDomains = domains + if (domains.length !== 0) JM.apiDomains = domains } isNum(str) { @@ -90,7 +90,7 @@ class JM extends ComicSource { `${url}?time=${today.getFullYear()}${today.getMonth() + 1}${today.getDate()}`, {headers: {"User-Agent": this.imgUa}} ) - if (res.status == 200) { + if (res.status === 200) { let data = this.convertData(await res.text(), domainSecret) let json = JSON.parse(data) if (json["Server"]) { @@ -99,7 +99,7 @@ class JM extends ComicSource { domains = json["Server"] } } - if (domains.length == 0) { + if (domains.length === 0) { title = "Update Failed" message = `Using built-in domains:\n\n` domains = JM.apiDomains @@ -113,7 +113,7 @@ class JM extends ComicSource { message, [ { - text: "Cancle", + text: "Cancel", callback: () => {} }, { @@ -171,7 +171,7 @@ class JM extends ComicSource { /** * * @param input {string} - * @param time {number} + * @param secret {string} * @returns {string} */ convertData(input, secret) { @@ -214,6 +214,62 @@ class JM extends ComicSource { return this.convertData(data, `${time}${kJmSecret}`) } + async post(url, body) { + let time = Math.floor(Date.now() / 1000) + let kJmSecret = "185Hcomic3PAPP7R" + let res = await Network.post(url, { + ...this.getHeaders(time), + "Content-Type": "application/x-www-form-urlencoded" + }, body) + if(res.status !== 200) { + if(res.status === 401) { + let json = JSON.parse(res.body) + let message = json.errorMsg + if(message === "請先登入會員" && this.isLogged) { + throw 'Login expired' + } + throw message ?? 'Invalid Status Code: ' + res.status + } + throw 'Invalid Status Code: ' + res.status + } + let json = JSON.parse(res.body) + let data = json.data + if(typeof data !== 'string') { + throw 'Invalid Data' + } + return this.convertData(data, `${time}${kJmSecret}`) + } + + // [Optional] account related + account = { + /** + * [Optional] login with account and password, return any value to indicate success + * @param account {string} + * @param pwd {string} + * @returns {Promise} + */ + login: async (account, pwd) => { + let time = Math.floor(Date.now() / 1000) + await this.post( + `${this.baseUrl}/login`, + `username=${encodeURIComponent(account)}&password=${encodeURIComponent(pwd)}` + ) + return "ok" + }, + + /** + * logout function, clear account related data + */ + logout: () => { + for (let url of JM.apiDomains) { + Network.deleteCookies(url) + } + }, + + // {string?} - register url + registerWebsite: null + } + // explore page list explore = [ { @@ -469,6 +525,85 @@ class JM extends ComicSource { ], } + // favorite related + favorites = { + multiFolder: true, + /** + * add or delete favorite. + * throw `Login expired` to indicate login expired, App will automatically re-login and re-add/delete favorite + * @param comicId {string} + * @param folderId {string} + * @param isAdding {boolean} - true for add, false for delete + * @param favoriteId {string?} - [Comic.favoriteId] + * @returns {Promise} - return any value to indicate success + */ + addOrDelFavorite: async (comicId, folderId, isAdding, favoriteId) => { + if (isAdding) { + await this.post(`${this.baseUrl}/favorite`, `aid=${comicId}`) + await this.post(`${this.baseUrl}/favorite_folder`, `type=move&folder_id=${folderId}&aid=${comicId}`) + } else { + await this.post(`${this.baseUrl}/favorite`, `aid=${comicId}`) + } + }, + /** + * load favorite folders. + * throw `Login expired` to indicate login expired, App will automatically re-login retry. + * if comicId is not null, return favorite folders which contains the comic. + * @param comicId {string?} + * @returns {Promise<{folders: {[p: string]: string}, favorited: string[]}>} - `folders` is a map of folder id to folder name, `favorited` is a list of folder id which contains the comic + */ + loadFolders: async (comicId) => { + let res = await this.get(`${this.baseUrl}/favorite`) + let folders = { + "0": this.translate("All") + } + let json = JSON.parse(res) + for (let e of json.folder_list) { + folders[e.FID.toString()] = e.name + } + return { + folders: folders, + favorited: [] + } + }, + /** + * add a folder + * @param name {string} + * @returns {Promise} - return any value to indicate success + */ + addFolder: async (name) => { + await this.post(`${this.baseUrl}/favorite_folder`, `type=add&folder_name=${name}`) + }, + /** + * delete a folder + * @param folderId {string} + * @returns {Promise} - return any value to indicate success + */ + deleteFolder: async (folderId) => { + await this.post(`${this.baseUrl}/favorite_folder`, `type=del&folder_id=${folderId}`) + }, + /** + * load comics in a folder + * throw `Login expired` to indicate login expired, App will automatically re-login retry. + * @param page {number} + * @param folder {string?} - folder id, null for non-multi-folder + * @returns {Promise<{comics: Comic[], maxPage: number}>} + */ + loadComics: async (page, folder) => { + let order = this.loadSetting('favoriteOrder') + let res = await this.get(`${this.baseUrl}/favorite?folder_id=${folder}&page=${page}&o=${order}`) + let json = JSON.parse(res) + let total = json.total + let maxPage = Math.ceil(total / 80) + let comics = json.list.map((e) => this.parseComic(e)) + return { + comics: comics, + maxPage: maxPage + } + }, + singleFolderForSingleComic: true, + } + /// single comic related comic = { /** @@ -517,6 +652,7 @@ class JM extends ComicSource { "標籤": tags, }, related: related, + isFavorite: data.is_favorite ?? false, }) }, /** @@ -641,6 +777,22 @@ class JM extends ComicSource { maxPage: Number(json.total.toString()) } }, + /** + * [Optional] send a comment, return any value to indicate success + * @param comicId {string} + * @param subId {string?} - ComicDetails.subId + * @param content {string} + * @param replyTo {string?} - commentId to reply, not null when reply to a comment + * @returns {Promise} + */ + sendComment: async (comicId, subId, content, replyTo) => { + let res = await this.post(`${this.baseUrl}/comment`, `aid=${comicId}&comment=${encodeURIComponent(content)}&status=undefined`) + let json = JSON.parse(res) + if (json.status === "fail") { + throw json.msg ?? 'Failed to send comment' + } + return "ok" + }, // {string?} - regex string, used to identify comic id from user input idMatch: "^(\\d+|jm\\d+)$", /** @@ -715,6 +867,21 @@ class JM extends ComicSource { }, ], default: "1", + }, + favoriteOrder: { + title: "Favorite Order", + type: "select", + options: [ + { + value: 'mr', + text: 'Add Time', + }, + { + value: 'mp', + text: 'Update Time', + } + ], + default: '0' } } @@ -726,6 +893,10 @@ class JM extends ComicSource { 'Refresh Domain List on Startup': '启动时刷新域名列表', 'Api Domain': 'Api域名', 'Image Stream': '图片分流', + 'Favorite Order': '收藏夹排序', + 'Add Time': '添加时间', + 'Update Time': '更新时间', + 'All': '全部', }, 'zh_TW': { 'Refresh Domain List': '刷新域名列表', @@ -733,6 +904,10 @@ class JM extends ComicSource { 'Refresh Domain List on Startup': '啟動時刷新域名列表', 'Api Domain': 'Api域名', 'Image Stream': '圖片分流', + 'Favorite Order': '收藏夾排序', + 'Add Time': '添加時間', + 'Update Time': '更新時間', + 'All': '全部', }, } } From 812aa0addcb4c4f451352e58ff0fc5fb7143abc7 Mon Sep 17 00:00:00 2001 From: Pacalini <141402887+Pacalini@users.noreply.github.com> Date: Wed, 12 Feb 2025 13:54:04 +0800 Subject: [PATCH 2/2] jm: fetch img url through setting api (#44) --- jm.js | 53 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/jm.js b/jm.js index 91b03a2..94b8dd1 100644 --- a/jm.js +++ b/jm.js @@ -21,12 +21,7 @@ class JM extends ComicSource { "www.cdnmhwscc.org" ]; - static imageDomains = [ - "cdn-msp.jmapiproxy3.cc", - "cdn-msp3.jmapiproxy3.cc", - "cdn-msp2.jmapiproxy1.cc", - "cdn-msp3.jmapiproxy3.cc", - ]; + static imageUrl = "https://cdn-msp.jmapinodeudzn.net" static apiUa = "Mozilla/5.0 (Linux; Android 10; K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.0.0 Mobile Safari/537.36" @@ -37,20 +32,22 @@ class JM extends ComicSource { return `https://${JM.apiDomains[index]}` } - overwriteApiUrls(domains) { - if (domains.length !== 0) JM.apiDomains = domains + get imageUrl() { + return JM.imageUrl + } + + overwriteApiDomains(domains) { + if (domains.length != 0) JM.apiDomains = domains + } + + overwriteImgUrl(url) { + if (url.length != 0) JM.imageUrl = url } isNum(str) { return /^\d+$/.test(str) } - get imageUrl() { - let stream = this.loadSetting('imageStream') - let index = parseInt(stream) - 1 - return `https://${JM.imageDomains[index]}` - } - get apiUa() { return JM.apiUa; } @@ -73,6 +70,7 @@ class JM extends ComicSource { async init() { if (this.loadSetting('refreshDomainsOnStart')) await this.refreshApiDomains(false) + this.refreshImgUrl(false) } /** @@ -117,13 +115,34 @@ class JM extends ComicSource { callback: () => {} }, { - text: "Save", - callback: () => this.overwriteApiUrls(domains) + text: "Apply", + callback: () => { + this.overwriteApiDomains(domains) + this.refreshImgUrl(true) + } } ] ) } else { - this.overwriteApiUrls(domains) + this.overwriteApiDomains(domains) + } + } + + /** + * + * @param showMessage {boolean} + */ + async refreshImgUrl(showMessage) { + let index = this.loadSetting('imageStream') + let res = await this.get( + `${this.baseUrl}/setting?app_img_shunt=${index}` + ) + let setting = JSON.parse(res) + if (setting["img_host"]) { + if (showMessage) { + UI.showMessage(`Image Stream ${index}:\n${setting["img_host"]}`) + } + this.overwriteImgUrl(setting["img_host"]) } }