mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2025-12-16 20:33:03 +00:00
添加文叔叔解析
1.修复QQ邮箱解析 2.添加文叔叔解析
This commit is contained in:
@@ -43,8 +43,12 @@
|
||||
- [ ] 登录, 上传, 下载, 分享
|
||||
- [X] 直链解析
|
||||
- [文叔叔 (ws) 开发中](https://www.wenshushu.cn/)
|
||||
- [夸克网盘 (qk) 开发中](https://pan.quark.cn/)
|
||||
- [ ] 登录, 上传, 下载, 分享
|
||||
- [X] 直链解析
|
||||
- [QQ邮箱 (qq) 开发中](https://wx.mail.qq.com/)
|
||||
- [ ] 登录, 上传, 下载, 分享
|
||||
- [*] 直链解析(用户无法直接使用直链)
|
||||
- [夸克网盘 (qk) 开发中](https://pan.quark.cn/)
|
||||
|
||||
**TODO:**
|
||||
- 登录接口, 文件上传/下载/分享后端接口
|
||||
|
||||
@@ -7,7 +7,6 @@ public interface IPanTool {
|
||||
Future<String> parse();
|
||||
|
||||
static IPanTool typeMatching(String type, String key, String pwd) {
|
||||
System.out.println(">>>>>>>> type: " + type + "\nkey: " + key + "\n pwd: " + pwd);
|
||||
return switch (type) {
|
||||
case "lz" -> new LzTool(key, pwd);
|
||||
case "cow" -> new CowTool(key, pwd);
|
||||
@@ -18,6 +17,7 @@ public interface IPanTool {
|
||||
case "fj" -> new FjTool(key, pwd);
|
||||
case "qk" -> new QkTool(key, pwd);
|
||||
case "le" -> new LeTool(key, pwd);
|
||||
case "ws" -> new WsTool(key, pwd);
|
||||
case "qq" -> new QQTool(key, pwd);
|
||||
default -> {
|
||||
throw new UnsupportedOperationException("未知分享类型");
|
||||
@@ -43,6 +43,8 @@ public interface IPanTool {
|
||||
return new LzTool(url, pwd);
|
||||
} else if (url.startsWith(LeTool.SHARE_URL_PREFIX)) {
|
||||
return new LeTool(url, pwd);
|
||||
} else if (url.contains(WsTool.SHARE_URL_PREFIX) || url.contains(WsTool.SHARE_URL_PREFIX2)) {
|
||||
return new WsTool(url, pwd);
|
||||
} else if (url.contains(QQTool.SHARE_URL_PREFIX)) {
|
||||
return new QQTool(url, pwd);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
package cn.qaiu.parser.impl;
|
||||
|
||||
import cn.qaiu.util.StringUtils;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
|
||||
import cn.qaiu.parser.IPanTool;
|
||||
import cn.qaiu.parser.PanBase;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.MultiMap;
|
||||
import io.vertx.ext.web.client.WebClient;
|
||||
|
||||
/**
|
||||
* <a href="https://wx.mail.qq.com/">QQ邮箱</a>
|
||||
*/
|
||||
public class QQTool extends PanBase implements IPanTool {
|
||||
|
||||
public static final String SHARE_URL_PREFIX = "wx.mail.qq.com/ftn/download?";
|
||||
|
||||
static String test = "";
|
||||
|
||||
public QQTool(String key, String pwd) {
|
||||
super(key, pwd);
|
||||
}
|
||||
@@ -22,19 +28,12 @@ public class QQTool extends PanBase implements IPanTool {
|
||||
WebClient httpClient = this.client;
|
||||
|
||||
// 补全链接
|
||||
if (!this.key.startsWith("https://" + SHARE_URL_PREFIX))
|
||||
{
|
||||
if (this.key.startsWith(SHARE_URL_PREFIX))
|
||||
{
|
||||
if (!this.key.startsWith("https://" + SHARE_URL_PREFIX)) {
|
||||
if (this.key.startsWith(SHARE_URL_PREFIX)) {
|
||||
this.key = "https://" + this.key;
|
||||
}
|
||||
else if (this.key.startsWith("func="))
|
||||
{
|
||||
} else if (this.key.startsWith("func=")) {
|
||||
this.key = "https://" + SHARE_URL_PREFIX + this.key;
|
||||
}
|
||||
else
|
||||
{
|
||||
// fail("未知分享链接: " + this.key);
|
||||
} else {
|
||||
throw new UnsupportedOperationException("未知分享类型");
|
||||
}
|
||||
}
|
||||
@@ -52,51 +51,35 @@ public class QQTool extends PanBase implements IPanTool {
|
||||
|
||||
// 获取下载中转站页面
|
||||
httpClient.getAbs(this.key).putHeaders(headers).send().onSuccess(res -> {
|
||||
if (res.statusCode() == 200)
|
||||
{
|
||||
if (res.statusCode() == 200) {
|
||||
String html = res.bodyAsString();
|
||||
|
||||
// 匹配文件信息
|
||||
String filename = StringCutNot(html, "var filename = \"", "\"");
|
||||
String filesize = StringCutNot(html, "var filesize = ", "\n");
|
||||
String url = StringCutNot(html, "var url = \"", "\"");
|
||||
String filename = StringUtils.StringCutNot(html, "var filename = \"", "\"");
|
||||
String filesize = StringUtils.StringCutNot(html, "var filesize = ", "\n");
|
||||
String fileurl = StringUtils.StringCutNot(html, "var url = \"", "\"");
|
||||
|
||||
if (filename != null && filesize != null && url != null)
|
||||
{
|
||||
if (filename != null && filesize != null && fileurl != null) {
|
||||
// 设置所需HTTP头部
|
||||
headers.set("Referer", "https://" + StringCutNot(this.key, "https://", "/") + "/");
|
||||
headers.set("Host", StringCutNot(url, "https://", "/"));
|
||||
headers.set("Referer", "https://" + StringUtils.StringCutNot(this.key, "https://", "/") + "/");
|
||||
headers.set("Host", StringUtils.StringCutNot(fileurl, "https://", "/"));
|
||||
res.headers().forEach((k, v) -> {
|
||||
if (k.toLowerCase().equals("set-cookie"))
|
||||
{
|
||||
test = StringCutNot(v, "mail5k=", ";");
|
||||
headers.set("Cookie", "mail5k=" + StringCutNot(v, "mail5k=", ";") + ";");
|
||||
if (k.toLowerCase().equals("set-cookie")) {
|
||||
headers.set("Cookie", "mail5k=" + StringUtils.StringCutNot(v, "mail5k=", ";") + ";");
|
||||
}
|
||||
});
|
||||
|
||||
// 调试匹配的情况
|
||||
System.out.println("文件名称: " + filename);
|
||||
System.out.println("文件大小: " + filesize);
|
||||
System.out.println("文件直链: " + url);
|
||||
System.out.println("mail5k= " + test);
|
||||
|
||||
// 访问直链
|
||||
httpClient.getAbs(url).putHeaders(headers).send().onSuccess(res2 -> {
|
||||
// 调试获取文件内容
|
||||
System.out.println("文件内容: " + res2.bodyAsString());
|
||||
System.out.println("文件直链: " + fileurl);
|
||||
|
||||
// 提交
|
||||
promise.complete(url);
|
||||
}).onFailure(this.handleFail(this.key));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
promise.complete(fileurl.replace("\\x26", "&"));
|
||||
} else {
|
||||
this.fail("匹配失败,可能是分享链接的方式已更新");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
this.fail("HTTP状态不正确,可能是分享链接的方式已更新");
|
||||
}
|
||||
}).onFailure(this.handleFail(this.key));
|
||||
@@ -104,78 +87,4 @@ public class QQTool extends PanBase implements IPanTool {
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
// 非贪婪截断匹配
|
||||
private String StringCutNot(final String strtarget, final String strstart, final String strend)
|
||||
{
|
||||
char[] target = strtarget.toCharArray();
|
||||
char[] start = strstart.toCharArray();
|
||||
char[] end = strend.toCharArray();
|
||||
|
||||
int startIdx = -1;
|
||||
int endIdx = -1;
|
||||
int targetLen = target.length;
|
||||
int startLen = start.length;
|
||||
int endLen = end.length;
|
||||
|
||||
for (int i = 0; i <= targetLen - startLen; i++)
|
||||
{
|
||||
boolean match = true;
|
||||
for (int j = 0; j < startLen; j++)
|
||||
{
|
||||
if (target[i + j] != start[j])
|
||||
{
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match)
|
||||
{
|
||||
startIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (startIdx == -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
for (int i = startIdx + startLen; i <= targetLen - endLen; i++)
|
||||
{
|
||||
boolean match = true;
|
||||
for (int j = 0; j < endLen; j++)
|
||||
{
|
||||
if (target[i + j] != end[j])
|
||||
{
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match)
|
||||
{
|
||||
endIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (endIdx == -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (startIdx + startLen < endIdx)
|
||||
{
|
||||
StringBuilder strbuilder = new StringBuilder();
|
||||
|
||||
for (int i = startIdx + startLen; i < endIdx; i++)
|
||||
{
|
||||
strbuilder.append(target[i]);
|
||||
}
|
||||
|
||||
return strbuilder.toString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
183
parser/src/main/java/cn/qaiu/parser/impl/WsTool.java
Normal file
183
parser/src/main/java/cn/qaiu/parser/impl/WsTool.java
Normal file
@@ -0,0 +1,183 @@
|
||||
package cn.qaiu.parser.impl;
|
||||
|
||||
import cn.qaiu.util.StringUtils;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
|
||||
import cn.qaiu.parser.IPanTool;
|
||||
import cn.qaiu.parser.PanBase;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.MultiMap;
|
||||
import io.vertx.core.json.DecodeException;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.client.WebClient;
|
||||
|
||||
/**
|
||||
* <a href="https://www.wenshushu.cn/">文叔叔</a>
|
||||
*/
|
||||
public class WsTool extends PanBase implements IPanTool {
|
||||
|
||||
public static final String SHARE_URL_PREFIX = "www.wenshushu.cn/f/";
|
||||
public static final String SHARE_URL_PREFIX2 = "f.ws59.cn/f/";
|
||||
public static final String SHARE_URL_API = "https://www.wenshushu.cn/ap/";
|
||||
|
||||
public WsTool(String key, String pwd) {
|
||||
super(key, pwd);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Future<String> parse() {
|
||||
|
||||
WebClient httpClient = this.client;
|
||||
|
||||
// 补全链接
|
||||
if (!this.key.startsWith("https://" + SHARE_URL_PREFIX) && !this.key.startsWith("https://" + SHARE_URL_PREFIX2)) {
|
||||
if (this.key.startsWith(SHARE_URL_PREFIX) || this.key.startsWith(SHARE_URL_PREFIX2)) {
|
||||
this.key = "https://" + this.key;
|
||||
} else if (this.key.matches("^[A-Za-z0-9]+$")) {
|
||||
this.key = "https://" + SHARE_URL_PREFIX + this.key;
|
||||
} else {
|
||||
throw new UnsupportedOperationException("未知分享类型");
|
||||
}
|
||||
}
|
||||
|
||||
// 设置基础HTTP头部
|
||||
var userAgent2 = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, " +
|
||||
"like " +
|
||||
"Gecko) Chrome/111.0.0.0 Mobile Safari/537.36";
|
||||
|
||||
MultiMap headers = MultiMap.caseInsensitiveMultiMap();
|
||||
headers.set("User-Agent", userAgent2);
|
||||
headers.set("sec-ch-ua-platform", "Android");
|
||||
headers.set("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2");
|
||||
headers.set("sec-ch-ua-mobile", "sec-ch-ua-mobile");
|
||||
|
||||
// 获取匿名登录token
|
||||
httpClient.postAbs(SHARE_URL_API + "login/anonymous").putHeaders(headers)
|
||||
.sendJsonObject(JsonObject.of("dev_info", "{}"))
|
||||
.onSuccess(res -> {
|
||||
|
||||
if (res.statusCode() == 200) {
|
||||
try {
|
||||
// 设置匿名登录token
|
||||
String token = res.bodyAsJsonObject().getJsonObject("data").getString("token");
|
||||
headers.set("X-Token", token);
|
||||
|
||||
// 获取文件夹信息
|
||||
httpClient.postAbs(SHARE_URL_API + "task/mgrtask").putHeaders(headers)
|
||||
.sendJsonObject(JsonObject.of(
|
||||
"tid", StringUtils.StringCutNot(key, this.key.startsWith(SHARE_URL_PREFIX) ? SHARE_URL_PREFIX : SHARE_URL_PREFIX2),
|
||||
"password", ""
|
||||
)).onSuccess(res2 -> {
|
||||
|
||||
if (res2.statusCode() == 200) {
|
||||
try {
|
||||
// 获取文件夹信息
|
||||
String filetime = res2.bodyAsJsonObject().getJsonObject("data").getString("expire"); // 文件夹剩余时间
|
||||
String filesize = res2.bodyAsJsonObject().getJsonObject("data").getString("file_size"); // 文件夹大小
|
||||
String filepid = res2.bodyAsJsonObject().getJsonObject("data").getString("ufileid"); // 文件夹pid
|
||||
String filebid = res2.bodyAsJsonObject().getJsonObject("data").getString("boxid"); // 文件夹bid
|
||||
|
||||
// 调试输出文件夹信息
|
||||
System.out.println("文件夹期限: " + filetime);
|
||||
System.out.println("文件夹大小: " + filesize);
|
||||
System.out.println("文件夹pid: " + filepid);
|
||||
System.out.println("文件夹bid: " + filebid);
|
||||
|
||||
// 获取文件信息
|
||||
httpClient.postAbs(SHARE_URL_API + "ufile/list").putHeaders(headers)
|
||||
.sendJsonObject(JsonObject.of(
|
||||
"start", 0,
|
||||
"sort", JsonObject.of(
|
||||
"name", "asc"
|
||||
),
|
||||
"bid", filebid,
|
||||
"pid", filepid,
|
||||
"type", 1,
|
||||
"options", JsonObject.of(
|
||||
"uploader", "true"
|
||||
),
|
||||
"size", 50
|
||||
)).onSuccess(res3 -> {
|
||||
|
||||
if (res3.statusCode() == 200) {
|
||||
try {
|
||||
// 获取文件信息
|
||||
String filename = res3.bodyAsJsonObject().getJsonObject("data")
|
||||
.getJsonArray("fileList").getJsonObject(0).getString("fname"); // 文件名称
|
||||
String filefid = res3.bodyAsJsonObject().getJsonObject("data")
|
||||
.getJsonArray("fileList").getJsonObject(0).getString("fid"); // 文件fid
|
||||
|
||||
// 调试输出文件信息
|
||||
System.out.println("文件名称: " + filename);
|
||||
System.out.println("文件fid: " + filefid);
|
||||
|
||||
// 检查文件是否失效
|
||||
httpClient.postAbs(SHARE_URL_API + "dl/sign").putHeaders(headers)
|
||||
.sendJsonObject(JsonObject.of(
|
||||
"consumeCode", 0,
|
||||
"type", 1,
|
||||
"ufileid", filefid
|
||||
)).onSuccess(res4 -> {
|
||||
|
||||
if (res4.statusCode() == 200) {
|
||||
try {
|
||||
// 获取直链
|
||||
String fileurl = res4.bodyAsJsonObject().getJsonObject("data").getString("url");
|
||||
|
||||
// 调试输出文件直链
|
||||
System.out.println("文件直链: " + fileurl);
|
||||
|
||||
if (!fileurl.equals(""))
|
||||
{
|
||||
try {
|
||||
promise.complete(URLDecoder.decode(fileurl, "UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
promise.complete(fileurl);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.fail("文件已失效");
|
||||
}
|
||||
|
||||
} catch (DecodeException | NullPointerException e) {
|
||||
this.fail("获取文件信息失败,可能是分享链接的方式已更新,或者对方的文件已失效");
|
||||
}
|
||||
} else {
|
||||
this.fail("HTTP状态不正确,可能是分享链接的方式已更新");
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
} catch (DecodeException | NullPointerException e) {
|
||||
this.fail("获取文件信息失败,可能是分享链接的方式已更新");
|
||||
}
|
||||
} else {
|
||||
this.fail("HTTP状态不正确,可能是分享链接的方式已更新");
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
} catch (DecodeException | NullPointerException e) {
|
||||
this.fail("获取文件夹信息失败,可能是分享链接的方式已更新");
|
||||
}
|
||||
} else {
|
||||
this.fail("HTTP状态不正确,可能是分享链接的方式已更新");
|
||||
}
|
||||
|
||||
}).onFailure(this.handleFail(this.key));
|
||||
|
||||
} catch (DecodeException | NullPointerException e) {
|
||||
this.fail("token获取失败,可能是分享链接的方式已更新");
|
||||
}
|
||||
} else {
|
||||
this.fail("HTTP状态不正确,可能是分享链接的方式已更新");
|
||||
}
|
||||
|
||||
}).onFailure(this.handleFail(this.key));
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
}
|
||||
36
parser/src/main/java/cn/qaiu/util/StringUtils.java
Normal file
36
parser/src/main/java/cn/qaiu/util/StringUtils.java
Normal file
@@ -0,0 +1,36 @@
|
||||
package cn.qaiu.util;
|
||||
|
||||
public class StringUtils {
|
||||
|
||||
// 非贪婪截断匹配
|
||||
public static String StringCutNot(final String strtarget, final String strstart)
|
||||
{
|
||||
int startIdx = strtarget.indexOf(strstart);
|
||||
|
||||
if (startIdx != -1) {
|
||||
startIdx += strstart.length();
|
||||
return strtarget.substring(startIdx);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// 非贪婪截断匹配
|
||||
public static String StringCutNot(final String strtarget, final String strstart, final String strend)
|
||||
{
|
||||
int startIdx = strtarget.indexOf(strstart);
|
||||
int endIdx = -1;
|
||||
|
||||
if (startIdx != -1) {
|
||||
startIdx += strstart.length();
|
||||
endIdx = strtarget.indexOf(strend, startIdx);
|
||||
|
||||
if (endIdx != -1) {
|
||||
return strtarget.substring(startIdx, endIdx);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user