mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2026-04-11 11:26:55 +00:00
Compare commits
340 Commits
f6209a8959
...
v0.1.9b15
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83af09cf58 | ||
|
|
449475785f | ||
|
|
047a8eab89 | ||
|
|
93835bd990 | ||
|
|
93ab3f3f3f | ||
|
|
de7703be83 | ||
|
|
ce1c4ee669 | ||
|
|
c60d9fdd61 | ||
|
|
2033542f49 | ||
|
|
3775cd0259 | ||
|
|
bec342d778 | ||
|
|
6305d805dd | ||
|
|
2ada2fddf7 | ||
|
|
66ba8b7ee8 | ||
|
|
2edf235941 | ||
|
|
57ef723368 | ||
|
|
366658b471 | ||
|
|
2a3244a8fa | ||
|
|
9912e6fef1 | ||
|
|
d475dcbcdc | ||
|
|
5ffe94e3a4 | ||
|
|
5809d3b664 | ||
|
|
2bed42a088 | ||
|
|
97e912c216 | ||
|
|
f657aa183c | ||
|
|
cf634c4464 | ||
|
|
a9b5d9ab57 | ||
|
|
f82267f8a7 | ||
|
|
46bd5819b3 | ||
|
|
d10a55d8cb | ||
|
|
e74d5ea97e | ||
|
|
1dfdff7024 | ||
|
|
f5c81e1b8e | ||
|
|
804b8853d9 | ||
|
|
f87a66bc79 | ||
|
|
74b9cc438c | ||
|
|
7829174cc0 | ||
|
|
2e558df96b | ||
|
|
474eea5f80 | ||
|
|
fc50d1f5ba | ||
|
|
3b63f48dfa | ||
|
|
95697de1d0 | ||
|
|
1aa9ecdc45 | ||
|
|
0a3dbc6342 | ||
|
|
72f215c301 | ||
|
|
add90984a5 | ||
|
|
857ac7a2c9 | ||
|
|
b807223614 | ||
|
|
c775dabd7e | ||
|
|
5ee0aae6e1 | ||
|
|
5c8e4d7754 | ||
|
|
79123448f6 | ||
|
|
8e4328f1eb | ||
|
|
63c810dcbb | ||
|
|
c71d4cfa87 | ||
|
|
2b17567a1d | ||
|
|
ba5f88af43 | ||
|
|
782f47c7c5 | ||
|
|
8f92ce9292 | ||
|
|
1c3b5082a2 | ||
|
|
8535f49786 | ||
|
|
92043cf415 | ||
|
|
c97a235e23 | ||
|
|
97c2fe6784 | ||
|
|
1baa6e36b8 | ||
|
|
9dbc8718a8 | ||
|
|
3107281cdc | ||
|
|
c5b2340fe6 | ||
|
|
2f6f977adb | ||
|
|
902443b511 | ||
|
|
322f173104 | ||
|
|
c61ba45d59 | ||
|
|
684e8d4c84 | ||
|
|
a96bde812d | ||
|
|
c0276f97d6 | ||
|
|
f60d2573e6 | ||
|
|
7e746ded60 | ||
|
|
11669c07ed | ||
|
|
0c0e4c9cc1 | ||
|
|
a3ab467c74 | ||
|
|
acb646ee2d | ||
|
|
5a02c38d57 | ||
|
|
bd9ac79ec9 | ||
|
|
d6c8b2f476 | ||
|
|
b616c59f1b | ||
|
|
c6603c0d83 | ||
|
|
ae0a5644b5 | ||
|
|
d87ab43a9b | ||
|
|
cfe084cb07 | ||
|
|
75521c87ba | ||
|
|
67042b39b3 | ||
|
|
c65b55d0c6 | ||
|
|
8e938acdb2 | ||
|
|
5db5b3c75a | ||
|
|
291df9d984 | ||
|
|
323c62f4e6 | ||
|
|
03e2bbbb91 | ||
|
|
4b8660932a | ||
|
|
35c7746e38 | ||
|
|
e2155aec45 | ||
|
|
aaf34a460e | ||
|
|
1bb30a9ed2 | ||
|
|
b1c68aa865 | ||
|
|
8b57d04a2e | ||
|
|
1a57bddef7 | ||
|
|
1d30716aa6 | ||
|
|
a2aabfc601 | ||
|
|
f7040a3c1c | ||
|
|
40d4a1fcb5 | ||
|
|
e01bb2d8d7 | ||
|
|
d8a4d2e39f | ||
|
|
dbbd5759ab | ||
|
|
0a59ff2357 | ||
|
|
4e1eb6654d | ||
|
|
b588ba740f | ||
|
|
af605a2be7 | ||
|
|
b2630a7e35 | ||
|
|
211fe92a35 | ||
|
|
be168a9f0d | ||
|
|
977f5dc6dd | ||
|
|
131cb7085d | ||
|
|
7fbd446a2a | ||
|
|
7b64699782 | ||
|
|
bad56037c4 | ||
|
|
180e54f278 | ||
|
|
ae89e0be26 | ||
|
|
f19b366b1b | ||
|
|
6b1bd573c8 | ||
|
|
43c82f2f16 | ||
|
|
e478486d73 | ||
|
|
32e5c69735 | ||
|
|
4ec5b491c5 | ||
|
|
6a0e4a695d | ||
|
|
e6d69331be | ||
|
|
f16e196040 | ||
|
|
2f67e3b04d | ||
|
|
4663751924 | ||
|
|
e6ae7e2545 | ||
|
|
74ec248b8c | ||
|
|
cd04eead07 | ||
|
|
2b4340a5d6 | ||
|
|
ff6913264f | ||
|
|
ffd88841d5 | ||
|
|
0ed0d2ed82 | ||
|
|
c2387c47ee | ||
|
|
65cc78de7d | ||
|
|
8bedfcf794 | ||
|
|
1cd059f842 | ||
|
|
42438f2bbd | ||
|
|
1fb6e5d660 | ||
|
|
3fa0588e03 | ||
|
|
5a008f4ddd | ||
|
|
1a0b3a75c3 | ||
|
|
c5ad19881c | ||
|
|
6188e5f2c6 | ||
|
|
6f19fbd300 | ||
|
|
5307da24ed | ||
|
|
e65da6fe4a | ||
|
|
7ed83e9437 | ||
|
|
f008f490d9 | ||
|
|
01109dedbd | ||
|
|
f2a78d8937 | ||
|
|
eef56b308c | ||
|
|
c9ca47b27d | ||
|
|
35e54f5ad5 | ||
|
|
310d516aac | ||
|
|
aee84e3cb3 | ||
|
|
f9fd68c0f4 | ||
|
|
9761a9e2d9 | ||
|
|
8bed7487de | ||
|
|
2b084a3cc8 | ||
|
|
1caabd1368 | ||
|
|
b5397ba303 | ||
|
|
36f9b53f9b | ||
|
|
26fcbaa9a6 | ||
|
|
a8a5b6e3ab | ||
|
|
588e087941 | ||
|
|
fe71db0967 | ||
|
|
9436575dcb | ||
|
|
4ea380d28e | ||
|
|
1f46b477e3 | ||
|
|
f7e17a10b5 | ||
|
|
6e19e29857 | ||
|
|
f6b5d72784 | ||
|
|
68f1334907 | ||
|
|
a83f4e9a2f | ||
|
|
fcd8709c7d | ||
|
|
854d0ea241 | ||
|
|
cff223ecd2 | ||
|
|
8f14762f43 | ||
|
|
c37ae638d2 | ||
|
|
bcfa46ec43 | ||
|
|
42cfe4aacf | ||
|
|
64dba88881 | ||
|
|
3f300ded96 | ||
|
|
1ceafc5c08 | ||
|
|
93adb06083 | ||
|
|
3ef1c81d13 | ||
|
|
84b4deffc6 | ||
|
|
2e7f1a7e52 | ||
|
|
59bea226e5 | ||
|
|
0bbef0c7a9 | ||
|
|
fc91192d0e | ||
|
|
8dbcaed813 | ||
|
|
577ec46f71 | ||
|
|
65bf4534c7 | ||
|
|
5b666e1fec | ||
|
|
e839f56a91 | ||
|
|
c5a327e2e0 | ||
|
|
5b5a11ac36 | ||
|
|
0c3efe326f | ||
|
|
b9100fb934 | ||
|
|
74a98713c1 | ||
|
|
339b80bbea | ||
|
|
ce0c3b3a1e | ||
|
|
1e611dd85b | ||
|
|
0791114e92 | ||
|
|
c85b9a01fd | ||
|
|
9c2a0e7f46 | ||
|
|
8699aef0da | ||
|
|
749bc67212 | ||
|
|
67e24e846b | ||
|
|
091def569a | ||
|
|
fb0cf9102b | ||
|
|
69ce48adb4 | ||
|
|
6c3ab975cc | ||
|
|
f501935365 | ||
|
|
c3593f335e | ||
|
|
351bdd9c58 | ||
|
|
812042b95e | ||
|
|
9957fcbce8 | ||
|
|
acb2c00cca | ||
|
|
9655b8715e | ||
|
|
13de30e8b5 | ||
|
|
1cfeae2ad2 | ||
|
|
31b4190eaf | ||
|
|
380e1c7482 | ||
|
|
f202800a0a | ||
|
|
96d246e64f | ||
|
|
75b460f4b0 | ||
|
|
1ba7af485d | ||
|
|
76c16c6312 | ||
|
|
66a09a8f1c | ||
|
|
235fed1b4e | ||
|
|
f75921ba03 | ||
|
|
202b261a53 | ||
|
|
95c39d258e | ||
|
|
71b35e4651 | ||
|
|
2465a4d6d3 | ||
|
|
30d4ce4781 | ||
|
|
27be70ea00 | ||
|
|
0c22267eaa | ||
|
|
d4f4ec67c3 | ||
|
|
df74d9a117 | ||
|
|
2fc7556f39 | ||
|
|
418dcde15f | ||
|
|
59db290cae | ||
|
|
2992d3586f | ||
|
|
7b8eea4865 | ||
|
|
6b2bba2854 | ||
|
|
4666e942b4 | ||
|
|
daa3c940ee | ||
|
|
e54c15dad1 | ||
|
|
054024e3e8 | ||
|
|
5be8c4548f | ||
|
|
60bd8ecf21 | ||
|
|
25edeaf709 | ||
|
|
1e7525f908 | ||
|
|
341e33a845 | ||
|
|
f5ef8c07a5 | ||
|
|
76da12da65 | ||
|
|
dacf93bd06 | ||
|
|
498bce5255 | ||
|
|
d67df58bc6 | ||
|
|
751c7163fe | ||
|
|
9bd5da8ac3 | ||
|
|
769e8545b6 | ||
|
|
a30c037138 | ||
|
|
3c7fc752fc | ||
|
|
54576e8cbc | ||
|
|
f2bac57ed3 | ||
|
|
a3220c36c1 | ||
|
|
821fc945cf | ||
|
|
784b3334f4 | ||
|
|
57c3355d3e | ||
|
|
9275e28e15 | ||
|
|
a82596dc70 | ||
|
|
add27186bc | ||
|
|
087ad0c120 | ||
|
|
5cd1db7e4b | ||
|
|
3cdbac1603 | ||
|
|
7402dd76cc | ||
|
|
de2e47628a | ||
|
|
aad0098169 | ||
|
|
922ce84000 | ||
|
|
aabfcd8c8b | ||
|
|
0e15d9f309 | ||
|
|
5ac89ac085 | ||
|
|
b3ec433d33 | ||
|
|
207b4cf6b3 | ||
|
|
03fbb3ef15 | ||
|
|
4ce62d6b98 | ||
|
|
1f504a8cd5 | ||
|
|
dd52f5a61f | ||
|
|
8369212bd3 | ||
|
|
c67911f1bb | ||
|
|
af95de8d15 | ||
|
|
ba2bfbd11a | ||
|
|
cc4b0ae90e | ||
|
|
399f55cae6 | ||
|
|
981513a557 | ||
|
|
326cfa838c | ||
|
|
b576e06099 | ||
|
|
1ad788c63f | ||
|
|
faf4d8e7ef | ||
|
|
d77095fe87 | ||
|
|
1bd1f611fe | ||
|
|
38f13524a1 | ||
|
|
d5588e3850 | ||
|
|
b7c2887d0c | ||
|
|
1cba9df571 | ||
|
|
32fc530a17 | ||
|
|
c9436f0a47 | ||
|
|
b8174a16f4 | ||
|
|
2a44a55764 | ||
|
|
0ad72c9108 | ||
|
|
b8340949d5 | ||
|
|
6c3433595f | ||
|
|
3e5e42fef9 | ||
|
|
c19f584b0d | ||
|
|
0bbf022c5d | ||
|
|
4516aa8300 | ||
|
|
30a53dd47c | ||
|
|
6e77c21021 | ||
|
|
f035cf4088 | ||
|
|
af5cba5ed6 | ||
|
|
8bb3d26b4c | ||
|
|
ef0829ab8f | ||
|
|
13f3546700 | ||
|
|
0cccba7b3b |
17
.github/dependabot.yml
vendored
17
.github/dependabot.yml
vendored
@@ -1,17 +0,0 @@
|
|||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: "maven"
|
|
||||||
directory: "/"
|
|
||||||
open-pull-requests-limit: 10
|
|
||||||
ignore:
|
|
||||||
# 忽略通过 BOM 管理的 Vert.x 依赖
|
|
||||||
# 这些依赖的版本通过 vertx-dependencies BOM 统一管理
|
|
||||||
# 应该通过更新 pom.xml 中的 vertx.version 属性来更新这些依赖
|
|
||||||
- dependency-name: "io.vertx:vertx-web"
|
|
||||||
- dependency-name: "io.vertx:vertx-codegen"
|
|
||||||
- dependency-name: "io.vertx:vertx-config"
|
|
||||||
- dependency-name: "io.vertx:vertx-config-yaml"
|
|
||||||
- dependency-name: "io.vertx:vertx-service-proxy"
|
|
||||||
- dependency-name: "io.vertx:vertx-web-proxy"
|
|
||||||
- dependency-name: "io.vertx:vertx-web-client"
|
|
||||||
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -29,8 +29,6 @@ target/
|
|||||||
/src/logs/
|
/src/logs/
|
||||||
*.zip
|
*.zip
|
||||||
sdkTest.log
|
sdkTest.log
|
||||||
app.yml
|
|
||||||
app-local.yml
|
|
||||||
|
|
||||||
|
|
||||||
#some local files
|
#some local files
|
||||||
|
|||||||
54
.vscode/launch.json
vendored
54
.vscode/launch.json
vendored
@@ -1,4 +1,7 @@
|
|||||||
{
|
{
|
||||||
|
// 使用 IntelliSense 了解相关属性。
|
||||||
|
// 悬停以查看现有属性的描述。
|
||||||
|
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
@@ -7,12 +10,61 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"mainClass": "${file}"
|
"mainClass": "${file}"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "StringCase",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "cn.qaiu.vx.core.util.StringCase",
|
||||||
|
"projectName": "core"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "FCURLParser",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "cn.qaiu.parser.FCURLParser",
|
||||||
|
"projectName": "parser"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "QkTool",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "cn.qaiu.parser.impl.QkTool",
|
||||||
|
"projectName": "parser"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "WebClientExample",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "qaiu.web.test.WebClientExample",
|
||||||
|
"projectName": "parser"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "java",
|
"type": "java",
|
||||||
"name": "AppMain",
|
"name": "AppMain",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"mainClass": "cn.qaiu.lz.AppMain",
|
"mainClass": "cn.qaiu.lz.AppMain",
|
||||||
"projectName": "web-service"
|
"projectName": "web-service"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "TestJs",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "cn.qaiu.web.test.TestJs",
|
||||||
|
"projectName": "web-service"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "TestOS",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "cn.qaiu.web.test.TestOS",
|
||||||
|
"projectName": "web-service"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "WebProxyExamples",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "cn.qaiu.web.test.WebProxyExamples",
|
||||||
|
"projectName": "web-service"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
12
README.md
12
README.md
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
|
|
||||||
# netdisk-fast-download 网盘分享链接云解析服务
|
# netdisk-fast-download 网盘分享链接云解析服务
|
||||||
QQ交流群:1017480890
|
QQ群:1017480890
|
||||||
|
|
||||||
netdisk-fast-download网盘直链云解析(nfd云解析)能把网盘分享下载链接转化为直链,支持多款云盘,已支持蓝奏云/蓝奏云优享/奶牛快传/移动云云空间/小飞机盘/亿方云/123云盘/Cloudreve等,支持加密分享,以及部分网盘文件夹分享。
|
netdisk-fast-download网盘直链云解析(nfd云解析)能把网盘分享下载链接转化为直链,支持多款云盘,已支持蓝奏云/蓝奏云优享/奶牛快传/移动云云空间/小飞机盘/亿方云/123云盘/Cloudreve等,支持加密分享,以及部分网盘文件夹分享。
|
||||||
|
|
||||||
@@ -40,12 +40,12 @@ https://nfd-parser.github.io/nfd-preview/preview.html?src=https%3A%2F%2Flz.qaiu.
|
|||||||
|
|
||||||
**JavaScript解析器文档:** [JavaScript解析器开发指南](parser/doc/JAVASCRIPT_PARSER_GUIDE.md) | [自定义解析器扩展指南](parser/doc/CUSTOM_PARSER_GUIDE.md) | [快速开始](parser/doc/CUSTOM_PARSER_QUICKSTART.md)
|
**JavaScript解析器文档:** [JavaScript解析器开发指南](parser/doc/JAVASCRIPT_PARSER_GUIDE.md) | [自定义解析器扩展指南](parser/doc/CUSTOM_PARSER_GUIDE.md) | [快速开始](parser/doc/CUSTOM_PARSER_QUICKSTART.md)
|
||||||
|
|
||||||
**Playground功能:** [JS解析器演练场密码保护说明](web-service/doc/PLAYGROUND_PASSWORD_PROTECTION.md)
|
**Playground功能:** [JS解析器演练场密码保护说明](PLAYGROUND_PASSWORD_PROTECTION.md)
|
||||||
|
|
||||||
## 预览地址
|
## 预览地址
|
||||||
[预览地址1](https://lz.qaiu.top)
|
[预览地址1](https://lz.qaiu.top)
|
||||||
[预览地址2](https://lz0.qaiu.top)
|
[预览地址2](https://lz0.qaiu.top)
|
||||||
[天翼云盘/移动云盘限时体验版](https://189.qaiu.top)
|
[移动/联通/天翼云盘大文件试用版](https://189.qaiu.top)
|
||||||
|
|
||||||
main分支依赖JDK17, 提供了JDK11分支[main-jdk11](https://github.com/qaiu/netdisk-fast-download/tree/main-jdk11)
|
main分支依赖JDK17, 提供了JDK11分支[main-jdk11](https://github.com/qaiu/netdisk-fast-download/tree/main-jdk11)
|
||||||
**0.1.8及以上版本json接口格式有调整 参考json返回数据格式示例**
|
**0.1.8及以上版本json接口格式有调整 参考json返回数据格式示例**
|
||||||
@@ -88,16 +88,12 @@ main分支依赖JDK17, 提供了JDK11分支[main-jdk11](https://github.com/qaiu/
|
|||||||
- Onedrive-pod
|
- Onedrive-pod
|
||||||
- Dropbox-pdp
|
- Dropbox-pdp
|
||||||
- iCloud-pic
|
- iCloud-pic
|
||||||
### 专属版提供
|
### 仅专属版提供
|
||||||
- [夸克云盘-qk](https://pan.quark.cn/)
|
|
||||||
- [UC云盘-uc](https://fast.uc.cn/)
|
|
||||||
- [移动云盘-p139](https://yun.139.com/)
|
- [移动云盘-p139](https://yun.139.com/)
|
||||||
- [联通云盘-pwo](https://pan.wo.cn/)
|
- [联通云盘-pwo](https://pan.wo.cn/)
|
||||||
- [天翼云盘-p189](https://cloud.189.cn/)
|
- [天翼云盘-p189](https://cloud.189.cn/)
|
||||||
|
|
||||||
## API接口
|
## API接口
|
||||||
|
|
||||||
[api接口文档](https://nfdparser.apifox.cn/)
|
|
||||||
|
|
||||||
### 服务端口
|
### 服务端口
|
||||||
- **6400**: API 服务端口(建议使用 Nginx 代理)
|
- **6400**: API 服务端口(建议使用 Nginx 代理)
|
||||||
|
|||||||
@@ -128,9 +128,7 @@ public class ReverseProxyVerticle extends AbstractVerticle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private HttpServer getHttpsServer(JsonObject proxyConf) {
|
private HttpServer getHttpsServer(JsonObject proxyConf) {
|
||||||
HttpServerOptions httpServerOptions = new HttpServerOptions()
|
HttpServerOptions httpServerOptions = new HttpServerOptions();
|
||||||
.setCompressionSupported(true);
|
|
||||||
|
|
||||||
if (proxyConf.containsKey("ssl")) {
|
if (proxyConf.containsKey("ssl")) {
|
||||||
JsonObject sslConfig = proxyConf.getJsonObject("ssl");
|
JsonObject sslConfig = proxyConf.getJsonObject("ssl");
|
||||||
|
|
||||||
@@ -184,7 +182,6 @@ public class ReverseProxyVerticle extends AbstractVerticle {
|
|||||||
} else {
|
} else {
|
||||||
staticHandler = StaticHandler.create();
|
staticHandler = StaticHandler.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (staticConf.containsKey("directory-listing")) {
|
if (staticConf.containsKey("directory-listing")) {
|
||||||
staticHandler.setDirectoryListing(staticConf.getBoolean("directory-listing"));
|
staticHandler.setDirectoryListing(staticConf.getBoolean("directory-listing"));
|
||||||
} else if (staticConf.containsKey("index")) {
|
} else if (staticConf.containsKey("index")) {
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ public enum PanDomainTemplate {
|
|||||||
"lanzoug|" +
|
"lanzoug|" +
|
||||||
"lanzoum" +
|
"lanzoum" +
|
||||||
")\\.com/(.+/)?(?<KEY>.+)"),
|
")\\.com/(.+/)?(?<KEY>.+)"),
|
||||||
"https://w1.lanzn.com/{shareKey}",
|
"https://lanzoux.com/{shareKey}",
|
||||||
LzTool.class),
|
LzTool.class),
|
||||||
|
|
||||||
// https://www.feijix.com/s/
|
// https://www.feijix.com/s/
|
||||||
|
|||||||
@@ -244,17 +244,19 @@ public class JsHttpClient {
|
|||||||
|
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
if (data instanceof String) {
|
if (data instanceof String) {
|
||||||
return request.sendBuffer(Buffer.buffer((String) data));
|
request.sendBuffer(Buffer.buffer((String) data));
|
||||||
} else if (data instanceof Map) {
|
} else if (data instanceof Map) {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Map<String, String> mapData = (Map<String, String>) data;
|
Map<String, String> mapData = (Map<String, String>) data;
|
||||||
return request.sendForm(MultiMap.caseInsensitiveMultiMap().addAll(mapData));
|
request.sendForm(MultiMap.caseInsensitiveMultiMap().addAll(mapData));
|
||||||
} else {
|
} else {
|
||||||
return request.sendJson(data);
|
request.sendJson(data);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return request.send();
|
request.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return request.send();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,17 +276,19 @@ public class JsHttpClient {
|
|||||||
|
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
if (data instanceof String) {
|
if (data instanceof String) {
|
||||||
return request.sendBuffer(Buffer.buffer((String) data));
|
request.sendBuffer(Buffer.buffer((String) data));
|
||||||
} else if (data instanceof Map) {
|
} else if (data instanceof Map) {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Map<String, String> mapData = (Map<String, String>) data;
|
Map<String, String> mapData = (Map<String, String>) data;
|
||||||
return request.sendForm(MultiMap.caseInsensitiveMultiMap().addAll(mapData));
|
request.sendForm(MultiMap.caseInsensitiveMultiMap().addAll(mapData));
|
||||||
} else {
|
} else {
|
||||||
return request.sendJson(data);
|
request.sendJson(data);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return request.send();
|
request.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return request.send();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,17 +322,19 @@ public class JsHttpClient {
|
|||||||
|
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
if (data instanceof String) {
|
if (data instanceof String) {
|
||||||
return request.sendBuffer(Buffer.buffer((String) data));
|
request.sendBuffer(Buffer.buffer((String) data));
|
||||||
} else if (data instanceof Map) {
|
} else if (data instanceof Map) {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Map<String, String> mapData = (Map<String, String>) data;
|
Map<String, String> mapData = (Map<String, String>) data;
|
||||||
return request.sendForm(MultiMap.caseInsensitiveMultiMap().addAll(mapData));
|
request.sendForm(MultiMap.caseInsensitiveMultiMap().addAll(mapData));
|
||||||
} else {
|
} else {
|
||||||
return request.sendJson(data);
|
request.sendJson(data);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return request.send();
|
request.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return request.send();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ public class JsPlaygroundExecutor {
|
|||||||
playgroundLogger.infoJava("✅ Fetch API和Promise polyfill注入成功");
|
playgroundLogger.infoJava("✅ Fetch API和Promise polyfill注入成功");
|
||||||
}
|
}
|
||||||
|
|
||||||
playgroundLogger.infoJava("初始化成功");
|
playgroundLogger.infoJava("🔒 安全的JavaScript引擎初始化成功(演练场)");
|
||||||
|
|
||||||
// 执行JavaScript代码
|
// 执行JavaScript代码
|
||||||
engine.eval(jsCode);
|
engine.eval(jsCode);
|
||||||
|
|||||||
@@ -4,15 +4,13 @@ import cn.qaiu.entity.FileInfo;
|
|||||||
import cn.qaiu.entity.ShareLinkInfo;
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
import cn.qaiu.parser.PanBase;
|
import cn.qaiu.parser.PanBase;
|
||||||
import cn.qaiu.util.AESUtils;
|
import cn.qaiu.util.AESUtils;
|
||||||
import cn.qaiu.util.AcwScV2Generator;
|
|
||||||
import cn.qaiu.util.FileSizeConverter;
|
import cn.qaiu.util.FileSizeConverter;
|
||||||
import io.netty.handler.codec.http.cookie.DefaultCookie;
|
import cn.qaiu.util.UUIDUtil;
|
||||||
import io.vertx.core.Future;
|
import io.vertx.core.Future;
|
||||||
import io.vertx.core.MultiMap;
|
import io.vertx.core.MultiMap;
|
||||||
import io.vertx.core.Promise;
|
import io.vertx.core.Promise;
|
||||||
import io.vertx.core.json.JsonArray;
|
import io.vertx.core.json.JsonArray;
|
||||||
import io.vertx.core.json.JsonObject;
|
import io.vertx.core.json.JsonObject;
|
||||||
import io.vertx.ext.web.client.WebClientSession;
|
|
||||||
import io.vertx.uritemplate.UriTemplate;
|
import io.vertx.uritemplate.UriTemplate;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
@@ -20,12 +18,10 @@ import java.util.*;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 蓝奏云优享
|
* 蓝奏云优享
|
||||||
* v019b22
|
*
|
||||||
*/
|
*/
|
||||||
public class IzTool extends PanBase {
|
public class IzTool extends PanBase {
|
||||||
|
|
||||||
WebClientSession webClientSession = WebClientSession.create(clientNoRedirects);
|
|
||||||
|
|
||||||
private static final String API_URL_PREFIX = "https://api.ilanzou.com/unproved/";
|
private static final String API_URL_PREFIX = "https://api.ilanzou.com/unproved/";
|
||||||
|
|
||||||
private static final String FIRST_REQUEST_URL = API_URL_PREFIX + "recommend/list?devType=6&devModel=Chrome" +
|
private static final String FIRST_REQUEST_URL = API_URL_PREFIX + "recommend/list?devType=6&devModel=Chrome" +
|
||||||
@@ -74,19 +70,6 @@ public class IzTool extends PanBase {
|
|||||||
super(shareLinkInfo);
|
super(shareLinkInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setCookie(String html) {
|
|
||||||
int beginIndex = html.indexOf("arg1='") + 6;
|
|
||||||
String arg1 = html.substring(beginIndex, html.indexOf("';", beginIndex));
|
|
||||||
String acw_sc__v2 = AcwScV2Generator.acwScV2Simple(arg1);
|
|
||||||
// 创建一个 Cookie 并放入 CookieStore
|
|
||||||
DefaultCookie nettyCookie = new DefaultCookie("acw_sc__v2", acw_sc__v2);
|
|
||||||
nettyCookie.setDomain(".ilanzou.com"); // 设置域名
|
|
||||||
nettyCookie.setPath("/"); // 设置路径
|
|
||||||
nettyCookie.setSecure(false);
|
|
||||||
nettyCookie.setHttpOnly(false);
|
|
||||||
webClientSession.cookieStore().put(nettyCookie);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Future<String> parse() {
|
public Future<String> parse() {
|
||||||
String shareId = shareLinkInfo.getShareKey();
|
String shareId = shareLinkInfo.getShareKey();
|
||||||
|
|
||||||
@@ -97,100 +80,70 @@ public class IzTool extends PanBase {
|
|||||||
// POST https://api.ilanzou.com/ws/recommend/list?devType=6&devModel=Chrome&extra=2&shareId=146731&type=0&offset=1&limit=60
|
// POST https://api.ilanzou.com/ws/recommend/list?devType=6&devModel=Chrome&extra=2&shareId=146731&type=0&offset=1&limit=60
|
||||||
String url = StringUtils.isBlank(shareLinkInfo.getSharePassword()) ? FIRST_REQUEST_URL
|
String url = StringUtils.isBlank(shareLinkInfo.getSharePassword()) ? FIRST_REQUEST_URL
|
||||||
: (FIRST_REQUEST_URL + "&code=" + shareLinkInfo.getSharePassword());
|
: (FIRST_REQUEST_URL + "&code=" + shareLinkInfo.getSharePassword());
|
||||||
webClientSession.postAbs(UriTemplate.of(VIP_REQUEST_URL))
|
client.postAbs(UriTemplate.of(VIP_REQUEST_URL))
|
||||||
.setTemplateParam("uuid", uuid)
|
.setTemplateParam("uuid", uuid)
|
||||||
.setTemplateParam("ts", tsEncode)
|
.setTemplateParam("ts", tsEncode)
|
||||||
.send().onSuccess(r0 -> { // 忽略res
|
.send().onSuccess(r0 -> { // 忽略res
|
||||||
// 第一次请求 获取文件信息
|
// 第一次请求 获取文件信息
|
||||||
// POST https://api.feijipan.com/ws/recommend/list?devType=6&devModel=Chrome&extra=2&shareId=146731&type=0&offset=1&limit=60
|
// POST https://api.feijipan.com/ws/recommend/list?devType=6&devModel=Chrome&extra=2&shareId=146731&type=0&offset=1&limit=60
|
||||||
webClientSession.postAbs(UriTemplate.of(url))
|
client.postAbs(UriTemplate.of(url))
|
||||||
.putHeaders(header)
|
.putHeaders(header)
|
||||||
.setTemplateParam("shareId", shareId)
|
.setTemplateParam("shareId", shareId)
|
||||||
.setTemplateParam("uuid", uuid)
|
.setTemplateParam("uuid", uuid)
|
||||||
.setTemplateParam("ts", tsEncode)
|
.setTemplateParam("ts", tsEncode)
|
||||||
.send().onSuccess(res -> {
|
.send().onSuccess(res -> {
|
||||||
String resBody = asText(res);
|
JsonObject resJson = asJson(res);
|
||||||
// 检查是否包含 cookie 验证
|
if (resJson.getInteger("code") != 200) {
|
||||||
if (resBody.contains("var arg1='")) {
|
fail(FIRST_REQUEST_URL + " 返回异常: " + resJson);
|
||||||
webClientSession = WebClientSession.create(clientNoRedirects);
|
|
||||||
setCookie(resBody);
|
|
||||||
// 重新请求
|
|
||||||
webClientSession.postAbs(UriTemplate.of(url))
|
|
||||||
.putHeaders(header)
|
|
||||||
.setTemplateParam("shareId", shareId)
|
|
||||||
.setTemplateParam("uuid", uuid)
|
|
||||||
.setTemplateParam("ts", tsEncode)
|
|
||||||
.send().onSuccess(res2 -> {
|
|
||||||
handleParseResponse(asText(res2), shareId);
|
|
||||||
}).onFailure(handleFail(FIRST_REQUEST_URL));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
handleParseResponse(resBody, shareId);
|
if (resJson.getJsonArray("list").isEmpty()) {
|
||||||
|
fail(FIRST_REQUEST_URL + " 解析文件列表为空: " + resJson);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!resJson.containsKey("list") || resJson.getJsonArray("list").isEmpty()) {
|
||||||
|
fail(FIRST_REQUEST_URL + " 解析文件列表为空: " + resJson);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 文件Id
|
||||||
|
JsonObject fileInfo = resJson.getJsonArray("list").getJsonObject(0);
|
||||||
|
// 如果是目录返回目录ID
|
||||||
|
if (!fileInfo.containsKey("fileList") || fileInfo.getJsonArray("fileList").isEmpty()) {
|
||||||
|
fail(FIRST_REQUEST_URL + " 文件列表为空: " + fileInfo);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
JsonObject fileList = fileInfo.getJsonArray("fileList").getJsonObject(0);
|
||||||
|
if (fileList.getInteger("fileType") == 2) {
|
||||||
|
promise.complete(fileList.getInteger("folderId").toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String fileId = fileInfo.getString("fileIds");
|
||||||
|
String userId = fileInfo.getString("userId");
|
||||||
|
// 其他参数
|
||||||
|
// String fidEncode = AESUtils.encrypt2HexIz(fileId + "|");
|
||||||
|
String fidEncode = AESUtils.encrypt2HexIz(fileId + "|" + userId);
|
||||||
|
String auth = AESUtils.encrypt2HexIz(fileId + "|" + nowTs);
|
||||||
|
// 第二次请求
|
||||||
|
clientNoRedirects.getAbs(UriTemplate.of(SECOND_REQUEST_URL))
|
||||||
|
.setTemplateParam("fidEncode", fidEncode)
|
||||||
|
.setTemplateParam("uuid", uuid)
|
||||||
|
.setTemplateParam("ts", tsEncode)
|
||||||
|
.setTemplateParam("auth", auth)
|
||||||
|
.setTemplateParam("shareId", shareId)
|
||||||
|
.putHeaders(header).send().onSuccess(res2 -> {
|
||||||
|
MultiMap headers = res2.headers();
|
||||||
|
if (!headers.contains("Location")) {
|
||||||
|
fail(SECOND_REQUEST_URL + " 未找到重定向URL: \n" + headers);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
promise.complete(headers.get("Location"));
|
||||||
|
}).onFailure(handleFail(SECOND_REQUEST_URL));
|
||||||
}).onFailure(handleFail(FIRST_REQUEST_URL));
|
}).onFailure(handleFail(FIRST_REQUEST_URL));
|
||||||
});
|
});
|
||||||
return promise.future();
|
return promise.future();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleParseResponse(String resBody, String shareId) {
|
|
||||||
JsonObject resJson;
|
|
||||||
try {
|
|
||||||
resJson = new JsonObject(resBody);
|
|
||||||
} catch (Exception e) {
|
|
||||||
fail(FIRST_REQUEST_URL + " 解析JSON失败: " + resBody);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (resJson.isEmpty()) {
|
|
||||||
fail(FIRST_REQUEST_URL + " 返回内容为空");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (resJson.getInteger("code") != 200) {
|
|
||||||
fail(FIRST_REQUEST_URL + " 返回异常: " + resJson);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (resJson.getJsonArray("list").isEmpty()) {
|
|
||||||
fail(FIRST_REQUEST_URL + " 解析文件列表为空: " + resJson);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!resJson.containsKey("list") || resJson.getJsonArray("list").isEmpty()) {
|
|
||||||
fail(FIRST_REQUEST_URL + " 解析文件列表为空: " + resJson);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 文件Id
|
|
||||||
JsonObject fileInfo = resJson.getJsonArray("list").getJsonObject(0);
|
|
||||||
// 如果是目录返回目录ID
|
|
||||||
if (!fileInfo.containsKey("fileList") || fileInfo.getJsonArray("fileList").isEmpty()) {
|
|
||||||
fail(FIRST_REQUEST_URL + " 文件列表为空: " + fileInfo);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
JsonObject fileList = fileInfo.getJsonArray("fileList").getJsonObject(0);
|
|
||||||
if (fileList.getInteger("fileType") == 2) {
|
|
||||||
promise.complete(fileList.getInteger("folderId").toString());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String fileId = fileInfo.getString("fileIds");
|
|
||||||
String userId = fileInfo.getString("userId");
|
|
||||||
// 其他参数
|
|
||||||
// String fidEncode = AESUtils.encrypt2HexIz(fileId + "|");
|
|
||||||
String fidEncode = AESUtils.encrypt2HexIz(fileId + "|" + userId);
|
|
||||||
String auth = AESUtils.encrypt2HexIz(fileId + "|" + nowTs);
|
|
||||||
// 第二次请求
|
|
||||||
webClientSession.getAbs(UriTemplate.of(SECOND_REQUEST_URL))
|
|
||||||
.setTemplateParam("fidEncode", fidEncode)
|
|
||||||
.setTemplateParam("uuid", uuid)
|
|
||||||
.setTemplateParam("ts", tsEncode)
|
|
||||||
.setTemplateParam("auth", auth)
|
|
||||||
.setTemplateParam("shareId", shareId)
|
|
||||||
.putHeaders(header).send().onSuccess(res2 -> {
|
|
||||||
MultiMap headers = res2.headers();
|
|
||||||
if (!headers.contains("Location")) {
|
|
||||||
fail(SECOND_REQUEST_URL + " 未找到重定向URL: \n" + headers);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
promise.complete(headers.get("Location"));
|
|
||||||
}).onFailure(handleFail(SECOND_REQUEST_URL));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Future<List<FileInfo>> parseFileList() {
|
public Future<List<FileInfo>> parseFileList() {
|
||||||
Promise<List<FileInfo>> promise = Promise.promise();
|
Promise<List<FileInfo>> promise = Promise.promise();
|
||||||
@@ -221,7 +174,7 @@ public class IzTool extends PanBase {
|
|||||||
log.debug("开始解析目录: {}, shareId: {}, uuid: {}, ts: {}", id, shareId, uuid, tsEncode);
|
log.debug("开始解析目录: {}, shareId: {}, uuid: {}, ts: {}", id, shareId, uuid, tsEncode);
|
||||||
// 开始解析目录: 164312216, shareId: bPMsbg5K, uuid: 0fmVWTx2Ea4zFwkpd7KXf, ts: 20865d7b7f00828279f437cd1f097860
|
// 开始解析目录: 164312216, shareId: bPMsbg5K, uuid: 0fmVWTx2Ea4zFwkpd7KXf, ts: 20865d7b7f00828279f437cd1f097860
|
||||||
// 拿到目录ID
|
// 拿到目录ID
|
||||||
webClientSession.postAbs(UriTemplate.of(FILE_LIST_URL))
|
client.postAbs(UriTemplate.of(FILE_LIST_URL))
|
||||||
.putHeaders(header)
|
.putHeaders(header)
|
||||||
.setTemplateParam("shareId", shareId)
|
.setTemplateParam("shareId", shareId)
|
||||||
.setTemplateParam("uuid", uuid)
|
.setTemplateParam("uuid", uuid)
|
||||||
@@ -311,7 +264,7 @@ public class IzTool extends PanBase {
|
|||||||
public Future<String> parseById() {
|
public Future<String> parseById() {
|
||||||
// 第二次请求
|
// 第二次请求
|
||||||
JsonObject paramJson = (JsonObject)shareLinkInfo.getOtherParam().get("paramJson");
|
JsonObject paramJson = (JsonObject)shareLinkInfo.getOtherParam().get("paramJson");
|
||||||
webClientSession.getAbs(UriTemplate.of(SECOND_REQUEST_URL))
|
clientNoRedirects.getAbs(UriTemplate.of(SECOND_REQUEST_URL))
|
||||||
.setTemplateParam("fidEncode", paramJson.getString("fidEncode"))
|
.setTemplateParam("fidEncode", paramJson.getString("fidEncode"))
|
||||||
.setTemplateParam("uuid", paramJson.getString("uuid"))
|
.setTemplateParam("uuid", paramJson.getString("uuid"))
|
||||||
.setTemplateParam("ts", paramJson.getString("ts"))
|
.setTemplateParam("ts", paramJson.getString("ts"))
|
||||||
|
|||||||
@@ -1,72 +1,30 @@
|
|||||||
package cn.qaiu.parser.impl;
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
import cn.qaiu.entity.FileInfo;
|
|
||||||
import cn.qaiu.entity.ShareLinkInfo;
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
import cn.qaiu.parser.PanBase;
|
import cn.qaiu.parser.PanBase;
|
||||||
import cn.qaiu.util.FileSizeConverter;
|
|
||||||
import io.vertx.core.Future;
|
import io.vertx.core.Future;
|
||||||
import io.vertx.core.MultiMap;
|
|
||||||
import io.vertx.core.Promise;
|
|
||||||
import io.vertx.core.json.JsonArray;
|
import io.vertx.core.json.JsonArray;
|
||||||
import io.vertx.core.json.JsonObject;
|
import io.vertx.core.json.JsonObject;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Base64;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <a href="https://lecloud.lenovo.com/">联想乐云</a>
|
* <a href="https://lecloud.lenovo.com/">联想乐云</a>
|
||||||
*/
|
*/
|
||||||
public class LeTool extends PanBase {
|
public class LeTool extends PanBase {
|
||||||
private static final String API_URL_PREFIX = "https://lecloud.lenovo.com/mshare/api/clouddiskapi/share/public/v1/";
|
private static final String API_URL_PREFIX = "https://lecloud.lenovo.com/share/api/clouddiskapi/share/public/v1/";
|
||||||
private static final String DEFAULT_FILE_TYPE = "file";
|
|
||||||
private static final int FILE_TYPE_DIRECTORY = 0; // 目录类型
|
|
||||||
|
|
||||||
private static final MultiMap HEADERS;
|
|
||||||
|
|
||||||
static {
|
|
||||||
HEADERS = MultiMap.caseInsensitiveMultiMap();
|
|
||||||
HEADERS.set("Accept", "application/json, text/plain, */*");
|
|
||||||
HEADERS.set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6");
|
|
||||||
HEADERS.set("Cache-Control", "no-cache");
|
|
||||||
HEADERS.set("Connection", "keep-alive");
|
|
||||||
HEADERS.set("Content-Type", "application/json");
|
|
||||||
HEADERS.set("DNT", "1");
|
|
||||||
HEADERS.set("Origin", "https://lecloud.lenovo.com");
|
|
||||||
HEADERS.set("Pragma", "no-cache");
|
|
||||||
HEADERS.set("Sec-Fetch-Dest", "empty");
|
|
||||||
HEADERS.set("Sec-Fetch-Mode", "cors");
|
|
||||||
HEADERS.set("Sec-Fetch-Site", "same-origin");
|
|
||||||
HEADERS.set("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1 Edg/143.0.0.0");
|
|
||||||
}
|
|
||||||
|
|
||||||
public LeTool(ShareLinkInfo shareLinkInfo) {
|
public LeTool(ShareLinkInfo shareLinkInfo) {
|
||||||
super(shareLinkInfo);
|
super(shareLinkInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取干净的 shareId(去掉可能的查询参数)
|
|
||||||
* URL 如 https://lecloud.lenovo.com/share/5eoN3RA5PLhQcH4zE?path=... 会导致 shareKey 包含查询参数
|
|
||||||
*/
|
|
||||||
private String getCleanShareId() {
|
|
||||||
String shareKey = shareLinkInfo.getShareKey();
|
|
||||||
if (shareKey != null && shareKey.contains("?")) {
|
|
||||||
return shareKey.split("\\?")[0];
|
|
||||||
}
|
|
||||||
return shareKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Future<String> parse() {
|
public Future<String> parse() {
|
||||||
final String dataKey = getCleanShareId();
|
final String dataKey = shareLinkInfo.getShareKey();
|
||||||
final String pwd = shareLinkInfo.getSharePassword();
|
final String pwd = shareLinkInfo.getSharePassword();
|
||||||
// {"shareId":"xxx","password":"xxx","directoryId":"-1"}
|
// {"shareId":"xxx","password":"xxx","directoryId":"-1"}
|
||||||
String apiUrl1 = API_URL_PREFIX + "shareInfo";
|
String apiUrl1 = API_URL_PREFIX + "shareInfo";
|
||||||
client.postAbs(apiUrl1)
|
client.postAbs(apiUrl1)
|
||||||
.putHeaders(HEADERS)
|
.sendJsonObject(JsonObject.of("shareId", dataKey, "password", pwd, "directoryId", -1))
|
||||||
.sendJsonObject(JsonObject.of("shareId", dataKey, "password", pwd, "directoryId", "-1"))
|
|
||||||
.onSuccess(res -> {
|
.onSuccess(res -> {
|
||||||
JsonObject resJson = asJson(res);
|
JsonObject resJson = asJson(res);
|
||||||
if (resJson.containsKey("result")) {
|
if (resJson.containsKey("result")) {
|
||||||
@@ -86,19 +44,7 @@ public class LeTool extends PanBase {
|
|||||||
}
|
}
|
||||||
JsonObject fileInfoJson = files.getJsonObject(0);
|
JsonObject fileInfoJson = files.getJsonObject(0);
|
||||||
if (fileInfoJson != null) {
|
if (fileInfoJson != null) {
|
||||||
// Extract and populate FileInfo
|
// TODO 文件大小fileSize和文件名fileName
|
||||||
FileInfo fileInfo = createFileInfo(fileInfoJson);
|
|
||||||
shareLinkInfo.getOtherParam().put("fileInfo", fileInfo);
|
|
||||||
|
|
||||||
// 判断是否为目录
|
|
||||||
Integer fileType = fileInfoJson.getInteger("fileType");
|
|
||||||
if (fileType != null && fileType == FILE_TYPE_DIRECTORY) {
|
|
||||||
// 如果是目录,返回目录ID
|
|
||||||
String fileId = fileInfoJson.getString("fileId");
|
|
||||||
promise.complete(fileId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String fileId = fileInfoJson.getString("fileId");
|
String fileId = fileInfoJson.getString("fileId");
|
||||||
// 根据文件ID获取跳转链接
|
// 根据文件ID获取跳转链接
|
||||||
getDownURL(dataKey, fileId);
|
getDownURL(dataKey, fileId);
|
||||||
@@ -113,205 +59,13 @@ public class LeTool extends PanBase {
|
|||||||
return promise.future();
|
return promise.future();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Future<List<FileInfo>> parseFileList() {
|
|
||||||
Promise<List<FileInfo>> listPromise = Promise.promise();
|
|
||||||
|
|
||||||
String dataKey = getCleanShareId();
|
|
||||||
|
|
||||||
// 如果参数里的目录ID不为空,则直接解析目录
|
|
||||||
String dirId = (String) shareLinkInfo.getOtherParam().get("dirId");
|
|
||||||
if (dirId == null || dirId.isEmpty()) {
|
|
||||||
// 如果没有指定目录ID,使用根目录ID "-1"
|
|
||||||
dirId = "-1";
|
|
||||||
}
|
|
||||||
|
|
||||||
// 直接请求shareInfo接口解析目录
|
|
||||||
parseDirectory(dirId, dataKey, listPromise);
|
|
||||||
return listPromise.future();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解析目录下的文件列表
|
|
||||||
*/
|
|
||||||
private void parseDirectory(String directoryId, String shareId, Promise<List<FileInfo>> promise) {
|
|
||||||
String pwd = shareLinkInfo.getSharePassword();
|
|
||||||
if (pwd == null) {
|
|
||||||
pwd = "";
|
|
||||||
}
|
|
||||||
String apiUrl = API_URL_PREFIX + "shareInfo";
|
|
||||||
|
|
||||||
JsonObject requestBody = JsonObject.of("shareId", shareId, "password", pwd, "directoryId", directoryId);
|
|
||||||
log.info("解析目录请求: url={}, body={}", apiUrl, requestBody.encode());
|
|
||||||
|
|
||||||
client.postAbs(apiUrl)
|
|
||||||
.putHeaders(HEADERS)
|
|
||||||
.sendJsonObject(requestBody)
|
|
||||||
.onSuccess(res -> {
|
|
||||||
JsonObject resJson = asJson(res);
|
|
||||||
|
|
||||||
if (!resJson.containsKey("result") || !resJson.getBoolean("result")) {
|
|
||||||
promise.fail("解析目录失败: " + resJson.encode());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonObject dataJson = resJson.getJsonObject("data");
|
|
||||||
if (!dataJson.getBoolean("passwordVerified")) {
|
|
||||||
promise.fail("密码验证失败");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonArray files = dataJson.getJsonArray("files");
|
|
||||||
if (files == null || files.isEmpty()) {
|
|
||||||
promise.complete(new ArrayList<>());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<FileInfo> fileList = new ArrayList<>();
|
|
||||||
for (int i = 0; i < files.size(); i++) {
|
|
||||||
JsonObject fileJson = files.getJsonObject(i);
|
|
||||||
FileInfo fileInfo = createFileInfoForList(fileJson, shareId);
|
|
||||||
fileList.add(fileInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
promise.complete(fileList);
|
|
||||||
})
|
|
||||||
.onFailure(err -> {
|
|
||||||
log.error("解析目录请求失败: {}", err.getMessage());
|
|
||||||
promise.fail(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 为文件列表创建 FileInfo 对象
|
|
||||||
*/
|
|
||||||
private FileInfo createFileInfoForList(JsonObject fileJson, String shareId) {
|
|
||||||
FileInfo fileInfo = new FileInfo();
|
|
||||||
|
|
||||||
try {
|
|
||||||
String fileId = fileJson.getString("fileId");
|
|
||||||
String fileName = fileJson.getString("fileName");
|
|
||||||
Long fileSize = fileJson.getLong("fileSize");
|
|
||||||
Integer fileType = fileJson.getInteger("fileType");
|
|
||||||
|
|
||||||
fileInfo.setFileId(fileId);
|
|
||||||
fileInfo.setFileName(fileName);
|
|
||||||
fileInfo.setPanType(shareLinkInfo.getType());
|
|
||||||
|
|
||||||
// 判断是否为目录
|
|
||||||
if (fileType != null && fileType == FILE_TYPE_DIRECTORY) {
|
|
||||||
// 目录类型
|
|
||||||
fileInfo.setFileType("folder");
|
|
||||||
fileInfo.setSize(0L);
|
|
||||||
fileInfo.setSizeStr("0B");
|
|
||||||
// 设置目录解析的URL - fileId 需要进行 URL 编码以保持特殊字符的编码状态
|
|
||||||
try {
|
|
||||||
String encodedFileId = URLEncoder.encode(fileId, "UTF-8");
|
|
||||||
fileInfo.setParserUrl(String.format("%s/v2/getFileList?url=%s&dirId=%s",
|
|
||||||
getDomainName(),
|
|
||||||
shareLinkInfo.getShareUrl(),
|
|
||||||
encodedFileId));
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
log.error("URL编码失败: {}", e.getMessage());
|
|
||||||
// 降级方案:直接使用原始 fileId
|
|
||||||
fileInfo.setParserUrl(String.format("%s/v2/getFileList?url=%s&dirId=%s",
|
|
||||||
getDomainName(),
|
|
||||||
shareLinkInfo.getShareUrl(),
|
|
||||||
fileId));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 文件类型
|
|
||||||
fileInfo.setFileType(fileType != null ? String.valueOf(fileType) : DEFAULT_FILE_TYPE);
|
|
||||||
fileInfo.setSize(fileSize);
|
|
||||||
fileInfo.setSizeStr(FileSizeConverter.convertToReadableSize(fileSize));
|
|
||||||
|
|
||||||
// 创建参数JSON并编码为Base64
|
|
||||||
JsonObject paramJson = JsonObject.of(
|
|
||||||
"shareId", shareId,
|
|
||||||
"fileId", fileId
|
|
||||||
);
|
|
||||||
String paramBase64 = Base64.getEncoder().encodeToString(paramJson.encode().getBytes());
|
|
||||||
|
|
||||||
// 设置解析URL和预览URL
|
|
||||||
fileInfo.setParserUrl(String.format("%s/v2/redirectUrl/%s/%s",
|
|
||||||
getDomainName(),
|
|
||||||
shareLinkInfo.getType(),
|
|
||||||
paramBase64))
|
|
||||||
.setPreviewUrl(String.format("%s/v2/viewUrl/%s/%s",
|
|
||||||
getDomainName(),
|
|
||||||
shareLinkInfo.getType(),
|
|
||||||
paramBase64));
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn("创建文件信息失败: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
return fileInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Future<String> parseById() {
|
|
||||||
Promise<String> parsePromise = Promise.promise();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 从参数中获取解析所需的信息
|
|
||||||
JsonObject paramJson = (JsonObject) shareLinkInfo.getOtherParam().get("paramJson");
|
|
||||||
String shareId = paramJson.getString("shareId");
|
|
||||||
String fileId = paramJson.getString("fileId");
|
|
||||||
|
|
||||||
// 调用获取下载链接
|
|
||||||
getDownURLForById(shareId, fileId, parsePromise);
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
parsePromise.fail("解析参数失败: " + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsePromise.future();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据文件ID获取下载URL (用于 parseById)
|
|
||||||
*/
|
|
||||||
private void getDownURLForById(String shareId, String fileId, Promise<String> promise) {
|
|
||||||
String uuid = UUID.randomUUID().toString();
|
|
||||||
JsonArray fileIds = JsonArray.of(fileId);
|
|
||||||
String apiUrl = API_URL_PREFIX + "packageDownloadWithFileIds";
|
|
||||||
|
|
||||||
client.postAbs(apiUrl)
|
|
||||||
.putHeaders(HEADERS)
|
|
||||||
.sendJsonObject(JsonObject.of("fileIds", fileIds, "shareId", shareId, "browserId", uuid))
|
|
||||||
.onSuccess(res -> {
|
|
||||||
JsonObject resJson = asJson(res);
|
|
||||||
if (resJson.containsKey("result")) {
|
|
||||||
if (resJson.getBoolean("result")) {
|
|
||||||
JsonObject dataJson = resJson.getJsonObject("data");
|
|
||||||
String downloadUrl = dataJson.getString("downloadUrl");
|
|
||||||
if (downloadUrl == null) {
|
|
||||||
promise.fail("Result JSON数据异常: downloadUrl不存在");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 获取重定向链接
|
|
||||||
clientNoRedirects.getAbs(downloadUrl).send()
|
|
||||||
.onSuccess(res2 -> promise.complete(res2.headers().get("Location")))
|
|
||||||
.onFailure(err -> promise.fail(err));
|
|
||||||
} else {
|
|
||||||
promise.fail(resJson.getString("errcode") + ": " + resJson.getString("errmsg"));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
promise.fail("Result JSON数据异常: result字段不存在");
|
|
||||||
}
|
|
||||||
}).onFailure(err -> promise.fail(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void getDownURL(String key, String fileId) {
|
private void getDownURL(String key, String fileId) {
|
||||||
String uuid = UUID.randomUUID().toString();
|
String uuid = UUID.randomUUID().toString();
|
||||||
JsonArray fileIds = JsonArray.of(fileId);
|
JsonArray fileIds = JsonArray.of(fileId);
|
||||||
String apiUrl2 = API_URL_PREFIX + "packageDownloadWithFileIds";
|
String apiUrl2 = API_URL_PREFIX + "packageDownloadWithFileIds";
|
||||||
// {"fileIds":[123],"shareId":"xxx","browserId":"uuid"}
|
// {"fileIds":[123],"shareId":"xxx","browserId":"uuid"}
|
||||||
client.postAbs(apiUrl2)
|
client.postAbs(apiUrl2)
|
||||||
.putHeaders(HEADERS)
|
.sendJsonObject(JsonObject.of("fileIds", fileIds, "shareId", key, "browserId", uuid))
|
||||||
.sendJsonObject(JsonObject.of("fileIds", fileIds, "shareId", key, "browserId", uuid))
|
|
||||||
.onSuccess(res -> {
|
.onSuccess(res -> {
|
||||||
JsonObject resJson = asJson(res);
|
JsonObject resJson = asJson(res);
|
||||||
if (resJson.containsKey("result")) {
|
if (resJson.containsKey("result")) {
|
||||||
@@ -335,51 +89,4 @@ public class LeTool extends PanBase {
|
|||||||
}
|
}
|
||||||
}).onFailure(handleFail(apiUrl2));
|
}).onFailure(handleFail(apiUrl2));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create FileInfo object from JSON response
|
|
||||||
* Uses exact field names from the API response without fallback checks
|
|
||||||
*/
|
|
||||||
private FileInfo createFileInfo(JsonObject fileInfoJson) {
|
|
||||||
FileInfo fileInfo = new FileInfo();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Set fileId
|
|
||||||
String fileId = fileInfoJson.getString("fileId");
|
|
||||||
if (fileId != null) {
|
|
||||||
fileInfo.setFileId(fileId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set fileName
|
|
||||||
String fileName = fileInfoJson.getString("fileName");
|
|
||||||
if (fileName != null) {
|
|
||||||
fileInfo.setFileName(fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set file size
|
|
||||||
Long fileSize = fileInfoJson.getLong("fileSize");
|
|
||||||
if (fileSize != null) {
|
|
||||||
fileInfo.setSize(fileSize);
|
|
||||||
// Convert to readable size string
|
|
||||||
fileInfo.setSizeStr(FileSizeConverter.convertToReadableSize(fileSize));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set fileType (API returns it as an integer)
|
|
||||||
Integer fileTypeInt = fileInfoJson.getInteger("fileType");
|
|
||||||
if (fileTypeInt != null) {
|
|
||||||
fileInfo.setFileType(String.valueOf(fileTypeInt));
|
|
||||||
} else {
|
|
||||||
// Default to generic file type if not available
|
|
||||||
fileInfo.setFileType(DEFAULT_FILE_TYPE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set panType
|
|
||||||
fileInfo.setPanType(shareLinkInfo.getType());
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn("Error extracting file info from JSON: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
return fileInfo;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,12 +11,14 @@ import io.vertx.core.Promise;
|
|||||||
import io.vertx.core.json.JsonObject;
|
import io.vertx.core.json.JsonObject;
|
||||||
import io.vertx.ext.web.client.WebClient;
|
import io.vertx.ext.web.client.WebClient;
|
||||||
import io.vertx.ext.web.client.WebClientSession;
|
import io.vertx.ext.web.client.WebClientSession;
|
||||||
|
import org.apache.commons.lang3.RegExUtils;
|
||||||
import org.openjdk.nashorn.api.scripting.ScriptObjectMirror;
|
import org.openjdk.nashorn.api.scripting.ScriptObjectMirror;
|
||||||
|
|
||||||
import javax.script.ScriptException;
|
import javax.script.ScriptException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@@ -27,14 +29,13 @@ import java.util.regex.Pattern;
|
|||||||
*/
|
*/
|
||||||
public class LzTool extends PanBase {
|
public class LzTool extends PanBase {
|
||||||
|
|
||||||
WebClientSession webClientSession = WebClientSession.create(clientNoRedirects);
|
public static final String SHARE_URL_PREFIX = "https://wwwwp.lanzoup.com";
|
||||||
|
|
||||||
public static final String SHARE_URL_PREFIX = "https://w1.lanzn.com/";
|
|
||||||
MultiMap headers0 = HeaderUtils.parseHeaders("""
|
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: 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
|
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
|
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
|
Cache-Control: max-age=0
|
||||||
|
Cookie: codelen=1; pc_ad1=1
|
||||||
DNT: 1
|
DNT: 1
|
||||||
Priority: u=0, i
|
Priority: u=0, i
|
||||||
Sec-CH-UA: "Chromium";v="140", "Not=A?Brand";v="24", "Microsoft Edge";v="140"
|
Sec-CH-UA: "Chromium";v="140", "Not=A?Brand";v="24", "Microsoft Edge";v="140"
|
||||||
@@ -62,100 +63,53 @@ public class LzTool extends PanBase {
|
|||||||
.putHeaders(headers0)
|
.putHeaders(headers0)
|
||||||
.send().onSuccess(res -> {
|
.send().onSuccess(res -> {
|
||||||
String html = asText(res);
|
String html = asText(res);
|
||||||
if (html.contains("var arg1='")) {
|
try {
|
||||||
webClientSession = WebClientSession.create(clientNoRedirects);
|
setFileInfo(html, shareLinkInfo);
|
||||||
setCookie(html);
|
} catch (Exception e) {
|
||||||
webClientSession.getAbs(sUrl)
|
e.printStackTrace();
|
||||||
.putHeaders(headers0)
|
|
||||||
.send().onSuccess(res2 -> {
|
|
||||||
String html2 = asText(res2);
|
|
||||||
doParser(html2, pwd, sUrl);
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
doParser(html, pwd, 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));
|
}).onFailure(handleFail(sUrl));
|
||||||
return promise.future();
|
return promise.future();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doParser(String html, String pwd, String sUrl) {
|
|
||||||
try {
|
|
||||||
setFileInfo(html, shareLinkInfo);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
// 匹配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, scriptObjectMirror);
|
|
||||||
} catch (Exception e) {
|
|
||||||
fail(e, "js引擎执行失败");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// 没有密码
|
|
||||||
String iframePath = matcher.group(1);
|
|
||||||
String absoluteURI = SHARE_URL_PREFIX + iframePath;
|
|
||||||
webClientSession.getAbs(absoluteURI).putHeaders(headers0).send().onSuccess(res2 -> {
|
|
||||||
String html2= asText(res2);
|
|
||||||
// Matcher matcher2 = Pattern.compile("'sign'\s*:\s*'(\\w+)'").matcher(html2);
|
|
||||||
String jsText = getJsText(html2);
|
|
||||||
if (jsText == null) {
|
|
||||||
headers0.add("Referer", absoluteURI);
|
|
||||||
setCookie(html2);
|
|
||||||
webClientSession.getAbs(absoluteURI).send().onSuccess(res3 -> {
|
|
||||||
String html3= asText(res3);
|
|
||||||
String jsText3 = getJsText(html3);
|
|
||||||
if (jsText3 != null) {
|
|
||||||
try {
|
|
||||||
ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText3, null);
|
|
||||||
getDownURL(sUrl, scriptObjectMirror);
|
|
||||||
} catch (ScriptException | NoSuchMethodException e) {
|
|
||||||
fail(e, "引擎执行失败");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
|
|
||||||
fail(SHARE_URL_PREFIX + iframePath + " -> " + sUrl + ": 获取失败0, 可能分享已失效");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText, null);
|
|
||||||
getDownURL(sUrl, scriptObjectMirror);
|
|
||||||
} catch (ScriptException | NoSuchMethodException e) {
|
|
||||||
fail(e, "js引擎执行失败");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).onFailure(handleFail(SHARE_URL_PREFIX));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setCookie(String html2) {
|
|
||||||
int beginIndex = html2.indexOf("arg1='") + 6;
|
|
||||||
String arg1 = html2.substring(beginIndex, html2.indexOf("';", beginIndex));
|
|
||||||
String acw_sc__v2 = AcwScV2Generator.acwScV2Simple(arg1);
|
|
||||||
// 创建一个 Cookie 并放入 CookieStore
|
|
||||||
DefaultCookie nettyCookie = new DefaultCookie("acw_sc__v2", acw_sc__v2);
|
|
||||||
nettyCookie.setDomain(".lanzn.com"); // 设置域名
|
|
||||||
nettyCookie.setPath("/"); // 设置路径
|
|
||||||
nettyCookie.setSecure(false);
|
|
||||||
nettyCookie.setHttpOnly(false);
|
|
||||||
webClientSession.cookieStore().put(nettyCookie);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getJsByPwd(String pwd, String html, String subText) {
|
private String getJsByPwd(String pwd, String html, String subText) {
|
||||||
String jsText = getJsText(html);
|
String jsText = getJsText(html);
|
||||||
|
|
||||||
if (jsText == null) {
|
if (jsText == null) {
|
||||||
throw new RuntimeException("获取失败1, 可能分享已失效");
|
throw new RuntimeException("js脚本匹配失败, 可能分享已失效");
|
||||||
}
|
}
|
||||||
jsText = jsText.replace("document.getElementById('pwd').value", "\"" + pwd + "\"");
|
jsText = jsText.replace("document.getElementById('pwd').value", "\"" + pwd + "\"");
|
||||||
int i = jsText.indexOf(subText);
|
int i = jsText.indexOf(subText);
|
||||||
@@ -177,7 +131,7 @@ public class LzTool extends PanBase {
|
|||||||
return html.substring(startPos, endPos).replaceAll("<!--.*-->", "");
|
return html.substring(startPos, endPos).replaceAll("<!--.*-->", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getDownURL(String key, Map<String, ?> obj) {
|
private void getDownURL(String key, WebClient client, Map<String, ?> obj) {
|
||||||
if (obj == null) {
|
if (obj == null) {
|
||||||
fail("需要访问密码");
|
fail("需要访问密码");
|
||||||
return;
|
return;
|
||||||
@@ -209,7 +163,7 @@ public class LzTool extends PanBase {
|
|||||||
headers.set("referer", key);
|
headers.set("referer", key);
|
||||||
// action=downprocess&signs=%3Fctdf&websignkey=I5gl&sign=BWMGOF1sBTRWXwI9BjZdYVA7BDhfNAIyUG9UawJtUGMIPlAhACkCa1UyUTAAYFxvUj5XY1E7UGFXaFVq&websign=&kd=1&ves=1
|
// action=downprocess&signs=%3Fctdf&websignkey=I5gl&sign=BWMGOF1sBTRWXwI9BjZdYVA7BDhfNAIyUG9UawJtUGMIPlAhACkCa1UyUTAAYFxvUj5XY1E7UGFXaFVq&websign=&kd=1&ves=1
|
||||||
String url = SHARE_URL_PREFIX + url0;
|
String url = SHARE_URL_PREFIX + url0;
|
||||||
webClientSession.postAbs(url).putHeaders(headers).sendForm(map).onSuccess(res2 -> {
|
client.postAbs(url).putHeaders(headers).sendForm(map).onSuccess(res2 -> {
|
||||||
try {
|
try {
|
||||||
JsonObject urlJson = asJson(res2);
|
JsonObject urlJson = asJson(res2);
|
||||||
String name = urlJson.getString("inf");
|
String name = urlJson.getString("inf");
|
||||||
@@ -224,6 +178,7 @@ public class LzTool extends PanBase {
|
|||||||
|
|
||||||
String downUrl = urlJson.getString("dom") + "/file/" + urlJson.getString("url");
|
String downUrl = urlJson.getString("dom") + "/file/" + urlJson.getString("url");
|
||||||
headers.remove("Referer");
|
headers.remove("Referer");
|
||||||
|
WebClientSession webClientSession = WebClientSession.create(client);
|
||||||
webClientSession.getAbs(downUrl).putHeaders(headers).send()
|
webClientSession.getAbs(downUrl).putHeaders(headers).send()
|
||||||
.onSuccess(res3 -> {
|
.onSuccess(res3 -> {
|
||||||
String location = res3.headers().get("Location");
|
String location = res3.headers().get("Location");
|
||||||
@@ -240,13 +195,12 @@ public class LzTool extends PanBase {
|
|||||||
nettyCookie.setPath("/"); // 设置路径
|
nettyCookie.setPath("/"); // 设置路径
|
||||||
nettyCookie.setSecure(false);
|
nettyCookie.setSecure(false);
|
||||||
nettyCookie.setHttpOnly(false);
|
nettyCookie.setHttpOnly(false);
|
||||||
WebClientSession webClientSession2 = WebClientSession.create(clientNoRedirects);
|
webClientSession.cookieStore().put(nettyCookie);
|
||||||
webClientSession2.cookieStore().put(nettyCookie);
|
webClientSession.getAbs(downUrl).putHeaders(headers).send()
|
||||||
webClientSession2.getAbs(downUrl).putHeaders(headers).send()
|
|
||||||
.onSuccess(res4 -> {
|
.onSuccess(res4 -> {
|
||||||
String location0 = res4.headers().get("Location");
|
String location0 = res4.headers().get("Location");
|
||||||
if (location0 == null) {
|
if (location0 == null) {
|
||||||
fail(downUrl + " -> 直链获取失败2, 可能分享已失效");
|
fail(downUrl + " -> 直链获取失败, 可能分享已失效");
|
||||||
} else {
|
} else {
|
||||||
setDateAndComplate(location0);
|
setDateAndComplate(location0);
|
||||||
}
|
}
|
||||||
@@ -294,98 +248,67 @@ public class LzTool extends PanBase {
|
|||||||
String sUrl = shareLinkInfo.getShareUrl();
|
String sUrl = shareLinkInfo.getShareUrl();
|
||||||
String pwd = shareLinkInfo.getSharePassword();
|
String pwd = shareLinkInfo.getSharePassword();
|
||||||
|
|
||||||
webClientSession.getAbs(sUrl).send().onSuccess(res -> {
|
WebClient client = clientNoRedirects;
|
||||||
|
client.getAbs(sUrl).send().onSuccess(res -> {
|
||||||
String html = res.bodyAsString();
|
String html = res.bodyAsString();
|
||||||
// 检查是否需要 cookie 验证
|
try {
|
||||||
if (html.contains("var arg1='")) {
|
String jsText = getJsByPwd(pwd, html, "var urls =window.location.href");
|
||||||
webClientSession = WebClientSession.create(clientNoRedirects);
|
ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText, "file");
|
||||||
setCookie(html);
|
Map<String, Object> data = CastUtil.cast(scriptObjectMirror.get("data"));
|
||||||
// 重新请求
|
MultiMap map = MultiMap.caseInsensitiveMultiMap();
|
||||||
webClientSession.getAbs(sUrl).send().onSuccess(res2 -> {
|
data.forEach((k, v) -> map.set(k, v.toString()));
|
||||||
handleFileListParse(res2.bodyAsString(), pwd, sUrl, promise);
|
log.debug("解析参数: {}", map);
|
||||||
}).onFailure(err -> promise.fail(err));
|
MultiMap headers = getHeaders(sUrl);
|
||||||
return;
|
|
||||||
|
String url = SHARE_URL_PREFIX + "/filemoreajax.php?file=" + data.get("fid");
|
||||||
|
client.postAbs(url).putHeaders(headers).sendForm(map).onSuccess(res2 -> {
|
||||||
|
JsonObject fileListJson = asJson(res2);
|
||||||
|
if (fileListJson.getInteger("zt") != 1) {
|
||||||
|
promise.fail(baseMsg() + fileListJson.getString("info"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<FileInfo> list = new ArrayList<>();
|
||||||
|
fileListJson.getJsonArray("text").forEach(item -> {
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"icon": "apk",
|
||||||
|
"t": 0,
|
||||||
|
"id": "iULV2n4361c",
|
||||||
|
"name_all": "xx.apk",
|
||||||
|
"size": "49.8 M",
|
||||||
|
"time": "2021-03-19",
|
||||||
|
"duan": "in4361",
|
||||||
|
"p_ico": 0
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
JsonObject fileJson = (JsonObject) item;
|
||||||
|
FileInfo fileInfo = new FileInfo();
|
||||||
|
String size = fileJson.getString("size");
|
||||||
|
Long sizeNum = FileSizeConverter.convertToBytes(size);
|
||||||
|
String panType = shareLinkInfo.getType();
|
||||||
|
String id = fileJson.getString("id");
|
||||||
|
fileInfo.setFileName(fileJson.getString("name_all"))
|
||||||
|
.setFileId(id)
|
||||||
|
.setCreateTime(fileJson.getString("time"))
|
||||||
|
.setFileType(fileJson.getString("icon"))
|
||||||
|
.setSizeStr(fileJson.getString("size"))
|
||||||
|
.setSize(sizeNum)
|
||||||
|
.setPanType(panType)
|
||||||
|
.setParserUrl(getDomainName() + "/d/" + panType + "/" + id)
|
||||||
|
.setPreviewUrl(String.format("%s/v2/view/%s/%s", getDomainName(),
|
||||||
|
shareLinkInfo.getType(), id));
|
||||||
|
log.debug("文件信息: {}", fileInfo);
|
||||||
|
list.add(fileInfo);
|
||||||
|
});
|
||||||
|
promise.complete(list);
|
||||||
|
});
|
||||||
|
} catch (ScriptException | NoSuchMethodException e) {
|
||||||
|
promise.fail(e);
|
||||||
}
|
}
|
||||||
handleFileListParse(html, pwd, sUrl, promise);
|
});
|
||||||
}).onFailure(err -> promise.fail(err));
|
|
||||||
return promise.future();
|
return promise.future();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleFileListParse(String html, String pwd, String sUrl, Promise<List<FileInfo>> promise) {
|
|
||||||
try {
|
|
||||||
String jsText = getJsByPwd(pwd, html, "var urls =window.location.href");
|
|
||||||
ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText, "file");
|
|
||||||
Map<String, Object> data = CastUtil.cast(scriptObjectMirror.get("data"));
|
|
||||||
MultiMap map = MultiMap.caseInsensitiveMultiMap();
|
|
||||||
data.forEach((k, v) -> map.set(k, v.toString()));
|
|
||||||
log.debug("解析参数: {}", map);
|
|
||||||
MultiMap headers = getHeaders(sUrl);
|
|
||||||
|
|
||||||
String url = SHARE_URL_PREFIX + "/filemoreajax.php?file=" + data.get("fid");
|
|
||||||
webClientSession.postAbs(url).putHeaders(headers).sendForm(map).onSuccess(res2 -> {
|
|
||||||
String resBody = asText(res2);
|
|
||||||
// 再次检查是否需要 cookie 验证
|
|
||||||
if (resBody.contains("var arg1='")) {
|
|
||||||
setCookie(resBody);
|
|
||||||
// 重新请求
|
|
||||||
webClientSession.postAbs(url).putHeaders(headers).sendForm(map).onSuccess(res3 -> {
|
|
||||||
handleFileListResponse(asText(res3), promise);
|
|
||||||
}).onFailure(err -> promise.fail(err));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleFileListResponse(resBody, promise);
|
|
||||||
}).onFailure(err -> promise.fail(err));
|
|
||||||
} catch (ScriptException | NoSuchMethodException e) {
|
|
||||||
promise.fail(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleFileListResponse(String responseBody, Promise<List<FileInfo>> promise) {
|
|
||||||
try {
|
|
||||||
JsonObject fileListJson = new JsonObject(responseBody);
|
|
||||||
if (fileListJson.getInteger("zt") != 1) {
|
|
||||||
promise.fail(baseMsg() + fileListJson.getString("info"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
List<FileInfo> list = new ArrayList<>();
|
|
||||||
fileListJson.getJsonArray("text").forEach(item -> {
|
|
||||||
/*
|
|
||||||
{
|
|
||||||
"icon": "apk",
|
|
||||||
"t": 0,
|
|
||||||
"id": "iULV2n4361c",
|
|
||||||
"name_all": "xx.apk",
|
|
||||||
"size": "49.8 M",
|
|
||||||
"time": "2021-03-19",
|
|
||||||
"duan": "in4361",
|
|
||||||
"p_ico": 0
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
JsonObject fileJson = (JsonObject) item;
|
|
||||||
FileInfo fileInfo = new FileInfo();
|
|
||||||
String size = fileJson.getString("size");
|
|
||||||
Long sizeNum = FileSizeConverter.convertToBytes(size);
|
|
||||||
String panType = shareLinkInfo.getType();
|
|
||||||
String id = fileJson.getString("id");
|
|
||||||
fileInfo.setFileName(fileJson.getString("name_all"))
|
|
||||||
.setFileId(id)
|
|
||||||
.setCreateTime(fileJson.getString("time"))
|
|
||||||
.setFileType(fileJson.getString("icon"))
|
|
||||||
.setSizeStr(fileJson.getString("size"))
|
|
||||||
.setSize(sizeNum)
|
|
||||||
.setPanType(panType)
|
|
||||||
.setParserUrl(getDomainName() + "/d/" + panType + "/" + id)
|
|
||||||
.setPreviewUrl(String.format("%s/v2/view/%s/%s", getDomainName(),
|
|
||||||
shareLinkInfo.getType(), id));
|
|
||||||
log.debug("文件信息: {}", fileInfo);
|
|
||||||
list.add(fileInfo);
|
|
||||||
});
|
|
||||||
promise.complete(list);
|
|
||||||
} catch (Exception e) {
|
|
||||||
promise.fail(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setFileInfo(String html, ShareLinkInfo shareLinkInfo) {
|
void setFileInfo(String html, ShareLinkInfo shareLinkInfo) {
|
||||||
// 写入 fileInfo
|
// 写入 fileInfo
|
||||||
FileInfo fileInfo = new FileInfo();
|
FileInfo fileInfo = new FileInfo();
|
||||||
|
|||||||
@@ -549,176 +549,6 @@ public class JsHttpClientTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testPostWithJsonString() {
|
|
||||||
System.out.println("\n[测试16] POST请求(JSON字符串) - httpbin.org/post");
|
|
||||||
System.out.println("测试修复:POST请求发送JSON字符串时请求体是否正确发送");
|
|
||||||
|
|
||||||
try {
|
|
||||||
String url = "https://httpbin.org/post";
|
|
||||||
System.out.println("请求URL: " + url);
|
|
||||||
|
|
||||||
// 模拟阿里云盘登录请求格式
|
|
||||||
String jsonData = "{\"grant_type\":\"refresh_token\",\"refresh_token\":\"test_token_123\"}";
|
|
||||||
System.out.println("POST数据(JSON字符串): " + jsonData);
|
|
||||||
|
|
||||||
// 设置Content-Type为application/json
|
|
||||||
httpClient.putHeader("Content-Type", "application/json");
|
|
||||||
System.out.println("设置Content-Type: application/json");
|
|
||||||
System.out.println("开始请求...");
|
|
||||||
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
JsHttpClient.JsHttpResponse response = httpClient.post(url, jsonData);
|
|
||||||
long endTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
System.out.println("请求完成,耗时: " + (endTime - startTime) + "ms");
|
|
||||||
System.out.println("状态码: " + response.statusCode());
|
|
||||||
|
|
||||||
String body = response.body();
|
|
||||||
System.out.println("响应体(前500字符): " + (body != null && body.length() > 500 ? body.substring(0, 500) + "..." : body));
|
|
||||||
|
|
||||||
// 验证结果
|
|
||||||
assertNotNull("响应不能为null", response);
|
|
||||||
assertEquals("状态码应该是200", 200, response.statusCode());
|
|
||||||
assertNotNull("响应体不能为null", body);
|
|
||||||
// 验证请求体是否正确发送(httpbin会回显请求数据)
|
|
||||||
assertTrue("响应体应该包含发送的JSON数据",
|
|
||||||
body.contains("grant_type") || body.contains("refresh_token"));
|
|
||||||
|
|
||||||
System.out.println("✓ 测试通过 - POST请求体已正确发送");
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.err.println("✗ 测试失败: " + e.getMessage());
|
|
||||||
e.printStackTrace();
|
|
||||||
fail("POST JSON字符串请求测试失败: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAlipanTokenApi() {
|
|
||||||
System.out.println("\n[测试20] 阿里云盘Token接口测试 - auth.aliyundrive.com/v2/account/token");
|
|
||||||
System.out.println("参考 alipan.js 中的登录逻辑,测试请求格式是否正确");
|
|
||||||
|
|
||||||
try {
|
|
||||||
String tokenUrl = "https://auth.aliyundrive.com/v2/account/token";
|
|
||||||
System.out.println("请求URL: " + tokenUrl);
|
|
||||||
|
|
||||||
// 参考 alipan.js 中的请求格式
|
|
||||||
// setJsonHeaders(http) 设置 Content-Type: application/json 和 User-Agent
|
|
||||||
httpClient.putHeader("Content-Type", "application/json");
|
|
||||||
httpClient.putHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
|
|
||||||
|
|
||||||
// 参考 alipan.js: JSON.stringify({grant_type: "refresh_token", refresh_token: REFRESH_TOKEN})
|
|
||||||
String jsonData = "{\"grant_type\":\"refresh_token\",\"refresh_token\":\"\"}";
|
|
||||||
System.out.println("POST数据(JSON字符串): " + jsonData);
|
|
||||||
System.out.println("注意:使用无效token测试错误响应格式");
|
|
||||||
System.out.println("开始请求...");
|
|
||||||
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
JsHttpClient.JsHttpResponse response = httpClient.post(tokenUrl, jsonData);
|
|
||||||
long endTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
System.out.println("请求完成,耗时: " + (endTime - startTime) + "ms");
|
|
||||||
System.out.println("状态码: " + response.statusCode());
|
|
||||||
|
|
||||||
String body = response.body();
|
|
||||||
System.out.println("响应体: " + body);
|
|
||||||
|
|
||||||
// 验证结果
|
|
||||||
assertNotNull("响应不能为null", response);
|
|
||||||
// 使用无效token应该返回400或401等错误状态码,但请求格式应该是正确的
|
|
||||||
assertTrue("状态码应该是4xx(无效token)或200(如果token有效)",
|
|
||||||
response.statusCode() >= 200 && response.statusCode() < 500);
|
|
||||||
assertNotNull("响应体不能为null", body);
|
|
||||||
|
|
||||||
// 验证响应格式(阿里云盘API通常返回JSON)
|
|
||||||
try {
|
|
||||||
Object jsonResponse = response.json();
|
|
||||||
System.out.println("响应JSON解析成功: " + jsonResponse);
|
|
||||||
assertNotNull("JSON响应不能为null", jsonResponse);
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.out.println("警告:响应不是有效的JSON格式");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证请求头是否正确设置
|
|
||||||
System.out.println("验证请求头设置...");
|
|
||||||
Map<String, String> headers = httpClient.getHeaders();
|
|
||||||
assertTrue("应该设置了Content-Type", headers.containsKey("Content-Type"));
|
|
||||||
assertEquals("Content-Type应该是application/json",
|
|
||||||
"application/json", headers.get("Content-Type"));
|
|
||||||
|
|
||||||
System.out.println("✓ 测试通过 - 请求格式正确,已成功发送到阿里云盘API");
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.err.println("✗ 测试失败: " + e.getMessage());
|
|
||||||
e.printStackTrace();
|
|
||||||
// 如果是超时或其他网络错误,说明请求格式可能有问题
|
|
||||||
if (e.getMessage() != null && e.getMessage().contains("超时")) {
|
|
||||||
fail("请求超时,可能是请求格式问题或网络问题: " + e.getMessage());
|
|
||||||
} else {
|
|
||||||
fail("阿里云盘Token接口测试失败: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAlipanTokenApiWithValidFormat() {
|
|
||||||
System.out.println("\n[测试21] 阿里云盘Token接口格式验证 - 使用httpbin验证请求格式");
|
|
||||||
System.out.println("通过httpbin回显验证请求格式是否与alipan.js中的格式一致");
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 使用httpbin来验证请求格式
|
|
||||||
String testUrl = "https://httpbin.org/post";
|
|
||||||
System.out.println("测试URL: " + testUrl);
|
|
||||||
|
|
||||||
// 参考 alipan.js 中的请求格式
|
|
||||||
httpClient.clearHeaders(); // 清空之前的头
|
|
||||||
httpClient.putHeader("Content-Type", "application/json");
|
|
||||||
httpClient.putHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
|
|
||||||
|
|
||||||
// 完全模拟 alipan.js 中的请求体格式
|
|
||||||
String jsonData = "{\"grant_type\":\"refresh_token\",\"refresh_token\":\"test_refresh_token_12345\"}";
|
|
||||||
System.out.println("POST数据(JSON字符串): " + jsonData);
|
|
||||||
System.out.println("开始请求...");
|
|
||||||
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
JsHttpClient.JsHttpResponse response = httpClient.post(testUrl, jsonData);
|
|
||||||
long endTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
System.out.println("请求完成,耗时: " + (endTime - startTime) + "ms");
|
|
||||||
System.out.println("状态码: " + response.statusCode());
|
|
||||||
|
|
||||||
String body = response.body();
|
|
||||||
System.out.println("响应体(前800字符): " + (body != null && body.length() > 800 ? body.substring(0, 800) + "..." : body));
|
|
||||||
|
|
||||||
// 验证结果
|
|
||||||
assertNotNull("响应不能为null", response);
|
|
||||||
assertEquals("状态码应该是200", 200, response.statusCode());
|
|
||||||
assertNotNull("响应体不能为null", body);
|
|
||||||
|
|
||||||
// httpbin会回显请求数据,验证请求体是否正确发送
|
|
||||||
assertTrue("响应体应该包含grant_type字段", body.contains("grant_type"));
|
|
||||||
assertTrue("响应体应该包含refresh_token字段", body.contains("refresh_token"));
|
|
||||||
assertTrue("响应体应该包含发送的refresh_token值", body.contains("test_refresh_token_12345"));
|
|
||||||
|
|
||||||
// 验证Content-Type是否正确
|
|
||||||
assertTrue("响应体应该包含Content-Type信息", body.contains("application/json"));
|
|
||||||
|
|
||||||
// 验证User-Agent是否正确
|
|
||||||
assertTrue("响应体应该包含User-Agent信息", body.contains("Mozilla"));
|
|
||||||
|
|
||||||
System.out.println("✓ 测试通过 - 请求格式与alipan.js中的格式完全一致");
|
|
||||||
System.out.println(" - JSON请求体正确发送");
|
|
||||||
System.out.println(" - Content-Type正确设置");
|
|
||||||
System.out.println(" - User-Agent正确设置");
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.err.println("✗ 测试失败: " + e.getMessage());
|
|
||||||
e.printStackTrace();
|
|
||||||
fail("阿里云盘Token接口格式验证失败: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSetTimeout() {
|
public void testSetTimeout() {
|
||||||
System.out.println("\n[测试15] 设置超时时间 - setTimeout方法");
|
System.out.println("\n[测试15] 设置超时时间 - setTimeout方法");
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ module.exports = {
|
|||||||
'@vue/cli-plugin-babel/preset'
|
'@vue/cli-plugin-babel/preset'
|
||||||
],
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
'@vue/babel-plugin-transform-vue-jsx',
|
'@vue/babel-plugin-transform-vue-jsx'
|
||||||
'@babel/plugin-transform-class-static-block'
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vue-cli-service serve",
|
||||||
"dev": "vue-cli-service serve",
|
"dev": "vue-cli-service serve",
|
||||||
"build": "vue-cli-service build && node scripts/compress-vs.js",
|
"build": "vue-cli-service build",
|
||||||
"build:no-compress": "vue-cli-service build",
|
|
||||||
"lint": "vue-cli-service lint"
|
"lint": "vue-cli-service lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -29,7 +28,6 @@
|
|||||||
"@babel/core": "^7.26.0",
|
"@babel/core": "^7.26.0",
|
||||||
"@babel/eslint-parser": "^7.25.9",
|
"@babel/eslint-parser": "^7.25.9",
|
||||||
"@babel/plugin-transform-class-properties": "^7.26.0",
|
"@babel/plugin-transform-class-properties": "^7.26.0",
|
||||||
"@babel/plugin-transform-class-static-block": "^7.26.0",
|
|
||||||
"@vue/babel-plugin-transform-vue-jsx": "^1.4.0",
|
"@vue/babel-plugin-transform-vue-jsx": "^1.4.0",
|
||||||
"@vue/cli-plugin-babel": "~5.0.8",
|
"@vue/cli-plugin-babel": "~5.0.8",
|
||||||
"@vue/cli-plugin-eslint": "~5.0.8",
|
"@vue/cli-plugin-eslint": "~5.0.8",
|
||||||
@@ -37,8 +35,7 @@
|
|||||||
"compression-webpack-plugin": "^11.1.0",
|
"compression-webpack-plugin": "^11.1.0",
|
||||||
"eslint": "^9.0.0",
|
"eslint": "^9.0.0",
|
||||||
"eslint-plugin-vue": "^9.30.0",
|
"eslint-plugin-vue": "^9.30.0",
|
||||||
"filemanager-webpack-plugin": "8.0.0",
|
"filemanager-webpack-plugin": "8.0.0"
|
||||||
"monaco-editor-webpack-plugin": "^7.1.1"
|
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"root": true,
|
"root": true,
|
||||||
|
|||||||
@@ -1,124 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
const path = require("path");
|
|
||||||
const fs = require("fs");
|
|
||||||
const zlib = require("zlib");
|
|
||||||
const { promisify } = require("util");
|
|
||||||
|
|
||||||
const gzip = promisify(zlib.gzip);
|
|
||||||
const readdir = promisify(fs.readdir);
|
|
||||||
const stat = promisify(fs.stat);
|
|
||||||
const readFile = promisify(fs.readFile);
|
|
||||||
const writeFile = promisify(fs.writeFile);
|
|
||||||
|
|
||||||
// 递归压缩目录下的所有文件
|
|
||||||
async function compressDirectory(dirPath, threshold = 1024) {
|
|
||||||
if (!fs.existsSync(dirPath)) {
|
|
||||||
console.warn(`目录不存在: ${dirPath}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const files = await readdir(dirPath, { withFileTypes: true });
|
|
||||||
let compressedCount = 0;
|
|
||||||
let totalOriginalSize = 0;
|
|
||||||
let totalCompressedSize = 0;
|
|
||||||
|
|
||||||
for (const file of files) {
|
|
||||||
const filePath = path.join(dirPath, file.name);
|
|
||||||
|
|
||||||
if (file.isDirectory()) {
|
|
||||||
await compressDirectory(filePath, threshold);
|
|
||||||
} else if (file.isFile()) {
|
|
||||||
const stats = await stat(filePath);
|
|
||||||
// 只压缩超过阈值且不是已压缩的文件
|
|
||||||
if (stats.size > threshold && !filePath.endsWith('.gz') && !filePath.endsWith('.map')) {
|
|
||||||
try {
|
|
||||||
const content = await readFile(filePath);
|
|
||||||
const compressed = await gzip(content);
|
|
||||||
await writeFile(filePath + '.gz', compressed);
|
|
||||||
compressedCount++;
|
|
||||||
totalOriginalSize += stats.size;
|
|
||||||
totalCompressedSize += compressed.length;
|
|
||||||
console.log(`✓ ${file.name} (${(stats.size / 1024).toFixed(2)}KB -> ${(compressed.length / 1024).toFixed(2)}KB)`);
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(`⚠ 压缩失败: ${filePath}`, error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (compressedCount > 0) {
|
|
||||||
console.log(`\n压缩完成: ${compressedCount} 个文件`);
|
|
||||||
console.log(`原始大小: ${(totalOriginalSize / 1024 / 1024).toFixed(2)}MB`);
|
|
||||||
console.log(`压缩后大小: ${(totalCompressedSize / 1024 / 1024).toFixed(2)}MB`);
|
|
||||||
console.log(`压缩率: ${((1 - totalCompressedSize / totalOriginalSize) * 100).toFixed(1)}%`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除未使用的 worker 文件
|
|
||||||
function deleteUnusedWorkers() {
|
|
||||||
const jsDir = path.join(__dirname, '../nfd-front/js');
|
|
||||||
const workers = ['editor.worker.js', 'editor.worker.js.gz', 'json.worker.js', 'json.worker.js.gz', 'ts.worker.js', 'ts.worker.js.gz'];
|
|
||||||
|
|
||||||
let deletedCount = 0;
|
|
||||||
for (const worker of workers) {
|
|
||||||
const filePath = path.join(jsDir, worker);
|
|
||||||
if (fs.existsSync(filePath)) {
|
|
||||||
try {
|
|
||||||
fs.unlinkSync(filePath);
|
|
||||||
deletedCount++;
|
|
||||||
console.log(`✓ 已删除未使用的文件: ${worker}`);
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(`⚠ 删除失败: ${worker}`, error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deletedCount > 0) {
|
|
||||||
console.log(`\n已删除 ${deletedCount} 个未使用的 worker 文件\n`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 复制到 webroot
|
|
||||||
function copyToWebroot() {
|
|
||||||
const source = path.join(__dirname, '../nfd-front');
|
|
||||||
const dest = path.join(__dirname, '../../webroot/nfd-front');
|
|
||||||
|
|
||||||
// 使用 FileManagerPlugin 的方式,这里用简单的复制
|
|
||||||
const { execSync } = require('child_process');
|
|
||||||
try {
|
|
||||||
// 删除目标目录
|
|
||||||
if (fs.existsSync(dest)) {
|
|
||||||
execSync(`rm -rf "${dest}"`, { stdio: 'inherit' });
|
|
||||||
}
|
|
||||||
// 复制整个目录
|
|
||||||
execSync(`cp -R "${source}" "${dest}"`, { stdio: 'inherit' });
|
|
||||||
console.log('\n✓ 已复制到 webroot');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('\n✗ 复制到 webroot 失败:', error.message);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 主函数
|
|
||||||
async function main() {
|
|
||||||
// 先删除未使用的 worker 文件
|
|
||||||
deleteUnusedWorkers();
|
|
||||||
|
|
||||||
// 然后压缩 vs 目录
|
|
||||||
const vsPath = path.join(__dirname, '../nfd-front/js/vs');
|
|
||||||
console.log('开始压缩 vs 目录下的文件...\n');
|
|
||||||
try {
|
|
||||||
await compressDirectory(vsPath, 1024); // 只压缩超过1KB的文件
|
|
||||||
console.log('\n✓ vs 目录压缩完成');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('\n✗ vs 目录压缩失败:', error);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 最后复制到 webroot
|
|
||||||
copyToWebroot();
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
||||||
|
|
||||||
@@ -34,7 +34,6 @@ export default {
|
|||||||
const editorContainer = ref(null);
|
const editorContainer = ref(null);
|
||||||
let editor = null;
|
let editor = null;
|
||||||
let monaco = null;
|
let monaco = null;
|
||||||
let touchHandlers = { start: null, move: null };
|
|
||||||
|
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
value: props.modelValue,
|
value: props.modelValue,
|
||||||
@@ -95,18 +94,13 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 配置Monaco Editor使用本地打包的文件,而不是CDN
|
// 配置Monaco Editor使用国内CDN (npmmirror)
|
||||||
if (loader.config) {
|
// npmmirror的路径格式: https://registry.npmmirror.com/包名/版本号/files/文件路径
|
||||||
const vsPath = process.env.NODE_ENV === 'production'
|
loader.config({
|
||||||
? './js/vs' // 生产环境使用相对路径
|
paths: {
|
||||||
: '/js/vs'; // 开发环境使用绝对路径
|
vs: 'https://registry.npmmirror.com/monaco-editor/0.55.1/files/min/vs'
|
||||||
|
}
|
||||||
loader.config({
|
});
|
||||||
paths: {
|
|
||||||
vs: vsPath
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化Monaco Editor
|
// 初始化Monaco Editor
|
||||||
monaco = await loader.init();
|
monaco = await loader.init();
|
||||||
@@ -137,46 +131,6 @@ export default {
|
|||||||
if (editorContainer.value) {
|
if (editorContainer.value) {
|
||||||
editorContainer.value.style.height = props.height;
|
editorContainer.value.style.height = props.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移动端:添加触摸缩放来调整字体大小
|
|
||||||
if (window.innerWidth <= 768 && editorContainer.value) {
|
|
||||||
let initialDistance = 0;
|
|
||||||
let initialFontSize = defaultOptions.fontSize || 14;
|
|
||||||
const minFontSize = 8;
|
|
||||||
const maxFontSize = 24;
|
|
||||||
|
|
||||||
const getTouchDistance = (touch1, touch2) => {
|
|
||||||
const dx = touch1.clientX - touch2.clientX;
|
|
||||||
const dy = touch1.clientY - touch2.clientY;
|
|
||||||
return Math.sqrt(dx * dx + dy * dy);
|
|
||||||
};
|
|
||||||
|
|
||||||
touchHandlers.start = (e) => {
|
|
||||||
if (e.touches.length === 2 && editor) {
|
|
||||||
initialDistance = getTouchDistance(e.touches[0], e.touches[1]);
|
|
||||||
initialFontSize = editor.getOption(monaco.editor.EditorOption.fontSize);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
touchHandlers.move = (e) => {
|
|
||||||
if (e.touches.length === 2 && editor) {
|
|
||||||
e.preventDefault(); // 防止页面缩放
|
|
||||||
const currentDistance = getTouchDistance(e.touches[0], e.touches[1]);
|
|
||||||
const scale = currentDistance / initialDistance;
|
|
||||||
const newFontSize = Math.round(initialFontSize * scale);
|
|
||||||
|
|
||||||
// 限制字体大小范围
|
|
||||||
const clampedFontSize = Math.max(minFontSize, Math.min(maxFontSize, newFontSize));
|
|
||||||
|
|
||||||
if (clampedFontSize !== editor.getOption(monaco.editor.EditorOption.fontSize)) {
|
|
||||||
editor.updateOptions({ fontSize: clampedFontSize });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
editorContainer.value.addEventListener('touchstart', touchHandlers.start, { passive: false });
|
|
||||||
editorContainer.value.addEventListener('touchmove', touchHandlers.move, { passive: false });
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Monaco Editor初始化失败:', error);
|
console.error('Monaco Editor初始化失败:', error);
|
||||||
console.error('错误详情:', error.stack);
|
console.error('错误详情:', error.stack);
|
||||||
@@ -220,11 +174,6 @@ export default {
|
|||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
// 清理触摸事件监听器
|
|
||||||
if (editorContainer.value && touchHandlers.start && touchHandlers.move) {
|
|
||||||
editorContainer.value.removeEventListener('touchstart', touchHandlers.start);
|
|
||||||
editorContainer.value.removeEventListener('touchmove', touchHandlers.move);
|
|
||||||
}
|
|
||||||
if (editor) {
|
if (editor) {
|
||||||
editor.dispose();
|
editor.dispose();
|
||||||
}
|
}
|
||||||
@@ -246,26 +195,10 @@ export default {
|
|||||||
border: 1px solid #dcdfe6;
|
border: 1px solid #dcdfe6;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
/* 允许用户选择文本 */
|
|
||||||
-webkit-user-select: text;
|
|
||||||
user-select: text;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.monaco-editor-container :deep(.monaco-editor) {
|
.monaco-editor-container :deep(.monaco-editor) {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 移动端:禁用页面缩放,只允许编辑器字体缩放 */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.monaco-editor-container {
|
|
||||||
/* 禁用页面级别的缩放,只允许编辑器内部字体缩放 */
|
|
||||||
touch-action: pan-x pan-y;
|
|
||||||
}
|
|
||||||
|
|
||||||
.monaco-editor-container :deep(.monaco-editor) {
|
|
||||||
/* 禁用页面级别的缩放 */
|
|
||||||
touch-action: pan-x pan-y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -400,7 +400,7 @@ export default {
|
|||||||
const data = result.data
|
const data = result.data
|
||||||
|
|
||||||
// 检查是否支持目录解析
|
// 检查是否支持目录解析
|
||||||
const supportedPans = ["iz", "lz", "fj", "ye", "le"]
|
const supportedPans = ["iz", "lz", "fj", "ye"]
|
||||||
if (!supportedPans.includes(data.shareLinkInfo.type)) {
|
if (!supportedPans.includes(data.shareLinkInfo.type)) {
|
||||||
this.$message.error("当前网盘不支持目录解析")
|
this.$message.error("当前网盘不支持目录解析")
|
||||||
return
|
return
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -4,10 +4,8 @@ const path = require("path");
|
|||||||
function resolve(dir) {
|
function resolve(dir) {
|
||||||
return path.join(__dirname, dir)
|
return path.join(__dirname, dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
const CompressionPlugin = require('compression-webpack-plugin');
|
const CompressionPlugin = require('compression-webpack-plugin');
|
||||||
const FileManagerPlugin = require('filemanager-webpack-plugin');
|
const FileManagerPlugin = require('filemanager-webpack-plugin')
|
||||||
const MonacoEditorPlugin = require('monaco-editor-webpack-plugin');
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
productionSourceMap: false, // 是否在构建生产包时生成sourceMap文件,false将提高构建速度
|
productionSourceMap: false, // 是否在构建生产包时生成sourceMap文件,false将提高构建速度
|
||||||
@@ -45,7 +43,7 @@ module.exports = {
|
|||||||
'@': resolve('src')
|
'@': resolve('src')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Monaco Editor配置 - 使用本地打包
|
// Monaco Editor配置 - 使用国内CDN
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
@@ -55,18 +53,9 @@ module.exports = {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new MonacoEditorPlugin({
|
|
||||||
languages: ['javascript', 'typescript', 'json'],
|
|
||||||
features: ['coreCommands', 'find', 'format', 'suggest', 'quickCommand'],
|
|
||||||
publicPath: process.env.NODE_ENV === 'production' ? './' : '/',
|
|
||||||
// Worker 文件输出路径
|
|
||||||
filename: 'js/[name].worker.js'
|
|
||||||
}),
|
|
||||||
new CompressionPlugin({
|
new CompressionPlugin({
|
||||||
test: /\.js$|\.html$|\.css/, // 匹配文件
|
test: /\.js$|\.html$|\.css/, // 匹配文件
|
||||||
threshold: 10240, // 对超过10k文件压缩
|
threshold: 10240 // 对超过10k文件压缩
|
||||||
// 排除 js 目录下的 worker 文件(Monaco Editor 使用 vs/assets 下的)
|
|
||||||
exclude: /js\/.*\.worker\.js$/
|
|
||||||
}),
|
}),
|
||||||
new FileManagerPlugin({ //初始化 filemanager-webpack-plugin 插件实例
|
new FileManagerPlugin({ //初始化 filemanager-webpack-plugin 插件实例
|
||||||
events: {
|
events: {
|
||||||
@@ -81,11 +70,7 @@ module.exports = {
|
|||||||
{ source: '../webroot/nfd-front/view/.gitignore', options: { force: true } },
|
{ source: '../webroot/nfd-front/view/.gitignore', options: { force: true } },
|
||||||
],
|
],
|
||||||
copy: [
|
copy: [
|
||||||
// 复制 Monaco Editor 的 vs 目录到 js/vs
|
{ source: './nfd-front', destination: '../webroot/nfd-front' }
|
||||||
{
|
|
||||||
source: './node_modules/monaco-editor/min/vs',
|
|
||||||
destination: './nfd-front/js/vs'
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
archive: [ //然后我们选择dist文件夹将之打包成dist.zip并放在根目录
|
archive: [ //然后我们选择dist文件夹将之打包成dist.zip并放在根目录
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ proxyConf: server-proxy
|
|||||||
# JS演练场配置
|
# JS演练场配置
|
||||||
playground:
|
playground:
|
||||||
# 是否启用演练场,默认false不启用
|
# 是否启用演练场,默认false不启用
|
||||||
enabled: true
|
enabled: false
|
||||||
# 公开模式,默认false需要密码访问,设为true则无需密码
|
# 公开模式,默认false需要密码访问,设为true则无需密码
|
||||||
public: false
|
public: false
|
||||||
# 访问密码,建议修改默认密码!
|
# 访问密码,建议修改默认密码!
|
||||||
@@ -110,4 +110,4 @@ auths:
|
|||||||
# 123网盘:配置用户名密码
|
# 123网盘:配置用户名密码
|
||||||
ye:
|
ye:
|
||||||
username:
|
username:
|
||||||
password:
|
password:
|
||||||
Reference in New Issue
Block a user