From f886f7e36614e4962663a5884e1d80b576c2bc15 Mon Sep 17 00:00:00 2001 From: qaiu <736226400@qq.com> Date: Sun, 15 Sep 2024 06:53:11 +0800 Subject: [PATCH] =?UTF-8?q?1.=20=E6=B7=BB=E5=8A=A0=E7=BC=93=E5=AD=98=202.?= =?UTF-8?q?=20=E4=BC=98=E5=8C=96=E8=A7=A3=E6=9E=90=E6=9E=B6=E6=9E=84=203.?= =?UTF-8?q?=20=E4=BC=98=E5=8C=96=E6=A0=B8=E5=BF=83=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 21 +- .../handlerfactory/RouterHandlerFactory.java | 3 + .../cn/qaiu/vx/core/util/ConfigConstant.java | 1 + .../java/cn/qaiu/entity/ShareLinkInfo.java | 114 +++++++++ .../main/java/cn/qaiu/parser/IPanTool.java | 65 ++--- .../src/main/java/cn/qaiu/parser/PanBase.java | 20 +- .../cn/qaiu/parser/PanDomainTemplate.java | 225 ++++++++++++++++++ .../main/java/cn/qaiu/parser/impl/CeTool.java | 44 ++-- .../java/cn/qaiu/parser/impl/CowTool.java | 12 +- .../main/java/cn/qaiu/parser/impl/EcTool.java | 14 +- .../main/java/cn/qaiu/parser/impl/FcTool.java | 12 +- .../main/java/cn/qaiu/parser/impl/FjTool.java | 18 +- .../main/java/cn/qaiu/parser/impl/IzTool.java | 8 +- .../main/java/cn/qaiu/parser/impl/LeTool.java | 15 +- .../main/java/cn/qaiu/parser/impl/LzTool.java | 54 +++-- .../main/java/cn/qaiu/parser/impl/QQTool.java | 59 ++--- .../main/java/cn/qaiu/parser/impl/QkTool.java | 16 +- .../main/java/cn/qaiu/parser/impl/UcTool.java | 82 ------- .../main/java/cn/qaiu/parser/impl/WsTool.java | 47 ++-- .../main/java/cn/qaiu/parser/impl/YeTool.java | 8 +- .../test/java/cn/qaiu/parser/FCURLParser.java | 37 +++ .../cn/qaiu/parser/PanDomainTemplateTest.java | 73 ++++++ pom.xml | 2 +- web-front/package-lock.json | 6 +- .../src/main/java/cn/qaiu/lz/AppMain.java | 10 +- .../lz/common/cache/CacheConfigLoader.java | 37 +++ .../cn/qaiu/lz/common/cache/CacheManager.java | 52 ++++ .../cn/qaiu/lz/common/util/URLParamUtil.java | 76 ++++++ .../java/cn/qaiu/lz/web/http/ServerApi.java | 57 ++--- .../cn/qaiu/lz/web/model/CacheLinkInfo.java | 56 +++++ .../lz/web/model/CacheLinkInfoConverter.java | 23 ++ .../cn/qaiu/lz/web/service/CacheService.java | 17 ++ .../cn/qaiu/lz/web/service/DbService.java | 1 + .../qaiu/lz/web/service/JdkProxyFactory.java | 18 -- .../lz/web/service/impl/CacheServiceImpl.java | 65 +++++ web-service/src/main/resources/app-dev.yml | 20 ++ web-service/src/main/resources/app.yml | 2 +- .../src/main/resources/http-tools/pan-cx.http | 10 + .../src/main/resources/http-tools/pan-fj.http | 7 + .../src/main/resources/http-tools/test.http | 58 ++--- 40 files changed, 1056 insertions(+), 409 deletions(-) create mode 100644 parser/src/main/java/cn/qaiu/entity/ShareLinkInfo.java create mode 100644 parser/src/main/java/cn/qaiu/parser/PanDomainTemplate.java delete mode 100644 parser/src/main/java/cn/qaiu/parser/impl/UcTool.java create mode 100644 parser/src/test/java/cn/qaiu/parser/FCURLParser.java create mode 100644 parser/src/test/java/cn/qaiu/parser/PanDomainTemplateTest.java create mode 100644 web-service/src/main/java/cn/qaiu/lz/common/cache/CacheConfigLoader.java create mode 100644 web-service/src/main/java/cn/qaiu/lz/common/cache/CacheManager.java create mode 100644 web-service/src/main/java/cn/qaiu/lz/common/util/URLParamUtil.java create mode 100644 web-service/src/main/java/cn/qaiu/lz/web/model/CacheLinkInfo.java create mode 100644 web-service/src/main/java/cn/qaiu/lz/web/model/CacheLinkInfoConverter.java create mode 100644 web-service/src/main/java/cn/qaiu/lz/web/service/CacheService.java delete mode 100644 web-service/src/main/java/cn/qaiu/lz/web/service/JdkProxyFactory.java create mode 100644 web-service/src/main/java/cn/qaiu/lz/web/service/impl/CacheServiceImpl.java create mode 100644 web-service/src/main/resources/http-tools/pan-cx.http diff --git a/README.md b/README.md index c0e4406..f44929d 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,9 @@ main分支依赖JDK17, 提供了JDK11分支[main-jdk11](https://github.com/qaiu/ - [Cloudreve自建网盘 (ce)](https://github.com/cloudreve/Cloudreve) - [ ] 登录, 上传, 下载, 分享 - [X] 直链解析 +- [超星网盘-教育用户专属推荐非常稳定! (cx)](https://passport2.chaoxing.com/login?newversion=true&refer=https%3A%2F%2Fpan-yz.chaoxing.com%2F) + - [ ] 登录, 上传, 下载, 分享 + - [ ] 直链解析 - [QQ邮箱 (qq) 暂不可用-存在cookie问题](https://wx.mail.qq.com/) - [ ] 登录, 上传, 下载, 分享 - [X] 直链解析(用户无法直接使用直链) @@ -71,7 +74,7 @@ your_host指的是您的域名或者IP,实际使用时替换为实际域名或 - 移动云云空间,小飞机网盘的加密分享的密码可以忽略 - 移动云空间分享key取分享链接中的data参数,比如`&data=xxx`的参数就是xxx -规则示例: +API规则: ``` 1. 解析并自动302跳转 : @@ -80,20 +83,6 @@ your_host指的是您的域名或者IP,实际使用时替换为实际域名或 2. 获取解析后的直链--JSON格式 http://your_host/json/parser?url=分享链接(&pwd=xxx) http://your_host/json/网盘标识/分享key(@分享密码) -3. 需要特殊处理的网盘分享: - 1. 移动云云空间(ec)使用parser?url= 解析时因为分享链接比较特殊(链接带有参数且含有#符号)所以要么对#进行转义%23要么直接去掉# 或者URL直接是主机名+'/'跟一个data参数 - 比如 http://your_host/parser?url=https://www.ecpan.cn/web//yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=81027a5c99af5b11ca004966c945cce6W9Bf2&isShare=1 - http://your_host/parser?url=https://www.ecpan.cn/web/%23/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=81027a5c99af5b11ca004966c945cce6W9Bf2&isShare=1 - http://your_host/parser?url=https://www.ecpan.cn/&data=81027a5c99af5b11ca004966c945cce6W9Bf2&isShare=1 - - 2. Cloudreve自建网盘解析规则: - 1. 标志短链: 根据网盘使用https和http选择 http://your_host/ce/https_网盘域名_s_wDz5TK 或 http://your_host/ce/http_网盘域名_s_wDz5TK - 网盘域名指的是Cloudreve搭建网盘的主域名比如pan.huang1111.cn,如果存在子路径需要将/替换为_,是否存在子路径看分享链接格式是否是://网盘域名/子路径/s/xxx,一般不存在子路径:网盘域名/s/xxx, - 比如: http://127.0.0.1:6400/ce/https_pan.huang1111.cn_s_wDz5TK - 2. parser接口 -> http://your_host/parser?url=分享链接(&pwd=xxx) - 比如: http://127.0.0.1:6400/parser?url=https://pan.huang1111.cn/s/wDz5TK - - ``` json返回数据格式示例: @@ -180,7 +169,7 @@ mvn package > 注意: netdisk-fast-download.service中的ExecStart的路径改为实际路径 ```shell cd ~ -wget -O netdisk-fast-download.zip https://github.com/qaiu/netdisk-fast-download/releases/download/0.1.7-release-fixed2/netdisk-fast-download-bin-fixed2.zip +wget -O netdisk-fast-download.zip https://github.com/qaiu/netdisk-fast-download/releases/download/0.1.8-release-fixed2/netdisk-fast-download-bin-fixed2.zip unzip netdisk-fast-download-bin.zip cd netdisk-fast-download bash service-install.sh diff --git a/core/src/main/java/cn/qaiu/vx/core/handlerfactory/RouterHandlerFactory.java b/core/src/main/java/cn/qaiu/vx/core/handlerfactory/RouterHandlerFactory.java index 1ae027c..5bc5de1 100644 --- a/core/src/main/java/cn/qaiu/vx/core/handlerfactory/RouterHandlerFactory.java +++ b/core/src/main/java/cn/qaiu/vx/core/handlerfactory/RouterHandlerFactory.java @@ -32,12 +32,14 @@ import org.slf4j.LoggerFactory; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.time.LocalDateTime; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; import static cn.qaiu.vx.core.util.ConfigConstant.ROUTE_TIME_OUT; import static io.vertx.core.http.HttpHeaders.*; +import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME; /** * 路由映射, 参数绑定 @@ -87,6 +89,7 @@ public class RouterHandlerFactory implements BaseHttpApi { LOGGER.debug("The HTTP service request address information ===>path:{}, uri:{}, method:{}", ctx.request().path(), ctx.request().absoluteURI(), ctx.request().method()); ctx.response().headers().add(ACCESS_CONTROL_ALLOW_ORIGIN, "*"); + ctx.response().headers().add(DATE, LocalDateTime.now().format(ISO_LOCAL_DATE_TIME)); ctx.response().headers().add(ACCESS_CONTROL_ALLOW_METHODS, "POST, GET, OPTIONS, PUT, DELETE, HEAD"); ctx.response().headers().add(ACCESS_CONTROL_ALLOW_HEADERS, "X-PINGOTHER, Origin,Content-Type, Accept, " + "X-Requested-With, Dev, Authorization, Version, Token"); diff --git a/core/src/main/java/cn/qaiu/vx/core/util/ConfigConstant.java b/core/src/main/java/cn/qaiu/vx/core/util/ConfigConstant.java index 32465b6..4b60743 100644 --- a/core/src/main/java/cn/qaiu/vx/core/util/ConfigConstant.java +++ b/core/src/main/java/cn/qaiu/vx/core/util/ConfigConstant.java @@ -6,6 +6,7 @@ public interface ConfigConstant { String EVENT_LOOP_POOL_SIZE = "eventLoopPoolSize"; String LOCAL = "local"; String SERVER = "server"; + String CACHE = "cache"; String GLOBAL_CONFIG = "globalConfig"; String CUSTOM_CONFIG = "customConfig"; String ASYNC_SERVICE_INSTANCES = "asyncServiceInstances"; diff --git a/parser/src/main/java/cn/qaiu/entity/ShareLinkInfo.java b/parser/src/main/java/cn/qaiu/entity/ShareLinkInfo.java new file mode 100644 index 0000000..7601475 --- /dev/null +++ b/parser/src/main/java/cn/qaiu/entity/ShareLinkInfo.java @@ -0,0 +1,114 @@ +package cn.qaiu.entity; + +public class ShareLinkInfo { + + private String shareKey; // 分享键 + private String type; // 分享类型 + private String sharePassword; // 分享密码(如果存在) + private String shareUrl; // 原始分享链接 + private String standardUrl; // 规范化的标准链接 + + private ShareLinkInfo(Builder builder) { + this.shareKey = builder.shareKey; + this.type = builder.type; + this.sharePassword = builder.sharePassword; + this.shareUrl = builder.shareUrl; + this.standardUrl = builder.standardUrl; + } + + // Getter和Setter方法 + + public String getShareKey() { + return shareKey; + } + + public void setShareKey(String shareKey) { + this.shareKey = shareKey; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getSharePassword() { + return sharePassword; + } + + public void setSharePassword(String sharePassword) { + this.sharePassword = sharePassword; + } + + public String getShareUrl() { + return shareUrl; + } + + public void setShareUrl(String shareUrl) { + this.shareUrl = shareUrl; + } + + public String getStandardUrl() { + return standardUrl; + } + + public void setStandardUrl(String standardUrl) { + this.standardUrl = standardUrl; + } + + // 静态方法创建建造者对象 + public static ShareLinkInfo.Builder newBuilder() { + return new ShareLinkInfo.Builder(); + } + + // 建造者类 + public static class Builder { + private String shareKey; // 分享键 + private String type; // 分享类型 + private String sharePassword = ""; // 分享密码(如果存在) + private String shareUrl; // 原始分享链接 + private String standardUrl; // 规范化的标准链接 + + public Builder shareKey(String shareKey) { + this.shareKey = shareKey; + return this; + } + + public Builder type(String type) { + this.type = type; + return this; + } + + public Builder sharePassword(String sharePassword) { + this.sharePassword = sharePassword; + return this; + } + + public Builder shareUrl(String shareUrl) { + this.shareUrl = shareUrl; + return this; + } + + public Builder standardUrl(String standardUrl) { + this.standardUrl = standardUrl; + return this; + } + + public ShareLinkInfo build() { + return new ShareLinkInfo(this); + } + } + + @Override + public String toString() { + return "ShareLinkInfo{" + + "shareKey='" + shareKey + '\'' + + ", type='" + type + '\'' + + ", sharePassword='" + sharePassword + '\'' + + ", shareUrl='" + shareUrl + '\'' + + ", standardUrl='" + standardUrl + '\'' + + '}'; + } +} diff --git a/parser/src/main/java/cn/qaiu/parser/IPanTool.java b/parser/src/main/java/cn/qaiu/parser/IPanTool.java index d01a57e..376f3a3 100644 --- a/parser/src/main/java/cn/qaiu/parser/IPanTool.java +++ b/parser/src/main/java/cn/qaiu/parser/IPanTool.java @@ -1,62 +1,31 @@ package cn.qaiu.parser;//package cn.qaiu.lz.common.parser; -import cn.qaiu.parser.impl.*; import io.vertx.core.Future; public interface IPanTool { Future parse(); - + // 基于枚举PanDomainTemplate匹配分享链接类型 static IPanTool typeMatching(String type, String key, String pwd) { - return switch (type) { - case "lz" -> new LzTool(key, pwd); - case "cow" -> new CowTool(key, pwd); - case "ec" -> new EcTool(key, pwd); - case "fc" -> new FcTool(key, pwd); - case "uc" -> new UcTool(key, pwd); - case "ye" -> new YeTool(key, pwd); - 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); - case "iz" -> new IzTool(key, pwd); - case "ce" -> new CeTool(key, pwd); - default -> { - throw new UnsupportedOperationException("未知分享类型"); - } - }; + try { + return PanDomainTemplate + .fromShortName(type) + .generateShareLink(key) + .setShareLinkInfoPwd(pwd) + .createTool(); + } catch (Exception e) { + throw new UnsupportedOperationException("未知分享类型", e); + } } static IPanTool shareURLPrefixMatching(String url, String pwd) { - - if (url.contains(CowTool.LINK_KEY)) { - return new CowTool(url, pwd); - } else if (url.startsWith(EcTool.SHARE_URL_PREFIX)) { - return new EcTool(url, pwd); - } else if (url.startsWith(FcTool.SHARE_URL_PREFIX0)) { - return new FcTool(url, pwd); - } else if (url.startsWith(UcTool.SHARE_URL_PREFIX)) { - return new UcTool(url, pwd); - } else if (url.startsWith(YeTool.SHARE_URL_PREFIX)) { - return new YeTool(url, pwd); - } else if (url.startsWith(FjTool.SHARE_URL_PREFIX) || url.startsWith(FjTool.SHARE_URL_PREFIX2)) { - return new FjTool(url, pwd); - } else if (url.startsWith(IzTool.SHARE_URL_PREFIX)) { - return new IzTool(url, pwd); - } else if (url.contains(LzTool.LINK_KEY)) { - 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); - } else if (url.contains("/s/")) { - // Cloudreve 网盘通用解析 - return new CeTool(url, pwd); + try { + return PanDomainTemplate + .fromShareUrl(url) + .setShareLinkInfoPwd(pwd) + .createTool(); + } catch (Exception e) { + throw new UnsupportedOperationException("未知分享类型", e); } - - throw new UnsupportedOperationException("未知分享类型"); } } diff --git a/parser/src/main/java/cn/qaiu/parser/PanBase.java b/parser/src/main/java/cn/qaiu/parser/PanBase.java index 8d8faa1..a06686c 100644 --- a/parser/src/main/java/cn/qaiu/parser/PanBase.java +++ b/parser/src/main/java/cn/qaiu/parser/PanBase.java @@ -1,7 +1,7 @@ package cn.qaiu.parser; import cn.qaiu.WebClientVertxInit; -import cn.qaiu.util.CommonUtils; +import cn.qaiu.entity.ShareLinkInfo; import io.vertx.core.Handler; import io.vertx.core.Promise; import io.vertx.ext.web.client.WebClient; @@ -35,16 +35,7 @@ public abstract class PanBase { protected WebClient clientNoRedirects = WebClient.create(WebClientVertxInit.get(), new WebClientOptions().setFollowRedirects(false)); - /** - * 分享key 可以是整个URL; 如果是URL实现该类时要 - * 使用{@link CommonUtils#adaptShortPaths(String urlPrefix, String key)}获取真实的分享key - */ - protected String key; - - /** - * 分享密码 - */ - protected String pwd; + protected ShareLinkInfo shareLinkInfo; /** * 子类重写此构造方法不需要添加额外逻辑 @@ -55,12 +46,9 @@ public abstract class PanBase { * } * * - * @param key 分享key/url - * @param pwd 分享密码 */ - protected PanBase(String key, String pwd) { - this.key = key; - this.pwd = pwd; + public PanBase(ShareLinkInfo shareLinkInfo) { + this.shareLinkInfo = shareLinkInfo; } /** diff --git a/parser/src/main/java/cn/qaiu/parser/PanDomainTemplate.java b/parser/src/main/java/cn/qaiu/parser/PanDomainTemplate.java new file mode 100644 index 0000000..6ec85ea --- /dev/null +++ b/parser/src/main/java/cn/qaiu/parser/PanDomainTemplate.java @@ -0,0 +1,225 @@ +package cn.qaiu.parser; + +import cn.qaiu.entity.ShareLinkInfo; +import cn.qaiu.parser.impl.*; +import org.apache.commons.lang3.StringUtils; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 枚举类 PanDomainTemplate 定义了不同网盘服务的模板信息,包括: + * + * 该类提供方法来解析和规范化不同来源的分享链接,确保它们可以转换为统一的标准链接格式。 + * 通过这种方式,应用程序可以更容易地处理和识别不同网盘服务的分享链接. + * + * @author QAIU + * at 2023/6/13 4:26 + */ +public enum PanDomainTemplate { + + // 网盘定义 + LZ("蓝奏云", + "lz", + "https://([a-z]+)?\\.?lanzou[a-z]\\.com/(.+/)?(.+)", + "https://lanzoux.com/{shareKey}", + LzTool.class), + + // https://www.feijix.com/s/ + // https://share.feijipan.com/s/ + FJ("小飞机网盘", + "fj", + "https://(share\\.feijipan\\.com|www\\.feijix\\.com)/s/(.+)", + "https://www.feijix.com/s/{shareKey}", + FjTool.class), + + // https://lecloud.lenovo.com/share/ + LE("联想乐云", + "le", + "https://lecloud?\\.lenovo\\.com/share/(.+)", + "https://lecloud.lenovo.com/share/{shareKey}", + LeTool.class), + + // https://v2.fangcloud.com/s/ + FC("亿方云", + "fc", + "https://v2\\.fangcloud\\.(com|cn)/(s|sharing)/([^/]+)", + "https://v2.fangcloud.com/s/{shareKey}", + FcTool.class), + // https://www.ilanzou.com/s/ + IZ("蓝奏云优享", + "iz", + "https://www\\.ilanzou\\.com/s/(.+)", + "https://www.ilanzou.com/s/{shareKey}", + IzTool.class), + // https://wx.mail.qq.com/ftn/download? + QQ("QQ邮箱中转站", + "qq", + "https://i?wx\\.mail\\.qq\\.com/ftn/download\\?(.+)", + "https://iwx.mail.qq.com/ftn/download/{shareKey}", + QQTool.class), + // https://f.ws59.cn/f/或者https://www.wenshushu.cn/f/ + WS("文叔叔", + "ws", + "https://(f\\.ws59\\.cn|www\\.wenshushu\\.cn)/f/(.+)", + "https://f.ws59.cn/f/{shareKey}", + WsTool.class), + // https://www.123pan.com/s/ + YE("123网盘", + "ye", + "https://www\\.123pan\\.com/s/(.+)\\.html", + "https://www.123pan.com/s/{shareKey}.html", + YeTool.class), + // https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data={code}&isShare=1 + EC("移动云空间", + "ec", + "https://www\\.ecpan\\.cn/web(/%23|/#)?/yunpanProxy\\?path=.*&data=" + + "([^&]+)&isShare=1", + "https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data={shareKey}&isShare=1", + EcTool.class), + // https://cowtransfer.com/s/ + COW("奶牛快传", + "cow", + "https://(.*)cowtransfer\\.com/s/(.+)", + "https://cowtransfer.com/s/{shareKey}", + CowTool.class), + // https://pan.huang1111.cn/s/ + CE("huang1111", + "ce", + "https://pan\\.huang1111\\.cn/s/(.+)", + "https://pan.huang1111.cn/s/{shareKey}", + CeTool.class); + + + // 网盘的显示名称,用于用户界面显示 + private final String displayName; + + // 网盘的简短名称,用于内部逻辑处理,如REST API路径 + private final String shortName; + + // 用于匹配和解析分享链接的正则表达式 + private final String regexPattern; + + // 网盘的标准链接模板,不含占位符,用于规范化分享链接 + private final String standardUrlTemplate; + private final ShareLinkInfo shareLinkInfo; + // 指向IPanTool实现类 + private final Class toolClass; + + PanDomainTemplate(String displayName, String shortName, String regexPattern, + String standardUrlTemplate, Class toolClass) { + this.displayName = displayName; + this.shortName = shortName; + this.regexPattern = regexPattern; + this.standardUrlTemplate = standardUrlTemplate; + this.toolClass = toolClass; + this.shareLinkInfo = ShareLinkInfo.newBuilder().type(shortName).build(); + } + + + // 解析并规范化分享链接 + synchronized public PanDomainTemplate normalizeShareLink() { + if (shareLinkInfo == null) { + throw new IllegalArgumentException("ShareLinkInfo not init"); + } + // 匹配并提取shareKey + String shareUrl = shareLinkInfo.getShareUrl(); + if (StringUtils.isEmpty(shareUrl)) { + throw new IllegalArgumentException("ShareLinkInfo shareUrl is empty"); + } + Pattern pattern = Pattern.compile(regexPattern); + Matcher matcher = pattern.matcher(shareUrl); + if (matcher.find()) { + String shareKey = matcher.group(matcher.groupCount()); + // 返回规范化的标准链接 + String standardUrl = standardUrlTemplate.replace("{shareKey}", shareKey); + shareLinkInfo.setShareUrl(shareUrl); + shareLinkInfo.setShareKey(shareKey); + shareLinkInfo.setStandardUrl(standardUrl); + return this; + } + throw new IllegalArgumentException("Invalid share URL for " + displayName); + } + + public IPanTool createTool() { + if (shareLinkInfo == null || StringUtils.isEmpty(shareLinkInfo.getType())) { + throw new IllegalArgumentException("ShareLinkInfo not init or type is empty"); + } + if (StringUtils.isEmpty(shareLinkInfo.getShareKey())) { + this.normalizeShareLink(); + } + try { + return toolClass + .getDeclaredConstructor(ShareLinkInfo.class) + .newInstance(shareLinkInfo); + } catch (Exception e) { + throw new RuntimeException("无法创建工具实例: " + toolClass.getName(), e); + } + } + + // 生成分享链接的方法 + synchronized public PanDomainTemplate generateShareLink(String shareKey) { + shareLinkInfo.setShareKey(shareKey); + shareLinkInfo.setStandardUrl(standardUrlTemplate.replace("{shareKey}", shareKey)); + return this; + } + + + public String getDisplayName() { + return this.displayName; + } + + public String getRegexPattern() { + return regexPattern; + } + + public String getStandardUrlTemplate() { + return standardUrlTemplate; + } + + + public ShareLinkInfo getShareLinkInfo() { + return shareLinkInfo; + } + + synchronized public PanDomainTemplate setShareLinkInfoPwd(String pwd) { + shareLinkInfo.setSharePassword(pwd); + return this; + } + + synchronized public PanDomainTemplate setShareLinkInfoUrl(String pwd) { + shareLinkInfo.setSharePassword(pwd); + return this; + } + + // 根据分享链接获取PanDomainTemplate实例 + synchronized public static PanDomainTemplate fromShareUrl(String shareUrl) { + for (PanDomainTemplate template : values()) { + if (shareUrl.matches(template.regexPattern)) { + template.getShareLinkInfo().setShareUrl(shareUrl); + return template.normalizeShareLink(); + } + } + throw new IllegalArgumentException("Unsupported share URL"); + } + + // 根据shortName获取枚举实例 + public static PanDomainTemplate fromShortName(String shortName) { + try { + return Enum.valueOf(PanDomainTemplate.class, shortName.toUpperCase()); + } catch (IllegalArgumentException ignore) { + // 如果没有找到对应的枚举实例,抛出异常 + throw new IllegalArgumentException("No enum constant for short name: " + shortName); + } + } + + public String getShortName() { + return shortName; + } +} diff --git a/parser/src/main/java/cn/qaiu/parser/impl/CeTool.java b/parser/src/main/java/cn/qaiu/parser/impl/CeTool.java index c147cc8..91aac9f 100644 --- a/parser/src/main/java/cn/qaiu/parser/impl/CeTool.java +++ b/parser/src/main/java/cn/qaiu/parser/impl/CeTool.java @@ -1,5 +1,6 @@ package cn.qaiu.parser.impl; +import cn.qaiu.entity.ShareLinkInfo; import cn.qaiu.parser.IPanTool; import cn.qaiu.parser.PanBase; import io.vertx.core.Future; @@ -7,9 +8,6 @@ import io.vertx.core.buffer.Buffer; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.client.HttpRequest; -import java.net.MalformedURLException; -import java.net.URL; - /** * Cloudreve网盘解析
* 暮希云盘
@@ -17,34 +15,40 @@ import java.net.URL; */ public class CeTool extends PanBase implements IPanTool { - private static final String DOWNLOAD_API_PATH = "/api/v3/share/download/"; + private static final String DOWNLOAD_API_PATH = "https://pan.huang1111.cn/api/v3/share/download/"; // api/v3/share/info/g31PcQ?password=qaiu - private static final String SHARE_API_PATH = "/api/v3/share/info/"; + private static final String SHARE_API_PATH = "https://pan.huang1111.cn/api/v3/share/info/"; - public CeTool(String key, String pwd) { - super(key, pwd); + public CeTool(ShareLinkInfo shareLinkInfo) { + super(shareLinkInfo); } + public Future parse() { + String key = shareLinkInfo.getShareKey(); + String pwd = shareLinkInfo.getSharePassword(); // https://pan.huang1111.cn/s/wDz5TK // https://pan.huang1111.cn/s/y12bI6 -> https://pan.huang1111 // .cn/api/v3/share/download/y12bI6?path=undefined%2Fundefined; // 类型解析 -> /ce/https_pan.huang1111.cn_s_wDz5TK // parser接口 -> /parser?url=https://pan.huang1111.cn/s/wDz5TK try { - if (key.startsWith("https_") || key.startsWith("http_")) { - key = key.replace("https_", "https://") - .replace("http_", "http://") - .replace("_", "/"); - } - // 处理URL - URL url = new URL(key); - String path = url.getPath(); - String shareKey = path.substring(3); - String downloadApiUrl = url.getProtocol() + "://" + url.getHost() + DOWNLOAD_API_PATH + shareKey + "?path" + - "=undefined/undefined;"; - String shareApiUrl = url.getProtocol() + "://" + url.getHost() + SHARE_API_PATH + shareKey; +// if (key.startsWith("https_") || key.startsWith("http_")) { +// key = key.replace("https_", "https://") +// .replace("http_", "http://") +// .replace("_", "/"); +// } +// // 处理URL +// URL url = new URL(key); +// String path = url.getPath(); +// String shareKey = path.substring(3); +// String downloadApiUrl = url.getProtocol() + "://" + url.getHost() + DOWNLOAD_API_PATH + shareKey + "?path" + +// "=undefined/undefined;"; +// String shareApiUrl = url.getProtocol() + "://" + url.getHost() + SHARE_API_PATH + shareKey; + + var shareApiUrl = SHARE_API_PATH; + var downloadApiUrl = DOWNLOAD_API_PATH; // 设置cookie HttpRequest httpRequest = clientSession.getAbs(shareApiUrl); @@ -53,7 +57,7 @@ public class CeTool extends PanBase implements IPanTool { } // 获取下载链接 httpRequest.send().onSuccess(res -> getDownURL(downloadApiUrl)).onFailure(handleFail(shareApiUrl)); - } catch (MalformedURLException e) { + } catch (Exception e) { fail(e, "URL解析错误"); } return promise.future(); diff --git a/parser/src/main/java/cn/qaiu/parser/impl/CowTool.java b/parser/src/main/java/cn/qaiu/parser/impl/CowTool.java index 04cd58c..e9a93ef 100644 --- a/parser/src/main/java/cn/qaiu/parser/impl/CowTool.java +++ b/parser/src/main/java/cn/qaiu/parser/impl/CowTool.java @@ -1,8 +1,8 @@ package cn.qaiu.parser.impl; +import cn.qaiu.entity.ShareLinkInfo; import cn.qaiu.parser.IPanTool; import cn.qaiu.parser.PanBase; -import cn.qaiu.util.CommonUtils; import io.vertx.core.Future; import io.vertx.core.json.JsonObject; import org.apache.commons.lang3.StringUtils; @@ -16,16 +16,14 @@ import org.apache.commons.lang3.StringUtils; public class CowTool extends PanBase implements IPanTool { private static final String API_REQUEST_URL = "https://cowtransfer.com/core/api/transfer/share"; - public static final String SHARE_URL_PREFIX = "https://cowtransfer.com/s/"; - public static final String LINK_KEY = "cowtransfer.com/s/"; - - public CowTool(String key, String pwd) { - super(key, pwd); + public CowTool(ShareLinkInfo shareLinkInfo) { + super(shareLinkInfo); } + public Future parse() { - key = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key); + final String key = shareLinkInfo.getShareKey(); String url = API_REQUEST_URL + "?uniqueUrl=" + key; client.getAbs(url).send().onSuccess(res -> { JsonObject resJson = res.bodyAsJsonObject(); diff --git a/parser/src/main/java/cn/qaiu/parser/impl/EcTool.java b/parser/src/main/java/cn/qaiu/parser/impl/EcTool.java index c461ff0..101d5a8 100644 --- a/parser/src/main/java/cn/qaiu/parser/impl/EcTool.java +++ b/parser/src/main/java/cn/qaiu/parser/impl/EcTool.java @@ -1,8 +1,8 @@ package cn.qaiu.parser.impl; +import cn.qaiu.entity.ShareLinkInfo; import cn.qaiu.parser.IPanTool; import cn.qaiu.parser.PanBase; -import cn.qaiu.util.CommonUtils; import io.vertx.core.Future; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; @@ -12,20 +12,22 @@ import io.vertx.uritemplate.UriTemplate; * 移动云空间解析 */ public class EcTool extends PanBase implements IPanTool { + // https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=4b3d786755688b85c6eb0c04b9124f4dalzdaJpXHx&isShare=1 private static final String FIRST_REQUEST_URL = "https://www.ecpan.cn/drive/fileextoverrid" + ".do?extractionCode={extractionCode}&chainUrlTemplate=https:%2F%2Fwww.ecpan" + ".cn%2Fweb%2F%23%2FyunpanProxy%3Fpath%3D%252F%2523%252Fdrive%252Foutside&parentId=-1&data={dataKey}"; private static final String DOWNLOAD_REQUEST_URL = "https://www.ecpan.cn/drive/sharedownload.do"; - public static final String SHARE_URL_PREFIX = "www.ecpan.cn/"; - - public EcTool(String key, String pwd) { - super(key, pwd); + public EcTool(ShareLinkInfo shareLinkInfo) { + super(shareLinkInfo); } + public Future parse() { - String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key); + final String dataKey = shareLinkInfo.getShareKey(); + final String pwd = shareLinkInfo.getSharePassword(); + // 第一次请求 获取文件信息 client.getAbs(UriTemplate.of(FIRST_REQUEST_URL)) .setTemplateParam("dataKey", dataKey) diff --git a/parser/src/main/java/cn/qaiu/parser/impl/FcTool.java b/parser/src/main/java/cn/qaiu/parser/impl/FcTool.java index 138eb68..4e60c06 100644 --- a/parser/src/main/java/cn/qaiu/parser/impl/FcTool.java +++ b/parser/src/main/java/cn/qaiu/parser/impl/FcTool.java @@ -1,8 +1,8 @@ package cn.qaiu.parser.impl; +import cn.qaiu.entity.ShareLinkInfo; import cn.qaiu.parser.IPanTool; import cn.qaiu.parser.PanBase; -import cn.qaiu.util.CommonUtils; import io.vertx.core.Future; import io.vertx.core.MultiMap; import io.vertx.core.Promise; @@ -21,19 +21,19 @@ import java.util.regex.Pattern; */ public class FcTool extends PanBase implements IPanTool { - public static final String SHARE_URL_PREFIX0 = "https://v2.fangcloud.com/s"; public static final String SHARE_URL_PREFIX = "https://v2.fangcloud.com/sharing/"; public static final String SHARE_URL_PREFIX2 = "https://v2.fangcloud.cn/sharing/"; private static final String DOWN_REQUEST_URL = "https://v2.fangcloud.cn/apps/files/download?file_id={fid}" + "&scenario=share&unique_name={uname}"; - public FcTool(String key, String pwd) { - super(key, pwd); + public FcTool(ShareLinkInfo shareLinkInfo) { + super(shareLinkInfo); } + public Future parse() { - String data = key.replace("share","sharing"); - String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, data); + final String dataKey = shareLinkInfo.getShareKey(); + final String pwd = shareLinkInfo.getSharePassword(); WebClientSession sClient = WebClientSession.create(client); // 第一次请求 自动重定向 sClient.getAbs(SHARE_URL_PREFIX + dataKey).send().onSuccess(res -> { diff --git a/parser/src/main/java/cn/qaiu/parser/impl/FjTool.java b/parser/src/main/java/cn/qaiu/parser/impl/FjTool.java index 280f940..f5d317c 100644 --- a/parser/src/main/java/cn/qaiu/parser/impl/FjTool.java +++ b/parser/src/main/java/cn/qaiu/parser/impl/FjTool.java @@ -1,9 +1,9 @@ package cn.qaiu.parser.impl; +import cn.qaiu.entity.ShareLinkInfo; import cn.qaiu.parser.IPanTool; import cn.qaiu.parser.PanBase; import cn.qaiu.util.AESUtils; -import cn.qaiu.util.CommonUtils; import cn.qaiu.util.UUIDUtil; import io.vertx.core.Future; import io.vertx.core.MultiMap; @@ -18,10 +18,7 @@ import io.vertx.uritemplate.UriTemplate; * @version V016_230609 */ public class FjTool extends PanBase implements IPanTool { - - public static final String SHARE_URL_PREFIX = "https://www.feijix.com/s/"; public static final String REFERER_URL = "https://share.feijipan.com/"; - public static final String SHARE_URL_PREFIX2 = REFERER_URL + "s/"; private static final String API_URL_PREFIX = "https://api.feijipan.com/ws/"; private static final String FIRST_REQUEST_URL = API_URL_PREFIX + "recommend/list?devType=6&devModel=Chrome" + @@ -38,19 +35,14 @@ public class FjTool extends PanBase implements IPanTool { "={uuid}&extra=2×tamp={ts}"; // https://api.feijipan.com/ws/buy/vip/list?devType=6&devModel=Chrome&uuid=WQAl5yBy1naGudJEILBvE&extra=2×tamp=E2C53155F6D09417A27981561134CB73 - public FjTool(String key, String pwd) { - super(key, pwd); + public FjTool(ShareLinkInfo shareLinkInfo) { + super(shareLinkInfo); } public Future parse() { - String dataKey; - if (key.startsWith(SHARE_URL_PREFIX2)) { - dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX2, key); - } else { - dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key); - } + final String dataKey = shareLinkInfo.getShareKey(); - // 240530 此处shareId又改为了原始的shareId, nm玩呢? + // 240530 此处shareId又改为了原始的shareId String shareId = dataKey; // String.valueOf(AESUtils.idEncrypt(dataKey)); long nowTs = System.currentTimeMillis(); String tsEncode = AESUtils.encrypt2Hex(Long.toString(nowTs)); diff --git a/parser/src/main/java/cn/qaiu/parser/impl/IzTool.java b/parser/src/main/java/cn/qaiu/parser/impl/IzTool.java index 018dfdd..c47bbab 100644 --- a/parser/src/main/java/cn/qaiu/parser/impl/IzTool.java +++ b/parser/src/main/java/cn/qaiu/parser/impl/IzTool.java @@ -1,9 +1,9 @@ package cn.qaiu.parser.impl; +import cn.qaiu.entity.ShareLinkInfo; import cn.qaiu.parser.IPanTool; import cn.qaiu.parser.PanBase; import cn.qaiu.util.AESUtils; -import cn.qaiu.util.CommonUtils; import io.vertx.core.Future; import io.vertx.core.MultiMap; import io.vertx.core.json.JsonObject; @@ -26,12 +26,12 @@ public class IzTool extends PanBase implements IPanTool { private static final String SECOND_REQUEST_URL = API_URL_PREFIX + "file/redirect?downloadId={fidEncode}&enable=1" + "&devType=6&uuid={uuid}×tamp={ts}&auth={auth}"; - public IzTool(String key, String pwd) { - super(key, pwd); + public IzTool(ShareLinkInfo shareLinkInfo) { + super(shareLinkInfo); } public Future parse() { - String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key); + String dataKey = shareLinkInfo.getShareKey(); // 24.5.12 ilanzou改规则无需计算shareId // String shareId = String.valueOf(AESUtils.idEncryptIz(dataKey)); diff --git a/parser/src/main/java/cn/qaiu/parser/impl/LeTool.java b/parser/src/main/java/cn/qaiu/parser/impl/LeTool.java index ce0640b..86dba49 100644 --- a/parser/src/main/java/cn/qaiu/parser/impl/LeTool.java +++ b/parser/src/main/java/cn/qaiu/parser/impl/LeTool.java @@ -1,8 +1,8 @@ package cn.qaiu.parser.impl; +import cn.qaiu.entity.ShareLinkInfo; import cn.qaiu.parser.IPanTool; import cn.qaiu.parser.PanBase; -import cn.qaiu.util.CommonUtils; import io.vertx.core.Future; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; @@ -13,16 +13,15 @@ import java.util.UUID; * 联想乐云 */ public class LeTool extends PanBase implements IPanTool { - - public static final String SHARE_URL_PREFIX = "https://lecloud.lenovo.com/share/"; private static final String API_URL_PREFIX = "https://lecloud.lenovo.com/share/api/clouddiskapi/share/public/v1/"; - public LeTool(String key, String pwd) { - super(key, pwd); + public LeTool(ShareLinkInfo shareLinkInfo) { + super(shareLinkInfo); } public Future parse() { - String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key); + final String dataKey = shareLinkInfo.getShareKey(); + final String pwd = shareLinkInfo.getSharePassword(); // {"shareId":"xxx","password":"xxx","directoryId":"-1"} String apiUrl1 = API_URL_PREFIX + "shareInfo"; client.postAbs(apiUrl1) @@ -47,7 +46,7 @@ public class LeTool extends PanBase implements IPanTool { JsonObject fileInfoJson = files.getJsonObject(0); if (fileInfoJson != null) { // TODO 文件大小fileSize和文件名fileName - Long fileId = fileInfoJson.getLong("fileId"); + String fileId = fileInfoJson.getString("fileId"); // 根据文件ID获取跳转链接 getDownURL(dataKey, fileId); } @@ -61,7 +60,7 @@ public class LeTool extends PanBase implements IPanTool { return promise.future(); } - private void getDownURL(String key, Long fileId) { + private void getDownURL(String key, String fileId) { String uuid = UUID.randomUUID().toString(); JsonArray fileIds = JsonArray.of(fileId); String apiUrl2 = API_URL_PREFIX + "packageDownloadWithFileIds"; diff --git a/parser/src/main/java/cn/qaiu/parser/impl/LzTool.java b/parser/src/main/java/cn/qaiu/parser/impl/LzTool.java index a9f9b81..c019365 100644 --- a/parser/src/main/java/cn/qaiu/parser/impl/LzTool.java +++ b/parser/src/main/java/cn/qaiu/parser/impl/LzTool.java @@ -1,5 +1,6 @@ package cn.qaiu.parser.impl; +import cn.qaiu.entity.ShareLinkInfo; import cn.qaiu.parser.IPanTool; import cn.qaiu.parser.PanBase; import cn.qaiu.util.JsExecUtils; @@ -23,15 +24,15 @@ public class LzTool extends PanBase implements IPanTool { public static final String SHARE_URL_PREFIX = "https://wwwa.lanzoux.com"; - public static final String LINK_KEY = "lanzou"; - public LzTool(String key, String pwd) { - super(key, pwd); + public LzTool(ShareLinkInfo shareLinkInfo) { + super(shareLinkInfo); } @SuppressWarnings("unchecked") public Future parse() { - String sUrl = key.startsWith("https://") ? key : SHARE_URL_PREFIX + "/" + key; + String sUrl = shareLinkInfo.getStandardUrl(); + String pwd = shareLinkInfo.getSharePassword(); WebClient client = clientNoRedirects; client.getAbs(sUrl).send().onSuccess(res -> { @@ -50,34 +51,37 @@ public class LzTool extends PanBase implements IPanTool { } jsText = jsText.replace("document.getElementById('pwd').value", "\"" + pwd + "\""); - jsText = jsText.substring(0, jsText.indexOf("document.getElementById('rpt')")); + int i = jsText.indexOf("document.getElementById('rpt')"); + if (i > 0) { + jsText = jsText.substring(0, i); + } try { ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText, "down_p"); getDownURL(sUrl, client, (Map) scriptObjectMirror.get("data")); } catch (ScriptException | NoSuchMethodException e) { fail(e, "js引擎执行失败"); - return; } - return; - } - String iframePath = matcher.group(1); - client.getAbs(SHARE_URL_PREFIX + iframePath).send().onSuccess(res2 -> { - String html2 = res2.bodyAsString(); + } 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, (Map) scriptObjectMirror.get("data")); - } catch (ScriptException | NoSuchMethodException e) { - fail(e, "js引擎执行失败"); - } - }).onFailure(handleFail(SHARE_URL_PREFIX)); + // 去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, (Map) scriptObjectMirror.get("data")); + } catch (ScriptException | NoSuchMethodException e) { + fail(e, "js引擎执行失败"); + } + }).onFailure(handleFail(SHARE_URL_PREFIX)); + } }).onFailure(handleFail(sUrl)); return promise.future(); } diff --git a/parser/src/main/java/cn/qaiu/parser/impl/QQTool.java b/parser/src/main/java/cn/qaiu/parser/impl/QQTool.java index d50c34b..62db8b6 100644 --- a/parser/src/main/java/cn/qaiu/parser/impl/QQTool.java +++ b/parser/src/main/java/cn/qaiu/parser/impl/QQTool.java @@ -1,43 +1,48 @@ package cn.qaiu.parser.impl; -import cn.qaiu.util.StringUtils; - -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; - +import cn.qaiu.entity.ShareLinkInfo; import cn.qaiu.parser.IPanTool; import cn.qaiu.parser.PanBase; +import cn.qaiu.util.StringUtils; +import io.netty.handler.codec.http.QueryStringDecoder; import io.vertx.core.Future; import io.vertx.core.MultiMap; -import io.vertx.ext.web.client.WebClient; + +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; /** * QQ邮箱 */ public class QQTool extends PanBase implements IPanTool { - public static final String SHARE_URL_PREFIX = "wx.mail.qq.com/ftn/download?"; + public static final String REDIRECT_URL_TEMP = "https://iwx.mail.qq.com/ftn/download?func=4&key={key}&code={code}"; - public QQTool(String key, String pwd) { - super(key, pwd); + public QQTool(ShareLinkInfo shareLinkInfo) { + super(shareLinkInfo); } - @SuppressWarnings("unchecked") public Future 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("未知分享类型"); - } + // QQ mail 直接替换为302链接 无需请求 + QueryStringDecoder queryStringDecoder = new QueryStringDecoder(shareLinkInfo.getShareUrl(), StandardCharsets.UTF_8); + Map> prms = queryStringDecoder.parameters(); + if (prms.containsKey("key") && prms.containsKey("code") && prms.containsKey("func")) { + log.info(prms.get("func").get(0)); + promise.complete(REDIRECT_URL_TEMP.replace("{key}", + prms.get("key").get(0)).replace("{code}", prms.get("code").get(0))); + } else { + fail("key 不合法"); } + + // 通过请求URL获取文件信息和直链 暂时不需要 + // getFileInfo(key); + + return promise.future(); + } + + private void getFileInfo(String key) { // 设置基础HTTP头部 var userAgent2 = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, " + "like " + @@ -50,7 +55,7 @@ public class QQTool extends PanBase implements IPanTool { headers.set("sec-ch-ua-mobile", "sec-ch-ua-mobile"); // 获取下载中转站页面 - httpClient.getAbs(this.key).putHeaders(headers).send().onSuccess(res -> { + client.getAbs(key).putHeaders(headers).send().onSuccess(res -> { if (res.statusCode() == 200) { String html = res.bodyAsString(); @@ -61,10 +66,10 @@ public class QQTool extends PanBase implements IPanTool { if (filename != null && filesize != null && fileurl != null) { // 设置所需HTTP头部 - headers.set("Referer", "https://" + StringUtils.StringCutNot(this.key, "https://", "/") + "/"); + headers.set("Referer", "https://" + StringUtils.StringCutNot(key, "https://", "/") + "/"); headers.set("Host", StringUtils.StringCutNot(fileurl, "https://", "/")); res.headers().forEach((k, v) -> { - if (k.toLowerCase().equals("set-cookie")) { + if (k.equalsIgnoreCase("set-cookie")) { headers.set("Cookie", "mail5k=" + StringUtils.StringCutNot(v, "mail5k=", ";") + ";"); } }); @@ -82,9 +87,7 @@ public class QQTool extends PanBase implements IPanTool { } else { this.fail("HTTP状态不正确,可能是分享链接的方式已更新"); } - }).onFailure(this.handleFail(this.key)); - - return promise.future(); + }).onFailure(this.handleFail(key)); } } diff --git a/parser/src/main/java/cn/qaiu/parser/impl/QkTool.java b/parser/src/main/java/cn/qaiu/parser/impl/QkTool.java index ce81bf7..70159a8 100644 --- a/parser/src/main/java/cn/qaiu/parser/impl/QkTool.java +++ b/parser/src/main/java/cn/qaiu/parser/impl/QkTool.java @@ -1,5 +1,6 @@ package cn.qaiu.parser.impl; +import cn.qaiu.entity.ShareLinkInfo; import cn.qaiu.parser.IPanTool; import cn.qaiu.parser.PanBase; import io.vertx.core.Future; @@ -9,11 +10,14 @@ import java.util.stream.IntStream; public class QkTool extends PanBase implements IPanTool { - public QkTool(String key, String pwd) { - super(key, pwd); + public QkTool(ShareLinkInfo shareLinkInfo) { + super(shareLinkInfo); } public Future parse() { + final String key = shareLinkInfo.getShareKey(); + final String pwd = shareLinkInfo.getSharePassword(); + promise.complete("https://lz.qaiu.top"); IntStream.range(0, 1000).forEach(num -> { clientNoRedirects.getAbs(key).send() @@ -33,14 +37,6 @@ public class QkTool extends PanBase implements IPanTool { public static void main(String[] args) { - new QkTool("https://pimapi.lenovomm.com/clouddiskapi/v1/shareRedirect?si=12298704&dk" + - "=19ab590770399d4438ea885446e27186cc668cdfa559f5fcc063a1ecf78008e5&pk" + - "=ef45aa4d25c1dcecb631b3394f51539d59cb35c6a40c3911df8ba431ba2a3244&pc=true&ot=ali&ob=sync-cloud-disk" + - "&ok=649593714557087744.dex&fn=classes" + - ".dex&ds=8909208&dc=1&bi=asdddsad&ri=&ts=1701235051759&sn" + - "=13dc33749bd9cc108009fa505b3ecca9f358d70874352858475956ba4240e4c3", "") - .parse().onSuccess((res) -> { - }); } } diff --git a/parser/src/main/java/cn/qaiu/parser/impl/UcTool.java b/parser/src/main/java/cn/qaiu/parser/impl/UcTool.java deleted file mode 100644 index bf80bf5..0000000 --- a/parser/src/main/java/cn/qaiu/parser/impl/UcTool.java +++ /dev/null @@ -1,82 +0,0 @@ -package cn.qaiu.parser.impl; - -import cn.qaiu.parser.IPanTool; -import cn.qaiu.parser.PanBase; -import cn.qaiu.util.CommonUtils; -import io.vertx.core.Future; -import io.vertx.core.json.JsonArray; -import io.vertx.core.json.JsonObject; -import io.vertx.uritemplate.UriTemplate; - -/** - * UC网盘解析 - */ -public class UcTool extends PanBase implements IPanTool { - private static final String API_URL_PREFIX = "https://pc-api.uc.cn/1/clouddrive/"; - - public static final String SHARE_URL_PREFIX = "https://fast.uc.cn/s/"; - - private static final String FIRST_REQUEST_URL = API_URL_PREFIX + "share/sharepage/token?entry=ft&fr=pc&pr" + - "=UCBrowser"; - - private static final String SECOND_REQUEST_URL = API_URL_PREFIX + "transfer_share/detail?pwd_id={pwd_id}&passcode" + - "={passcode}&stoken={stoken}"; - - private static final String THIRD_REQUEST_URL = API_URL_PREFIX + "file/download?entry=ft&fr=pc&pr=UCBrowser"; - - public UcTool(String key, String pwd) { - super(key, pwd); - } - - public Future parse() { - var dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key); - var passcode = (pwd == null) ? "" : pwd; - var jsonObject = JsonObject.of("share_for_transfer", true); - jsonObject.put("pwd_id", dataKey); - jsonObject.put("passcode", passcode); - // 第一次请求 获取文件信息 - client.postAbs(FIRST_REQUEST_URL).sendJsonObject(jsonObject).onSuccess(res -> { - log.debug("第一阶段 {}", res.body()); - var resJson = res.bodyAsJsonObject(); - if (resJson.getInteger("code") != 0) { - fail(FIRST_REQUEST_URL + " 返回异常: " + resJson); - return; - } - var stoken = resJson.getJsonObject("data").getString("stoken"); - // 第二次请求 - client.getAbs(UriTemplate.of(SECOND_REQUEST_URL)) - .setTemplateParam("pwd_id", dataKey) - .setTemplateParam("passcode", passcode) - .setTemplateParam("stoken", stoken) - .send().onSuccess(res2 -> { - log.debug("第二阶段 {}", res2.body()); - JsonObject resJson2 = res2.bodyAsJsonObject(); - if (resJson2.getInteger("code") != 0) { - fail(FIRST_REQUEST_URL + " 返回异常: " + resJson2); - return; - } - // 文件信息 - var info = resJson2.getJsonObject("data").getJsonArray("list").getJsonObject(0); - // 第二次请求 - var bodyJson = JsonObject.of() - .put("fids", JsonArray.of(info.getString("fid"))) - .put("pwd_id", dataKey) - .put("stoken", stoken) - .put("fids_token", JsonArray.of(info.getString("share_fid_token"))); - client.postAbs(THIRD_REQUEST_URL).sendJsonObject(bodyJson) - .onSuccess(res3 -> { - log.debug("第三阶段 {}", res3.body()); - var resJson3 = res3.bodyAsJsonObject(); - if (resJson3.getInteger("code") != 0) { - fail(FIRST_REQUEST_URL + " 返回异常: " + resJson2); - return; - } - promise.complete(resJson3.getJsonArray("data").getJsonObject(0).getString("download_url")); - }).onFailure(handleFail(THIRD_REQUEST_URL)); - - }).onFailure(handleFail(SECOND_REQUEST_URL)); - } - ).onFailure(handleFail(FIRST_REQUEST_URL)); - return promise.future(); - } -} diff --git a/parser/src/main/java/cn/qaiu/parser/impl/WsTool.java b/parser/src/main/java/cn/qaiu/parser/impl/WsTool.java index 70b6c46..9dd2c06 100644 --- a/parser/src/main/java/cn/qaiu/parser/impl/WsTool.java +++ b/parser/src/main/java/cn/qaiu/parser/impl/WsTool.java @@ -1,10 +1,6 @@ package cn.qaiu.parser.impl; -import cn.qaiu.util.StringUtils; - -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; - +import cn.qaiu.entity.ShareLinkInfo; import cn.qaiu.parser.IPanTool; import cn.qaiu.parser.PanBase; import io.vertx.core.Future; @@ -13,33 +9,27 @@ import io.vertx.core.json.DecodeException; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.client.WebClient; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; + /** * 文叔叔 */ 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); + public WsTool(ShareLinkInfo shareLinkInfo) { + super(shareLinkInfo); } public Future parse() { WebClient httpClient = this.client; + final String key = shareLinkInfo.getShareKey(); + final String pwd = shareLinkInfo.getSharePassword(); - // 补全链接 - 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, " + @@ -66,8 +56,8 @@ public class WsTool extends PanBase implements IPanTool { // 获取文件夹信息 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", "" + "tid", key, + "password", pwd )).onSuccess(res2 -> { if (res2.statusCode() == 200) { @@ -128,16 +118,9 @@ public class WsTool extends PanBase implements IPanTool { // 调试输出文件直链 System.out.println("文件直链: " + fileurl); - if (!fileurl.equals("")) - { - try { - promise.complete(URLDecoder.decode(fileurl, "UTF-8")); - } catch (UnsupportedEncodingException e) { - promise.complete(fileurl); - } - } - else - { + if (!fileurl.equals("")) { + promise.complete(URLDecoder.decode(fileurl, StandardCharsets.UTF_8)); + } else { this.fail("文件已失效"); } @@ -166,7 +149,7 @@ public class WsTool extends PanBase implements IPanTool { this.fail("HTTP状态不正确,可能是分享链接的方式已更新"); } - }).onFailure(this.handleFail(this.key)); + }).onFailure(this.handleFail(key)); } catch (DecodeException | NullPointerException e) { this.fail("token获取失败,可能是分享链接的方式已更新"); @@ -175,7 +158,7 @@ public class WsTool extends PanBase implements IPanTool { this.fail("HTTP状态不正确,可能是分享链接的方式已更新"); } - }).onFailure(this.handleFail(this.key)); + }).onFailure(this.handleFail(key)); return promise.future(); } diff --git a/parser/src/main/java/cn/qaiu/parser/impl/YeTool.java b/parser/src/main/java/cn/qaiu/parser/impl/YeTool.java index 91a2953..63bc1e7 100644 --- a/parser/src/main/java/cn/qaiu/parser/impl/YeTool.java +++ b/parser/src/main/java/cn/qaiu/parser/impl/YeTool.java @@ -1,5 +1,6 @@ package cn.qaiu.parser.impl; +import cn.qaiu.entity.ShareLinkInfo; import cn.qaiu.parser.IPanTool; import cn.qaiu.parser.PanBase; import cn.qaiu.util.CommonUtils; @@ -30,13 +31,14 @@ public class YeTool extends PanBase implements IPanTool { "&shareKey={shareKey}&SharePwd={pwd}&ParentFileId=0&Page=1&event=homeListFile&operateType=1"; private static final String DOWNLOAD_API_URL = "https://www.123pan.com/a/api/share/download/info?{authK}={authV}"; - public YeTool(String key, String pwd) { - super(key, pwd); + public YeTool(ShareLinkInfo shareLinkInfo) { + super(shareLinkInfo); } public Future parse() { - String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key); + final String dataKey = shareLinkInfo.getShareKey(); + final String pwd = shareLinkInfo.getSharePassword(); client.getAbs(UriTemplate.of(FIRST_REQUEST_URL)).setTemplateParam("key", dataKey).send().onSuccess(res -> { diff --git a/parser/src/test/java/cn/qaiu/parser/FCURLParser.java b/parser/src/test/java/cn/qaiu/parser/FCURLParser.java new file mode 100644 index 0000000..39416c1 --- /dev/null +++ b/parser/src/test/java/cn/qaiu/parser/FCURLParser.java @@ -0,0 +1,37 @@ +package cn.qaiu.parser; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class FCURLParser {// 定义前缀 + public static final String SHARE_URL_PREFIX0 = "https://v2.fangcloud.com/s"; + public static final String SHARE_URL_PREFIX = "https://v2.fangcloud.com/sharing/"; + public static final String SHARE_URL_PREFIX2 = "https://v2.fangcloud.cn/sharing/"; + + // 定义正则表达式,适用于所有前缀 + private static final String SHARING_REGEX = "https://www\\.ecpan\\.cn/web(/%23|/#)?/yunpanProxy\\?path=.*&data=" + + "([^&]+)&isShare=1"; + + public static void main(String[] args) { + // 测试 URL + String[] urls = { + "https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=4b3d786755688b85c6eb0c04b9124f4dalzdaJpXHx&isShare=1", + "https://www.ecpan.cn/web/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=4b3d786755688b85c6eb0c04b9124f4dalzdaJpXHx&isShare=1", + "https://v2.fangcloud.cn/sharing/xyz789" + }; + + // 编译正则表达式 + Pattern pattern = Pattern.compile(SHARING_REGEX); + + for (String url : urls) { + Matcher matcher = pattern.matcher(url); + if (matcher.find()) { + System.out.println(matcher.groupCount()); + String shareKey = matcher.group(matcher.groupCount()); // 捕捉组 3 + System.out.println("Captured part: " + shareKey); + } else { + System.out.println("No match found."); + } + } + } +} diff --git a/parser/src/test/java/cn/qaiu/parser/PanDomainTemplateTest.java b/parser/src/test/java/cn/qaiu/parser/PanDomainTemplateTest.java new file mode 100644 index 0000000..99953aa --- /dev/null +++ b/parser/src/test/java/cn/qaiu/parser/PanDomainTemplateTest.java @@ -0,0 +1,73 @@ +package cn.qaiu.parser; + +import cn.qaiu.entity.ShareLinkInfo; +import org.junit.Test; + +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * @author QAIU + * @date 2024/8/8 2:39 + */ +public class PanDomainTemplateTest { + + @Test + public void normalizeShareLink() { + // 准备测试数据 + String testShareUrl = "https://test.lanzoux.com/s/someShareKey"; + + PanDomainTemplate template = PanDomainTemplate.fromShareUrl(testShareUrl); // 假设使用LZ网盘模板 + + // 调用normalizeShareLink方法 + ShareLinkInfo result = template.getShareLinkInfo(); + System.out.println(result); + // 断言结果是否符合预期 + assertNotNull("Result should not be null", result); + assertEquals("Share key should match", "someShareKey", result.getShareKey()); + assertEquals("Standard URL should be generated correctly", template.getStandardUrlTemplate().replace("{shareKey}", "someShareKey"), result.getStandardUrl()); + // 可以添加更多的断言来验证其他字段 + } + + @Test + public void fromShareUrl() throws InterruptedException { + // 准备测试数据 + String lzUrl = "https://wwn.lanzouy.com/ihLkw1gezutg"; + String cowUrl = "https://cowtransfer.com/s/9a644fe3e3a748"; + String ceUrl = "https://pan.huang1111.cn/s/g31PcQ"; + String wsUrl = "https://f.ws59.cn/f/f25625rv6p6"; + PanDomainTemplate.fromShareUrl(wsUrl).createTool() + .parse().onSuccess(System.out::println); + PanDomainTemplate.fromShareUrl(lzUrl).createTool() + .parse().onSuccess(System.out::println); + PanDomainTemplate.fromShareUrl(cowUrl).createTool() + .parse().onSuccess(System.out::println); + PanDomainTemplate.fromShareUrl(lzUrl).createTool() + .parse().onSuccess(System.out::println); + +// PanDomainTemplate.fromShortName("lz").generateShareLink("ihLkw1gezutg") +// .createTool().parse().onSuccess(System.out::println); +// PanDomainTemplate.LZ.generateShareLink("ihLkw1gezutg") +// .createTool().parse().onSuccess(System.out::println); + + + // 调用fromShareUrl方法 +// PanDomainTemplate resultTemplate = PanDomainTemplate.fromShareUrl(testShareUrl); +// System.out.println(resultTemplate.normalizeShareLink(testShareUrl)); +// System.out.println(resultTemplate.generateShareLink("xxx")); +// System.out.println(resultTemplate.createTool("xxx",null).parse() +// .onSuccess(System.out::println)); +// System.out.println(resultTemplate.getDisplayName()); +// System.out.println(resultTemplate.getStandardUrlTemplate()); +// System.out.println(resultTemplate.getRegexPattern()); +// +// // 断言结果是否符合预期 +// assertNotNull("Result should not be null", resultTemplate); +// assertEquals("Should return the correct template", PanDomainTemplate.LZ, resultTemplate); +// // 可以添加更多的断言来验证正则表达式匹配逻辑 +// new Scanner(System.in).nextLine(); + TimeUnit.SECONDS.sleep(5); + } +} diff --git a/pom.xml b/pom.xml index 34484b4..5303b13 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ - 0.1.7 + 0.1.8 17 17 17 diff --git a/web-front/package-lock.json b/web-front/package-lock.json index 139933b..5a92cbe 100644 --- a/web-front/package-lock.json +++ b/web-front/package-lock.json @@ -6086,7 +6086,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.8", "proxy-addr": "~2.0.7", "qs": "6.11.0", "range-parser": "~1.2.1", @@ -8746,8 +8746,8 @@ "dev": true }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.8.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", "dev": true }, diff --git a/web-service/src/main/java/cn/qaiu/lz/AppMain.java b/web-service/src/main/java/cn/qaiu/lz/AppMain.java index 29f4952..a6d05f2 100644 --- a/web-service/src/main/java/cn/qaiu/lz/AppMain.java +++ b/web-service/src/main/java/cn/qaiu/lz/AppMain.java @@ -2,6 +2,7 @@ package cn.qaiu.lz; import cn.qaiu.WebClientVertxInit; import cn.qaiu.db.pool.JDBCPoolInit; +import cn.qaiu.lz.common.cache.CacheConfigLoader; import cn.qaiu.vx.core.Deploy; import cn.qaiu.vx.core.util.ConfigConstant; import cn.qaiu.vx.core.util.VertxHolder; @@ -23,17 +24,20 @@ public class AppMain { } /** - * 初始化数据库 + * 初始化数据库/缓存等 * * @param jsonObject 配置 */ private static void exec(JsonObject jsonObject) { WebClientVertxInit.init(VertxHolder.getVertxInstance()); DatabindCodec.mapper().registerModule(new JavaTimeModule()); + // 数据库 if (jsonObject.getJsonObject(ConfigConstant.SERVER).getBoolean("enableDatabase")) { JDBCPoolInit.builder().config(jsonObject.getJsonObject("dataSource")).build().initPool(); } + // 缓存 + if (jsonObject.containsKey(ConfigConstant.CACHE)) { + CacheConfigLoader.init(jsonObject.getJsonObject(ConfigConstant.CACHE)); + } } - - } diff --git a/web-service/src/main/java/cn/qaiu/lz/common/cache/CacheConfigLoader.java b/web-service/src/main/java/cn/qaiu/lz/common/cache/CacheConfigLoader.java new file mode 100644 index 0000000..b891bca --- /dev/null +++ b/web-service/src/main/java/cn/qaiu/lz/common/cache/CacheConfigLoader.java @@ -0,0 +1,37 @@ +package cn.qaiu.lz.common.cache; + +import cn.qaiu.parser.PanDomainTemplate; +import io.vertx.core.json.JsonObject; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author QAIU + * @date 2024/9/12 7:38 + */ +public class CacheConfigLoader { + private static final Map CONFIGS = new HashMap<>(); + public static String TYPE; + public static Integer DEFAULT_DURATION; + + public static void init(JsonObject config) { + TYPE = config.getString("type"); + Integer defaultDuration = config.getInteger("defaultDuration"); + DEFAULT_DURATION = defaultDuration == null ? 60 : defaultDuration; + config.getJsonObject("duration").getMap().forEach((k,v) -> { + if (v == null) { + CONFIGS.put(k, DEFAULT_DURATION); + } else { + CONFIGS.put(k, (Integer) v); + } + }); + } + + public static Integer getDuration(PanDomainTemplate pdt) { + return CONFIGS.get(pdt.getShortName()); + } + public static Integer getDuration(String shortName) { + return CONFIGS.get(shortName); + } +} diff --git a/web-service/src/main/java/cn/qaiu/lz/common/cache/CacheManager.java b/web-service/src/main/java/cn/qaiu/lz/common/cache/CacheManager.java new file mode 100644 index 0000000..58cf53b --- /dev/null +++ b/web-service/src/main/java/cn/qaiu/lz/common/cache/CacheManager.java @@ -0,0 +1,52 @@ +package cn.qaiu.lz.common.cache; + +import cn.qaiu.db.pool.JDBCPoolInit; +import cn.qaiu.lz.web.model.CacheLinkInfo; +import io.vertx.core.Future; +import io.vertx.core.Promise; +import io.vertx.core.json.JsonObject; +import io.vertx.jdbcclient.JDBCPool; +import io.vertx.sqlclient.templates.SqlTemplate; + +import java.util.HashMap; +import java.util.Map; + +public class CacheManager { + private final JDBCPool jdbcPool = JDBCPoolInit.instance().getPool(); + + + public Future get(String cacheKey) { + String sql = "SELECT direct_link as directLink, expiration FROM cache_link_info WHERE share_key = #{share_key}"; + Map params = new HashMap<>(); + params.put("share_key", cacheKey); + Promise promise = Promise.promise(); + SqlTemplate.forQuery(jdbcPool, sql) + .mapTo(CacheLinkInfo.class) + .execute(params) + .onSuccess(rows->{ + CacheLinkInfo cacheHit; + if (rows.size() > 0) { + cacheHit = rows.iterator().next(); + cacheHit.setCacheHit(true); + } else { + cacheHit = new CacheLinkInfo(JsonObject.of("cacheHit", false)); + } + promise.complete(cacheHit); + }).onFailure(Throwable::printStackTrace); + return promise.future(); + } + + + // 插入或更新缓存数据 + public Future cacheShareLink(CacheLinkInfo cacheLinkInfo) { + String sql = "MERGE INTO cache_link_info (share_key, direct_link, expiration) " + + "KEY (share_key) " + + "VALUES (#{shareKey}, #{directLink}, #{expiration})"; + + // 直接传递 CacheLinkInfo 实体类 + return SqlTemplate.forUpdate(jdbcPool, sql) + .mapFrom(CacheLinkInfo.class) // 将实体类映射为 Tuple 参数 + .execute(cacheLinkInfo) + .mapEmpty(); + } +} diff --git a/web-service/src/main/java/cn/qaiu/lz/common/util/URLParamUtil.java b/web-service/src/main/java/cn/qaiu/lz/common/util/URLParamUtil.java new file mode 100644 index 0000000..5625e98 --- /dev/null +++ b/web-service/src/main/java/cn/qaiu/lz/common/util/URLParamUtil.java @@ -0,0 +1,76 @@ +package cn.qaiu.lz.common.util; + +import io.vertx.core.MultiMap; +import io.vertx.core.http.HttpServerRequest; + +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; + +/** + * 处理URL截断问题,拼接被截断的参数,特殊处理pwd参数。 + * + * @author QAIU + * @date 2024/9/13 + */ +public class URLParamUtil { + + /** + * 解析并处理截断的URL参数 + * + * @param request HttpServerRequest对象 + * @return 完整的URL字符串 + */ + public static String parserParams(HttpServerRequest request) { + String url = request.absoluteURI(); + MultiMap params = request.params(); + + // 处理URL截断的情况,例如: url='https://...&key=...&code=...' + if (params.contains("url")) { + String encodedUrl = params.get("url"); + url = handleTruncatedUrl(encodedUrl, params); + } + + return url; + } + + /** + * 处理被截断的URL,拼接所有参数,特殊处理pwd参数。 + * + * @param encodedUrl 被截断的url参数 + * @param params 请求的其他参数 + * @return 重新拼接后的完整URL + */ + private static String handleTruncatedUrl(String encodedUrl, MultiMap params) { + // 对URL进行解码,以便获取完整的URL + String decodedUrl = URLDecoder.decode(encodedUrl, StandardCharsets.UTF_8); + + // 如果URL已经包含查询参数,不需要额外拼接 + if (params.contains("pwd")) { + if (params.size() == 2) { + return decodedUrl; + } + } else { + if (params.size() == 1) { + return decodedUrl; + } + } + + // 拼接被截断的URL参数,忽略pwd参数 + StringBuilder urlBuilder = new StringBuilder(decodedUrl); + boolean firstParam = !decodedUrl.contains("?"); + + for (String paramName : params.names()) { + if (!paramName.equals("url") && !paramName.equals("pwd")) { // 忽略 "url" 和 "pwd" 参数 + if (firstParam) { + urlBuilder.append("?"); + firstParam = false; + } else { + urlBuilder.append("&"); + } + urlBuilder.append(paramName).append("=").append(params.get(paramName)); + } + } + + return urlBuilder.toString(); + } +} diff --git a/web-service/src/main/java/cn/qaiu/lz/web/http/ServerApi.java b/web-service/src/main/java/cn/qaiu/lz/web/http/ServerApi.java index 5a0db91..40117b1 100644 --- a/web-service/src/main/java/cn/qaiu/lz/web/http/ServerApi.java +++ b/web-service/src/main/java/cn/qaiu/lz/web/http/ServerApi.java @@ -1,13 +1,13 @@ package cn.qaiu.lz.web.http; -import cn.qaiu.parser.IPanTool; -import cn.qaiu.parser.impl.EcTool; -import cn.qaiu.parser.impl.QQTool; +import cn.qaiu.lz.common.util.URLParamUtil; +import cn.qaiu.lz.web.service.CacheService; +import cn.qaiu.parser.PanDomainTemplate; import cn.qaiu.vx.core.annotaions.RouteHandler; import cn.qaiu.vx.core.annotaions.RouteMapping; import cn.qaiu.vx.core.enums.RouteMethod; +import cn.qaiu.vx.core.util.AsyncServiceUtil; import cn.qaiu.vx.core.util.ResponseUtil; -import cn.qaiu.vx.core.util.VertxHolder; import io.vertx.core.Future; import io.vertx.core.Promise; import io.vertx.core.http.HttpServerRequest; @@ -24,33 +24,27 @@ import lombok.extern.slf4j.Slf4j; @RouteHandler("/") public class ServerApi { - @RouteMapping(value = "/parser", method = RouteMethod.GET, order = 4) - public Future parse(HttpServerResponse response, HttpServerRequest request, String url, String pwd) { + private final CacheService cacheService = AsyncServiceUtil.getAsyncServiceInstance(CacheService.class); + @RouteMapping(value = "/parser", method = RouteMethod.GET, order = 4) + public Future parse(HttpServerResponse response, HttpServerRequest request, String pwd) { Promise promise = Promise.promise(); - if (url.contains(EcTool.SHARE_URL_PREFIX)) { - // 默认读取Url参数会被截断手动获取一下其他参数 - 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 -> ResponseUtil.redirect(response, resUrl, promise)) + String url = URLParamUtil.parserParams(request); + + PanDomainTemplate panDomainTemplate = PanDomainTemplate.fromShareUrl(url).setShareLinkInfoPwd(pwd); + cacheService.getAndSaveCachedShareLink(panDomainTemplate) + .onSuccess(res -> ResponseUtil.redirect( + response.putHeader("nfd-cache-hit", res.getCacheHit().toString()) + .putHeader("nfd-cache-expires", res.getExpires()), + res.getDirectLink(), promise)) .onFailure(t -> promise.fail(t.fillInStackTrace())); return promise.future(); } @RouteMapping(value = "/json/parser", method = RouteMethod.GET, order = 3) - public Future parseJson(HttpServerRequest request, String url, String pwd) { - if (url.contains(EcTool.SHARE_URL_PREFIX)) { - // 默认读取Url参数会被截断手动获取一下其他参数 - url = EcTool.SHARE_URL_PREFIX + request.getParam("data"); - } - return IPanTool.shareURLPrefixMatching(url, pwd).parse(); + public Future parseJson(HttpServerRequest request, String pwd) { + String url = URLParamUtil.parserParams(request); + return PanDomainTemplate.fromShareUrl(url).setShareLinkInfoPwd(pwd).createTool().parse(); } @RouteMapping(value = "/json/:type/:key", method = RouteMethod.GET, order = 2) @@ -61,7 +55,8 @@ public class ServerApi { key = keys[0]; code = keys[1]; } - return IPanTool.typeMatching(type, key, code).parse(); + return PanDomainTemplate.fromShortName(type) + .generateShareLink(key).setShareLinkInfoPwd(code).createTool().parse(); } @RouteMapping(value = "/:type/:key", method = RouteMethod.GET, order = 1) @@ -74,8 +69,16 @@ public class ServerApi { code = keys[1]; } - IPanTool.typeMatching(type, key, code).parse() - .onSuccess(resUrl -> ResponseUtil.redirect(response, resUrl, promise)) + PanDomainTemplate panDomainTemplate = PanDomainTemplate + .fromShortName(type) + .generateShareLink(key) + .setShareLinkInfoPwd(code); + + cacheService.getAndSaveCachedShareLink(panDomainTemplate) + .onSuccess(res -> ResponseUtil.redirect( + response.putHeader("nfd-cache-hit", res.getCacheHit().toString()) + .putHeader("nfd-cache-expires", res.getExpires()), + res.getDirectLink(), promise)) .onFailure(t -> promise.fail(t.fillInStackTrace())); return promise.future(); } diff --git a/web-service/src/main/java/cn/qaiu/lz/web/model/CacheLinkInfo.java b/web-service/src/main/java/cn/qaiu/lz/web/model/CacheLinkInfo.java new file mode 100644 index 0000000..adc2d84 --- /dev/null +++ b/web-service/src/main/java/cn/qaiu/lz/web/model/CacheLinkInfo.java @@ -0,0 +1,56 @@ +package cn.qaiu.lz.web.model; + +import cn.qaiu.db.ddl.Length; +import cn.qaiu.db.ddl.Table; +import cn.qaiu.db.ddl.TableGenIgnore; +import cn.qaiu.lz.common.ToJson; +import io.vertx.codegen.annotations.DataObject; +import io.vertx.core.json.JsonObject; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author QAIU + * @date 2024/9/11 16:06 + */ +@Table(value = "cache_link_info", keyFields = "share_key") +@Data +@DataObject +@NoArgsConstructor +public class CacheLinkInfo implements ToJson { + + /** + * 缓存key: type@ShareKey; e.g. lz@xxxx + */ + @Length(varcharSize = 4096) + private String shareKey; + + /** + * 解析后的直链 + */ + @Length(varcharSize = 4096) + private String directLink; + + /** + * 是否命中缓存 + */ + @TableGenIgnore + private Boolean cacheHit; + + /** + * 到期时间 yyyy-MM-dd hh:mm:ss + */ + @TableGenIgnore + private String expires; + + /** + * 有效期 + */ + private Long expiration; + + + // 使用 JsonObject 构造 + public CacheLinkInfo(JsonObject json) { + CacheLinkInfoConverter.fromJson(json, this); + } +} diff --git a/web-service/src/main/java/cn/qaiu/lz/web/model/CacheLinkInfoConverter.java b/web-service/src/main/java/cn/qaiu/lz/web/model/CacheLinkInfoConverter.java new file mode 100644 index 0000000..5fc96ce --- /dev/null +++ b/web-service/src/main/java/cn/qaiu/lz/web/model/CacheLinkInfoConverter.java @@ -0,0 +1,23 @@ +package cn.qaiu.lz.web.model; + +import io.vertx.core.json.JsonObject; + +// CacheLinkInfoConverter.java +public class CacheLinkInfoConverter { + + public static void fromJson(JsonObject json, CacheLinkInfo obj) { + if (json.containsKey("shareKey")) { + obj.setShareKey(json.getString("shareKey")); + } + if (json.containsKey("directLink")) { + obj.setDirectLink(json.getString("directLink")); + } + if (json.containsKey("expires")) { + obj.setExpires(json.getString("expires")); + } + if (json.containsKey("expiration")) { + obj.setExpiration(json.getLong("expiration")); + } + obj.setCacheHit(json.getBoolean("cacheHit", false)); + } +} diff --git a/web-service/src/main/java/cn/qaiu/lz/web/service/CacheService.java b/web-service/src/main/java/cn/qaiu/lz/web/service/CacheService.java new file mode 100644 index 0000000..dc3bfb6 --- /dev/null +++ b/web-service/src/main/java/cn/qaiu/lz/web/service/CacheService.java @@ -0,0 +1,17 @@ +package cn.qaiu.lz.web.service; + +import cn.qaiu.lz.web.model.CacheLinkInfo; +import cn.qaiu.parser.PanDomainTemplate; +import cn.qaiu.vx.core.base.BaseAsyncService; +import io.vertx.codegen.annotations.ProxyGen; +import io.vertx.core.Future; + +/** + * @author QAIU + * @date 2024/9/12 8:26 + */ +@ProxyGen +public interface CacheService extends BaseAsyncService { + + Future getAndSaveCachedShareLink(PanDomainTemplate shareLinkInfo); +} diff --git a/web-service/src/main/java/cn/qaiu/lz/web/service/DbService.java b/web-service/src/main/java/cn/qaiu/lz/web/service/DbService.java index cde3827..61d1cbd 100644 --- a/web-service/src/main/java/cn/qaiu/lz/web/service/DbService.java +++ b/web-service/src/main/java/cn/qaiu/lz/web/service/DbService.java @@ -19,4 +19,5 @@ public interface DbService extends BaseAsyncService { Future sayOk2(String data, UserInfo holder); Future getStatisticsInfo(); + } diff --git a/web-service/src/main/java/cn/qaiu/lz/web/service/JdkProxyFactory.java b/web-service/src/main/java/cn/qaiu/lz/web/service/JdkProxyFactory.java deleted file mode 100644 index 8cc40c4..0000000 --- a/web-service/src/main/java/cn/qaiu/lz/web/service/JdkProxyFactory.java +++ /dev/null @@ -1,18 +0,0 @@ -package cn.qaiu.lz.web.service; - -import cn.qaiu.vx.core.util.CastUtil; - -import java.lang.reflect.Proxy; - -/** - * JDK代理类工厂 - */ -public class JdkProxyFactory { - public static T getProxy(T target) { - return CastUtil.cast(Proxy.newProxyInstance( - target.getClass().getClassLoader(), - target.getClass().getInterfaces(), - new ServiceJdkProxy<>(target)) - ); - } -} diff --git a/web-service/src/main/java/cn/qaiu/lz/web/service/impl/CacheServiceImpl.java b/web-service/src/main/java/cn/qaiu/lz/web/service/impl/CacheServiceImpl.java new file mode 100644 index 0000000..94d44ed --- /dev/null +++ b/web-service/src/main/java/cn/qaiu/lz/web/service/impl/CacheServiceImpl.java @@ -0,0 +1,65 @@ +package cn.qaiu.lz.web.service.impl; + +import cn.qaiu.entity.ShareLinkInfo; +import cn.qaiu.lz.common.cache.CacheConfigLoader; +import cn.qaiu.lz.common.cache.CacheManager; +import cn.qaiu.lz.web.model.CacheLinkInfo; +import cn.qaiu.lz.web.service.CacheService; +import cn.qaiu.parser.PanDomainTemplate; +import cn.qaiu.vx.core.annotaions.Service; +import io.vertx.core.Future; +import io.vertx.core.Promise; +import io.vertx.core.json.JsonObject; +import org.apache.commons.lang3.time.DateFormatUtils; + +import java.util.Date; + +@Service +public class CacheServiceImpl implements CacheService { + + private final CacheManager cacheManager = new CacheManager(); + + @Override + public Future getAndSaveCachedShareLink(PanDomainTemplate template) { + Promise promise = Promise.promise(); + + // 构建组合的缓存key + ShareLinkInfo shareLinkInfo = template.getShareLinkInfo(); + String cacheKey = generateCacheKey(shareLinkInfo.getType(), shareLinkInfo.getShareKey()); + // 尝试从缓存中获取 + cacheManager.get(cacheKey).onSuccess(result -> { + // 判断是否已过期 + // 未命中或者过期 + if (!result.getCacheHit() || result.getExpiration() < System.currentTimeMillis()) { + template.createTool().parse().onSuccess(redirectUrl -> { + long expires = System.currentTimeMillis() + + CacheConfigLoader.getDuration(shareLinkInfo.getType()) * 60 * 1000; + result.setDirectLink(redirectUrl); + // result.setExpires(generateDate(expires)); + promise.complete(result); + // 更新缓存 + // 将直链存储到缓存 + CacheLinkInfo cacheLinkInfo = new CacheLinkInfo(JsonObject.of( + "directLink", redirectUrl, + "expiration", expires, + "shareKey", cacheKey + )); + cacheManager.cacheShareLink(cacheLinkInfo).onFailure(Throwable::printStackTrace); + }).onFailure(promise::fail); + } else { + result.setExpires(generateDate(result.getExpiration())); + promise.complete(result); + } + }).onFailure(t -> promise.fail(t.fillInStackTrace())); + return promise.future(); + } + + private String generateCacheKey(String type, String shareKey) { + // 将type和shareKey组合成一个字符串作为缓存key + return type + ":" + shareKey; + } + + private String generateDate(Long ts) { + return DateFormatUtils.format(new Date(ts), "yyyy-MM-dd hh:mm:ss"); + } +} diff --git a/web-service/src/main/resources/app-dev.yml b/web-service/src/main/resources/app-dev.yml index 577ed07..00b1ba3 100644 --- a/web-service/src/main/resources/app-dev.yml +++ b/web-service/src/main/resources/app-dev.yml @@ -39,3 +39,23 @@ dataSource: driverClassName: org.h2.Driver username: root password: '123456' + +# 缓存配置 +cache: + type: h2db + # 默认时长: 单位分钟, 实际有效期分钟-1 + defaultDuration: 59 + duration: + ce: + cow: + ec: + fc: + fj: + iz: + le: 2879 + lz: + qq: + ws: + ye: + + diff --git a/web-service/src/main/resources/app.yml b/web-service/src/main/resources/app.yml index f128143..f20f511 100644 --- a/web-service/src/main/resources/app.yml +++ b/web-service/src/main/resources/app.yml @@ -1,6 +1,6 @@ # 要激活的配置: dev--连接本地数据库; prod连接线上数据库 active: dev # 版本号 -version_app: 0.1.7 +version_app: 0.1.8 # 公司名称 -> LOGO版权文字 copyright: QAIU diff --git a/web-service/src/main/resources/http-tools/pan-cx.http b/web-service/src/main/resources/http-tools/pan-cx.http new file mode 100644 index 0000000..e1b3fad --- /dev/null +++ b/web-service/src/main/resources/http-tools/pan-cx.http @@ -0,0 +1,10 @@ +###超星 + +### 直链 Referer +https://d0.ananas.chaoxing.com/download/8e8c9baca640d24680d974331390a158?at_=1717958244333&ak_=783925f9ba6eb2d0c711977b777a13e0&ad_=58ffecd38be494bea68f0cda68b18c0a&fn=testgles.c +Referer: https://pan-yz.chaoxing.com/external/m/file/1006748113111711744 + + + +### + diff --git a/web-service/src/main/resources/http-tools/pan-fj.http b/web-service/src/main/resources/http-tools/pan-fj.http index 6d18adf..74e2596 100644 --- a/web-service/src/main/resources/http-tools/pan-fj.http +++ b/web-service/src/main/resources/http-tools/pan-fj.http @@ -59,3 +59,10 @@ Origin:https://www.feijipan.com Referer:https://www.feijipan.com/ {"loginName":"","loginPwd":""} + + +### 240530 规则修改 +### https://share.feijipan.com/s/nMtCOXL +POST https://api.feijipan.com/ws/recommend/list?devType=6&devModel=Chrome&uuid=WQAl5yBy1naGudJEILBvE&extra=2×tamp=A8ECC3C7C50191ACEB9CB8444FD37624&shareId=nMtCOXL&type=0&offset=1&limit=60 + + diff --git a/web-service/src/main/resources/http-tools/test.http b/web-service/src/main/resources/http-tools/test.http index 07d1fd5..886624a 100644 --- a/web-service/src/main/resources/http-tools/test.http +++ b/web-service/src/main/resources/http-tools/test.http @@ -1,6 +1,6 @@ -### 蓝奏云 +### PASS 蓝奏云 # @no-redirect -GET http://127.0.0.1:6400/parser?url=https://lanzoux.com/ia2cntg +GET http://127.0.0.1:6400/parser?url=https://www.lanzoux.com/ia2cntg ### 蓝奏云 # @no-redirect @@ -20,7 +20,7 @@ GET http://127.0.0.1:6400/lz/icBp6qqj82b@QAIU ### 蓝奏云 GET http://127.0.0.1:6400/json/lz/ia2cntg -### 蓝奏云优享 +### PASS 蓝奏云优享 GET http://127.0.0.1:6400/json/iz/lGFndCM ### @@ -34,7 +34,7 @@ GET http://127.0.0.1:6400/json/parser?url=https://www.ilanzou.com/s/zHkna1S GET http://127.0.0.1:6400/json/cow/9a644fe3e3a748 Referer: https://cowtransfer.com/ -### 奶牛 +### PASS 奶牛 # @no-redirect GET http://127.0.0.1:6400/cow/e4f41b51b5da4f @@ -47,9 +47,9 @@ GET http://127.0.0.1:6400/parser?url=https://cowtransfer.com/s/9a644fe3e3a748 # @no-redirect GET http://127.0.0.1:6400/parser?url=https://goldrepo.cowtransfer.com/s/026a638795634b -### 移动云空间 https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=81027a5c99af5b11ca004966c945cce6W9Bf2&isShare=1 +### 移动云空间 PASS https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=81027a5c99af5b11ca004966c945cce6W9Bf2&isShare=1 # @no-redirect -GET http://127.0.0.1:6400/parser?url=https://www.ecpan.cn/web//yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=81027a5c99af5b11ca004966c945cce6W9Bf2&isShare=1 +GET http://127.0.0.1:6400/parser?url=https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=81027a5c99af5b11ca004966c945cce6W9Bf2&isShare=1 ### 移动云空间 https://www.ecpan.cn/drive/fileextoverrid.do?chainUrlTemplate=https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=aa0cae0164d8885e6d35826b5b2901eckbWJBalM&parentId=-1 # @no-redirect @@ -62,21 +62,7 @@ GET http://127.0.0.1:6400/json/ec/4b3d786755688b85c6eb0c04b9124f4dalzdaJpXHx@T6O #@no-redirect GET http://127.0.0.1:6400/parser?url=https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=4b3d786755688b85c6eb0c04b9124f4dalzdaJpXHx&isShare=1&pwd=T6O1RC - - -### UC网盘 -# @no-redirect -GET http://127.0.0.1:6400/uc/33197dd53ace4 - -### UC网盘 -GET http://127.0.0.1:6400/json/uc/33197dd53ace4 - -### UC网盘 -# @no-redirect -GET http://127.0.0.1:6400/parser?url=https://fast.uc.cn/s/33197dd53ace4 - - -### 小飞机盘https://share.feijipan.com/s/nMtCOXL +### 小飞机盘 PASS https://share.feijipan.com/s/nMtCOXL # @no-redirect GET http://127.0.0.1:6400/parser?url=https://share.feijipan.com/s/nMtCOXL @@ -98,7 +84,7 @@ GET http://127.0.0.1:6400/parser?url=https://v2.fangcloud.com/sharing/fb54bdf03c ### 360亿方云 GET http://127.0.0.1:6400/json/fc/fb54bdf03c66c04334fe3687a3 -### 360亿方云 +### 360亿方云 PASS # @no-redirect GET http://127.0.0.1:6400/fc/e5079007dc31226096628870c7@QAIU @@ -113,7 +99,7 @@ GET http://127.0.0.1:6400/json/ye/iaKtVv-6OECd@DcGe ### 123 GET https://lz.qaiu.top/json/ye/iaKtVv-6OECd@DcGe -### 123 +### 123 PASS # @no-redirect GET http://127.0.0.1:6400/ye/iaKtVv-qOECd @@ -121,7 +107,7 @@ GET http://127.0.0.1:6400/ye/iaKtVv-qOECd # @no-redirect GET http://127.0.0.1:6400/ye/Ev1lVv-t3SY3 -### 123 https://www.123pan.com/s/iaKtVv-6OECd.html +### 123 PASS https://www.123pan.com/s/iaKtVv-6OECd.html # @no-redirect GET http://127.0.0.1:6400/json/parser?url=https://www.123pan.com/s/iaKtVv-6OECd.html&pwd=DcGe @@ -131,29 +117,37 @@ GET http://127.0.0.1:6400/json/parser?url=https://www.123pan.com/s/iaKtVv-6OECd. GET http://127.0.0.1:6400/parser?url=https://www.123pan.com/s/zF07Vv-WkHWd.html&pwd=bios -### 联想乐云 +### 联想乐云 PASS # @no-redirect GET http://127.0.0.1:6400/parser?url=https://lecloud.lenovo.com/share/4DANWdRQsHHyiFB4a -### 联想乐云 +### 联想乐云 PASS # @no-redirect GET http://127.0.0.1:6400/le/4DANWdRQsHHyiFB4a -### 联想乐云 +### 联想乐云 PASS # @no-redirect GET http://127.0.0.1:6400/le/2RkKbLP9BrppS9b43@ex2b -### 联想乐云 +### 联想乐云 PASS GET http://127.0.0.1:6400/json/le/2RkKbLP9BrppS9b43@ex2b -### 文叔叔 -GET http://127.0.0.1:6400/json/parser?url=https://f.ws59.cn/f/e3peohu6192 +### PASS 文叔叔 +GET http://127.0.0.1:6400/json/parser?url=https://f.ws59.cn/f/f25625rv6p6 +### +https://f.wss.cc/f/f25625rv6p6 -### Cloudreve +### TODO Cloudreve GET http://127.0.0.1:6400/json/ce/https_pan.huang1111.cn_s_wDz5TK ### Cloudreve https://pan.huang1111.cn/s/g31PcQ -GET http://127.0.0.1:6400/json/parser?url=https://pan.huang1111.cn/s/g31PcQ&pwd=qaiu +GET http://127.0.0.1:6400/parser?url=https://pan.huang1111.cn/s/g31PcQ&pwd=qaiu + +### PASS QQ +# @no-redirect +GET http://127.0.0.1:6400/parser?url=https://iwx.mail.qq.com/ftn/download?func=3&key=c79c5732038ad41cf5ef1e3261663537f2cf4133636635371d4c484657050c0e5d561d070252021a0a0552024e5257530b4e0157540256525e5751565a053b374014540057560c070b511e53130d21f0361c829bf15b3e4a6355fbada6dd10dfdd11a33263663537386330&code=8c02cf57&k=c79c5732038ad41cf5ef1e3261663537f2cf4133636635371d4c484657050c0e5d561d070252021a0a0552024e5257530b4e0157540256525e5751565a053b374014540057560c070b511e53130d21f0361c829bf15b3e4a6355fbada6dd10dfdd11a33263663537386330 + + ### GET http://127.0.0.1:6400/v2/statisticsInfo