mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2026-08-30 05:22:01 +00:00
324 lines
15 KiB
Java
324 lines
15 KiB
Java
package cn.qaiu.parser.impl;
|
|
|
|
import cn.qaiu.entity.FileInfo;
|
|
import cn.qaiu.entity.ShareLinkInfo;
|
|
import cn.qaiu.parser.PanBase;
|
|
import cn.qaiu.util.CommonUtils;
|
|
import cn.qaiu.util.FileSizeConverter;
|
|
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.JsonArray;
|
|
import io.vertx.core.json.JsonObject;
|
|
import io.vertx.core.json.pointer.JsonPointer;
|
|
import io.vertx.ext.web.client.WebClient;
|
|
import io.vertx.uritemplate.UriTemplate;
|
|
import org.apache.commons.lang3.StringUtils;
|
|
import org.openjdk.nashorn.api.scripting.ScriptObjectMirror;
|
|
|
|
import java.net.MalformedURLException;
|
|
import java.time.OffsetDateTime;
|
|
import java.time.format.DateTimeFormatter;
|
|
import java.util.ArrayList;
|
|
import java.util.Base64;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
import static cn.qaiu.util.RandomStringGenerator.gen36String;
|
|
|
|
/**
|
|
* 123网盘
|
|
*/
|
|
public class YeTool extends PanBase {
|
|
|
|
public static final String SHARE_URL_PREFIX = "https://www.123pan.com/s/";
|
|
public static final String FIRST_REQUEST_URL = SHARE_URL_PREFIX + "{key}.html";
|
|
|
|
private static final String GET_FILE_INFO_URL = "https://www.123pan.com/a/api/share/get?limit=100&next=1&orderBy" +
|
|
"=file_name&orderDirection=asc" +
|
|
"&shareKey={shareKey}&SharePwd={pwd}&ParentFileId={ParentFileId}&Page=1&event=homeListFile&operateType=1";
|
|
private static final String DOWNLOAD_API_URL = "https://www.123pan.com/a/api/share/download/info?{authK}={authV}";
|
|
|
|
private static final String BATCH_DOWNLOAD_API_URL = "https://www.123pan.com/b/api/file/batch_download_share_info?{authK}={authV}";
|
|
private final MultiMap header = MultiMap.caseInsensitiveMultiMap();
|
|
|
|
public YeTool(ShareLinkInfo shareLinkInfo) {
|
|
super(shareLinkInfo);
|
|
header.set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6");
|
|
header.set("App-Version", "3");
|
|
header.set("Cache-Control", "no-cache");
|
|
header.set("Connection", "keep-alive");
|
|
//header.set("DNT", "1");
|
|
//header.set("Host", "www.123pan.com");
|
|
header.set("LoginUuid", gen36String());
|
|
header.set("Pragma", "no-cache");
|
|
header.set("Referer", shareLinkInfo.getStandardUrl());
|
|
header.set("Sec-Fetch-Dest", "empty");
|
|
header.set("Sec-Fetch-Mode", "cors");
|
|
header.set("Sec-Fetch-Site", "same-origin");
|
|
header.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Edg/127.0.0.0");
|
|
header.set("platform", "web");
|
|
header.set("sec-ch-ua", "\"Not)A;Brand\";v=\"99\", \"Microsoft Edge\";v=\"127\", \"Chromium\";v=\"127\"");
|
|
header.set("sec-ch-ua-mobile", "?0");
|
|
header.set("sec-ch-ua-platform", "Windows");
|
|
}
|
|
|
|
@Override
|
|
public Future<String> parse() {
|
|
|
|
final String shareKey = shareLinkInfo.getShareKey().replaceAll("(\\..*)|(#.*)", "");
|
|
final String pwd = shareLinkInfo.getSharePassword();
|
|
|
|
client.getAbs(UriTemplate.of(GET_FILE_INFO_URL))
|
|
.setTemplateParam("shareKey", shareKey)
|
|
.setTemplateParam("pwd", pwd)
|
|
.setTemplateParam("ParentFileId", "0")
|
|
// .setTemplateParam("authKey", AESUtils.getAuthKey("/a/api/share/get"))
|
|
.putHeader("Platform", "web")
|
|
.putHeader("App-Version", "3")
|
|
.send().onSuccess(res2 -> {
|
|
JsonObject infoJson = asJson(res2);
|
|
if (infoJson.getInteger("code") != 0) {
|
|
fail("{} 状态码异常 {}", shareKey, infoJson);
|
|
return;
|
|
}
|
|
|
|
JsonObject getFileInfoJson =
|
|
infoJson.getJsonObject("data").getJsonArray("InfoList").getJsonObject(0);
|
|
getFileInfoJson.put("ShareKey", shareKey);
|
|
|
|
// 判断是否为文件夹: data->InfoList->0->Type: 1为文件夹, 0为文件
|
|
try {
|
|
int type = (Integer)JsonPointer.from("/data/InfoList/0/Type").queryJson(infoJson);
|
|
if (type == 1) {
|
|
getZipDownUrl(client, getFileInfoJson);
|
|
return;
|
|
}
|
|
} catch (Exception exception) {
|
|
fail("该分享[{}]解析异常: {}", shareKey, exception.getMessage());
|
|
return;
|
|
}
|
|
|
|
getDownUrl(client, getFileInfoJson);
|
|
}).onFailure(this.handleFail(GET_FILE_INFO_URL));
|
|
return promise.future();
|
|
}
|
|
|
|
private void getDownUrl(WebClient client, JsonObject reqBodyJson) {
|
|
setFileInfo(reqBodyJson);
|
|
log.info(reqBodyJson.encodePrettily());
|
|
JsonObject jsonObject = new JsonObject();
|
|
// {"ShareKey":"iaKtVv-6OECd","FileID":2193732,"S3keyFlag":"1811834632-0","Size":4203111,
|
|
// "Etag":"69c94adbc0b9190cf23c4e958d8c7c53"}
|
|
jsonObject.put("ShareKey", reqBodyJson.getString("ShareKey"));
|
|
jsonObject.put("FileID", reqBodyJson.getInteger("FileId"));
|
|
jsonObject.put("S3keyFlag", reqBodyJson.getString("S3KeyFlag"));
|
|
jsonObject.put("Size", reqBodyJson.getLong("Size"));
|
|
jsonObject.put("Etag", reqBodyJson.getString("Etag"));
|
|
|
|
// 调用JS文件获取签名
|
|
down(client, jsonObject, DOWNLOAD_API_URL);
|
|
}
|
|
|
|
|
|
private void getZipDownUrl(WebClient client, JsonObject reqBodyJson) {
|
|
log.info(reqBodyJson.encodePrettily());
|
|
JsonObject jsonObject = new JsonObject();
|
|
// {"ShareKey":"LH3rTd-1ENed","fileIdList":[{"fileId":17525952}]}
|
|
jsonObject.put("ShareKey", reqBodyJson.getString("ShareKey"));
|
|
jsonObject.put("fileIdList", new JsonArray().add(JsonObject.of("fileId", reqBodyJson.getInteger("FileId"))));
|
|
// 调用JS文件获取签名
|
|
down(client, jsonObject, BATCH_DOWNLOAD_API_URL);
|
|
}
|
|
|
|
private void down(WebClient client, JsonObject jsonObject, String api) {
|
|
ScriptObjectMirror getSign;
|
|
try {
|
|
getSign = JsExecUtils.executeJs("getSign", "/a/api/share/download/info");
|
|
} catch (Exception e) {
|
|
fail(e, "JS函数执行异常");
|
|
return;
|
|
}
|
|
log.info("ye getSign: {}={}", getSign.get("0").toString(), getSign.get("1").toString());
|
|
|
|
client.postAbs(UriTemplate.of(api))
|
|
.setTemplateParam("authK", getSign.get("0").toString())
|
|
.setTemplateParam("authV", getSign.get("1").toString())
|
|
.putHeader("Platform", "web")
|
|
.putHeader("App-Version", "3")
|
|
.sendJsonObject(jsonObject).onSuccess(res2 -> {
|
|
JsonObject downURLJson = asJson(res2);
|
|
|
|
try {
|
|
if (downURLJson.getInteger("code") != 0) {
|
|
fail("Ye: downURLJson返回值异常->" + downURLJson);
|
|
return;
|
|
}
|
|
} catch (Exception ignored) {
|
|
fail("Ye: downURLJson格式异常->" + downURLJson);
|
|
return;
|
|
}
|
|
String downURL = downURLJson.getJsonObject("data")
|
|
.getString(api.contains("batch_download_share_info")? "DownloadUrl" : "DownloadURL");
|
|
try {
|
|
Map<String, String> urlParams = CommonUtils.getURLParams(downURL);
|
|
String params = urlParams.get("params");
|
|
byte[] decodeByte = Base64.getDecoder().decode(params);
|
|
String downUrl2 = new String(decodeByte);
|
|
|
|
// 获取直链
|
|
client.getAbs(downUrl2).send().onSuccess(res3 -> {
|
|
JsonObject res3Json = asJson(res3);
|
|
try {
|
|
if (res3Json.getInteger("code") != 0) {
|
|
fail("Ye: downUrl2返回值异常->" + res3Json);
|
|
return;
|
|
}
|
|
} catch (Exception ignored) {
|
|
fail("Ye: downUrl2格式异常->" + downURLJson);
|
|
return;
|
|
}
|
|
|
|
promise.complete(res3Json.getJsonObject("data").getString("redirect_url"));
|
|
|
|
}).onFailure(this.handleFail("获取直链失败"));
|
|
|
|
} catch (MalformedURLException e) {
|
|
fail("urlParams解析异常" + e.getMessage());
|
|
}
|
|
}).onFailure(this.handleFail(DOWNLOAD_API_URL));
|
|
}
|
|
|
|
|
|
// dir parser
|
|
@Override
|
|
public Future<List<FileInfo>> parseFileList() {
|
|
Promise<List<FileInfo>> promise = Promise.promise();
|
|
|
|
String shareKey = shareLinkInfo.getShareKey(); // 分享链接的唯一标识
|
|
String pwd = shareLinkInfo.getSharePassword(); // 分享密码
|
|
String parentFileId = "0"; // 根目录的文件ID
|
|
|
|
// 如果参数里的目录ID不为空,则直接解析目录
|
|
String dirId = (String) shareLinkInfo.getOtherParam().get("dirId");
|
|
if (StringUtils.isNotBlank(dirId)) {
|
|
parentFileId = dirId;
|
|
}
|
|
|
|
|
|
// 构造文件列表接口的URL
|
|
client.getAbs(UriTemplate.of(GET_FILE_INFO_URL))
|
|
.setTemplateParam("shareKey", shareKey)
|
|
.setTemplateParam("pwd", pwd)
|
|
.setTemplateParam("ParentFileId", parentFileId)
|
|
.putHeaders(header)
|
|
.send().onSuccess(res -> {
|
|
JsonObject response = asJson(res);
|
|
if (response.getInteger("code") != 0) {
|
|
promise.fail("API错误: " + response.getString("message"));
|
|
return;
|
|
}
|
|
|
|
JsonArray infoList = response.getJsonObject("data").getJsonArray("InfoList");
|
|
List<FileInfo> result = new ArrayList<>();
|
|
|
|
// 遍历返回的文件和目录信息
|
|
for (int i = 0; i < infoList.size(); i++) {
|
|
JsonObject item = infoList.getJsonObject(i);
|
|
FileInfo fileInfo = new FileInfo();
|
|
// "FileId": 16603582,
|
|
// "FileName": "pdf",
|
|
// "Type": 1,
|
|
// "Size": 0,
|
|
// "ContentType": "0",
|
|
// "S3KeyFlag": "",
|
|
// "CreateAt": "2025-07-09T06:56:20+08:00",
|
|
// "UpdateAt": "2025-07-09T06:56:20+08:00",
|
|
// "Etag": "",
|
|
// "DownloadUrl": "",
|
|
// "Status": 0,
|
|
// "ParentFileId": 16603579,
|
|
// "Category": 0,
|
|
// "PunishFlag": 0,
|
|
// "StorageNode": "m0",
|
|
// "PreviewType": 0
|
|
|
|
// =>
|
|
// {
|
|
// "ShareKey":"iaKtVv-FTaCd",
|
|
// "FileID":16604189,
|
|
// "S3keyFlag":"1815268665-0",
|
|
// "Size":425929,
|
|
// "Etag":"70049de67075ab2b269c62d690424601",
|
|
// "OrderId":""}
|
|
JsonObject postData = JsonObject.of()
|
|
.put("ShareKey", shareKey)
|
|
.put("FileID", item.getInteger("FileId"))
|
|
.put("S3keyFlag", item.getString("S3KeyFlag"))
|
|
.put("Size", item.getLong("Size"))
|
|
.put("Etag", item.getString("Etag"));
|
|
|
|
byte[] encode = Base64.getEncoder().encode(postData.encode().getBytes());
|
|
String param = new String(encode);
|
|
|
|
if (item.getInteger("Type") == 0) { // 文件
|
|
fileInfo.setFileName(item.getString("FileName"))
|
|
.setFileId(item.getString("FileId"))
|
|
.setFileType("file")
|
|
.setSize(item.getLong("Size"))
|
|
.setCreateTime(item.getString("CreateAt"))
|
|
.setUpdateTime(item.getString("UpdateAt"))
|
|
.setSizeStr(FileSizeConverter.convertToReadableSize(item.getLong("Size")))
|
|
.setParserUrl(String.format("%s/v2/redirectUrl/%s/%s", getDomainName(),
|
|
shareLinkInfo.getType(), param))
|
|
.setPreviewUrl(String.format("%s/v2/viewUrl/%s/%s", getDomainName(),
|
|
shareLinkInfo.getType(), param));
|
|
result.add(fileInfo);
|
|
} else if (item.getInteger("Type") == 1) { // 目录
|
|
fileInfo.setFileName(item.getString("FileName"))
|
|
.setFileId(item.getString("FileId"))
|
|
.setCreateTime(item.getString("CreateAt"))
|
|
.setUpdateTime(item.getString("UpdateAt"))
|
|
.setSize(0L)
|
|
.setFileType("folder")
|
|
.setParserUrl(
|
|
String.format("%s/v2/getFileList?url=%s&dirId=%s&pwd=%s",
|
|
getDomainName(),
|
|
shareLinkInfo.getShareUrl(),
|
|
item.getString("FileId"),
|
|
pwd)
|
|
);
|
|
result.add(fileInfo);
|
|
}
|
|
}
|
|
promise.complete(result);
|
|
}).onFailure(promise::fail);
|
|
|
|
return promise.future();
|
|
}
|
|
|
|
@Override
|
|
public Future<String> parseById() {
|
|
JsonObject paramJson = (JsonObject) shareLinkInfo.getOtherParam().get("paramJson");
|
|
// 调用下载接口获取直链
|
|
down(client, paramJson, DOWNLOAD_API_URL);
|
|
return promise.future();
|
|
}
|
|
|
|
void setFileInfo(JsonObject reqBodyJson) {
|
|
FileInfo fileInfo = new FileInfo();
|
|
fileInfo.setFileId(reqBodyJson.getInteger("FileId").toString());
|
|
fileInfo.setFileName(reqBodyJson.getString("FileName"));
|
|
fileInfo.setSize(reqBodyJson.getLong("Size"));
|
|
fileInfo.setHash(reqBodyJson.getString("Etag"));
|
|
fileInfo.setCreateTime(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
|
|
.format(OffsetDateTime.parse(reqBodyJson.getString("CreateAt")).toLocalDateTime()));
|
|
fileInfo.setUpdateTime(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
|
|
.format(OffsetDateTime.parse(reqBodyJson.getString("UpdateAt")).toLocalDateTime()));
|
|
shareLinkInfo.getOtherParam().put("fileInfo", fileInfo);
|
|
}
|
|
}
|