Merge pull request #28 from qaiu/dev-reference

Dev reference 添加文叔叔解析
This commit is contained in:
qaiu
2023-12-16 14:35:44 +08:00
committed by GitHub
8 changed files with 356 additions and 22 deletions

View File

@@ -43,6 +43,11 @@
- [ ] 登录, 上传, 下载, 分享 - [ ] 登录, 上传, 下载, 分享
- [X] 直链解析 - [X] 直链解析
- [文叔叔 (ws) 开发中](https://www.wenshushu.cn/) - [文叔叔 (ws) 开发中](https://www.wenshushu.cn/)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析
- [QQ邮箱 (qq) 开发中](https://wx.mail.qq.com/)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析(用户无法直接使用直链)
- [夸克网盘 (qk) 开发中](https://pan.quark.cn/) - [夸克网盘 (qk) 开发中](https://pan.quark.cn/)
**TODO:** **TODO:**

View File

@@ -17,6 +17,8 @@ public interface IPanTool {
case "fj" -> new FjTool(key, pwd); case "fj" -> new FjTool(key, pwd);
case "qk" -> new QkTool(key, pwd); case "qk" -> new QkTool(key, pwd);
case "le" -> new LeTool(key, pwd); case "le" -> new LeTool(key, pwd);
case "ws" -> new WsTool(key, pwd);
case "qq" -> new QQTool(key, pwd);
default -> { default -> {
throw new UnsupportedOperationException("未知分享类型"); throw new UnsupportedOperationException("未知分享类型");
} }
@@ -41,6 +43,10 @@ public interface IPanTool {
return new LzTool(url, pwd); return new LzTool(url, pwd);
} else if (url.startsWith(LeTool.SHARE_URL_PREFIX)) { } else if (url.startsWith(LeTool.SHARE_URL_PREFIX)) {
return new LeTool(url, pwd); 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);
} }
throw new UnsupportedOperationException("未知分享类型"); throw new UnsupportedOperationException("未知分享类型");

View File

@@ -0,0 +1,90 @@
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?";
public QQTool(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)) {
if (this.key.startsWith(SHARE_URL_PREFIX)) {
this.key = "https://" + this.key;
} else if (this.key.startsWith("func=")) {
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");
// 获取下载中转站页面
httpClient.getAbs(this.key).putHeaders(headers).send().onSuccess(res -> {
if (res.statusCode() == 200) {
String html = res.bodyAsString();
// 匹配文件信息
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 && fileurl != null) {
// 设置所需HTTP头部
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")) {
headers.set("Cookie", "mail5k=" + StringUtils.StringCutNot(v, "mail5k=", ";") + ";");
}
});
// 调试匹配的情况
System.out.println("文件名称: " + filename);
System.out.println("文件大小: " + filesize);
System.out.println("文件直链: " + fileurl);
// 提交
promise.complete(fileurl.replace("\\x26", "&"));
} else {
this.fail("匹配失败,可能是分享链接的方式已更新");
}
} else {
this.fail("HTTP状态不正确可能是分享链接的方式已更新");
}
}).onFailure(this.handleFail(this.key));
return promise.future();
}
}

View 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();
}
}

View 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;
}
}

View File

@@ -36,6 +36,12 @@
<artifactId>logback-classic</artifactId> <artifactId>logback-classic</artifactId>
<version>${logback.version}</version> <version>${logback.version}</version>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/org.fusesource.jansi/jansi -->
<dependency>
<groupId>org.fusesource.jansi</groupId>
<artifactId>jansi</artifactId>
<version>1.17.1</version>
</dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId> <artifactId>slf4j-api</artifactId>

View File

@@ -2,6 +2,7 @@ package cn.qaiu.lz.web.http;
import cn.qaiu.parser.IPanTool; import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.impl.EcTool; import cn.qaiu.parser.impl.EcTool;
import cn.qaiu.parser.impl.QQTool;
import cn.qaiu.vx.core.annotaions.RouteHandler; import cn.qaiu.vx.core.annotaions.RouteHandler;
import cn.qaiu.vx.core.annotaions.RouteMapping; import cn.qaiu.vx.core.annotaions.RouteMapping;
import cn.qaiu.vx.core.enums.RouteMethod; import cn.qaiu.vx.core.enums.RouteMethod;
@@ -30,6 +31,12 @@ public class ServerApi {
// 默认读取Url参数会被截断手动获取一下其他参数 // 默认读取Url参数会被截断手动获取一下其他参数
url = EcTool.SHARE_URL_PREFIX + request.getParam("data"); url = EcTool.SHARE_URL_PREFIX + request.getParam("data");
} }
if (url.contains(QQTool.SHARE_URL_PREFIX)) {
// 默认读取Url参数会被截断手动获取一下其他参数
url = url + "&key=" + request.getParam("key") +
"&code=" + request.getParam("code") + "&k=" + request.getParam("k") +
"&fweb=" + request.getParam("fweb") + "&cl=" + request.getParam("cl");
}
IPanTool.shareURLPrefixMatching(url, pwd).parse().onSuccess(resUrl -> { IPanTool.shareURLPrefixMatching(url, pwd).parse().onSuccess(resUrl -> {
ResponseUtil.redirect(response, resUrl, promise); ResponseUtil.redirect(response, resUrl, promise);
}).onFailure(t -> promise.fail(t.fillInStackTrace())); }).onFailure(t -> promise.fail(t.fillInStackTrace()));

View File

@@ -46,6 +46,7 @@
</appender> </appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<withJansi>true</withJansi>
<encoder> <encoder>
<!--格式化输出:%d表示日期%thread表示线程名%-5level级别从左显示5个字符宽度%msg日志消息%n是换行符--> <!--格式化输出:%d表示日期%thread表示线程名%-5level级别从左显示5个字符宽度%msg日志消息%n是换行符-->
<pattern>${CUSTOMER_PATTERN2}</pattern> <pattern>${CUSTOMER_PATTERN2}</pattern>