1. 添加联想乐云直链解析 2. 优化代码逻辑

This commit is contained in:
qaiu
2023-11-28 16:51:51 +08:00
parent 66e62aead0
commit c958229960
7 changed files with 252 additions and 9 deletions

View File

@@ -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("未知分享类型");

View File

@@ -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<String> 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;
/**
* 子类重写此构造方法不需要添加额外逻辑
* 如:
* <blockquote><pre>
* public XxTool(String key, String pwd) {
* super(key, pwd);
* }
* </pre></blockquote>
*
* @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<Throwable> handleFail(String errorMsg) {
return t -> fail(this.getClass().getSimpleName() + " - 请求异常 {}: -> {}", errorMsg, t.fillInStackTrace());
}

View File

@@ -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;
/**
* <a href="https://lecloud.lenovo.com/">联想乐云</a>
*/
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<String> 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));
}
}

View File

@@ -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<String, String>) scriptObjectMirror.get("data"));
getDownURL(sUrl, client, (Map<String, String>) 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<String, String>) scriptObjectMirror.get("data"));
getDownURL(sUrl, client, (Map<String, String>) 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<String> promise, String key, WebClient client, Map<String, ?> signMap) {
private void getDownURL(String key, WebClient client, Map<String, ?> signMap) {
MultiMap map = MultiMap.caseInsensitiveMultiMap();
signMap.forEach((k, v) -> {
map.set(k, v.toString());

View File

@@ -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<String> parse() {
Promise<String> promise = Promise.promise();
promise.complete("https://lz.qaiu.top");
return promise.future();
}

View File

@@ -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
# }
#}

View File

@@ -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