mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2025-12-16 04:13:03 +00:00
Add Ce4Tool for Cloudreve 4.x API support and update CeTool with version detection
Co-authored-by: qaiu <29825328+qaiu@users.noreply.github.com>
This commit is contained in:
195
parser/src/main/java/cn/qaiu/parser/impl/Ce4Tool.java
Normal file
195
parser/src/main/java/cn/qaiu/parser/impl/Ce4Tool.java
Normal file
@@ -0,0 +1,195 @@
|
||||
package cn.qaiu.parser.impl;
|
||||
|
||||
import cn.qaiu.entity.ShareLinkInfo;
|
||||
import cn.qaiu.parser.PanBase;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.buffer.Buffer;
|
||||
import io.vertx.core.json.JsonArray;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.client.HttpRequest;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* <a href="https://github.com/cloudreve/Cloudreve">Cloudreve 4.x 自建网盘解析</a> <br>
|
||||
* Cloudreve 4.x API 版本解析器
|
||||
*/
|
||||
public class Ce4Tool extends PanBase {
|
||||
|
||||
// Cloudreve 4.x uses /api/v3/ prefix for most APIs
|
||||
private static final String PING_API_V3_PATH = "/api/v3/site/ping";
|
||||
private static final String PING_API_V4_PATH = "/api/v4/site/ping";
|
||||
private static final String FILE_URL_API_PATH = "/api/v3/file/url";
|
||||
private static final String SHARE_API_PATH = "/api/v3/share/info/";
|
||||
|
||||
public Ce4Tool(ShareLinkInfo shareLinkInfo) {
|
||||
super(shareLinkInfo);
|
||||
}
|
||||
|
||||
public Future<String> parse() {
|
||||
String key = shareLinkInfo.getShareKey();
|
||||
String pwd = shareLinkInfo.getSharePassword();
|
||||
|
||||
try {
|
||||
URL url = new URL(shareLinkInfo.getShareUrl());
|
||||
String baseUrl = url.getProtocol() + "://" + url.getHost();
|
||||
|
||||
// First, detect API version by pinging
|
||||
detectVersion(baseUrl, key, pwd);
|
||||
} catch (Exception e) {
|
||||
fail(e, "URL解析错误");
|
||||
}
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect Cloudreve version by pinging /api/v3/site/ping or /api/v4/site/ping
|
||||
*/
|
||||
private void detectVersion(String baseUrl, String key, String pwd) {
|
||||
String pingUrlV3 = baseUrl + PING_API_V3_PATH;
|
||||
|
||||
// Try v3 ping first (which also works for 4.x)
|
||||
clientSession.getAbs(pingUrlV3).send().onSuccess(res -> {
|
||||
if (res.statusCode() == 200) {
|
||||
try {
|
||||
JsonObject pingResponse = asJson(res);
|
||||
// If we get a valid JSON response, this is a Cloudreve instance
|
||||
// Check if it's 4.x by trying the share API
|
||||
getShareInfo(baseUrl, key, pwd);
|
||||
} catch (Exception e) {
|
||||
// Not a valid JSON response, try v4 ping
|
||||
tryV4Ping(baseUrl, key, pwd);
|
||||
}
|
||||
} else if (res.statusCode() == 404) {
|
||||
// Try v4 ping
|
||||
tryV4Ping(baseUrl, key, pwd);
|
||||
} else {
|
||||
// Not a Cloudreve instance, try next parser
|
||||
nextParser();
|
||||
}
|
||||
}).onFailure(t -> {
|
||||
// Network error or not accessible, try next parser
|
||||
nextParser();
|
||||
});
|
||||
}
|
||||
|
||||
private void tryV4Ping(String baseUrl, String key, String pwd) {
|
||||
String pingUrlV4 = baseUrl + PING_API_V4_PATH;
|
||||
|
||||
clientSession.getAbs(pingUrlV4).send().onSuccess(res -> {
|
||||
if (res.statusCode() == 200) {
|
||||
try {
|
||||
JsonObject pingResponse = asJson(res);
|
||||
// Valid v4 response
|
||||
getShareInfo(baseUrl, key, pwd);
|
||||
} catch (Exception e) {
|
||||
// Not a Cloudreve instance
|
||||
nextParser();
|
||||
}
|
||||
} else {
|
||||
// Not a Cloudreve instance
|
||||
nextParser();
|
||||
}
|
||||
}).onFailure(t -> {
|
||||
// Not accessible, try next parser
|
||||
nextParser();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get share information from Cloudreve 4.x
|
||||
*/
|
||||
private void getShareInfo(String baseUrl, String key, String pwd) {
|
||||
String shareApiUrl = baseUrl + SHARE_API_PATH + key;
|
||||
|
||||
HttpRequest<Buffer> httpRequest = clientSession.getAbs(shareApiUrl);
|
||||
if (pwd != null && !pwd.isEmpty()) {
|
||||
httpRequest.addQueryParam("password", pwd);
|
||||
}
|
||||
|
||||
httpRequest.send().onSuccess(res -> {
|
||||
try {
|
||||
if (res.statusCode() == 200) {
|
||||
JsonObject jsonObject = asJson(res);
|
||||
if (jsonObject.containsKey("code")) {
|
||||
int code = jsonObject.getInteger("code");
|
||||
if (code == 0) {
|
||||
// Success, get file info and download URL
|
||||
JsonObject data = jsonObject.getJsonObject("data");
|
||||
if (data != null) {
|
||||
// Get file path or use default
|
||||
String filePath = "/";
|
||||
if (data.containsKey("path")) {
|
||||
filePath = data.getString("path");
|
||||
}
|
||||
// For 4.x, we need to get the download URL via POST /api/v3/file/url
|
||||
getDownloadUrl(baseUrl, key, filePath);
|
||||
} else {
|
||||
fail("分享信息获取失败: data字段为空");
|
||||
}
|
||||
} else {
|
||||
// Error code, might be wrong password or invalid share
|
||||
String msg = jsonObject.getString("msg", "未知错误");
|
||||
fail("分享验证失败: {}", msg);
|
||||
}
|
||||
} else {
|
||||
// Not a Cloudreve 4.x response, try next parser
|
||||
nextParser();
|
||||
}
|
||||
} else {
|
||||
// HTTP error, not a valid Cloudreve instance
|
||||
nextParser();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// JSON parsing error, not a Cloudreve instance
|
||||
nextParser();
|
||||
}
|
||||
}).onFailure(t -> {
|
||||
// Network error, try next parser
|
||||
nextParser();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get download URL via POST /api/v3/file/url (Cloudreve 4.x API)
|
||||
*/
|
||||
private void getDownloadUrl(String baseUrl, String key, String filePath) {
|
||||
String fileUrlApi = baseUrl + FILE_URL_API_PATH;
|
||||
|
||||
// Prepare request body for Cloudreve 4.x
|
||||
JsonObject requestBody = new JsonObject()
|
||||
.put("uris", new JsonArray().add(filePath))
|
||||
.put("download", true);
|
||||
|
||||
clientSession.postAbs(fileUrlApi)
|
||||
.putHeader("Content-Type", "application/json")
|
||||
.sendJsonObject(requestBody)
|
||||
.onSuccess(res -> {
|
||||
try {
|
||||
if (res.statusCode() == 200) {
|
||||
JsonObject jsonObject = asJson(res);
|
||||
if (jsonObject.containsKey("urls")) {
|
||||
JsonArray urls = jsonObject.getJsonArray("urls");
|
||||
if (urls != null && urls.size() > 0) {
|
||||
JsonObject urlObj = urls.getJsonObject(0);
|
||||
String downloadUrl = urlObj.getString("url");
|
||||
if (downloadUrl != null && !downloadUrl.isEmpty()) {
|
||||
promise.complete(downloadUrl);
|
||||
} else {
|
||||
fail("下载链接为空");
|
||||
}
|
||||
} else {
|
||||
fail("下载链接列表为空");
|
||||
}
|
||||
} else {
|
||||
fail("响应中不包含urls字段");
|
||||
}
|
||||
} else {
|
||||
fail("获取下载链接失败: HTTP {}", res.statusCode());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
fail(e, "解析下载链接响应失败");
|
||||
}
|
||||
}).onFailure(handleFail(fileUrlApi));
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import java.net.URL;
|
||||
* <a href="https://pan.huang1111.cn">huang1111</a> <br>
|
||||
* <a href="https://pan.seeoss.com">看见存储</a> <br>
|
||||
* <a href="https://dav.yiandrive.com">亿安云盘</a> <br>
|
||||
* Cloudreve 3.x 解析器,会自动检测版本并在4.x时转发到Ce4Tool
|
||||
*/
|
||||
public class CeTool extends PanBase {
|
||||
|
||||
@@ -22,6 +23,9 @@ public class CeTool extends PanBase {
|
||||
|
||||
// api/v3/share/info/g31PcQ?password=qaiu
|
||||
private static final String SHARE_API_PATH = "/api/v3/share/info/";
|
||||
|
||||
private static final String PING_API_V3_PATH = "/api/v3/site/ping";
|
||||
private static final String PING_API_V4_PATH = "/api/v4/site/ping";
|
||||
|
||||
public CeTool(ShareLinkInfo shareLinkInfo) {
|
||||
super(shareLinkInfo);
|
||||
@@ -39,31 +43,132 @@ public class CeTool extends PanBase {
|
||||
try {
|
||||
// // 处理URL
|
||||
URL url = new URL(shareLinkInfo.getShareUrl());
|
||||
String downloadApiUrl = url.getProtocol() + "://" + url.getHost() + DOWNLOAD_API_PATH + key + "?path" +
|
||||
"=undefined/undefined;";
|
||||
String shareApiUrl = url.getProtocol() + "://" + url.getHost() + SHARE_API_PATH + key;
|
||||
// 设置cookie
|
||||
HttpRequest<Buffer> httpRequest = clientSession.getAbs(shareApiUrl);
|
||||
if (pwd != null) {
|
||||
httpRequest.addQueryParam("password", pwd);
|
||||
}
|
||||
// 获取下载链接
|
||||
httpRequest.send().onSuccess(res -> {
|
||||
try {
|
||||
if (res.statusCode() == 200 && res.bodyAsJsonObject().containsKey("code")) {
|
||||
getDownURL(downloadApiUrl);
|
||||
} else {
|
||||
nextParser();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
nextParser();
|
||||
}
|
||||
}).onFailure(handleFail(shareApiUrl));
|
||||
String baseUrl = url.getProtocol() + "://" + url.getHost();
|
||||
|
||||
// 先检测API版本
|
||||
detectVersionAndParse(baseUrl, key, pwd);
|
||||
} catch (Exception e) {
|
||||
fail(e, "URL解析错误");
|
||||
}
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测Cloudreve版本并选择合适的解析器
|
||||
* 先调用 /api/v3/site/ping 或 /api/v4/site/ping 判断是哪个版本
|
||||
* 如果都返回404说明不是Cloudreve盘,则调用nextParser
|
||||
*/
|
||||
private void detectVersionAndParse(String baseUrl, String key, String pwd) {
|
||||
String pingUrlV3 = baseUrl + PING_API_V3_PATH;
|
||||
|
||||
// 先尝试v3 ping
|
||||
clientSession.getAbs(pingUrlV3).send().onSuccess(res -> {
|
||||
if (res.statusCode() == 200) {
|
||||
try {
|
||||
JsonObject pingResponse = asJson(res);
|
||||
// 获取到JSON响应,检查是否是4.x版本
|
||||
// 4.x的ping响应可能有不同的结构,我们通过share API来判断
|
||||
checkVersionByShareApi(baseUrl, key, pwd);
|
||||
} catch (Exception e) {
|
||||
// JSON解析失败,尝试v4 ping
|
||||
tryV4PingAndParse(baseUrl, key, pwd);
|
||||
}
|
||||
} else if (res.statusCode() == 404) {
|
||||
// v3 ping不存在,尝试v4
|
||||
tryV4PingAndParse(baseUrl, key, pwd);
|
||||
} else {
|
||||
// 其他错误,不是Cloudreve盘
|
||||
nextParser();
|
||||
}
|
||||
}).onFailure(t -> {
|
||||
// 网络错误,尝试下一个解析器
|
||||
nextParser();
|
||||
});
|
||||
}
|
||||
|
||||
private void tryV4PingAndParse(String baseUrl, String key, String pwd) {
|
||||
String pingUrlV4 = baseUrl + PING_API_V4_PATH;
|
||||
|
||||
clientSession.getAbs(pingUrlV4).send().onSuccess(res -> {
|
||||
if (res.statusCode() == 200) {
|
||||
try {
|
||||
JsonObject pingResponse = asJson(res);
|
||||
// v4 ping成功,使用Ce4Tool
|
||||
delegateToCe4Tool();
|
||||
} catch (Exception e) {
|
||||
// 不是Cloudreve盘
|
||||
nextParser();
|
||||
}
|
||||
} else {
|
||||
// 不是Cloudreve盘
|
||||
nextParser();
|
||||
}
|
||||
}).onFailure(t -> {
|
||||
// 网络错误,尝试下一个解析器
|
||||
nextParser();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过Share API的响应来判断版本
|
||||
* 3.x和4.x的share API响应格式可能不同
|
||||
*/
|
||||
private void checkVersionByShareApi(String baseUrl, String key, String pwd) {
|
||||
String shareApiUrl = baseUrl + SHARE_API_PATH + key;
|
||||
HttpRequest<Buffer> httpRequest = clientSession.getAbs(shareApiUrl);
|
||||
if (pwd != null) {
|
||||
httpRequest.addQueryParam("password", pwd);
|
||||
}
|
||||
|
||||
httpRequest.send().onSuccess(res -> {
|
||||
try {
|
||||
if (res.statusCode() == 200 && res.bodyAsJsonObject().containsKey("code")) {
|
||||
JsonObject jsonObject = asJson(res);
|
||||
// 检查响应结构来判断版本
|
||||
// 如果share API成功,但download API返回404,说明是4.x
|
||||
// 这里我们先尝试3.x的download API
|
||||
String downloadApiUrl = baseUrl + DOWNLOAD_API_PATH + key + "?path=undefined/undefined;";
|
||||
checkDownloadApi(downloadApiUrl, baseUrl, key, pwd);
|
||||
} else {
|
||||
nextParser();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
nextParser();
|
||||
}
|
||||
}).onFailure(t -> {
|
||||
nextParser();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查3.x的download API是否存在
|
||||
* 如果不存在,说明是4.x版本
|
||||
*/
|
||||
private void checkDownloadApi(String downloadApiUrl, String baseUrl, String key, String pwd) {
|
||||
clientSession.putAbs(downloadApiUrl).send().onSuccess(res -> {
|
||||
if (res.statusCode() == 404 || res.statusCode() == 405) {
|
||||
// download API不存在或方法不允许,说明是4.x
|
||||
delegateToCe4Tool();
|
||||
} else if (res.statusCode() == 200) {
|
||||
// 3.x版本,继续使用当前逻辑
|
||||
getDownURL(downloadApiUrl);
|
||||
} else {
|
||||
// 其他错误
|
||||
fail("无法确定Cloudreve版本或接口调用失败");
|
||||
}
|
||||
}).onFailure(t -> {
|
||||
// 尝试使用4.x
|
||||
delegateToCe4Tool();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 转发到Ce4Tool处理4.x版本
|
||||
*/
|
||||
private void delegateToCe4Tool() {
|
||||
log.debug("检测到Cloudreve 4.x,转发到Ce4Tool处理");
|
||||
new Ce4Tool(shareLinkInfo).parse().onComplete(promise);
|
||||
}
|
||||
|
||||
|
||||
private void getDownURL(String shareApiUrl) {
|
||||
|
||||
Reference in New Issue
Block a user