From c958229960246e7f736ce5fd8b0e077142f162bd Mon Sep 17 00:00:00 2001 From: qaiu <736226400@qq.com> Date: Tue, 28 Nov 2023 16:51:51 +0800 Subject: [PATCH] =?UTF-8?q?1.=20=E6=B7=BB=E5=8A=A0=E8=81=94=E6=83=B3?= =?UTF-8?q?=E4=B9=90=E4=BA=91=E7=9B=B4=E9=93=BE=E8=A7=A3=E6=9E=90=202.=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/cn/qaiu/parser/IPanTool.java | 3 + .../src/main/java/cn/qaiu/parser/PanBase.java | 54 ++++++++++- .../main/java/cn/qaiu/parser/impl/LeTool.java | 94 +++++++++++++++++++ .../main/java/cn/qaiu/parser/impl/LzTool.java | 7 +- .../main/java/cn/qaiu/parser/impl/QkTool.java | 3 - .../src/main/resources/http-tools/pan-le.http | 79 ++++++++++++++++ .../src/main/resources/http-tools/test.http | 21 +++++ 7 files changed, 252 insertions(+), 9 deletions(-) create mode 100644 parser/src/main/java/cn/qaiu/parser/impl/LeTool.java create mode 100644 web-service/src/main/resources/http-tools/pan-le.http diff --git a/parser/src/main/java/cn/qaiu/parser/IPanTool.java b/parser/src/main/java/cn/qaiu/parser/IPanTool.java index b601c7f..f554cf5 100644 --- a/parser/src/main/java/cn/qaiu/parser/IPanTool.java +++ b/parser/src/main/java/cn/qaiu/parser/IPanTool.java @@ -16,6 +16,7 @@ public interface IPanTool { case "ye" -> new YeTool(key, pwd); case "fj" -> new FjTool(key, pwd); case "qk" -> new QkTool(key, pwd); + case "le" -> new LeTool(key, pwd); default -> { throw new UnsupportedOperationException("未知分享类型"); } @@ -38,6 +39,8 @@ public interface IPanTool { return new FjTool(url, pwd); } else if (url.contains(LzTool.LINK_KEY)) { return new LzTool(url, pwd); + } else if (url.startsWith(LeTool.SHARE_URL_PREFIX)) { + return new LeTool(url, pwd); } throw new UnsupportedOperationException("未知分享类型"); diff --git a/parser/src/main/java/cn/qaiu/parser/PanBase.java b/parser/src/main/java/cn/qaiu/parser/PanBase.java index 27287a9..5102fd5 100644 --- a/parser/src/main/java/cn/qaiu/parser/PanBase.java +++ b/parser/src/main/java/cn/qaiu/parser/PanBase.java @@ -1,6 +1,7 @@ package cn.qaiu.parser; import cn.qaiu.WebClientVertxInit; +import cn.qaiu.util.CommonUtils; import io.vertx.core.Handler; import io.vertx.core.Promise; import io.vertx.ext.web.client.WebClient; @@ -8,24 +9,61 @@ import io.vertx.ext.web.client.WebClientOptions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * 解析器抽象类包含promise, HTTP Client, 默认失败方法等; + * 新增网盘解析器需要继承该类. + */ public abstract class PanBase { protected Logger log = LoggerFactory.getLogger(this.getClass()); protected Promise promise = Promise.promise(); + /** + * Http client + */ protected WebClient client = WebClient.create(WebClientVertxInit.get()); - protected WebClient clientNoRedirects = WebClient.create(WebClientVertxInit.get(), OPTIONS); - private static final WebClientOptions OPTIONS = new WebClientOptions().setFollowRedirects(false); + /** + * Http client 不自动跳转 + */ + protected WebClient clientNoRedirects = WebClient.create(WebClientVertxInit.get(), + new WebClientOptions().setFollowRedirects(false)); + /** + * 分享key 可以是整个URL; 如果是URL实现该类时要 + * 使用{@link CommonUtils#adaptShortPaths(String urlPrefix, String key)}获取真实的分享key + */ protected String key; + + /** + * 分享密码 + */ protected String pwd; + /** + * 子类重写此构造方法不需要添加额外逻辑 + * 如: + *
+     *  public XxTool(String key, String pwd) {
+     *      super(key, pwd);
+     *  }
+     * 
+ * + * @param key 分享key/url + * @param pwd 分享密码 + */ protected PanBase(String key, String pwd) { this.key = key; this.pwd = pwd; } + /** + * 失败时生成异常消息 + * + * @param t 异常实例 + * @param errorMsg 提示消息 + * @param args log参数变量 + */ protected void fail(Throwable t, String errorMsg, Object... args) { try { String s = String.format(errorMsg.replaceAll("\\{}", "%s"), args); @@ -38,6 +76,12 @@ public abstract class PanBase { } } + /** + * 失败时生成异常消息 + * + * @param errorMsg 提示消息 + * @param args log参数变量 + */ protected void fail(String errorMsg, Object... args) { try { String s = String.format(errorMsg.replaceAll("\\{}", "%s"), args); @@ -50,6 +94,12 @@ public abstract class PanBase { } } + /** + * 生成失败Future的处理器 + * + * @param errorMsg 提示消息 + * @return Handler + */ protected Handler handleFail(String errorMsg) { return t -> fail(this.getClass().getSimpleName() + " - 请求异常 {}: -> {}", errorMsg, t.fillInStackTrace()); } diff --git a/parser/src/main/java/cn/qaiu/parser/impl/LeTool.java b/parser/src/main/java/cn/qaiu/parser/impl/LeTool.java new file mode 100644 index 0000000..ce0640b --- /dev/null +++ b/parser/src/main/java/cn/qaiu/parser/impl/LeTool.java @@ -0,0 +1,94 @@ +package cn.qaiu.parser.impl; + +import cn.qaiu.parser.IPanTool; +import cn.qaiu.parser.PanBase; +import cn.qaiu.util.CommonUtils; +import io.vertx.core.Future; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; + +import java.util.UUID; + +/** + * 联想乐云 + */ +public class LeTool extends PanBase implements IPanTool { + + public static final String SHARE_URL_PREFIX = "https://lecloud.lenovo.com/share/"; + private static final String API_URL_PREFIX = "https://lecloud.lenovo.com/share/api/clouddiskapi/share/public/v1/"; + + public LeTool(String key, String pwd) { + super(key, pwd); + } + + public Future parse() { + String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key); + // {"shareId":"xxx","password":"xxx","directoryId":"-1"} + String apiUrl1 = API_URL_PREFIX + "shareInfo"; + client.postAbs(apiUrl1) + .sendJsonObject(JsonObject.of("shareId", dataKey, "password", pwd, "directoryId", -1)) + .onSuccess(res -> { + JsonObject resJson = res.bodyAsJsonObject(); + if (resJson.containsKey("result")) { + if (resJson.getBoolean("result")) { + JsonObject dataJson = resJson.getJsonObject("data"); + // 密码验证失败 + if (!dataJson.getBoolean("passwordVerified")) { + fail("密码验证失败, 分享key: {}, 密码: {}", dataKey, pwd); + return; + } + + // 获取文件信息 + JsonArray files = dataJson.getJsonArray("files"); + if (files == null || files.size() == 0) { + fail("Result JSON数据异常: files字段不存在或jsonArray长度为空"); + return; + } + JsonObject fileInfoJson = files.getJsonObject(0); + if (fileInfoJson != null) { + // TODO 文件大小fileSize和文件名fileName + Long fileId = fileInfoJson.getLong("fileId"); + // 根据文件ID获取跳转链接 + getDownURL(dataKey, fileId); + } + } else { + fail("{}: {}", resJson.getString("errcode"), resJson.getString("errmsg")); + } + } else { + fail("Result JSON数据异常: result字段不存在"); + } + }).onFailure(handleFail(apiUrl1)); + return promise.future(); + } + + private void getDownURL(String key, Long fileId) { + String uuid = UUID.randomUUID().toString(); + JsonArray fileIds = JsonArray.of(fileId); + String apiUrl2 = API_URL_PREFIX + "packageDownloadWithFileIds"; + // {"fileIds":[123],"shareId":"xxx","browserId":"uuid"} + client.postAbs(apiUrl2) + .sendJsonObject(JsonObject.of("fileIds", fileIds, "shareId", key, "browserId", uuid)) + .onSuccess(res -> { + JsonObject resJson = res.bodyAsJsonObject(); + if (resJson.containsKey("result")) { + if (resJson.getBoolean("result")) { + JsonObject dataJson = resJson.getJsonObject("data"); + // 获取重定向链接跳转链接 + String downloadUrl = dataJson.getString("downloadUrl"); + if (downloadUrl == null) { + fail("Result JSON数据异常: downloadUrl不存在"); + return; + } + // 获取重定向链接跳转链接 + clientNoRedirects.getAbs(downloadUrl).send() + .onSuccess(res2 -> promise.complete(res2.headers().get("Location"))) + .onFailure(handleFail(downloadUrl)); + } else { + fail("{}: {}", resJson.getString("errcode"), resJson.getString("errmsg")); + } + } else { + fail("Result JSON数据异常: result字段不存在"); + } + }).onFailure(handleFail(apiUrl2)); + } +} diff --git a/parser/src/main/java/cn/qaiu/parser/impl/LzTool.java b/parser/src/main/java/cn/qaiu/parser/impl/LzTool.java index eeb6931..118c355 100644 --- a/parser/src/main/java/cn/qaiu/parser/impl/LzTool.java +++ b/parser/src/main/java/cn/qaiu/parser/impl/LzTool.java @@ -5,7 +5,6 @@ import cn.qaiu.parser.PanBase; import cn.qaiu.util.JsExecUtils; import io.vertx.core.Future; import io.vertx.core.MultiMap; -import io.vertx.core.Promise; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.client.WebClient; import org.openjdk.nashorn.api.scripting.ScriptObjectMirror; @@ -55,7 +54,7 @@ public class LzTool extends PanBase implements IPanTool { jsText = jsText.substring(0, jsText.indexOf("document.getElementById('rpt')")); try { ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText, "down_p"); - getDownURL(promise, sUrl, client, (Map) scriptObjectMirror.get("data")); + getDownURL(sUrl, client, (Map) scriptObjectMirror.get("data")); } catch (ScriptException | NoSuchMethodException e) { fail(e, "js引擎执行失败"); return; @@ -75,7 +74,7 @@ public class LzTool extends PanBase implements IPanTool { } try { ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText, null); - getDownURL(promise, sUrl, client, (Map) scriptObjectMirror.get("data")); + getDownURL(sUrl, client, (Map) scriptObjectMirror.get("data")); } catch (ScriptException | NoSuchMethodException e) { fail(e, "js引擎执行失败"); } @@ -96,7 +95,7 @@ public class LzTool extends PanBase implements IPanTool { return html.substring(startPos, endPos); } - private void getDownURL(Promise promise, String key, WebClient client, Map signMap) { + private void getDownURL(String key, WebClient client, Map signMap) { MultiMap map = MultiMap.caseInsensitiveMultiMap(); signMap.forEach((k, v) -> { map.set(k, v.toString()); diff --git a/parser/src/main/java/cn/qaiu/parser/impl/QkTool.java b/parser/src/main/java/cn/qaiu/parser/impl/QkTool.java index 1635e68..9b99680 100644 --- a/parser/src/main/java/cn/qaiu/parser/impl/QkTool.java +++ b/parser/src/main/java/cn/qaiu/parser/impl/QkTool.java @@ -3,7 +3,6 @@ package cn.qaiu.parser.impl; import cn.qaiu.parser.IPanTool; import cn.qaiu.parser.PanBase; import io.vertx.core.Future; -import io.vertx.core.Promise; public class QkTool extends PanBase implements IPanTool { @@ -12,8 +11,6 @@ public class QkTool extends PanBase implements IPanTool { } public Future parse() { - Promise promise = Promise.promise(); - promise.complete("https://lz.qaiu.top"); return promise.future(); } diff --git a/web-service/src/main/resources/http-tools/pan-le.http b/web-service/src/main/resources/http-tools/pan-le.http new file mode 100644 index 0000000..024fd96 --- /dev/null +++ b/web-service/src/main/resources/http-tools/pan-le.http @@ -0,0 +1,79 @@ +// 联想乐云(联想云服务) + +### 第一步 https://lecloud.lenovo.com/share/4DANWdRQsHHyiFB4a +POST https://lecloud.lenovo.com/share/api/clouddiskapi/share/public/v1/shareInfo +Content-Type:application/json;charset=UTF-8 + +{"shareId":"4DANWdRQsHHyiFB4a1","password":"","directoryId":"-1"} + +### res +#{ +# "result": true, +# "data": { +# "senderUserName": "7362264*****.com", +# "validSecondsLeft": -1, +# "memo": "", +# "passwordVerified": true, +# "files": [ +# { +# "fileType": 99, +# "fileId": 12298705, +# "fileName": "classes.dex", +# "fileSize": 8909208 +# } +# ] +# } +#} + +#{ +# "result": false, +# "errcode": "SHARE_ID_INVALID", +# "errmsg": "分享ID无效" +#} + +### +# @name 第二步 +POST https://lecloud.lenovo.com/share/api/clouddiskapi/share/public/v1/packageDownloadWithFileIds +Content-Type:application/json;charset=UTF-8 + +{"fileIds":[12299013],"shareId":"2RkKbLP9BrppS9b43","browserId":"asdddsad"} + +### +#{ +# "result": true, +# "data": { +# "downloadUrl": "https://pimapi.lenovomm.com/clouddiskapi/v1/shareRedirect?si=12299012&dk=b9d2870c001b56f6b23f6036fd049b237b3b3d47c0dbd30513ddaa7137cd64c9&pk=5a69c8b5f3835171b01ad866241de857d02c8b6e4985e51d652ab1bf331a3ae0&pc=true&ot=ali&ob=sync-cloud-disk&ok=649599424036290560.zip&fn=c4droid_aarch64_gcc11_new.zip&ds=140596045&dc=1&bi=asdddsad&ri=&ts=1701156293620&sn=b9dd3816eb1fd7743f5fa904986c15cb36fd9c08b4fbe0d94d198bd2bbf7c5be" +# } +#} + +#{ +# "result": false, +# "errcode": "FILE_NOT_EXIST", +# "errmsg": "文件不存在" +#} + +### +# @name 第三步 +# @no-redirect +https://pimapi.lenovomm.com/clouddiskapi/v1/shareRedirect?si=12298704&dk=ad0ef0fe93134baf6850294701e96986b1b933231c346589ccd635520908ebec&pk=ef45aa4d25c1dcecb631b3394f51539d59cb35c6a40c3911df8ba431ba2a3244&pc=true&ot=ali&ob=sync-cloud-disk&ok=649593714557087744.dex&fn=classes.dex&ds=8909208&dc=1&bi=18175394-7437-4eca-8c55-40b73fc70186&ri=&ts=1701148198092&sn=30589173efacb5b493fd47e0f134309ab598252951a5a82d8292011e626b5c26 + +### +https://lecloud4.lenovomm.cn/dlserver/fileman/ali/sync-cloud-disk/649593714557087744.dex?KEY1=7be0212f4bd3155951942d9d62b8dfd9&KEY2=65681948&order=0&uuid=96b1edd21f4e441ca3d305e86ba714c6&cMD5=false&sorder=0&group=&ts=1701148232156&cpn=-1&cid=3b9aca4b3b9aca4c&__bc=10007&__cid=3b9aca4b3b9aca4c&__ip=60.216.19.75&__ept=1&dck=1&fn=classes.dex + + +### https://lecloud.lenovo.com/share/2RkKbLP9BrppS9b43(密码:ex2b) +POST https://lecloud.lenovo.com/share/api/clouddiskapi/share/public/v1/shareInfo +Content-Type:application/json;charset=UTF-8 + +{"shareId":"2RkKbLP9BrppS9b43","password":"ex2b","directoryId":"-1"} + +### 错误Result +#{ +# "result": true, +# "data": { +# "senderUserName": "7362264*****.com", +# "validSecondsLeft": -1, +# "memo": "tttttt", +# "passwordVerified": false +# } +#} diff --git a/web-service/src/main/resources/http-tools/test.http b/web-service/src/main/resources/http-tools/test.http index 1146c9f..dfd164f 100644 --- a/web-service/src/main/resources/http-tools/test.http +++ b/web-service/src/main/resources/http-tools/test.http @@ -107,9 +107,30 @@ GET http://127.0.0.1:6400/parser?url=https://www.123pan.com/s/iaKtVv-6OECd.html& ### 123 # @no-redirect GET http://127.0.0.1:6400/parser?url=https://www.123pan.com/s/zF07Vv-WkHWd.html&pwd=bios + + +### 联想乐云 +# @no-redirect +GET http://127.0.0.1:6400/parser?url=https://lecloud.lenovo.com/share/4DANWdRQsHHyiFB4a + +### 联想乐云 +# @no-redirect +GET http://127.0.0.1:6400/le/4DANWdRQsHHyiFB4a + +### 联想乐云 +# @no-redirect +GET http://127.0.0.1:6400/le/2RkKbLP9BrppS9b43@ex2b + +### 联想乐云 +GET http://127.0.0.1:6400/json/le/2RkKbLP9BrppS9b43@ex2b + + ### GET http://127.0.0.1:6400/v2/statisticsInfo ### POST http://127.0.0.1:6400/v2/login?username=asd + + +