parser v10.1.17发布到maven central 允许开发者依赖

1. 添加自定义解析器扩展和相关示例
2. 优化pom结构
This commit is contained in:
q
2025-10-17 15:50:45 +08:00
parent c16bde6bb8
commit 5e09b8e92a
33 changed files with 2421 additions and 93 deletions

View File

@@ -0,0 +1,222 @@
package cn.qaiu.parser;
import cn.qaiu.entity.ShareLinkInfo;
import java.util.regex.Pattern;
/**
* 用户自定义解析器配置类
* 用于描述自定义解析器的元信息
*
* @author <a href="https://qaiu.top">QAIU</a>
* Create at 2025/10/17
*/
public class CustomParserConfig {
/**
* 解析器类型标识(唯一,建议使用小写英文)
*/
private final String type;
/**
* 网盘显示名称
*/
private final String displayName;
/**
* 解析工具实现类(必须实现 IPanTool 接口,且有 ShareLinkInfo 单参构造器)
*/
private final Class<? extends IPanTool> toolClass;
/**
* 标准URL模板可选用于规范化分享链接
*/
private final String standardUrlTemplate;
/**
* 网盘域名(可选)
*/
private final String panDomain;
/**
* 匹配正则表达式(可选,用于从分享链接中识别和提取信息)
* 如果提供,则支持通过 fromShareUrl 方法自动识别自定义解析器
* 正则表达式必须包含命名捕获组 KEY用于提取分享键
* 可选包含命名捕获组 PWD用于提取分享密码
*/
private final Pattern matchPattern;
private CustomParserConfig(Builder builder) {
this.type = builder.type;
this.displayName = builder.displayName;
this.toolClass = builder.toolClass;
this.standardUrlTemplate = builder.standardUrlTemplate;
this.panDomain = builder.panDomain;
this.matchPattern = builder.matchPattern;
}
public String getType() {
return type;
}
public String getDisplayName() {
return displayName;
}
public Class<? extends IPanTool> getToolClass() {
return toolClass;
}
public String getStandardUrlTemplate() {
return standardUrlTemplate;
}
public String getPanDomain() {
return panDomain;
}
public Pattern getMatchPattern() {
return matchPattern;
}
/**
* 检查是否支持从分享链接自动识别
* @return true表示支持false表示不支持
*/
public boolean supportsFromShareUrl() {
return matchPattern != null;
}
public static Builder builder() {
return new Builder();
}
/**
* 建造者类
*/
public static class Builder {
private String type;
private String displayName;
private Class<? extends IPanTool> toolClass;
private String standardUrlTemplate;
private String panDomain;
private Pattern matchPattern;
/**
* 设置解析器类型标识(必填,唯一)
* @param type 类型标识(建议使用小写英文)
*/
public Builder type(String type) {
this.type = type;
return this;
}
/**
* 设置网盘显示名称(必填)
* @param displayName 显示名称
*/
public Builder displayName(String displayName) {
this.displayName = displayName;
return this;
}
/**
* 设置解析工具实现类(必填)
* @param toolClass 工具类(必须实现 IPanTool 接口)
*/
public Builder toolClass(Class<? extends IPanTool> toolClass) {
this.toolClass = toolClass;
return this;
}
/**
* 设置标准URL模板可选
* @param standardUrlTemplate URL模板
*/
public Builder standardUrlTemplate(String standardUrlTemplate) {
this.standardUrlTemplate = standardUrlTemplate;
return this;
}
/**
* 设置网盘域名(可选)
* @param panDomain 网盘域名
*/
public Builder panDomain(String panDomain) {
this.panDomain = panDomain;
return this;
}
/**
* 设置匹配正则表达式(可选)
* @param pattern 正则表达式Pattern对象
*/
public Builder matchPattern(Pattern pattern) {
this.matchPattern = pattern;
return this;
}
/**
* 设置匹配正则表达式(可选)
* @param regex 正则表达式字符串
*/
public Builder matchPattern(String regex) {
if (regex != null && !regex.trim().isEmpty()) {
this.matchPattern = Pattern.compile(regex);
}
return this;
}
/**
* 构建配置对象
* @return CustomParserConfig
*/
public CustomParserConfig build() {
if (type == null || type.trim().isEmpty()) {
throw new IllegalArgumentException("type不能为空");
}
if (displayName == null || displayName.trim().isEmpty()) {
throw new IllegalArgumentException("displayName不能为空");
}
if (toolClass == null) {
throw new IllegalArgumentException("toolClass不能为空");
}
// 验证toolClass是否实现了IPanTool接口
if (!IPanTool.class.isAssignableFrom(toolClass)) {
throw new IllegalArgumentException("toolClass必须实现IPanTool接口");
}
// 验证toolClass是否有ShareLinkInfo单参构造器
try {
toolClass.getDeclaredConstructor(ShareLinkInfo.class);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("toolClass必须有ShareLinkInfo单参构造器", e);
}
// 验证正则表达式(如果提供)
if (matchPattern != null) {
// 检查正则表达式是否包含KEY命名捕获组
String patternStr = matchPattern.pattern();
if (!patternStr.contains("(?<KEY>")) {
throw new IllegalArgumentException("正则表达式必须包含命名捕获组 KEY用于提取分享键");
}
}
return new CustomParserConfig(this);
}
}
@Override
public String toString() {
return "CustomParserConfig{" +
"type='" + type + '\'' +
", displayName='" + displayName + '\'' +
", toolClass=" + toolClass.getName() +
", standardUrlTemplate='" + standardUrlTemplate + '\'' +
", panDomain='" + panDomain + '\'' +
", matchPattern=" + (matchPattern != null ? matchPattern.pattern() : "null") +
'}';
}
}

View File

@@ -0,0 +1,120 @@
package cn.qaiu.parser;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 自定义解析器注册中心
* 用户可以通过此类注册自己的解析器实现
*
* @author <a href="https://qaiu.top">QAIU</a>
* Create at 2025/10/17
*/
public class CustomParserRegistry {
/**
* 存储自定义解析器配置的Mapkey为类型标识value为配置对象
*/
private static final Map<String, CustomParserConfig> CUSTOM_PARSERS = new ConcurrentHashMap<>();
/**
* 注册自定义解析器
*
* @param config 解析器配置
* @throws IllegalArgumentException 如果type已存在或与内置解析器冲突
*/
public static void register(CustomParserConfig config) {
if (config == null) {
throw new IllegalArgumentException("config不能为空");
}
String type = config.getType().toLowerCase();
// 检查是否与内置枚举冲突
try {
PanDomainTemplate.valueOf(type.toUpperCase());
throw new IllegalArgumentException(
"类型标识 '" + type + "' 与内置解析器冲突,请使用其他标识"
);
} catch (IllegalArgumentException e) {
// 如果valueOf抛出异常说明不存在该枚举这是正常情况
if (e.getMessage().startsWith("类型标识")) {
throw e; // 重新抛出我们自己的异常
}
}
// 检查是否已注册
if (CUSTOM_PARSERS.containsKey(type)) {
throw new IllegalArgumentException(
"类型标识 '" + type + "' 已被注册,请先注销或使用其他标识"
);
}
CUSTOM_PARSERS.put(type, config);
}
/**
* 注销自定义解析器
*
* @param type 解析器类型标识
* @return 是否注销成功
*/
public static boolean unregister(String type) {
if (type == null || type.trim().isEmpty()) {
return false;
}
return CUSTOM_PARSERS.remove(type.toLowerCase()) != null;
}
/**
* 根据类型获取自定义解析器配置
*
* @param type 解析器类型标识
* @return 解析器配置如果不存在则返回null
*/
public static CustomParserConfig get(String type) {
if (type == null || type.trim().isEmpty()) {
return null;
}
return CUSTOM_PARSERS.get(type.toLowerCase());
}
/**
* 检查指定类型的解析器是否已注册
*
* @param type 解析器类型标识
* @return 是否已注册
*/
public static boolean contains(String type) {
if (type == null || type.trim().isEmpty()) {
return false;
}
return CUSTOM_PARSERS.containsKey(type.toLowerCase());
}
/**
* 清空所有自定义解析器
*/
public static void clear() {
CUSTOM_PARSERS.clear();
}
/**
* 获取已注册的自定义解析器数量
*
* @return 数量
*/
public static int size() {
return CUSTOM_PARSERS.size();
}
/**
* 获取所有已注册的自定义解析器配置(只读视图)
*
* @return 不可修改的Map
*/
public static Map<String, CustomParserConfig> getAll() {
return Map.copyOf(CUSTOM_PARSERS);
}
}

View File

@@ -16,19 +16,38 @@ import static cn.qaiu.parser.PanDomainTemplate.PWD;
* 通过这种方式,应用程序可以更容易地处理和识别不同网盘服务的分享链接。
*
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2024/9/15 14:10
* Create at 2024/9/15 14:10
*/
public class ParserCreate {
private final PanDomainTemplate panDomainTemplate;
private final ShareLinkInfo shareLinkInfo;
// 自定义解析器配置(与 panDomainTemplate 二选一)
private final CustomParserConfig customParserConfig;
private String standardUrl;
// 标识是否为自定义解析器
private final boolean isCustomParser;
public ParserCreate(PanDomainTemplate panDomainTemplate, ShareLinkInfo shareLinkInfo) {
this.panDomainTemplate = panDomainTemplate;
this.shareLinkInfo = shareLinkInfo;
this.customParserConfig = null;
this.isCustomParser = false;
this.standardUrl = panDomainTemplate.getStandardUrlTemplate();
}
/**
* 自定义解析器专用构造器
*/
private ParserCreate(CustomParserConfig customParserConfig, ShareLinkInfo shareLinkInfo) {
this.customParserConfig = customParserConfig;
this.shareLinkInfo = shareLinkInfo;
this.panDomainTemplate = null;
this.isCustomParser = true;
this.standardUrl = customParserConfig.getStandardUrlTemplate();
}
// 解析并规范化分享链接
@@ -36,6 +55,60 @@ public class ParserCreate {
if (shareLinkInfo == null) {
throw new IllegalArgumentException("ShareLinkInfo not init");
}
// 自定义解析器处理
if (isCustomParser) {
if (!customParserConfig.supportsFromShareUrl()) {
throw new UnsupportedOperationException(
"自定义解析器不支持 normalizeShareLink 方法,请使用 shareKey 方法设置分享键");
}
// 使用自定义解析器的正则表达式进行匹配
String shareUrl = shareLinkInfo.getShareUrl();
if (StringUtils.isEmpty(shareUrl)) {
throw new IllegalArgumentException("ShareLinkInfo shareUrl is empty");
}
java.util.regex.Matcher matcher = customParserConfig.getMatchPattern().matcher(shareUrl);
if (matcher.matches()) {
// 提取分享键
try {
String shareKey = matcher.group("KEY");
if (shareKey != null) {
shareLinkInfo.setShareKey(shareKey);
}
} catch (Exception ignored) {}
// 提取密码
try {
String pwd = matcher.group("PWD");
if (StringUtils.isNotEmpty(pwd)) {
shareLinkInfo.setSharePassword(pwd);
}
} catch (Exception ignored) {}
// 设置标准URL
if (customParserConfig.getStandardUrlTemplate() != null) {
String standardUrl = customParserConfig.getStandardUrlTemplate()
.replace("{shareKey}", shareLinkInfo.getShareKey() != null ? shareLinkInfo.getShareKey() : "");
// 处理密码替换
if (shareLinkInfo.getSharePassword() != null && !shareLinkInfo.getSharePassword().isEmpty()) {
standardUrl = standardUrl.replace("{pwd}", shareLinkInfo.getSharePassword());
} else {
// 如果密码为空,移除包含 {pwd} 的部分
standardUrl = standardUrl.replaceAll("\\?pwd=\\{pwd\\}", "").replaceAll("&pwd=\\{pwd\\}", "");
}
shareLinkInfo.setStandardUrl(standardUrl);
}
return this;
}
throw new IllegalArgumentException("Invalid share URL for " + customParserConfig.getDisplayName());
}
// 内置解析器处理
// 匹配并提取shareKey
String shareUrl = shareLinkInfo.getShareUrl();
if (StringUtils.isEmpty(shareUrl)) {
@@ -72,6 +145,20 @@ public class ParserCreate {
if (shareLinkInfo == null || StringUtils.isEmpty(shareLinkInfo.getType())) {
throw new IllegalArgumentException("ShareLinkInfo not init or type is empty");
}
// 自定义解析器处理
if (isCustomParser) {
try {
return this.customParserConfig.getToolClass()
.getDeclaredConstructor(ShareLinkInfo.class)
.newInstance(shareLinkInfo);
} catch (Exception e) {
throw new RuntimeException("无法创建自定义工具实例: " +
customParserConfig.getToolClass().getName(), e);
}
}
// 内置解析器处理
if (StringUtils.isEmpty(shareLinkInfo.getShareKey())) {
this.normalizeShareLink();
}
@@ -86,6 +173,20 @@ public class ParserCreate {
// set share key
public ParserCreate shareKey(String shareKey) {
// 自定义解析器处理
if (isCustomParser) {
shareLinkInfo.setShareKey(shareKey);
if (standardUrl != null) {
standardUrl = standardUrl.replace("{shareKey}", shareKey);
shareLinkInfo.setStandardUrl(standardUrl);
}
if (StringUtils.isEmpty(shareLinkInfo.getShareUrl())) {
shareLinkInfo.setShareUrl(standardUrl != null ? standardUrl : shareKey);
}
return this;
}
// 内置解析器处理
if (panDomainTemplate.ordinal() >= PanDomainTemplate.CE.ordinal()) {
// 处理Cloudreve(ce)类: pan.huang1111.cn_s_wDz5TK _ -> /
String[] s = shareKey.split("_");
@@ -112,6 +213,9 @@ public class ParserCreate {
}
public String getStandardUrlTemplate() {
if (isCustomParser) {
return this.customParserConfig.getStandardUrlTemplate();
}
return this.panDomainTemplate.getStandardUrlTemplate();
}
@@ -131,8 +235,56 @@ public class ParserCreate {
return this;
}
// 根据分享链接获取PanDomainTemplate实例
// 根据分享链接获取PanDomainTemplate实例(优先匹配自定义解析器)
public synchronized static ParserCreate fromShareUrl(String shareUrl) {
// 优先查找支持正则匹配的自定义解析器
for (CustomParserConfig customConfig : CustomParserRegistry.getAll().values()) {
if (customConfig.supportsFromShareUrl()) {
java.util.regex.Matcher matcher = customConfig.getMatchPattern().matcher(shareUrl);
if (matcher.matches()) {
ShareLinkInfo shareLinkInfo = ShareLinkInfo.newBuilder()
.type(customConfig.getType())
.panName(customConfig.getDisplayName())
.shareUrl(shareUrl)
.build();
// 提取分享键和密码
try {
String shareKey = matcher.group("KEY");
if (shareKey != null) {
shareLinkInfo.setShareKey(shareKey);
}
} catch (Exception ignored) {}
try {
String password = matcher.group("PWD");
if (password != null) {
shareLinkInfo.setSharePassword(password);
}
} catch (Exception ignored) {}
// 设置标准URL如果有模板
if (customConfig.getStandardUrlTemplate() != null) {
String standardUrl = customConfig.getStandardUrlTemplate()
.replace("{shareKey}", shareLinkInfo.getShareKey() != null ? shareLinkInfo.getShareKey() : "");
// 处理密码替换
if (shareLinkInfo.getSharePassword() != null && !shareLinkInfo.getSharePassword().isEmpty()) {
standardUrl = standardUrl.replace("{pwd}", shareLinkInfo.getSharePassword());
} else {
// 如果密码为空,移除包含 {pwd} 的部分
standardUrl = standardUrl.replaceAll("\\?pwd=\\{pwd\\}", "").replaceAll("&pwd=\\{pwd\\}", "");
}
shareLinkInfo.setStandardUrl(standardUrl);
}
return new ParserCreate(customConfig, shareLinkInfo);
}
}
}
// 查找内置解析器
for (PanDomainTemplate panDomainTemplate : PanDomainTemplate.values()) {
if (panDomainTemplate.getPattern().matcher(shareUrl).matches()) {
ShareLinkInfo shareLinkInfo = ShareLinkInfo.newBuilder()
@@ -149,25 +301,47 @@ public class ParserCreate {
throw new IllegalArgumentException("Unsupported share URL");
}
// 根据type获取枚举实例
// 根据type获取枚举实例(优先查找自定义解析器)
public synchronized static ParserCreate fromType(String type) {
if (type == null || type.trim().isEmpty()) {
throw new IllegalArgumentException("type不能为空");
}
String normalizedType = type.toLowerCase();
// 优先查找自定义解析器
CustomParserConfig customConfig = CustomParserRegistry.get(normalizedType);
if (customConfig != null) {
ShareLinkInfo shareLinkInfo = ShareLinkInfo.newBuilder()
.type(normalizedType)
.panName(customConfig.getDisplayName())
.build();
return new ParserCreate(customConfig, shareLinkInfo);
}
// 查找内置解析器
try {
PanDomainTemplate panDomainTemplate = Enum.valueOf(PanDomainTemplate.class, type.toUpperCase());
ShareLinkInfo shareLinkInfo = ShareLinkInfo.newBuilder()
.type(type.toLowerCase()).build();
shareLinkInfo.setPanName(panDomainTemplate.getDisplayName());
.type(normalizedType)
.panName(panDomainTemplate.getDisplayName())
.build();
return new ParserCreate(panDomainTemplate, shareLinkInfo);
} catch (IllegalArgumentException ignore) {
// 如果没有找到对应的枚举实例,抛出异常
throw new IllegalArgumentException("No enum constant for type name: " + type);
// 如果没有找到对应的解析器,抛出异常
throw new IllegalArgumentException("未找到类型为 '" + type + "' 的解析器," +
"请检查是否已注册自定义解析器或使用正确的内置类型");
}
}
// 生成parser短链path(不包含domainName)
public String genPathSuffix() {
String path;
if (panDomainTemplate.ordinal() >= PanDomainTemplate.CE.ordinal()) {
// 自定义解析器处理
if (isCustomParser) {
path = this.shareLinkInfo.getType() + "/" + this.shareLinkInfo.getShareKey();
} else if (panDomainTemplate.ordinal() >= PanDomainTemplate.CE.ordinal()) {
// 处理Cloudreve(ce)类: pan.huang1111.cn_s_wDz5TK _ -> /
path = this.shareLinkInfo.getType() + "/"
+ this.shareLinkInfo.getShareUrl()
@@ -175,8 +349,33 @@ public class ParserCreate {
} else {
path = this.shareLinkInfo.getType() + "/" + this.shareLinkInfo.getShareKey();
}
String sharePassword = this.shareLinkInfo.getSharePassword();
return path + (StringUtils.isBlank(sharePassword) ? "" : ("@" + sharePassword));
}
/**
* 判断当前是否为自定义解析器
* @return true表示自定义解析器false表示内置解析器
*/
public boolean isCustomParser() {
return isCustomParser;
}
/**
* 获取自定义解析器配置仅当isCustomParser为true时有效
* @return 自定义解析器配置如果不是自定义解析器则返回null
*/
public CustomParserConfig getCustomParserConfig() {
return customParserConfig;
}
/**
* 获取内置解析器模板仅当isCustomParser为false时有效
* @return 内置解析器模板如果是自定义解析器则返回null
*/
public PanDomainTemplate getPanDomainTemplate() {
return panDomainTemplate;
}
}

View File

@@ -10,7 +10,7 @@ import org.apache.commons.lang3.StringUtils;
* 奶牛快传解析工具
*
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2023/4/21 21:19
* Create at 2023/4/21 21:19
*/
public class CowTool extends PanBase {

View File

@@ -11,12 +11,14 @@ import io.vertx.core.Promise;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.client.WebClientSession;
import org.apache.commons.lang3.RegExUtils;
import org.openjdk.nashorn.api.scripting.ScriptObjectMirror;
import javax.script.ScriptException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -28,10 +30,9 @@ import java.util.regex.Pattern;
public class LzTool extends PanBase {
public static final String SHARE_URL_PREFIX = "https://wwww.lanzoum.com";
MultiMap headers0 = HeaderUtils.parseHeaders("""
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cache-Control: max-age=0
Cookie: codelen=1; pc_ad1=1
@@ -48,6 +49,7 @@ public class LzTool extends PanBase {
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36 Edg/140.0.0.0
""");
public LzTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
@@ -57,42 +59,49 @@ public class LzTool extends PanBase {
String pwd = shareLinkInfo.getSharePassword();
WebClient client = clientNoRedirects;
client.getAbs(sUrl).putHeaders(headers0).send().onSuccess(res -> {
String html = res.bodyAsString();
// 匹配iframe
Pattern compile = Pattern.compile("src=\"(/fn\\?[a-zA-Z\\d_+/=]{16,})\"");
Matcher matcher = compile.matcher(html);
// 没有Iframe说明是加密分享, 匹配sign通过密码请求下载页面
if (!matcher.find()) {
try {
String jsText = getJsByPwd(pwd, html, "document.getElementById('rpt')");
ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText, "down_p");
getDownURL(sUrl, client, scriptObjectMirror);
} catch (Exception e) {
fail(e, "js引擎执行失败");
}
} else {
// 没有密码
String iframePath = matcher.group(1);
client.getAbs(SHARE_URL_PREFIX + iframePath).send().onSuccess(res2 -> {
String html2 = res2.bodyAsString();
// 去TMD正则
// Matcher matcher2 = Pattern.compile("'sign'\s*:\s*'(\\w+)'").matcher(html2);
String jsText = getJsText(html2);
if (jsText == null) {
fail(SHARE_URL_PREFIX + iframePath + " -> " + sUrl + ": js脚本匹配失败, 可能分享已失效");
return;
}
client.getAbs(sUrl)
.putHeaders(headers0)
.send().onSuccess(res -> {
String html = asText(res);
try {
ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText, null);
getDownURL(sUrl, client, scriptObjectMirror);
} catch (ScriptException | NoSuchMethodException e) {
fail(e, "js引擎执行失败");
setFileInfo(html, shareLinkInfo);
} catch (Exception e) {
e.printStackTrace();
}
}).onFailure(handleFail(SHARE_URL_PREFIX));
}
}).onFailure(handleFail(sUrl));
// 匹配iframe
Pattern compile = Pattern.compile("src=\"(/fn\\?[a-zA-Z\\d_+/=]{16,})\"");
Matcher matcher = compile.matcher(html);
// 没有Iframe说明是加密分享, 匹配sign通过密码请求下载页面
if (!matcher.find()) {
try {
String jsText = getJsByPwd(pwd, html, "document.getElementById('rpt')");
ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText, "down_p");
getDownURL(sUrl, client, scriptObjectMirror);
} catch (Exception e) {
fail(e, "js引擎执行失败");
}
} else {
// 没有密码
String iframePath = matcher.group(1);
client.getAbs(SHARE_URL_PREFIX + iframePath).send().onSuccess(res2 -> {
String html2 = res2.bodyAsString();
// 去TMD正则
// Matcher matcher2 = Pattern.compile("'sign'\s*:\s*'(\\w+)'").matcher(html2);
String jsText = getJsText(html2);
if (jsText == null) {
fail(SHARE_URL_PREFIX + iframePath + " -> " + sUrl + ": js脚本匹配失败, 可能分享已失效");
return;
}
try {
ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText, null);
getDownURL(sUrl, client, scriptObjectMirror);
} catch (ScriptException | NoSuchMethodException e) {
fail(e, "js引擎执行失败");
}
}).onFailure(handleFail(SHARE_URL_PREFIX));
}
}).onFailure(handleFail(sUrl));
return promise.future();
}
@@ -163,7 +172,10 @@ public class LzTool extends PanBase {
return;
}
// 文件名
((FileInfo)shareLinkInfo.getOtherParam().get("fileInfo")).setFileName(name);
if (urlJson.containsKey("inf") && urlJson.getMap().get("inf") instanceof Character) {
((FileInfo)shareLinkInfo.getOtherParam().get("fileInfo")).setFileName(name);
}
String downUrl = urlJson.getString("dom") + "/file/" + urlJson.getString("url");
headers.remove("Referer");
WebClientSession webClientSession = WebClientSession.create(client);
@@ -186,17 +198,16 @@ public class LzTool extends PanBase {
webClientSession.cookieStore().put(nettyCookie);
webClientSession.getAbs(downUrl).putHeaders(headers).send()
.onSuccess(res4 -> {
String location0 = res4.headers().get("Location");
if (location0 == null) {
fail(downUrl + " -> 直链获取失败, 可能分享已失效");
} else {
promise.complete(location0);
}
}).onFailure(handleFail(downUrl));
String location0 = res4.headers().get("Location");
if (location0 == null) {
fail(downUrl + " -> 直链获取失败, 可能分享已失效");
} else {
setDateAndComplate(location0);
}
}).onFailure(handleFail(downUrl));
return;
}
promise.complete(location);
setDateAndComplate(location);
})
.onFailure(handleFail(downUrl));
} catch (Exception e) {
@@ -205,6 +216,17 @@ public class LzTool extends PanBase {
}).onFailure(handleFail(url));
}
private void setDateAndComplate(String location0) {
// 分享时间 提取url中的时间戳格式lanzoui.com/abc/abc/yyyy/mm/dd/
String regex = "(\\d{4}/\\d{1,2}/\\d{1,2})";
Matcher matcher = Pattern.compile(regex).matcher(location0);
if (matcher.find()) {
String dateStr = matcher.group().replace("/", "-");
((FileInfo)shareLinkInfo.getOtherParam().get("fileInfo")).setCreateTime(dateStr);
}
promise.complete(location0);
}
private static MultiMap getHeaders(String key) {
MultiMap headers = MultiMap.caseInsensitiveMultiMap();
var userAgent2 = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, " +
@@ -286,4 +308,36 @@ public class LzTool extends PanBase {
});
return promise.future();
}
void setFileInfo(String html, ShareLinkInfo shareLinkInfo) {
// 写入 fileInfo
FileInfo fileInfo = new FileInfo();
shareLinkInfo.getOtherParam().put("fileInfo", fileInfo);
try {
// 提取文件名
String fileName = CommonUtils.extract(html, Pattern.compile("padding: 56px 0px 20px 0px;\">(.*?)<|filenajax\">(.*?)<"));
String sizeStr = CommonUtils.extract(html, Pattern.compile(">文件大小:</span>(.*?)<br>|\"n_filesize\">大小:(.*?)</div>"));
String createBy = CommonUtils.extract(html, Pattern.compile(">分享用户:</span><font>(.*?)</font>|获取<span>(.*?)</span>的文件|\"user-name\">(.*?)</"));
String description = CommonUtils.extract(html, Pattern.compile("(?s)文件描述:</span><br>(.*?)</td>|class=\"n_box_des\">(.*?)</div>"));
// String icon = CommonUtils.extract(html, Pattern.compile("class=\"n_file_icon\" src=\"(.*?)\""));
String fileId = CommonUtils.extract(html, Pattern.compile("\\?f=(.*?)&|fid = (.*?);"));
String createTime = CommonUtils.extract(html, Pattern.compile(">上传时间:</span>(.*?)<"));
try {
long bytes = FileSizeConverter.convertToBytes(sizeStr);
fileInfo.setFileName(fileName)
.setSize(bytes)
.setSizeStr(FileSizeConverter.convertToReadableSize(bytes))
.setCreateBy(createBy)
.setPanType(shareLinkInfo.getType())
.setDescription(description)
.setFileType("file")
.setFileId(fileId)
.setCreateTime(createTime);
} catch (Exception e) {
log.warn("文件信息解析异常", e);
}
} catch (Exception e) {
log.warn("文件信息匹配异常", e);
}
}
}

View File

@@ -4,6 +4,8 @@ import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class CommonUtils {
@@ -44,4 +46,29 @@ public class CommonUtils {
return map;
}
/**
* 提取第一个匹配的非空捕捉组
* @param matcher 已创建的 Matcher
* @return 第一个非空 group或 "" 如果没有
*/
public static String firstNonEmptyGroup(Matcher matcher) {
if (!matcher.find()) {
return "";
}
for (int i = 1; i <= matcher.groupCount(); i++) {
String g = matcher.group(i);
if (g != null && !g.trim().isEmpty()) {
return g.trim();
}
}
return "";
}
/**
* 直接传 html 和 regex返回第一个非空捕捉组
*/
public static String extract(String input, Pattern pattern) {
Matcher matcher = pattern.matcher(input);
return firstNonEmptyGroup(matcher);
}
}

View File

@@ -17,7 +17,7 @@ import static cn.qaiu.util.AESUtils.encrypt;
* 执行Js脚本
*
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2023/7/29 17:35
* Create at 2023/7/29 17:35
*/
public class JsExecUtils {
private static final Invocable inv;

View File

@@ -2,7 +2,7 @@ package cn.qaiu.util;
/**
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2023/7/16 1:53
* Create at 2023/7/16 1:53
*/
public class PanExceptionUtils {

View File

@@ -4,7 +4,7 @@ import java.security.SecureRandom;
/**
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2024/5/13 4:10
* Create at 2024/5/13 4:10
*/
public class UUIDUtil {