fix(QQwTool): 改用 POST JSON API 解析 QQ 邮箱云盘链接

旧实现通过 GET 请求获取 HTML 并正则提取 JS 变量,但接口已改为返回 JSON,
导致 NumberFormatException。改为 POST 请求 `https://wx.mail.qq.com/s`,
body 为 `f=json&k={shareKey}`,解析 JSON 响应中的 body.url/name/size。

- 使用 postAbs() 替代 request() 以正确处理 HTTPS
- 使用 asJson() 兼容 gzip 响应
- 使用 complete() 正确存储 downloadUrl
- 添加 User-Agent、URL 编码 shareKey
This commit is contained in:
yukaidi
2026-05-29 22:26:44 +08:00
parent f81b3852ee
commit 13f83e8795

View File

@@ -3,10 +3,11 @@ package cn.qaiu.parser.impl;
import cn.qaiu.entity.FileInfo; import cn.qaiu.entity.FileInfo;
import cn.qaiu.entity.ShareLinkInfo; import cn.qaiu.entity.ShareLinkInfo;
import io.vertx.core.Future; import io.vertx.core.Future;
import java.util.HashMap; import io.vertx.core.buffer.Buffer;
import java.util.Map; import io.vertx.core.json.JsonObject;
import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
public class QQwTool extends QQTool { public class QQwTool extends QQTool {
@@ -16,54 +17,51 @@ public class QQwTool extends QQTool {
@Override @Override
public Future<String> parse() { public Future<String> parse() {
client.getAbs(shareLinkInfo.getShareUrl()).send().onSuccess(res -> { String k = shareLinkInfo.getShareKey();
String html = res.bodyAsString(); String postBody = "f=json&k=" + URLEncoder.encode(k, StandardCharsets.UTF_8);
Map<String, String> stringStringMap = extractVariables(html);
String url = stringStringMap.get("url");
String fn = stringStringMap.get("filename");
String size = stringStringMap.get("filesize");
String createBy = stringStringMap.get("nick");
FileInfo fileInfo = new FileInfo().setFileName(fn).setSize(Long.parseLong(size)).setCreateBy(createBy);
shareLinkInfo.getOtherParam().put("fileInfo", fileInfo);
if (url != null) {
String url302 = url.replace("\\x26", "&");
promise.complete(url302);
/* client.postAbs("https://wx.mail.qq.com/s")
clientNoRedirects.getAbs(url302).send().onSuccess(res2 -> { .putHeader("Content-Type", "application/x-www-form-urlencoded")
MultiMap headers = res2.headers(); .putHeader("Accept", "application/json, text/plain, */*")
if (headers.contains("Location")) { .putHeader("Referer", shareLinkInfo.getShareUrl())
promise.complete(headers.get("Location")); .putHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
} else { + "(KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0")
fail("找不到重定向URL"); .sendBuffer(Buffer.buffer(postBody))
.onSuccess(res -> {
try {
JsonObject data = asJson(res);
JsonObject head = data.getJsonObject("head");
if (head == null || head.getInteger("ret", -1) != 0) {
String msg = head != null ? head.getString("msg", "未知错误") : "未知错误";
fail("API错误: " + msg);
return;
}
JsonObject body = data.getJsonObject("body");
if (body == null) {
fail("文件信息为空");
return;
}
String url = body.getString("url");
String fn = body.getString("name", "");
long size = body.getLong("size", 0L);
if (url == null || url.isEmpty()) {
fail("分享链接解析失败, 可能是链接失效");
return;
}
FileInfo fileInfo = new FileInfo().setFileName(fn).setSize(size);
shareLinkInfo.getOtherParam().put("fileInfo", fileInfo);
String url302 = url.replace("\\x26", "&");
complete(url302);
} catch (Exception e) {
fail(e, "解析响应失败");
} }
}).onFailure(handleFail()); }).onFailure(handleFail());
*/
} else {
fail("分享链接解析失败, 可能是链接失效");
}
}).onFailure(handleFail());
return promise.future(); return promise.future();
} }
private Map<String, String> extractVariables(String jsCode) {
Map<String, String> variables = new HashMap<>();
// 正则表达式匹配 var 变量定义
String regex = "\\s+var\\s+(\\w+)\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^;\\r\\n]*))";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(jsCode);
while (m.find()) {
String name = m.group(1);
String value = m.group(2) != null ? m.group(2)
: m.group(3) != null ? m.group(3)
: m.group(4);
variables.put(name, value);
}
return variables;
}
} }