diff --git a/parser/src/main/java/cn/qaiu/parser/impl/Ce4Tool.java b/parser/src/main/java/cn/qaiu/parser/impl/Ce4Tool.java
new file mode 100644
index 0000000..baf5a5b
--- /dev/null
+++ b/parser/src/main/java/cn/qaiu/parser/impl/Ce4Tool.java
@@ -0,0 +1,136 @@
+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;
+
+/**
+ * Cloudreve 4.x 自建网盘解析
+ * Cloudreve 4.x API 版本解析器
+ * 此解析器专门处理Cloudreve 4.x版本的API,使用新的下载流程
+ */
+public class Ce4Tool extends PanBase {
+
+ // Cloudreve 4.x uses /api/v3/ prefix for most APIs
+ 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 parse() {
+ String key = shareLinkInfo.getShareKey();
+ String pwd = shareLinkInfo.getSharePassword();
+
+ try {
+ URL url = new URL(shareLinkInfo.getShareUrl());
+ String baseUrl = url.getProtocol() + "://" + url.getHost();
+
+ // 获取分享信息
+ getShareInfo(baseUrl, key, pwd);
+ } catch (Exception e) {
+ fail(e, "URL解析错误");
+ }
+ return promise.future();
+ }
+
+ /**
+ * 获取Cloudreve 4.x分享信息
+ */
+ private void getShareInfo(String baseUrl, String key, String pwd) {
+ String shareApiUrl = baseUrl + SHARE_API_PATH + key;
+
+ HttpRequest 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) {
+ // 成功,获取文件信息和下载链接
+ JsonObject data = jsonObject.getJsonObject("data");
+ if (data != null) {
+ // 获取文件路径,如果没有则使用默认路径
+ String filePath = "/";
+ if (data.containsKey("path")) {
+ filePath = data.getString("path");
+ }
+ // 对于4.x,需要通过 POST /api/v3/file/url 获取下载链接
+ getDownloadUrl(baseUrl, filePath);
+ } else {
+ fail("分享信息获取失败: data字段为空");
+ }
+ } else {
+ // 错误码,可能是密码错误或分享失效
+ String msg = jsonObject.getString("msg", "未知错误");
+ fail("分享验证失败: {}", msg);
+ }
+ } else {
+ // 响应格式不符合预期
+ fail("响应格式不符合Cloudreve 4.x规范");
+ }
+ } else {
+ // HTTP错误
+ fail("获取分享信息失败: HTTP {}", res.statusCode());
+ }
+ } catch (Exception e) {
+ fail(e, "解析分享信息响应失败");
+ }
+ }).onFailure(handleFail(shareApiUrl));
+ }
+
+ /**
+ * 通过 POST /api/v3/file/url 获取下载链接 (Cloudreve 4.x API)
+ */
+ private void getDownloadUrl(String baseUrl, String filePath) {
+ String fileUrlApi = baseUrl + FILE_URL_API_PATH;
+
+ // 准备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字段: {}", jsonObject.encodePrettily());
+ }
+ } else {
+ fail("获取下载链接失败: HTTP {}", res.statusCode());
+ }
+ } catch (Exception e) {
+ fail(e, "解析下载链接响应失败");
+ }
+ }).onFailure(handleFail(fileUrlApi));
+ }
+}
diff --git a/parser/src/main/java/cn/qaiu/parser/impl/CeTool.java b/parser/src/main/java/cn/qaiu/parser/impl/CeTool.java
index 968f6cc..5a6f4b9 100644
--- a/parser/src/main/java/cn/qaiu/parser/impl/CeTool.java
+++ b/parser/src/main/java/cn/qaiu/parser/impl/CeTool.java
@@ -15,6 +15,7 @@ import java.net.URL;
* huang1111
* 看见存储
* 亿安云盘
+ * 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);
@@ -31,39 +35,121 @@ public class CeTool extends PanBase {
public Future parse() {
String key = shareLinkInfo.getShareKey();
String pwd = shareLinkInfo.getSharePassword();
- // https://pan.huang1111.cn/s/wDz5TK
- // https://pan.huang1111.cn/s/y12bI6 -> https://pan.huang1111
- // .cn/api/v3/share/download/y12bI6?path=undefined%2Fundefined;
- // 类型解析 -> /ce/pan.huang1111.cn_s_wDz5TK
- // parser接口 -> /parser?url=https://pan.huang1111.cn/s/wDz5TK
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 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 如果/v3 或者/v4 能查询到json响应,可以判断是哪个版本
+ * 不然返回404说明不是ce盘直接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 {
+ asJson(res);
+ // v3 ping成功,可能是3.x或4.x,尝试3.x的download API来判断
+ String shareApiUrl = baseUrl + SHARE_API_PATH + key;
+ String downloadApiUrl = baseUrl + DOWNLOAD_API_PATH + key + "?path=undefined/undefined;";
+ checkIfV3(shareApiUrl, downloadApiUrl, pwd);
+ } catch (Exception e) {
+ // JSON解析失败,尝试v4 ping
+ tryV4Ping(baseUrl, key, pwd);
+ }
+ } else if (res.statusCode() == 404) {
+ // v3 ping不存在,尝试v4
+ tryV4Ping(baseUrl, key, pwd);
+ } else {
+ // 其他错误,不是Cloudreve盘
+ nextParser();
+ }
+ }).onFailure(t -> {
+ // 网络错误或不可达,尝试v4 ping
+ tryV4Ping(baseUrl, key, pwd);
+ });
+ }
+
+ 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 {
+ asJson(res);
+ // v4 ping成功,使用Ce4Tool
+ delegateToCe4Tool();
+ } catch (Exception e) {
+ // JSON解析失败,不是Cloudreve盘
+ nextParser();
+ }
+ } else {
+ // v4 ping失败,不是Cloudreve盘
+ nextParser();
+ }
+ }).onFailure(t -> {
+ // 网络错误,尝试下一个解析器
+ nextParser();
+ });
+ }
+
+ /**
+ * 检查是否是3.x版本,通过尝试调用3.x的API
+ */
+ private void checkIfV3(String shareApiUrl, String downloadApiUrl, String pwd) {
+ HttpRequest httpRequest = clientSession.getAbs(shareApiUrl);
+ if (pwd != null) {
+ httpRequest.addQueryParam("password", pwd);
+ }
+
+ httpRequest.send().onSuccess(res -> {
+ try {
+ if (res.statusCode() == 200 && res.bodyAsJsonObject().containsKey("code")) {
+ // share API成功,尝试download API
+ clientSession.putAbs(downloadApiUrl).send().onSuccess(res2 -> {
+ if (res2.statusCode() == 200 || res2.statusCode() == 400) {
+ // 3.x版本的download API存在
+ getDownURL(downloadApiUrl);
+ } else if (res2.statusCode() == 404 || res2.statusCode() == 405) {
+ // download API不存在,说明是4.x
+ delegateToCe4Tool();
+ } else {
+ // 其他错误,可能是4.x
+ delegateToCe4Tool();
+ }
+ }).onFailure(t -> {
+ // 请求失败,尝试4.x
+ delegateToCe4Tool();
+ });
+ } else {
+ nextParser();
+ }
+ } catch (Exception e) {
+ nextParser();
+ }
+ }).onFailure(t -> {
+ nextParser();
+ });
+ }
+
+ /**
+ * 转发到Ce4Tool处理4.x版本
+ */
+ private void delegateToCe4Tool() {
+ log.debug("检测到Cloudreve 4.x,转发到Ce4Tool处理");
+ new Ce4Tool(shareLinkInfo).parse().onComplete(promise);
+ }
private void getDownURL(String shareApiUrl) {