Compare commits

..
Author SHA1 Message Date
qaiu a1ae6b07fe - 升级vertx到4.5.6
- 添加Cloudreve
- 蓝奏云优享和小飞机规则修改
2024-04-09 12:13:26 +08:00
qaiu 0fa5f24fb0 Merge pull request #35 from qaiu/dependabot/npm_and_yarn/web-front/webpack-dev-middleware-5.3.4
Bump webpack-dev-middleware from 5.3.3 to 5.3.4 in /web-front
2024-03-24 15:23:37 +08:00
qaiu 0b225044a2 Merge pull request #34 from qaiu/dependabot/npm_and_yarn/web-front/follow-redirects-1.15.6
Bump follow-redirects from 1.15.2 to 1.15.6 in /web-front
2024-03-24 15:23:15 +08:00
dependabot[bot] 27ac7bf91a Bump webpack-dev-middleware from 5.3.3 to 5.3.4 in /web-front
Bumps [webpack-dev-middleware](https://github.com/webpack/webpack-dev-middleware) from 5.3.3 to 5.3.4.
- [Release notes](https://github.com/webpack/webpack-dev-middleware/releases)
- [Changelog](https://github.com/webpack/webpack-dev-middleware/blob/v5.3.4/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-dev-middleware/compare/v5.3.3...v5.3.4)

---
updated-dependencies:
- dependency-name: webpack-dev-middleware
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
2024-03-23 19:01:51 +00:00
dependabot[bot] e66ed57611 Bump follow-redirects from 1.15.2 to 1.15.6 in /web-front
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.2 to 1.15.6.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.2...v1.15.6)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
2024-03-16 14:15:40 +00:00
qaiu 9e6feb1178 更新 README.md 2024-01-29 00:35:23 +08:00
qaiu 1413231c82 更新 README.md 2024-01-29 00:34:43 +08:00
qaiu 47b84201f0 更新 README.md 2024-01-29 00:23:34 +08:00
qaiu ab570d5f06 更新 README.md 2024-01-29 00:22:39 +08:00
qaiu 002f32f680 .. 2024-01-28 15:29:41 +08:00
qaiu 595a83bf58 添加蓝奏云优享解析支持 2024-01-28 15:26:22 +08:00
qaiu 21b085ba57 蓝奏云域名修改, shell分隔符调整 2024-01-27 10:53:52 +08:00
qaiu 644d23fdf7 Update README.md 2023-12-27 14:27:31 +08:00
qaiu 2d79165ae4 Merge pull request #28 from qaiu/dev-reference
Dev reference 添加文叔叔解析
2023-12-16 14:35:44 +08:00
qaiu 07c5ecb4bc 加入Jansi 让windows cmd日志彩色输出 2023-12-15 10:14:00 +08:00
BLSKWOLF 001f634d8e 更新 README.md 2023-12-11 23:56:10 +08:00
BLSKWOLF 69e0714f7e 添加文叔叔解析
1.修复QQ邮箱解析
2.添加文叔叔解析
2023-12-11 23:51:49 +08:00
BLSKWOLF 1bd346feee 更新 README.md 2023-12-11 17:46:42 +08:00
BLSKWOLF 317d04d1b6 [未完成]添加QQ邮箱文件解析
添加QQ邮箱文件解析,暂时未解决cookie的问题
2023-12-11 17:36:39 +08:00
qaiu 1ac59187f8 Update README.md 2023-12-05 17:32:17 +08:00
qaiu 2d77cfe71a Update README.md 2023-12-05 12:49:07 +08:00
qaiu 84311cd131 Update README.md 2023-12-05 11:43:04 +08:00
qaiu 176aeac64c Update README.md 2023-12-05 11:39:26 +08:00
qaiu 056e0138c1 Merge pull request #27 from qaiu/dependabot/maven/ch.qos.logback-logback-classic-1.4.12
Bump ch.qos.logback:logback-classic from 1.4.7 to 1.4.12
2023-12-05 09:48:52 +08:00
dependabot[bot] 6b4869df73 Bump ch.qos.logback:logback-classic from 1.4.7 to 1.4.12
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.4.7 to 1.4.12.
- [Commits](https://github.com/qos-ch/logback/compare/v_1.4.7...v_1.4.12)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-classic
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
2023-12-05 01:47:03 +00:00
qaiu 3ace95c0cc Update README.md 2023-12-05 09:46:22 +08:00
qaiu 6867e70450 1. 日志配置微调 2. 乐云验证 2023-11-29 14:44:42 +08:00
qaiu a7e855b3af vertx升级4.5.0 2023-11-28 17:00:11 +08:00
qaiu 4e297912cd Merge remote-tracking branch 'origin/main' 2023-11-28 16:53:48 +08:00
qaiu 5bbab18419 1. 添加联想乐云直链解析 2. 优化代码逻辑 2023-11-28 16:51:51 +08:00
qaiu dd3fca5e39 Merge pull request #25 from qaiu/dependabot/npm_and_yarn/web-front/axios-1.6.0
Bump axios from 1.2.2 to 1.6.0 in /web-front
2023-11-10 10:03:31 +08:00
dependabot[bot] e8922dd31a Bump axios from 1.2.2 to 1.6.0 in /web-front
Bumps [axios](https://github.com/axios/axios) from 1.2.2 to 1.6.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/1.2.2...v1.6.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
2023-11-10 02:02:37 +00:00
qaiu 7770fbd96f Merge pull request #22 from qaiu/dependabot/npm_and_yarn/web-front/babel/traverse-7.23.2
Bump @babel/traverse from 7.20.12 to 7.23.2 in /web-front
2023-11-10 10:02:09 +08:00
qaiu 061efb6b7e 小飞机网盘分享新URL格式适配 2023-11-02 10:43:10 +08:00
dependabot[bot] 819dfe427c Bump @babel/traverse from 7.20.12 to 7.23.2 in /web-front
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.20.12 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
2023-10-18 17:31:32 +00:00
qaiu d4d12e8bd0 更新 README.md 2023-09-23 11:21:47 +08:00
qaiu 97745e7240 Update README.md 2023-09-21 17:32:09 +08:00
qaiu 995117a026 Update README.md 更新jdk下载网盘为移动云空间 2023-09-21 17:30:55 +08:00
qaiu 7cccabfc2b Update README.md 2023-09-17 09:54:39 +08:00
qaiu 1077ca1763 蓝奏云js引入问题修复 2023-09-17 09:48:23 +08:00
QAIU 25f7c4d9c9 Merge remote-tracking branch 'origin/main' 2023-09-08 17:42:09 +08:00
QAIU afd399dc8a update test 2023-09-08 17:41:30 +08:00
qaiu 5299a539f6 Update README.md 2023-09-07 08:52:43 +08:00
qaiu 90d53b6db1 Update README.md 错别字 2023-09-06 03:08:17 +08:00
qaiu 4c6c0ee2ae Update README.md 2023-09-06 03:06:55 +08:00
qaiu b96561accb Update README.md 2023-09-06 03:05:47 +08:00
QAIU 0308bf4668 解析用到的js文件改为String方式引入 2023-09-01 18:07:21 +08:00
qaiu c405d0a993 Update README.md 2023-08-28 22:37:26 +08:00
QAIU 45e50d689e 去除web-service无用依赖 2023-08-28 17:58:00 +08:00
QAIU 9f697f3426 预览首页统计修复,123盘大文件长度取值错误问题 2023-08-28 17:54:39 +08:00
QAIU 9b4f03f55e aaa 2023-08-25 18:30:27 +08:00
QAIU 1b8aaacb60 Merge remote-tracking branch 'origin/main' 2023-08-25 18:29:00 +08:00
QAIU aeb1ff7c4d 版本更新至0.1.7,启用h2db,添加统计功能,框架优化 2023-08-25 18:28:45 +08:00
qaiu 39caa93d9d 错别字 2023-08-25 17:04:37 +08:00
QAIU e3054835ed Merge remote-tracking branch 'origin/main' 2023-08-25 16:56:00 +08:00
QAIU 508f5370c3 版本更新至0.1.7,启用h2db,添加统计功能,框架优化 2023-08-25 16:55:38 +08:00
qaiu 7c368de2a8 Update README.md 2023-08-25 10:04:44 +08:00
qaiu 1648688189 Update README.md 2023-08-25 10:04:27 +08:00
QAIU efe2dabce4 蓝奏云规则微调, 框架优化(70%) 2023-08-23 14:55:08 +08:00
qaiu 18f932f831 Update LzTool.java 2023-08-21 12:27:39 +08:00
qaiu 2818011518 Update README.md 2023-08-20 10:36:39 +08:00
qaiu 991e9bc153 Merge pull request #13 from qaiu/dev
蓝奏云规则修改, 奶牛功能增强
2023-08-20 10:35:02 +08:00
qaiu bee8401580 修复蓝奏云pan解析错误的问题#12 2023-08-20 10:29:00 +08:00
QAIU 15be132d54 Merge remote-tracking branch 'origin/dev' into dev 2023-08-19 17:29:04 +08:00
QAIU cac06295e4 奶牛快传支持下载zip目录分享#11, core框架优化 2023-08-19 17:28:30 +08:00
qaiu db430832cc Update README.md 2023-08-13 00:58:12 +08:00
qaiu d8c8bee898 Update README.md 2023-08-13 00:55:20 +08:00
qaiu 0b050a407f Update README.md 2023-08-13 00:54:21 +08:00
QAIU 10e4082d44 奶牛快传支持下载zip目录分享#11, core框架优化 2023-08-11 15:28:37 +08:00
QAIU 92d4f4fc79 core框架优化 2023-08-10 14:54:45 +08:00
qaiu 6a0e22add8 修复123pan解析错误的问题#9 2023-08-10 01:49:41 +08:00
qaiu a33863983e 代码结构优化;修复123pan解析错误的问题#9 2023-08-10 01:45:39 +08:00
qaiu 36db7b28bb 代码结构优化;修复123pan解析错误的问题#9 2023-08-10 01:42:34 +08:00
qaiu 123eef9f7b 代码结构优化
修复123pan解析错误的问题#9
2023-08-10 01:38:20 +08:00
qaiu 346d8fd253 Merge pull request #10 from qaiu/master
Master
2023-08-10 01:17:11 +08:00
qaiu 86f50adaf4 fixed 123pan /b/api/share/download/info->/a/api/share/download/info 2023-08-10 00:59:09 +08:00
QAIU 281df7b3b1 代码结构优化, 异常处理优化 2023-08-08 17:36:36 +08:00
QAIU a2b286c58a 代码结构优化, 异常处理优化 2023-08-08 11:34:32 +08:00
QAIU fd0a4abfaa 0 2023-08-04 17:49:30 +08:00
qaiu 989dd8fec5 Update README.md 2023-08-02 17:53:04 +08:00
QAIU dd3a43ccf8 0 2023-08-01 14:42:32 +08:00
qaiu 31b1e7e955 前端页面优化 2023-07-31 22:43:23 +08:00
qaiu 38fb9623bb 前端页面优化 2023-07-31 22:41:33 +08:00
QAIU 8b450a4850 0 2023-07-31 14:07:12 +08:00
QAIU 88806b4622 Merge remote-tracking branch 'origin/main' 2023-07-31 13:56:49 +08:00
QAIU e87149358c 360亿方云API-URL变更 2023-07-31 13:56:35 +08:00
qaiu 98aa767787 Update README.md 2023-07-31 12:06:59 +08:00
qaiu 0946047136 Update README.md 2023-07-31 12:03:34 +08:00
qaiu 1628a8f486 Merge pull request #6 from qaiu/dependabot/npm_and_yarn/web-front/webpack-5.88.2
Bump webpack from 5.75.0 to 5.88.2 in /web-front
2023-07-31 11:42:09 +08:00
qaiu 581c0662f8 Merge pull request #7 from qaiu/dependabot/npm_and_yarn/web-front/word-wrap-1.2.5
Bump word-wrap from 1.2.3 to 1.2.5 in /web-front
2023-07-31 11:41:57 +08:00
dependabot[bot] 5b3f614a64 Bump webpack from 5.75.0 to 5.88.2 in /web-front
Bumps [webpack](https://github.com/webpack/webpack) from 5.75.0 to 5.88.2.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.75.0...v5.88.2)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
2023-07-31 03:39:18 +00:00
QAIU db4131c4f9 lz.qaiu.top加入前端页面 2023-07-31 11:38:23 +08:00
qaiu 1c0426d827 前端优化 2023-07-31 05:08:13 +08:00
dependabot[bot] 010780b82d Bump word-wrap from 1.2.3 to 1.2.5 in /web-front
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.5.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.5)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
2023-07-30 19:22:56 +00:00
qaiu 78f29f7a75 Merge remote-tracking branch 'origin/main' 2023-07-31 03:22:02 +08:00
qaiu 5a630db5a6 添加前端页面 2023-07-31 03:21:39 +08:00
qaiu cefcc576b6 Update README.md 2023-07-31 00:20:52 +08:00
qaiu 0e3b5a3007 Update README.md 2023-07-30 16:29:27 +08:00
qaiu 5e271b853c 添加 通过JS文件获取123pan签名 2023-07-29 18:08:30 +08:00
QAIU 54e8f73427 123网盘解析规则优化 2023-07-27 17:45:53 +08:00
QAIU 34baecb535 123网盘解析规则优化 2023-07-24 10:47:01 +08:00
QAIU f75f333cda Merge remote-tracking branch 'origin/main' 2023-07-24 10:41:43 +08:00
QAIU 8ea8338cbf 123网盘解析规则优化 2023-07-24 10:41:25 +08:00
qaiu fc1e10b016 Update README.md 2023-07-23 15:15:25 +08:00
qaiu 87e2e6128a Update README.md 2023-07-23 15:13:23 +08:00
qaiu 7799719b99 Update README.md 2023-07-23 15:12:58 +08:00
qaiu 55a5b52faf Update README.md
lz.qaiu.top测试123pan 下载Dragonwell JDK17
2023-07-22 15:33:10 +08:00
qaiu a578bc58be Update README.md
lz.qaiu.top测试123pan 下载Dragonwell JDK17
2023-07-22 15:32:13 +08:00
QAIU 18dc30c03d 修改123网盘解析规则 2023-07-22 12:34:02 +08:00
QAIU ff2b7608d2 细节优化 2023-07-21 10:29:21 +08:00
qaiu 5ac5cdced9 Update README.md 2023-07-16 04:26:05 +08:00
qaiu b6d0a6cd06 修改win服务模板 2023-07-16 03:45:54 +08:00
qaiu 9a20636d2d - 更新版本号: 0.1.5->0.1.6 2023-07-16 03:11:41 +08:00
qaiu f5abaca5a1 - WebServer: PanTool优化异常处理
- Core: Vertx事件循环线程数调整
2023-07-16 02:47:39 +08:00
qaiu 26ab5f7945 Merge pull request #5 from qaiu/dependabot/maven/com.h2database-h2-2.2.220
Bump h2 from 2.1.214 to 2.2.220
2023-07-10 06:05:17 +08:00
qaiu c14d8fccfd Merge pull request #4 from qaiu/dependabot/maven/core-database/com.h2database-h2-2.2.220
Bump h2 from 2.1.214 to 2.2.220 in /core-database
2023-07-10 06:04:58 +08:00
dependabot[bot] fe28050893 Bump h2 from 2.1.214 to 2.2.220
Bumps [h2](https://github.com/h2database/h2database) from 2.1.214 to 2.2.220.
- [Release notes](https://github.com/h2database/h2database/releases)
- [Commits](https://github.com/h2database/h2database/compare/version-2.1.214...version-2.2.220)

---
updated-dependencies:
- dependency-name: com.h2database:h2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
2023-07-07 22:01:48 +00:00
dependabot[bot] af9d890f98 Bump h2 from 2.1.214 to 2.2.220 in /core-database
Bumps [h2](https://github.com/h2database/h2database) from 2.1.214 to 2.2.220.
- [Release notes](https://github.com/h2database/h2database/releases)
- [Commits](https://github.com/h2database/h2database/compare/version-2.1.214...version-2.2.220)

---
updated-dependencies:
- dependency-name: com.h2database:h2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
2023-07-07 22:00:11 +00:00
QAIU c640b9d9d1 解决一些 warning jdk14对可序列化对象添加@Serial 2023-06-21 16:49:01 +08:00
QAIU a1dff75bdf Merge remote-tracking branch 'origin/main' 2023-06-21 15:40:42 +08:00
QAIU f02826339a musetransfer api test 2023-06-21 15:40:24 +08:00
qaiu 656fd672fc Zzz 2023-06-21 02:53:58 +00:00
qaiu baa2e0ac75 Cow解析重复创建实例VertxHolder.getVertxInstance() 2023-06-21 02:53:24 +00:00
qaiu 3375144af6 cow parse fail return 2023-06-21 02:50:19 +00:00
QAIU 028b75cfd4 add mu pan 2023-06-14 17:47:20 +08:00
QAIU 026cd72a4b Zzz 2023-06-14 16:14:37 +08:00
qaiu db21eb99f0 Update README.md 2023-06-14 15:48:21 +08:00
qaiu 09aa9a97c2 Update README.md 2023-06-14 15:48:08 +08:00
qaiu 44d18ad273 Update README.md 2023-06-14 15:40:08 +08:00
qaiu 44b82da51d Create LICENSE 2023-06-14 15:23:59 +08:00
QAIU 0f65e6cb2e Zzz 2023-06-14 11:57:58 +08:00
QAIU 407f4fafd9 Zzz 2023-06-14 11:56:33 +08:00
QAIU 66850a72f7 Zzz 2023-06-14 11:53:27 +08:00
QAIU 4acf716868 依赖打包优化 2023-06-14 11:26:33 +08:00
qaiu 2158be14ca 修复Linux运行脚本 2023-06-14 10:30:16 +08:00
QAIU 21d3340f3c Linux部署服务模板修改 2023-06-13 15:11:32 +08:00
qaiu 6972b5e761 Update README.md 2023-06-13 13:58:26 +08:00
QAIU eac1d62f76 update 0.1.5 2023-06-13 13:25:22 +08:00
QAIU b88fee9e83 update 0.1.5 2023-06-13 13:23:09 +08:00
QAIU e075967c14 移动云空间需要注意的地方 2023-06-13 10:53:02 +08:00
qaiu 2c8ee31cb7 Merge pull request #3 from qaiu/dev
Controller 层不支持方法重载: ServerApi
2023-06-13 10:30:36 +08:00
qaiu 1bbb7728d8 Controller 层不支持方法重载: ServerApi 2023-06-13 10:29:23 +08:00
qaiu 7e0d4b3bad 接口重构 2023-06-13 08:22:11 +08:00
qaiu 5f25d32f97 接口重构 2023-06-13 05:43:37 +08:00
QAIU a890ef54a3 测试相关 2023-06-12 16:41:06 +08:00
QAIU 68b7c57384 Zzzz 2023-06-12 16:07:28 +08:00
QAIU 2596e58943 重构蓝奏云解析代码, 加入蓝奏云加密分享解析 2023-06-12 10:39:33 +08:00
QAIU 9419c7e0ca 蓝奏云密码解析测试 2023-06-10 17:45:25 +08:00
QAIU 8973a0e4b2 蓝奏云密码解析测试 2023-06-10 17:32:25 +08:00
QAIU 3b3dd0634f change README.md 2023-06-10 16:41:25 +08:00
QAIU 5343c68603 change README.md 2023-06-10 16:35:33 +08:00
QAIU b8057135a9 添加123云盘直链解析 2023-06-10 16:31:17 +08:00
QAIU e941d76507 修改密码分隔符$->@ 2023-06-10 13:34:01 +08:00
QAIU b189614d29 - 加入360亿方云直链解析
- 优化代码
2023-06-10 13:29:22 +08:00
QAIU 70fe0c23ae - 加入小飞机盘直链解析
- 优化代码
2023-06-09 15:43:21 +08:00
QAIU 9a3baebec9 Merge remote-tracking branch 'origin/main' 2023-06-08 17:34:02 +08:00
QAIU 74e95d0f12 其他网盘API 2023-06-08 17:33:42 +08:00
152 changed files with 15339 additions and 13659 deletions
-28
View File
@@ -1,28 +0,0 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/java
{
"name": "Java",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/java:0-17",
"features": {
"ghcr.io/devcontainers/features/java:1": {
"version": "none",
"installMaven": "true",
"installGradle": "false"
},
"ghcr.io/devcontainers-contrib/features/ant-sdkman:2": {}
}
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "java -version",
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
-8
View File
@@ -1,8 +0,0 @@
# 文本文件使用 LF 换行符,适用于 Linux 和 macOS
*.sh text eol=lf
*.service text eol=lf
# Windows 执行的文件使用 CRLF 换行符
*.bat text eol=crlf
*.cmd text eol=crlf
bin/nfd-service-template.xml text eol=crlf
-2
View File
@@ -37,8 +37,6 @@ jobs:
# Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive
- name: Update dependency graph
uses: advanced-security/maven-dependency-submission-action@v3
with:
ignore-maven-wrapper: true
# - uses: release-drafter/release-drafter@v5
# env:
-1
View File
@@ -39,4 +39,3 @@ gradlew.bat
unused.txt
/web-service/src/main/generated/
/db
/webroot/nfd-front/
-117
View File
@@ -1,117 +0,0 @@
/*
* Copyright 2007-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.net.*;
import java.io.*;
import java.nio.channels.*;
import java.util.Properties;
public class MavenWrapperDownloader {
private static final String WRAPPER_VERSION = "0.5.6";
/**
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
*/
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
/**
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
* use instead of the default one.
*/
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
".mvn/wrapper/maven-wrapper.properties";
/**
* Path where the maven-wrapper.jar will be saved to.
*/
private static final String MAVEN_WRAPPER_JAR_PATH =
".mvn/wrapper/maven-wrapper.jar";
/**
* Name of the property which should be used to override the default download url for the wrapper.
*/
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
public static void main(String args[]) {
System.out.println("- Downloader started");
File baseDirectory = new File(args[0]);
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
// If the maven-wrapper.properties exists, read it and check if it contains a custom
// wrapperUrl parameter.
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
String url = DEFAULT_DOWNLOAD_URL;
if(mavenWrapperPropertyFile.exists()) {
FileInputStream mavenWrapperPropertyFileInputStream = null;
try {
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
Properties mavenWrapperProperties = new Properties();
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
} catch (IOException e) {
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
} finally {
try {
if(mavenWrapperPropertyFileInputStream != null) {
mavenWrapperPropertyFileInputStream.close();
}
} catch (IOException e) {
// Ignore ...
}
}
}
System.out.println("- Downloading from: " + url);
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
if(!outputFile.getParentFile().exists()) {
if(!outputFile.getParentFile().mkdirs()) {
System.out.println(
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
}
}
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
try {
downloadFileFromURL(url, outputFile);
System.out.println("Done");
System.exit(0);
} catch (Throwable e) {
System.out.println("- Error downloading");
e.printStackTrace();
System.exit(1);
}
}
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
String username = System.getenv("MVNW_USERNAME");
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
}
URL website = new URL(urlString);
ReadableByteChannel rbc;
rbc = Channels.newChannel(website.openStream());
FileOutputStream fos = new FileOutputStream(destination);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
fos.close();
rbc.close();
}
}
BIN
View File
Binary file not shown.
-2
View File
@@ -1,2 +0,0 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
-15
View File
@@ -1,15 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="AppMain" type="Application" factoryName="Application" nameIsGenerated="true">
<option name="MAIN_CLASS_NAME" value="cn.qaiu.lz.AppMain" />
<module name="web-service" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="cn.qaiu.lz.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
-70
View File
@@ -1,70 +0,0 @@
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "Current File",
"request": "launch",
"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",
"name": "AppMain",
"request": "launch",
"mainClass": "cn.qaiu.lz.AppMain",
"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"
}
]
}
+1 -2
View File
@@ -1,4 +1,3 @@
{
"java.compile.nullAnalysis.mode": "automatic",
"java.configuration.updateBuildConfiguration": "interactive"
"java.compile.nullAnalysis.mode": "automatic"
}
+75 -62
View File
@@ -1,20 +1,18 @@
云盘解析服务 (nfd云解析)
预览地址 https://lz.qaiu.top
main分支依赖JDK17, 提供了JDK11分支[main-jdk11](https://github.com/qaiu/netdisk-fast-download/tree/main-jdk11)
**注意: 请不要过度依赖lz.qaiu.top预览地址服务,建议本地搭建或者云服务器自行搭建。
解析次数过多IP会被部分网盘厂商限制,不推荐做公共解析。**
[![Java CI with Maven](https://github.com/qaiu/netdisk-fast-download/actions/workflows/maven.yml/badge.svg)](https://github.com/qaiu/netdisk-fast-download/actions/workflows/maven.yml)
[![jdk](https://img.shields.io/badge/jdk-%3E%3D17-blue)](https://www.oracle.com/cn/java/technologies/downloads/)
[![vert.x](https://img.shields.io/badge/vert.x-4.5.6-blue)](https://vertx-china.github.io/)
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/qaiu/netdisk-fast-download)](https://github.com/qaiu/netdisk-fast-download/releases/latest)
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/qaiu/netdisk-fast-download)](https://github.com/qaiu/netdisk-fast-download/releases/tag/0.1.6-releases)
## 项目介绍
网盘直链解析工具能把网盘分享下载链接转化为直链,已支持蓝奏云/蓝奏云优享/奶牛快传/移动云云空间/小飞机盘/亿方云/123云盘/Cloudreve等,支持加密分享。
**0.1.8及以上版本json接口格式有调整,尤其依赖lz.qaiu.top做下载服务的朋友们记得修改, 参考json返回数据格式示例**
*重要声明:本项目仅供学习参考;请不要将此项目用于任何商业用途,否则可能带来严重的后果。转发/分享该项目请注明来源*
*重要声明:本项目仅供学习参考;请不要将此项目用于任何商业用途,否则可能带来严重的后果。*
## 网盘支持情况:
> 20230905 奶牛云直链做了防盗链,需加入请求头:Referer: https://cowtransfer.com/
@@ -24,63 +22,87 @@ main分支依赖JDK17, 提供了JDK11分支[main-jdk11](https://github.com/qaiu/
`网盘名称(网盘标识):`
- [蓝奏云 (lz)](https://pc.woozooo.com/)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析
- [蓝奏云优享 (iz)](https://www.ilanzou.com/)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析
- [奶牛快传 (cow)](https://cowtransfer.com/)
- [移动云云空间 (ec)](https://www.ecpan.cn/web)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析
- [移动云空间 (ec)](https://www.ecpan.cn/web)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析
- [小飞机网盘 (fj)](https://www.feijipan.com/)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析
- [亿方云 (fc)](https://www.fangcloud.com/)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析
- [123云盘 (ye)](https://www.123pan.com/)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析
- [文叔叔 (ws)](https://www.wenshushu.cn/)
- [联想乐云 (le)](https://lecloud.lenovo.com/)
- [QQ邮箱文件中转站 (qq)](https://mail.qq.com/)
- [超星网盘-开发中 (cx)](https://passport2.chaoxing.com/login?newversion=true&refer=https%3A%2F%2Fpan-yz.chaoxing.com%2F)
- [城通网盘(ct)](https://www.ctfile.com)
- [网易云音乐(mne)](https://music.163.com)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析
- [Cloudreve自建网盘 (ce)](https://github.com/cloudreve/Cloudreve)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析
- [QQ邮箱 (qq) 暂不可用-存在cookie问题](https://wx.mail.qq.com/)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析(用户无法直接使用直链)
- [夸克网盘 (qk) 寄了](https://pan.quark.cn/)
- [UC网盘 (uc) 寄了](https://fast.uc.cn/)
**TODO:**
- 登录接口, 文件上传/下载/分享后端接口
- 短地址服务
- 前端界面(建设中...)
### API接口说明
your_host指的是您的域名或者IP,实际使用时替换为实际域名或者IP,端口默认6400,可以使用nginx代理来做域名访问
解析方式分为两种类型直接跳转下载文件和获取下载链接,
每一种都提供了两种接口形式: `通用接口parser?url=``网盘标志/分享key拼接的短地址(标志短链)`,所有规则参考示例。
your_host指的是您的域名或者IP,实际使用时替换为实际域名或者IP。
解析方式分为两种类型直接跳转下载链接和获取下载链接(JSON),每一种都提供了两种接口形式parser和网盘标志/分享key拼接的短地址(标志短链),所有规则参考示例。
- 通用接口: `/parser?url=分享链接`,加密分享需要加上参数pwd=密码;
- 标志短链: `/网盘标识/分享key` 在分享Key后面加上@密码;
- 直链JSON: `通用接口``标志短链`前加上`/json` 加密分享的密码规则同上;
- 网盘标识参考上面网盘支持情况
- 当带有分享密码时需要加上密码参数(pwd)
- 移动云空间,小飞机网盘的加密分享的密码可以忽略
- 移动云空间分享key取分享链接中的data参数,比如`&data=xxx`的参数就是xxx
- 括号内是可选内容: 表示当带有分享密码时需要加上密码参数
- 移动云空间,小飞机网盘的加密分享的密码可以忽略
API规则:
规则示例:
```
1. 解析并自动302跳转 :
http://your_host/parser?url=分享链接&pwd=xxx
http://your_host/网盘标识/分享key@分享密码
http://your_host/parser?url=分享链接(&pwd=xxx)
http://your_host/网盘标识/分享key(@分享密码)
2. 获取解析后的直链--JSON格式
http://your_host/json/parser?url=分享链接&pwd=xxx
http://your_host/json/网盘标识/分享key@分享密码
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返回数据格式示例:
`shareKey`: 全局分享key
`directLink`: 下载链接
`cacheHit`: 是否为缓存链接
`expires`: 缓存到期时间
```json
{
"code": 200,
"msg": "success",
"success": true,
"count": 0,
"data": {
"shareKey": "lz:xxx",
"directLink": "下载直链",
"cacheHit": true,
"expires": "2024-09-18 01:48:02",
"expiration": 1726638482825
},
"timestamp": 1726637151902
"data": "https://下载链接",
"timestamp": 1690733953927
}
```
@@ -123,17 +145,17 @@ GET http://127.0.0.1:6400/json/fc/e5079007dc31226096628870c7@QAIU
# 网盘对比
| 网盘名称 | 免登陆下载分享 | 加密分享 | 初始网盘空间 | 单文件大小限制 |
|-------------|---------|----------|-----------|-----------------|
| 蓝奏云 | √ | √ | 不限空间 | 100M |
| 奶牛快传 | √ | X | 10G | 不限大小 |
| 移动云空间(个人版) | √ | √(密码可忽略) | 5G(个人) | 不限大小 |
| 小飞机网盘 | | √(密码可忽略) | 10G | 不限大小 |
| 360亿方云 | √ | √(密码可忽略) | 100G(须实名) | 不限大小 |
| 123云盘 | √ | √ | 2T | 100G>100M需要登录) |
| 文叔叔 | √ | √ | 10G | 5GB |
| 夸克网盘 | x | √ | 10G | 不限大小 |
| UC网盘 | x | √ | 10G | 不限大小 |
| 网盘名称 | 可直接下载分享 | 加密分享 | 初始网盘空间 | 单文件大小限制 | 登录接口 |
|------------|------------------------|----------|-----------|-----------------|------|
| 蓝奏云 | √ | √ | 不限空间 | 100M | TODO |
| 奶牛快传 | √ | X | 10G | 不限大小 | TODO |
| 移动云空间 | √ | √(密码可忽略) | 5G(个人) | 不限大小 | TODO |
| UC网盘 | 需要登录 | √ | 10G | 不限大小 | TODO |
| 小飞机网盘 | √ | √(密码可忽略) | 10G | 不限大小 | TODO |
| 360亿方云 | √(试用账号有时间限制企业版需要599续费) | √(密码可忽略) | 100G(须实名) | 不限大小 | TODO |
| 123云盘 | √ | √ | 2T | 100G>100M需要登录) | TODO |
| 文叔叔(TODO) | √(注意有时间限制) | √ | 10G | 5GB | TODO |
| 夸克网盘(TODO) | 需要登录 | √ | 10G | 不限大小 | TODO |
# 打包部署
@@ -141,7 +163,7 @@ GET http://127.0.0.1:6400/json/fc/e5079007dc31226096628870c7@QAIU
- [阿里jdk17(Dragonwell17-windows-x86)](https://lz.qaiu.top/ec/e957acef36ce89e1053979672a90d219n)
- [阿里jdk17(Dragonwell17-linux-x86)](https://lz.qaiu.top/ec/6ebc9f2e0bbd53b4c4d5b11013f40a80NHvcYU)
- [阿里jdk17(Dragonwell17-linux-aarch64)](https://lz.qaiu.top/ec/d14c2d06296f61b52a876b525265e0f8tzxTc5)
- [解析有效性测试-移动云空间-阿里jdk17-linux-x86](https://lz.qaiu.top/json/ec/6ebc9f2e0bbd53b4c4d5b11013f40a80NHvcYU)
- [解析有效性测试-移动云空间-阿里jdk17-linux-x86](https://lz.qaiu.top/json/ec/6ebc9f2e0bbd53b4c4d5b11013f40a80NHvcYU)
## 开发和打包
@@ -153,11 +175,10 @@ mvn package
```
打包好的文件位于 web-service/target/netdisk-fast-download-bin.zip
## Linux服务部署
### [宝塔安装参考](https://blog.qaiu.top/archives/netdisk-fast-download-bao-ta-an-zhuang-jiao-cheng)
> 注意: netdisk-fast-download.service中的ExecStart的路径改为实际路径
```shell
cd ~
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
wget -O netdisk-fast-download.zip https://github.com/qaiu/netdisk-fast-download/releases/download/0.1.7/netdisk-fast-download.zip
unzip netdisk-fast-download-bin.zip
cd netdisk-fast-download
bash service-install.sh
@@ -189,20 +210,13 @@ bash service-install.sh
如果不想使用服务运行可以直接运行run.bat
> 注意: 如果jdk环境变量的java版本不是17请修改nfd-service-template.xml中的java命令的路径改为实际路径
## 相关配置说明
## 0.1.8 开发计划
- Docker部署
- 联想乐云解析支持
- CLoudreve解析解析
- 直链缓存
- 日志优化
resources目录下包含服务端配置文件 配置文件自带说明,具体请查看配置文件内容,
app-dev.yml 可以配置解析服务相关信息, 包括端口,域名,缓存时长等
server-proxy.yml 可以配置代理服务运行的相关信息, 包括前端反向代理端口,路径等
## 0.1.9 开发计划
- 超星网盘解析 doing
- 带Referer头的js请求下载 doing
- 城通网盘解析 √
- 目录解析(专属版)
- 带cookie/token参数解析大文件(专属版)
- docker
**技术栈:**
Jdk17+Vert.x4.4.1
@@ -215,8 +229,7 @@ Core模块集成Vert.x实现类似spring的注解式路由API
## 支持该项目
开源不易,用爱发电,本项目长期维护如果觉得有帮助, 可以请作者喝杯咖啡, 感谢支持
赞助88元以上, 可以优先体验专享版--大文件解析,目录解析
本项目长期维护如果觉得有帮助, 可以请作者喝杯咖啡, 感谢支持
![image](https://github.com/qaiu/netdisk-fast-download/assets/29825328/54276aee-cc3f-4ebd-8973-2e15f6295819)
[手机端支付宝打赏跳转链接](https://qr.alipay.com/fkx01882dnoxxtjenhlxt53)
-22
View File
@@ -1,22 +0,0 @@
@echo off
setlocal
rem 获取当前 Java 版本信息并搜索是否包含 "17."
java -version 2>&1 | find "17." >nul
rem 如果找不到 JDK 17.x,则下载并安装
if errorlevel 1 (
echo JDK 17.x not found. Downloading and installing...
REM 这里添加下载和安装 JDK 的代码
rem 验证安装
java -version
echo JDK 17.x installation complete.
) else (
echo JDK 17.x is already installed.
)
endlocal
pause
+2 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>netdisk-fast-download</artifactId>
<groupId>cn.qaiu</groupId>
<version>${revision}</version>
<version>0.1.7</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -23,6 +23,7 @@
<dependency>
<groupId>cn.qaiu</groupId>
<artifactId>core</artifactId>
<version>1.0.8</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
@@ -1,6 +1,5 @@
package cn.qaiu.db.ddl;
import cn.qaiu.db.pool.JDBCType;
import cn.qaiu.vx.core.util.ReflectionUtil;
import io.vertx.codegen.format.CamelCase;
import io.vertx.codegen.format.Case;
@@ -66,9 +65,7 @@ public class CreateTable {
}
}
public static String getCreateTableSQL(Class<?> clz, JDBCType type) {
String quotationMarks = type == JDBCType.H2DB ? "\"" : "`";
String endStr = type == JDBCType.H2DB ? ");" : ")ENGINE=InnoDB DEFAULT CHARSET=utf8;";
public static String getCreateTableSQL(Class<?> clz) {
// 判断类上是否有次注解
String primaryKey = null; // 主键
String tableName = null; // 表名
@@ -96,7 +93,7 @@ public class CreateTable {
int[] decimalSize = {22, 2};
int varcharSize = 255;
StringBuilder sb = new StringBuilder(50);
sb.append("CREATE TABLE IF NOT EXISTS ").append(quotationMarks).append(tableName).append(quotationMarks).append(" ( \r\n ");
sb.append("CREATE TABLE IF NOT EXISTS \"").append(tableName).append("\" ( \r\n ");
boolean firstId = true;
for (Field f : fields) {
Class<?> paramType = f.getType();
@@ -117,7 +114,7 @@ public class CreateTable {
decimalSize = fieldAnnotation.decimalSize();
varcharSize = fieldAnnotation.varcharSize();
}
sb.append(quotationMarks).append(column).append(quotationMarks);
sb.append("\"").append(column).append("\"");
sb.append(" ").append(sqlType);
// 添加类型长度
if (sqlType.equals("DECIMAL")) {
@@ -158,20 +155,17 @@ public class CreateTable {
//去掉最后一个逗号
int lastIndex = sql.lastIndexOf(",");
sql = sql.substring(0, lastIndex) + sql.substring(lastIndex + 1);
return sql.substring(0, sql.length() - 1) + endStr;
return sql.substring(0, sql.length() - 1) + ");\r\n";
}
public static void createTable(JDBCPool pool, JDBCType type) {
public static void createTable(JDBCPool pool) {
Set<Class<?>> tableClassList = ReflectionUtil.getReflections().getTypesAnnotatedWith(Table.class);
if (tableClassList.isEmpty()) LOGGER.info("Table model class not fount");
tableClassList.forEach(clazz -> {
String createTableSQL = getCreateTableSQL(clazz, type);
String createTableSQL = getCreateTableSQL(clazz);
pool.query(createTableSQL).execute().onSuccess(
rs -> LOGGER.info("table auto generate:\n" + createTableSQL)
).onFailure(e -> {
LOGGER.error(e.getMessage() + " SQL: \n" + createTableSQL);
});
rs -> LOGGER.info("\n" + createTableSQL + "create table --> ok")
).onFailure(Throwable::printStackTrace);
});
}
}
@@ -1,13 +1,20 @@
package cn.qaiu.db.pool;
import cn.qaiu.db.ddl.CreateTable;
import cn.qaiu.db.server.H2ServerHolder;
import cn.qaiu.vx.core.util.VertxHolder;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
import io.vertx.jdbcclient.JDBCPool;
import org.h2.tools.Server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
/**
* 初始化JDBC
* <br>Create date 2021/8/10 12:04
@@ -21,14 +28,12 @@ public class JDBCPoolInit {
JsonObject dbConfig;
Vertx vertx = VertxHolder.getVertxInstance();
String url;
private final JDBCType type;
private static JDBCPoolInit instance;
public JDBCPoolInit(Builder builder) {
this.dbConfig = builder.dbConfig;
this.url = builder.url;
this.type = builder.type;
}
public static Builder builder() {
@@ -42,12 +47,10 @@ public class JDBCPoolInit {
public static class Builder {
private JsonObject dbConfig;
private String url;
private JDBCType type;
public Builder config(JsonObject dbConfig) {
this.dbConfig = dbConfig;
this.url = dbConfig.getString("jdbcUrl");
this.type = JDBCUtil.getJDBCType(dbConfig.getString("driverClassName"));
return this;
}
@@ -64,17 +67,73 @@ public class JDBCPoolInit {
* init h2db<br>
* 这个方法只允许调用一次
*/
synchronized public void initPool() {
public void initPool() {
if (pool != null) {
LOGGER.error("pool 重复初始化");
return;
}
// 异步启动H2服务
vertx.createSharedWorkerExecutor("h2-server", 1, Long.MAX_VALUE)
.executeBlocking(this::h2serverExecute)
.onSuccess(res->{
LOGGER.info(res);
// 初始化数据库连接
vertx.createSharedWorkerExecutor("sql-pool-init")
.executeBlocking(this::poolInitExecute)
.onSuccess(LOGGER::info)
.onFailure(Throwable::printStackTrace);
})
.onFailure(Throwable::printStackTrace);
}
private void poolInitExecute(Promise<String> promise) {
// 初始化连接池
pool = JDBCPool.pool(vertx, dbConfig);
CreateTable.createTable(pool, type);
LOGGER.info("数据库连接初始化: URL=" + url);
CreateTable.createTable(pool);
promise.complete("init jdbc pool success");
}
private void checkOrCreateDBFile() {
LOGGER.info("init sql start");
String[] path = url.split("\\./");
path[1] = path[1].split(";")[0];
path[1] += ".mv.db";
File file = new File(path[1]);
if (!file.exists()) {
if (!file.getParentFile().exists()) {
if (file.getParentFile().mkdirs()) {
LOGGER.info("mkdirs -> {}", file.getParentFile().getAbsolutePath());
}
}
try {
if (file.createNewFile()) {
LOGGER.info("create file -> {}", file.getAbsolutePath());
}
} catch (IOException e) {
LOGGER.error(e.getMessage());
throw new RuntimeException("file create failed");
}
}
}
private void h2serverExecute(Promise<String> promise) {
// 初始化H2db, 创建本地db文件
checkOrCreateDBFile();
try {
String url = dbConfig.getString("jdbcUrl");
String[] portStr = url.split(":");
String port = portStr[portStr.length - 1].split("[/\\\\]")[0];
LOGGER.info("H2server listen port to {}", port);
H2ServerHolder.init(Server.createTcpServer("-tcp", "-tcpAllowOthers", "-tcpPort", port).start());
promise.complete("Start h2Server success");
} catch (SQLException e) {
throw new RuntimeException("Start h2Server failed: " + e.getMessage());
}
}
/**
@@ -82,7 +141,7 @@ public class JDBCPoolInit {
*
* @return pool
*/
synchronized public JDBCPool getPool() {
public JDBCPool getPool() {
return pool;
}
}
@@ -1,9 +0,0 @@
package cn.qaiu.db.pool;
/**
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2023/10/10 14:06
*/
public enum JDBCType {
MySQL, H2DB
}
@@ -1,18 +0,0 @@
package cn.qaiu.db.pool;
/**
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2023/10/10 14:05
*/
public class JDBCUtil {
public static JDBCType getJDBCType(String deviceName) {
switch (deviceName) {
case "com.mysql.cj.jdbc.Driver":
case "com.mysql.jdbc.Driver":
return JDBCType.MySQL;
case "org.h2.Driver":
return JDBCType.H2DB;
}
throw new RuntimeException("不支持的SQL驱动类型: " + deviceName);
}
}
+19 -15
View File
@@ -5,15 +5,28 @@
<parent>
<artifactId>netdisk-fast-download</artifactId>
<groupId>cn.qaiu</groupId>
<version>${revision}</version>
<version>0.1.7</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<version>1.0.8</version>
<artifactId>core</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-dependencies</artifactId>
<version>${vertx.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--logback日志实现-->
<dependency>
@@ -60,7 +73,11 @@
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
@@ -76,12 +93,6 @@
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@@ -90,13 +101,6 @@
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<!-- 代码生成器 -->
<annotationProcessors>
<annotationProcessor>io.vertx.codegen.CodeGenProcessor</annotationProcessor>
</annotationProcessors>
<generatedSourcesDirectory>
${project.basedir}/src/main/generated
</generatedSourcesDirectory>
</configuration>
</plugin>
</plugins>
+5 -17
View File
@@ -1,13 +1,11 @@
package cn.qaiu.vx.core;
import cn.qaiu.vx.core.util.CommonUtil;
import cn.qaiu.vx.core.util.ConfigUtil;
import cn.qaiu.vx.core.util.VertxHolder;
import cn.qaiu.vx.core.verticle.ReverseProxyVerticle;
import cn.qaiu.vx.core.verticle.RouterVerticle;
import cn.qaiu.vx.core.verticle.ServiceVerticle;
import io.vertx.core.*;
import io.vertx.core.dns.AddressResolverOptions;
import io.vertx.core.impl.launcher.commands.VersionCommand;
import io.vertx.core.json.JsonObject;
import io.vertx.core.shareddata.LocalMap;
@@ -103,7 +101,7 @@ public final class Deploy {
""";
System.out.printf(logoTemplate,
CommonUtil.getAppVersion(),
conf.getString("version_app"),
VersionCommand.getVersion(),
conf.getString("copyright"),
year
@@ -123,12 +121,6 @@ public final class Deploy {
var vertxOptions = vertxConfigELPS == 0 ?
new VertxOptions() : new VertxOptions(vertxConfig);
vertxOptions.setAddressResolverOptions(
new AddressResolverOptions().
addServer("114.114.114.114").
addServer("114.114.115.115").
addServer("8.8.8.8").
addServer("8.8.4.4"));
LOGGER.info("vertxConfigEventLoopPoolSize: {}, eventLoopPoolSize: {}, workerPoolSize: {}", vertxConfigELPS,
vertxOptions.getEventLoopPoolSize(),
vertxOptions.getWorkerPoolSize());
@@ -140,23 +132,19 @@ public final class Deploy {
localMap.put(GLOBAL_CONFIG, globalConfig);
localMap.put(CUSTOM_CONFIG, customConfig);
localMap.put(SERVER, globalConfig.getJsonObject(SERVER));
var future0 = vertx.createSharedWorkerExecutor("other-handle")
.executeBlocking(() -> {
var future0 = vertx.createSharedWorkerExecutor("other-handle").executeBlocking(bch -> {
handle.handle(globalConfig);
return "Other handle complete";
bch.complete("other handle complete");
});
future0.onSuccess(res -> {
LOGGER.info(res);
// 部署 路由、异步service、反向代理 服务
var future1 = vertx.deployVerticle(RouterVerticle.class, getWorkDeploymentOptions("Router"));
var future2 = vertx.deployVerticle(ServiceVerticle.class, getWorkDeploymentOptions("Service"));
var future3 = vertx.deployVerticle(ReverseProxyVerticle.class, getWorkDeploymentOptions("proxy"));
Future.all(future1, future2, future3)
CompositeFuture.all(future1, future2, future3, future0)
.onSuccess(this::deployWorkVerticalSuccess)
.onFailure(this::deployVerticalFailed);
}).onFailure(e -> LOGGER.error("Other handle error", e));
}
/**
@@ -193,7 +181,7 @@ public final class Deploy {
private DeploymentOptions getWorkDeploymentOptions(String name, int ins) {
return new DeploymentOptions()
.setWorkerPoolName(name)
.setThreadingModel(ThreadingModel.WORKER)
.setWorker(true)
.setInstances(ins);
}
@@ -32,15 +32,12 @@ 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 cn.qaiu.vx.core.verticle.ReverseProxyVerticle.REROUTE_PATH_PREFIX;
import static io.vertx.core.http.HttpHeaders.*;
import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME;
/**
* 路由映射, 参数绑定
@@ -73,19 +70,23 @@ public class RouterHandlerFactory implements BaseHttpApi {
public Router createRouter() {
// 主路由
Router mainRouter = Router.router(VertxHolder.getVertxInstance());
mainRouter.route().handler(ctx -> {
String realPath = ctx.request().uri();;
if (realPath.startsWith(REROUTE_PATH_PREFIX)) {
// vertx web proxy暂不支持rewrite, 所以这里进行手动替换, 请求地址中的请求path前缀替换为originPath
String rePath = realPath.substring(REROUTE_PATH_PREFIX.length());
ctx.reroute(rePath);
return;
// 静态资源
String path = SharedDataUtil.getJsonConfig("server")
.getString("staticResourcePath");
if (!StringUtils.isEmpty(path)) {
// 静态资源
mainRouter.route("/*").handler(StaticHandler
.create(path)
.setCachingEnabled(true)
.setDefaultContentEncoding("UTF-8"));
}
mainRouter.route().handler(ctx -> {
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");
@@ -1,7 +0,0 @@
/**
* ModuleGen cn.qaiu.vx.core
*/
@ModuleGen(name = "vertx-http-proxy", groupPackage = "cn.qaiu.vx.core", useFutures = true)
package cn.qaiu.vx.core;
import io.vertx.codegen.annotations.ModuleGen;
@@ -3,6 +3,8 @@ package cn.qaiu.vx.core.util;
import cn.qaiu.vx.core.annotaions.HandleSortFilter;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.JsonObject;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.Converter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -14,7 +16,6 @@ import java.net.URL;
import java.net.UnknownHostException;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.stream.Collectors;
@@ -98,6 +99,23 @@ public class CommonUtil {
return data;
}
/**
* 注册枚举转换器
*
* @param enums 枚举类
*/
@SafeVarargs
@SuppressWarnings({"unchecked", "rawtypes"})
public static void enumConvert(Class<? extends Enum>... enums) {
for (Class<? extends Enum> anEnum : enums) {
ConvertUtils.register(new Converter() {
public Object convert(Class type, Object value) {
return Enum.valueOf(anEnum, (String) value);
}
}, anEnum);
}
}
/**
* 处理其他配置
*
@@ -140,21 +158,4 @@ public class CommonUtil {
}
}).collect(Collectors.toSet());
}
private static String appVersion;
public static String getAppVersion() {
if (null == appVersion) {
Properties properties = new Properties();
try {
properties.load(CommonUtil.class.getClassLoader().getResourceAsStream("app.properties"));
if (!properties.isEmpty()) {
appVersion = properties.getProperty("app.version");
}
} catch (IOException e) {
e.printStackTrace();
}
}
return appVersion;
}
}
@@ -6,9 +6,6 @@ public interface ConfigConstant {
String EVENT_LOOP_POOL_SIZE = "eventLoopPoolSize";
String LOCAL = "local";
String SERVER = "server";
String CACHE = "cache";
String PROXY = "proxy";
String GLOBAL_CONFIG = "globalConfig";
String CUSTOM_CONFIG = "customConfig";
String ASYNC_SERVICE_INSTANCES = "asyncServiceInstances";
@@ -1,46 +0,0 @@
package cn.qaiu.vx.core.util;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import io.vertx.core.json.jackson.DatabindCodec;
import org.slf4j.LoggerFactory;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
/**
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2023/10/14 9:07
*/
public class JacksonConfig {
static {
// 通过该方法对mapper对象进行设置,所有序列化的对象都将按改规则进行系列化
// Include.Include.ALWAYS 默认
// Include.NON_DEFAULT 属性为默认值不序列化
// Include.NON_EMPTY 属性为 空("") 或者为 NULL 都不序列化,则返回的json是没有这个字段的。这样对移动端会更省流量
// Include.NON_NULL 属性为NULL 不序列化,就是为null的字段不参加序列化
// objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
ObjectMapper objectMapper = DatabindCodec.mapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addDeserializer(LocalDateTime.class,
new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
javaTimeModule.addDeserializer(LocalDate.class,
new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
javaTimeModule.addDeserializer(LocalTime.class,
new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
objectMapper.registerModule(javaTimeModule);
LoggerFactory.getLogger(JacksonConfig.class).info("Global JacksonConfig complete.");
}
public static void nothing() {}
}
@@ -1,10 +1,11 @@
package cn.qaiu.vx.core.util;
import io.vertx.core.MultiMap;
import io.vertx.core.json.JsonObject;
import org.apache.commons.beanutils.BeanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
@@ -17,21 +18,29 @@ import java.util.Map;
public final class ParamUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(ParamUtil.class);
public static Map<String, Object> multiMapToMap(MultiMap multiMap) {
public static Map<String, String> multiMapToMap(MultiMap multiMap) {
if (multiMap == null) return null;
Map<String, Object> map = new HashMap<>();
Map<String, String> map = new HashMap<>();
for (Map.Entry<String, String> entry : multiMap.entries()) {
map.put(entry.getKey(), entry.getValue());
}
return map;
}
public static <T> T multiMapToEntity(MultiMap multiMap, Class<T> tClass) {
Map<String, Object> map = multiMapToMap(multiMap);
if (map == null) {
return null;
public static <T> T multiMapToEntity(MultiMap multiMap, Class<T> tClass) throws NoSuchMethodException {
Map<String, String> map = multiMapToMap(multiMap);
T obj = null;
try {
obj = tClass.getDeclaredConstructor().newInstance();
BeanUtils.populate(obj, map);
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
LOGGER.error("实例化异常");
} catch (InvocationTargetException e2) {
e2.printStackTrace();
LOGGER.error("map2bean转换异常");
}
return new JsonObject(map).mapTo(tClass);
return obj;
}
public static MultiMap paramsToMap(String paramString) {
@@ -5,13 +5,12 @@ import javassist.bytecode.AccessFlag;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
import org.apache.commons.beanutils.ConversionException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.reflections.Reflections;
import org.reflections.scanners.MemberUsageScanner;
import org.reflections.scanners.MethodParameterNamesScanner;
import org.reflections.scanners.Scanners;
import org.reflections.scanners.*;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;
@@ -186,10 +185,10 @@ public final class ReflectionUtil {
return DateUtils.parseDate(value, fmt);
} catch (ParseException e) {
e.printStackTrace();
throw new RuntimeException("无法将格式化日期");
throw new ConversionException("无法将格式化日期");
}
default:
throw new RuntimeException("无法将String类型" + value + "转为[" + name + "]");
throw new ConversionException("无法将String类型" + value + "转为[" + name + "]");
}
}
@@ -201,7 +200,7 @@ public final class ReflectionUtil {
* @return Array
*/
public static Object conversionArray(CtClass ctClass, String value) {
if (!isBasicTypeArray(ctClass)) throw new RuntimeException("无法解析数组");
if (!isBasicTypeArray(ctClass)) throw new ConversionException("无法解析数组");
String[] strArr = value.split(",");
List<Object> obj = new ArrayList<>();
Arrays.stream(strArr).forEach(v -> obj.add(conversion(ctClass, v, null)));
@@ -1,184 +0,0 @@
package cn.qaiu.vx.core.verticle;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
import io.vertx.core.dns.AddressResolverOptions;
import io.vertx.core.http.*;
import io.vertx.core.net.NetClient;
import io.vertx.core.net.NetClientOptions;
import io.vertx.core.net.NetSocket;
import io.vertx.core.net.ProxyOptions;
import java.util.Base64;
/**
*
*/
public class HttpProxyVerticle extends AbstractVerticle {
private HttpClient httpClient;
private NetClient netClient;
@Override
public void start() {
ProxyOptions proxyOptions = new ProxyOptions().setHost("127.0.0.1").setPort(7890);
// 初始化 HTTP 客户端,用于向目标服务器发送 HTTP 请求
HttpClientOptions httpClientOptions = new HttpClientOptions();
httpClient = vertx.createHttpClient(httpClientOptions.setProxyOptions(proxyOptions));
// 创建并启动 HTTP 代理服务器,监听指定端口
HttpServer server = vertx.createHttpServer(new HttpServerOptions().setClientAuth(ClientAuth.REQUIRED));
server.requestHandler(this::handleClientRequest);
// 初始化 NetClient,用于在 CONNECT 请求中建立 TCP 连接隧道
netClient = vertx.createNetClient(new NetClientOptions()
.setProxyOptions(proxyOptions)
.setConnectTimeout(15000)
.setTrustAll(true));
// 启动 HTTP 代理服务器
server.listen(7891, ar -> {
if (ar.succeeded()) {
System.out.println("HTTP Proxy server started on port 7891");
} else {
System.err.println("Failed to start HTTP Proxy server: " + ar.cause());
}
});
}
// 处理 HTTP CONNECT 请求,用于代理 HTTPS 流量
private void handleConnectRequest(HttpServerRequest clientRequest) {
String[] uriParts = clientRequest.uri().split(":");
if (uriParts.length != 2) {
clientRequest.response().setStatusCode(400).end("Bad Request: Invalid URI format");
return;
}
// 解析目标主机和端口
String targetHost = uriParts[0];
int targetPort;
try {
targetPort = Integer.parseInt(uriParts[1]);
} catch (NumberFormatException e) {
clientRequest.response().setStatusCode(400).end("Bad Request: Invalid port");
return;
}
clientRequest.pause();
// 通过 NetClient 连接目标服务器并创建隧道
netClient.connect(targetPort, targetHost, connectionAttempt -> {
if (connectionAttempt.succeeded()) {
NetSocket targetSocket = connectionAttempt.result();
// 升级客户端连接到 NetSocket 并实现双向数据流
clientRequest.toNetSocket().onComplete(clientSocketAttempt -> {
if (clientSocketAttempt.succeeded()) {
NetSocket clientSocket = clientSocketAttempt.result();
// 设置双向数据流转发
clientSocket.handler(targetSocket::write);
targetSocket.handler(clientSocket::write);
// 关闭其中一方时关闭另一方
clientSocket.closeHandler(v -> targetSocket.close());
targetSocket.closeHandler(v -> clientSocket.close());
} else {
System.err.println("Failed to upgrade client connection to socket: " + clientSocketAttempt.cause().getMessage());
targetSocket.close();
clientRequest.response().setStatusCode(500).end("Internal Server Error");
}
});
} else {
System.err.println("Failed to connect to target: " + connectionAttempt.cause().getMessage());
clientRequest.response().setStatusCode(502).end("Bad Gateway: Unable to connect to target");
}
});
}
// 处理客户端的 HTTP 请求
private void handleClientRequest(HttpServerRequest clientRequest) {
String s = clientRequest.headers().get("Proxy-Authorization");
if (s == null) {
clientRequest.response().setStatusCode(403).end();
return;
}
String[] split = new String(Base64.getDecoder().decode(s.replace("Basic ", ""))).split(":");
if (split.length > 1) {
System.out.println(split[0]);
System.out.println(split[1]);
// TODO
}
if (clientRequest.method() == HttpMethod.CONNECT) {
// 处理 CONNECT 请求
handleConnectRequest(clientRequest);
} else {
// 处理普通的 HTTP 请求
handleHttpRequest(clientRequest);
}
}
// 处理 HTTP 请求,转发至目标服务器并返回响应
private void handleHttpRequest(HttpServerRequest clientRequest) {
// 获取目标主机
String hostHeader = clientRequest.getHeader("Host");
if (hostHeader == null) {
clientRequest.response().setStatusCode(400).end("Host header is missing");
return;
}
String targetHost = hostHeader.split(":")[0];
int targetPort = 80; // 默认为 HTTP 的端口
clientRequest.pause(); // 暂停客户端请求的读取,避免数据丢失
httpClient.request(clientRequest.method(), targetPort, targetHost, clientRequest.uri())
.onSuccess(request -> {
clientRequest.resume(); // 恢复客户端请求的读取
// 逐个设置请求头
clientRequest.headers().forEach(header -> request.putHeader(header.getKey(), header.getValue()));
// 将客户端请求的 body 转发给目标服务器
clientRequest.bodyHandler(body -> request.send(body, ar -> {
if (ar.succeeded()) {
var response = ar.result();
clientRequest.response().setStatusCode(response.statusCode());
clientRequest.response().headers().setAll(response.headers());
response.body().onSuccess(b-> clientRequest.response().end(b));
} else {
clientRequest.response().setStatusCode(502).end("Bad Gateway: Unable to reach target");
}
}));
})
.onFailure(err -> {
err.printStackTrace();
clientRequest.response().setStatusCode(502).end("Bad Gateway: Request failed");
});
}
@Override
public void stop() {
// 停止 HTTP 客户端以释放资源
if (httpClient != null) {
httpClient.close();
}
}
/**
* TODO add Deploy
* @param args
*/
public static void main(String[] args) {
// 配置 DNS 解析器,使用多个 DNS 服务器来提升解析速度
Vertx vertx = Vertx.vertx(new VertxOptions()
.setAddressResolverOptions(new AddressResolverOptions()
.addServer("114.114.114.114")
.addServer("114.114.115.115")
.addServer("8.8.8.8")
.addServer("8.8.4.4")));
// 部署 Verticle 并启动动态 HTTP 代理服务器
vertx.deployVerticle(new HttpProxyVerticle());
}
}
@@ -6,13 +6,13 @@ import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.http.WebSocket;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.core.net.PemKeyCertOptions;
import io.vertx.ext.web.Route;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.StaticHandler;
import io.vertx.ext.web.handler.sockjs.SockJSHandler;
import io.vertx.ext.web.handler.sockjs.SockJSHandlerOptions;
import io.vertx.ext.web.proxy.handler.ProxyHandler;
import io.vertx.httpproxy.HttpProxy;
import org.apache.commons.lang3.StringUtils;
@@ -40,17 +40,14 @@ public class ReverseProxyVerticle extends AbstractVerticle {
.getJsonConfig(ConfigConstant.GLOBAL_CONFIG)
.getString("proxyConf");
private static final Future<JsonObject> CONFIG = ConfigUtil.readYamlConfig(PATH_PROXY_CONFIG);
private static final String DEFAULT_PATH_404 = "webroot/err/page404.html";
private static final String DEFAULT_PATH_404 = "webroot/err/404.html";
private static String serverName = "Vert.x-proxy-server"; //Server name in Http response header
public static String REROUTE_PATH_PREFIX = "/__rrvpspp"; //re_route_vert_proxy_server_path_prefix 硬编码
@Override
public void start(Promise<Void> startPromise) {
public void start(Promise<Void> startPromise) throws Exception {
CONFIG.onSuccess(this::handleProxyConfList);
// createFileListener
startPromise.complete();
}
@@ -77,24 +74,22 @@ public class ReverseProxyVerticle extends AbstractVerticle {
* @param proxyConf 代理配置
*/
private void handleProxyConf(JsonObject proxyConf) {
// page404 path
if (proxyConf.containsKey(
"page404")) {
// 404 path
if (proxyConf.containsKey("404")) {
System.getProperty("user.dir");
String path = proxyConf.getString("page404");
String path = proxyConf.getString("404");
if (StringUtils.isEmpty(path)) {
proxyConf.put("page404", DEFAULT_PATH_404);
proxyConf.put("404", DEFAULT_PATH_404);
} else {
if (!path.startsWith("/")) {
path = "/" + path;
}
if (!new File(System.getProperty("user.dir") + path).exists()) {
proxyConf.put("page404", DEFAULT_PATH_404);
proxyConf.put("404", DEFAULT_PATH_404);
}
}
} else {
proxyConf.put("page404", DEFAULT_PATH_404);
proxyConf.put("404", DEFAULT_PATH_404);
}
final HttpClient httpClient = VertxHolder.getVertxInstance().createHttpClient();
@@ -116,10 +111,17 @@ public class ReverseProxyVerticle extends AbstractVerticle {
handleStatic(proxyConf.getJsonObject("static"), proxyRouter);
}
// Send page404 page
proxyRouter.errorHandler(404, ctx -> ctx.response().sendFile(proxyConf.getString("page404")));
// static server
if (proxyConf.containsKey("sock")) {
handleSock(proxyConf.getJsonArray("sock"), httpClient, proxyRouter);
}
HttpServer server = getHttpsServer(proxyConf);
// Send 404 page
proxyRouter.errorHandler(404, ctx -> {
ctx.response().sendFile(proxyConf.getString("404"));
});
HttpServer server = vertx.createHttpServer();
server.requestHandler(proxyRouter);
Integer port = proxyConf.getInteger("listen");
@@ -127,38 +129,6 @@ public class ReverseProxyVerticle extends AbstractVerticle {
server.listen(port);
}
private HttpServer getHttpsServer(JsonObject proxyConf) {
HttpServerOptions httpServerOptions = new HttpServerOptions();
if (proxyConf.containsKey("ssl")) {
JsonObject sslConfig = proxyConf.getJsonObject("ssl");
URL sslUrl = this.getClass().getClassLoader().getResource("");
if (sslUrl == null) {
throw new RuntimeException("SSL url not exist...");
}
if (sslConfig.containsKey("enable") && sslConfig.getBoolean("enable")) {
String sslCertificatePath = sslUrl.getPath() + sslConfig.getString("ssl_certificate");
String sslCertificateKeyPath = sslUrl.getPath() + sslConfig.getString("ssl_certificate_key");
LOGGER.info("enable ssl config. ");
httpServerOptions
.setSsl(true)
.setKeyCertOptions(
new PemKeyCertOptions()
.setKeyPath(sslCertificateKeyPath)
.setCertPath(sslCertificatePath)
).addEnabledSecureTransportProtocol(sslConfig.getString("ssl_protocols"));
String sslCiphers = sslConfig.getString("ssl_ciphers");
if (sslCiphers != null && !sslCiphers.isEmpty()) {
for (String s : sslCiphers.split(":")) {
httpServerOptions.addEnabledCipherSuite(s);
}
}
}
}
return vertx.createHttpServer(httpServerOptions);
}
/**
* 处理静态资源配置
*
@@ -175,12 +145,9 @@ public class ReverseProxyVerticle extends AbstractVerticle {
ctx.next();
});
StaticHandler staticHandler;
final StaticHandler staticHandler = StaticHandler.create();
if (staticConf.containsKey("root")) {
staticHandler = StaticHandler.create(staticConf.getString("root"));
} else {
staticHandler = StaticHandler.create();
staticHandler.setWebRoot(staticConf.getString("root"));
}
if (staticConf.containsKey("directory-listing")) {
staticHandler.setDirectoryListing(staticConf.getBoolean("directory-listing"));
@@ -211,7 +178,7 @@ public class ReverseProxyVerticle extends AbstractVerticle {
port = 80;
}
String originPath = url.getPath();
LOGGER.info("path {}, originPath {}, to {}:{}", path, originPath, host, port);
LOGGER.debug("Conf(path, originPath, host, port) ----> {},{},{},{}", path, originPath, host, port);
// 注意这里不能origin多个代理地址, 一个实例只能代理一个origin
final HttpProxy httpProxy = HttpProxy.reverseProxy(httpClient);
@@ -222,21 +189,14 @@ public class ReverseProxyVerticle extends AbstractVerticle {
// 代理目标路径为空 就像nginx一样路径穿透 (相对路径)
if (StringUtils.isEmpty(originPath) || path.equals(originPath)) {
Route route = path.startsWith("~") ? proxyRouter.routeWithRegex(path.substring(1))
: proxyRouter.route(path);
route.handler(ProxyHandler.create(httpProxy));
proxyRouter.route(path + "*").handler(ProxyHandler.create(httpProxy));
} else {
// 配置 /api/, / => 请求 /api/test 代理后 /test
// 配置 /api/, /xxx => 请求 /api/test 代理后 /xxx/test
final String path0 = path;
final String originPath0 = REROUTE_PATH_PREFIX + originPath;
proxyRouter.route(originPath0 + "*").handler(ProxyHandler.create(httpProxy));
proxyRouter.route(path0 + "*").handler(ctx -> {
String realPath = ctx.request().uri();
if (realPath.startsWith(path0)) {
proxyRouter.route(originPath + "*").handler(ProxyHandler.create(httpProxy));
proxyRouter.route(path + "*").handler(ctx -> {
String realPath = ctx.request().path();
if (realPath.startsWith(path)) {
// vertx web proxy暂不支持rewrite, 所以这里进行手动替换, 请求地址中的请求path前缀替换为originPath
String rePath = realPath.replaceAll("^" + path0, originPath0);
String rePath = realPath.replaceAll("^" + path, originPath);
ctx.reroute(rePath);
} else {
ctx.next();
@@ -250,4 +210,54 @@ public class ReverseProxyVerticle extends AbstractVerticle {
});
}
/**
* 处理websocket
*
* @param confList sock配置
* @param httpClient 客户端
* @param proxyRouter 代理路由
*/
private void handleSock(JsonArray confList, HttpClient httpClient, Router proxyRouter) {
// 代理规则
confList.stream().map(e -> (JsonObject) e).forEach(conf -> {
String origin = conf.getString("origin");
String path = conf.getString("path");
LOGGER.info("websocket proxy: {}, {}",origin,path);
SockJSHandlerOptions options = new SockJSHandlerOptions()
.setHeartbeatInterval(2000)
.setRegisterWriteHandler(true);
SockJSHandler sockJSHandler = SockJSHandler.create(VertxHolder.getVertxInstance(), options);
if (!path.startsWith("/")) {
path = "/" + path;
}
Router route = sockJSHandler.socketHandler(sock -> {
sock.handler(buffer -> {
Future<WebSocket> webSocketFuture = httpClient.webSocket(8086,"127.0.0.1",sock.uri());
webSocketFuture.onSuccess(s -> {
System.out.println(buffer.toString());
s.write(buffer).onSuccess(v -> {
s.handler(w->{
System.out.println("--------"+w.toString());
});
});
});
});
sock.endHandler(v -> {
});
sock.closeHandler(v -> {
});
});
proxyRouter.mountSubRouter("/real/serverApi/test", route);
});
}
}
@@ -2,7 +2,6 @@ package cn.qaiu.vx.core.verticle;
import cn.qaiu.vx.core.handlerfactory.RouterHandlerFactory;
import cn.qaiu.vx.core.util.CommonUtil;
import cn.qaiu.vx.core.util.JacksonConfig;
import cn.qaiu.vx.core.util.SharedDataUtil;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
@@ -31,8 +30,6 @@ public class RouterVerticle extends AbstractVerticle {
private HttpServer server;
static {
LOGGER.info(JacksonConfig.class.getSimpleName() + " >> ");
JacksonConfig.nothing();
LOGGER.info("To start listening to port {} ......", port);
}
@@ -1,89 +0,0 @@
package cn.qaiu.vx.core.verticle.conf;
import io.vertx.codegen.annotations.DataObject;
import io.vertx.codegen.json.annotations.JsonGen;
import io.vertx.core.json.JsonObject;
import io.vertx.core.net.ProxyOptions;
import java.util.UUID;
@DataObject
@JsonGen(publicConverter = false)
public class HttpProxyConf {
public static final String DEFAULT_USERNAME = UUID.randomUUID().toString();
public static final String DEFAULT_PASSWORD = UUID.randomUUID().toString();
public static final Integer DEFAULT_PORT = 6402;
public static final Integer DEFAULT_TIMEOUT = 15000;
Integer timeout;
String username;
String password;
Integer port;
ProxyOptions preProxyOptions;
public HttpProxyConf() {
this.username = DEFAULT_USERNAME;
this.password = DEFAULT_PASSWORD;
this.timeout = DEFAULT_PORT;
this.timeout = DEFAULT_TIMEOUT;
this.preProxyOptions = new ProxyOptions();
}
public HttpProxyConf(JsonObject json) {
this();
}
public Integer getTimeout() {
return timeout;
}
public HttpProxyConf setTimeout(Integer timeout) {
this.timeout = timeout;
return this;
}
public String getUsername() {
return username;
}
public HttpProxyConf setUsername(String username) {
this.username = username;
return this;
}
public String getPassword() {
return password;
}
public HttpProxyConf setPassword(String password) {
this.password = password;
return this;
}
public Integer getPort() {
return port;
}
public HttpProxyConf setPort(Integer port) {
this.port = port;
return this;
}
public ProxyOptions getPreProxyOptions() {
return preProxyOptions;
}
public HttpProxyConf setPreProxyOptions(ProxyOptions preProxyOptions) {
this.preProxyOptions = preProxyOptions;
return this;
}
}
-1
View File
@@ -1 +0,0 @@
app.version=${project.version}
Vendored
-310
View File
@@ -1,310 +0,0 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`which java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
Vendored
-182
View File
@@ -1,182 +0,0 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
exit /B %ERROR_CODE%
-66
View File
@@ -1,66 +0,0 @@
直链缓存设计
每个网盘对应的标准分享URL如下
蓝奏云 (lz) https://lanzoux.com/{shareKey}
蓝奏云优享 (iz) https://www.ilanzou.com/s/{shareKey}
奶牛快传 (cow) https://cowtransfer.com/s/{shareKey}
移动云云空间 (ec) https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data={shareKey}&isShare=1
小飞机网盘 (fj) https://share.feijipan.com/s/{shareKey}
亿方云 (fc) https://v2.fangcloud.com/sharing/{shareKey}
123云盘 (ye) https://www.123pan.com/s/{shareKey}.html
文叔叔 (ws) https://f.ws59.cn/f/{shareKey}
联想乐云 (le) https://lecloud.lenovo.com/share/{shareKey}
私有化网盘需要自己的域名也就是origin地址.
Cloudreve自建网盘 (ce) {origin}/s/{shareKey}
分享URL -> 类型+key
类型+key -> 标准分享URL
缓存key -> 下载URL
分享链接 -> add 网盘类型 pwd origin(私有化) -> 直链
https://f.ws59.cn/f/e3peohu6192
开源版 TODO
1. 缓存优化, 配置自动重载
专属版 功能设计
1. 支持绑定域名, 后台管理-账号管理, token管理, 账号解析次数限制
2. 流量统计, 文件分享信息, 目录解析, 文件云下载
3. IP代理池
网页跳转 防盗链
可禁用parser接口
标志短链 鉴权后 生成混淆链接
短链算法:
1. 基于Hash映射 hash(type:key:pwd) = h/xxxxx
鉴权实现:
auth-jdbc
// 基于标准SQL语法
支持H2, MySQL
用户:
jwt鉴权用户
角色:
超级管理员
注册用户
定义操作(权限):
用户的创建/删除/查询/修改, 生成短链/删除短链/修改解析次数和有效期/查询短链信息(
文件信息: 文件/文件夹, 文件数量, 文件大小, 文件类型; 链接信息: 解析次数, 缓存次数等)
微服务设计:
+4 -80
View File
@@ -2,18 +2,14 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>netdisk-fast-download</artifactId>
<groupId>cn.qaiu</groupId>
<version>${revision}</version>
<version>0.1.7</version>
</parent>
<artifactId>parser</artifactId>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<name>${project.groupId}:${project.artifactId}</name>
<description>NFD parser</description>
<url>https://qaiu.top</url>
<artifactId>parser</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -51,39 +47,10 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<licenses>
<license>
<name>MIT License</name>
<url>https://opensource.org/license/mit</url>
</license>
</licenses>
<developers>
<developer>
<name>qaiu</name>
<email>[email protected]</email>
<organization>https://qaiu.top</organization>
</developer>
</developers>
<scm>
<connection>scm:[email protected]:qaiu/netdisk-fast-download.git</connection>
<developerConnection>scm:[email protected]:qaiu/netdisk-fast-download.git</developerConnection>
<url>[email protected]:qaiu/netdisk-fast-download.git</url>
</scm>
<distributionManagement>
<snapshotRepository>
<id>sonatype</id>
<url>https://s01.oss.sonatype.org/content/repositories/snapshots/</url>
</snapshotRepository>
<repository>
<id>sonatype</id>
<url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>
<build>
<plugins>
@@ -96,49 +63,6 @@
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<attach>true</attach>
</configuration>
<executions>
<execution>
<id>attach-sources</id>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<!-- Gpg Signature -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<version>0.6.0</version>
<extensions>true</extensions>
<configuration>
<publishingServerId>central</publishingServerId>
<autoPublish>true</autoPublish>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -1,146 +0,0 @@
package cn.qaiu.entity;
import java.util.Map;
public class FileInfo {
/**
* 文件名
*/
String fileName;
/**
* 文件ID
*/
String fileId;
/**
* 文件大小(byte)
*/
Long size;
/**
* MIME类型
*/
String fileMIME;
/**
* 文件路径
*/
String filePath;
/**
* 创建(上传)时间 yyyy-MM-dd HH:mm:ss格式
*/
String createTime;
/**
* 创建者
*/
String createBy;
/**
* 文件描述
*/
String description;
/**
* 下载次数
*/
Integer downloadCount;
/**
* 扩展参数
*/
Map<String, Object> extParameters;
public String getFileName() {
return fileName;
}
public FileInfo setFileName(String fileName) {
this.fileName = fileName;
return this;
}
public String getFileId() {
return fileId;
}
public FileInfo setFileId(String fileId) {
this.fileId = fileId;
return this;
}
public Long getSize() {
return size;
}
public FileInfo setSize(Long size) {
this.size = size;
return this;
}
public String getFileMIME() {
return fileMIME;
}
public FileInfo setFileMIME(String fileMIME) {
this.fileMIME = fileMIME;
return this;
}
public String getFilePath() {
return filePath;
}
public FileInfo setFilePath(String filePath) {
this.filePath = filePath;
return this;
}
public String getCreateTime() {
return createTime;
}
public FileInfo setCreateTime(String createTime) {
this.createTime = createTime;
return this;
}
public String getCreateBy() {
return createBy;
}
public FileInfo setCreateBy(String createBy) {
this.createBy = createBy;
return this;
}
public String getDescription() {
return description;
}
public FileInfo setDescription(String description) {
this.description = description;
return this;
}
public Integer getDownloadCount() {
return downloadCount;
}
public FileInfo setDownloadCount(Integer downloadCount) {
this.downloadCount = downloadCount;
return this;
}
public Map<String, Object> getExtParameters() {
return extParameters;
}
public FileInfo setExtParameters(Map<String, Object> extParameters) {
this.extParameters = extParameters;
return this;
}
}
@@ -1,159 +0,0 @@
package cn.qaiu.entity;
import java.util.HashMap;
import java.util.Map;
public class ShareLinkInfo {
private String shareKey; // 分享键
private String panName; // 网盘名称
private String type; // 分享类型
private String sharePassword; // 分享密码(如果存在)
private String shareUrl; // 原始分享链接
private String standardUrl; // 规范化的标准链接
private Map<String, Object> otherParam; // 其他参数
private ShareLinkInfo(Builder builder) {
this.shareKey = builder.shareKey;
this.panName = builder.panName;
this.type = builder.type;
this.sharePassword = builder.sharePassword;
this.shareUrl = builder.shareUrl;
this.standardUrl = builder.standardUrl;
this.otherParam = builder.otherParam;
}
// Getter和Setter方法
public String getShareKey() {
return shareKey;
}
public String getPanName() {
return panName;
}
public void setShareKey(String shareKey) {
this.shareKey = shareKey;
}
public void setPanName(String panName) {
this.panName = panName;
}
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 String getCacheKey() {
// 将type和shareKey组合成一个字符串作为缓存key
return type + ":" + shareKey;
}
public ShareLinkInfo setOtherParam(Map<String, Object> otherParam) {
this.otherParam = otherParam;
return this;
}
public Map<String, Object> getOtherParam() {
return otherParam;
}
// 静态方法创建建造者对象
public static ShareLinkInfo.Builder newBuilder() {
return new ShareLinkInfo.Builder();
}
// 建造者类
public static class Builder {
public String panName; // 分享网盘名称
private String shareKey; // 分享键
private String type; // 分享类型 (网盘模板枚举的小写)
private String sharePassword = ""; // 分享密码(如果存在)
private String shareUrl; // 原始分享链接
private String standardUrl; // 规范化的标准链接
private Map<String, Object> otherParam = new HashMap<>(); // 其他参数
public Builder shareKey(String shareKey) {
this.shareKey = shareKey;
return this;
}
public Builder panName(String panName) {
this.panName = panName;
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 Builder otherParam(Map<String, Object> otherParam) {
this.otherParam = otherParam;
return this;
}
public ShareLinkInfo build() {
return new ShareLinkInfo(this);
}
}
@Override
public String toString() {
return "ShareLinkInfo{" +
"shareKey='" + shareKey + '\'' +
", panName='" + panName + '\'' +
", type='" + type + '\'' +
", sharePassword='" + sharePassword + '\'' +
", shareUrl='" + shareUrl + '\'' +
", standardUrl='" + standardUrl + '\'' +
", otherParam='" + otherParam + '\'' +
'}';
}
}
@@ -1,11 +1,62 @@
package cn.qaiu.parser;//package cn.qaiu.lz.common.parser;
import cn.qaiu.parser.impl.*;
import io.vertx.core.Future;
public interface IPanTool {
Future<String> parse();
default String parseSync() {
return parse().toCompletionStage().toCompletableFuture().join();
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("未知分享类型");
}
};
}
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);
}
throw new UnsupportedOperationException("未知分享类型");
}
}
+26 -110
View File
@@ -1,36 +1,20 @@
package cn.qaiu.parser;
import cn.qaiu.WebClientVertxInit;
import cn.qaiu.entity.ShareLinkInfo;
import io.vertx.core.Future;
import cn.qaiu.util.CommonUtils;
import io.vertx.core.Handler;
import io.vertx.core.Promise;
import io.vertx.core.json.DecodeException;
import io.vertx.core.json.Json;
import io.vertx.core.json.JsonObject;
import io.vertx.core.net.ProxyOptions;
import io.vertx.core.net.ProxyType;
import io.vertx.core.net.impl.VertxHandler;
import io.vertx.ext.web.client.HttpResponse;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.client.WebClientOptions;
import io.vertx.ext.web.client.WebClientSession;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
/**
* 解析器抽象类包含promise, HTTP Client, 默认失败方法等;
* 新增网盘解析器需要继承该类. <br>
* <h2>实现类命名规则: </h2>
* <p>{网盘标识}Tool, 网盘标识不超过5个字符, 可以取网盘名称首字母缩写或拼音首字母, <br>
* 音乐类型的解析以M开头, 例如网易云音乐Mne</p>
* 新增网盘解析器需要继承该类.
*/
public abstract class PanBase implements IPanTool {
public abstract class PanBase {
protected Logger log = LoggerFactory.getLogger(this.getClass());
protected Promise<String> promise = Promise.promise();
@@ -38,8 +22,7 @@ public abstract class PanBase implements IPanTool {
/**
* Http client
*/
protected WebClient client = WebClient.create(WebClientVertxInit.get(),
new WebClientOptions().setUserAgentEnabled(false));
protected WebClient client = WebClient.create(WebClientVertxInit.get());
/**
* Http client session (会话管理, 带cookie请求)
@@ -52,7 +35,16 @@ public abstract class PanBase implements IPanTool {
protected WebClient clientNoRedirects = WebClient.create(WebClientVertxInit.get(),
new WebClientOptions().setFollowRedirects(false));
protected ShareLinkInfo shareLinkInfo;
/**
* 分享key 可以是整个URL; 如果是URL实现该类时要
* 使用{@link CommonUtils#adaptShortPaths(String urlPrefix, String key)}获取真实的分享key
*/
protected String key;
/**
* 分享密码
*/
protected String pwd;
/**
* 子类重写此构造方法不需要添加额外逻辑
@@ -63,38 +55,13 @@ public abstract class PanBase implements IPanTool {
* }
* </pre></blockquote>
*
* @param shareLinkInfo 分享链接信息
* @param key 分享key/url
* @param pwd 分享密码
*/
public PanBase(ShareLinkInfo shareLinkInfo) {
this.shareLinkInfo = shareLinkInfo;
if (shareLinkInfo.getOtherParam().containsKey("proxy")) {
JsonObject proxy = (JsonObject) shareLinkInfo.getOtherParam().get("proxy");
ProxyOptions proxyOptions = new ProxyOptions()
.setType(ProxyType.valueOf(proxy.getString("type").toUpperCase()))
.setHost(proxy.getString("host"))
.setPort(proxy.getInteger("port"));
if (StringUtils.isNotEmpty(proxy.getString("username"))) {
proxyOptions.setUsername(proxy.getString("username"));
protected PanBase(String key, String pwd) {
this.key = key;
this.pwd = pwd;
}
if (StringUtils.isNotEmpty(proxy.getString("password"))) {
proxyOptions.setUsername(proxy.getString("password"));
}
this.client = WebClient.create(WebClientVertxInit.get(),
new WebClientOptions()
.setUserAgentEnabled(false)
.setProxyOptions(proxyOptions));
this.clientSession = WebClientSession.create(client);
this.clientNoRedirects = WebClient.create(WebClientVertxInit.get(),
new WebClientOptions().setFollowRedirects(false)
.setUserAgentEnabled(false)
.setProxyOptions(proxyOptions));
}
}
protected PanBase() {
}
/**
* 失败时生成异常消息
@@ -107,11 +74,11 @@ public abstract class PanBase implements IPanTool {
try {
String s = String.format(errorMsg.replaceAll("\\{}", "%s"), args);
log.error("解析异常: " + s, t.fillInStackTrace());
promise.fail(shareLinkInfo.getPanName() + "-" + shareLinkInfo.getType() + ": 解析异常: " + s + " -> " + t);
promise.fail(this.getClass().getSimpleName() + ": 解析异常: " + s + " -> " + t);
} catch (Exception e) {
log.error("ErrorMsg format fail. The parameter has been discarded", e);
log.error("解析异常: " + errorMsg, t.fillInStackTrace());
promise.fail(shareLinkInfo.getPanName() + "-" + shareLinkInfo.getType() + ": 解析异常: " + errorMsg + " -> " + t);
promise.fail(this.getClass().getSimpleName() + ": 解析异常: " + errorMsg + " -> " + t);
}
}
@@ -124,17 +91,15 @@ public abstract class PanBase implements IPanTool {
protected void fail(String errorMsg, Object... args) {
try {
String s = String.format(errorMsg.replaceAll("\\{}", "%s"), args);
promise.fail(shareLinkInfo.getPanName() + "-" + shareLinkInfo.getType() + " - 解析异常: " + s);
log.error("解析异常: " + s);
promise.fail(this.getClass().getSimpleName() + " - 解析异常: " + s);
} catch (Exception e) {
log.error("ErrorMsg format fail. The parameter has been discarded", e);
promise.fail(shareLinkInfo.getPanName() + "-" + shareLinkInfo.getType() + " - 解析异常: " + errorMsg);
log.error("解析异常: " + errorMsg);
promise.fail(this.getClass().getSimpleName() + " - 解析异常: " + errorMsg);
}
}
protected void fail() {
fail("");
}
/**
* 生成失败Future的处理器
*
@@ -142,56 +107,7 @@ public abstract class PanBase implements IPanTool {
* @return Handler
*/
protected Handler<Throwable> handleFail(String errorMsg) {
return t -> fail(shareLinkInfo.getPanName() + "-" + shareLinkInfo.getType() + " - 请求异常 {}: -> {}", errorMsg, t.fillInStackTrace());
}
protected Handler<Throwable> handleFail() {
return handleFail("");
}
/**
* bodyAsJsonObject的封装, 会自动处理异常
* @param res HttpResponse
* @return JsonObject
*/
protected JsonObject asJson(HttpResponse<?> res) {
try {
return res.bodyAsJsonObject();
} catch (DecodeException e) {
fail("解析失败: json格式异常: {}", res.bodyAsString());
throw new RuntimeException("解析失败: json格式异常");
}
}
protected void complete(String url) {
promise.complete(url);
}
protected Future<String> future() {
return promise.future();
}
/**
* 调用下一个解析器, 通用域名解析
*/
protected void nextParser() {
Iterator<PanDomainTemplate> iterator = Arrays.asList(PanDomainTemplate.values()).iterator();
while (iterator.hasNext()) {
if (iterator.next().name().equalsIgnoreCase(shareLinkInfo.getType())) {
if (iterator.hasNext()) {
PanDomainTemplate next = iterator.next();
log.debug("规则不匹配, 处理解析器转发: {} -> {}", shareLinkInfo.getPanName(), next.getDisplayName());
ParserCreate.fromType(next.name())
.fromAnyShareUrl(shareLinkInfo.getShareUrl())
.createTool()
.parse()
.onComplete(promise);
} else {
fail("error: 没有下一个解析处理器");
}
}
}
return t -> fail(this.getClass().getSimpleName() + " - 请求异常 {}: -> {}", errorMsg, t.fillInStackTrace());
}
}
@@ -1,262 +0,0 @@
package cn.qaiu.parser;
import cn.qaiu.parser.impl.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.regex.Pattern;
import static java.util.regex.Pattern.compile;
/**
* 枚举类 PanDomainTemplate 定义了不同网盘服务的模板信息,包括:
* <ul>
* <li>displayName: 网盘服务的显示名称,用于用户界面展示。</li>
* <li>regexPattern: 用于匹配和解析分享链接的正则表达式。</li>
* <li>standardUrlTemplate: 网盘服务的标准URL模板,用于规范化分享链接。</li>
* <li>toolClass: 网盘解析工具实现类。</li>
* </ul>
* 请注意:增添网盘时,保证正则表达式最后一个捕捉组能匹配到分享key
* @author <a href="https://qaiu.top">QAIU</a>
* at 2023/6/13 4:26
*/
public enum PanDomainTemplate {
// 网盘定义
LZ("蓝奏云",
compile("https://(?:[a-zA-Z\\d-]+\\.)?lanzou[a-z]\\.com/(.+/)?(?<KEY>.+)"),
"https://lanzoux.com/{shareKey}",
LzTool.class),
// https://www.feijix.com/s/
// https://share.feijipan.com/s/
FJ("小飞机网盘",
compile("https://(share\\.feijipan\\.com|www\\.feijix\\.com)/s/(?<KEY>.+)"),
"https://www.feijix.com/s/{shareKey}",
FjTool.class),
// https://lecloud.lenovo.com/share/
LE("联想乐云",
compile("https://lecloud?\\.lenovo\\.com/share/(?<KEY>.+)"),
"https://lecloud.lenovo.com/share/{shareKey}",
LeTool.class),
// https://v2.fangcloud.com/s/
FC("亿方云",
compile("https://v2\\.fangcloud\\.(com|cn)/(s|sharing)/(?<KEY>.+)"),
"https://v2.fangcloud.com/s/{shareKey}",
"https://www.fangcloud.com/",
FcTool.class),
// https://www.ilanzou.com/s/
IZ("蓝奏云优享",
compile("https://www\\.ilanzou\\.com/s/(?<KEY>.+)"),
"https://www.ilanzou.com/s/{shareKey}",
IzTool.class),
// https://wx.mail.qq.com/ftn/download?
QQ("QQ邮箱中转站",
compile("https://i?wx\\.mail\\.qq\\.com/ftn/download\\?(?<KEY>.+)"),
"https://iwx.mail.qq.com/ftn/download/{shareKey}",
"https://mail.qq.com",
QQTool.class),
// https://f.ws59.cn/f/或者https://www.wenshushu.cn/f/
WS("文叔叔",
compile("https://(f\\.ws(\\d{2})\\.cn|www\\.wenshushu\\.cn)/f/(?<KEY>.+)"),
"https://www.wenshushu.cn/f/{shareKey}",
WsTool.class),
// https://www.123pan.com/s/
YE("123网盘",
compile("https://www\\.(123pan|123865|123684)\\.com/s/(?<KEY>.+)(.html)?"),
"https://www.123pan.com/s/{shareKey}",
YeTool.class),
// https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data={code}&isShare=1
EC("移动云空间",
compile("https://www\\.ecpan\\.cn/web(/%23|/#)?/yunpanProxy\\?path=.*&data=" +
"(?<KEY>[^&]+)&isShare=1"),
"https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data={shareKey}&isShare=1",
EcTool.class),
// https://cowtransfer.com/s/
COW("奶牛快传",
compile("https://(.*)cowtransfer\\.com/s/(?<KEY>.+)"),
"https://cowtransfer.com/s/{shareKey}",
CowTool.class),
CT("城通网盘",
compile("https://(?:[a-zA-Z\\d-]+\\.)?(ctfile|545c|u062|ghpym|474b)\\.com/file/(?<KEY>.+)"),
"https://474b.com/file/{shareKey}",
CtTool.class),
// https://xxx.118pan.com/bxxx
P118("118网盘",
compile("https://(?:[a-zA-Z\\d-]+\\.)?118pan\\.com/b(?<KEY>.+)"),
"https://qaiu.118pan.com/b{shareKey}",
P118Tool.class),
// https://www.vyuyun.com/s/QMa6ie?password=I4KG7H
PVYY("微雨云存储",
compile("https://www\\.vyuyun\\.com/s/(?<KEY>[a-zA-Z\\d-]+)(\\?password=.*)?"),
"https://www.vyuyun.com/s/{shareKey}",
PvyyTool.class),
// https://1drv.ms/w/s!Alg0feQmCv2rnRFd60DQOmMa-Oh_?e=buaRtp
POD("OneDrive",
compile("https://1drv\\.ms/[uw]/s!(?<KEY>.+)"),
"https://onedrive.live.com/",
PodTool.class),
// 404网盘 https://drive.google.com/file/d/xxx/view?usp=sharing
PGD("GoogleDrive",
compile("https://drive\\.google\\.com/file/d/(?<KEY>.+)/view(\\?usp=(sharing|drive_link))?"),
"https://drive.google.com/file/d/{shareKey}/view?usp=sharing",
PgdTool.class),
// iCloud https://www.icloud.com.cn/iclouddrive/xxx#fonts
PIC("iCloud",
compile("https://www\\.icloud\\.com\\.cn/iclouddrive/(?<KEY>[a-z_A-Z\\d-=]+)(#(.+))?"),
"https://www.icloud.com.cn/iclouddrive/{shareKey}",
PicTool.class),
// https://www.dropbox.com/scl/fi/cwnbms1yn8u6rcatzyta7/emqx-5.0.26-el7-amd64.tar.gz?rlkey=3uoi4bxz5mv93jmlaws0nlol1&e=8&st=fe0lclc2&dl=0
PDB("dropbox",
compile("https://www.dropbox.com/scl/fi/(?<KEY>\\w+)/.+?rlkey=(?<PWD>\\w+).*"),
"https://www.dropbox.com/scl/fi/{shareKey}/?rlkey={pwd}&dl=0",
PdbTool.class),
// =====================音乐类解析 分享链接标志->MxxS (单歌曲/普通音质)==========================
// http://163cn.tv/xxx
MNES("网易云音乐分享",
compile("http(s)?://163cn\\.tv/(?<KEY>.+)"),
"http://163cn.tv/{shareKey}",
MnesTool.class),
// https://music.163.com/#/song?id=xxx
MNE("网易云音乐歌曲详情",
compile("https://music\\.163\\.com/(#/)?song\\?id=(?<KEY>.+)"),
"https://music.163.com/#/song?id={shareKey}",
MnesTool.MneTool.class),
// https://c6.y.qq.com/base/fcgi-bin/u?__=xxx
MQQS("QQ音乐分享",
compile("https://(?:[a-zA-Z\\d-]+\\.)?y\\.qq\\.com/base/fcgi-bin/u\\?__=(?<KEY>.+)"),
"https://c6.y.qq.com/base/fcgi-bin/u?__={shareKey}",
MqqsTool.class),
// https://y.qq.com/n/ryqq/songDetail/000XjcLg0fbRjv?songtype=0
MQQ("QQ音乐歌曲详情",
compile("https://y\\.qq\\.com/n/ryqq/songDetail/(?<KEY>.+)(\\?.*)?"),
"https://y.qq.com/n/ryqq/songDetail/{shareKey}",
MqqsTool.MqqTool.class),
// https://t1.kugou.com/song.html?id=xxx
MKGS("酷狗音乐分享",
compile("https://(?:[a-zA-Z\\d-]+\\.)?kugou\\.com/song\\.html\\?id=(?<KEY>.+)"),
"https://t1.kugou.com/song.html?id={shareKey}",
MkgsTool.class),
// https://www.kugou.com/share/2bi8Fe9CSV3.html?id=2bi8Fe9CSV3#6ed9gna4"
MKGS2("酷狗音乐分享2",
compile("https://(?:[a-zA-Z\\d-]+\\.)?kugou\\.com/share/(?<KEY>.+).html.*"),
"https://www.kugou.com/share/{shareKey}.html",
MkgsTool.Mkgs2Tool.class),
// https://www.kugou.com/mixsong/2bi8Fe9CSV3
MKG("酷狗音乐歌曲详情",
compile("https://(?:[a-zA-Z\\d-]+\\.)?kugou\\.com/mixsong/(?<KEY>.+)\\.html.*"),
"https://www.kugou.com/mixsong/{shareKey}.html",
MkgsTool.MkgTool.class),
// https://kuwo.cn/play_detail/395500809
// https://m.kuwo.cn/newh5app/play_detail/318448522
MKWS("酷我音乐分享*",
compile("https://(m\\.)?kuwo\\.cn/(newh5app/)?play_detail/(?<KEY>.+)"),
"https://kuwo.cn/play_detail/{shareKey}",
MkwTool.class),
// https://music.migu.cn/v3/music/song/6326951FKBJ?channelId=001002H
MMGS("咪咕音乐分享",
compile("https://music\\.migu\\.cn/v3/music/song/(?<KEY>.+)(\\?.*)?"),
"https://music.migu.cn/v3/music/song/{shareKey}",
MkwTool.class),
// =====================私有盘解析==========================
// Cloudreve自定义域名解析, 解析器CeTool兜底策略, 即任意域名如果匹配不到对应的规则, 则由CeTool统一处理,
// 如果不属于Cloudreve盘 则调用下一个自定义域名解析器, 若都处理不了则抛出异常, 这种匹配模式类似责任链
// https://pan.huang1111.cn/s/xxx
// 通用域名([a-z\\d]+(-[a-z\\d]+)*\.)+[a-z]{2,}
CE("Cloudreve",
compile("http(s)?://([a-zA-Z\\d]+(-[a-zA-Z\\d]+)*\\.)+[a-zA-Z]{2,}(/s)?/(?<KEY>.+)"),
"https://{any}/s/{shareKey}",
"https://cloudreve.org/",
CeTool.class),
// 可道云自定义域名解析
KD("可道云",
compile("http(s)?://([a-zA-Z\\d]+(-[a-zA-Z\\d]+)*\\.)+[a-zA-Z]{2,}(/#s)?/(?<KEY>.+)"),
"https://{any}/#s/{shareKey}",
"https://kodcloud.com/",
KdTool.class),
// 其他自定义域名解析
OTHER("其他网盘",
compile("http(s)?://([a-zA-Z\\d]+(-[a-zA-Z\\d]+)*\\.)+[a-zA-Z]{2,}/(?<KEY>.+)"),
"https://{any}/{shareKey}",
OtherTool.class);
public static final String KEY = "KEY";
public static final String PWD = "PWD";
// 网盘的显示名称,用于用户界面显示
private final String displayName;
// 用于匹配和解析分享链接的正则表达式,保证最后一个捕捉组能匹配到分享key
private final Pattern pattern;
private final String regex;
// 网盘的标准链接模板,不含占位符,用于规范化分享链接
private final String standardUrlTemplate;
// 网盘的域名, 如果在分享链接里能提取到, 则可不写
private String panDomain;
// 指向解析工具IPanTool实现类
private final Class<? extends IPanTool> toolClass;
PanDomainTemplate(String displayName, Pattern pattern, String standardUrlTemplate,
Class<? extends IPanTool> toolClass) {
this.displayName = displayName;
this.pattern = pattern;
this.regex = pattern.pattern();
this.standardUrlTemplate = standardUrlTemplate;
this.toolClass = toolClass;
}
PanDomainTemplate(String displayName, Pattern pattern, String standardUrlTemplate, String panDomain,
Class<? extends IPanTool> toolClass) {
this.displayName = displayName;
this.pattern = pattern;
this.regex = pattern.pattern();
this.standardUrlTemplate = standardUrlTemplate;
this.panDomain = panDomain;
this.toolClass = toolClass;
}
public String getDisplayName() {
return displayName;
}
public Pattern getPattern() {
return pattern;
}
public String getRegex() {
return regex;
}
public String getStandardUrlTemplate() {
return standardUrlTemplate;
}
public Class<? extends IPanTool> getToolClass() {
return toolClass;
}
public String getPanDomain() {
if (panDomain == null) {
String url = standardUrlTemplate
.replace("{shareKey}", "");
URL panDomainUrl = null;
try {
panDomainUrl = new URL(url);
} catch (MalformedURLException ignored) {}
return panDomainUrl != null ? (panDomainUrl.getProtocol() + "://" + panDomainUrl.getHost()) : "";
}
return panDomain;
}
}
@@ -0,0 +1,13 @@
package cn.qaiu.parser;
/**
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2023/6/13 4:26
*/
public enum PanType {
LZ("lz"),
COW("cow");
PanType(String type) {
}
}
@@ -1,162 +0,0 @@
package cn.qaiu.parser;
import cn.qaiu.entity.ShareLinkInfo;
import org.apache.commons.lang3.StringUtils;
import java.util.regex.Matcher;
import static cn.qaiu.parser.PanDomainTemplate.KEY;
import static cn.qaiu.parser.PanDomainTemplate.PWD;
/**
* 该类提供方法来解析和规范化不同来源的分享链接,确保它们可以转换为统一的标准链接格式。
* 通过这种方式,应用程序可以更容易地处理和识别不同网盘服务的分享链接。
*
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2024/9/15 14:10
*/
public class ParserCreate {
private final PanDomainTemplate panDomainTemplate;
private final ShareLinkInfo shareLinkInfo;
public ParserCreate(PanDomainTemplate panDomainTemplate, ShareLinkInfo shareLinkInfo) {
this.panDomainTemplate = panDomainTemplate;
this.shareLinkInfo = shareLinkInfo;
}
// 解析并规范化分享链接
public ParserCreate 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");
}
Matcher matcher = this.panDomainTemplate.getPattern().matcher(shareUrl);
if (matcher.find()) {
String shareKey = matcher.group(KEY);
// 返回规范化的标准链接
String standardUrl = getStandardUrlTemplate()
.replace("{shareKey}", shareKey);
try {
String pwd = matcher.group(PWD);
standardUrl = standardUrl .replace("{pwd}", pwd);
} catch (Exception ignored) {}
shareLinkInfo.setShareUrl(shareUrl);
shareLinkInfo.setShareKey(shareKey);
if (!(panDomainTemplate.ordinal() >= PanDomainTemplate.CE.ordinal())) {
shareLinkInfo.setStandardUrl(standardUrl);
}
return this;
}
throw new IllegalArgumentException("Invalid share URL for " + this.panDomainTemplate.getDisplayName());
}
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 this.panDomainTemplate.getToolClass()
.getDeclaredConstructor(ShareLinkInfo.class)
.newInstance(shareLinkInfo);
} catch (Exception e) {
throw new RuntimeException("无法创建工具实例: " + panDomainTemplate.getToolClass().getName(), e);
}
}
// set share key
public ParserCreate shareKey(String shareKey) {
if (panDomainTemplate.ordinal() >= PanDomainTemplate.CE.ordinal()) {
// 处理Cloudreve(ce)类: pan.huang1111.cn_s_wDz5TK _ -> /
String[] s = shareKey.split("_");
String standardUrl = "https://" + String.join("/", s);
shareLinkInfo.setShareKey(s[s.length - 1]);
shareLinkInfo.setStandardUrl(standardUrl);
shareLinkInfo.setShareUrl(standardUrl);
} else {
shareLinkInfo.setShareKey(shareKey);
shareLinkInfo.setStandardUrl(panDomainTemplate.getStandardUrlTemplate().replace("{shareKey}", shareKey));
}
return this;
}
// set any share url
public ParserCreate fromAnyShareUrl(String url) {
shareLinkInfo.setStandardUrl(url);
shareLinkInfo.setShareUrl(url);
return this;
}
public String getStandardUrlTemplate() {
return this.panDomainTemplate.getStandardUrlTemplate();
}
public ShareLinkInfo getShareLinkInfo() {
return shareLinkInfo;
}
public ParserCreate setShareLinkInfoPwd(String pwd) {
shareLinkInfo.setSharePassword(pwd);
return this;
}
// 根据分享链接获取PanDomainTemplate实例
public synchronized static ParserCreate fromShareUrl(String shareUrl) {
for (PanDomainTemplate panDomainTemplate : PanDomainTemplate.values()) {
if (panDomainTemplate.getPattern().matcher(shareUrl).matches()) {
ShareLinkInfo shareLinkInfo = ShareLinkInfo.newBuilder()
.type(panDomainTemplate.name().toLowerCase())
.panName(panDomainTemplate.getDisplayName())
.shareUrl(shareUrl).build();
if (panDomainTemplate.ordinal() >= PanDomainTemplate.CE.ordinal()) {
shareLinkInfo.setStandardUrl(shareUrl);
}
ParserCreate parserCreate = new ParserCreate(panDomainTemplate, shareLinkInfo);
return parserCreate.normalizeShareLink();
}
}
throw new IllegalArgumentException("Unsupported share URL");
}
// 根据type获取枚举实例
public synchronized static ParserCreate fromType(String type) {
try {
PanDomainTemplate panDomainTemplate = Enum.valueOf(PanDomainTemplate.class, type.toUpperCase());
ShareLinkInfo shareLinkInfo = ShareLinkInfo.newBuilder()
.type(type.toLowerCase()).build();
shareLinkInfo.setPanName(panDomainTemplate.getDisplayName());
return new ParserCreate(panDomainTemplate, shareLinkInfo);
} catch (IllegalArgumentException ignore) {
// 如果没有找到对应的枚举实例,抛出异常
throw new IllegalArgumentException("No enum constant for type name: " + type);
}
}
// 生成parser短链path(不包含domainName)
public String genPathSuffix() {
String path;
if (panDomainTemplate.ordinal() >= PanDomainTemplate.CE.ordinal()) {
// 处理Cloudreve(ce)类: pan.huang1111.cn_s_wDz5TK _ -> /
path = this.shareLinkInfo.getType() + "/"
+ this.shareLinkInfo.getShareUrl()
.substring("https://".length()).replace("/", "_");
} else {
path = this.shareLinkInfo.getType() + "/" + this.shareLinkInfo.getShareKey();
}
String sharePassword = this.shareLinkInfo.getSharePassword();
return path + (StringUtils.isBlank(sharePassword) ? "" : ("@" + sharePassword));
}
}
@@ -1,84 +1,73 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.PanBase;
import cn.qaiu.parser.PanDomainTemplate;
import cn.qaiu.parser.ParserCreate;
import io.vertx.core.Future;
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;
import java.util.Arrays;
import java.util.Iterator;
/**
* <a href="https://github.com/cloudreve/Cloudreve">Cloudreve自建网盘解析</a> <br>
* <a href="https://github.com/cloudreve/Cloudreve">Cloudreve网盘解析</a> <br>
* <a href="https://pan.xiaomuxi.cn">暮希云盘</a> <br>
* <a href="https://pan.huang1111.cn">huang1111</a> <br>
* <a href="https://pan.seeoss.com">看见存储</a> <br>
* <a href="https://dav.yiandrive.com">亿安云盘</a> <br>
*/
public class CeTool extends PanBase {
public class CeTool extends PanBase implements IPanTool {
private static final String DOWNLOAD_API_PATH = "/api/v3/share/download/";
// api/v3/share/info/g31PcQ?password=qaiu
private static final String SHARE_API_PATH = "/api/v3/share/info/";
public CeTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
public CeTool(String key, String pwd) {
super(key, pwd);
}
public Future<String> 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/pan.huang1111.cn_s_wDz5TK
// 类型解析 -> /ce/https_pan.huang1111.cn_s_wDz5TK
// parser接口 -> /parser?url=https://pan.huang1111.cn/s/wDz5TK
try {
// // 处理URL
URL url = new URL(shareLinkInfo.getShareUrl());
String downloadApiUrl = url.getProtocol() + "://" + url.getHost() + DOWNLOAD_API_PATH + key + "?path" +
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 + key;
String shareApiUrl = url.getProtocol() + "://" + url.getHost() + SHARE_API_PATH + shareKey;
// 设置cookie
HttpRequest<Buffer> httpRequest = clientSession.getAbs(shareApiUrl);
if (pwd != null) {
httpRequest.addQueryParam("password", pwd);
}
// 获取下载链接
httpRequest.send().onSuccess(res -> {
try {
if (res.statusCode() == 200 && res.bodyAsJsonObject().containsKey("code")) {
getDownURL(downloadApiUrl);
} else {
nextParser();
}
} catch (Exception e) {
nextParser();
}
}).onFailure(handleFail(shareApiUrl));
} catch (Exception e) {
httpRequest.send().onSuccess(res -> getDownURL(downloadApiUrl)).onFailure(handleFail(shareApiUrl));
} catch (MalformedURLException e) {
fail(e, "URL解析错误");
}
return promise.future();
}
private void getDownURL(String shareApiUrl) {
clientSession.putAbs(shareApiUrl).send().onSuccess(res -> {
JsonObject jsonObject = asJson(res);
private void getDownURL(String apiUrl) {
clientSession.putAbs(apiUrl).send().onSuccess(res -> {
JsonObject jsonObject = res.bodyAsJsonObject();
System.out.println(jsonObject.encodePrettily());
if (jsonObject.containsKey("code") && jsonObject.getInteger("code") == 0) {
promise.complete(jsonObject.getString("data"));
} else {
fail("JSON解析失败: {}", jsonObject.encodePrettily());
}
}).onFailure(handleFail(shareApiUrl));
}).onFailure(handleFail(apiUrl));
}
}
@@ -1,7 +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;
@@ -12,20 +13,22 @@ import org.apache.commons.lang3.StringUtils;
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2023/4/21 21:19
*/
public class CowTool extends PanBase {
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 CowTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
public static final String LINK_KEY = "cowtransfer.com/s/";
public CowTool(String key, String pwd) {
super(key, pwd);
}
public Future<String> parse() {
final String key = shareLinkInfo.getShareKey();
key = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key);
String url = API_REQUEST_URL + "?uniqueUrl=" + key;
client.getAbs(url).send().onSuccess(res -> {
JsonObject resJson = asJson(res);
JsonObject resJson = res.bodyAsJsonObject();
if ("success".equals(resJson.getString("message")) && resJson.containsKey("data")) {
JsonObject dataJson = resJson.getJsonObject("data");
String guid = dataJson.getString("guid");
@@ -40,7 +43,7 @@ public class CowTool extends PanBase {
}
String url2 = url2Build.toString();
client.getAbs(url2).send().onSuccess(res2 -> {
JsonObject res2Json = asJson(res2);
JsonObject res2Json = res2.bodyAsJsonObject();
if ("success".equals(res2Json.getString("message")) && res2Json.containsKey("data")) {
JsonObject data2 = res2Json.getJsonObject("data");
String downloadUrl = data2.getString("downloadUrl");
@@ -1,90 +0,0 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.PanBase;
import cn.qaiu.util.RandomStringGenerator;
import io.vertx.core.Future;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.HttpRequest;
import io.vertx.uritemplate.UriTemplate;
/**
* <a href="https://www.ctfile.com">诚通网盘</a>
*/
public class CtTool extends PanBase {
private static final String API_URL_PREFIX = "https://webapi.ctfile.com";
private static final String API1 = API_URL_PREFIX + "/getfile.php?path=file" +
"&f={shareKey}&passcode={pwd}&token={token}&r={rand}&ref=";
private static final String API2 = API_URL_PREFIX + "/get_file_url.php?" +
"uid={uid}&fid={fid}&folder_id=0&file_chk={file_chk}&mb=0&token={token}&app=0&acheck=0&verifycode=" +
"&rd={rand}";
/**
* 子类重写此构造方法不需要添加额外逻辑
* 如:
* <blockquote><pre>
* public XxTool(String key, String pwd) {
* super(key, pwd);
* }
* </pre></blockquote>
*
* @param shareLinkInfo 分享链接信息
*/
public CtTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
@Override
public Future<String> parse() {
final String shareKey = shareLinkInfo.getShareKey();
if (shareKey.indexOf('-') == -1) {
fail("shareKey格式不正确找不到'-': {}", shareKey);
return promise.future();
}
String[] split = shareKey.split("-");
String uid = split[0], fid = split[1];
String token = RandomStringGenerator.generateRandomString();
HttpRequest<Buffer> bufferHttpRequest1 = clientSession.getAbs(UriTemplate.of(API1))
.setTemplateParam("shareKey", shareKey)
.setTemplateParam("pwd", shareLinkInfo.getSharePassword())
.setTemplateParam("token", token)
.setTemplateParam("r", Math.random() + "");
bufferHttpRequest1
.send().onSuccess(res -> {
var resJson = asJson(res);
if (resJson.containsKey("file")) {
var fileJson = resJson.getJsonObject("file");
if (fileJson.containsKey("file_chk")) {
var file_chk = fileJson.getString("file_chk");
HttpRequest<Buffer> bufferHttpRequest2 = clientSession.getAbs(UriTemplate.of(API2))
.setTemplateParam("uid", uid)
.setTemplateParam("fid", fid)
.setTemplateParam("file_chk", file_chk)
.setTemplateParam("token", token)
.setTemplateParam("rd", Math.random() + "");
bufferHttpRequest2
.send().onSuccess(res2 -> {
JsonObject resJson2 = asJson(res2);
if (resJson2.containsKey("downurl")) {
promise.complete(resJson2.getString("downurl"));
} else {
fail("解析失败, 可能分享已失效: json: {} 字段 {} 不存在", resJson2, "downurl");
}
}).onFailure(handleFail(bufferHttpRequest1.queryParams().toString()));
} else {
fail("解析失败, 可能分享已失效: json: {} 字段 {} 不存在", resJson, "file_chk");
}
} else {
fail("解析失败, 可能分享已失效: json: {} 字段 {} 不存在", resJson, "file");
}
}).onFailure(handleFail(bufferHttpRequest1.queryParams().toString()));
return promise.future();
}
}
@@ -1,7 +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;
@@ -10,30 +11,24 @@ import io.vertx.uritemplate.UriTemplate;
/**
* 移动云空间解析
*/
public class EcTool extends PanBase {
// https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=4b3d786755688b85c6eb0c04b9124f4dalzdaJpXHx&isShare=1
public class EcTool extends PanBase implements IPanTool {
private static final String FIRST_REQUEST_URL = "https://www.ecpan.cn/drive/fileextoverrid" +
".do?extractionCode={extractionCode}&chainUrlTemplate=https:%2F%2Fwww.ecpan" +
".do?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 EcTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
public static final String SHARE_URL_PREFIX = "www.ecpan.cn/";
public EcTool(String key, String pwd) {
super(key, pwd);
}
public Future<String> parse() {
final String dataKey = shareLinkInfo.getShareKey();
final String pwd = shareLinkInfo.getSharePassword();
String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key);
// 第一次请求 获取文件信息
client.getAbs(UriTemplate.of(FIRST_REQUEST_URL))
.setTemplateParam("dataKey", dataKey)
.setTemplateParam("extractionCode", pwd == null ? "" : pwd)
.send()
.onSuccess(res -> {
JsonObject jsonObject = asJson(res);
client.getAbs(UriTemplate.of(FIRST_REQUEST_URL)).setTemplateParam("dataKey", dataKey).send().onSuccess(res -> {
JsonObject jsonObject = res.bodyAsJsonObject();
log.debug("ecPan get file info -> {}", jsonObject);
JsonObject fileInfo = jsonObject
.getJsonObject("var")
@@ -42,11 +37,6 @@ public class EcTool extends PanBase {
fail("{} 解析失败:{} key = {}", FIRST_REQUEST_URL, fileInfo.getString("errMesg"), dataKey);
return;
}
if (!fileInfo.containsKey("cloudpFile")) {
fail("{} 解析失败:cloudpFile不存在 key = {}", FIRST_REQUEST_URL, dataKey);
return;
}
JsonObject cloudpFile = fileInfo.getJsonObject("cloudpFile");
JsonArray fileIdList = JsonArray.of(cloudpFile);
// 构造请求JSON {"extCodeFlag":0,"isIp":0}
@@ -58,7 +48,7 @@ public class EcTool extends PanBase {
// 第二次请求 获取下载链接
client.postAbs(DOWNLOAD_REQUEST_URL).sendJsonObject(requestBodyJson).onSuccess(res2 -> {
JsonObject jsonRes = asJson(res2);
JsonObject jsonRes = res2.bodyAsJsonObject();
log.debug("ecPan get download url -> {}", res2.body().toString());
promise.complete(jsonRes.getJsonObject("var").getString("downloadUrl"));
}).onFailure(handleFail(""));
@@ -1,7 +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;
@@ -18,21 +19,21 @@ import java.util.regex.Pattern;
/**
* 360亿方云
*/
public class FcTool extends PanBase {
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(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
public FcTool(String key, String pwd) {
super(key, pwd);
}
public Future<String> parse() {
final String dataKey = shareLinkInfo.getShareKey();
final String pwd = shareLinkInfo.getSharePassword();
String data = key.replace("share","sharing");
String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, data);
WebClientSession sClient = WebClientSession.create(client);
// 第一次请求 自动重定向
sClient.getAbs(SHARE_URL_PREFIX + dataKey).send().onSuccess(res -> {
@@ -87,7 +88,7 @@ public class FcTool extends PanBase {
.setTemplateParam("unique_name", dataKey).send().onSuccess(res2 -> {
JsonObject resJson;
try {
resJson = asJson(res2);
resJson = res2.bodyAsJsonObject();
} catch (Exception e) {
fail(e, DOWN_REQUEST_URL + " 第二次请求没有返回JSON, 可能下载受限");
return;
@@ -1,68 +1,54 @@
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.UUIDUtil;
import cn.qaiu.util.CommonUtils;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.HttpRequest;
import io.vertx.ext.web.client.WebClient;
import io.vertx.uritemplate.UriTemplate;
import java.util.UUID;
/**
* 小飞机网盘
*
* @version V016_230609
*/
public class FjTool extends PanBase {
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" +
"&uuid={uuid}&extra=2&timestamp={ts}&shareId={shareId}&type=0&offset=1&limit=60";
/// recommend/list?devType=6&devModel=Chrome&uuid={uuid}&extra=2&timestamp={ts}&shareId={shareId}&type=0&offset=1
// &limit=60
private static final String FIRST_REQUEST_URL = API_URL_PREFIX + "recommend/list?devType=6&devModel=Chrome&extra" +
"=2&shareId={shareId}&type=0&offset=1&limit=60";
private static final String SECOND_REQUEST_URL = API_URL_PREFIX + "file/redirect?downloadId={fidEncode}&enable=1" +
"&devType=6&uuid={uuid}&timestamp={ts}&auth={auth}&shareId={dataKey}";
// https://api.feijipan.com/ws/file/redirect?downloadId={fidEncode}&enable=1&devType=6&uuid={uuid}&timestamp={ts}&auth={auth}&shareId={dataKey}
"&devType=6&uuid={uuid}&timestamp={ts}&auth={auth}";
private static final String VIP_REQUEST_URL = API_URL_PREFIX + "/buy/vip/list?devType=6&devModel=Chrome&uuid" +
"={uuid}&extra=2&timestamp={ts}";
// https://api.feijipan.com/ws/buy/vip/list?devType=6&devModel=Chrome&uuid=WQAl5yBy1naGudJEILBvE&extra=2&timestamp=E2C53155F6D09417A27981561134CB73
public FjTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
public FjTool(String key, String pwd) {
super(key, pwd);
}
public Future<String> parse() {
final String dataKey = shareLinkInfo.getShareKey();
String dataKey;
if (key.startsWith(SHARE_URL_PREFIX2)) {
dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX2, key);
} else {
dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key);
}
// 240530 此处shareId又改为了原始的shareId
String shareId = dataKey; // String.valueOf(AESUtils.idEncrypt(dataKey));
long nowTs = System.currentTimeMillis();
String tsEncode = AESUtils.encrypt2Hex(Long.toString(nowTs));
String uuid = UUIDUtil.fjUuid(); // 也可以使用 UUID.randomUUID().toString()
WebClient client = clientNoRedirects;
String shareId = String.valueOf(AESUtils.idEncrypt(dataKey));
// 24.5.12 飞机盘 规则修改 需要固定UUID先请求会员接口, 再请求后续接口
client.postAbs(UriTemplate.of(VIP_REQUEST_URL))
.setTemplateParam("uuid", uuid)
.setTemplateParam("ts", tsEncode)
.send().onSuccess(r0 -> { // 忽略res
// long nowTs0 = System.currentTimeMillis();
String tsEncode0 = AESUtils.encrypt2Hex(Long.toString(nowTs));
// 第一次请求 获取文件信息
// POST https://api.feijipan.com/ws/recommend/list?devType=6&devModel=Chrome&extra=2&shareId=146731&type=0&offset=1&limit=60
client.postAbs(UriTemplate.of(FIRST_REQUEST_URL))
.setTemplateParam("shareId", shareId)
.setTemplateParam("uuid", uuid)
.setTemplateParam("ts", tsEncode0)
.send().onSuccess(res -> {
JsonObject resJson = asJson(res);
client.postAbs(UriTemplate.of(FIRST_REQUEST_URL)).setTemplateParam("shareId", shareId).send().onSuccess(res -> {
JsonObject resJson = res.bodyAsJsonObject();
if (resJson.getInteger("code") != 200) {
fail(FIRST_REQUEST_URL + " 返回异常: " + resJson);
return;
@@ -76,24 +62,21 @@ public class FjTool extends PanBase {
String fileId = fileInfo.getString("fileIds");
String userId = fileInfo.getString("userId");
// 其他参数
long nowTs2 = System.currentTimeMillis();
String tsEncode2 = AESUtils.encrypt2Hex(Long.toString(nowTs2));
String fidEncode = AESUtils.encrypt2Hex(fileId + "|" + userId);
String auth = AESUtils.encrypt2Hex(fileId + "|" + nowTs2);
long nowTs = System.currentTimeMillis();
String tsEncode = AESUtils.encrypt2Hex(Long.toString(nowTs));
String uuid = UUID.randomUUID().toString();
String fidEncode = AESUtils.encrypt2Hex(fileId + "|");
String auth = AESUtils.encrypt2Hex(fileId + "|" + nowTs);
MultiMap headers0 = MultiMap.caseInsensitiveMultiMap();
headers0.set("referer", REFERER_URL);
// 第二次请求
HttpRequest<Buffer> httpRequest =
clientNoRedirects.getAbs(UriTemplate.of(SECOND_REQUEST_URL))
client.getAbs(UriTemplate.of(SECOND_REQUEST_URL))
.putHeaders(headers0)
.setTemplateParam("fidEncode", fidEncode)
.setTemplateParam("uuid", uuid)
.setTemplateParam("ts", tsEncode2)
.setTemplateParam("auth", auth)
.setTemplateParam("dataKey", dataKey);
System.out.println(httpRequest.toString());
httpRequest.send().onSuccess(res2 -> {
.setTemplateParam("ts", tsEncode)
.setTemplateParam("auth", auth).send().onSuccess(res2 -> {
MultiMap headers = res2.headers();
if (!headers.contains("Location")) {
fail(SECOND_REQUEST_URL + " 未找到重定向URL: \n" + res.headers());
@@ -103,8 +86,6 @@ public class FjTool extends PanBase {
}).onFailure(handleFail(SECOND_REQUEST_URL));
}).onFailure(handleFail(FIRST_REQUEST_URL));
}).onFailure(handleFail(FIRST_REQUEST_URL));
return promise.future();
}
}
@@ -1,132 +0,0 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.PanBase;
import io.netty.handler.codec.http.cookie.Cookie;
import io.netty.handler.codec.http.cookie.DefaultCookie;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.impl.headers.HeadersMultiMap;
import io.vertx.core.json.JsonObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* t.cn 短链生成 //
*/
public class GenShortUrl extends PanBase {
private static final Logger log = LoggerFactory.getLogger(GenShortUrl.class);
private static final String COMMENT_URL = "https://www.weibo.com/aj/v6/comment/add";
private static final String DELETE_COMMENT_URL = "https://www.weibo.com/aj/comment/del";
private static final String WRAPPER_URL = "https://www.so.com/link?m=ewgUSYiFWXIoTybC3fJH8YoJy8y10iRquo6cazgINwWjTn3HvVJ92TrCJu0PmMUR0RMDfOAucP3wa4G8j64SrhNH9Z0Cr0PEyn9ASuvpkUGmAjjUEGJkO5%2BIDGWVrEkPHsL7UsoKO6%2BlT%2BD6r&ccc=";
private static final String MID = "5095144728824883"; // 微博的mid
private static final MultiMap HEADER = HeadersMultiMap.headers()
.add("Content-Type", "application/x-www-form-urlencoded")
.add("Referer", "https://www.weibo.com")
.add("Content-Type", "application/x-www-form-urlencoded")
.add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36");
Cookie cookie = new DefaultCookie("SUB", "_2A25KJE5vDeRhGeRJ6lsR9SjJzDuIHXVpWM-nrDV8PUJbkNAbLVPlkW1NUmJm3GjYtRHBsHdMUKafkdTL_YheMEmu");
public GenShortUrl(ShareLinkInfo build) {
super(build);
}
@Override
public Future<String> parse() {
String longUrl = shareLinkInfo.getShareUrl();
String url = WRAPPER_URL + Base64.getEncoder().encodeToString(longUrl.getBytes());
String payload = "mid=" + MID + "&content=" + URLEncoder.encode(url, StandardCharsets.UTF_8);
clientSession.cookieStore().put(cookie);
clientSession.postAbs(COMMENT_URL)
.putHeaders(HEADER)
.sendBuffer(Buffer.buffer(payload))
.onSuccess(res -> {
JsonObject data = asJson(res).getJsonObject("data");
if (data.isEmpty()) {
fail(asJson(res).getString("msg"));
return;
}
String comment = data.getString("comment");
String shortUrl = extractShortUrl(comment);
if (shortUrl != null) {
log.info("生成的短链:{}", shortUrl);
String commentId = extractCommentId(comment);
if (commentId != null) {
deleteComment(commentId);
} else {
promise.fail("未能提取评论ID");
}
} else {
promise.fail("未能生成短链");
}
})
.onFailure(err -> {
log.error("添加评论失败", err);
promise.fail(err);
});
return promise.future();
}
private void deleteComment(String commentId) {
String payload = "mid=" + MID + "&cid=" + commentId;
clientSession.postAbs(DELETE_COMMENT_URL)
.putHeaders(HEADER)
.sendBuffer(Buffer.buffer(payload))
.onSuccess(res -> {
JsonObject responseJson = res.bodyAsJsonObject();
if (responseJson.getString("code").equals("100000")) {
log.info("评论已删除: {}", commentId);
} else {
log.error("删除评论失败: {}", responseJson.encode());
}
})
.onFailure(err -> {
log.error("删除评论失败", err);
});
}
private String extractShortUrl(String comment) {
Pattern pattern = Pattern.compile("(https?)://t.cn/\\w+");
Matcher matcher = pattern.matcher(comment);
if (matcher.find()) {
return matcher.group(0);
}
return null;
}
private String extractCommentId(String comment) {
Pattern pattern = Pattern.compile("comment_id=\"(\\d+)\"");
Matcher matcher = pattern.matcher(comment);
if (matcher.find()) {
return matcher.group(1);
}
return null;
}
public static void main(String[] args) {
// http://t.cn/A6nfZn86
// http://t.cn/A6nfZn86
new GenShortUrl(ShareLinkInfo.newBuilder().shareUrl("https://qaiu.top/sdfsdf").build()).parse().onSuccess(
System.out::println
);
}
}
@@ -1,11 +1,13 @@
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;
import io.vertx.ext.web.client.WebClient;
import io.vertx.uritemplate.UriTemplate;
import java.util.UUID;
@@ -14,32 +16,31 @@ import java.util.UUID;
* 蓝奏云优享
*
*/
public class IzTool extends PanBase {
public class IzTool extends PanBase implements IPanTool {
public static final String SHARE_URL_PREFIX = "https://www.ilanzou.com/s/";
private static final String API_URL_PREFIX = "https://api.ilanzou.com/unproved/";
private static final String FIRST_REQUEST_URL = API_URL_PREFIX +
"recommend/list?devModel=Chrome&extra=2&shareId={shareId}&type=0&offset=1&limit=60";
private static final String FIRST_REQUEST_URL = API_URL_PREFIX + "recommend/list?devType=6&devModel=Chrome&extra" +
"=2&shareId={shareId}&type=0&offset=1&limit=60";
private static final String SECOND_REQUEST_URL = API_URL_PREFIX +
"file/redirect?downloadId={fidEncode}&enable=1&devType=6&uuid={uuid}&timestamp={ts}&auth={auth}&shareId={shareId}";
// downloadId=x&enable=1&devType=6&uuid=x&timestamp=x&auth=x&shareId=lGFndCM
private static final String SECOND_REQUEST_URL = API_URL_PREFIX + "file/redirect?downloadId={fidEncode}&enable=1" +
"&devType=6&uuid={uuid}&timestamp={ts}&auth={auth}";
public IzTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
public IzTool(String key, String pwd) {
super(key, pwd);
}
public Future<String> parse() {
String dataKey = shareLinkInfo.getShareKey();
String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key);
// 24.5.12 ilanzou改规则无需计算shareId
// String shareId = String.valueOf(AESUtils.idEncryptIz(dataKey));
WebClient client = clientNoRedirects;
String shareId = String.valueOf(AESUtils.idEncryptIz(dataKey));
// 第一次请求 获取文件信息
// POST https://api.feijipan.com/ws/recommend/list?devType=6&devModel=Chrome&extra=2&shareId=146731&type=0&offset=1&limit=60
client.postAbs(UriTemplate.of(FIRST_REQUEST_URL)).setTemplateParam("shareId", dataKey).send().onSuccess(res -> {
JsonObject resJson = asJson(res);
client.postAbs(UriTemplate.of(FIRST_REQUEST_URL)).setTemplateParam("shareId", shareId).send().onSuccess(res -> {
JsonObject resJson = res.bodyAsJsonObject();
if (resJson.getInteger("code") != 200) {
fail(FIRST_REQUEST_URL + " 返回异常: " + resJson);
return;
@@ -60,12 +61,11 @@ public class IzTool extends PanBase {
String fidEncode = AESUtils.encrypt2HexIz(fileId + "|" + userId);
String auth = AESUtils.encrypt2HexIz(fileId + "|" + nowTs);
// 第二次请求
clientNoRedirects.getAbs(UriTemplate.of(SECOND_REQUEST_URL))
client.getAbs(UriTemplate.of(SECOND_REQUEST_URL))
.setTemplateParam("fidEncode", fidEncode)
.setTemplateParam("uuid", uuid)
.setTemplateParam("ts", tsEncode)
.setTemplateParam("auth", auth)
.setTemplateParam("shareId", dataKey).send().onSuccess(res2 -> {
.setTemplateParam("auth", auth).send().onSuccess(res2 -> {
MultiMap headers = res2.headers();
if (!headers.contains("Location")) {
fail(SECOND_REQUEST_URL + " 未找到重定向URL: \n" + res.headers());
@@ -1,26 +0,0 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.PanBase;
import io.vertx.core.Future;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import java.util.UUID;
/**
* <a href="https://kodcloud.com/">可道云</a>
*/
public class KdTool extends PanBase {
private static final String API_URL_PREFIX = "";
public KdTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
public Future<String> parse() {
nextParser();
// TODO
return future();
}
}
@@ -1,7 +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;
@@ -11,22 +12,23 @@ import java.util.UUID;
/**
* <a href="https://lecloud.lenovo.com/">联想乐云</a>
*/
public class LeTool extends PanBase {
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(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
public LeTool(String key, String pwd) {
super(key, pwd);
}
public Future<String> parse() {
final String dataKey = shareLinkInfo.getShareKey();
final String pwd = shareLinkInfo.getSharePassword();
String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key);
// {"shareId":"xxx","password":"xxx","directoryId":"-1"}
String apiUrl1 = API_URL_PREFIX + "shareInfo";
client.postAbs(apiUrl1)
.sendJsonObject(JsonObject.of("shareId", dataKey, "password", pwd, "directoryId", -1))
.onSuccess(res -> {
JsonObject resJson = asJson(res);
JsonObject resJson = res.bodyAsJsonObject();
if (resJson.containsKey("result")) {
if (resJson.getBoolean("result")) {
JsonObject dataJson = resJson.getJsonObject("data");
@@ -45,7 +47,7 @@ public class LeTool extends PanBase {
JsonObject fileInfoJson = files.getJsonObject(0);
if (fileInfoJson != null) {
// TODO 文件大小fileSize和文件名fileName
String fileId = fileInfoJson.getString("fileId");
Long fileId = fileInfoJson.getLong("fileId");
// 根据文件ID获取跳转链接
getDownURL(dataKey, fileId);
}
@@ -59,7 +61,7 @@ public class LeTool extends PanBase {
return promise.future();
}
private void getDownURL(String key, String fileId) {
private void getDownURL(String key, Long fileId) {
String uuid = UUID.randomUUID().toString();
JsonArray fileIds = JsonArray.of(fileId);
String apiUrl2 = API_URL_PREFIX + "packageDownloadWithFileIds";
@@ -67,7 +69,7 @@ public class LeTool extends PanBase {
client.postAbs(apiUrl2)
.sendJsonObject(JsonObject.of("fileIds", fileIds, "shareId", key, "browserId", uuid))
.onSuccess(res -> {
JsonObject resJson = asJson(res);
JsonObject resJson = res.bodyAsJsonObject();
if (resJson.containsKey("result")) {
if (resJson.getBoolean("result")) {
JsonObject dataJson = resJson.getJsonObject("data");
@@ -1,6 +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;
import io.vertx.core.Future;
@@ -19,19 +19,19 @@ import java.util.regex.Pattern;
*
* @author QAIU
*/
public class LzTool extends PanBase {
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(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
public LzTool(String key, String pwd) {
super(key, pwd);
}
@SuppressWarnings("unchecked")
public Future<String> parse() {
String sUrl = shareLinkInfo.getStandardUrl();
String pwd = shareLinkInfo.getSharePassword();
String sUrl = key.startsWith("https://") ? key : SHARE_URL_PREFIX + "/" + key;
WebClient client = clientNoRedirects;
client.getAbs(sUrl).send().onSuccess(res -> {
@@ -50,18 +50,16 @@ public class LzTool extends PanBase {
}
jsText = jsText.replace("document.getElementById('pwd').value", "\"" + pwd + "\"");
int i = jsText.indexOf("document.getElementById('rpt')");
if (i > 0) {
jsText = jsText.substring(0, i);
}
jsText = jsText.substring(0, jsText.indexOf("document.getElementById('rpt')"));
try {
ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText, "down_p");
getDownURL(sUrl, client, (Map<String, String>) scriptObjectMirror.get("data"));
} catch (ScriptException | NoSuchMethodException e) {
fail(e, "js引擎执行失败");
return;
}
return;
}
} else {
// 没有密码
String iframePath = matcher.group(1);
client.getAbs(SHARE_URL_PREFIX + iframePath).send().onSuccess(res2 -> {
String html2 = res2.bodyAsString();
@@ -80,7 +78,6 @@ public class LzTool extends PanBase {
fail(e, "js引擎执行失败");
}
}).onFailure(handleFail(SHARE_URL_PREFIX));
}
}).onFailure(handleFail(sUrl));
return promise.future();
}
@@ -114,7 +111,7 @@ public class LzTool extends PanBase {
String url = SHARE_URL_PREFIX + "/ajaxm.php";
client.postAbs(url).putHeaders(headers).sendForm(map).onSuccess(res2 -> {
JsonObject urlJson = asJson(res2);
JsonObject urlJson = res2.bodyAsJsonObject();
if (urlJson.getInteger("zt") != 1) {
fail(urlJson.getString("inf"));
return;
@@ -1,125 +0,0 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.PanBase;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.json.JsonObject;
import io.vertx.uritemplate.UriTemplate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 酷狗音乐分享
* <a href="https://t1.kugou.com/song.html?id=2bi8Fe9CSV3">分享链接1</a>
* <a href="https://www.kugou.com/share/2bi8Fe9CSV3.html?id=2bi8Fe9CSV3#6ed9gna4">分享链接2</a>
* <a href="https://www.kugou.com/share/2bi8Fe9CSV3.html">分享链接3</a>
* <a href="https://www.kugou.com/mixsong/8odv4ef8.html">歌曲链接1</a>
*/
public class MkgsTool extends PanBase {
public static final String API_URL = "https://m.kugou.com/app/i/getSongInfo.php?cmd=playInfo&hash={hash}";
private static final MultiMap headers = MultiMap.caseInsensitiveMultiMap();
static {
// 设置 User-Agent
headers.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0");
// 设置 Accept
headers.set("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");
// 设置 If-Modified-Since
headers.set("If-Modified-Since", "Mon, 21 Oct 2024 08:45:50 GMT");
// 设置 Priority
headers.set("Priority", "u=0, i");
// 设置 Sec-CH-UA
headers.set("Sec-CH-UA", "\"Microsoft Edge\";v=\"129\", \"Not=A?Brand\";v=\"8\", \"Chromium\";v=\"129\"");
// 设置 Sec-CH-UA-Mobile
headers.set("Sec-CH-UA-Mobile", "?0");
// 设置 Sec-CH-UA-Platform
headers.set("Sec-CH-UA-Platform", "\"Windows\"");
// 设置 Sec-Fetch-Dest
headers.set("Sec-Fetch-Dest", "document");
// 设置 Sec-Fetch-Mode
headers.set("Sec-Fetch-Mode", "navigate");
// 设置 Sec-Fetch-Site
headers.set("Sec-Fetch-Site", "none");
// 设置 Sec-Fetch-User
headers.set("Sec-Fetch-User", "?1");
// 设置 Upgrade-Insecure-Requests
headers.set("Upgrade-Insecure-Requests", "1");
};
public MkgsTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
public Future<String> parse() {
String shareUrl = shareLinkInfo.getStandardUrl();
// String shareUrl = "https://t1.kugou.com/song.html?id=2bi8Fe9CSV3";
clientNoRedirects.getAbs(shareUrl).send().onSuccess(res -> {
String locationURL = res.headers().get("Location");
downUrl(locationURL);
}).onFailure(handleFail(shareUrl));
return promise.future();
}
protected void downUrl(String locationURL) {
client.getAbs(locationURL).putHeaders(headers).send().onSuccess(res2->{
String body = res2.bodyAsString();
// 正则表达式匹配 hash 字段
String regex = "\"hash\"\s*:\s*\"([A-F0-9]+)\"";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(body);
// 查找并输出 hash 字段的值
if (matcher.find()) {
String hashValue = matcher.group(1); // 获取第一个捕获组
System.out.println(hashValue);
client.getAbs(UriTemplate.of(API_URL)).setTemplateParam("hash", hashValue).send().onSuccess(res3 -> {
JsonObject jsonObject = asJson(res3);
System.out.println(jsonObject.encodePrettily());
if (jsonObject.containsKey("url")) {
promise.complete(jsonObject.getString("url"));
} else {
fail("下载链接不存在");
}
}).onFailure(handleFail(API_URL.replace("{hash}", hashValue)));
} else {
fail("歌曲hash匹配失败, 可能分享已失效");
}
}).onFailure(handleFail(locationURL));
}
public static class MkgTool extends MkgsTool {
public MkgTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
@Override
public Future<String> parse() {
downUrl(shareLinkInfo.getStandardUrl());
return promise.future();
}
;
}
public static class Mkgs2Tool extends MkgTool {
public Mkgs2Tool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
}
}
@@ -1,68 +0,0 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.PanBase;
import cn.qaiu.util.JsExecUtils;
import io.vertx.core.Future;
import io.vertx.core.json.JsonObject;
import io.vertx.uritemplate.UriTemplate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 酷我音乐分享
* <a href="https://kuwo.cn/play_detail/395500809">分享示例</a>
* <a href="https://m.kuwo.cn/newh5app/play_detail/318448522">分享示例</a>
*/
public class MkwTool extends PanBase {
public static final String API_URL = "https://www.kuwo.cn/api/v1/www/music/playUrl?mid={mid}&type=music&httpsStatus=1&reqId=&plat=web_www&from=";
public MkwTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
public Future<String> parse() {
String shareUrl = shareLinkInfo.getStandardUrl();
clientSession.getAbs(shareUrl).send().onSuccess(result -> {
String cookie = result.headers().get("set-cookie");
if (!cookie.isEmpty()) {
String regex = "([A-Za-z0-9_]+)=([A-Za-z0-9]+)";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(cookie);
if (matcher.find()) {
System.out.println(matcher.group(1));
System.out.println(matcher.group(2));
var key = matcher.group(1);
var token = matcher.group(2);
String sign = JsExecUtils.getKwSign(token, key);
System.out.println(sign);
clientSession.getAbs(UriTemplate.of(API_URL)).setTemplateParam("mid", shareLinkInfo.getShareKey())
.putHeader("Secret", sign).send().onSuccess(res -> {
JsonObject json = asJson(res);
log.debug(json.encodePrettily());
try {
if (json.getInteger("code") == 200) {
complete(json.getJsonObject("data").getString("url"));
} else {
fail("链接已失效/需要VIP");
}
} catch (Exception e) {
e.printStackTrace();
fail("解析失败");
}
});
}
}
});
return promise.future();
}
}
@@ -1,26 +0,0 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.PanBase;
import io.vertx.core.Future;
/**
* 咪咕音乐分享
*/
public class MmgTool extends PanBase {
public static final String API_URL = "";
public MmgTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
public Future<String> parse() {
String shareUrl = shareLinkInfo.getStandardUrl();
// TODO
promise.complete("暂未实现, 敬请期待");
return promise.future();
}
}
@@ -1,59 +0,0 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.PanBase;
import cn.qaiu.util.URLUtil;
import io.vertx.core.Future;
import io.vertx.uritemplate.UriTemplate;
/**
* 网易云音乐, 单歌曲直链解析
* <a href="http://163cn.tv/ykLZJJT">示例分享1</a>
* <a href="https://music.163.com/#/song?id=472194327">示例分享2</a>
*/
public class MnesTool extends PanBase {
public static final String API_URL = "https://music.163.com/song/media/outer/url?id={id}";
public MnesTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
public Future<String> parse() {
String shareUrl = shareLinkInfo.getStandardUrl();
clientNoRedirects.getAbs(shareUrl).send().onSuccess(res -> {
String locationURL = res.headers().get("Location");
downUrl(locationURL);
}).onFailure(handleFail(shareUrl));
return promise.future();
}
protected void downUrl(String locationURL) {
String id = URLUtil.from(locationURL).getParam("id");
clientNoRedirects.getAbs(UriTemplate.of(API_URL)).setTemplateParam("id", id).send()
.onSuccess(res2 -> {
String location = res2.headers().get("Location");
if (location.endsWith("/404")) {
fail("链接已失效: id={}", id);
} else {
promise.complete(location);
}
}).onFailure(handleFail(API_URL.replace("{id}", id)));
}
public static class MneTool extends MnesTool{
public MneTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
@Override
public Future<String> parse() {
downUrl(shareLinkInfo.getStandardUrl());
return promise.future();
}
}
}
@@ -1,78 +0,0 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.PanBase;
import cn.qaiu.util.URLUtil;
import io.vertx.core.Future;
import io.vertx.core.json.JsonObject;
import io.vertx.uritemplate.UriTemplate;
/**
*
* QQ音乐分享解析
* <a href="https://c6.y.qq.com/base/fcgi-bin/u?__=w3lqEpOHACLO">分享示例</a>
* <a href="https://y.qq.com/n/ryqq/songDetail/000XjcLg0fbRjv?songtype=0">详情页</a>
*/
public class MqqsTool extends PanBase {
public static final String API_URL = "https://u.y.qq.com/cgi-bin/musicu" +
".fcg?-=getplaysongvkey2682247447678878&g_tk=5381&loginUin=956581739&hostUin=0&format=json&inCharset=utf8" +
"&outCharset=utf-8&notice=0&platform=yqq.json&needNewCode=0&data=%7B%22req_0%22%3A%7B%22module%22%3A" +
"%22vkey.GetVkeyServer%22%2C%22method%22%3A%22CgiGetVkey%22%2C%22param%22%3A%7B%22guid%22%3A%222796982635" +
"%22%2C%22songmid%22%3A%5B%22{songmid}%22%5D%2C%22songtype%22%3A%5B1%5D%2C%22uin%22%3A%22956581739%22" +
"%2C%22loginflag%22%3A1%2C%22platform%22%3A%2220%22%7D%7D%2C%22comm%22%3A%7B%22uin%22%3A956581739%2C" +
"%22format%22%3A%22json%22%2C%22ct%22%3A24%2C%22cv%22%3A0%7D%7D";
public MqqsTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
public Future<String> parse() {
String shareUrl = shareLinkInfo.getStandardUrl();
// https://c6.y.qq.com/base/fcgi-bin/u?__=uXXtsB
// String shareUrl = "https://c6.y.qq.com/base/fcgi-bin/u?__=k8gafY6HAQ5Y";
clientNoRedirects.getAbs(shareUrl).send().onSuccess(res -> {
String locationURL = res.headers().get("Location");
String id = URLUtil.from(locationURL).getParam("songmid");
downUrl(id);
}).onFailure(handleFail(shareUrl));
return promise.future();
}
protected void downUrl(String id) {
clientNoRedirects.getAbs(UriTemplate.of(API_URL)).setTemplateParam("songmid", id).send().onSuccess(res2 -> {
JsonObject jsonObject = asJson(res2);
log.debug(jsonObject.encodePrettily());
try {
JsonObject data = jsonObject.getJsonObject("req_0").getJsonObject("data");
String path = data.getJsonArray("midurlinfo").getJsonObject(0).getString("purl");
if (path.isEmpty()) {
fail("暂不支持VIP音乐");
return;
}
String downURL = data.getJsonArray("sip").getString(0)
.replace("http://", "https://") + path;
promise.complete(downURL);
} catch (Exception e) {
fail("获取失败");
}
}).onFailure(handleFail(API_URL.replace("{id}", id)));
}
public static class MqqTool extends MqqsTool{
public MqqTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
@Override
public Future<String> parse() {
downUrl(shareLinkInfo.getShareKey());
return promise.future();
}
}
}
@@ -1,22 +0,0 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.PanBase;
import io.vertx.core.Future;
/**
* 其他网盘解析
*/
public class OtherTool extends PanBase {
private static final String API_URL_PREFIX = "";
public OtherTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
public Future<String> parse() {
// TODO
fail("暂未实现, 敬请期待");
return future();
}
}
@@ -1,48 +0,0 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.PanBase;
import io.vertx.core.Future;
import io.vertx.core.buffer.Buffer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 118网盘解析
*/
public class P118Tool extends PanBase {
private static final String API_URL_PREFIX = "https://qaiu.118pan.com/ajax.php";
// private static final String
public P118Tool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
public Future<String> parse() {
client.postAbs(API_URL_PREFIX)
.putHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
.sendBuffer(Buffer.buffer("action=load_down_addr1&file_id=" + shareLinkInfo.getShareKey()))
.onSuccess(res -> {
System.out.println(res.headers());
Pattern compile = Pattern.compile("href=\"([^\"]+)\"");
Matcher matcher = compile.matcher(res.bodyAsString());
if (matcher.find()) {
System.out.println(matcher.group(1));
complete(matcher.group(1));
} else {
fail();
}
}).onFailure(handleFail(""));
return future();
}
// public static void main(String[] args) {
// String s = new P118Tool(ShareLinkInfo.newBuilder().shareUrl("https://xiguage.118pan.com/b11848261").shareKey(
// "11848261").build()).parseSync();
// System.out.println(s);
// }
}
@@ -1,95 +0,0 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.PanBase;
import cn.qaiu.util.URLUtil;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.net.ProxyOptions;
import io.vertx.ext.web.client.HttpRequest;
import io.vertx.ext.web.client.HttpResponse;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* <a href="https://www.dropbox.com/">dropbox</a>
* Dropbox网盘--不支持大陆地区
*/
public class PdbTool extends PanBase implements IPanTool {
private static final String API_URL =
"https://www.dropbox.com/sharing/fetch_user_content_link";
static final String COOKIE_KEY = "__Host-js_csrf=";
public PdbTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
@Override
public Future<String> parse() {
// https://www.dropbox.com/scl/fi/cwnbms1yn8u6rcatzyta7/emqx-5.0.26-el7-amd64.tar.gz?rlkey=3uoi4bxz5mv93jmlaws0nlol1&e=8&st=fe0lclc2&dl=0
// https://www.dropbox.com/scl/fi/()/Get-Started-with-Dropbox.pdf?rlkey=yrddd6s9gxsq967pmbgtzvfl3&st=2trcc1f3&dl=0
//
clientSession.getAbs(shareLinkInfo.getShareUrl())
.send()
.onSuccess(res->{
List<String> collect =
res.cookies().stream().filter(key -> key.contains(COOKIE_KEY)).toList();
if (collect.isEmpty()) {
fail("cookie未找到");
return;
}
Matcher matcher = Pattern.compile(COOKIE_KEY + "([\\w-]+);").matcher(collect.get(0));
String _t;
if (matcher.find()) {
_t = matcher.group(1);
} else {
fail("cookie未找到");
return;
}
MultiMap headers = MultiMap.caseInsensitiveMultiMap();
headers.set("accept", "*/*");
headers.set("accept-language", "zh-CN,zh;q=0.9");
headers.set("cache-control", "no-cache");
headers.set("dnt", "1");
headers.set("origin", "https://www.dropbox.com");
headers.set("pragma", "no-cache");
headers.set("priority", "u=1, i");
headers.set("referer", shareLinkInfo.getShareUrl());
headers.set("sec-ch-ua", "\"Chromium\";v=\"130\", \"Microsoft Edge\";v=\"130\", \"Not?A_Brand\";v=\"99\"");
headers.set("sec-ch-ua-mobile", "?0");
headers.set("sec-ch-ua-platform", "\"Windows\"");
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 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0");
headers.set("x-dropbox-client-yaps-attribution", "edison_atlasservlet.file_viewer-edison:prod");
headers.set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
try {
URL url = new URL(shareLinkInfo.getShareUrl());
// https://www.dropbox.com/scl/fi/cwnbms1yn8u6rcatzyta7/xxx?rlkey=xx&dl=0
String u0 = URLEncoder.encode((url.getProtocol() + "://" + url.getHost() + url.getPath() + "?rlkey=%s&dl=0")
.formatted(URLUtil.from(shareLinkInfo.getShareUrl()).getParam("rlkey")), StandardCharsets.UTF_8);
clientSession.postAbs(API_URL)
.sendBuffer(Buffer.buffer("is_xhr=true&t=%s&url=%s&origin=PREVIEW_PAGE".formatted(_t, u0)))
.onSuccess(res2 -> {
complete(res2.bodyAsString());
})
.onFailure(handleFail());
} catch (Exception e) {
e.printStackTrace();
}
})
.onFailure(handleFail("请求下载链接失败"));
return future();
}
}
@@ -1,98 +0,0 @@
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;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.net.ProxyOptions;
import io.vertx.ext.web.client.HttpRequest;
import io.vertx.ext.web.client.HttpResponse;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* <a href="https://drive.google.com/">GoogleDrive</a>
* Google Drive文件解析工具.
*/
public class PgdTool extends PanBase implements IPanTool {
private static final String DOWN_URL_TEMPLATE =
"https://drive.usercontent.google.com/download";
private String downloadUrl;
public PgdTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
@Override
public Future<String> parse() {
downloadUrl = DOWN_URL_TEMPLATE + "?id=" + shareLinkInfo.getShareKey() + "&export=download";
if (shareLinkInfo.getOtherParam().containsKey("proxy")) {
// if (shareLinkInfo.getOtherParam().containsKey("bypassCheck")
// && "true".equalsIgnoreCase(shareLinkInfo.getOtherParam().get("bypassCheck").toString())) {
// 发起请求但不真正下载文件, 只检查响应头
client.headAbs(downloadUrl).send()
.onSuccess(this::handleResponse)
.onFailure(handleFail("请求下载链接失败"));
return future();
}
complete(downloadUrl);
return future();
}
/**
* 处理下载链接的响应.
*/
private void handleResponse(HttpResponse<Buffer> response) {
String contentType = response.getHeader("Content-Type");
if (contentType != null && !contentType.contains("text/html")) {
complete(downloadUrl);
} else {
// 如果不是文件流类型,从 HTML 中解析出真实下载链接
client.getAbs(downloadUrl)
.send()
.onSuccess(res0 -> {
parseHtmlForRealLink(res0.bodyAsString());
})
.onFailure(handleFail("请求下载链接失败"));
}
}
/**
* 从HTML内容中解析真实下载链接.
*/
private void parseHtmlForRealLink(String html) {
// 使用正则表达式匹配 id、export、confirm、uuid、at 等参数
String id = extractHiddenInputValue(html, "id");
String confirm = extractHiddenInputValue(html, "confirm");
String uuid = extractHiddenInputValue(html, "uuid");
String at = extractHiddenInputValue(html, "at");
if (id != null && confirm != null && uuid != null) {
String realDownloadLink = DOWN_URL_TEMPLATE +
"?id=" + id +
"&export=download" +
"&confirm=" + confirm +
"&uuid=" + uuid;
if (at != null) {
realDownloadLink += ( "&at=" + at);
}
complete(realDownloadLink);
} else {
fail("无法找到完整的下载链接参数");
}
}
/**
* 辅助方法: 从HTML中提取指定name的input隐藏字段的value
*/
private String extractHiddenInputValue(String html, String name) {
Pattern pattern = Pattern.compile("<input[^>]*name=\"" + name + "\"[^>]*value=\"([^\"]*)\"");
Matcher matcher = pattern.matcher(html);
return matcher.find() ? matcher.group(1) : null;
}
}
@@ -1,58 +0,0 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.PanBase;
import io.vertx.core.Future;
import io.vertx.core.json.JsonObject;
/**
* <a href="https://www.icloud.com.cn/">iCloud云盘(pic)</a>
*/
public class PicTool extends PanBase {
private static final String api = "https://ckdatabasews.icloud.com.cn/database/1/com.apple.cloudkit/production/public/records/resolve";
public PicTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
public Future<String> parse() {
// {"shortGUIDs":[{"value":"xxx"}]}
JsonObject jsonObject =
new JsonObject("{\"shortGUIDs\":[{\"value\":\"%s\"}]}".formatted(shareLinkInfo.getShareKey()));
client.postAbs(api).sendJsonObject(jsonObject).onSuccess(res -> {
// results->rootRecord->fields->fileContent->value->downloadURL // ${f}->fileName
// fileName: results->share->fields->cloudkit.title->value + "." + results->rootRecord->fields->extension->value
JsonObject json = asJson(res);
try {
JsonObject result = json.getJsonArray("results").getJsonObject(0);
JsonObject fileInfo = result
.getJsonObject("rootRecord")
.getJsonObject("fields");
String downURL = fileInfo
.getJsonObject("fileContent")
.getJsonObject("value")
.getString("downloadURL");
String extension = fileInfo
.getJsonObject("extension")
.getString("value");
String fileTitle = result
.getJsonObject("share")
.getJsonObject("fields")
.getJsonObject("cloudkit.title")
.getString("value");
complete(downURL.replace("${f}", fileTitle + "." + extension));
} catch (Exception e) {
fail(e, "json解析失败");
}
}).onFailure(handleFail());
return promise.future();
}
}
@@ -1,67 +0,0 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.PanBase;
import io.vertx.core.Future;
import io.vertx.core.json.JsonObject;
import io.vertx.uritemplate.UriTemplate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* <a href="https://onedrive.live.com/">onedrive分享(od)</a>
*/
public class PodTool extends PanBase {
/*
* https://1drv.ms/w/s!Alg0feQmCv2rnRFd60DQOmMa-Oh_?e=buaRtp --302->
* https://api.onedrive.com/v1.0/drives/abfd0a26e47d3458/items/ABFD0A26E47D3458!3729?authkey=!AF3rQNA6Yxr46H8
* https://onedrive.live.com/redir?resid=(?<cid>)!(?<cid2>)&authkey=(?<authkey>)&e=hV98W1
* cid: abfd0a26e47d3458, cid2: ABFD0A26E47D3458!3729 authkey: !AF3rQNA6Yxr46H8
* -> @content.downloadUrl
*/
private static final String api = "https://api.onedrive.com/v1.0/drives/{cid}/items/{cid20}?authkey={authkey}";
private static final Pattern redirectUrlRegex =
Pattern.compile("=(?<cid>.+)!(?<cid2>.+)&authkey=(?<authkey>.+)&e=(?<e>.+)");
public PodTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
public Future<String> parse() {
clientNoRedirects.getAbs(shareLinkInfo.getShareUrl()).send().onSuccess(r0 -> {
String location = r0.getHeader("Location");
Matcher matcher = redirectUrlRegex.matcher(location);
if (matcher.find()) {
var cid= matcher.group("cid");
var cid2= matcher.group("cid2");
var authkey= matcher.group("authkey");
client.getAbs(UriTemplate.of(api))
.setTemplateParam("cid", cid)
.setTemplateParam("cid20", cid + "!" + cid2)
.setTemplateParam("authkey", authkey).send()
.onSuccess(res -> {
JsonObject jsonObject = asJson(res);
// System.out.println(jsonObject);
complete(jsonObject.getString("@content.downloadUrl"));
})
.onFailure(handleFail());
}
}).onFailure(handleFail());
return promise.future();
}
//https://onedrive.live.com/redir?resid=ABFD0A26E47D3458!4699&e=OggA4s&migratedtospo=true&redeem=aHR0cHM6Ly8xZHJ2Lm1zL3UvcyFBbGcwZmVRbUN2MnJwRnZ1NDQ0aGc1eVZxRGNLP2U9T2dnQTRz
// public static void main(String[] args) {
// Matcher matcher = redirectUrlRegex.matcher("https://onedrive.live.com/redir?resid=ABFD0A26E47D3458!4698" +
// "&authkey=!ACpvXghP5xhG_cg&e=hV98W1");
// if (matcher.find()) {
// System.out.println(matcher.group("cid"));
// System.out.println(matcher.group("cid2"));
// System.out.println(matcher.group("authkey"));
// }
// }
}
@@ -1,51 +0,0 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.PanBase;
import io.vertx.core.Future;
import io.vertx.uritemplate.UriTemplate;
/**
* 微雨云
*/
public class PvyyTool extends PanBase {
private static final String API_URL_PREFIX1 = "https://www.vyuyun.com/apiv1/share/file/{key}?password={pwd}";
private static final String API_URL_PREFIX2 = "https://www.vyuyun.com/apiv1/share/getShareDownUrl/{key}/{id}?password={pwd}";
public PvyyTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
public Future<String> parse() {
//
client.getAbs(UriTemplate.of(API_URL_PREFIX1))
.setTemplateParam("key", shareLinkInfo.getShareKey())
.setTemplateParam("pwd", shareLinkInfo.getSharePassword())
.putHeader("referer", "https://www.vyuyun.com")
.send().onSuccess(res -> {
try {
String id = asJson(res).getJsonObject("data").getJsonObject("data").getString("id");
client.getAbs(UriTemplate.of(API_URL_PREFIX2))
.setTemplateParam("key", shareLinkInfo.getShareKey())
.setTemplateParam("pwd", shareLinkInfo.getSharePassword())
.setTemplateParam("id", id)
.putHeader("referer", "https://www.vyuyun.com")
.send().onSuccess(res2 -> {
try {
// data->downInfo->url
String url =
asJson(res2).getJsonObject("data").getJsonObject("downInfo").getString("url");
complete(url);
} catch (Exception ignored) {
fail(asJson(res2).encodePrettily());
}
});
} catch (Exception ignored) {
fail(asJson(res).encodePrettily());
}
});
return future();
}
}
@@ -1,47 +1,43 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.PanBase;
import cn.qaiu.util.StringUtils;
import io.netty.handler.codec.http.QueryStringDecoder;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.PanBase;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import io.vertx.ext.web.client.WebClient;
/**
* <a href="https://wx.mail.qq.com/">QQ邮箱</a>
*/
public class QQTool extends PanBase {
public class QQTool extends PanBase implements IPanTool {
public static final String REDIRECT_URL_TEMP = "https://iwx.mail.qq.com/ftn/download?func=4&key={key}&code={code}";
public static final String SHARE_URL_PREFIX = "wx.mail.qq.com/ftn/download?";
public QQTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
public QQTool(String key, String pwd) {
super(key, pwd);
}
@SuppressWarnings("unchecked")
public Future<String> parse() {
// QQ mail 直接替换为302链接 无需请求
QueryStringDecoder queryStringDecoder = new QueryStringDecoder(shareLinkInfo.getShareUrl(), StandardCharsets.UTF_8);
Map<String, List<String>> 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)));
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 {
fail("key 不合法");
throw new UnsupportedOperationException("未知分享类型");
}
}
// 通过请求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 " +
@@ -54,7 +50,7 @@ public class QQTool extends PanBase {
headers.set("sec-ch-ua-mobile", "sec-ch-ua-mobile");
// 获取下载中转站页面
client.getAbs(key).putHeaders(headers).send().onSuccess(res -> {
httpClient.getAbs(this.key).putHeaders(headers).send().onSuccess(res -> {
if (res.statusCode() == 200) {
String html = res.bodyAsString();
@@ -65,10 +61,10 @@ public class QQTool extends PanBase {
if (filename != null && filesize != null && fileurl != null) {
// 设置所需HTTP头部
headers.set("Referer", "https://" + StringUtils.StringCutNot(key, "https://", "/") + "/");
headers.set("Referer", "https://" + StringUtils.StringCutNot(this.key, "https://", "/") + "/");
headers.set("Host", StringUtils.StringCutNot(fileurl, "https://", "/"));
res.headers().forEach((k, v) -> {
if (k.equalsIgnoreCase("set-cookie")) {
if (k.toLowerCase().equals("set-cookie")) {
headers.set("Cookie", "mail5k=" + StringUtils.StringCutNot(v, "mail5k=", ";") + ";");
}
});
@@ -86,7 +82,9 @@ public class QQTool extends PanBase {
} else {
this.fail("HTTP状态不正确,可能是分享链接的方式已更新");
}
}).onFailure(this.handleFail(key));
}).onFailure(this.handleFail(this.key));
return promise.future();
}
}
@@ -1,22 +1,19 @@
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;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
public class QkTool extends PanBase {
public class QkTool extends PanBase implements IPanTool {
public QkTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
public QkTool(String key, String pwd) {
super(key, pwd);
}
public Future<String> 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()
@@ -36,6 +33,14 @@ public class QkTool extends PanBase {
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) -> {
});
}
}
@@ -0,0 +1,82 @@
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<String> 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();
}
}
@@ -1,6 +1,11 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.util.StringUtils;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.PanBase;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
@@ -8,27 +13,34 @@ 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;
/**
* <a href="https://www.wenshushu.cn/">文叔叔</a>
*/
public class WsTool extends PanBase {
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(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
public WsTool(String key, String pwd) {
super(key, pwd);
}
@SuppressWarnings("unchecked")
public Future<String> 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, " +
@@ -49,23 +61,23 @@ public class WsTool extends PanBase {
if (res.statusCode() == 200) {
try {
// 设置匿名登录token
String token = asJson(res).getJsonObject("data").getString("token");
String token = res.bodyAsJsonObject().getJsonObject("data").getString("token");
headers.set("X-Token", token);
// 获取文件夹信息
httpClient.postAbs(SHARE_URL_API + "task/mgrtask").putHeaders(headers)
.sendJsonObject(JsonObject.of(
"tid", key,
"password", pwd
"tid", StringUtils.StringCutNot(key, this.key.startsWith(SHARE_URL_PREFIX) ? SHARE_URL_PREFIX : SHARE_URL_PREFIX2),
"password", ""
)).onSuccess(res2 -> {
if (res2.statusCode() == 200) {
try {
// 获取文件夹信息
String filetime = asJson(res2).getJsonObject("data").getString("expire"); // 文件夹剩余时间
String filesize = asJson(res2).getJsonObject("data").getString("file_size"); // 文件夹大小
String filepid = asJson(res2).getJsonObject("data").getString("ufileid"); // 文件夹pid
String filebid = asJson(res2).getJsonObject("data").getString("boxid"); // 文件夹bid
String filetime = res2.bodyAsJsonObject().getJsonObject("data").getString("expire"); // 文件夹剩余时间
String filesize = res2.bodyAsJsonObject().getJsonObject("data").getString("file_size"); // 文件夹大小
String filepid = res2.bodyAsJsonObject().getJsonObject("data").getString("ufileid"); // 文件夹pid
String filebid = res2.bodyAsJsonObject().getJsonObject("data").getString("boxid"); // 文件夹bid
// 调试输出文件夹信息
System.out.println("文件夹期限: " + filetime);
@@ -92,9 +104,9 @@ public class WsTool extends PanBase {
if (res3.statusCode() == 200) {
try {
// 获取文件信息
String filename = asJson(res3).getJsonObject("data")
String filename = res3.bodyAsJsonObject().getJsonObject("data")
.getJsonArray("fileList").getJsonObject(0).getString("fname"); // 文件名称
String filefid = asJson(res3).getJsonObject("data")
String filefid = res3.bodyAsJsonObject().getJsonObject("data")
.getJsonArray("fileList").getJsonObject(0).getString("fid"); // 文件fid
// 调试输出文件信息
@@ -112,14 +124,21 @@ public class WsTool extends PanBase {
if (res4.statusCode() == 200) {
try {
// 获取直链
String fileurl = asJson(res4).getJsonObject("data").getString("url");
String fileurl = res4.bodyAsJsonObject().getJsonObject("data").getString("url");
// 调试输出文件直链
System.out.println("文件直链: " + fileurl);
if (!fileurl.equals("")) {
promise.complete(URLDecoder.decode(fileurl, StandardCharsets.UTF_8));
} else {
if (!fileurl.equals(""))
{
try {
promise.complete(URLDecoder.decode(fileurl, "UTF-8"));
} catch (UnsupportedEncodingException e) {
promise.complete(fileurl);
}
}
else
{
this.fail("文件已失效");
}
@@ -148,7 +167,7 @@ public class WsTool extends PanBase {
this.fail("HTTP状态不正确,可能是分享链接的方式已更新");
}
}).onFailure(this.handleFail(key));
}).onFailure(this.handleFail(this.key));
} catch (DecodeException | NullPointerException e) {
this.fail("token获取失败,可能是分享链接的方式已更新");
@@ -157,7 +176,7 @@ public class WsTool extends PanBase {
this.fail("HTTP状态不正确,可能是分享链接的方式已更新");
}
}).onFailure(this.handleFail(key));
}).onFailure(this.handleFail(this.key));
return promise.future();
}
@@ -1,6 +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;
import cn.qaiu.util.JsExecUtils;
@@ -20,7 +20,7 @@ import java.util.regex.Pattern;
/**
* 123网盘
*/
public class YeTool extends PanBase {
public class YeTool extends PanBase implements IPanTool {
public static final String SHARE_URL_PREFIX = "https://www.123pan.com/s/";
public static final String FIRST_REQUEST_URL = SHARE_URL_PREFIX + "{key}.html";
@@ -30,29 +30,22 @@ public class YeTool extends PanBase {
"&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(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
public YeTool(String key, String pwd) {
super(key, pwd);
}
public Future<String> parse() {
final String dataKey = shareLinkInfo.getShareKey();
final String pwd = shareLinkInfo.getSharePassword();
String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key);
client.getAbs(UriTemplate.of(FIRST_REQUEST_URL)).setTemplateParam("key", dataKey).send().onSuccess(res -> {
String html = res.bodyAsString();
// 判断分享是否已经失效
if (html.contains("分享链接已失效")) {
fail("该分享已失效({})已失效", shareLinkInfo.getShareUrl());
return;
}
Pattern compile = Pattern.compile("window.g_initialProps\\s*=\\s*(.*);");
Matcher matcher = compile.matcher(html);
if (!matcher.find()) {
fail("该分享({})文件信息找不到, 可能分享已失效", shareLinkInfo.getShareUrl());
fail(html + "\n Ye: " + dataKey + " 正则匹配失败");
return;
}
String fileInfoString = matcher.group(1);
@@ -65,7 +58,6 @@ public class YeTool extends PanBase {
return;
}
String shareKey = resJson.getJsonObject("data").getString("ShareKey");
if (resListJson == null || resListJson.getInteger("code") != 0) {
// 加密分享
if (StringUtils.isNotEmpty(pwd)) {
@@ -76,7 +68,7 @@ public class YeTool extends PanBase {
.putHeader("Platform", "web")
.putHeader("App-Version", "3")
.send().onSuccess(res2 -> {
JsonObject infoJson = asJson(res2);
JsonObject infoJson = res2.bodyAsJsonObject();
if (infoJson.getInteger("code") != 0) {
fail("{} 状态码异常 {}", dataKey, infoJson);
return;
@@ -127,7 +119,7 @@ public class YeTool extends PanBase {
.putHeader("Platform", "web")
.putHeader("App-Version", "3")
.sendJsonObject(jsonObject).onSuccess(res2 -> {
JsonObject downURLJson = asJson(res2);
JsonObject downURLJson = res2.bodyAsJsonObject();
try {
if (downURLJson.getInteger("code") != 0) {
@@ -147,7 +139,7 @@ public class YeTool extends PanBase {
// 获取直链
client.getAbs(downUrl2).send().onSuccess(res3 -> {
JsonObject res3Json = asJson(res3);
JsonObject res3Json = res3.bodyAsJsonObject();
try {
if (res3Json.getInteger("code") != 0) {
fail("Ye: downUrl2返回值异常->" + res3Json);
@@ -1,22 +1,19 @@
package cn.qaiu.util;
import io.vertx.core.net.impl.URIDecoder;
import org.apache.commons.lang3.StringUtils;
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Date;
import java.util.HexFormat;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* AES加解密工具类
@@ -42,12 +39,6 @@ public class AESUtils {
public static final String CIPHER_AES0;
public static final String CIPHER_AES0_IZ;
public static final String MG_PKEY2 = "D8jg+H2iNX94zvHhRLnSM3oy59dH2QQjxQ0GgKJSL+mJclbCcItjV3AmkPY6WcbV4hNQk5+hN2J1eTrxPQqF4p28e3FTsGRCXVN80CLS+XqpFNY/9xuyf2bvbeq5JJU1IBCXgSZmEo8zu0/VGS3YNeDsHtjg92QSrRY8i4A+shihZBSz0/0KOL1VPd/K4tAYvsI9YjVFOI7z9mJJ8Ek8rVUplurJyGkjevRfvReN7xQ67PR+yZovk72yTZKlHDz5jVpLGLOy2iwTTSTbTvtnOi9TSE6sSPtRHv16cxZYZQY=";
public static final String MG_PKEY;
public static final String MG_KEY = "4ea5c508a6566e76240543f8feb06fd457777be39549c4016436afda65d2330e";
/**
* 秘钥长度
*/
@@ -72,7 +63,6 @@ public class AESUtils {
try {
CIPHER_AES0 = decryptByBase64AES(CIPHER_AES2, CIPHER_AES);
CIPHER_AES0_IZ = decryptByBase64AES(CIPHER_AES2_IZ, CIPHER_AES);
MG_PKEY = decryptByBase64AES(MG_PKEY2, CIPHER_AES2);
} catch (IllegalBlockSizeException | BadPaddingException | NoSuchPaddingException | NoSuchAlgorithmException |
InvalidKeyException e) {
throw new RuntimeException(e);
@@ -256,7 +246,6 @@ public class AESUtils {
return idEncrypt0(str, array, 2, 2);
}
// ================================蓝奏优享版Id解密========================================== //
public static int idEncryptIz(String str) {
// idEncrypt(e) {
@@ -339,73 +328,4 @@ public class AESUtils {
return _0x53928f + "-" + _0x430930 + "-" + _0x49ec94;
}
public static String encrypt(String str, String pwd) {
if (pwd == null || pwd.length() <= 0) {
throw new IllegalArgumentException("Please enter a password with which to encrypt the message.");
}
// 生成 prand 值
StringBuilder prand = new StringBuilder();
for (int i = 0; i < pwd.length(); i++) {
prand.append((int) pwd.charAt(i));
}
// 计算 sPos, mult, incr, modu
int sPos = prand.length() / 5;
long mult = Long.parseLong(prand.substring(sPos, sPos + 1) +
prand.substring(sPos * 2, sPos * 2 + 1) +
prand.substring(sPos * 3, sPos * 3 + 1) +
prand.substring(sPos * 4, sPos * 4 + 1) +
prand.substring(sPos * 5, sPos * 5 + 1));
int incr = (int) Math.ceil(pwd.length() / 2.0);
long modu = (long) Math.pow(2, 31) - 1;
if (mult < 2) {
throw new IllegalArgumentException("Algorithm cannot find a suitable hash. Please choose a different password.");
}
// 生成 salt 并加到 prand 上
long salt = Math.round(Math.random() * 1000000000) % 100000000;
prand.append(salt);
// 使用 BigInteger 处理超过 Long 范围的 prand 值
BigInteger prandValue = new BigInteger(prand.toString());
while (prandValue.toString().length() > 10) {
prandValue = prandValue.divide(BigInteger.TEN).add(prandValue.remainder(BigInteger.TEN));
}
// 将 prand 转换为 long 进行后续处理
prandValue = prandValue.multiply(BigInteger.valueOf(mult)).add(BigInteger.valueOf(incr)).mod(BigInteger.valueOf(modu));
// 加密过程
StringBuilder encStr = new StringBuilder();
for (int i = 0; i < str.length(); i++) {
// 将字符和异或的结果强制转换为 int
int encChr = (int) (str.charAt(i) ^ (int) ((prandValue.doubleValue() / modu) * 255));
String hexStr = Integer.toHexString(encChr);
if (hexStr.length() < 2) {
encStr.append("0");
}
encStr.append(hexStr);
prandValue = prandValue.multiply(BigInteger.valueOf(mult)).add(BigInteger.valueOf(incr)).mod(BigInteger.valueOf(modu));
}
// 将 salt 转换为 16 进制并追加到加密结果
String saltHex = Long.toHexString(salt);
while (saltHex.length() < 8) {
saltHex = "0" + saltHex;
}
encStr.append(saltHex);
return encStr.toString();
}
public static void main(String[] args) {
// 示例
String encrypted = encrypt("HelloWorld", "password123");
System.out.println("Encrypted String: " + encrypted);
}
}
@@ -1,80 +0,0 @@
package cn.qaiu.util;
import io.vertx.core.MultiMap;
import io.vertx.core.Vertx;
import io.vertx.core.http.impl.headers.HeadersMultiMap;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.client.WebClientSession;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.ArrayList;
import java.util.List;
public class IpExtractor {
public static void main(String[] args) throws InterruptedException {
// 创建请求头Map
MultiMap headers = new HeadersMultiMap();
headers.add("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");
headers.add("accept-language", "zh-CN,zh;q=0.9,en;q=0.8");
headers.add("cache-control", "no-cache");
headers.add("dnt", "1");
headers.add("origin", "https://ip.ihuan.me");
headers.add("pragma", "no-cache");
headers.add("priority", "u=0, i");
headers.add("referer", "https://ip.ihuan.me/ti.html");
headers.add("sec-ch-ua", "\"Google Chrome\";v=\"129\", \"Not=A?Brand\";v=\"8\", \"Chromium\";v=\"129\"");
headers.add("sec-ch-ua-mobile", "?0");
headers.add("sec-ch-ua-platform", "\"Windows\"");
headers.add("sec-fetch-dest", "document");
headers.add("sec-fetch-mode", "navigate");
headers.add("sec-fetch-site", "same-origin");
headers.add("sec-fetch-user", "?1");
headers.add("upgrade-insecure-requests", "1");
headers.add("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36");
headers.add("Content-Type", "application/x-www-form-urlencoded");
WebClient client = WebClient.create(Vertx.vertx());
WebClientSession webClientSession = WebClientSession.create(client);
webClientSession.getAbs("https://ip.ihuan.me").putHeaders(headers).send().onSuccess(res->{
System.out.println(res.toString());
webClientSession.getAbs("https://ip.ihuan.me").putHeaders(headers).send().onSuccess(res2->{
System.out.println(res2.toString());
});
});
//
// String htmlContent = "<div class=\"panel-heading\">提取结果</div>\n" +
// "<div class=\"panel-body\">111.1.27.85:80<br>42.63.65.46:80<br>118.195.242.20:8080<br>42.63.65.119:80<br>117.50.108.90:7890<br>116.62.50.250:7890<br>114.231.8.177:8089<br>190.43.92.90:999<br>221.178.85.68:8080<br><br>61.160.202.86:80<br>42.63.65.86:80<br>42.63.65.7:80<br>42.63.65.41:80<br>159.226.227.119:80<br>61.160.202.52:80<br>42.63.65.15:80<br>112.17.16.204:80<br>61.160.202.53:80<br>42.63.65.9:80<br>42.63.65.60:80<br>42.63.65.18:80<br>203.190.115.177:8071<br>42.63.65.38:80<br>42.63.65.31:80<br>91.185.3.126:8080<br>139.9.119.20:80<br>1.15.47.213:443<br>183.164.243.108:8089<br>165.225.208.177:80<br>194.163.132.232:3128<br>91.235.220.122:80<br>39.100.120.200:7890<br>141.147.33.121:80<br>183.164.243.138:8089<br>104.129.205.94:54321<br>117.160.250.138:81<br>180.120.213.208:8089<br>61.130.9.37:443<br>182.34.18.206:9999<br>117.86.12.150:8089<br>27.192.173.108:9000<br>183.164.243.11:8089<br>114.231.41.205:8089<br>103.104.233.78:8080<br>183.164.243.174:8089<br>36.6.144.230:8089<br>111.224.213.239:8089<br>182.92.73.106:80<br>36.6.145.81:8089<br>117.69.232.125:8089<br>36.6.144.64:8089<br>117.57.92.20:8089<br>47.100.69.29:8888<br>117.57.93.246:8089<br>120.234.203.171:9002<br>114.231.46.231:8089<br>183.164.242.199:8089<br>117.69.237.179:8089<br>182.44.32.239:7890<br>47.100.91.57:8080<br>117.69.236.127:8089<br>114.231.8.18:8089<br>117.69.232.183:8089<br>117.69.237.29:8089<br>183.164.242.67:8089<br>183.164.242.35:8089<br>183.164.243.71:8089<br>113.223.214.155:8089<br>36.6.145.132:8089<br>182.106.220.252:9091<br>113.223.212.176:8089<br>62.152.53.186:8909<br>117.57.92.16:8089<br>183.164.243.186:8089<br>36.6.144.210:8089<br>183.164.242.189:8089<br>213.178.39.170:8080<br>121.52.145.163:8080<br>36.6.144.240:8089<br>60.188.5.234:80<br>113.223.213.48:8089<br>183.164.243.149:8089<br>200.58.87.195:8080<br>36.6.144.153:8089<br>36.6.144.67:8089<br>36.6.145.182:8089<br>117.57.93.226:8089<br>42.112.24.127:8888<br>43.138.20.156:80<br>117.57.92.79:8089<br>65.109.111.238:3128<br>183.166.137.201:41122<br>113.223.213.150:8089<br>36.6.145.154:8089<br>185.5.209.101:80<br>36.6.144.17:8089<br>114.231.8.244:8089<br>117.69.237.24:8089<br>117.69.236.232:8089<br>117.69.236.127:8089<br>114.231.8.18:8089<br>117.69.232.183:8089<br>117.69.237.29:8089<br>183.164.242.67:8089<br>183.164.242.35:8089<br>183.164.243.71:8089<br>113.223.214.155:8089<br>36.6.145.132:8089<br>182.106.220.252:9091<br>113.223.212.176:8089<br>62.152.53.186:8909<br>117.57.92.16:8089<br>183.164.243.186:8089<br>36.6.144.210:8089<br>183.164.242.189:8089<br>213.178.39.170:8080<br>121.52.145.163:8080<br>36.6.144.240:8089<br>60.188.5.234:80<br>113.223.213.48:8089<br>183.164.243.149:8089<br>200.58.87.195:8080<br>36.6.144.153:8089<br>36.6.144.67:8089<br>36.6.145.182:8089<br>117.57.93.226:8089<br>42.112.24.127:8888<br>43.138.20.156:80<br>117.57.92.79:8089<br>65.109.111.238:3128<br>183.166.137.201:41122<br>113.223.213.150:8089<br>36.6.145.154:8089<br>185.5.209.101:80<br>36.6.144.17:8089<br>114.231.8.244:8089<br>117.69.237.24:8089<br>117.69.236.232:8089</div>";
//
// // 正则表达式匹配提取结果关键字下面的IP地址
// Pattern pattern = Pattern.compile("<div class=\"panel-heading\">提取结果</div>\\s*<div class=\"panel-body\">([\\s\\S]*?)(?=</div>)", Pattern.DOTALL);
// Matcher matcher = pattern.matcher(htmlContent);
//
// if (matcher.find()) {
// String ipText = matcher.group(1); // 获取匹配的IP地址部分
// Pattern ipPattern = Pattern.compile("(?:[0-9]{1,3}\\.){3}[0-9]{1,3}(?::\\d+)?");
// Matcher ipMatcher = ipPattern.matcher(ipText);
//
// List<String> ips = new ArrayList<>();
// while (ipMatcher.find()) {
// ips.add(ipMatcher.group());
// }
//
// System.out.println("提取到的IP地址:");
// for (String ip : ips) {
//// System.out.println(ip);
// }
// } else {
// System.out.println("没有找到匹配的IP地址");
// }
//
// TimeUnit.SECONDS.sleep(1000);
}
}
@@ -149,909 +149,4 @@ public interface JsContent {
""";
String kwSignString = """
function encrypt(str, pwd) {
if (pwd == null || pwd.length <= 0) {
return null;
}
var prand = "";
for (var i = 0; i < pwd.length; i++) {
prand += pwd.charCodeAt(i).toString();
}
var sPos = Math.floor(prand.length / 5);
var mult = parseInt(prand.charAt(sPos) + prand.charAt(sPos * 2) + prand.charAt(sPos * 3) + prand.charAt(sPos * 4) + prand.charAt(sPos * 5));
var incr = Math.ceil(pwd.length / 2);
var modu = Math.pow(2, 31) - 1;
if (mult < 2) {
return null;
}
var salt = Math.round(Math.random() * 1000000000) % 100000000;
prand += salt;
var flag = 1;
while (prand.length > 10) {
prand = (parseInt(prand.substring(0, 10)) + (flag ?parseFloat(prand.substring(10, prand.length)) : parseInt(prand.substring(10, prand.length)) )).toString();
flag = 0;
}
prand = (mult * prand + incr) % modu;
var enc_chr = "";
var enc_str = "";
for (var i = 0; i < str.length; i++) {
enc_chr = parseInt(str.charCodeAt(i) ^ Math.floor((prand / modu) * 255));
if (enc_chr < 16) {
enc_str += "0" + enc_chr.toString(16);
} else enc_str += enc_chr.toString(16);
prand = (mult * prand + incr) % modu;
}
salt = salt.toString(16);
while (salt.length < 8) salt = "0" + salt;
enc_str += salt;
return enc_str;
}
""";
String mgJS = """
var XO, qO = {
exports: {}
}, JO = {
exports: {}
};
var qb = globalThis = this
JO.exports = (XO = XO || function (e, t) {
var n;
if ("undefined" != typeof window && window.crypto && (n = window.crypto),
"undefined" != typeof self && self.crypto && (n = self.crypto),
"undefined" != typeof globalThis && globalThis.crypto && (n = globalThis.crypto),
!n && "undefined" != typeof window && window.msCrypto && (n = window.msCrypto),
!n && void 0 !== qb && qb.crypto && (n = qb.crypto),
!n)
try {
n = require("crypto")
} catch (g) {
}
var r = function () {
return Math.floor(Math.random() * 0xFFFFFFFF + 0)
}
, o = Object.create || function () {
function e() {
}
return function (t) {
var n;
return e.prototype = t,
n = new e,
e.prototype = null,
n
}
}()
, a = {}
, i = a.lib = {}
, l = i.Base = {
extend: function (e) {
var t = o(this);
return e && t.mixIn(e),
t.hasOwnProperty("init") && this.init !== t.init || (t.init = function () {
t.$super.init.apply(this, arguments)
}
),
t.init.prototype = t,
t.$super = this,
t
},
create: function () {
var e = this.extend();
return e.init.apply(e, arguments),
e
},
init: function () {
},
mixIn: function (e) {
for (var t in e)
e.hasOwnProperty(t) && (this[t] = e[t]);
e.hasOwnProperty("toString") && (this.toString = e.toString)
},
clone: function () {
return this.init.prototype.extend(this)
}
}
, s = i.WordArray = l.extend({
init: function (e, n) {
e = this.words = e || [],
this.sigBytes = n != t ? n : 4 * e.length
},
toString: function (e) {
return (e || c).stringify(this)
},
concat: function (e) {
var t = this.words
, n = e.words
, r = this.sigBytes
, o = e.sigBytes;
if (this.clamp(),
r % 4)
for (var a = 0; a < o; a++) {
var i = n[a >>> 2] >>> 24 - a % 4 * 8 & 255;
t[r + a >>> 2] |= i << 24 - (r + a) % 4 * 8
}
else
for (var l = 0; l < o; l += 4)
t[r + l >>> 2] = n[l >>> 2];
return this.sigBytes += o,
this
},
clamp: function () {
var t = this.words
, n = this.sigBytes;
t[n >>> 2] &= 4294967295 << 32 - n % 4 * 8,
t.length = e.ceil(n / 4)
},
clone: function () {
var e = l.clone.call(this);
return e.words = this.words.slice(0),
e
},
random: function (e) {
for (var t = [], n = 0; n < e; n += 4)
t.push(r());
return new s.init(t, e)
}
})
, u = a.enc = {}
, c = u.Hex = {
stringify: function (e) {
for (var t = e.words, n = e.sigBytes, r = [], o = 0; o < n; o++) {
var a = t[o >>> 2] >>> 24 - o % 4 * 8 & 255;
r.push((a >>> 4).toString(16)),
r.push((15 & a).toString(16))
}
return r.join("")
},
parse: function (e) {
for (var t = e.length, n = [], r = 0; r < t; r += 2)
n[r >>> 3] |= parseInt(e.substr(r, 2), 16) << 24 - r % 8 * 4;
return new s.init(n, t / 2)
}
}
, d = u.Latin1 = {
stringify: function (e) {
for (var t = e.words, n = e.sigBytes, r = [], o = 0; o < n; o++) {
var a = t[o >>> 2] >>> 24 - o % 4 * 8 & 255;
r.push(String.fromCharCode(a))
}
return r.join("")
},
parse: function (e) {
for (var t = e.length, n = [], r = 0; r < t; r++)
n[r >>> 2] |= (255 & e.charCodeAt(r)) << 24 - r % 4 * 8;
return new s.init(n, t)
}
}
, p = u.Utf8 = {
stringify: function (e) {
try {
return decodeURIComponent(escape(d.stringify(e)))
} catch (RE) {
throw new Error("Malformed UTF-8 data")
}
},
parse: function (e) {
return d.parse(unescape(encodeURIComponent(e)))
}
}
, f = i.BufferedBlockAlgorithm = l.extend({
reset: function () {
this._data = new s.init,
this._nDataBytes = 0
},
_append: function (e) {
"string" == typeof e && (e = p.parse(e)),
this._data.concat(e),
this._nDataBytes += e.sigBytes
},
_process: function (t) {
var n, r = this._data, o = r.words, a = r.sigBytes, i = this.blockSize, l = a / (4 * i),
u = (l = t ? e.ceil(l) : e.max((0 | l) - this._minBufferSize, 0)) * i, c = e.min(4 * u, a);
if (u) {
for (var d = 0; d < u; d += i)
this._doProcessBlock(o, d);
n = o.splice(0, u),
r.sigBytes -= c
}
return new s.init(n, c)
},
clone: function () {
var e = l.clone.call(this);
return e._data = this._data.clone(),
e
},
_minBufferSize: 0
});
i.Hasher = f.extend({
cfg: l.extend(),
init: function (e) {
this.cfg = this.cfg.extend(e),
this.reset()
},
reset: function () {
f.reset.call(this),
this._doReset()
},
update: function (e) {
return this._append(e),
this._process(),
this
},
finalize: function (e) {
return e && this._append(e),
this._doFinalize()
},
blockSize: 16,
_createHelper: function (e) {
return function (t, n) {
return new e.init(n).finalize(t)
}
},
_createHmacHelper: function (e) {
return function (t, n) {
return new h.HMAC.init(e, n).finalize(t)
}
}
});
var h = a.algo = {};
return a
}(Math), XO);
var QO = {
exports: {}
};
QO.exports = function (e) {
return r = (n = e).lib,
o = r.Base,
a = r.WordArray,
(i = n.x64 = {}).Word = o.extend({
init: function (e, t) {
this.high = e,
this.low = t
}
}),
i.WordArray = o.extend({
init: function (e, n) {
e = this.words = e || [],
this.sigBytes = n != t ? n : 8 * e.length
},
toX32: function () {
for (var e = this.words, t = e.length, n = [], r = 0; r < t; r++) {
var o = e[r];
n.push(o.high),
n.push(o.low)
}
return a.create(n, this.sigBytes)
},
clone: function () {
for (var e = o.clone.call(this), t = e.words = this.words.slice(0), n = t.length, r = 0; r < n; r++)
t[r] = t[r].clone();
return e
}
}),
e;
var t, n, r, o, a, i
}(JO.exports);
var nM = {
exports: {}
};
nM.exports = function (e) {
return function () {
var t = e
, n = t.lib.WordArray;
function r(e, t, r) {
for (var o = [], a = 0, i = 0; i < t; i++)
if (i % 4) {
var l = r[e.charCodeAt(i - 1)] << i % 4 * 2 | r[e.charCodeAt(i)] >>> 6 - i % 4 * 2;
o[a >>> 2] |= l << 24 - a % 4 * 8,
a++
}
return n.create(o, a)
}
t.enc.Base64 = {
stringify: function (e) {
var t = e.words
, n = e.sigBytes
, r = this._map;
e.clamp();
for (var o = [], a = 0; a < n; a += 3)
for (var i = (t[a >>> 2] >>> 24 - a % 4 * 8 & 255) << 16 | (t[a + 1 >>> 2] >>> 24 - (a + 1) % 4 * 8 & 255) << 8 | t[a + 2 >>> 2] >>> 24 - (a + 2) % 4 * 8 & 255, l = 0; l < 4 && a + .75 * l < n; l++)
o.push(r.charAt(i >>> 6 * (3 - l) & 63));
var s = r.charAt(64);
if (s)
for (; o.length % 4;)
o.push(s);
return o.join("")
},
parse: function (e) {
var t = e.length
, n = this._map
, o = this._reverseMap;
if (!o) {
o = this._reverseMap = [];
for (var a = 0; a < n.length; a++)
o[n.charCodeAt(a)] = a
}
var i = n.charAt(64);
if (i) {
var l = e.indexOf(i);
-1 !== l && (t = l)
}
return r(e, t, o)
},
_map: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
}
}(),
e.enc.Base64
}(JO.exports);
var oM = {
exports: {}
};
oM.exports = function (e) {
return function (t) {
var n = e
, r = n.lib
, o = r.WordArray
, a = r.Hasher
, i = n.algo
, l = [];
!function () {
for (var e = 0; e < 64; e++)
l[e] = 4294967296 * t.abs(t.sin(e + 1)) | 0
}();
var s = i.MD5 = a.extend({
_doReset: function () {
this._hash = new o.init([1732584193, 4023233417, 2562383102, 271733878])
},
_doProcessBlock: function (e, t) {
for (var n = 0; n < 16; n++) {
var r = t + n
, o = e[r];
e[r] = 16711935 & (o << 8 | o >>> 24) | 4278255360 & (o << 24 | o >>> 8)
}
var a = this._hash.words
, i = e[t + 0]
, s = e[t + 1]
, f = e[t + 2]
, h = e[t + 3]
, g = e[t + 4]
, v = e[t + 5]
, m = e[t + 6]
, y = e[t + 7]
, b = e[t + 8]
, C = e[t + 9]
, _ = e[t + 10]
, E = e[t + 11]
, F = e[t + 12]
, A = e[t + 13]
, w = e[t + 14]
, D = e[t + 15]
, x = a[0]
, S = a[1]
, B = a[2]
, k = a[3];
x = u(x, S, B, k, i, 7, l[0]),
k = u(k, x, S, B, s, 12, l[1]),
B = u(B, k, x, S, f, 17, l[2]),
S = u(S, B, k, x, h, 22, l[3]),
x = u(x, S, B, k, g, 7, l[4]),
k = u(k, x, S, B, v, 12, l[5]),
B = u(B, k, x, S, m, 17, l[6]),
S = u(S, B, k, x, y, 22, l[7]),
x = u(x, S, B, k, b, 7, l[8]),
k = u(k, x, S, B, C, 12, l[9]),
B = u(B, k, x, S, _, 17, l[10]),
S = u(S, B, k, x, E, 22, l[11]),
x = u(x, S, B, k, F, 7, l[12]),
k = u(k, x, S, B, A, 12, l[13]),
B = u(B, k, x, S, w, 17, l[14]),
x = c(x, S = u(S, B, k, x, D, 22, l[15]), B, k, s, 5, l[16]),
k = c(k, x, S, B, m, 9, l[17]),
B = c(B, k, x, S, E, 14, l[18]),
S = c(S, B, k, x, i, 20, l[19]),
x = c(x, S, B, k, v, 5, l[20]),
k = c(k, x, S, B, _, 9, l[21]),
B = c(B, k, x, S, D, 14, l[22]),
S = c(S, B, k, x, g, 20, l[23]),
x = c(x, S, B, k, C, 5, l[24]),
k = c(k, x, S, B, w, 9, l[25]),
B = c(B, k, x, S, h, 14, l[26]),
S = c(S, B, k, x, b, 20, l[27]),
x = c(x, S, B, k, A, 5, l[28]),
k = c(k, x, S, B, f, 9, l[29]),
B = c(B, k, x, S, y, 14, l[30]),
x = d(x, S = c(S, B, k, x, F, 20, l[31]), B, k, v, 4, l[32]),
k = d(k, x, S, B, b, 11, l[33]),
B = d(B, k, x, S, E, 16, l[34]),
S = d(S, B, k, x, w, 23, l[35]),
x = d(x, S, B, k, s, 4, l[36]),
k = d(k, x, S, B, g, 11, l[37]),
B = d(B, k, x, S, y, 16, l[38]),
S = d(S, B, k, x, _, 23, l[39]),
x = d(x, S, B, k, A, 4, l[40]),
k = d(k, x, S, B, i, 11, l[41]),
B = d(B, k, x, S, h, 16, l[42]),
S = d(S, B, k, x, m, 23, l[43]),
x = d(x, S, B, k, C, 4, l[44]),
k = d(k, x, S, B, F, 11, l[45]),
B = d(B, k, x, S, D, 16, l[46]),
x = p(x, S = d(S, B, k, x, f, 23, l[47]), B, k, i, 6, l[48]),
k = p(k, x, S, B, y, 10, l[49]),
B = p(B, k, x, S, w, 15, l[50]),
S = p(S, B, k, x, v, 21, l[51]),
x = p(x, S, B, k, F, 6, l[52]),
k = p(k, x, S, B, h, 10, l[53]),
B = p(B, k, x, S, _, 15, l[54]),
S = p(S, B, k, x, s, 21, l[55]),
x = p(x, S, B, k, b, 6, l[56]),
k = p(k, x, S, B, D, 10, l[57]),
B = p(B, k, x, S, m, 15, l[58]),
S = p(S, B, k, x, A, 21, l[59]),
x = p(x, S, B, k, g, 6, l[60]),
k = p(k, x, S, B, E, 10, l[61]),
B = p(B, k, x, S, f, 15, l[62]),
S = p(S, B, k, x, C, 21, l[63]),
a[0] = a[0] + x | 0,
a[1] = a[1] + S | 0,
a[2] = a[2] + B | 0,
a[3] = a[3] + k | 0
},
_doFinalize: function () {
var e = this._data
, n = e.words
, r = 8 * this._nDataBytes
, o = 8 * e.sigBytes;
n[o >>> 5] |= 128 << 24 - o % 32;
var a = t.floor(r / 4294967296)
, i = r;
n[15 + (o + 64 >>> 9 << 4)] = 16711935 & (a << 8 | a >>> 24) | 4278255360 & (a << 24 | a >>> 8),
n[14 + (o + 64 >>> 9 << 4)] = 16711935 & (i << 8 | i >>> 24) | 4278255360 & (i << 24 | i >>> 8),
e.sigBytes = 4 * (n.length + 1),
this._process();
for (var l = this._hash, s = l.words, u = 0; u < 4; u++) {
var c = s[u];
s[u] = 16711935 & (c << 8 | c >>> 24) | 4278255360 & (c << 24 | c >>> 8)
}
return l
},
clone: function () {
var e = a.clone.call(this);
return e._hash = this._hash.clone(),
e
}
});
function u(e, t, n, r, o, a, i) {
var l = e + (t & n | ~t & r) + o + i;
return (l << a | l >>> 32 - a) + t
}
function c(e, t, n, r, o, a, i) {
var l = e + (t & r | n & ~r) + o + i;
return (l << a | l >>> 32 - a) + t
}
function d(e, t, n, r, o, a, i) {
var l = e + (t ^ n ^ r) + o + i;
return (l << a | l >>> 32 - a) + t
}
function p(e, t, n, r, o, a, i) {
var l = e + (n ^ (t | ~r)) + o + i;
return (l << a | l >>> 32 - a) + t
}
n.MD5 = a._createHelper(s),
n.HmacMD5 = a._createHmacHelper(s)
}(Math),
e.MD5
}(JO.exports);
var hM = {
exports: {}
};
hM.exports = function (e) {
return n = (t = e).lib,
r = n.Base,
o = n.WordArray,
a = t.algo,
i = a.MD5,
l = a.EvpKDF = r.extend({
cfg: r.extend({
keySize: 4,
hasher: i,
iterations: 1
}),
init: function (e) {
this.cfg = this.cfg.extend(e)
},
compute: function (e, t) {
for (var n, r = this.cfg, a = r.hasher.create(), i = o.create(), l = i.words, s = r.keySize, u = r.iterations; l.length < s;) {
n && a.update(n),
n = a.update(e).finalize(t),
a.reset();
for (var c = 1; c < u; c++)
n = a.finalize(n),
a.reset();
i.concat(n)
}
return i.sigBytes = 4 * s,
i
}
}),
t.EvpKDF = function (e, t, n) {
return l.create(n).compute(e, t)
}
,
e.EvpKDF;
var t, n, r, o, a, i, l
}(JO.exports);
var gM = {
exports: {}
};
gM.exports = function (e) {
e.lib.Cipher || function (t) {
var n = e
, r = n.lib
, o = r.Base
, a = r.WordArray
, i = r.BufferedBlockAlgorithm
, l = n.enc;
l.Utf8;
var s = l.Base64
, u = n.algo.EvpKDF
, c = r.Cipher = i.extend({
cfg: o.extend(),
createEncryptor: function (e, t) {
return this.create(this._ENC_XFORM_MODE, e, t)
},
createDecryptor: function (e, t) {
return this.create(this._DEC_XFORM_MODE, e, t)
},
init: function (e, t, n) {
this.cfg = this.cfg.extend(n),
this._xformMode = e,
this._key = t,
this.reset()
},
reset: function () {
i.reset.call(this),
this._doReset()
},
process: function (e) {
return this._append(e),
this._process()
},
finalize: function (e) {
return e && this._append(e),
this._doFinalize()
},
keySize: 4,
ivSize: 4,
_ENC_XFORM_MODE: 1,
_DEC_XFORM_MODE: 2,
_createHelper: function () {
function e(e) {
return "string" == typeof e ? b : m
}
return function (t) {
return {
encrypt: function (n, r, o) {
return e(r).encrypt(t, n, r, o)
},
decrypt: function (n, r, o) {
return e(r).decrypt(t, n, r, o)
}
}
}
}()
});
r.StreamCipher = c.extend({
_doFinalize: function () {
return this._process(!0)
},
blockSize: 1
});
var d = n.mode = {}
, p = r.BlockCipherMode = o.extend({
createEncryptor: function (e, t) {
return this.Encryptor.create(e, t)
},
createDecryptor: function (e, t) {
return this.Decryptor.create(e, t)
},
init: function (e, t) {
this._cipher = e,
this._iv = t
}
})
, f = d.CBC = function () {
var e = p.extend();
function n(e, n, r) {
var o, a = this._iv;
a ? (o = a,
this._iv = t) : o = this._prevBlock;
for (var i = 0; i < r; i++)
e[n + i] ^= o[i]
}
return e.Encryptor = e.extend({
processBlock: function (e, t) {
var r = this._cipher
, o = r.blockSize;
n.call(this, e, t, o),
r.encryptBlock(e, t),
this._prevBlock = e.slice(t, t + o)
}
}),
e.Decryptor = e.extend({
processBlock: function (e, t) {
var r = this._cipher
, o = r.blockSize
, a = e.slice(t, t + o);
r.decryptBlock(e, t),
n.call(this, e, t, o),
this._prevBlock = a
}
}),
e
}()
, h = (n.pad = {}).Pkcs7 = {
pad: function (e, t) {
for (var n = 4 * t, r = n - e.sigBytes % n, o = r << 24 | r << 16 | r << 8 | r, i = [], l = 0; l < r; l += 4)
i.push(o);
var s = a.create(i, r);
e.concat(s)
},
unpad: function (e) {
var t = 255 & e.words[e.sigBytes - 1 >>> 2];
e.sigBytes -= t
}
};
r.BlockCipher = c.extend({
cfg: c.cfg.extend({
mode: f,
padding: h
}),
reset: function () {
var e;
c.reset.call(this);
var t = this.cfg
, n = t.iv
, r = t.mode;
this._xformMode == this._ENC_XFORM_MODE ? e = r.createEncryptor : (e = r.createDecryptor,
this._minBufferSize = 1),
this._mode && this._mode.__creator == e ? this._mode.init(this, n && n.words) : (this._mode = e.call(r, this, n && n.words),
this._mode.__creator = e)
},
_doProcessBlock: function (e, t) {
this._mode.processBlock(e, t)
},
_doFinalize: function () {
var e, t = this.cfg.padding;
return this._xformMode == this._ENC_XFORM_MODE ? (t.pad(this._data, this.blockSize),
e = this._process(!0)) : (e = this._process(!0),
t.unpad(e)),
e
},
blockSize: 4
});
var g = r.CipherParams = o.extend({
init: function (e) {
this.mixIn(e)
},
toString: function (e) {
return (e || this.formatter).stringify(this)
}
})
, v = (n.format = {}).OpenSSL = {
stringify: function (e) {
var t = e.ciphertext
, n = e.salt;
return (n ? a.create([1398893684, 1701076831]).concat(n).concat(t) : t).toString(s)
},
parse: function (e) {
var t, n = s.parse(e), r = n.words;
return 1398893684 == r[0] && 1701076831 == r[1] && (t = a.create(r.slice(2, 4)),
r.splice(0, 4),
n.sigBytes -= 16),
g.create({
ciphertext: n,
salt: t
})
}
}
, m = r.SerializableCipher = o.extend({
cfg: o.extend({
format: v
}),
encrypt: function (e, t, n, r) {
r = this.cfg.extend(r);
var o = e.createEncryptor(n, r)
, a = o.finalize(t)
, i = o.cfg;
return g.create({
ciphertext: a,
key: n,
iv: i.iv,
algorithm: e,
mode: i.mode,
padding: i.padding,
blockSize: e.blockSize,
formatter: r.format
})
},
decrypt: function (e, t, n, r) {
return r = this.cfg.extend(r),
t = this._parse(t, r.format),
e.createDecryptor(n, r).finalize(t.ciphertext)
},
_parse: function (e, t) {
return "string" == typeof e ? t.parse(e, this) : e
}
})
, y = (n.kdf = {}).OpenSSL = {
execute: function (e, t, n, r) {
r || (r = a.random(8));
var o = u.create({
keySize: t + n
}).compute(e, r)
, i = a.create(o.words.slice(t), 4 * n);
return o.sigBytes = 4 * t,
g.create({
key: o,
iv: i,
salt: r
})
}
}
, b = r.PasswordBasedCipher = m.extend({
cfg: m.cfg.extend({
kdf: y
}),
encrypt: function (e, t, n, r) {
var o = (r = this.cfg.extend(r)).kdf.execute(n, e.keySize, e.ivSize);
r.iv = o.iv;
var a = m.encrypt.call(this, e, t, o.key, r);
return a.mixIn(o),
a
},
decrypt: function (e, t, n, r) {
r = this.cfg.extend(r),
t = this._parse(t, r.format);
var o = r.kdf.execute(n, e.keySize, e.ivSize, t.salt);
return r.iv = o.iv,
m.decrypt.call(this, e, t, o.key, r)
}
})
}()
}(JO.exports);
var xM = {
exports: {}
};
xM.exports = function (e) {
return function () {
var t = e
, n = t.lib.BlockCipher
, r = t.algo
, o = []
, a = []
, i = []
, l = []
, s = []
, u = []
, c = []
, d = []
, p = []
, f = [];
!function () {
for (var e = [], t = 0; t < 256; t++)
e[t] = t < 128 ? t << 1 : t << 1 ^ 283;
var n = 0
, r = 0;
for (t = 0; t < 256; t++) {
var h = r ^ r << 1 ^ r << 2 ^ r << 3 ^ r << 4;
h = h >>> 8 ^ 255 & h ^ 99,
o[n] = h,
a[h] = n;
var g = e[n]
, v = e[g]
, m = e[v]
, y = 257 * e[h] ^ 16843008 * h;
i[n] = y << 24 | y >>> 8,
l[n] = y << 16 | y >>> 16,
s[n] = y << 8 | y >>> 24,
u[n] = y,
y = 16843009 * m ^ 65537 * v ^ 257 * g ^ 16843008 * n,
c[h] = y << 24 | y >>> 8,
d[h] = y << 16 | y >>> 16,
p[h] = y << 8 | y >>> 24,
f[h] = y,
n ? (n = g ^ e[e[e[m ^ g]]],
r ^= e[e[r]]) : n = r = 1
}
}();
var h = [0, 1, 2, 4, 8, 16, 32, 64, 128, 27, 54]
, g = r.AES = n.extend({
_doReset: function () {
if (!this._nRounds || this._keyPriorReset !== this._key) {
for (var e = this._keyPriorReset = this._key, t = e.words, n = e.sigBytes / 4, r = 4 * ((this._nRounds = n + 6) + 1), a = this._keySchedule = [], i = 0; i < r; i++)
i < n ? a[i] = t[i] : (u = a[i - 1],
i % n ? n > 6 && i % n == 4 && (u = o[u >>> 24] << 24 | o[u >>> 16 & 255] << 16 | o[u >>> 8 & 255] << 8 | o[255 & u]) : (u = o[(u = u << 8 | u >>> 24) >>> 24] << 24 | o[u >>> 16 & 255] << 16 | o[u >>> 8 & 255] << 8 | o[255 & u],
u ^= h[i / n | 0] << 24),
a[i] = a[i - n] ^ u);
for (var l = this._invKeySchedule = [], s = 0; s < r; s++) {
if (i = r - s,
s % 4)
var u = a[i];
else
u = a[i - 4];
l[s] = s < 4 || i <= 4 ? u : c[o[u >>> 24]] ^ d[o[u >>> 16 & 255]] ^ p[o[u >>> 8 & 255]] ^ f[o[255 & u]]
}
}
},
encryptBlock: function (e, t) {
this._doCryptBlock(e, t, this._keySchedule, i, l, s, u, o)
},
decryptBlock: function (e, t) {
var n = e[t + 1];
e[t + 1] = e[t + 3],
e[t + 3] = n,
this._doCryptBlock(e, t, this._invKeySchedule, c, d, p, f, a),
n = e[t + 1],
e[t + 1] = e[t + 3],
e[t + 3] = n
},
_doCryptBlock: function (e, t, n, r, o, a, i, l) {
for (var s = this._nRounds, u = e[t] ^ n[0], c = e[t + 1] ^ n[1], d = e[t + 2] ^ n[2], p = e[t + 3] ^ n[3], f = 4, h = 1; h < s; h++) {
var g = r[u >>> 24] ^ o[c >>> 16 & 255] ^ a[d >>> 8 & 255] ^ i[255 & p] ^ n[f++]
, v = r[c >>> 24] ^ o[d >>> 16 & 255] ^ a[p >>> 8 & 255] ^ i[255 & u] ^ n[f++]
, m = r[d >>> 24] ^ o[p >>> 16 & 255] ^ a[u >>> 8 & 255] ^ i[255 & c] ^ n[f++]
, y = r[p >>> 24] ^ o[u >>> 16 & 255] ^ a[c >>> 8 & 255] ^ i[255 & d] ^ n[f++];
u = g,
c = v,
d = m,
p = y
}
g = (l[u >>> 24] << 24 | l[c >>> 16 & 255] << 16 | l[d >>> 8 & 255] << 8 | l[255 & p]) ^ n[f++],
v = (l[c >>> 24] << 24 | l[d >>> 16 & 255] << 16 | l[p >>> 8 & 255] << 8 | l[255 & u]) ^ n[f++],
m = (l[d >>> 24] << 24 | l[p >>> 16 & 255] << 16 | l[u >>> 8 & 255] << 8 | l[255 & c]) ^ n[f++],
y = (l[p >>> 24] << 24 | l[u >>> 16 & 255] << 16 | l[c >>> 8 & 255] << 8 | l[255 & d]) ^ n[f++],
e[t] = g,
e[t + 1] = v,
e[t + 2] = m,
e[t + 3] = y
},
keySize: 8
});
t.AES = n._createHelper(g)
}(),
e.AES
}(JO.exports);
var OM = qO.exports = JO.exports;
function enc(data, key) {
return OM.AES.encrypt(data, key).toString()
}
""";
}
@@ -8,11 +8,6 @@ import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import static cn.qaiu.util.AESUtils.encrypt;
/**
* 执行Js脚本
*
@@ -62,49 +57,4 @@ public class JsExecUtils {
}
/**
* 调用执行蓝奏云js文件
*/
public static Object executeOtherJs(String jsText, String funName, Object ... args) throws ScriptException,
NoSuchMethodException {
ScriptEngineManager engineManager = new ScriptEngineManager();
ScriptEngine engine = engineManager.getEngineByName("JavaScript"); // 得到脚本引擎
engine.eval(jsText);
Invocable inv = (Invocable) engine;
//调用js中的函数
if (StringUtils.isNotEmpty(funName)) {
return inv.invokeFunction(funName, args);
}
throw new ScriptException("funName is null");
}
public static String getKwSign(String s, String pwd) {
try {
return executeOtherJs(JsContent.kwSignString, "encrypt", s, pwd).toString();
} catch (ScriptException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
public static String mgEncData(String data, String key) {
try {
return executeOtherJs(JsContent.mgJS, "enc", data, key).toString();
} catch (ScriptException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
// return OM.AES.encrypt('{"copyrightId":"6326951FKBL","type":1,"auditionsFlag":0}',
// '4ea5c508a6566e76240543f8feb06fd457777be39549c4016436afda65d2330e').toString()
//
public static void main(String[] args) {
System.out.println(URLEncoder
.encode(mgEncData("{\"copyrightId\":\"6326951FKBL\",\"type\":1,\"auditionsFlag\":0}", AESUtils.MG_KEY), StandardCharsets.UTF_8));
// U2FsdGVkX1/UiZC91ImQvQY7qDBSEbTykAcVoARiquibPCZhWSs3kWQw3j2PNme5wNLqt2oau498ni1hgjGFuxwORnkk6x9rzk/X0arElUo=
}
}
@@ -1,22 +0,0 @@
package cn.qaiu.util;
import java.security.SecureRandom;
public class RandomStringGenerator {
private static final String CHARACTERS = "abcdefghijklmnopqrstuvwxyz0123456789";
private static final int LENGTH = 13; // 每段长度为13
public static String generateRandomString() {
SecureRandom random = new SecureRandom();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 2; i++) { // 生成两段
for (int j = 0; j < LENGTH; j++) {
int index = random.nextInt(CHARACTERS.length());
sb.append(CHARACTERS.charAt(index));
}
}
return sb.toString();
}
}
@@ -1,74 +0,0 @@
package cn.qaiu.util;
import io.vertx.core.AsyncResult;
import io.vertx.core.MultiMap;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.impl.headers.HeadersMultiMap;
import io.vertx.ext.web.client.HttpResponse;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.client.WebClientSession;
public class ReqIpUtil {
public static String BASE_URL = "https://ip.ihuan.me";
public static String BASE_URL_TEMPLATE = BASE_URL + "/{path}";
// GET https://ip.ihuan.me/mouse.do -> $("input[name='key']").val("30b4975b5547fed806bd2b9caa18485a");
public static String PATH1 = "mouse.do";
public static String PATH2 = "tqdl.html";
// 创建请求头Map
static MultiMap headers = new HeadersMultiMap();
static {
headers.set("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");
headers.set("accept-language", "zh-CN,zh;q=0.9,en;q=0.8");
headers.set("cache-control", "no-cache");
headers.set("dnt", "1");
headers.set("origin", "https://ip.ihuan.me");
headers.set("pragma", "no-cache");
headers.set("priority", "u=0, i");
headers.set("referer", "https://ip.ihuan.me");
headers.set("sec-ch-ua", "\"Google Chrome\";v=\"129\", \"Not=A?Brand\";v=\"8\", \"Chromium\";v=\"129\"");
headers.set("sec-ch-ua-mobile", "?0");
headers.set("sec-ch-ua-platform", "\"Windows\"");
headers.set("sec-fetch-dest", "document");
headers.set("sec-fetch-mode", "navigate");
headers.set("sec-fetch-site", "same-origin");
headers.set("sec-fetch-user", "?1");
headers.set("upgrade-insecure-requests", "1");
// headers.set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36");
}
Vertx vertx = Vertx.vertx();
WebClient webClient = WebClient.create(vertx);
// 发送GET请求
WebClientSession webClientSession = WebClientSession.create(webClient);
public void exec() {
webClientSession.getAbs(BASE_URL)
.putHeaders(headers) // 将请求头Map添加到请求中
.send(this::next);
}
void next(AsyncResult<HttpResponse<Buffer>> response) {
if (response.failed()) {
response.cause().printStackTrace();
} else {
HttpResponse<Buffer> res = response.result();
System.out.println("Received response with status code " + res.statusCode());
System.out.println("Body: " + res.body());
webClientSession.getAbs(BASE_URL_TEMPLATE).setTemplateParam("path", PATH1)
.putHeaders(headers) // 将请求头Map添加到请求中
.send(response2 -> {
System.out.println(response2.result().bodyAsString());
});
}
}
}
@@ -1,47 +0,0 @@
package cn.qaiu.util;
import org.apache.commons.lang3.StringUtils;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public class URLUtil {
private final Map<String, String> queryParams = new HashMap<>();
// 构造函数,传入URL并解析参数
private URLUtil(String url) {
try {
URL parsedUrl = new URL(url);
String ref = parsedUrl.getRef();
if (StringUtils.isNotEmpty(ref)) {
parsedUrl = new URL(parsedUrl.getProtocol() + "://" + parsedUrl.getHost() + ref);
}
String query = parsedUrl.getQuery();
if (query != null) {
String[] pairs = query.split("&");
for (String pair : pairs) {
String[] keyValue = pair.split("=");
String key = URLDecoder.decode(keyValue[0], StandardCharsets.UTF_8);
String value = keyValue.length > 1 ? URLDecoder.decode(keyValue[1], StandardCharsets.UTF_8) : "";
queryParams.put(key, value);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 静态方法,用于创建UrlUtil实例
public static URLUtil from(String url) {
return new URLUtil(url);
}
// 获取参数的方法
public String getParam(String param) {
return queryParams.get(param);
}
}
@@ -1,35 +0,0 @@
package cn.qaiu.util;
import java.security.SecureRandom;
/**
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2024/5/13 4:10
*/
public class UUIDUtil {
public static String fjUuid() {
return generateRandomString(21);
}
public static String generateRandomString(int length) {
SecureRandom random = new SecureRandom();
byte[] randomBytes = new byte[length];
random.nextBytes(randomBytes);
StringBuilder sb = new StringBuilder();
for (byte b : randomBytes) {
int value = b & 0x3F; // 63 in hexadecimal
if (value < 36) {
sb.append(Integer.toString(value, 36));
} else if (value < 62) {
sb.append(Character.toUpperCase(Integer.toString(value - 26, 36).charAt(0)));
} else if (value > 62) {
sb.append("-");
} else { // value == 62 or 63
sb.append("_");
}
}
return sb.toString();
}
}
-64
View File
@@ -1,64 +0,0 @@
import requests
import urllib.parse
import re
import base64
"""
https://github.com/chenhal/short_url
"""
headers = {
'Cookie': 'SUB=',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36',
'Referer': 'https://www.weibo.com',
'Content-Type': 'application/x-www-form-urlencoded'
}
def get_short_url(long_url):
url = "https://www.weibo.com/aj/v6/comment/add"
payload = urllib.parse.urlencode({
'mid': '5094736413852129',
'content': long_url
})
response = requests.post(url, headers=headers, data=payload)
try:
# print(response.json())
data = response.json()['data']['comment']
short_url = re.search(r'(https?)://t.cn/\w+', data).group(0)
comment_id = re.findall(r'comment_id="(.+\d)"', data)[-1] # 评论id
print('微博短链:' + short_url)
del_comment(comment_id) # 需要删除评论,可以取消该行注释
except:
print('失败')
pass
# 删除评论
def del_comment(comment_id):
url = 'https://www.weibo.com/aj/comment/del'
payload = urllib.parse.urlencode({
'mid': '微博mid',
'cid': comment_id # 评论id
})
response = requests.post(url, headers=headers, data=payload)
try:
if response.json()['code'] == '100000':
print('评论已删除')
except:
pass
if __name__ == '__main__':
# https://so.toutiao.com/search/jump?url=https%3A%2F%2Fblog.qaiu.top%2Farchives%2Fpydroidall&aid=4916&jtoken=297f06127cb010274213422b1967bdc2ae8469b627205941dc287173b58a2a8439ea0d813d24ada8780047d33f37d7e82c6a620760de1ca37640c1dc143b4e01
# https://so.toutiao.com/search/jump?url=https%3A%2F%2Fblog.qaiu.top%2Farchives%2Fpydroidall1&aid=4916&jtoken=297f06127cb010274213422b1967bdc2ae8469b627205941dc287173b58a2a8439ea0d813d24ada8780047d33f37d7e82c6a620760de1ca37640c1dc143b4e01
# 原始 URL
url = "https://lz.qaiu.top/json/lz/i5vOm0xho2cj"
# 将 URL 编码为 Base64
encoded_url = base64.b64encode(url.encode("utf-8")).decode("utf-8")
get_short_url('https://www.so.com/link?m=ewgUSYiFWXIoTybC3fJH8YoJy8y10iRquo6cazgINwWjTn3HvVJ92TrCJu0PmMUR0RMDfOAucP3wa4G8j64SrhNH9Z0Cr0PEyn9ASuvpkUGmAjjUEGJkO5%2BIDGWVrEkPHsL7UsoKO6%2BlT%2BD6r&ccc=' + encoded_url)
@@ -1,37 +0,0 @@
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.");
}
}
}
}
@@ -1,106 +0,0 @@
package cn.qaiu.parser;
import cn.qaiu.entity.ShareLinkInfo;
import org.junit.Test;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.stream.Collectors;
import static java.util.regex.Pattern.compile;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2024/8/8 2:39
*/
public class PanDomainTemplateTest {
@Test
public void normalizeShareLink() {
// 准备测试数据
String testShareUrl = "https://test.lanzoux.com/s/someShareKey";
ParserCreate parserCreate = ParserCreate.fromShareUrl(testShareUrl); // 假设使用LZ网盘模板
// 调用normalizeShareLink方法
ShareLinkInfo result = parserCreate.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", parserCreate.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";
// ParserCreate.fromShareUrl(wsUrl).createTool()
// .parse().onSuccess(System.out::println);
// ParserCreate.fromShareUrl(lzUrl).createTool()
// .parse().onSuccess(System.out::println);
// ParserCreate.fromShareUrl(cowUrl).createTool()
// .parse().onSuccess(System.out::println);
ParserCreate.fromShareUrl(lzUrl).createTool()
.parse().onSuccess(System.out::println);
// ParserCreate.fromType("lz").shareKey("ihLkw1gezutg")
// .createTool().parse().onSuccess(System.out::println);
// ParserCreate.LZ.shareKey("ihLkw1gezutg")
// .createTool().parse().onSuccess(System.out::println);
// 调用fromShareUrl方法
// PanDomainTemplate resultTemplate = ParserCreate.fromShareUrl(testShareUrl);
// System.out.println(resultTemplate.normalizeShareLink(testShareUrl));
// System.out.println(resultTemplate.shareKey("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", ParserCreate.LZ, resultTemplate);
// // 可以添加更多的断言来验证正则表达式匹配逻辑
// new Scanner(System.in).nextLine();
TimeUnit.SECONDS.sleep(5);
}
@Test
public void verifyDuplicates() {
Matcher matcher = compile("https://(?:[a-zA-Z\\d-]+\\.)?drive\\.google\\.com/file/d/(?<KEY>.+)/view(\\?usp=(sharing|drive_link))?")
.matcher("https://adsd.drive.google.com/file/d/151bR-nk-tOBm9QAFaozJIVt2WYyCMkoz/view");
if (matcher.find()) {
System.out.println(matcher.group());
System.out.println(matcher.group("KEY"));
}
// 校验重复
Set<String> collect =
Arrays.stream(PanDomainTemplate.values()).map(PanDomainTemplate::getRegex).collect(Collectors.toSet());
if (collect.size()<PanDomainTemplate.values().length) {
System.out.println("有重复枚举正则");
} else {
System.out.println("正则无重复");
}
Set<String> collect2 =
Arrays.stream(PanDomainTemplate.values()).map(PanDomainTemplate::getStandardUrlTemplate).collect(Collectors.toSet());
if (collect2.size()<PanDomainTemplate.values().length) {
System.out.println("有重复枚举标准链接");
} else {
System.out.println("标准链接无重复");
}
}
}
@@ -1,32 +0,0 @@
package cn.qaiu.util;
import org.junit.Test;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TestRegex {
@Test
public void regexYFC() {
String html = """
true|
<div class="load" id="go"><a
href="https://nsub2d3.118pan.com/dl.php?YzljNGFnOHFabWpsdzVXNUhyOXNiQ0ZYMHEzUjFoQ0R6MExCckdKZmRQL1RXbERPSTUwS0xlZkUvdmN5dGNqTHhCNDJVZURFekthV0tROHNINXdNRkZYUXB5aGQ5TEdXeTgrNkc1TkZwOWhzYVdpak83dEZleWxpYnloc0c1Ky9wbVBSZ0xiSHo5aTBnVVpCRmpyMGFBRERjcldtc0FnbS9vMnd3ZGRHWU4zYTNzNncwbk56UEtOVWhFeGIvWlM1aWZLQjlaTjB3b2V0cGVrb2lpQ0x3U0dtQkJqd2plWk1yeU1mZG1oNXpRYUJpYmFhZVgvWFZScThsTFhmU1ZKRW9UT3haVHFMVGVRTUhR"
onclick="down_process2('1228264');" target="_blank"><span class="txt">极速下载</span></a><a
href="https://nsub2t3.118pan.com/dl.php?YzljNGFnOHFabWpsdzVXNUhyOXNiQ0ZYMHEzUjFoQ0R6MExCckdKZmRQL1RXbERPSTUwS0xlZkUvdmN5dGNqTHhCNDJVZURFekthV0tROHNINXdNRkZYUXB5aGQ5TEdXeTgrNkc1TkZwOWhzYVdpak83dEZleWxpYnloc0c1Ky9wbVBSZ0xiSHo5aTBnVVpCRmpyMGFBRERjcldtc0FnbS9vMnd3ZGRHWU4zYTNzNncwbk56UEtOVWhFeGIvWlM1aWZLQjlaTjB3b2V0cGVrb2lpQ0x3U0dtQkJqd2plWk1yeU1mZG1oNXpRYUJpYmFhZVgvWFZScThsTFhmU1ZKRW9UT3haVHFMVGVRTUhR"
onclick="down_process2('1228264');" target="_blank"><span class="txt txtc">电信下载</span></a><a
href="https://nsub2u3.118pan.com/dl.php?YzljNGFnOHFabWpsdzVXNUhyOXNiQ0ZYMHEzUjFoQ0R6MExCckdKZmRQL1RXbERPSTUwS0xlZkUvdmN5dGNqTHhCNDJVZURFekthV0tROHNINXdNRkZYUXB5aGQ5TEdXeTgrNkc1TkZwOWhzYVdpak83dEZleWxpYnloc0c1Ky9wbVBSZ0xiSHo5aTBnVVpCRmpyMGFBRERjcldtc0FnbS9vMnd3ZGRHWU4zYTNzNncwbk56UEtOVWhFeGIvWlM1aWZLQjlaTjB3b2V0cGVrb2lpQ0x3U0dtQkJqd2plWk1yeU1mZG1oNXpRYUJpYmFhZVgvWFZScThsTFhmU1ZKRW9UT3haVHFMVGVRTUhR"
onclick="down_process2('1228264');" target="_blank"><span class="txt">联通下载</span></a><a
href="https://nsub2d3.118pan.com/dl.php?YzljNGFnOHFabWpsdzVXNUhyOXNiQ0ZYMHEzUjFoQ0R6MExCckdKZmRQL1RXbERPSTUwS0xlZkUvdmN5dGNqTHhCNDJVZURFekthV0tROHNINXdNRkZYUXB5aGQ5TEdXeTgrNkc1TkZwOWhzYVdpak83dEZleWxpYnloc0c1Ky9wbVBSZ0xiSHo5aTBnVVpCRmpyMGFBRERjcldtc0FnbS9vMnd3ZGRHWU4zYTNzNncwbk56UEtOVWhFeGIvWlM1aWZLQjlaTjB3b2V0cGVrb2lpQ0x3U0dtQkJqd2plWk1yeU1mZG1oNXpRYUJpYmFhZVgvWFZScThsTFhmU1ZKRW9UT3haVHFMVGVRTUhR"
onclick="down_process2('1228264');" target="_blank"><span class="txt txtc">普通下载</span></a></div>
""";
Pattern compile = Pattern.compile("href=\"([^\"]+)\"");
Matcher matcher = compile.matcher(html);
if (matcher.find()) {
// System.out.println(matcher.group(0));
System.out.println(matcher.group(1));
}
}
}
@@ -1,19 +0,0 @@
package cn.qaiu.util;
import org.junit.Assert;
import org.junit.Test;
import static org.junit.Assert.*;
public class URLUtilTest {
@Test
public void getParam() {
URLUtil util = URLUtil.from("https://i.y.qq.com/v8/playsong.html?ADTAG=cbshare&_wv=1&appshare=android_qq" +
"&appsongtype=1&appversion=13100008&channelId=10036163&hosteuin=7iosow-s7enz&openinqqmusic=1&platform" +
"=11&songmid=000XjcLg0fbRjv&type=0");
Assert.assertEquals(util.getParam("songmid"), "000XjcLg0fbRjv");
Assert.assertEquals(util.getParam("type"), "0");
}
}
@@ -1,4 +1,4 @@
package cn.qaiu.util;
package qaiu.web.test;
import cn.qaiu.util.AESUtils;
import org.junit.Assert;
@@ -18,11 +18,7 @@ public class TestAESUtil {
@Test
public void decode() throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException,
BadPaddingException, InvalidKeyException {
String hex = AESUtils.encryptBase64ByAES("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8asrfSaoOb4je+DSmKdriQJKW\n" +
"VJ2oDZrs3wi5W67m3LwTB9QVR+cE3XWU21Nx+YBxS0yun8wDcjgQvYt625ZCcgin\n" +
"2ro/eOkNyUOTBIbuj9CvMnhUYiR61lC1f1IGbrSYYimqBVSjpifVufxtx/I3exRe\n" +
"ZosTByYp4Xwpb1+WAQIDAQAB", AESUtils.CIPHER_AES2);
System.out.println(hex);
String hex = AESUtils.encryptHexByAES("1686215935703", AESUtils.CIPHER_AES2);
Assert.assertEquals("B4C5B9833113ACA41F16AABADE17349C", hex.toUpperCase());
}
@@ -0,0 +1,24 @@
package qaiu.web.test;
import org.junit.Test;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TestRegex {
@Test
public void regexYFC() {
String html = """
<input type="hidden" id="typed_id" value="file_559003251828">
<input type="hidden" id="share_link_token" value="9cbe4b73521ba4d65a8cd38a8c">
""";
Pattern compile = Pattern.compile("id=\"typed_id\"\\s+value=\"file_(\\d+)\"");
Matcher matcher = compile.matcher(html);
if (matcher.find()) {
System.out.println(matcher.group(0));
System.out.println(matcher.group(1));
}
}
}
@@ -0,0 +1,54 @@
package qaiu.web.test;
import io.vertx.core.MultiMap;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.WebClient;
import org.junit.Test;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TestWebClient2 {
@Test
public void matcherHtml() {
Pattern compile = Pattern.compile("class=\"ifr2\" name=.+src=\"(/fn\\?[a-zA-Z0-9_+/=]{16,})\"");
var text = """
<div class="ifr"><!--<iframe class="ifr2" name="1" src="/fn?v2" frameborder="0" scrolling="no"></iframe>-->
<iframe class="ifr2" name="1685001208" src="/fn?UzUBa1oxBmUAYgNsUDUFNVI6BjJfJlchV21TZFU_aVWwANVQzXTBXMlUxUTcLZ1dwUn8DYwQ5AHFVOwdmBjRUPlM2AS9aOgY3AGIDMFA2" frameborder="0" scrolling="no"></iframe>""";
System.out.println(text);
Matcher matcher = compile.matcher(text);
if (matcher.find()) {
System.out.println(matcher.group(0));
System.out.println(matcher.group(1));
}
}
@Test
public void lzClient() {
Vertx vertx = Vertx.vertx();
WebClient client = WebClient.create(vertx);
MultiMap form = MultiMap.caseInsensitiveMultiMap();
// action=downprocess&sign=AGYHOQk4BjdTWgQ7BzcGOlU_bATVSNQMxBDFQZgZoBj4HMFEgWnMOZ1I1A2NWOgUxB20HMlM_aUGoLOgQz&p=e4k4
form.set("action", "downprocess");
form.set("sign", "VzFWaA4_aBzYIAQI9ADBUaARvATVRNlNhUGUBNwBuATkDNFEgXHVVPAZhB2dTP1ZiVD5UYQBpV2EBPwA3");
form.set("p", "e4k4");
client.postAbs("https://wwsd.lanzoue.com/ajaxm.php")
.putHeader("referer", "https://wwsd.lanzoue.com/iFhd00x8k0kh")
.sendForm(form).onSuccess(res -> {
JsonObject jsonObject = res.bodyAsJsonObject();
System.out.println(jsonObject);
vertx.close();
});
//
// https://developer.lanzoug.com/file/?VTMBPwEwU2IGD1dvV2ICblBvU2sENQZhVSZSMAA1WihSOVYsDTZTZlN2UXMFfAZjBzJXJQAxAG5XP1UyXGQGKlVlAXgBbVMpBmNXLFdhAmpQZFN4BCEGbVUiUnIAOloyUj5WZA0PU25TYVE6BWAGNgdlV2IAbQAyV2JValw3BiFVMwElAWFTNgZmVzBXMwIyUDpTYARrBiJVIlIkAGFaaVJiVjMNY1MoUzVRMgV+BjUHaFd9ADwAMVdlVTFcOAYyVWcBYgFqUz4GaVdlVzMCNFBrUzcEOAZgVWJSZQA/WmJSM1Y2DWhTNFMzUTEFYgY3B2VXZgBxAHhXOVUjXCYGclUmATMBLlNuBjRXPFcyAjNQP1NvBG8GPVVqUnIAKFoyUj9WZA02UzpTNFExBWkGMgdtV2IAaQAxV2FVYFwuBilVcwEwATBTcAZtVzBXNQI7UD9TZgRrBjFVY1JlAGlafVInVnENJ1M6UzRRMQVpBjIHbVdiAG0AMldgVWRcJgZyVTwBJgFhUzYGYVczVy0CMVA5U2QEdQY1VWZSYgByWmxSag==
// https://developer.lanzoug.com/file/?B2FWaA4/BDVTWgc/UWRVOQQ7BT1VZFYxUSJUNgE0UiAEbwJ4CDMOOwInU3EKc1w5ATRSIAIzUz1ROVcwATkDLwc3Vi8OYgR+UzYHfFFnVT0EMAUuVXBWPVEmVHQBO1I6BGgCMAgKDjMCMFM4Cm9cbAFjUmcCb1NhUWRXaAFqAyQHYVZyDm4EYVMzB2BRNVVlBG4FNlU6VnJRJlQiAWBSYQQ0AmcIZg51AmRTMApxXG8BblJ4Aj5TYlFjVzMBZQM3BzVWNQ5lBGlTPAc1UTVVYwQ/BWFVaVYwUWZUYwE+UmoEZQJiCG0OaQJiUzMKbVxtAWNSYwJzUytRP1chAXsDdwd0VmQOIQQ5U2EHbFE0VWQEawU5VT5WbVFuVHQBKVI6BGkCMAgzDmcCZVMzCmZcaAFrUmcCaFNlUWNXZwFzAywHIVZnDj8EJ1M4B2BRM1VsBGsFMFU6VmZRb1RgAW5SdQRxAiUIIg5nAmVTMwpmXGgBa1JnAm9TYVFmV2YBewN3B25WcQ5uBGFTNAdjUStVZgRtBTJVJFZlUWJUZAFzUmQEPA==
// https://developer.lanzoug.com/file/?CW9WaAk4BzZUXVRsCz5cMAE+Bj5UZVM0USJUNlRhA3FUPwJ4CTJUYQInASMHflI3ATQGdFdmAW9ROQFmVGwEKAk5Vi8JZQd9VDFULws9XDQBNQYtVHFTOFEmVHRUbgNrVDgCMAkLVGkCMAFqB2JSYgFjBjNXOgEzUWQBPlQ/BCMJb1ZyCWkHYlQ0VDMLb1xsAWsGNVQ7U3dRJlQiVDUDMFRkAmcJZ1QvAmQBYgd8UmEBbgYsV2sBMFFjAWVUMAQwCTtWNQliB2pUO1RmC29cagE6BmJUaFM1UWZUY1RrAztUNQJiCWxUMwJiAWEHYFJjAWMGN1cmAXlRPwF3VC4EcAl6VmQJJgc6VGZUPwtuXG0BbgY6VD9TaFFuVHRUfANrVDkCMAkyVD0CZQFhB2tSZgFrBjNXPgE6UWABM1QmBCsJL1ZnCTgHJFQ/VDMLaVxlAW4GM1Q7U2RRb1RmVDgDJFQhAiUJI1Q9AmUBYQdrUmYBawYzVzoBM1FmATBULgRwCWBWcQlpB2JUM1QwC3FcbwFoBjFUJVNgUWJUZFQmAzVUbA==
}
}
@@ -1,4 +1,4 @@
package cn.qaiu.parser;
package qaiu.web.test;
import io.vertx.core.MultiMap;
import io.vertx.core.Vertx;
@@ -17,7 +17,7 @@ public class WebClientExample {
WebClient client = WebClient.create(vertx);
MultipartForm form = new MultipartFormImpl()
.attribute("email", "")
.attribute("email", "[email protected]")
.attribute("password", "");
client.postAbs("https://cowtransfer.com/api/user/emaillogin")
+4 -35
View File
@@ -7,7 +7,7 @@
<groupId>cn.qaiu</groupId>
<artifactId>netdisk-fast-download</artifactId>
<packaging>pom</packaging>
<version>${revision}</version>
<version>0.1.7</version>
<modules>
<module>core</module>
@@ -17,7 +17,6 @@
</modules>
<properties>
<revision>0.1.8</revision>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
@@ -25,46 +24,16 @@
<packageDirectory>${project.basedir}/web-service/target/package</packageDirectory>
<slf4j.version>2.0.5</slf4j.version>
<vertx.version>4.5.6</vertx.version>
<org.reflections.version>0.10.2</org.reflections.version>
<lombok.version>1.18.30</lombok.version>
<lombok.version>1.18.12</lombok.version>
<slf4j.version>2.0.5</slf4j.version>
<commons-lang3.version>3.12.0</commons-lang3.version>
<commons-beanutils2.version>2.0.0</commons-beanutils2.version>
<jackson.version>2.14.2</jackson.version>
<logback.version>1.5.8</logback.version>
<junit.version>4.13.2</junit.version>
<logback.version>1.4.12</logback.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-dependencies</artifactId>
<version>${vertx.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>cn.qaiu</groupId>
<artifactId>core</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.qaiu</groupId>
<artifactId>core-database</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.qaiu</groupId>
<artifactId>parser</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
-1
View File
@@ -23,4 +23,3 @@ pnpm-debug.log*
*.sw?
/nfd-front.zip
/nfd-front
/package-lock.json
+2 -28
View File
@@ -1,28 +1,7 @@
# nfd-web
使用vue3+element-plus打造
解析服务的前端页面, 提供API测试, 统计查询, 二维码生成等;
20241101 支持剪切板链接自动识别解析, 一键生成短链
20241111 vue框架升级为3.0
![img_2.png](img/img_2.png)
![img.png](img/img.png)
![img_1.png](img/img_1.png)
## 关于如何将前端项目和java一块打包:
1. 先打包前端模块
2. ~~打包后请将当前目录下的nfd-front目录放置在项目下webroot目录, 然后使用maven打包java模块即可~~ `npm run build` 会直接打包到后端代理目录下, 无需复制
3. 项目部署后演示页面的代理端口是6401默认使用http, 如需https可以加nginx代理, 也可以使用本项目自带的代理服务和配置证书路径
## nginx配置
```nginx
location / {
proxy_pass http://127.0.0.1:6401;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
```
当前页面修改自开源项目 https://github.com/HurryBy/CloudDiskAnalysis
![11115](https://bd2.qaiu.cn/blog/11115.png)
## Project setup
```
npm install
@@ -45,8 +24,3 @@ npm run lint
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
## 参考项目
- https://github.com/HurryBy/CloudDiskAnalysis
- https://github.com/syhyz1990/panAI
Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

+13867
View File
File diff suppressed because it is too large Load Diff
+17 -19
View File
@@ -1,6 +1,6 @@
{
"name": "nfd-web",
"version": "0.1.9",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
@@ -8,27 +8,25 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@vueuse/core": "^11.2.0",
"axios": "^1.7.4",
"clipboard": "^2.0.11",
"axios": "^1.6.0",
"core-js": "^3.8.3",
"element-plus": "^2.8.7",
"qrcode": "^1.5.4",
"vue": "^3.5.12",
"vue-clipboard3": "^2.0.0",
"vue3-json-viewer": "^2.2.2"
"element-ui": "^2.15.12",
"vue": "^2.6.14",
"vue-clipboard2": "^0.3.3",
"vue-json-viewer": "^2.2.22"
},
"devDependencies": {
"@babel/core": "^7.26.0",
"@babel/eslint-parser": "^7.25.9",
"@vue/cli-plugin-babel": "~5.0.8",
"@vue/cli-plugin-eslint": "~5.0.8",
"@vue/cli-service": "~5.0.8",
"compression-webpack-plugin": "^11.1.0",
"eslint": "^9.14.0",
"eslint-plugin-vue": "^9.30.0",
"filemanager-webpack-plugin": "8.0.0"
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3",
"vue-template-compiler": "^2.6.14",
"compression-webpack-plugin": "^6.1.1",
"filemanager-webpack-plugin": "2.0.5"
},
"eslintConfig": {
"root": true,
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

+3
View File
@@ -9,6 +9,9 @@
content="Netdisk fast download,网盘直链解析工具">
<meta name="description"
content="Netdisk fast download 网盘直链解析工具">
<script charset="UTF-8" id="LA_COLLECT" src="//sdk.51.la/js-sdk-pro.min.js"></script>
<script>LA.init({id:"K8zkCkZMgFA6ShZK",ck:"K8zkCkZMgFA6ShZK"})</script>
<style>
.page-loading-wrap {
padding: 120px;
+70 -233
View File
@@ -1,77 +1,42 @@
<template>
<div id="app">
<el-row :gutter="20">
<el-card class="box-card">
<div style="text-align: right"><DarkMode/></div>
<div class="demo-basic--circle">
<div class="block" style="text-align: center;">
<img :height="150" src="../public/images/lanzou111.png" alt="lz"></img>
<el-avatar :size="150" :src="avatar"></el-avatar>
</div>
</div>
<h3 style="text-align: center;">NFD网盘直链解析0.1.8_bate2(API演示)</h3>
<h3 style="text-align: center;">NFD网盘直链解析(API演示)</h3>
<div class="typo">
<p style="text-align: center;">
<span>
<el-link href="https://github.com/qaiu/netdisk-fast-download" target="_blank" rel="nofollow">
<u>GitHub</u></el-link>
</span>
<span style="margin-left: 30px">
<el-link href="https://blog.qaiu.top/archives/netdisk-fast-download-bao-ta-an-zhuang-jiao-cheng" target="_blank"
rel="nofollow"><u>宝塔部署安装教程</u>
</el-link>
</span>
<span style="margin-left: 30px">
<el-link href="https://blog.qaiu.top" target="_blank"
rel="nofollow"><u>QAIU博客</u>
</el-link>
</span></p>
<p><strong>目前支持 </strong>蓝奏云/蓝奏云优享/小飞机盘/123云盘/奶牛快传/移动云云空间/亿方云/文叔叔/QQ邮箱文件中转站</p>
<p>已加入缓存机制, 如果遇到解析出的下载链接失效的情况请及时到<a href="https://github.com/qaiu/netdisk-fast-download/issues">
<u><strong>项目GitHub反馈</strong></u></a></p>
<p>节点1: 回源请求数:{{ node1Info.parserTotal }}, 缓存请求数:{{ node1Info.cacheTotal }}, 总数:{{ node1Info.total }}</p>
<!-- <p>节点2: 成功:{{ node2Info.success }},失败:{{ node2Info.fail }},总数:{{ node2Info.total }}</p>-->
<p><strong>项目GitHub </strong><a href="https://github.com/qaiu/netdisk-fast-download" target="_blank"
rel="nofollow"><u>netdisk-fast-download</u></a></p>
<p><strong>当前页面修改自开源项目</strong><a href="https://github.com/HurryBy/CloudDiskAnalysis"
target="_blank"
rel="nofollow"><u>CloudDiskAnalysis</u></a></p>
<p><strong>目前支持 </strong>已支持蓝奏云/奶牛快传/移动云云空间/UC网盘(暂时失效)/小飞机盘/亿方云/123云盘</p>
<p>
<el-button><strong @click="getInfo">刷新API调用统计</strong></el-button>
</p>
<p>节点1: 成功:{{ node1Info.success }},失败:{{ node1Info.fail }},总数:{{ node1Info.total }}</p>
<p>节点2: 成功:{{ node2Info.success }},失败:{{ node2Info.fail }},总数:{{ node2Info.total }}</p>
</div>
<hr>
<div class="main" v-loading="isLoading">
<div class="grid-content">
<!-- 开关按钮控制是否自动读取剪切板 -->
<el-switch
v-model="autoReadClipboard"
active-text="自动识别剪切板"
></el-switch>
<el-input placeholder="请粘贴分享链接(http://或https://)" v-model="link" id="url">
<template #prepend>分享链接</template>
<template #append v-if="!autoReadClipboard">
<el-button @click="() => getPaste(1)">读取剪切板</el-button>
</template>
<el-input placeholder="请粘贴分享链接" v-model="link" id="url" lass="input-with-select">
<strong slot="prepend">分享链接</strong>
<el-button slot="append" @click="onSubmit">解析</el-button>
</el-input>
<el-input placeholder="请输入密码" v-model="password" id="url">
<template #prepend>分享密码</template>
</el-input>
<el-input v-show="getLink2" :value="getLink2" id="url">
<template #prepend>智能直链</template>
<template #append>
<el-button v-clipboard:copy="getLink2"
<el-input placeholder="请输入密码" v-model="password" id="url" lass="input-with-select"></el-input>
<el-input v-show="respData.data" placeholder="解析地址" :value="getLink2" id="url" lass="input-with-select">
<el-button slot="append" v-clipboard:copy="getLink2"
v-clipboard:success="onCopy"
v-clipboard:error="onError">
<el-icon><CopyDocument/></el-icon>
v-clipboard:error="onError">点我复制
</el-button>
</template>
</el-input>
<p style="text-align: center">
<el-button style="margin-left: 40px;margin-bottom: 10px" @click="onSubmit">解析测试</el-button>
<el-button style="margin-left: 20px;margin-bottom: 10px" @click="genMd">生成Markdown链接</el-button>
<el-button style="margin-left: 20px" @click="generateQRCode">生成二维码</el-button>
<el-button style="margin-left: 20px" @click="getTj">链接信息统计</el-button>
</p>
</div>
<div v-if="respData.code" style="margin-top: 10px">
<div v-show="respData.code" style="margin-top: 10px">
<strong>解析结果: </strong>
<json-viewer
:value="respData"
@@ -82,38 +47,6 @@
/>
<a :href="downUrl" v-show="downUrl">点击下载</a>
</div>
<div v-if="mdText" style="text-align: center">
<el-input :value="mdText" readonly>
<template #append>
<el-button v-clipboard:copy="mdText"
v-clipboard:success="onCopy"
v-clipboard:error="onError">
<el-icon><CopyDocument/></el-icon>
</el-button>
</template>
</el-input>
</div>
<div style="text-align: center" v-show="showQrc">
<canvas ref="qrcodeCanvas"></canvas>
<div style="text-align: center"><el-link target="_blank" :href="codeUrl">{{ codeUrl }}</el-link></div>
</div>
<div v-if="tjData.shareLinkInfo">
<el-descriptions class="margin-top" title="分享详情" :column="1" border>
<template slot="extra">
<el-button type="primary" size="small">操作</el-button>
</template>
<el-descriptions-item label="网盘名称">{{ tjData.shareLinkInfo.panName }}</el-descriptions-item>
<el-descriptions-item label="网盘标识">{{ tjData.shareLinkInfo.type }}</el-descriptions-item>
<el-descriptions-item label="分享Key">{{ tjData.shareLinkInfo.shareKey }}</el-descriptions-item>
<el-descriptions-item label="分享链接"> <el-link target="_blank" :href="tjData.shareLinkInfo.shareUrl">{{ tjData.shareLinkInfo.shareUrl }}</el-link></el-descriptions-item>
<el-descriptions-item label="jsonApi链接"> <el-link target="_blank" :href="tjData.apiLink">{{ tjData.apiLink }}</el-link></el-descriptions-item>
<el-descriptions-item label="302下载链接"> <el-link target="_blank" :href="tjData.downLink">{{ tjData.downLink }}</el-link></el-descriptions-item>
<el-descriptions-item label="解析次数">{{ tjData.parserTotal }}</el-descriptions-item>
<el-descriptions-item label="缓存命中次数">{{ tjData.cacheHitTotal }}</el-descriptions-item>
<el-descriptions-item label="总请求次数">{{ tjData.sumTotal }}</el-descriptions-item>
</el-descriptions>
</div>
</div>
</el-card>
</el-row>
@@ -122,30 +55,39 @@
<script>
import axios from 'axios'
import QRCode from 'qrcode'
import DarkMode from '@/components/DarkMode'
import parserUrl from './parserUrl1'
/*
蓝奏云 (lz)
登录, 上传, 下载, 分享
直链解析
奶牛快传 (cow)
登录, 上传, 下载, 分享
直链解析
移动云空间 (ec)
登录, 上传, 下载, 分享
直链解析
UC网盘 (uc)似乎已经失效,需要登录
登录, 上传, 下载, 分享
直链解析
小飞机网盘 (fj)
登录, 上传, 下载, 分享
直链解析
亿方云 (fc)
登录, 上传, 下载, 分享
直链解析
123云盘 (ye)
登录, 上传, 下载,, 分享
*/
export default {
name: 'App',
components: {DarkMode},
data() {
return {
// baseAPI: `${location.protocol}//${location.hostname}:6400`,
baseAPI: `${location.protocol}//${location.host}`,
autoReadClipboard: true, // 开关状态,默认为自动读取
current: {}, // 当前分享
showQrc: false,
codeUrl: '',
mdText: '',
link: "",
password: "",
isLoading: false,
downUrl: null,
avatar: "https://q2.qlogo.cn/headimg_dl?dst_uin=736226400&spec=640",
select: "lz",
respData: {},
tjData: {},
panList: [
{
name: "蓝奏云",
@@ -177,39 +119,28 @@ export default {
value: 'ye'
},
],
getLink: null,
getLink: '',
getLink2: '',
getLinkInfo: null,
node1Info: {},
node2Info: {},
}
},
methods: {
// toggleDark() {
// toggleDark()
// },
check() {
this.mdText = ''
this.showQrc = false
this.respData = {}
this.tjData = {}
if (!this.link.startsWith("https://") && !this.link.startsWith("http://")) {
this.$message.error("请输入有效链接!")
throw new Error('请输入有效链接')
}
},
onSubmit() {
this.check()
if (!this.link.startsWith("https://")) {
this.$message.error("请输入有效链接!")
return
}
this.isLoading = true
this.downUrl = ''
this.respData = {}
this.getLink2 = `${this.baseAPI}/parser?url=${this.link}`
this.getLink2 = `${location.protocol}//${location.host}/parser?url=${this.link}`
// this.getLink = `${location.protocol}//${location.host}/api/json/parser?url=${this.link}`
// this.getLink = `${location.protocol}//${location.host}/json/parser`
this.getLink = `${location.protocol}//${location.host}/json/parser?url=${this.link}`
if (this.password) {
this.getLink2 += `&pwd=${this.password}`
this.getLink += `&pwd=${this.password}`
}
axios.get(this.getLink, {params: {url: this.link, pwd: this.password}}).then(
axios.get(this.getLink).then(
response => {
this.isLoading = false
this.respData = response.data
@@ -218,11 +149,10 @@ export default {
message: response.data.msg,
type: 'success'
})
this.downUrl = response.data.data.directLink
this.downUrl = response.data.data
} else {
this.$message.error(response.data.msg)
}
this.getInfo()
},
error => {
this.isLoading = false
@@ -238,126 +168,23 @@ export default {
},
getInfo() {
// 初始化统计信息
axios.get('/v2/statisticsInfo').then(
axios.get('/n1/statisticsInfo').then(
response => {
if (response.data.success) {
this.node1Info = response.data.data
}
})
// axios.get('/n2/statisticsInfo').then(
// response => {
// if (response.data.success) {
// this.node2Info = response.data.data
// }
// })
},
genMd() {
this.check()
axios.get(this.getLinkInfo, {params: {url: this.link, pwd: this.password}}).then(
axios.get('/n2/statisticsInfo').then(
response => {
this.isLoading = false
if (response.data.code === 200) {
this.$message({
message: response.data.msg,
type: 'success'
if (response.data.success) {
this.node2Info = response.data.data
}
})
this.mdText = this.buildMd('快速下载地址',response.data.data.downLink)
} else {
this.$message.error(response.data.msg)
}
});
},
buildMd(title, url) {
return `[${title}](${url})`
},
generateQRCode() {
this.check()
const options = { // 设置二维码的参数,例如大小、边距等
width: 150,
height: 150,
margin: 2
};
axios.get(this.getLinkInfo, {params: {url: this.link, pwd: this.password}}).then(
response => {
this.isLoading = false
if (response.data.code === 200) {
this.$message({
message: response.data.msg,
type: 'success'
})
this.codeUrl = response.data.data.downLink
QRCode.toCanvas(this.$refs.qrcodeCanvas, this.codeUrl, options, error => {
if (error) console.error(error);
});
this.showQrc = true
} else {
this.$message.error(response.data.msg)
}
});
},
getTj() {
this.check()
axios.get(this.getLinkInfo, {params: {url: this.link, pwd: this.password}}).then(
response => {
this.isLoading = false
if (response.data.code === 200) {
this.$message({
message: response.data.msg,
type: 'success'
})
this.tjData = response.data.data
} else {
this.$message.error(response.data.msg)
}
});
},
async getPaste(v) {
const text = await navigator.clipboard.readText();
console.log('获取到的文本内容是:', text);
let linkInfo = parserUrl.parseLink(text);
let pwd = parserUrl.parsePwd(text) || '';
if (linkInfo.link) {
if(linkInfo.link !== this.link || pwd !== this.password ) {
this.password = pwd;
this.link = linkInfo.link;
this.getLink2 = `${this.baseAPI}/parser?url=${this.link}`
if (this.link) this.$message.success(`自动识别分享成功, 网盘类型: ${linkInfo.name}; 分享URL ${this.link}; 分享密码: ${this.password || '空'}`);
} else {
v || this.$message.warning(`[${linkInfo.name}]分享信息无变化`)
}
} else {
this.$message.warning("未能提取到分享链接, 该分享可能尚未支持, 你可以复制任意网盘/音乐App的分享到该页面, 系统智能识别")
}
},
},
mounted() {
this.getLinkInfo = `${this.baseAPI}/v2/linkInfo`
this.getLink = `${this.baseAPI}/json/parser`
let item = window.localStorage.getItem("autoReadClipboard");
if (item) {
this.autoReadClipboard = (item === 'true');
}
this.getInfo()
// 页面首次加载时,根据开关状态判断是否读取剪切板
if (this.autoReadClipboard) {
this.getPaste()
}
// 当文档获得焦点时触发
window.addEventListener('focus', () => {
if (this.autoReadClipboard) {
this.getPaste()
}
});
},
watch: {
autoReadClipboard(val) {
window.localStorage.setItem("autoReadClipboard", val)
}
}
}
</script>
@@ -372,6 +199,9 @@ export default {
max-width: 900px;
}
::selection {
background: rgba(0, 149, 255, .1);
}
body:before {
top: 0;
@@ -380,6 +210,7 @@ body:before {
bottom: 0;
opacity: .3;
z-index: -1;
content: "";
position: fixed;
}
@@ -393,6 +224,11 @@ body:before {
width: 130px;
}
.input-with-select .el-input-group__prepend {
background-color: #fff;
}
.box-card {
margin-top: 4em !important;
margin-bottom: 4em !important;
@@ -420,7 +256,8 @@ body:before {
}
.typo a {
color: #0077ff;
color: #2c3e50;
text-decoration: none;
}
hr {

Some files were not shown because too many files have changed in this diff Show More