17 Commits
2.2.0 ... 3.0.2

Author SHA1 Message Date
putyy
e024a812d0 Merge pull request #124 from putyy/wails
update md
2025-01-10 17:36:10 +08:00
putyy
f5b5767997 update md 2025-01-10 17:35:39 +08:00
putyy
5e7022ec81 Merge pull request #123 from putyy/wails
Linux支持
2025-01-10 17:33:40 +08:00
putyy
e54177e8bf Linux支持 2025-01-10 17:33:05 +08:00
putyy
69cc5383d1 Merge pull request #122 from taotieren/fix-linux
Add Linux install
2025-01-09 11:30:43 +08:00
taotieren
11f88e86e3 Add Linux install 2025-01-09 11:27:07 +08:00
putyy
9e87e64223 优化 2024-12-24 14:39:13 +08:00
putyy
9ffef9db8e Delete .DS_Store 2024-12-23 17:27:38 +08:00
putyy
6d2705112d 优化 2024-12-23 16:59:26 +08:00
putyy
f92d898136 merge 2024-12-22 16:32:47 +08:00
putyy
d6b4c37138 3.0全新版 2024-12-22 16:21:30 +08:00
putyy
b87ff38353 Update README.md 2024-12-17 10:30:24 +08:00
putyy
d9259bb259 Merge pull request #114 from putyy/dev
win7支持
2024-12-04 16:17:08 +08:00
putyy
8756e5357a 修改Md 2024-12-04 16:15:20 +08:00
putyy
4c09b8f3be x 2024-12-04 15:47:38 +08:00
putyy
6f3e361ee6 x 2024-12-04 15:46:57 +08:00
putyy
93d9dbe32b win7支持,修复目录缓存失效 2024-12-02 17:36:51 +08:00
177 changed files with 10242 additions and 5958 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -1 +0,0 @@
VITE_APP_API=""

View File

@@ -1 +0,0 @@
VITE_APP_API=""

36
.gitignore vendored
View File

@@ -1,33 +1,5 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
temp
test
dist-ssr
dist-electron
release
*.local
# Editor directories and files
.vscode/.debug.env
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
.env
# lockfile
package-lock.json
yarn.lock
pnpm-lock.yaml
test
build/bin
node_modules
frontend/dist

214
LICENSE
View File

@@ -1,21 +1,201 @@
MIT License
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
Copyright (c) 2023 草鞋没号
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
1. Definitions.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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
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.

View File

@@ -1,76 +1,47 @@
## res-downloader
### 爱享素材下载器【[加入群聊](https://qm.qq.com/q/mfDMSpCxQ4)】
🎯 基于 [electron-vite-vue](https://github.com/electron-vite/electron-vite-vue.git)
📦 操作简单、可获取不同类型资源
🖥️ 支持Win10、Win11、Mac、Linux
🌐 支持视频、音频、图片、m3u8、直播流等常见网络资源拦截
## res-downloader V3全新版来袭、全新实现支持更多设置视频号、直播流、m3u8预览等
### 爱享素材下载器【[加入群聊](https://qm.qq.com/q/HS8FdhpZCK)】
🎯 基于Go + [wails](https://github.com/wailsapp/wails)
📦 操作简单、可获取不同类型资源
🖥️ 支持Windows、Mac、Linux
🌐 支持视频、音频、图片、m3u8、直播流等常见网络资源
💪 支持微信视频号、小程序、抖音、快手、小红书、酷狗音乐、qq音乐等网络资源下载
👼 支持设置代理以获取特殊网络下的资源
## 软件下载
## 软件下载(Win7下载2.3.0版本)
🆕 [github下载](https://github.com/putyy/res-downloader/releases)
🆕 [蓝奏云下载 密码:9vs5](https://wwjv.lanzoum.com/b04wgtfyb)
## 使用方法
> 0. 安装一定要同意安装证书文件,安装一定要同意安装证书文件,安装一定要同意安装证书文件!
> 1. 打开本软件
> 2. 软件首页选择要获取的资源类型(默认选中的视频)
> 3. 打开要捕获的源, 如:视频号、网页、小程序等等
> 4. 返回软件首页即可看到资源列表
> 0. 安装时一定要同意安装证书文件、一定要允许网络访问
> 1. 打开本软件 软件首页左上角点击 “启动代理”
> 2. 软件首页选择要获取的资源类型(默认选中的全部)
> 3. 打开要捕获的源, 如:视频号、网页、小程序等等
> 4. 返回软件首页即可看到资源列表
## 软件截图
![](public/show.webp)
![](preview/show.webp)
## 常见问题
m3u8预览和下载
m3u8: 预览和下载:
> [下载](https://m3u8-down.gowas.cn/) [预览](https://m3u8play.com/)
直播流 预览和录制:
> [使用obs进行预览和录制]( https://obsproject.com/)
直播流: 预览和录制:
> [使用obs进行预览和录制 使用教程自行百度]( https://obsproject.com/)
下载慢、大视频下载失败(最新版本以内置aria2下载器)
下载慢、大视频下载失败
> 推荐使用如下工具加速下载,视频号可以下载完成后再到对应视频操作项选择 “视频解密(视频号)” 按钮
>> [Neat Download Manager](https://www.neatdownloadmanager.com/index.php/en/)、[Motrix](https://motrix.app/download)等软件进行下载
Win7无法使用
> 软件不支持,也无计划支持
打开本软件,无法正常拦截获取
> 检查系统代理是否正确设置 代理地址127.0.0.1 端口8899
关闭软件后无法正常上网
> 手动关闭系统代理设置
打开本软件后无法上网
> 手动删除安装标识锁文件,之后再打开软件会进行检查证书是否正确安装
>> MAC: /Users/你的用户名称/.res-downloader@putyy/res-downloader-installed.lock
>> Win: C:\Users\Admin\.res-downloader@putyy/res-downloader-installed.lock
其他问题
[github](https://github.com/putyy/res-downloader/issues) 、 [爱享论坛](https://s.gowas.cn/d/4089)
[github](https://github.com/putyy/res-downloader/issues) 、 [爱享论坛](https://s.gowas.cn/d/4089)
## 二次开发
> ps 打包慢的问题可以参考 https://www.putyy.com/articles/87
```sh
git clone https://github.com/putyy/res-downloader
cd res-downloader
yarn install
yarn run dev
# 打包mac
yarn run build --universal --mac
# 打包win
yarn run build --win
# 打包linux
yarn run build --linux
```
## 实现&初衷
## 实现 & 初衷
通过代理网络抓包拦截响应,筛选出有用的资源, 同fiddler、charles等抓包软件、浏览器F12打开控制也能达到目的只不过这些软件需要手动进行筛选对于小白用户上手还是有点难度本软件对部分资源做了特殊处理更适合大众用户所以就有了本项目。
## 免责声明

9
auto-imports.d.ts vendored
View File

@@ -1,9 +0,0 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
export {}
declare global {
}

BIN
build/.DS_Store vendored Normal file

Binary file not shown.

75
build/README.md Normal file
View File

@@ -0,0 +1,75 @@
## Mac
```bash
wails build -platform "darwin/universal" --dmg-name
create-dmg 'build/bin/res-downloader.app' \
--overwrite --dmg-title="res-downloader" \
--dmg-name "res-downloader_$(jq -r '.info.productVersion' wails.json).dmg" \
./build/bin
```
## Windows
```bash
wails build -f -nsis -platform "windows/amd64" -webview2 Embed
wails build -f -nsis -platform "windows/arm64" -webview2 Embed
```
## Linux
### docker方式
> x86_64
```bash
docker build --network host -f build/linux/dockerfile -t res-downloader-amd-linux .
docker run -it --name res-downloader-amd-build --network host --privileged -v ./:/www/res-downloader res-downloader-amd-linux /bin/bash
# 容器内
cd /www/res-downloader
wails build
# 打包debian
cp build/bin/res-downloader build/linux/Debian/usr/local/bin/
echo "$(cat build/linux/Debian/DEBIAN/.control | sed -e "s/{{Version}}/$(jq -r '.info.productVersion' wails.json)/g")" > build/linux/Debian/DEBIAN/control
dpkg-deb --build ./build/linux/Debian build/bin/res-downloader_$(jq -r '.info.productVersion' wails.json)_x64.deb
# 打包AppImage
cp build/bin/res-downloader build/linux/AppImage/usr/bin/
# 复制WebKit相关文件
pushd build/linux/AppImage
find /usr/lib* -name WebKitNetworkProcess -exec mkdir -p $(dirname '{}') \; -exec cp --parents '{}' "." \; || true
find /usr/lib* -name WebKitWebProcess -exec mkdir -p $(dirname '{}') \; -exec cp --parents '{}' "." \; || true
find /usr/lib* -name libwebkit2gtkinjectedbundle.so -exec mkdir -p $(dirname '{}') \; -exec cp --parents '{}' "." \; || true
popd
wget -O ./build/bin/appimagetool-x86_64.AppImage https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-x86_64.AppImage
chmod +x ./build/bin/appimagetool-x86_64.AppImage
./build/bin/appimagetool-x86_64.AppImage build/linux/AppImage build/bin/res-downloader_$(jq -r '.info.productVersion' wails.json)_x64.AppImage
```
> arm64
```bash
# arm
docker build --platform linux/arm64 --network host -f build/linux/dockerfile -t res-downloader-arm-linux .
docker run --platform linux/arm64 -it --name res-downloader-arm-build --network host --privileged -v ./:/www/res-downloader res-downloader-arm-linux /bin/bash
# 容器内
cd /www/res-downloader
wails build
# 打包debian
cp build/bin/res-downloader build/linux/Debian/usr/local/bin/
echo "$(cat build/linux/Debian/DEBIAN/.control | sed -e "s/{{Version}}/$(jq -r '.info.productVersion' wails.json)/g")" > build/linux/Debian/DEBIAN/control
dpkg-deb --build ./build/linux/Debian build/bin/res-downloader_$(jq -r '.info.productVersion' wails.json)_arm.deb
```
> ArchLinux环境
> 安装
> yay -Syu res-downloader
[![Packaging status](https://repology.org/badge/vertical-allrepos/res-downloader.svg)](https://repology.org/project/res-downloader/versions)
>
```bash
git clone https://github.com/putyy/res-downloader.git
cd res-downloader
wails build
cd build
sudo install -Dvm755 bin/res-downloader -t /usr/bin
sudo install -Dvm644 appicon.png /usr/share/icons/hicolor/512x512/apps/res-downloader.png
sudo install -Dvm644 linux/res-downloader.desktop /usr/share/applications/res-downloader.desktop
```

View File

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 120 KiB

View File

@@ -0,0 +1,68 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleName</key>
<string>{{.Info.ProductName}}</string>
<key>CFBundleExecutable</key>
<string>{{.Name}}</string>
<key>CFBundleIdentifier</key>
<string>com.wails.{{.Name}}</string>
<key>CFBundleVersion</key>
<string>{{.Info.ProductVersion}}</string>
<key>CFBundleGetInfoString</key>
<string>{{.Info.Comments}}</string>
<key>CFBundleShortVersionString</key>
<string>{{.Info.ProductVersion}}</string>
<key>CFBundleIconFile</key>
<string>iconfile</string>
<key>LSMinimumSystemVersion</key>
<string>10.13.0</string>
<key>NSHighResolutionCapable</key>
<string>true</string>
<key>NSHumanReadableCopyright</key>
<string>{{.Info.Copyright}}</string>
{{if .Info.FileAssociations}}
<key>CFBundleDocumentTypes</key>
<array>
{{range .Info.FileAssociations}}
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>{{.Ext}}</string>
</array>
<key>CFBundleTypeName</key>
<string>{{.Name}}</string>
<key>CFBundleTypeRole</key>
<string>{{.Role}}</string>
<key>CFBundleTypeIconFile</key>
<string>{{.IconName}}</string>
</dict>
{{end}}
</array>
{{end}}
{{if .Info.Protocols}}
<key>CFBundleURLTypes</key>
<array>
{{range .Info.Protocols}}
<dict>
<key>CFBundleURLName</key>
<string>com.wails.{{.Scheme}}</string>
<key>CFBundleURLSchemes</key>
<array>
<string>{{.Scheme}}</string>
</array>
<key>CFBundleTypeRole</key>
<string>{{.Role}}</string>
</dict>
{{end}}
</array>
{{end}}
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
</dict>
</plist>

63
build/darwin/Info.plist Normal file
View File

@@ -0,0 +1,63 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleName</key>
<string>{{.Info.ProductName}}</string>
<key>CFBundleExecutable</key>
<string>{{.Name}}</string>
<key>CFBundleIdentifier</key>
<string>com.wails.{{.Name}}</string>
<key>CFBundleVersion</key>
<string>{{.Info.ProductVersion}}</string>
<key>CFBundleGetInfoString</key>
<string>{{.Info.Comments}}</string>
<key>CFBundleShortVersionString</key>
<string>{{.Info.ProductVersion}}</string>
<key>CFBundleIconFile</key>
<string>iconfile</string>
<key>LSMinimumSystemVersion</key>
<string>10.13.0</string>
<key>NSHighResolutionCapable</key>
<string>true</string>
<key>NSHumanReadableCopyright</key>
<string>{{.Info.Copyright}}</string>
{{if .Info.FileAssociations}}
<key>CFBundleDocumentTypes</key>
<array>
{{range .Info.FileAssociations}}
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>{{.Ext}}</string>
</array>
<key>CFBundleTypeName</key>
<string>{{.Name}}</string>
<key>CFBundleTypeRole</key>
<string>{{.Role}}</string>
<key>CFBundleTypeIconFile</key>
<string>{{.IconName}}</string>
</dict>
{{end}}
</array>
{{end}}
{{if .Info.Protocols}}
<key>CFBundleURLTypes</key>
<array>
{{range .Info.Protocols}}
<dict>
<key>CFBundleURLName</key>
<string>com.wails.{{.Scheme}}</string>
<key>CFBundleURLSchemes</key>
<array>
<string>{{.Scheme}}</string>
</array>
<key>CFBundleTypeRole</key>
<string>{{.Role}}</string>
</dict>
{{end}}
</array>
{{end}}
</dict>
</plist>

5
build/linux/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
!.gitkeep
debian/usr/local/bin/*
debian/DEBIAN/control
AppImage/usr/bin/*
AppImage/usr/lib/*

View File

@@ -0,0 +1 @@
res-downloader.png

View File

@@ -0,0 +1,8 @@
[Desktop Entry]
Type=Application
Name=res-downloader
Comment=This is a high-value and high-performance and diverse resource downloader called res-downloader
Exec=/usr/bin/res-downloader
Icon=/usr/share/icons/hicolor/256x256/apps/res-downloader
Terminal=false
Categories=Utility;

BIN
build/linux/AppImage/usr/.DS_Store vendored Normal file

Binary file not shown.

View File

View File

@@ -0,0 +1,8 @@
[Desktop Entry]
Type=Application
Name=res-downloader
Comment=This is a high-value and high-performance and diverse resource downloader called res-downloader
Exec=/usr/bin/res-downloader
Icon=/usr/share/icons/hicolor/256x256/apps/res-downloader
Terminal=false
Categories=Utility;

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
build/linux/Debian/.DS_Store vendored Normal file

Binary file not shown.

BIN
build/linux/Debian/DEBIAN/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,9 @@
Package: res-downloader
Version: {{Version}}
Section: utils
Priority: optional
Architecture: amd64
Depends: libwebkit2gtk-4.0-37
Maintainer: putyy@qq.com
Homepage: https://github.com/putyy/res-downloader
Description: This is a high-value and high-performance and diverse resource downloader called res-downloader

BIN
build/linux/Debian/usr/local/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,8 @@
[Desktop Entry]
Type=Application
Name=res-downloader
Comment=This is a high-value and high-performance and diverse resource downloader called res-downloader
Exec=/usr/local/bin/res-downloader
Icon=/usr/share/icons/hicolor/256x256/apps/res-downloader.png
Terminal=false
Categories=Utility;

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

29
build/linux/dockerfile Normal file
View File

@@ -0,0 +1,29 @@
FROM golang:1.23.4-bookworm
WORKDIR /
RUN apt-get update && \
apt-get install -y --fix-missing \
build-essential \
git \
jq \
kmod \
fuse \
libgtk-3-dev \
libwebkit2gtk-4.0-dev \
nsis \
wget \
curl \
gnupg2 \
lsb-release \
libfuse-dev \
libfuse2 \
file \
&& rm -rf /var/lib/apt/lists/*
RUN curl -sL https://deb.nodesource.com/setup_20.x | bash - \
&& apt-get install -y nodejs
RUN go install github.com/wailsapp/wails/v2/cmd/wails@latest
RUN wails doctor

BIN
build/windows/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

15
build/windows/info.json Normal file
View File

@@ -0,0 +1,15 @@
{
"fixed": {
"file_version": "{{.Info.ProductVersion}}"
},
"info": {
"0000": {
"ProductVersion": "{{.Info.ProductVersion}}",
"CompanyName": "{{.Info.CompanyName}}",
"FileDescription": "{{.Info.ProductName}}",
"LegalCopyright": "{{.Info.Copyright}}",
"ProductName": "{{.Info.ProductName}}",
"Comments": "{{.Info.Comments}}"
}
}
}

View File

@@ -0,0 +1,114 @@
Unicode true
####
## Please note: Template replacements don't work in this file. They are provided with default defines like
## mentioned underneath.
## If the keyword is not defined, "wails_tools.nsh" will populate them with the values from ProjectInfo.
## If they are defined here, "wails_tools.nsh" will not touch them. This allows to use this project.nsi manually
## from outside of Wails for debugging and development of the installer.
##
## For development first make a wails nsis build to populate the "wails_tools.nsh":
## > wails build --target windows/amd64 --nsis
## Then you can call makensis on this file with specifying the path to your binary:
## For a AMD64 only installer:
## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app.exe
## For a ARM64 only installer:
## > makensis -DARG_WAILS_ARM64_BINARY=..\..\bin\app.exe
## For a installer with both architectures:
## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe
####
## The following information is taken from the ProjectInfo file, but they can be overwritten here.
####
## !define INFO_PROJECTNAME "MyProject" # Default "{{.Name}}"
## !define INFO_COMPANYNAME "MyCompany" # Default "{{.Info.CompanyName}}"
## !define INFO_PRODUCTNAME "MyProduct" # Default "{{.Info.ProductName}}"
## !define INFO_PRODUCTVERSION "1.0.0" # Default "{{.Info.ProductVersion}}"
## !define INFO_COPYRIGHT "Copyright" # Default "{{.Info.Copyright}}"
###
## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe"
## !define UNINST_KEY_NAME "UninstKeyInRegistry" # Default "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}"
####
## !define REQUEST_EXECUTION_LEVEL "admin" # Default "admin" see also https://nsis.sourceforge.io/Docs/Chapter4.html
####
## Include the wails tools
####
!include "wails_tools.nsh"
# The version information for this two must consist of 4 parts
VIProductVersion "${INFO_PRODUCTVERSION}.0"
VIFileVersion "${INFO_PRODUCTVERSION}.0"
VIAddVersionKey "CompanyName" "${INFO_COMPANYNAME}"
VIAddVersionKey "FileDescription" "${INFO_PRODUCTNAME} Installer"
VIAddVersionKey "ProductVersion" "${INFO_PRODUCTVERSION}"
VIAddVersionKey "FileVersion" "${INFO_PRODUCTVERSION}"
VIAddVersionKey "LegalCopyright" "${INFO_COPYRIGHT}"
VIAddVersionKey "ProductName" "${INFO_PRODUCTNAME}"
# Enable HiDPI support. https://nsis.sourceforge.io/Reference/ManifestDPIAware
ManifestDPIAware true
!include "MUI.nsh"
!define MUI_ICON "..\icon.ico"
!define MUI_UNICON "..\icon.ico"
# !define MUI_WELCOMEFINISHPAGE_BITMAP "resources\leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314
!define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps
!define MUI_ABORTWARNING # This will warn the user if they exit from the installer.
!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page.
# !insertmacro MUI_PAGE_LICENSE "resources\eula.txt" # Adds a EULA page to the installer
!insertmacro MUI_PAGE_DIRECTORY # In which folder install page.
!insertmacro MUI_PAGE_INSTFILES # Installing page.
!insertmacro MUI_PAGE_FINISH # Finished installation page.
!insertmacro MUI_UNPAGE_INSTFILES # Uinstalling page
!insertmacro MUI_LANGUAGE "English" # Set the Language of the installer
## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1
#!uninstfinalize 'signtool --file "%1"'
#!finalize 'signtool --file "%1"'
Name "${INFO_PRODUCTNAME}"
OutFile "..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file.
InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder).
ShowInstDetails show # This will always show the installation details.
Function .onInit
!insertmacro wails.checkArchitecture
FunctionEnd
Section
!insertmacro wails.setShellContext
!insertmacro wails.webview2runtime
SetOutPath $INSTDIR
!insertmacro wails.files
CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"
CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"
!insertmacro wails.associateFiles
!insertmacro wails.associateCustomProtocols
!insertmacro wails.writeUninstaller
SectionEnd
Section "uninstall"
!insertmacro wails.setShellContext
RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath
RMDir /r $INSTDIR
Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk"
Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk"
!insertmacro wails.unassociateFiles
!insertmacro wails.unassociateCustomProtocols
!insertmacro wails.deleteUninstaller
SectionEnd

Binary file not shown.

View File

@@ -0,0 +1,236 @@
# DO NOT EDIT - Generated automatically by `wails build`
!include "x64.nsh"
!include "WinVer.nsh"
!include "FileFunc.nsh"
!ifndef INFO_PROJECTNAME
!define INFO_PROJECTNAME "res-downloader"
!endif
!ifndef INFO_COMPANYNAME
!define INFO_COMPANYNAME "res-downloader"
!endif
!ifndef INFO_PRODUCTNAME
!define INFO_PRODUCTNAME "res-downloader"
!endif
!ifndef INFO_PRODUCTVERSION
!define INFO_PRODUCTVERSION "3.0.2"
!endif
!ifndef INFO_COPYRIGHT
!define INFO_COPYRIGHT "Copyright © 2023"
!endif
!ifndef PRODUCT_EXECUTABLE
!define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe"
!endif
!ifndef UNINST_KEY_NAME
!define UNINST_KEY_NAME "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}"
!endif
!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}"
!ifndef REQUEST_EXECUTION_LEVEL
!define REQUEST_EXECUTION_LEVEL "admin"
!endif
RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}"
!ifdef ARG_WAILS_AMD64_BINARY
!define SUPPORTS_AMD64
!endif
!ifdef ARG_WAILS_ARM64_BINARY
!define SUPPORTS_ARM64
!endif
!ifdef SUPPORTS_AMD64
!ifdef SUPPORTS_ARM64
!define ARCH "amd64_arm64"
!else
!define ARCH "amd64"
!endif
!else
!ifdef SUPPORTS_ARM64
!define ARCH "arm64"
!else
!error "Wails: Undefined ARCH, please provide at least one of ARG_WAILS_AMD64_BINARY or ARG_WAILS_ARM64_BINARY"
!endif
!endif
!macro wails.checkArchitecture
!ifndef WAILS_WIN10_REQUIRED
!define WAILS_WIN10_REQUIRED "This product is only supported on Windows 10 (Server 2016) and later."
!endif
!ifndef WAILS_ARCHITECTURE_NOT_SUPPORTED
!define WAILS_ARCHITECTURE_NOT_SUPPORTED "This product can't be installed on the current Windows architecture. Supports: ${ARCH}"
!endif
${If} ${AtLeastWin10}
!ifdef SUPPORTS_AMD64
${if} ${IsNativeAMD64}
Goto ok
${EndIf}
!endif
!ifdef SUPPORTS_ARM64
${if} ${IsNativeARM64}
Goto ok
${EndIf}
!endif
IfSilent silentArch notSilentArch
silentArch:
SetErrorLevel 65
Abort
notSilentArch:
MessageBox MB_OK "${WAILS_ARCHITECTURE_NOT_SUPPORTED}"
Quit
${else}
IfSilent silentWin notSilentWin
silentWin:
SetErrorLevel 64
Abort
notSilentWin:
MessageBox MB_OK "${WAILS_WIN10_REQUIRED}"
Quit
${EndIf}
ok:
!macroend
!macro wails.files
!ifdef SUPPORTS_AMD64
${if} ${IsNativeAMD64}
File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_AMD64_BINARY}"
${EndIf}
!endif
!ifdef SUPPORTS_ARM64
${if} ${IsNativeARM64}
File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_ARM64_BINARY}"
${EndIf}
!endif
!macroend
!macro wails.writeUninstaller
WriteUninstaller "$INSTDIR\uninstall.exe"
SetRegView 64
WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}"
WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}"
WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}"
WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}"
WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\""
WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S"
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
IntFmt $0 "0x%08X" $0
WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0"
!macroend
!macro wails.deleteUninstaller
Delete "$INSTDIR\uninstall.exe"
SetRegView 64
DeleteRegKey HKLM "${UNINST_KEY}"
!macroend
!macro wails.setShellContext
${If} ${REQUEST_EXECUTION_LEVEL} == "admin"
SetShellVarContext all
${else}
SetShellVarContext current
${EndIf}
!macroend
# Install webview2 by launching the bootstrapper
# See https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment
!macro wails.webview2runtime
!ifndef WAILS_INSTALL_WEBVIEW_DETAILPRINT
!define WAILS_INSTALL_WEBVIEW_DETAILPRINT "Installing: WebView2 Runtime"
!endif
SetRegView 64
# If the admin key exists and is not empty then webview2 is already installed
ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
${If} $0 != ""
Goto ok
${EndIf}
${If} ${REQUEST_EXECUTION_LEVEL} == "user"
# If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed
ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
${If} $0 != ""
Goto ok
${EndIf}
${EndIf}
SetDetailsPrint both
DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}"
SetDetailsPrint listonly
InitPluginsDir
CreateDirectory "$pluginsdir\webview2bootstrapper"
SetOutPath "$pluginsdir\webview2bootstrapper"
File "tmp\MicrosoftEdgeWebview2Setup.exe"
ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install'
SetDetailsPrint both
ok:
!macroend
# Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b
!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND
; Backup the previously associated file class
ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" ""
WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0"
WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}"
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}`
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}`
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open"
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}`
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}`
!macroend
!macro APP_UNASSOCIATE EXT FILECLASS
; Backup the previously associated file class
ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup`
WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0"
DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}`
!macroend
!macro wails.associateFiles
; Create file associations
!macroend
!macro wails.unassociateFiles
; Delete app associations
!macroend
!macro CUSTOM_PROTOCOL_ASSOCIATE PROTOCOL DESCRIPTION ICON COMMAND
DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}"
WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "" "${DESCRIPTION}"
WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "URL Protocol" ""
WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\DefaultIcon" "" "${ICON}"
WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell" "" ""
WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open" "" ""
WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open\command" "" "${COMMAND}"
!macroend
!macro CUSTOM_PROTOCOL_UNASSOCIATE PROTOCOL
DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}"
!macroend
!macro wails.associateCustomProtocols
; Create custom protocols associations
!macroend
!macro wails.unassociateCustomProtocols
; Delete app custom protocol associations
!macroend

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<assemblyIdentity type="win32" name="com.wails.{{.Name}}" version="{{.Info.ProductVersion}}.0" processorArchitecture="*"/>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
</dependentAssembly>
</dependency>
<asmv3:application>
<asmv3:windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- fallback for Windows 7 and 8 -->
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2,permonitor</dpiAwareness> <!-- falls back to per-monitor if per-monitor v2 is not supported -->
</asmv3:windowsSettings>
</asmv3:application>
</assembly>

34
components.d.ts vendored
View File

@@ -1,34 +0,0 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
declare module 'vue' {
export interface GlobalComponents {
ElButton: typeof import('element-plus/es')['ElButton']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElFooter: typeof import('element-plus/es')['ElFooter']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput']
ElMain: typeof import('element-plus/es')['ElMain']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption']
ElPopover: typeof import('element-plus/es')['ElPopover']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
Footer: typeof import('./src/components/layout/Footer.vue')['default']
Index: typeof import('./src/components/layout/Index.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
Sidebar: typeof import('./src/components/layout/Sidebar.vue')['default']
}
}

198
core/app.go Normal file
View File

@@ -0,0 +1,198 @@
package core
import (
"context"
"embed"
"github.com/vrischmann/userdir"
"github.com/wailsapp/wails/v2/pkg/runtime"
"os"
"path/filepath"
sysRuntime "runtime"
"strconv"
"strings"
"time"
)
type App struct {
ctx context.Context
assets embed.FS
AppName string `json:"AppName"`
Version string `json:"Version"`
Description string `json:"Description"`
Copyright string `json:"Copyright"`
UserDir string `json:"-"`
LockFile string `json:"-"`
PublicCrt []byte `json:"-"`
PrivateKey []byte `json:"-"`
IsProxy bool `json:"-"`
}
var (
appOnce *App
globalConfig *Config
globalLogger *Logger
resourceOnce *Resource
systemOnce *SystemSetup
proxyOnce *Proxy
httpServerOnce *HttpServer
)
func GetApp(assets embed.FS) *App {
if appOnce == nil {
appOnce = &App{
assets: assets,
AppName: "res-downloader",
Version: "3.0.2",
Description: "res-downloader是一款集网络资源嗅探 + 高速下载功能于一体的软件,高颜值、高性能和多样化,提供个人用户下载自己上传到各大平台的网络资源功能!",
Copyright: "Copyright © 2023~" + strconv.Itoa(time.Now().Year()),
PublicCrt: []byte(`
-----BEGIN CERTIFICATE-----
MIIDwzCCAqugAwIBAgIUFAnC6268dp/z1DR9E1UepiWgWzkwDQYJKoZIhvcNAQEL
BQAwcDELMAkGA1UEBhMCQ04xEjAQBgNVBAgMCUNob25ncWluZzESMBAGA1UEBwwJ
Q2hvbmdxaW5nMQ4wDAYDVQQKDAVnb3dhczEWMBQGA1UECwwNSVQgRGVwYXJ0bWVu
dDERMA8GA1UEAwwIZ293YXMuY24wIBcNMjQwMjE4MDIwOTI2WhgPMjEyNDAxMjUw
MjA5MjZaMHAxCzAJBgNVBAYTAkNOMRIwEAYDVQQIDAlDaG9uZ3FpbmcxEjAQBgNV
BAcMCUNob25ncWluZzEOMAwGA1UECgwFZ293YXMxFjAUBgNVBAsMDUlUIERlcGFy
dG1lbnQxETAPBgNVBAMMCGdvd2FzLmNuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEA3A7dt7eoqAaBxv2Npjo8Z7VkGvXT93jZfpgAuuNuQ5RLcnOnMzQC
CrrjPcLfsAMA0AIK3eUWsXXKSR9SZTJBLQRZCJHZ9AIPfA+58JVQPTjd8UIuQZJf
rDf6FjhPJTsLzcjTU+mT7t6lEimPEl2VWN9eXWqs9nkVrJtqLao6m1hoYfXOxRh6
96/WgBtPHcmjujryteBiSITVflDjx+YQzDGsbqw7fM52klMPd2+w/vmhJ4pxq6P7
Ni2OBvdXYDPIuLfPFFqG16arORjBkyNCJy19iOuh5LXh+EUX11wvbLwNgsTd8j9v
eBSD+4HUUNQhiXiXJbs7I7cdFYthvb609QIDAQABo1MwUTAdBgNVHQ4EFgQUdI8p
aY1A47rWCRvQKSTRCCk6FoMwHwYDVR0jBBgwFoAUdI8paY1A47rWCRvQKSTRCCk6
FoMwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEArMCAfqidgXL7
cW5TAZTCqnUeKzbbqMJgk6iFsma8scMRsUXz9ZhF0UVf98376KvoJpy4vd81afbi
TehQ8wVBuKTtkHeh/MkXMWC/FU4HqSjtvxpic2+Or5dMjIrfa5VYPgzfqNaBIUh4
InD5lo8b/n5V+jdwX7RX9VYAKug6QZlCg5YSKIvgNRChb36JmrGcvsp5R0Vejnii
e3oowvgwikqm6XR6BEcRpPkztqcKST7jPFGHiXWsAqiibc+/plMW9qebhfMXEGhQ
5yVNeSxX2zqasZvP/fRy+3I5iVilxtKvJuVpPZ0UZzGS0CJ/lF67ntibktiPa3sR
D8HixYbEDg==
-----END CERTIFICATE-----
`),
PrivateKey: []byte(`
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDcDt23t6ioBoHG
/Y2mOjxntWQa9dP3eNl+mAC6425DlEtyc6czNAIKuuM9wt+wAwDQAgrd5RaxdcpJ
H1JlMkEtBFkIkdn0Ag98D7nwlVA9ON3xQi5Bkl+sN/oWOE8lOwvNyNNT6ZPu3qUS
KY8SXZVY315daqz2eRWsm2otqjqbWGhh9c7FGHr3r9aAG08dyaO6OvK14GJIhNV+
UOPH5hDMMaxurDt8znaSUw93b7D++aEninGro/s2LY4G91dgM8i4t88UWobXpqs5
GMGTI0InLX2I66HkteH4RRfXXC9svA2CxN3yP294FIP7gdRQ1CGJeJcluzsjtx0V
i2G9vrT1AgMBAAECggEAF0obfQ4a82183qqHC0iui+tOpOvPeyl3G0bLDPx09wIC
2iITV//xF2GgGzE8q0wmEd2leMZ+GFn3BrYh6kPfUfxbz+RfxMtTCDZB34xt6YzT
MG1op9ft+DQUa7WZ6r7NCQJwGzllRqqZncp4MeFlpPo+6nQXyh4WhSYNnredbENE
uPZ63Kme4RZfMvtVso+XgAQM3oDih0onv1YitmNQpL9rRzlthTfybAT4737DBINq
zsmBNE6QIsXnSKpzo11OtDgof2QM9ac6eAXf73oTpDxfodwCotILytKn+8WYvlR+
T15uuknb4M3XI1FPVolkF4qtK5SLAAbVzV4DsCmuIQKBgQD6bTKKbL2huvU6dEKx
bgS079LfQUxxOTClgwkhVsMxRtvcPBnHYMAsPK4mnMhEh9x+TF6wxMx0pmhQluPI
ZULNBj/qdoiBL0RwVLA+9jgE0NeWB3XXFDsEavQBr9Q8CC0uzrsgsxFcvHpqqs2Q
RtngxRWtJP06D6mKC23s4YjDHwKBgQDg9KUCFqOmWcRXyeg9gYMC4jFFQw4lUQBd
sYpqSMHDw1b+T1W/dCPbwbxZL/+d8y930BYy9QYDtQwHdLyXCH0pHM7S6rfgr5xk
2Szd8xBUIqmeV/zcR00mTeQHJ1M50VHfclAVgZgkpWSoLwbX+bXyx/mfqLAtynZ5
yU9RfrT5awKBgQC0uJ8TlFvZXjFgyMvkfY/5/2R/ZwFCaFI573FkVNeyNP+vVNQJ
tUGZ6wSGqvg/tIgjwPtIuA0QVZLMLcgeMy1dBhiUHIxwJetO4V77YPaWSxx5kdKx
r1DT5FdI7FnOJNxufhQ/CdsKwJ3bYn3Mk8TiV3hIJnx0LR9dltfybeQjYwKBgDOY
6aApATBOtrJMJXC2HA61QwfX8Y6tnZ/f8RefyJHWZEXAfLKFORRWw5TRZZgdB247
1Furx81h4Xh0Vi1uTQb5DJdkLvjiTsTy60+dSMmDidQ/6ke8Mv3uL7dUVcqVMGpI
FgZYy0TcitHot3EiXZFqPN9aGc7m+XXFruPKZEgxAoGBAMA96jsow7CzulU+GRW8
Njg4zWuAEVErgPoNBcOXAVWLCTU/qGIEMNpZL6Ok34kf13pJDMjQ8eDuQHu5CSqf
0ul5Zy85fwfVq2IvNAyYT8eflQprTejFw22CHhfPBfADVW9ro8dK/Jw+J/31Vh7V
ILKEQKmPPzKs7kp/7Nz+2cT3
-----END PRIVATE KEY-----
`),
}
appOnce.UserDir = filepath.Join(userdir.GetConfigHome(), appOnce.AppName)
appOnce.LockFile = filepath.Join(appOnce.UserDir, "install.lock")
initLogger()
initConfig()
initProxy()
initResource()
initHttpServer()
initSystem()
}
return appOnce
}
func (a *App) Startup(ctx context.Context) {
a.ctx = ctx
go httpServerOnce.run()
time.AfterFunc(200*time.Millisecond, func() {
if globalConfig.AutoProxy {
appOnce.OpenSystemProxy()
}
})
go func() {
if a.isInstall() {
return
}
err := os.MkdirAll(a.UserDir, os.ModePerm)
if err != nil {
return
}
a.installCert()
}()
}
func (a *App) OnExit() {
a.UnsetSystemProxy()
globalLogger.Close()
}
func (a *App) installCert() {
if res, err := systemOnce.installCert(); err != nil {
if sysRuntime.GOOS == "darwin" {
_ = runtime.ClipboardSetText(appOnce.ctx, `echo "输入本地登录密码" && sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "`+systemOnce.CertFile+`" && touch `+a.LockFile+` && echo "安装完成"`)
DialogErr("证书安装失败,请打开终端执行安装(命令已复制到剪切板),err:" + err.Error() + ", " + res)
} else if sysRuntime.GOOS == "windows" && strings.Contains(err.Error(), "Access is denied.") {
DialogErr("首次启用本软件,请使用鼠标右键选择以管理员身份运行")
} else if sysRuntime.GOOS == "linux" && strings.Contains(err.Error(), "Access is denied.") {
DialogErr("证书路径: " + systemOnce.CertFile + ", 请手动安装,安装完成后请执行: touch" + a.LockFile + " err:" + err.Error() + ", " + res)
} else {
globalLogger.Esg(err, res)
DialogErr("err:" + err.Error() + ", " + res)
}
} else {
if err := a.lock(); err != nil {
globalLogger.err(err)
}
}
}
func (a *App) OpenSystemProxy() bool {
if a.IsProxy {
return true
}
err := systemOnce.setProxy()
if err == nil {
a.IsProxy = true
return true
}
DialogErr("设置失败" + err.Error())
return false
}
func (a *App) UnsetSystemProxy() bool {
if !a.IsProxy {
return true
}
err := systemOnce.unsetProxy()
if err == nil {
a.IsProxy = false
return true
}
DialogErr("设置失败")
return false
}
func (a *App) isInstall() bool {
return FileExist(a.LockFile)
}
func (a *App) lock() error {
err := os.WriteFile(a.LockFile, []byte("success"), 0777)
if err != nil {
return err
}
return nil
}

81
core/config.go Normal file
View File

@@ -0,0 +1,81 @@
package core
import (
"encoding/json"
"runtime"
"strconv"
"strings"
)
// Config struct
type Config struct {
storage *Storage
Theme string `json:"Theme"`
Host string `json:"Host"`
Port string `json:"Port"`
Quality int `json:"Quality"`
SaveDirectory string `json:"SaveDirectory"`
UpstreamProxy string `json:"UpstreamProxy"`
OpenProxy bool `json:"OpenProxy"`
DownloadProxy bool `json:"DownloadProxy"`
AutoProxy bool `json:"AutoProxy"`
WxAction bool `json:"WxAction"`
TaskNumber int `json:"TaskNumber"`
UserAgent string `json:"UserAgent"`
}
func initConfig() *Config {
if globalConfig == nil {
def := `
{
"Host": "0.0.0.0",
"Port": "8899",
"Theme": "lightTheme",
"Quality": 0,
"SaveDirectory": "",
"UpstreamProxy": "",
"OpenProxy": false,
"DownloadProxy": false,
"AutoProxy": false,
"WxAction": true,
"TaskNumber": __TaskNumber__,
"UserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36"
}
`
def = strings.ReplaceAll(def, "__TaskNumber__", strconv.Itoa(runtime.NumCPU()*2))
globalConfig = &Config{
storage: NewStorage("config.json", []byte(def)),
}
data, err := globalConfig.storage.Load()
if err == nil {
_ = json.Unmarshal(data, &globalConfig)
} else {
globalLogger.Esg(err, "load config err")
}
}
return globalConfig
}
func (c *Config) setConfig(config Config) {
oldProxy := c.UpstreamProxy
c.Host = config.Host
c.Port = config.Port
c.Theme = config.Theme
c.Quality = config.Quality
c.SaveDirectory = config.SaveDirectory
c.UpstreamProxy = config.UpstreamProxy
c.UserAgent = config.UserAgent
c.OpenProxy = config.OpenProxy
c.DownloadProxy = config.DownloadProxy
c.AutoProxy = config.AutoProxy
c.TaskNumber = config.TaskNumber
c.WxAction = config.WxAction
if oldProxy != c.UpstreamProxy {
proxyOnce.setTransport()
}
jsonData, err := json.Marshal(c)
if err == nil {
_ = globalConfig.storage.Store(jsonData)
}
}

213
core/downloader.go Normal file
View File

@@ -0,0 +1,213 @@
package core
import (
"fmt"
"io"
"log"
"net/http"
"net/http/cookiejar"
"net/url"
"os"
"path/filepath"
"strings"
"sync"
)
type ProgressCallback func(totalDownloaded float64)
type DownloadTask struct {
taskID int
rangeStart int64
rangeEnd int64
downloadedSize int64
isCompleted bool
}
type FileDownloader struct {
Url string
Referer string
ProxyUrl *url.URL
FileName string
File *os.File
totalTasks int
TotalSize int64
IsMultiPart bool
DownloadTaskList []*DownloadTask
progressCallback ProgressCallback
}
func NewFileDownloader(url, filename string, totalTasks int) *FileDownloader {
return &FileDownloader{
Url: url,
FileName: filename,
totalTasks: totalTasks,
IsMultiPart: false,
TotalSize: 0,
DownloadTaskList: make([]*DownloadTask, 0),
}
}
func (fd *FileDownloader) buildClient() *http.Client {
transport := &http.Transport{}
if fd.ProxyUrl != nil {
transport.Proxy = http.ProxyURL(fd.ProxyUrl)
}
// Cookie handle
jar, _ := cookiejar.New(nil)
return &http.Client{
Transport: transport,
Jar: jar,
}
}
func (fd *FileDownloader) init() error {
parsedURL, err := url.Parse(fd.Url)
if err != nil {
return err
}
if parsedURL.Scheme != "" && parsedURL.Host != "" {
fd.Referer = parsedURL.Scheme + "://" + parsedURL.Host + "/"
}
if globalConfig.DownloadProxy && globalConfig.UpstreamProxy != "" && !strings.Contains(globalConfig.UpstreamProxy, globalConfig.Port) {
proxyURL, err := url.Parse(globalConfig.UpstreamProxy)
if err == nil {
fd.ProxyUrl = proxyURL
}
}
resp, e := http.Head(fd.Url)
if e != nil {
return e
}
fd.TotalSize = resp.ContentLength
if resp.Header.Get("Accept-Ranges") == "bytes" && fd.TotalSize > 10485760 {
fd.IsMultiPart = true
}
resp.Body.Close()
fd.FileName = filepath.Clean(fd.FileName)
_, err = os.Stat(fd.FileName)
if err != nil {
if os.IsNotExist(err) {
fd.File, err = os.Create(fd.FileName)
if err != nil && fd.TotalSize > 0 {
err = fd.File.Truncate(fd.TotalSize)
}
}
} else {
fd.File, err = os.OpenFile(fd.FileName, os.O_RDWR, os.ModeAppend)
}
if err != nil {
return err
}
return nil
}
func (fd *FileDownloader) createDownloadTasks() {
if fd.IsMultiPart {
if int64(fd.totalTasks) > fd.TotalSize {
fd.totalTasks = int(fd.TotalSize)
}
eachSize := fd.TotalSize / int64(fd.totalTasks)
for i := 0; i < fd.totalTasks; i++ {
fd.DownloadTaskList = append(fd.DownloadTaskList, &DownloadTask{
taskID: i,
rangeStart: eachSize * int64(i),
rangeEnd: eachSize*int64(i+1) - 1,
downloadedSize: 0,
isCompleted: false,
})
}
fd.DownloadTaskList[len(fd.DownloadTaskList)-1].rangeEnd = fd.TotalSize - 1
} else {
fd.DownloadTaskList = append(fd.DownloadTaskList, &DownloadTask{
taskID: 0,
rangeStart: 0,
rangeEnd: 0,
downloadedSize: 0,
isCompleted: false,
})
}
}
func (fd *FileDownloader) startDownload() {
waitGroup := &sync.WaitGroup{}
progressChan := make(chan int64)
for _, task := range fd.DownloadTaskList {
go fd.startDownloadTask(waitGroup, progressChan, task)
waitGroup.Add(1)
}
go func() {
waitGroup.Wait()
close(progressChan)
}()
if fd.progressCallback != nil {
totalDownloaded := int64(0)
for progress := range progressChan {
totalDownloaded += progress
fd.progressCallback(float64(totalDownloaded) * 100 / float64(fd.TotalSize))
}
}
}
func (fd *FileDownloader) startDownloadTask(waitGroup *sync.WaitGroup, progressChan chan int64, task *DownloadTask) {
defer waitGroup.Done()
request, err := http.NewRequest("GET", fd.Url, nil)
if err != nil {
globalLogger.Error().Stack().Err(err).Msgf("任务%d创建请求出错", task.taskID)
return
}
request.Header.Set("User-Agent", globalConfig.UserAgent)
request.Header.Set("Referer", fd.Referer)
if fd.IsMultiPart {
request.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", task.rangeStart, task.rangeEnd))
}
resp, err := fd.buildClient().Do(request)
if err != nil {
log.Printf("任务%d发送下载请求出错%s", task.taskID, err)
return
}
defer resp.Body.Close()
buf := make([]byte, 8192)
for {
n, err := resp.Body.Read(buf)
if n > 0 {
_, err := fd.File.WriteAt(buf[:n], task.rangeStart+task.downloadedSize)
if err != nil {
log.Printf("任务%d写入文件时出现错误位置:%d, err: %s\n", task.taskID, task.rangeStart+task.downloadedSize, err)
return
}
downSize := int64(n)
task.downloadedSize += downSize
progressChan <- downSize
}
if err != nil {
if err == io.EOF {
task.isCompleted = true
break
}
log.Printf("任务%d读取响应错误%s", task.taskID, err)
return
}
}
}
func (fd *FileDownloader) Start() error {
err := fd.init()
if err != nil {
return err
}
fd.createDownloadTasks()
fd.startDownload()
defer fd.File.Close()
return nil
}

330
core/http.go Normal file
View File

@@ -0,0 +1,330 @@
package core
import (
"bytes"
"encoding/json"
"fmt"
"github.com/wailsapp/wails/v2/pkg/runtime"
"io"
"log"
"net"
"net/http"
"net/url"
"os/exec"
sysRuntime "runtime"
"strings"
)
type ResponseData struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data"`
}
type HttpServer struct{}
func initHttpServer() *HttpServer {
if httpServerOnce == nil {
httpServerOnce = &HttpServer{}
}
return httpServerOnce
}
func (h *HttpServer) run() {
listener, err := net.Listen("tcp", globalConfig.Host+":"+globalConfig.Port)
if err != nil {
log.Fatalf("无法启动监听: %v", err)
}
fmt.Println("服务已启动,监听 http://" + globalConfig.Host + ":" + globalConfig.Port)
if err := http.Serve(listener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Host == "127.0.0.1:"+globalConfig.Port && strings.Contains(r.URL.Path, "/cert") {
w.Header().Set("Content-Type", "application/x-x509-ca-data")
w.Header().Set("Content-Disposition", "attachment;filename=res-downloader-public.crt")
w.Header().Set("Content-Transfer-Encoding", "binary")
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(appOnce.PublicCrt)))
w.WriteHeader(http.StatusOK)
_, err = io.Copy(w, io.NopCloser(bytes.NewReader(appOnce.PublicCrt)))
} else {
proxyOnce.Proxy.ServeHTTP(w, r) // 代理
}
})); err != nil {
fmt.Printf("服务器异常: %v", err)
}
}
func (h *HttpServer) preview(w http.ResponseWriter, r *http.Request) {
realURL := r.URL.Query().Get("url")
if realURL == "" {
http.Error(w, "Missing 'url' parameter", http.StatusBadRequest)
return
}
realURL, _ = url.QueryUnescape(realURL)
parsedURL, err := url.Parse(realURL)
if err != nil {
http.Error(w, "Invalid URL", http.StatusBadRequest)
return
}
request, err := http.NewRequest("GET", parsedURL.String(), nil)
if err != nil {
http.Error(w, "Failed to fetch the resource", http.StatusInternalServerError)
return
}
if rangeHeader := r.Header.Get("Range"); rangeHeader != "" {
request.Header.Set("Range", rangeHeader)
}
//request.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36")
//request.Header.Set("Referer", parsedURL.Scheme+"://"+parsedURL.Host+"/")
resp, err := http.DefaultClient.Do(request)
if err != nil {
http.Error(w, "Failed to fetch the resource", http.StatusInternalServerError)
return
}
defer resp.Body.Close()
w.Header().Set("Content-Type", resp.Header.Get("Content-Type"))
w.WriteHeader(resp.StatusCode)
if contentRange := resp.Header.Get("Content-Range"); contentRange != "" {
w.Header().Set("Content-Range", contentRange)
}
_, err = io.Copy(w, resp.Body)
if err != nil {
http.Error(w, "Failed to serve the resource", http.StatusInternalServerError)
}
return
}
func (h *HttpServer) send(t string, data interface{}) {
jsonData, err := json.Marshal(map[string]interface{}{
"type": t,
"data": data,
})
if err != nil {
fmt.Println("Error converting map to JSON:", err)
return
}
runtime.EventsEmit(appOnce.ctx, "event", string(jsonData))
}
func (h *HttpServer) writeJson(w http.ResponseWriter, data ResponseData) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(200)
err := json.NewEncoder(w).Encode(data)
if err != nil {
globalLogger.err(err)
}
}
func (h *HttpServer) openDirectoryDialog(w http.ResponseWriter, r *http.Request) {
folder, err := runtime.OpenDirectoryDialog(appOnce.ctx, runtime.OpenDialogOptions{
DefaultDirectory: "",
Title: "Select a folder",
})
if err != nil {
h.writeJson(w, ResponseData{Code: 0, Message: err.Error()})
return
}
h.writeJson(w, ResponseData{
Code: 1,
Data: map[string]interface{}{
"folder": folder,
},
})
}
func (h *HttpServer) openFileDialog(w http.ResponseWriter, r *http.Request) {
filePath, err := runtime.OpenFileDialog(appOnce.ctx, runtime.OpenDialogOptions{
Filters: []runtime.FileFilter{
{
DisplayName: "Videos (*.mov;*.mp4)",
Pattern: "*.mp4",
},
},
Title: "Select a file",
})
if err != nil {
h.writeJson(w, ResponseData{Code: 0, Message: err.Error()})
return
}
h.writeJson(w, ResponseData{
Code: 1,
Data: map[string]interface{}{
"file": filePath,
},
})
}
func (h *HttpServer) openFolder(w http.ResponseWriter, r *http.Request) {
var data struct {
FilePath string `json:"filePath"`
}
err := json.NewDecoder(r.Body).Decode(&data)
if err == nil && data.FilePath == "" {
return
}
filePath := data.FilePath
var cmd *exec.Cmd
switch sysRuntime.GOOS {
case "darwin":
// macOS
cmd = exec.Command("open", "-R", filePath)
case "windows":
// Windows
cmd = exec.Command("explorer", "/select,", filePath)
case "linux":
// linux
// 尝试使用不同的文件管理器
cmd = exec.Command("nautilus", filePath)
if err := cmd.Start(); err != nil {
cmd = exec.Command("thunar", filePath)
if err := cmd.Start(); err != nil {
cmd = exec.Command("dolphin", filePath)
if err := cmd.Start(); err != nil {
cmd = exec.Command("pcmanfm", filePath)
if err := cmd.Start(); err != nil {
globalLogger.err(err)
h.writeJson(w, ResponseData{Code: 0, Message: err.Error()})
return
}
}
}
}
default:
h.writeJson(w, ResponseData{Code: 0, Message: "unsupported platform"})
return
}
err = cmd.Start()
if err != nil {
globalLogger.err(err)
h.writeJson(w, ResponseData{Code: 0, Message: err.Error()})
return
}
h.writeJson(w, ResponseData{Code: 1})
}
func (h *HttpServer) openSystemProxy(w http.ResponseWriter, r *http.Request) {
appOnce.OpenSystemProxy()
h.writeJson(w, ResponseData{
Code: 1,
Data: map[string]bool{
"isProxy": appOnce.IsProxy,
},
})
}
func (h *HttpServer) unsetSystemProxy(w http.ResponseWriter, r *http.Request) {
appOnce.UnsetSystemProxy()
h.writeJson(w, ResponseData{
Code: 1,
Data: map[string]bool{
"isProxy": appOnce.IsProxy,
},
})
}
func (h *HttpServer) isProxy(w http.ResponseWriter, r *http.Request) {
h.writeJson(w, ResponseData{
Code: 1,
Data: map[string]interface{}{
"isProxy": appOnce.IsProxy,
},
})
}
func (h *HttpServer) appInfo(w http.ResponseWriter, r *http.Request) {
h.writeJson(w, ResponseData{
Code: 1,
Data: appOnce,
})
}
func (h *HttpServer) getConfig(w http.ResponseWriter, r *http.Request) {
h.writeJson(w, ResponseData{
Code: 1,
Data: globalConfig,
})
}
func (h *HttpServer) setConfig(w http.ResponseWriter, r *http.Request) {
var data Config
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
h.writeJson(w, ResponseData{Code: 0, Message: err.Error()})
return
}
globalConfig.setConfig(data)
h.writeJson(w, ResponseData{Code: 1})
}
func (h *HttpServer) setType(w http.ResponseWriter, r *http.Request) {
var data struct {
Type string `json:"type"`
}
err := json.NewDecoder(r.Body).Decode(&data)
if err == nil {
if data.Type != "" {
resourceOnce.setResType(strings.Split(data.Type, ","))
} else {
resourceOnce.setResType([]string{})
}
}
h.writeJson(w, ResponseData{Code: 1})
}
func (h *HttpServer) clear(w http.ResponseWriter, r *http.Request) {
resourceOnce.clear()
h.writeJson(w, ResponseData{Code: 1})
}
func (h *HttpServer) delete(w http.ResponseWriter, r *http.Request) {
var data struct {
Sign string `json:"sign"`
}
err := json.NewDecoder(r.Body).Decode(&data)
if err == nil && data.Sign != "" {
resourceOnce.delete(data.Sign)
}
h.writeJson(w, ResponseData{Code: 1})
}
func (h *HttpServer) download(w http.ResponseWriter, r *http.Request) {
var data struct {
MediaInfo
DecodeStr string `json:"decodeStr"`
}
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
h.writeJson(w, ResponseData{Code: 0, Message: err.Error()})
return
}
resourceOnce.download(data.MediaInfo, data.DecodeStr)
h.writeJson(w, ResponseData{Code: 1})
}
func (h *HttpServer) wxFileDecode(w http.ResponseWriter, r *http.Request) {
var data struct {
MediaInfo
Filename string `json:"filename"`
DecodeStr string `json:"decodeStr"`
}
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
h.writeJson(w, ResponseData{Code: 0, Message: err.Error()})
return
}
savePath, err := resourceOnce.wxFileDecode(data.MediaInfo, data.Filename, data.DecodeStr)
if err != nil {
h.writeJson(w, ResponseData{Code: 0, Message: err.Error()})
return
}
h.writeJson(w, ResponseData{
Code: 1,
Data: map[string]string{
"save_path": savePath,
},
})
}

67
core/logger.go Normal file
View File

@@ -0,0 +1,67 @@
package core
import (
"fmt"
"github.com/rs/zerolog"
"io"
"os"
"path/filepath"
)
type Logger struct {
zerolog.Logger
logFile *os.File
}
func initLogger() *Logger {
if globalLogger == nil {
globalLogger = NewLogger(!IsDevelopment(), filepath.Join(appOnce.UserDir, "logs", "app.log"))
}
return globalLogger
}
func (l *Logger) Close() {
_ = l.logFile.Close()
}
func (l *Logger) err(err error) {
l.Error().Stack().Err(err)
}
func (l *Logger) Esg(err error, format string, v ...interface{}) {
l.Error().Stack().Err(err).Msgf(fmt.Sprintf(format, v...))
}
// NewLogger create a new logger
func NewLogger(logFile bool, logPath string) *Logger {
var out io.Writer
if logFile {
// log to file
logDir := filepath.Dir(logPath)
if err := CreateDirIfNotExist(logDir); err != nil {
panic(err)
}
var (
logfile *os.File
err error
)
logfile, err = os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
panic(err)
}
out = logfile
} else {
out = os.Stdout
}
logger := &Logger{}
if logFile {
logger.logFile = out.(*os.File)
}
logger.Logger = zerolog.New(zerolog.ConsoleWriter{
NoColor: true,
Out: out,
TimeFormat: "2006-01-02 15:04:05",
}).With().Timestamp().Logger()
return logger
}

61
core/middleware.go Normal file
View File

@@ -0,0 +1,61 @@
package core
import (
"net/http"
"strings"
)
func Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if HandleApi(w, r) {
return
}
next.ServeHTTP(w, r)
})
}
func HandleApi(w http.ResponseWriter, r *http.Request) bool {
if strings.HasPrefix(r.URL.Path, "/api") {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return true
}
switch r.URL.Path {
case "/api/preview":
httpServerOnce.preview(w, r)
case "/api/proxy-open":
httpServerOnce.openSystemProxy(w, r)
case "/api/proxy-unset":
httpServerOnce.unsetSystemProxy(w, r)
case "/api/open-directory":
httpServerOnce.openDirectoryDialog(w, r)
case "/api/open-file":
httpServerOnce.openFileDialog(w, r)
case "/api/open-folder":
httpServerOnce.openFolder(w, r)
case "/api/is-proxy":
httpServerOnce.isProxy(w, r)
case "/api/app-info":
httpServerOnce.appInfo(w, r)
case "/api/set-config":
httpServerOnce.setConfig(w, r)
case "/api/get-config":
httpServerOnce.getConfig(w, r)
case "/api/set-type":
httpServerOnce.setType(w, r)
case "/api/clear":
httpServerOnce.clear(w, r)
case "/api/delete":
httpServerOnce.delete(w, r)
case "/api/download":
httpServerOnce.download(w, r)
case "/api/wx-file-decode":
httpServerOnce.wxFileDecode(w, r)
}
return true
}
return false
}

354
core/proxy.go Normal file
View File

@@ -0,0 +1,354 @@
package core
import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"github.com/elazarl/goproxy"
gonanoid "github.com/matoous/go-nanoid/v2"
"io"
"net"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"time"
)
type Proxy struct {
ctx context.Context
Proxy *goproxy.ProxyHttpServer
Is bool
}
type MediaInfo struct {
Id string
Url string
UrlSign string
CoverUrl string
Size string
Domain string
Classify string
Suffix string
SavePath string
Status string
DecodeKey string
Description string
ContentType string
OtherData map[string]string
}
func initProxy() *Proxy {
if proxyOnce == nil {
proxyOnce = &Proxy{}
proxyOnce.Startup()
}
return proxyOnce
}
func (p *Proxy) Startup() {
err := p.setCa()
if err != nil {
DialogErr("启动代理服务失败:" + err.Error())
return
}
p.Proxy = goproxy.NewProxyHttpServer()
//p.Proxy.KeepDestinationHeaders = true
//p.Proxy.Verbose = false
p.setTransport()
p.Proxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)
p.Proxy.OnRequest().DoFunc(p.httpRequestEvent)
p.Proxy.OnResponse().DoFunc(p.httpResponseEvent)
}
func (p *Proxy) setCa() error {
ca, err := tls.X509KeyPair(appOnce.PublicCrt, appOnce.PrivateKey)
if err != nil {
DialogErr("启动代理服务失败1")
return err
}
if ca.Leaf, err = x509.ParseCertificate(ca.Certificate[0]); err != nil {
return err
}
goproxy.GoproxyCa = ca
goproxy.OkConnect = &goproxy.ConnectAction{Action: goproxy.ConnectAccept, TLSConfig: goproxy.TLSConfigFromCA(&ca)}
goproxy.MitmConnect = &goproxy.ConnectAction{Action: goproxy.ConnectMitm, TLSConfig: goproxy.TLSConfigFromCA(&ca)}
goproxy.HTTPMitmConnect = &goproxy.ConnectAction{Action: goproxy.ConnectHTTPMitm, TLSConfig: goproxy.TLSConfigFromCA(&ca)}
goproxy.RejectConnect = &goproxy.ConnectAction{Action: goproxy.ConnectReject, TLSConfig: goproxy.TLSConfigFromCA(&ca)}
return nil
}
func (p *Proxy) setTransport() {
transport := &http.Transport{
DisableKeepAlives: false,
// MaxIdleConnsPerHost: 10,
DialContext: (&net.Dialer{
Timeout: 60 * time.Second,
}).DialContext,
TLSHandshakeTimeout: 60 * time.Second,
ResponseHeaderTimeout: 60 * time.Second,
IdleConnTimeout: 30 * time.Second,
}
if globalConfig.UpstreamProxy != "" && globalConfig.OpenProxy && !strings.Contains(globalConfig.UpstreamProxy, globalConfig.Port) {
proxyURL, err := url.Parse(globalConfig.UpstreamProxy)
if err == nil {
transport.Proxy = http.ProxyURL(proxyURL)
}
}
p.Proxy.Tr = transport
}
func (p *Proxy) httpRequestEvent(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
if strings.Contains(r.Host, "res-downloader.666666.com") && strings.Contains(r.URL.Path, "/wechat") {
if globalConfig.WxAction && r.URL.Query().Get("type") == "1" {
return p.handleWechatRequest(r, ctx)
} else if !globalConfig.WxAction && r.URL.Query().Get("type") == "2" {
return p.handleWechatRequest(r, ctx)
} else {
return r, p.buildEmptyResponse(r)
}
}
return r, nil
}
func (p *Proxy) handleWechatRequest(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
body, err := io.ReadAll(r.Body)
if err != nil {
fmt.Println(err)
return r, p.buildEmptyResponse(r)
}
isAll, _ := resourceOnce.getResType("all")
isClassify, _ := resourceOnce.getResType("video")
if !isAll && !isClassify {
return r, p.buildEmptyResponse(r)
}
go func(body []byte) {
var result map[string]interface{}
err = json.Unmarshal(body, &result)
if err != nil {
return
}
media, ok := result["media"].([]interface{})
if !ok || len(media) <= 0 {
return
}
firstMedia, ok := media[0].(map[string]interface{})
if !ok {
return
}
rowUrl, ok := firstMedia["url"]
if !ok {
return
}
resourceOnce.markMu.Lock()
defer resourceOnce.markMu.Unlock()
urlSign := Md5(rowUrl.(string))
if _, ok := resourceOnce.mark[urlSign]; ok {
return
}
id, err := gonanoid.New()
if err != nil {
id = urlSign
}
res := MediaInfo{
Id: id,
Url: rowUrl.(string),
UrlSign: urlSign,
CoverUrl: "",
Size: "0",
Domain: GetTopLevelDomain(rowUrl.(string)),
Classify: "video",
Suffix: ".mp4",
Status: DownloadStatusReady,
SavePath: "",
DecodeKey: "",
OtherData: map[string]string{},
Description: "",
ContentType: "video/mp4",
}
if urlToken, ok := firstMedia["urlToken"].(string); ok {
res.Url = res.Url + urlToken
}
if fileSize, ok := firstMedia["fileSize"].(float64); ok {
res.Size = FormatSize(fileSize)
}
if coverUrl, ok := firstMedia["coverUrl"].(string); ok {
res.CoverUrl = coverUrl
}
if fileSize, ok := firstMedia["fileSize"].(string); ok {
value, err := strconv.ParseFloat(fileSize, 64)
if err == nil {
res.Size = FormatSize(value)
}
}
if decodeKey, ok := firstMedia["decodeKey"].(string); ok {
res.DecodeKey = decodeKey
}
if desc, ok := result["description"].(string); ok {
res.Description = desc
}
if spec, ok := firstMedia["spec"].([]interface{}); ok {
var fileFormats []string
for _, item := range spec {
if itemMap, ok := item.(map[string]interface{}); ok {
if format, exists := itemMap["fileFormat"].(string); exists {
fileFormats = append(fileFormats, format)
}
}
}
res.OtherData["wx_file_formats"] = strings.Join(fileFormats, "#")
}
resourceOnce.mark[urlSign] = true
httpServerOnce.send("newResources", res)
}(body)
return r, p.buildEmptyResponse(r)
}
func (p *Proxy) buildEmptyResponse(r *http.Request) *http.Response {
body := "内容不存在"
resp := &http.Response{
Status: "200 OK",
StatusCode: http.StatusOK,
Header: make(http.Header),
Body: io.NopCloser(strings.NewReader(body)),
ContentLength: int64(len(body)),
Request: r,
}
resp.Header.Set("Content-Type", "text/plain")
return resp
}
func (p *Proxy) httpResponseEvent(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {
if resp == nil || resp.Request == nil || (resp.StatusCode != 200 && resp.StatusCode != 206) {
return resp
}
host := resp.Request.Host
Path := resp.Request.URL.Path
if strings.HasSuffix(host, "channels.weixin.qq.com") &&
(strings.Contains(Path, "/web/pages/feed") || strings.Contains(Path, "/web/pages/home")) {
return p.replaceWxJsContent(resp, ".js\"", ".js?v="+p.v()+"\"")
}
if strings.HasSuffix(host, "res.wx.qq.com") {
respTemp := resp
if strings.HasSuffix(respTemp.Request.URL.RequestURI(), ".js?v="+p.v()) {
respTemp = p.replaceWxJsContent(respTemp, ".js\"", ".js?v="+p.v()+"\"")
}
if strings.Contains(Path, "web/web-finder/res/js/virtual_svg-icons-register.publish") {
body, err := io.ReadAll(respTemp.Body)
if err != nil {
return respTemp
}
bodyStr := string(body)
newBody := regexp.MustCompile(`get\s*media\(\)\{`).
ReplaceAllString(bodyStr, `
get media(){
if(this.objectDesc){
fetch("https://res-downloader.666666.com/wechat?type=1", {
method: "POST",
mode: "no-cors",
body: JSON.stringify(this.objectDesc),
});
};
`)
newBody = regexp.MustCompile(`async\s*finderGetCommentDetail\((\w+)\)\s*\{return(.*?)\s*}\s*async`).
ReplaceAllString(newBody, `
async finderGetCommentDetail($1) {
var res = await$2;
if (res?.data?.object?.objectDesc) {
fetch("https://res-downloader.666666.com/wechat?type=2", {
method: "POST",
mode: "no-cors",
body: JSON.stringify(res.data.object.objectDesc),
});
}
return res;
}async
`)
newBodyBytes := []byte(newBody)
respTemp.Body = io.NopCloser(bytes.NewBuffer(newBodyBytes))
respTemp.ContentLength = int64(len(newBodyBytes))
respTemp.Header.Set("Content-Length", fmt.Sprintf("%d", len(newBodyBytes)))
return respTemp
}
return respTemp
}
classify, suffix := TypeSuffix(resp.Header.Get("Content-Type"))
if classify == "" {
return resp
}
if classify == "video" && strings.HasSuffix(host, "finder.video.qq.com") {
//if !globalConfig.WxAction && classify == "video" && strings.HasSuffix(host, "finder.video.qq.com") {
return resp
}
rawUrl := resp.Request.URL.String()
resourceOnce.markMu.Lock()
defer resourceOnce.markMu.Unlock()
isAll, _ := resourceOnce.getResType("all")
isClassify, _ := resourceOnce.getResType(classify)
urlSign := Md5(rawUrl)
if _, ok := resourceOnce.mark[urlSign]; !ok && (isAll || isClassify) {
value, _ := strconv.ParseFloat(resp.Header.Get("content-length"), 64)
id, err := gonanoid.New()
if err != nil {
id = urlSign
}
res := MediaInfo{
Id: id,
Url: rawUrl,
UrlSign: urlSign,
CoverUrl: "",
Size: FormatSize(value),
Domain: GetTopLevelDomain(rawUrl),
Classify: classify,
Suffix: suffix,
Status: DownloadStatusReady,
SavePath: "",
DecodeKey: "",
OtherData: map[string]string{},
Description: "",
ContentType: resp.Header.Get("Content-Type"),
}
resourceOnce.mark[urlSign] = true
httpServerOnce.send("newResources", res)
}
return resp
}
func (p *Proxy) replaceWxJsContent(resp *http.Response, old, new string) *http.Response {
body, err := io.ReadAll(resp.Body)
if err != nil {
return resp
}
bodyString := string(body)
newBodyString := strings.ReplaceAll(bodyString, old, new)
newBodyBytes := []byte(newBodyString)
resp.Body = io.NopCloser(bytes.NewBuffer(newBodyBytes))
resp.ContentLength = int64(len(newBodyBytes))
resp.Header.Set("Content-Length", fmt.Sprintf("%d", len(newBodyBytes)))
return resp
}
func (p *Proxy) v() string {
return appOnce.Version
}

242
core/resource.go Normal file
View File

@@ -0,0 +1,242 @@
package core
import (
"encoding/base64"
"io"
"net/url"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"sync"
)
const (
DownloadStatusReady string = "ready" // task create but not start
DownloadStatusRunning string = "running"
DownloadStatusError string = "error"
DownloadStatusDone string = "done"
DownloadStatusHandle string = "handle"
)
type WxFileDecodeResult struct {
SavePath string
Message string
}
type Resource struct {
mark map[string]bool
markMu sync.RWMutex
resType map[string]bool
resTypeMu sync.RWMutex
}
func initResource() *Resource {
if resourceOnce == nil {
resourceOnce = &Resource{
mark: make(map[string]bool),
resType: map[string]bool{
"all": true,
"image": true,
"audio": true,
"video": true,
"m3u8": true,
"live": true,
"xls": true,
"doc": true,
"pdf": true,
},
}
}
return resourceOnce
}
func (r *Resource) getMark(key string) (bool, bool) {
r.markMu.RLock()
defer r.markMu.RUnlock()
value, ok := r.mark[key]
return value, ok
}
func (r *Resource) setMark(key string, value bool) {
r.markMu.Lock()
defer r.markMu.Unlock()
r.mark[key] = value
}
func (r *Resource) getResType(key string) (bool, bool) {
r.resTypeMu.RLock()
defer r.resTypeMu.RUnlock()
value, ok := r.resType[key]
return value, ok
}
func (r *Resource) setResType(n []string) {
r.resTypeMu.Lock()
defer r.resTypeMu.Unlock()
r.resType = map[string]bool{
"all": false,
"image": false,
"audio": false,
"video": false,
"m3u8": false,
"live": false,
"xls": false,
"doc": false,
"pdf": false,
}
for _, value := range n {
r.resType[value] = true
}
}
func (r *Resource) clear() {
r.markMu.Lock()
defer r.markMu.Unlock()
r.mark = make(map[string]bool)
}
func (r *Resource) delete(sign string) {
r.markMu.Lock()
defer r.markMu.Unlock()
delete(r.mark, sign)
}
func (r *Resource) download(mediaInfo MediaInfo, decodeStr string) {
if globalConfig.SaveDirectory == "" {
return
}
go func(mediaInfo MediaInfo) {
rawUrl := mediaInfo.Url
fileName := Md5(rawUrl)
if mediaInfo.Description != "" {
fileName = regexp.MustCompile(`[^\w\p{Han}]`).ReplaceAllString(mediaInfo.Description, "")
runes := []rune(fileName)
if len(runes) > 10 {
fileName = string(runes[:10])
}
}
mediaInfo.SavePath = filepath.Join(globalConfig.SaveDirectory, fileName+"_"+GetCurrentDateTimeFormatted()+mediaInfo.Suffix)
if strings.Contains(rawUrl, "qq.com") {
if globalConfig.Quality == 1 &&
strings.Contains(rawUrl, "encfilekey=") &&
strings.Contains(rawUrl, "token=") {
parseUrl, err := url.Parse(rawUrl)
queryParams := parseUrl.Query()
if err == nil && queryParams.Has("encfilekey") && queryParams.Has("token") {
rawUrl = parseUrl.Scheme + "://" + parseUrl.Host + "/" + parseUrl.Path +
"?encfilekey=" + queryParams.Get("encfilekey") +
"&token=" + queryParams.Get("token")
}
} else if globalConfig.Quality > 1 && mediaInfo.OtherData["wx_file_formats"] != "" {
format := strings.Split(mediaInfo.OtherData["wx_file_formats"], "#")
qualityMap := []string{
format[0],
format[len(format)/2],
format[len(format)-1],
}
rawUrl += "&X-snsvideoflag=" + qualityMap[globalConfig.Quality-2]
}
}
downloader := NewFileDownloader(rawUrl, mediaInfo.SavePath, globalConfig.TaskNumber)
downloader.progressCallback = func(totalDownloaded float64) {
r.progressEventsEmit(mediaInfo, strconv.Itoa(int(totalDownloaded))+"%", DownloadStatusRunning)
}
err := downloader.Start()
if err != nil {
r.progressEventsEmit(mediaInfo, err.Error())
return
}
if decodeStr != "" {
r.progressEventsEmit(mediaInfo, "解密中", DownloadStatusRunning)
if err := r.decodeWxFile(mediaInfo.SavePath, decodeStr); err != nil {
r.progressEventsEmit(mediaInfo, "解密出错"+err.Error())
return
}
}
r.progressEventsEmit(mediaInfo, "完成", DownloadStatusDone)
}(mediaInfo)
}
func (r *Resource) wxFileDecode(mediaInfo MediaInfo, fileName, decodeStr string) (string, error) {
sourceFile, err := os.Open(fileName)
if err != nil {
return "", err
}
defer sourceFile.Close()
mediaInfo.SavePath = strings.ReplaceAll(fileName, ".mp4", "_解密.mp4")
destinationFile, err := os.Create(mediaInfo.SavePath)
if err != nil {
return "", err
}
defer destinationFile.Close()
_, err = io.Copy(destinationFile, sourceFile)
if err != nil {
return "", err
}
err = r.decodeWxFile(mediaInfo.SavePath, decodeStr)
if err != nil {
return "", err
}
return mediaInfo.SavePath, nil
}
func (r *Resource) progressEventsEmit(mediaInfo MediaInfo, args ...string) {
Status := DownloadStatusError
Message := "ok"
if len(args) > 0 {
Message = args[0]
}
if len(args) > 1 {
Status = args[1]
}
httpServerOnce.send("downloadProgress", map[string]interface{}{
"Id": mediaInfo.Id,
"Status": Status,
"SavePath": mediaInfo.SavePath,
"Message": Message,
})
return
}
func (r *Resource) decodeWxFile(fileName, decodeStr string) error {
decodedBytes, err := base64.StdEncoding.DecodeString(decodeStr)
if err != nil {
return err
}
file, err := os.OpenFile(fileName, os.O_RDWR, 0644)
if err != nil {
return err
}
defer file.Close()
byteCount := len(decodedBytes)
fileBytes := make([]byte, byteCount)
_, err = file.Read(fileBytes)
if err != nil && err != io.EOF {
return err
}
xorResult := make([]byte, byteCount)
for i := 0; i < byteCount; i++ {
xorResult[i] = decodedBytes[i] ^ fileBytes[i]
}
_, err = file.Seek(0, 0)
if err != nil {
return err
}
_, err = file.Write(xorResult)
if err != nil {
return err
}
return nil
}

40
core/storage.go Normal file
View File

@@ -0,0 +1,40 @@
package core
import (
"os"
"path"
)
type Storage struct {
fileName string
def []byte
}
func NewStorage(filename string, def []byte) *Storage {
return &Storage{
fileName: path.Join(appOnce.UserDir, filename),
def: def,
}
}
func (l *Storage) Load() ([]byte, error) {
if !FileExist(l.fileName) {
err := os.WriteFile(l.fileName, l.def, 0777)
if err != nil {
return nil, err
}
return l.def, nil
}
d, err := os.ReadFile(l.fileName)
if err != nil {
return nil, err
}
return d, err
}
func (l *Storage) Store(data []byte) error {
if err := os.WriteFile(l.fileName, data, 0777); err != nil {
return err
}
return nil
}

35
core/system.go Normal file
View File

@@ -0,0 +1,35 @@
package core
import (
"os"
"path/filepath"
)
type SystemSetup struct {
CertFile string
}
func initSystem() *SystemSetup {
if systemOnce == nil {
systemOnce = &SystemSetup{
CertFile: filepath.Join(appOnce.UserDir, "cert.crt"),
}
}
return systemOnce
}
func (s *SystemSetup) initCert() ([]byte, error) {
content, err := os.ReadFile(s.CertFile)
if err == nil {
return content, nil
}
if os.IsNotExist(err) {
err = os.WriteFile(s.CertFile, appOnce.PublicCrt, 0777)
if err != nil {
return nil, err
}
return appOnce.PublicCrt, nil
} else {
return nil, err
}
}

113
core/system_darwin.go Normal file
View File

@@ -0,0 +1,113 @@
//go:build darwin
package core
import (
"bytes"
"fmt"
"os/exec"
"strings"
)
func (s *SystemSetup) getNetworkServices() ([]string, error) {
cmd := exec.Command("networksetup", "-listallnetworkservices")
output, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("failed to execute command: %v", err)
}
services := strings.Split(string(output), "\n")
var validServices []string
for _, service := range services {
service = strings.TrimSpace(service)
if service != "" && !strings.Contains(service, "*") && !strings.Contains(service, "Serial Port") {
validServices = append(validServices, service)
}
}
return validServices, nil
}
func (s *SystemSetup) setProxy() error {
services, err := s.getNetworkServices()
if err != nil {
return err
}
if len(services) == 0 {
return fmt.Errorf("find to Network failed")
}
is := false
for _, serviceName := range services {
if err := exec.Command("networksetup", "-setwebproxy", serviceName, "127.0.0.1", globalConfig.Port).Run(); err != nil {
fmt.Println(err)
} else {
is = true
}
if err := exec.Command("networksetup", "-setsecurewebproxy", serviceName, "127.0.0.1", globalConfig.Port).Run(); err != nil {
fmt.Println(err)
} else {
is = true
}
}
if is {
return nil
}
return fmt.Errorf("find to Network failed")
}
func (s *SystemSetup) unsetProxy() error {
services, err := s.getNetworkServices()
if err != nil {
return err
}
if len(services) == 0 {
return fmt.Errorf("find to Network failed")
}
is := false
for _, serviceName := range services {
if err := exec.Command("networksetup", "-setwebproxystate", serviceName, "off").Run(); err != nil {
fmt.Println(err)
} else {
is = true
}
if err := exec.Command("networksetup", "-setsecurewebproxystate", serviceName, "off").Run(); err != nil {
fmt.Println(err)
} else {
is = true
}
}
if is {
return nil
}
return fmt.Errorf("find to Network failed")
}
func (s *SystemSetup) installCert() (string, error) {
_, err := s.initCert()
if err != nil {
return "", err
}
getPasswordCmd := exec.Command("osascript", "-e", `tell app "System Events" to display dialog "请输入你的电脑密码,用于安装证书文件:" default answer "" with hidden answer`, "-e", `text returned of result`)
passwordOutput, err := getPasswordCmd.Output()
if err != nil {
return string(passwordOutput), err
}
password := bytes.TrimSpace(passwordOutput)
cmd := exec.Command("sudo", "-S", "security", "add-trusted-cert", "-d", "-r", "trustRoot", "-k", "/Library/Keychains/System.keychain", s.CertFile)
cmd.Stdin = bytes.NewReader(append(password, '\n'))
output, err := cmd.CombinedOutput()
if err != nil {
return string(output), err
}
return "", nil
}

74
core/system_linux.go Normal file
View File

@@ -0,0 +1,74 @@
//go:build linux
package core
import (
"fmt"
"os/exec"
)
func (s *SystemSetup) setProxy() error {
commands := [][]string{
{"gsettings", "set", "org.gnome.system.proxy", "mode", "manual"},
{"gsettings", "set", "org.gnome.system.proxy.http", "host", "127.0.0.1"},
{"gsettings", "set", "org.gnome.system.proxy.http", "port", globalConfig.Port},
{"gsettings", "set", "org.gnome.system.proxy.https", "host", "127.0.0.1"},
{"gsettings", "set", "org.gnome.system.proxy.https", "port", globalConfig.Port},
}
is := false
for _, cmd := range commands {
if err := exec.Command(cmd[0], cmd[1:]...).Run(); err != nil {
fmt.Println(err)
} else {
is = true
}
}
if is {
return nil
}
return fmt.Errorf("Failed to activate proxy")
}
func (s *SystemSetup) unsetProxy() error {
cmd := []string{"gsettings", "set", "org.gnome.system.proxy", "mode", "none"}
return exec.Command(cmd[0], cmd[1:]...).Run()
}
func (s *SystemSetup) installCert() (string, error) {
_, err := s.initCert()
if err != nil {
return "", err
}
actions := [][]string{
{"/usr/local/share/ca-certificates/", "update-ca-certificates"},
{"/usr/share/ca-certificates/trust-source/anchors/", "update-ca-trust"},
{"/usr/share/ca-certificates/trust-source/anchors/", "trust extract-compat"},
{"/etc/pki/ca-trust/source/anchors/", "update-ca-trust"},
{"/etc/ssl/ca-certificates/", "update-ca-certificates"},
}
is := false
for _, action := range actions {
dir := action[0]
if err := exec.Command("sudo", "cp", "-f", s.CertFile, dir+appOnce.AppName+".crt").Run(); err != nil {
fmt.Printf("Failed to copy to %s: %v\n", dir, err)
continue
}
cmd := action[1]
if err := exec.Command("sudo", cmd).Run(); err != nil {
fmt.Printf("Failed to refresh certificates using %s: %v\n", cmd, err)
continue
}
is = true
}
if !is {
return "", fmt.Errorf("Certificate installation failed")
}
return "", nil
}

83
core/system_windows.go Normal file
View File

@@ -0,0 +1,83 @@
//go:build windows
package core
import (
"crypto/x509"
"encoding/pem"
"errors"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"
"unsafe"
)
func (s *SystemSetup) setProxy() error {
key, err := registry.OpenKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Internet Settings`, registry.SET_VALUE)
if err != nil {
return err
}
defer key.Close()
err = key.SetStringValue("ProxyServer", "127.0.0.1:"+globalConfig.Port)
if err != nil {
return err
}
err = key.SetDWordValue("ProxyEnable", 1)
if err != nil {
return err
}
return nil
}
func (s *SystemSetup) unsetProxy() error {
key, err := registry.OpenKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Internet Settings`, registry.SET_VALUE)
if err != nil {
return err
}
defer key.Close()
err = key.SetDWordValue("ProxyEnable", 0)
if err != nil {
return err
}
return nil
}
func (s *SystemSetup) installCert() (string, error) {
certData, err := s.initCert()
if err != nil {
return "", errors.New("installCert1:" + err.Error())
}
block, _ := pem.Decode(certData)
if block == nil {
return "", errors.New("Failed to parse certificate PEM" + err.Error())
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return "", errors.New("installCert3:" + err.Error())
}
rootStorePtr, err := windows.UTF16PtrFromString("ROOT")
if err != nil {
return "", errors.New("installCert4:" + err.Error())
}
store, err := windows.CertOpenStore(windows.CERT_STORE_PROV_SYSTEM, 0, 0, windows.CERT_SYSTEM_STORE_LOCAL_MACHINE, uintptr(unsafe.Pointer(rootStorePtr)))
if err != nil {
return "", errors.New("installCert5:" + err.Error())
}
defer windows.CertCloseStore(store, 0)
certContext, err := windows.CertCreateCertificateContext(windows.X509_ASN_ENCODING|windows.PKCS_7_ASN_ENCODING, &cert.Raw[0], uint32(len(cert.Raw)))
if err != nil {
return "", errors.New("installCert6:" + err.Error())
}
defer windows.CertFreeCertificateContext(certContext)
err = windows.CertAddCertificateContextToStore(store, certContext, windows.CERT_STORE_ADD_REPLACE_EXISTING, nil)
if err != nil {
return "", errors.New("installCert7:" + err.Error())
}
return "", nil
}

157
core/utils.go Normal file
View File

@@ -0,0 +1,157 @@
package core
import (
"crypto/md5"
"encoding/hex"
"fmt"
"github.com/wailsapp/wails/v2/pkg/runtime"
"net/url"
"os"
"strings"
"time"
)
func Empty(data interface{}) {
}
func DialogErr(message string) {
_, _ = runtime.MessageDialog(appOnce.ctx, runtime.MessageDialogOptions{
Type: runtime.ErrorDialog,
Title: "Error",
Message: message,
DefaultButton: "Cancel",
})
}
func IsDevelopment() bool {
return os.Getenv("APP_ENV") == "development"
}
func FileExist(file string) bool {
_, err := os.Stat(file)
if err != nil {
return false
}
if os.IsNotExist(err) {
return false
}
return true
}
func CreateDirIfNotExist(dir string) error {
if _, err := os.Stat(dir); os.IsNotExist(err) {
return os.MkdirAll(dir, 0777)
}
return nil
}
func TypeSuffix(mime string) (string, string) {
switch strings.ToLower(mime) {
case "image/png",
"image/webp",
"image/jpeg",
"image/jpg",
"image/gif",
"image/avif",
"image/bmp",
"image/tiff",
"image/heic",
"image/x-icon",
"image/svg+xml",
"image/vnd.adobe.photoshop":
return "image", ".png"
case "audio/mpeg",
"audio/wav",
"audio/aiff",
"audio/x-aiff",
"audio/aac",
"audio/ogg",
"audio/flac",
"audio/midi",
"audio/x-midi",
"audio/x-ms-wma",
"audio/opus",
"audio/webm",
"audio/mp4",
"audio/mp3":
return "audio", ".mp3"
case "video/mp4",
"video/webm",
"video/ogg",
"video/x-msvideo",
"video/mpeg",
"video/quicktime",
"video/x-ms-wmv",
"video/3gpp",
"video/x-matroska":
return "video", ".mp4"
case "audio/video",
"video/x-flv":
return "live", ".mp4"
case "application/vnd.apple.mpegurl",
"application/x-mpegurl":
return "m3u8", ".m3u8"
case "application/pdf":
return "pdf", ".pdf"
case "application/vnd.ms-powerpoint",
"application/vnd.openxmlformats-officedocument.presentationml.presentation":
return "ppt", ".ppt"
case "application/vnd.ms-excel",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
return "xls", ".xls"
case "application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document":
return "doc", ".doc"
}
return "", ""
}
func BuildReferer(rawURL string) string {
u, err := url.Parse(rawURL)
if err != nil {
return ""
}
return u.Scheme + "://" + u.Host + "/"
}
func Md5(data string) string {
hashNew := md5.New()
hashNew.Write([]byte(data))
hash := hashNew.Sum(nil)
return hex.EncodeToString(hash)
}
func FormatSize(size float64) string {
if size > 1048576 {
return fmt.Sprintf("%.2fMB", float64(size)/1048576)
}
if size > 1024 {
return fmt.Sprintf("%.2fKB", float64(size)/1024)
}
return fmt.Sprintf("%.0fb", size)
}
func GetTopLevelDomain(rawURL string) string {
parsedURL, err := url.Parse(rawURL)
if err != nil {
return ""
}
host := parsedURL.Hostname()
parts := strings.Split(host, ".")
if len(parts) < 2 {
return ""
}
return strings.Join(parts[len(parts)-2:], ".")
}
func GetCurrentDateTimeFormatted() string {
now := time.Now()
return fmt.Sprintf("%04d%02d%02d%02d%02d%02d",
now.Year(),
now.Month(),
now.Day(),
now.Hour(),
now.Minute(),
now.Second())
}

View File

@@ -1,107 +0,0 @@
/**
* @see https://www.electron.build/configuration/configuration
*/
{
"$schema": "https://raw.githubusercontent.com/electron-userland/electron-builder/master/packages/app-builder-lib/scheme.json",
"appId": "com.putyy.res-downloader",
"asar": true,
"directories": {
"output": "release/${version}"
},
"files": [
"dist-electron",
"dist"
],
"mac": {
"icon": "electron/res/icon/mac.icns",
"artifactName": "${productName}_${version}.${arch}.${ext}",
"singleArchFiles": "*",
"target": [
{
"target": "dmg",
"arch": [
'x64',
'arm64',
'universal'
]
}
],
"extraResources": [
{
"from": "electron/res/darwin/aria2/aria2.conf",
"to": "electron/res/darwin/aria2/aria2.conf",
"filter": ["**/*"]
},
{
"from": "electron/res/darwin/aria2/${arch}/aria2c",
"to": "electron/res/darwin/aria2/aria2c",
"filter": ["**/*"],
}
]
},
"linux": {
"icon": "electron/res/icon/icon.png",
"artifactName": "${productName}_${version}.${arch}.${ext}",
"category": "Network",
"target": [
{
"target": "AppImage",
"arch": [
"x64",
"arm64",
"armv7l"
]
},
{
"target": "deb",
"arch": [
"x64",
"arm64",
"armv7l"
]
}
],
"extraResources": [
{
"from": "electron/res/linux/aria2/aria2.conf",
"to": "electron/res/linux/aria2/aria2.conf",
"filter": ["**/*"]
},
{
"from": "electron/res/linux/aria2/${arch}/aria2c",
"to": "electron/res/linux/aria2/aria2c",
"filter": ["**/*"],
}
]
},
"win": {
"icon": "electron/res/icon/win.ico",
"artifactName": "${productName}_${version}.${ext}",
"target": [
{
"target": "nsis",
"arch": [
"x64"
]
}
],
"extraResources": [
{
"from": "electron/res/win",
"to": "electron/res/win",
"filter": ["**/*"],
}
]
},
"nsis": {
"oneClick": false,
"perMachine": false,
"allowElevation": true,
"allowToChangeInstallationDirectory": true,
"deleteAppDataOnUninstall": false
},
"extraResources": [
"electron/res/icon",
"electron/res/keys",
]
}

View File

@@ -1,11 +0,0 @@
/// <reference types="vite-plugin-electron/electron-env" />
declare namespace NodeJS {
interface ProcessEnv {
VSCODE_DEBUG?: 'true'
DIST_ELECTRON: string
DIST: string
/** /dist/ or /public/ */
VITE_PUBLIC: string
}
}

View File

@@ -1,51 +0,0 @@
const axios = require('axios')
import CONFIG from './const'
export class Aria2RPC {
constructor() {
this.url = `http://127.0.0.1:${CONFIG.ARIA_PORT}/jsonrpc`
this.id = 1
}
call(method, params) {
const requestData = {
jsonrpc: "2.0",
method: method,
params: params,
id: this.id++
};
return axios.post(this.url, requestData, {
headers: {
'Content-Type': 'application/json'
},
}).then((response)=>{
return response.data
})
}
addUri(uri, dir, filename, headers = {}) {
return this.call('aria2.addUri', [uri, {
dir: dir,
out: filename,
headers: headers,
}]);
}
tellStatus(gid) {
return this.call('aria2.tellStatus', [gid]);
}
calculateDownloadProgress(bitfield) {
// 将十六进制的 bitfield 转换为二进制字符串
const totalPieces = bitfield.length * 4; // 每个十六进制字符对应 4 位
const binaryString = bitfield.split('').map(hex => parseInt(hex, 16).toString(2).padStart(4, '0')).join('');
// 计算已下载的部分数
const downloadedPieces = binaryString.split('').filter(bit => bit === '1').length;
// 计算进度百分比
const progressPercentage = (downloadedPieces / totalPieces) * 100;
return progressPercentage.toFixed(2); // 保留两位小数
}
}

View File

@@ -1,70 +0,0 @@
import CONFIG from './const'
import {mkdirp} from 'mkdirp'
import fs from 'fs'
import path from 'path'
import {clipboard, dialog} from 'electron'
import spawn from 'cross-spawn'
export function checkCertInstalled() {
return fs.existsSync(CONFIG.INSTALL_CERT_FLAG)
}
export function installCert(checkInstalled = true) {
try {
if (checkInstalled && checkCertInstalled()) {
return;
}
mkdirp.sync(path.dirname(CONFIG.INSTALL_CERT_FLAG))
if (process.platform === 'darwin') {
handleMacCertInstallation()
} else if (process.platform === 'win32') {
handleWindowsCertInstallation()
} else {
handleOtherCertInstallation()
}
} catch (e) {
handleOtherCertInstallation()
}
}
// MacOS 证书安装处理
function handleMacCertInstallation() {
clipboard.writeText(
`echo "输入本地登录密码" && sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "${CONFIG.CERT_PUBLIC_PATH}" && touch ${CONFIG.INSTALL_CERT_FLAG} && echo "安装完成"`
);
dialog.showMessageBoxSync({
type: 'info',
message: '命令已复制到剪贴板,粘贴到终端并运行以安装并信任证书',
});
}
// Linux 证书安装处理
function handleOtherCertInstallation() {
clipboard.writeText(CONFIG.CERT_PUBLIC_PATH);
dialog.showMessageBoxSync({
type: "info",
message: `请手动安装证书,证书文件路径:${CONFIG.CERT_PUBLIC_PATH} 已复制到剪贴板`,
});
}
// Windows 证书安装处理
function handleWindowsCertInstallation() {
const result = spawn.sync(CONFIG.WIN_CERT_INSTALL_HELPER, [
'-c',
'-add',
CONFIG.CERT_PUBLIC_PATH,
'-s',
'root',
]);
if (result.stdout.toString().includes('Succeeded')) {
fs.writeFileSync(CONFIG.INSTALL_CERT_FLAG, '');
} else {
handleOtherCertInstallation();
}
}

View File

@@ -1,30 +0,0 @@
import path from 'path'
import isDev from 'electron-is-dev'
import os from 'os'
import {app} from 'electron'
const APP_PATH = app.getAppPath();
// 对于一些 shell 去执行的文件asar 目录下无法使用。配合 extraResources
const EXECUTABLE_PATH = path.join(
APP_PATH.indexOf('app.asar') > -1
? APP_PATH.substring(0, APP_PATH.indexOf('app.asar'))
: APP_PATH,
'electron/res',
)
const HOME_PATH = path.join(os.homedir(), '.res-downloader@putyy')
export default {
IS_DEV: isDev,
EXECUTABLE_PATH,
HOME_PATH,
APP_CN_NAME: '爱享素材下载器',
APP_EN_NAME: 'ResDownloader',
CERT_PRIVATE_PATH: path.join(EXECUTABLE_PATH, './keys/private.pem'),
CERT_PUBLIC_PATH: path.join(EXECUTABLE_PATH, './keys/public.pem'),
INSTALL_CERT_FLAG: path.join(HOME_PATH, './res-downloader-installed.lock'),
WIN_CERT_INSTALL_HELPER: path.join(EXECUTABLE_PATH, './win/w_c.exe'),
REGEDIT_VBS_PATH: path.join(EXECUTABLE_PATH, './win/regedit-vbs'),
OPEN_SSL_BIN_PATH: path.join(EXECUTABLE_PATH, './win/openssl/openssl.exe'),
OPEN_SSL_CNF_PATH: path.join(EXECUTABLE_PATH, './win/openssl/openssl.cnf'),
ARIA_PORT: "18899",
};

View File

@@ -1,243 +0,0 @@
import {app, BrowserWindow, shell, ipcMain, Menu} from 'electron'
import {release} from 'node:os'
import {join} from 'node:path'
import CONFIG from './const'
import initIPC, {setWin} from './ipc'
import {closeProxy} from "./setProxy"
import log from "electron-log"
import path from 'path'
import {spawn} from 'child_process'
import {startServer} from "./proxyServer";
import fs from "fs";
// The built directory structure
//
// ├─┬ dist-electron
// │ ├─┬ main
// │ │ └── index.js > Electron-Main
// │ └─┬ preload
// │ └── index.js > Preload-Scripts
// ├─┬ dist
// │ └── index.html > Electron-Renderer
//
process.env.DIST_ELECTRON = join(__dirname, '..')
process.env.DIST = join(process.env.DIST_ELECTRON, '../dist')
process.env.VITE_PUBLIC = process.env.VITE_DEV_SERVER_URL
? join(process.env.DIST_ELECTRON, '../public')
: process.env.DIST
// Disable GPU Acceleration for Windows 7
if (release().startsWith('6.1')) app.disableHardwareAcceleration()
// Set application name for Windows 10+ notifications
if (process.platform === 'win32') app.setAppUserModelId(app.getName())
if (!app.requestSingleInstanceLock()) {
app.quit()
process.exit(0)
}
// Remove electron security warnings
// This warning only shows in development mode
// Read more on https://www.electronjs.org/docs/latest/tutorial/security
// process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'
app.commandLine.appendSwitch('--no-proxy-server')
process.on('uncaughtException', () => {
});
process.on('unhandledRejection', () => {
});
let mainWindow: BrowserWindow | null = null
let previewWin: BrowserWindow | null = null
let aria2Process
// Here, you can also use other preload
const preload = join(__dirname, '../preload/index.js')
const url = process.env.VITE_DEV_SERVER_URL
const indexHtml = join(process.env.DIST, 'index.html')
global.videoList = {}
global.isStartProxy = false
global.isSettingProxy = false
global.resdConfig = {
save_dir: "",
quality: "-1",
proxy: "",
port: 8899,
}
// app.whenReady().then(createWindow)
app.on('window-all-closed', () => {
mainWindow = null
if (process.platform !== 'darwin') app.quit()
})
app.on('second-instance', () => {
if (mainWindow) {
// Focus on the main window if the user tried to open another
if (mainWindow.isMinimized()) mainWindow.restore()
mainWindow.focus()
}
})
app.on('activate', () => {
const allWindows = BrowserWindow.getAllWindows()
if (allWindows.length) {
allWindows[0].focus()
} else {
createWindow()
createPreviewWindow(mainWindow)
setWin(mainWindow, previewWin)
}
})
app.on('before-quit', async e => {
e.preventDefault()
try {
await closeProxy()
aria2Process && aria2Process.kill();
log.log("--------------closeProxy success--------------")
} catch (error) {
log.log("--------------proxy catch err--------------", error)
}
app.exit()
})
function createWindow() {
Menu.setApplicationMenu(null);
mainWindow = new BrowserWindow({
title: 'Main window',
icon: join(process.env.VITE_PUBLIC, 'favicon.ico'),
width: 1024,
minWidth: 960,
height: 768,
minHeight: 640,
webPreferences: {
preload,
// Warning: Enable nodeIntegration and disable contextIsolation is not secure in production
// Consider using contextBridge.exposeInMainWorld
// Read more on https://www.electronjs.org/docs/latest/tutorial/context-isolation
nodeIntegration: true,
contextIsolation: false,
webSecurity: false,
},
})
if (process.env.VITE_DEV_SERVER_URL) { // electron-vite-vue#298
mainWindow.loadURL(url).then(r => {
})
// Open devTool if the app is not packaged
// mainWindow.webContents.openDevTools()
} else {
mainWindow.loadFile(indexHtml).then(r => {
})
}
CONFIG.IS_DEV && mainWindow.webContents.openDevTools()
// Test actively push message to the Electron-Renderer
mainWindow.webContents.on('did-finish-load', () => {
mainWindow?.webContents.send('main-process-message', new Date().toLocaleString())
})
// Make all links open with the browser, not with the application
mainWindow.webContents.setWindowOpenHandler(({url}) => {
if (url.startsWith('https:')) shell.openExternal(url)
return {action: 'deny'}
})
// win.webContents.on('will-navigate', (event, url) => { }) #344
}
function createPreviewWindow(parent: BrowserWindow) {
previewWin = new BrowserWindow({
parent: parent,
width: 600,
height: 400,
minWidth: 600,
minHeight: 400,
show: false,
// paintWhenInitiallyHidden: false,
webPreferences: {
webSecurity: false,
nodeIntegration: true,
contextIsolation: false,
},
})
// previewWin.hide()
previewWin.setTitle("预览")
previewWin.on("page-title-updated", (event) => {
event.preventDefault()
})
previewWin.on("close", (event) => {
event.preventDefault()
previewWin.hide()
})
}
function createAria2Process() {
// 根据操作系统选择 aria2 的路径
try {
let aria2Path, aria2Conf
if (process.platform === 'win32') {
// Windows
aria2Path = path.join(CONFIG.EXECUTABLE_PATH, "./win/aria2/aria2c.exe")
aria2Conf = path.join(CONFIG.EXECUTABLE_PATH, "./win/aria2/aria2.conf")
} else {
aria2Path = path.join(CONFIG.EXECUTABLE_PATH, `./${process.platform}/aria2` + (CONFIG.IS_DEV ? `/${process.arch}` : '/') + "/aria2c");
aria2Conf = path.join(CONFIG.EXECUTABLE_PATH, `./${process.platform}/aria2/aria2.conf`)
}
aria2Process = spawn(aria2Path, [`--conf-path=${aria2Conf}`, `--rpc-listen-port=${CONFIG.ARIA_PORT}`], {
windowsHide: false,
stdio: CONFIG.IS_DEV ? 'pipe' : 'ignore'
});
if(!aria2Process){
console.log("start aria2 error")
}
if (CONFIG.IS_DEV) {
aria2Process.stdout.on('data', (data) => {
console.log(`aria2: ${data}`);
});
aria2Process.stderr.on('data', (data) => {
console.log(`aria2 error: ${data}`);
});
}
} catch (e) {
console.log(`aria2 process start err`, e);
}
}
function initConfig(){
const configPath = path.join(app.getPath('userData'), 'resd_config.json')
if (!fs.existsSync(configPath)) {
return
}
const buff = fs.readFileSync(configPath);
if (buff) {
try {
const jsonData = JSON.parse(buff)
global.resdConfig = Object.assign({}, global.resdConfig, jsonData)
if (!global.resdConfig.port) {
global.resdConfig.port = 8899
}
} catch (parseErr) {
}
}
}
app.whenReady().then(() => {
createWindow()
createPreviewWindow(mainWindow)
setWin(mainWindow, previewWin)
initConfig()
initIPC()
startServer(mainWindow)
createAria2Process()
})

View File

@@ -1,225 +0,0 @@
import {ipcMain, dialog, BrowserWindow, app, shell} from 'electron'
import {startServer} from './proxyServer'
import {installCert, checkCertInstalled} from './cert'
import {decodeWxFile, typeSuffix, getCurrentDateTimeFormatted} from './utils'
// @ts-ignore
import {hexMD5} from '../../src/common/md5'
import {Aria2RPC} from './aria2Rpc'
import fs from "fs"
import urlTool from "url";
import {closeProxy, setProxy} from "./setProxy";
import path from 'path'
let win: BrowserWindow
let previewWin: BrowserWindow
const aria2RpcClient = new Aria2RPC()
export default function initIPC() {
ipcMain.handle('invoke_app_is_init', async (event, arg) => {
// 初始化应用 安装证书相关
return checkCertInstalled()
})
ipcMain.handle('invoke_init_app', (event, arg) => {
// 开始 初始化应用 安装证书相关
installCert(false)
})
ipcMain.handle('invoke_set_config', (event, data) => {
const filePath = path.join(app.getPath('userData'), 'resd_config.json');
fs.writeFile(filePath, JSON.stringify(data), ()=>{})
global.resdConfig = Object.assign({}, global.resdConfig, data)
global.resdConfig.port = parseInt(global.resdConfig.port)
return true
})
ipcMain.handle('invoke_set_proxy', async (event, arg) => {
// 启动代理服务
if (!global.isStartProxy) {
dialog.showMessageBoxSync({
type: "error",
message: "代理未启动",
});
return false
}
try {
if (arg.proxy) {
await setProxy('127.0.0.1', global.resdConfig.port)
}else{
await closeProxy('127.0.0.1', global.resdConfig.port)
}
return true
} catch (err) {
console.error(err);
dialog.showMessageBoxSync({
type: "error",
message: err.toString(),
});
return false
}
// let upstream_proxy = ""
// if (arg.upstream_proxy && !arg.upstream_proxy.includes(':8899')) {
// upstream_proxy = arg.upstream_proxy
// }
//
// global.isStartProxy = true
// return startServer({
// win: win,
// upstreamProxy: upstream_proxy,
// setProxyErrorCallback: err => {
// console.log('setProxyErrorCallback', err)
// },
// })
})
ipcMain.handle('invoke_select_down_dir', async (event, arg) => {
// 选择下载位置
const result = dialog.showOpenDialogSync({title: '保存', properties: ['openDirectory']})
if (!result?.[0]) {
return false
}
return result?.[0]
})
ipcMain.handle('invoke_select_wx_file', async (event, {index, data}) => {
// 选择下载位置
const result = dialog.showOpenDialogSync({title: '保存', properties: ['openFile']})
if (!result?.[0]) {
return false
}
return decodeWxFile(result?.[0], data.decode_key, result?.[0].replace(".mp4", "_解密.mp4"))
})
ipcMain.handle('invoke_file_exists', async (event, {save_path, url, description}) => {
let fileName = description ? description.replace(/[^a-zA-Z\u4e00-\u9fa5]/g, '') : hexMD5(url);
let res = fs.existsSync(`${save_path}/${fileName}.mp4`)
return {is_file: res, fileName: `${save_path}/${fileName}.mp4`}
})
ipcMain.handle('invoke_down_file', async (event, {data, save_path, quality}) => {
let down_url = data.url
if (!down_url) {
return new Promise((resolve, reject) => {
resolve(false);
});
}
if (quality === "0" && data.decode_key) {
const urlInfo = urlTool.parse(down_url, true);
console.log('urlInfo', urlInfo)
if (urlInfo.query["token"] && urlInfo.query["encfilekey"]) {
down_url = urlInfo.protocol + "//" + urlInfo.hostname + urlInfo.pathname.replace("251/20302", "251/20304") +
"?encfilekey=" + urlInfo.query["encfilekey"] +
"&token=" + urlInfo.query["token"]
console.log("down_url:", down_url)
}
} else if (quality !== "-1" && data.decode_key && data.file_format) {
const format = data.file_format.split('#');
const qualityMap = [
format[0],
format[Math.floor(format.length / 2)],
format[format.length - 1]
];
down_url += "&X-snsvideoflag=" + qualityMap[quality];
}
let fileName = data?.description ? data.description.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, '') : hexMD5(down_url);
fileName = fileName + "_" + getCurrentDateTimeFormatted() + typeSuffix(data.type)[1]
let save_path_file = `${save_path}/${fileName}`
if (process.platform === 'win32') {
save_path_file = `${save_path}\\${fileName}`
}
let headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36',
}
return new Promise((resolve, reject) => {
if (data?.referer) {
headers['Referer'] = data?.referer
}
aria2RpcClient.addUri([down_url], save_path, fileName, headers).then((response) => {
let currentGid = response.result
let progressIntervalId = null
// // 开始定时查询下载进度
progressIntervalId = setInterval(() => {
aria2RpcClient.tellStatus(currentGid).then((status) => {
if (status.result.status !== "complete") {
const progress = aria2RpcClient.calculateDownloadProgress(status.result.bitfield)
win?.webContents.send('on_down_file_schedule', {schedule: `已下载${progress}%`})
} else {
clearInterval(progressIntervalId);
if (data.decode_key) {
win?.webContents.send('on_down_file_schedule', {schedule: `开始解密`})
decodeWxFile(save_path_file, data.decode_key, save_path_file.replace(".mp4", "_wx.mp4")).then((res) => {
fs.unlink(save_path_file, (err) => {
})
resolve(res)
}).catch((error) => {
console.log("err:", error)
resolve(false);
})
} else {
resolve({
fullFileName: save_path_file,
})
}
}
}).catch((error) => {
console.error(error)
clearInterval(progressIntervalId)
resolve(false)
});
}, 1000)
}).catch((error) => {
console.log("err:", error)
resolve(false)
});
});
});
ipcMain.handle('invoke_resources_preview', async (event, {url}) => {
if (!url) {
return
}
previewWin.loadURL(url).then(r => {
return
}).catch(res => {
})
previewWin.show()
return
})
ipcMain.handle('invoke_open_default_browser', (event, {url}) => {
shell.openExternal(url).then(r => {
})
})
ipcMain.handle('invoke_open_file_dir', (event, {save_path}) => {
shell.showItemInFolder(save_path)
})
ipcMain.handle('invoke_file_del', (event, {url_sign}) => {
if (url_sign === "all") {
global.videoList = {}
return
}
if (url_sign) {
delete global.videoList[url_sign]
return
}
})
ipcMain.handle('invoke_window_restart', (event) => {
app.relaunch()
app.exit()
})
}
export function setWin(w, p) {
win = w
previewWin = p
}

View File

@@ -1,173 +0,0 @@
import fs from 'fs'
import log from 'electron-log'
import CONFIG from './const'
import * as urlTool from "url"
import {toSize, typeSuffix} from "./utils"
// @ts-ignore
import {hexMD5} from '../../src/common/md5'
import pkg from '../../package.json'
const hoXy = require('hoxy')
if (process.platform === 'win32') {
process.env.OPENSSL_BIN = CONFIG.OPEN_SSL_BIN_PATH
process.env.OPENSSL_CONF = CONFIG.OPEN_SSL_CNF_PATH
}
const resObject = {
url: "",
url_sign: "",
referer: "",
cover_url: "",
file_format: "",
platform: "",
size: "",
type: "video/mp4",
type_str: 'video',
progress_bar: "",
save_path: "",
decode_key: "",
description: ""
}
const vv = hexMD5(pkg.version) + (CONFIG.IS_DEV ? Math.random() : "")
export function startServer(win) {
try {
let upstreamProxy = ""
if (global.resdConfig.proxy && !global.resdConfig.proxy.includes(':' + global.resdConfig.port)) {
upstreamProxy = global.resdConfig?.proxy
}
console.log("global.resdConfig.port:", global.resdConfig.port)
const proxy = hoXy.createServer({
upstreamProxy: upstreamProxy,
certAuthority: {
key: fs.readFileSync(CONFIG.CERT_PRIVATE_PATH),
cert: fs.readFileSync(CONFIG.CERT_PUBLIC_PATH),
},
})
.listen(global.resdConfig.port, () => {
global.isStartProxy = true
})
.on('error', err => {
console.error("hoXy err:", err);
})
intercept(proxy, win)
} catch (e) {
console.error("--------------proxy catch err--------------");
}
}
function intercept(proxy, win) {
proxy.intercept(
{
phase: 'request',
hostname: 'res-downloader.666666.com',
as: 'json',
},
(req, res) => {
res.string = 'ok'
res.statusCode = 200
try {
if (req.json?.media?.length <= 0) {
return
}
const media = req.json?.media[0]
const url_sign: string = hexMD5(media.url)
if (!media?.decodeKey || global.videoList.hasOwnProperty(url_sign) === true) {
return
}
const urlInfo = urlTool.parse(media.url, true)
global.videoList[url_sign] = media.url
win.webContents.send('on_get_queue', Object.assign({}, resObject, {
url_sign: url_sign,
url: media.url + media.urlToken,
cover_url: media.coverUrl,
referer: "",
file_format: media.spec.map((res) => res.fileFormat).join('#'),
platform: urlInfo.hostname,
size: toSize(media.fileSize),
type: "video/mp4",
type_str: 'video',
decode_key: media.decodeKey,
description: req.json.description,
}))
} catch (e) {
log.log(e.toString())
}
},
)
proxy.intercept(
{
phase: 'response',
hostname: 'channels.weixin.qq.com',
as: 'string',
},
async (req, res) => {
if (req.url.includes('/web/pages/feed') || req.url.includes('/web/pages/home')) {
res.string = res.string.replaceAll('.js"', '.js?v=' + vv + '"')
res.statusCode = 200
}
},
)
proxy.intercept(
{
phase: 'response',
hostname: 'res.wx.qq.com',
as: 'string',
},
async (req, res) => {
if (req.url.endsWith('.js?v=' + vv)) {
res.string = res.string.replaceAll('.js"', '.js?v=' + vv + '"');
}
if (req.url.includes("web/web-finder/res/js/virtual_svg-icons-register.publish")) {
res.string = res.string.replace(/get\s*media\s*\(\)\s*\{/, `
get media(){
if(this.objectDesc){
fetch("https://res-downloader.666666.com", {
method: "POST",
mode: "no-cors",
body: JSON.stringify(this.objectDesc),
});
};
`)
}
}
);
proxy.intercept(
{
phase: 'response',
},
async (req, res) => {
try {
// 拦截响应
const contentType = res?._data?.headers?.['content-type']
const [resType, suffix] = typeSuffix(contentType)
if (resType) {
const url_sign: string = hexMD5(req.fullUrl())
const res_url = req.fullUrl()
const urlInfo = urlTool.parse(res_url, true)
const contentLength = res?._data?.headers?.['content-length']
if (global.videoList.hasOwnProperty(url_sign) === false) {
global.videoList[url_sign] = res_url
let referer = req?._data?.headers?.['referer']
win.webContents.send('on_get_queue', Object.assign({}, resObject, {
url: res_url,
url_sign: url_sign,
referer: referer ? referer : "",
platform: urlInfo.hostname,
size: toSize(contentLength ? contentLength : 0),
type: contentType,
type_str: resType,
}))
}
}
} catch (e) {
log.log("--------------proxy response err--------------", e)
}
},
)
}

View File

@@ -1,140 +0,0 @@
import {exec} from 'child_process'
// @ts-ignore
import regedit from 'regedit'
import CONFIG from './const'
import {dialog} from "electron";
regedit.setExternalVBSLocation(CONFIG.REGEDIT_VBS_PATH)
export async function setProxy(host, port) {
if (process.platform === 'darwin') {
const networks = await getMacAvailableNetworks()
// @ts-ignore
if (networks.length === 0) {
throw 'no network'
}
return Promise.all(
// @ts-ignore
networks.map(network => {
return new Promise((resolve, reject) => {
exec(`networksetup -setsecurewebproxy "${network}" ${host} ${port}`, error => {
if (error) {
reject(null)
} else {
exec(`networksetup -setwebproxy "${network}" ${host} ${port}`, error => {
if (error) {
reject(null)
} else {
resolve(network)
}
});
}
});
});
}),
);
} else if (process.platform === 'linux') {
dialog.showMessageBoxSync({
type: "info",
message: `请手动设置系统代理 默认为: 127.0.0.1:8899`,
});
return new Promise((resolve, reject) => {})
} else {
const valuesToPut = {
'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings': {
ProxyServer: {
value: `${host}:${port}`,
type: 'REG_SZ',
},
ProxyEnable: {
value: 1,
type: 'REG_DWORD',
},
},
};
// @ts-ignore
return regedit.promisified.putValue(valuesToPut)
}
}
export async function closeProxy() {
if (process.platform === 'darwin') {
const networks = await getMacAvailableNetworks()
// @ts-ignore
if (networks.length === 0) {
throw 'no network'
}
return Promise.all(
// @ts-ignore
networks.map(network => {
return new Promise((resolve, reject) => {
exec(`networksetup -setsecurewebproxystate "${network}" off`, error => {
if (error) {
reject(null)
} else {
exec(`networksetup -setwebproxystate "${network}" off`, error => {
if (error) {
reject(null)
} else {
resolve(network)
}
});
}
});
});
}),
);
} else if (process.platform === 'linux') {
dialog.showMessageBoxSync({
type: "info",
message: `请手动取消系统代理`,
});
} else {
const valuesToPut = {
'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings': {
ProxyEnable: {
value: 0,
type: 'REG_DWORD',
},
},
};
// @ts-ignore
return regedit.promisified.putValue(valuesToPut)
}
}
function getMacAvailableNetworks() {
return new Promise((resolve, reject) => {
exec('networksetup -listallnetworkservices', (error, stdout) => {
if (error) {
reject(error)
} else {
Promise.all(
stdout
.toString()
.split('\n')
.map(network => {
return new Promise(resolve => {
exec(
`networksetup getinfo "${network}" | grep "^IP address:\\s\\d"`,
(error, stdout) => {
if (error) {
resolve(null)
} else {
resolve(stdout ? network : null)
}
},
)
})
}),
).then(networks => {
resolve(networks.filter(Boolean))
})
}
})
})
}

View File

@@ -1,176 +0,0 @@
import fs from 'fs'
import {Transform} from 'stream'
import {getDecryptionArray} from '../wxjs/decrypt.js'
const axios = require('axios')
function xorTransform(decryptionArray) {
let processedBytes = 0;
return new Transform({
transform(chunk, encoding, callback) {
if (processedBytes < decryptionArray.length) {
let remaining = Math.min(decryptionArray.length - processedBytes, chunk.length);
for (let i = 0; i < remaining; i++) {
chunk[i] = chunk[i] ^ decryptionArray[processedBytes + i];
}
processedBytes += remaining;
}
this.push(chunk);
callback();
}
});
}
function downloadFile(url, decodeKey, fullFileName, progressCallback) {
let xorStream = null
if (decodeKey) {
xorStream = xorTransform(getDecryptionArray(decodeKey));
}
let config = {
responseType: 'stream',
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36',
},
}
if (url.includes("douyin")) {
config.headers['Referer'] = url
}
return axios.get(url, config).then(({data, headers}) => {
let currentLen = 0
const totalLen = headers['content-length']
return new Promise((resolve, reject) => {
data.on('data', ({length}) => {
currentLen += length
// @ts-ignore
progressCallback?.(currentLen / totalLen)
});
data.on('error', err => reject(err))
if (xorStream) {
data.pipe(xorStream).pipe(
fs.createWriteStream(fullFileName).on('finish', () => {
resolve({
fullFileName,
totalLen,
});
}),
);
} else {
data.pipe(
fs.createWriteStream(fullFileName).on('finish', () => {
resolve({
fullFileName,
totalLen,
});
}),
);
}
});
});
}
function decodeWxFile(fileName, decodeKey, fullFileName) {
let xorStream = xorTransform(getDecryptionArray(decodeKey));
let data = fs.createReadStream(fileName);
return new Promise((resolve, reject) => {
data.on('error', err => reject(err));
data.pipe(xorStream).pipe(
fs.createWriteStream(fullFileName).on('finish', () => {
resolve({
fullFileName,
});
}),
);
});
}
function toSize(size: number) {
if (size > 1048576) {
return (size / 1048576).toFixed(2) + "MB"
}
if (size > 1024) {
return (size / 1024).toFixed(2) + "KB"
}
return size + 'b'
}
function typeSuffix(type: string) {
switch (type ? type.toLowerCase() : type) {
case "video/mp4":
case "video/webm":
case "video/ogg":
case "video/x-msvideo":
case "video/mpeg":
case "video/quicktime":
case "video/x-ms-wmv":
case "video/3gpp":
case "video/x-matroska":
return ["video", ".mp4"];
case "audio/video":
case "video/x-flv":
return ["live", ".mp4"];
case "image/png":
case "image/webp":
case "image/jpeg":
case "image/jpg":
case "image/gif":
case "image/avif":
case "image/bmp":
case "image/tiff":
case "image/heic":
case "image/x-icon":
case "image/svg+xml":
case "image/vnd.adobe.photoshop":
return ["image", ".png"];
case "audio/mpeg":
case "audio/wav":
case "audio/aiff":
case "audio/x-aiff":
case "audio/aac":
case "audio/ogg":
case "audio/flac":
case "audio/midi":
case "audio/x-midi":
case "audio/x-ms-wma":
case "audio/opus":
case "audio/webm":
case "audio/mp4":
case "audio/mp3":
case "audio/mp4;charset=UTF-8":
return ["audio", ".mp3"];
case "application/vnd.apple.mpegurl":
case "application/x-mpegurl":
return ["m3u8", ".m3u8"];
case "application/pdf":
return ["pdf", ".pdf"];
case "application/vnd.ms-powerpoint":
case "application/vnd.openxmlformats-officedocument.presentationml.presentation":
return ["ppt", ".ppt"];
case "application/vnd.ms-excel":
case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
return ["xls", ".xls"];
case "application/msword":
case "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
return ["doc", ".doc"];
}
return ["", ""]
}
function getCurrentDateTimeFormatted() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0'); // 月份从0开始所以要加1
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
return `${year}${month}${day}${hours}${minutes}${seconds}`;
}
export {downloadFile, toSize, decodeWxFile, typeSuffix, getCurrentDateTimeFormatted}

View File

@@ -1,86 +0,0 @@
function domReady(condition: DocumentReadyState[] = ['complete', 'interactive']) {
return new Promise((resolve) => {
if (condition.includes(document.readyState)) {
resolve(true)
} else {
document.addEventListener('readystatechange', () => {
if (condition.includes(document.readyState)) {
resolve(true)
}
})
}
})
}
const safeDOM = {
append(parent: HTMLElement, child: HTMLElement) {
if (!Array.from(parent.children).find(e => e === child)) {
return parent.appendChild(child)
}
},
remove(parent: HTMLElement, child: HTMLElement) {
if (Array.from(parent.children).find(e => e === child)) {
return parent.removeChild(child)
}
},
}
function useLoading() {
const className = `loaders-css__square-spin`
const styleContent = `
@keyframes square-spin {
25% { transform: perspective(100px) rotateX(180deg) rotateY(0); }
50% { transform: perspective(100px) rotateX(180deg) rotateY(180deg); }
75% { transform: perspective(100px) rotateX(0) rotateY(180deg); }
100% { transform: perspective(100px) rotateX(0) rotateY(0); }
}
.${className} > div {
animation-fill-mode: both;
width: 50px;
height: 50px;
background: #fff;
animation: square-spin 3s 0s cubic-bezier(0.09, 0.57, 0.49, 0.9) infinite;
}
.app-loading-wrap {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: #282c34;
z-index: 9;
}
`
const oStyle = document.createElement('style')
const oDiv = document.createElement('div')
oStyle.id = 'app-loading-style'
oStyle.innerHTML = styleContent
oDiv.className = 'app-loading-wrap'
oDiv.innerHTML = `<div class="${className}"><div></div></div>`
return {
appendLoading() {
safeDOM.append(document.head, oStyle)
safeDOM.append(document.body, oDiv)
},
removeLoading() {
safeDOM.remove(document.head, oStyle)
safeDOM.remove(document.body, oDiv)
},
}
}
// ----------------------------------------------------------------------
const { appendLoading, removeLoading } = useLoading()
domReady().then(appendLoading)
window.onmessage = (ev) => {
ev.data.payload === 'removeLoading' && removeLoading()
}
setTimeout(removeLoading, 4999)

View File

@@ -1,25 +0,0 @@
#允许rpc
enable-rpc=true
#允许非外部访问
rpc-listen-all=true
#最大同时下载数(任务数), 路由建议值: 3
max-concurrent-downloads=3
#断点续传
continue=true
#同服务器连接数
max-connection-per-server=10
#最小文件分片大小, 下载线程数上限取决于能分出多少片, 对于小文件重要
min-split-size=10M
#单文件最大线程数, 路由建议值: 5
split=10
#下载速度限制
max-overall-download-limit=0
#单文件速度限制
max-download-limit=0
#上传速度限制
max-overall-upload-limit=0
#单文件速度限制
max-upload-limit=0
check-certificate=false

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 KiB

View File

@@ -1,12 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>爱享素材下载器</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

View File

@@ -1,28 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDcDt23t6ioBoHG
/Y2mOjxntWQa9dP3eNl+mAC6425DlEtyc6czNAIKuuM9wt+wAwDQAgrd5RaxdcpJ
H1JlMkEtBFkIkdn0Ag98D7nwlVA9ON3xQi5Bkl+sN/oWOE8lOwvNyNNT6ZPu3qUS
KY8SXZVY315daqz2eRWsm2otqjqbWGhh9c7FGHr3r9aAG08dyaO6OvK14GJIhNV+
UOPH5hDMMaxurDt8znaSUw93b7D++aEninGro/s2LY4G91dgM8i4t88UWobXpqs5
GMGTI0InLX2I66HkteH4RRfXXC9svA2CxN3yP294FIP7gdRQ1CGJeJcluzsjtx0V
i2G9vrT1AgMBAAECggEAF0obfQ4a82183qqHC0iui+tOpOvPeyl3G0bLDPx09wIC
2iITV//xF2GgGzE8q0wmEd2leMZ+GFn3BrYh6kPfUfxbz+RfxMtTCDZB34xt6YzT
MG1op9ft+DQUa7WZ6r7NCQJwGzllRqqZncp4MeFlpPo+6nQXyh4WhSYNnredbENE
uPZ63Kme4RZfMvtVso+XgAQM3oDih0onv1YitmNQpL9rRzlthTfybAT4737DBINq
zsmBNE6QIsXnSKpzo11OtDgof2QM9ac6eAXf73oTpDxfodwCotILytKn+8WYvlR+
T15uuknb4M3XI1FPVolkF4qtK5SLAAbVzV4DsCmuIQKBgQD6bTKKbL2huvU6dEKx
bgS079LfQUxxOTClgwkhVsMxRtvcPBnHYMAsPK4mnMhEh9x+TF6wxMx0pmhQluPI
ZULNBj/qdoiBL0RwVLA+9jgE0NeWB3XXFDsEavQBr9Q8CC0uzrsgsxFcvHpqqs2Q
RtngxRWtJP06D6mKC23s4YjDHwKBgQDg9KUCFqOmWcRXyeg9gYMC4jFFQw4lUQBd
sYpqSMHDw1b+T1W/dCPbwbxZL/+d8y930BYy9QYDtQwHdLyXCH0pHM7S6rfgr5xk
2Szd8xBUIqmeV/zcR00mTeQHJ1M50VHfclAVgZgkpWSoLwbX+bXyx/mfqLAtynZ5
yU9RfrT5awKBgQC0uJ8TlFvZXjFgyMvkfY/5/2R/ZwFCaFI573FkVNeyNP+vVNQJ
tUGZ6wSGqvg/tIgjwPtIuA0QVZLMLcgeMy1dBhiUHIxwJetO4V77YPaWSxx5kdKx
r1DT5FdI7FnOJNxufhQ/CdsKwJ3bYn3Mk8TiV3hIJnx0LR9dltfybeQjYwKBgDOY
6aApATBOtrJMJXC2HA61QwfX8Y6tnZ/f8RefyJHWZEXAfLKFORRWw5TRZZgdB247
1Furx81h4Xh0Vi1uTQb5DJdkLvjiTsTy60+dSMmDidQ/6ke8Mv3uL7dUVcqVMGpI
FgZYy0TcitHot3EiXZFqPN9aGc7m+XXFruPKZEgxAoGBAMA96jsow7CzulU+GRW8
Njg4zWuAEVErgPoNBcOXAVWLCTU/qGIEMNpZL6Ok34kf13pJDMjQ8eDuQHu5CSqf
0ul5Zy85fwfVq2IvNAyYT8eflQprTejFw22CHhfPBfADVW9ro8dK/Jw+J/31Vh7V
ILKEQKmPPzKs7kp/7Nz+2cT3
-----END PRIVATE KEY-----

View File

@@ -1,28 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDcDt23t6ioBoHG
/Y2mOjxntWQa9dP3eNl+mAC6425DlEtyc6czNAIKuuM9wt+wAwDQAgrd5RaxdcpJ
H1JlMkEtBFkIkdn0Ag98D7nwlVA9ON3xQi5Bkl+sN/oWOE8lOwvNyNNT6ZPu3qUS
KY8SXZVY315daqz2eRWsm2otqjqbWGhh9c7FGHr3r9aAG08dyaO6OvK14GJIhNV+
UOPH5hDMMaxurDt8znaSUw93b7D++aEninGro/s2LY4G91dgM8i4t88UWobXpqs5
GMGTI0InLX2I66HkteH4RRfXXC9svA2CxN3yP294FIP7gdRQ1CGJeJcluzsjtx0V
i2G9vrT1AgMBAAECggEAF0obfQ4a82183qqHC0iui+tOpOvPeyl3G0bLDPx09wIC
2iITV//xF2GgGzE8q0wmEd2leMZ+GFn3BrYh6kPfUfxbz+RfxMtTCDZB34xt6YzT
MG1op9ft+DQUa7WZ6r7NCQJwGzllRqqZncp4MeFlpPo+6nQXyh4WhSYNnredbENE
uPZ63Kme4RZfMvtVso+XgAQM3oDih0onv1YitmNQpL9rRzlthTfybAT4737DBINq
zsmBNE6QIsXnSKpzo11OtDgof2QM9ac6eAXf73oTpDxfodwCotILytKn+8WYvlR+
T15uuknb4M3XI1FPVolkF4qtK5SLAAbVzV4DsCmuIQKBgQD6bTKKbL2huvU6dEKx
bgS079LfQUxxOTClgwkhVsMxRtvcPBnHYMAsPK4mnMhEh9x+TF6wxMx0pmhQluPI
ZULNBj/qdoiBL0RwVLA+9jgE0NeWB3XXFDsEavQBr9Q8CC0uzrsgsxFcvHpqqs2Q
RtngxRWtJP06D6mKC23s4YjDHwKBgQDg9KUCFqOmWcRXyeg9gYMC4jFFQw4lUQBd
sYpqSMHDw1b+T1W/dCPbwbxZL/+d8y930BYy9QYDtQwHdLyXCH0pHM7S6rfgr5xk
2Szd8xBUIqmeV/zcR00mTeQHJ1M50VHfclAVgZgkpWSoLwbX+bXyx/mfqLAtynZ5
yU9RfrT5awKBgQC0uJ8TlFvZXjFgyMvkfY/5/2R/ZwFCaFI573FkVNeyNP+vVNQJ
tUGZ6wSGqvg/tIgjwPtIuA0QVZLMLcgeMy1dBhiUHIxwJetO4V77YPaWSxx5kdKx
r1DT5FdI7FnOJNxufhQ/CdsKwJ3bYn3Mk8TiV3hIJnx0LR9dltfybeQjYwKBgDOY
6aApATBOtrJMJXC2HA61QwfX8Y6tnZ/f8RefyJHWZEXAfLKFORRWw5TRZZgdB247
1Furx81h4Xh0Vi1uTQb5DJdkLvjiTsTy60+dSMmDidQ/6ke8Mv3uL7dUVcqVMGpI
FgZYy0TcitHot3EiXZFqPN9aGc7m+XXFruPKZEgxAoGBAMA96jsow7CzulU+GRW8
Njg4zWuAEVErgPoNBcOXAVWLCTU/qGIEMNpZL6Ok34kf13pJDMjQ8eDuQHu5CSqf
0ul5Zy85fwfVq2IvNAyYT8eflQprTejFw22CHhfPBfADVW9ro8dK/Jw+J/31Vh7V
ILKEQKmPPzKs7kp/7Nz+2cT3
-----END PRIVATE KEY-----

View File

@@ -1,23 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDwzCCAqugAwIBAgIUFAnC6268dp/z1DR9E1UepiWgWzkwDQYJKoZIhvcNAQEL
BQAwcDELMAkGA1UEBhMCQ04xEjAQBgNVBAgMCUNob25ncWluZzESMBAGA1UEBwwJ
Q2hvbmdxaW5nMQ4wDAYDVQQKDAVnb3dhczEWMBQGA1UECwwNSVQgRGVwYXJ0bWVu
dDERMA8GA1UEAwwIZ293YXMuY24wIBcNMjQwMjE4MDIwOTI2WhgPMjEyNDAxMjUw
MjA5MjZaMHAxCzAJBgNVBAYTAkNOMRIwEAYDVQQIDAlDaG9uZ3FpbmcxEjAQBgNV
BAcMCUNob25ncWluZzEOMAwGA1UECgwFZ293YXMxFjAUBgNVBAsMDUlUIERlcGFy
dG1lbnQxETAPBgNVBAMMCGdvd2FzLmNuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEA3A7dt7eoqAaBxv2Npjo8Z7VkGvXT93jZfpgAuuNuQ5RLcnOnMzQC
CrrjPcLfsAMA0AIK3eUWsXXKSR9SZTJBLQRZCJHZ9AIPfA+58JVQPTjd8UIuQZJf
rDf6FjhPJTsLzcjTU+mT7t6lEimPEl2VWN9eXWqs9nkVrJtqLao6m1hoYfXOxRh6
96/WgBtPHcmjujryteBiSITVflDjx+YQzDGsbqw7fM52klMPd2+w/vmhJ4pxq6P7
Ni2OBvdXYDPIuLfPFFqG16arORjBkyNCJy19iOuh5LXh+EUX11wvbLwNgsTd8j9v
eBSD+4HUUNQhiXiXJbs7I7cdFYthvb609QIDAQABo1MwUTAdBgNVHQ4EFgQUdI8p
aY1A47rWCRvQKSTRCCk6FoMwHwYDVR0jBBgwFoAUdI8paY1A47rWCRvQKSTRCCk6
FoMwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEArMCAfqidgXL7
cW5TAZTCqnUeKzbbqMJgk6iFsma8scMRsUXz9ZhF0UVf98376KvoJpy4vd81afbi
TehQ8wVBuKTtkHeh/MkXMWC/FU4HqSjtvxpic2+Or5dMjIrfa5VYPgzfqNaBIUh4
InD5lo8b/n5V+jdwX7RX9VYAKug6QZlCg5YSKIvgNRChb36JmrGcvsp5R0Vejnii
e3oowvgwikqm6XR6BEcRpPkztqcKST7jPFGHiXWsAqiibc+/plMW9qebhfMXEGhQ
5yVNeSxX2zqasZvP/fRy+3I5iVilxtKvJuVpPZ0UZzGS0CJ/lF67ntibktiPa3sR
D8HixYbEDg==
-----END CERTIFICATE-----

View File

@@ -1,23 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDwzCCAqugAwIBAgIUFAnC6268dp/z1DR9E1UepiWgWzkwDQYJKoZIhvcNAQEL
BQAwcDELMAkGA1UEBhMCQ04xEjAQBgNVBAgMCUNob25ncWluZzESMBAGA1UEBwwJ
Q2hvbmdxaW5nMQ4wDAYDVQQKDAVnb3dhczEWMBQGA1UECwwNSVQgRGVwYXJ0bWVu
dDERMA8GA1UEAwwIZ293YXMuY24wIBcNMjQwMjE4MDIwOTI2WhgPMjEyNDAxMjUw
MjA5MjZaMHAxCzAJBgNVBAYTAkNOMRIwEAYDVQQIDAlDaG9uZ3FpbmcxEjAQBgNV
BAcMCUNob25ncWluZzEOMAwGA1UECgwFZ293YXMxFjAUBgNVBAsMDUlUIERlcGFy
dG1lbnQxETAPBgNVBAMMCGdvd2FzLmNuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEA3A7dt7eoqAaBxv2Npjo8Z7VkGvXT93jZfpgAuuNuQ5RLcnOnMzQC
CrrjPcLfsAMA0AIK3eUWsXXKSR9SZTJBLQRZCJHZ9AIPfA+58JVQPTjd8UIuQZJf
rDf6FjhPJTsLzcjTU+mT7t6lEimPEl2VWN9eXWqs9nkVrJtqLao6m1hoYfXOxRh6
96/WgBtPHcmjujryteBiSITVflDjx+YQzDGsbqw7fM52klMPd2+w/vmhJ4pxq6P7
Ni2OBvdXYDPIuLfPFFqG16arORjBkyNCJy19iOuh5LXh+EUX11wvbLwNgsTd8j9v
eBSD+4HUUNQhiXiXJbs7I7cdFYthvb609QIDAQABo1MwUTAdBgNVHQ4EFgQUdI8p
aY1A47rWCRvQKSTRCCk6FoMwHwYDVR0jBBgwFoAUdI8paY1A47rWCRvQKSTRCCk6
FoMwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEArMCAfqidgXL7
cW5TAZTCqnUeKzbbqMJgk6iFsma8scMRsUXz9ZhF0UVf98376KvoJpy4vd81afbi
TehQ8wVBuKTtkHeh/MkXMWC/FU4HqSjtvxpic2+Or5dMjIrfa5VYPgzfqNaBIUh4
InD5lo8b/n5V+jdwX7RX9VYAKug6QZlCg5YSKIvgNRChb36JmrGcvsp5R0Vejnii
e3oowvgwikqm6XR6BEcRpPkztqcKST7jPFGHiXWsAqiibc+/plMW9qebhfMXEGhQ
5yVNeSxX2zqasZvP/fRy+3I5iVilxtKvJuVpPZ0UZzGS0CJ/lF67ntibktiPa3sR
D8HixYbEDg==
-----END CERTIFICATE-----

View File

@@ -1,25 +0,0 @@
#允许rpc
enable-rpc=true
#允许非外部访问
rpc-listen-all=true
#最大同时下载数(任务数), 路由建议值: 3
max-concurrent-downloads=3
#断点续传
continue=true
#同服务器连接数
max-connection-per-server=10
#最小文件分片大小, 下载线程数上限取决于能分出多少片, 对于小文件重要
min-split-size=10M
#单文件最大线程数, 路由建议值: 5
split=10
#下载速度限制
max-overall-download-limit=0
#单文件速度限制
max-download-limit=0
#上传速度限制
max-overall-upload-limit=0
#单文件速度限制
max-upload-limit=0
check-certificate=false

Binary file not shown.

Binary file not shown.

View File

@@ -1,25 +0,0 @@
#允许rpc
enable-rpc=true
#允许非外部访问
rpc-listen-all=true
#最大同时下载数(任务数), 路由建议值: 3
max-concurrent-downloads=3
#断点续传
continue=true
#同服务器连接数
max-connection-per-server=10
#最小文件分片大小, 下载线程数上限取决于能分出多少片, 对于小文件重要
min-split-size=10M
#单文件最大线程数, 路由建议值: 5
split=10
#下载速度限制
max-overall-download-limit=0
#单文件速度限制
max-download-limit=0
#上传速度限制
max-overall-upload-limit=0
#单文件速度限制
max-upload-limit=0
check-certificate=false

Binary file not shown.

View File

@@ -1,239 +0,0 @@
#!/usr/bin/env perl
# Copyright 2000-2021 The OpenSSL Project Authors. All Rights Reserved.
#
# Licensed under the Apache License 2.0 (the "License"). You may not use
# this file except in compliance with the License. You can obtain a copy
# in the file LICENSE in the source distribution or at
# https://www.openssl.org/source/license.html
#
# Wrapper around the ca to make it easier to use
#
# WARNING: do not edit!
# Generated by makefile from apps\CA.pl.in
use strict;
use warnings;
my $verbose = 1;
my @OPENSSL_CMDS = ("req", "ca", "pkcs12", "x509", "verify");
my $openssl = $ENV{'OPENSSL'} // "openssl";
$ENV{'OPENSSL'} = $openssl;
my $OPENSSL_CONFIG = $ENV{"OPENSSL_CONFIG"} // "";
# Command invocations.
my $REQ = "$openssl req $OPENSSL_CONFIG";
my $CA = "$openssl ca $OPENSSL_CONFIG";
my $VERIFY = "$openssl verify";
my $X509 = "$openssl x509";
my $PKCS12 = "$openssl pkcs12";
# Default values for various configuration settings.
my $CATOP = "./demoCA";
my $CAKEY = "cakey.pem";
my $CAREQ = "careq.pem";
my $CACERT = "cacert.pem";
my $CACRL = "crl.pem";
my $DAYS = "-days 365";
my $CADAYS = "-days 1095"; # 3 years
my $EXTENSIONS = "-extensions v3_ca";
my $POLICY = "-policy policy_anything";
my $NEWKEY = "newkey.pem";
my $NEWREQ = "newreq.pem";
my $NEWCERT = "newcert.pem";
my $NEWP12 = "newcert.p12";
# Commandline parsing
my %EXTRA;
my $WHAT = shift @ARGV || "";
@ARGV = parse_extra(@ARGV);
my $RET = 0;
# Split out "-extra-CMD value", and return new |@ARGV|. Fill in
# |EXTRA{CMD}| with list of values.
sub parse_extra
{
foreach ( @OPENSSL_CMDS ) {
$EXTRA{$_} = '';
}
my @result;
while ( scalar(@_) > 0 ) {
my $arg = shift;
if ( $arg !~ m/-extra-([a-z0-9]+)/ ) {
push @result, $arg;
next;
}
$arg =~ s/-extra-//;
die("Unknown \"-${arg}-extra\" option, exiting")
unless scalar grep { $arg eq $_ } @OPENSSL_CMDS;
$EXTRA{$arg} .= " " . shift;
}
return @result;
}
# See if reason for a CRL entry is valid; exit if not.
sub crl_reason_ok
{
my $r = shift;
if ($r eq 'unspecified' || $r eq 'keyCompromise'
|| $r eq 'CACompromise' || $r eq 'affiliationChanged'
|| $r eq 'superseded' || $r eq 'cessationOfOperation'
|| $r eq 'certificateHold' || $r eq 'removeFromCRL') {
return 1;
}
print STDERR "Invalid CRL reason; must be one of:\n";
print STDERR " unspecified, keyCompromise, CACompromise,\n";
print STDERR " affiliationChanged, superseded, cessationOfOperation\n";
print STDERR " certificateHold, removeFromCRL";
exit 1;
}
# Copy a PEM-format file; return like exit status (zero means ok)
sub copy_pemfile
{
my ($infile, $outfile, $bound) = @_;
my $found = 0;
open IN, $infile || die "Cannot open $infile, $!";
open OUT, ">$outfile" || die "Cannot write to $outfile, $!";
while (<IN>) {
$found = 1 if /^-----BEGIN.*$bound/;
print OUT $_ if $found;
$found = 2, last if /^-----END.*$bound/;
}
close IN;
close OUT;
return $found == 2 ? 0 : 1;
}
# Wrapper around system; useful for debugging. Returns just the exit status
sub run
{
my $cmd = shift;
print "====\n$cmd\n" if $verbose;
my $status = system($cmd);
print "==> $status\n====\n" if $verbose;
return $status >> 8;
}
if ( $WHAT =~ /^(-\?|-h|-help)$/ ) {
print STDERR <<EOF;
Usage:
CA.pl -newcert | -newreq | -newreq-nodes | -xsign | -sign | -signCA | -signcert | -crl | -newca [-extra-cmd parameter]
CA.pl -pkcs12 [certname]
CA.pl -verify certfile ...
CA.pl -revoke certfile [reason]
EOF
exit 0;
}
if ($WHAT eq '-newcert' ) {
# create a certificate
$RET = run("$REQ -new -x509 -keyout $NEWKEY -out $NEWCERT $DAYS"
. " $EXTRA{req}");
print "Cert is in $NEWCERT, private key is in $NEWKEY\n" if $RET == 0;
} elsif ($WHAT eq '-precert' ) {
# create a pre-certificate
$RET = run("$REQ -x509 -precert -keyout $NEWKEY -out $NEWCERT $DAYS"
. " $EXTRA{req}");
print "Pre-cert is in $NEWCERT, private key is in $NEWKEY\n" if $RET == 0;
} elsif ($WHAT =~ /^\-newreq(\-nodes)?$/ ) {
# create a certificate request
$RET = run("$REQ -new $1 -keyout $NEWKEY -out $NEWREQ $DAYS $EXTRA{req}");
print "Request is in $NEWREQ, private key is in $NEWKEY\n" if $RET == 0;
} elsif ($WHAT eq '-newca' ) {
# create the directory hierarchy
my @dirs = ( "${CATOP}", "${CATOP}/certs", "${CATOP}/crl",
"${CATOP}/newcerts", "${CATOP}/private" );
die "${CATOP}/index.txt exists.\nRemove old sub-tree to proceed,"
if -f "${CATOP}/index.txt";
die "${CATOP}/serial exists.\nRemove old sub-tree to proceed,"
if -f "${CATOP}/serial";
foreach my $d ( @dirs ) {
if ( -d $d ) {
warn "Directory $d exists" if -d $d;
} else {
mkdir $d or die "Can't mkdir $d, $!";
}
}
open OUT, ">${CATOP}/index.txt";
close OUT;
open OUT, ">${CATOP}/crlnumber";
print OUT "01\n";
close OUT;
# ask user for existing CA certificate
print "CA certificate filename (or enter to create)\n";
my $FILE;
$FILE = "" unless defined($FILE = <STDIN>);
$FILE =~ s{\R$}{};
if ($FILE ne "") {
copy_pemfile($FILE,"${CATOP}/private/$CAKEY", "PRIVATE");
copy_pemfile($FILE,"${CATOP}/$CACERT", "CERTIFICATE");
} else {
print "Making CA certificate ...\n";
$RET = run("$REQ -new -keyout ${CATOP}/private/$CAKEY"
. " -out ${CATOP}/$CAREQ $EXTRA{req}");
$RET = run("$CA -create_serial"
. " -out ${CATOP}/$CACERT $CADAYS -batch"
. " -keyfile ${CATOP}/private/$CAKEY -selfsign"
. " $EXTENSIONS"
. " -infiles ${CATOP}/$CAREQ $EXTRA{ca}") if $RET == 0;
print "CA certificate is in ${CATOP}/$CACERT\n" if $RET == 0;
}
} elsif ($WHAT eq '-pkcs12' ) {
my $cname = $ARGV[0];
$cname = "My Certificate" unless defined $cname;
$RET = run("$PKCS12 -in $NEWCERT -inkey $NEWKEY"
. " -certfile ${CATOP}/$CACERT -out $NEWP12"
. " -export -name \"$cname\" $EXTRA{pkcs12}");
print "PKCS #12 file is in $NEWP12\n" if $RET == 0;
} elsif ($WHAT eq '-xsign' ) {
$RET = run("$CA $POLICY -infiles $NEWREQ $EXTRA{ca}");
} elsif ($WHAT eq '-sign' ) {
$RET = run("$CA $POLICY -out $NEWCERT"
. " -infiles $NEWREQ $EXTRA{ca}");
print "Signed certificate is in $NEWCERT\n" if $RET == 0;
} elsif ($WHAT eq '-signCA' ) {
$RET = run("$CA $POLICY -out $NEWCERT"
. " $EXTENSIONS -infiles $NEWREQ $EXTRA{ca}");
print "Signed CA certificate is in $NEWCERT\n" if $RET == 0;
} elsif ($WHAT eq '-signcert' ) {
$RET = run("$X509 -x509toreq -in $NEWREQ -signkey $NEWREQ"
. " -out tmp.pem $EXTRA{x509}");
$RET = run("$CA $POLICY -out $NEWCERT"
. "-infiles tmp.pem $EXTRA{ca}") if $RET == 0;
print "Signed certificate is in $NEWCERT\n" if $RET == 0;
} elsif ($WHAT eq '-verify' ) {
my @files = @ARGV ? @ARGV : ( $NEWCERT );
foreach my $file (@files) {
# -CAfile quoted for VMS, since the C RTL downcases all unquoted
# arguments to C programs
my $status = run("$VERIFY \"-CAfile\" ${CATOP}/$CACERT $file $EXTRA{verify}");
$RET = $status if $status != 0;
}
} elsif ($WHAT eq '-crl' ) {
$RET = run("$CA -gencrl -out ${CATOP}/crl/$CACRL $EXTRA{ca}");
print "Generated CRL is in ${CATOP}/crl/$CACRL\n" if $RET == 0;
} elsif ($WHAT eq '-revoke' ) {
my $cname = $ARGV[0];
if (!defined $cname) {
print "Certificate filename is required; reason optional.\n";
exit 1;
}
my $reason = $ARGV[1];
$reason = " -crl_reason $reason"
if defined $reason && crl_reason_ok($reason);
$RET = run("$CA -revoke \"$cname\"" . $reason . $EXTRA{ca});
} else {
print STDERR "Unknown arg \"$WHAT\"\n";
print STDERR "Use -help for help.\n";
exit 1;
}
exit $RET;

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,350 +0,0 @@
#
# OpenSSL example configuration file.
# This is mostly being used for generation of certificate requests.
#
# Note that you can include other files from the main configuration
# file using the .include directive.
#.include filename
# This definition stops the following lines choking if HOME isn't
# defined.
HOME = .
# Extra OBJECT IDENTIFIER info:
#oid_file = $ENV::HOME/.oid
oid_section = new_oids
# To use this configuration file with the "-extfile" option of the
# "openssl x509" utility, name here the section containing the
# X.509v3 extensions to use:
# extensions =
# (Alternatively, use a configuration file that has only
# X.509v3 extensions in its main [= default] section.)
[ new_oids ]
# We can add new OIDs in here for use by 'ca', 'req' and 'ts'.
# Add a simple OID like this:
# testoid1=1.2.3.4
# Or use config file substitution like this:
# testoid2=${testoid1}.5.6
# Policies used by the TSA examples.
tsa_policy1 = 1.2.3.4.1
tsa_policy2 = 1.2.3.4.5.6
tsa_policy3 = 1.2.3.4.5.7
####################################################################
[ ca ]
default_ca = CA_default # The default ca section
####################################################################
[ CA_default ]
dir = ./demoCA # Where everything is kept
certs = $dir/certs # Where the issued certs are kept
crl_dir = $dir/crl # Where the issued crl are kept
database = $dir/index.txt # database index file.
#unique_subject = no # Set to 'no' to allow creation of
# several certs with same subject.
new_certs_dir = $dir/newcerts # default place for new certs.
certificate = $dir/cacert.pem # The CA certificate
serial = $dir/serial # The current serial number
crlnumber = $dir/crlnumber # the current crl number
# must be commented out to leave a V1 CRL
crl = $dir/crl.pem # The current CRL
private_key = $dir/private/cakey.pem# The private key
x509_extensions = usr_cert # The extensions to add to the cert
# Comment out the following two lines for the "traditional"
# (and highly broken) format.
name_opt = ca_default # Subject Name options
cert_opt = ca_default # Certificate field options
# Extension copying option: use with caution.
# copy_extensions = copy
# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs
# so this is commented out by default to leave a V1 CRL.
# crlnumber must also be commented out to leave a V1 CRL.
# crl_extensions = crl_ext
default_days = 365 # how long to certify for
default_crl_days= 30 # how long before next CRL
default_md = default # use public key default MD
preserve = no # keep passed DN ordering
# A few difference way of specifying how similar the request should look
# For type CA, the listed attributes must be the same, and the optional
# and supplied fields are just that :-)
policy = policy_match
# For the CA policy
[ policy_match ]
countryName = match
stateOrProvinceName = match
organizationName = match
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
# For the 'anything' policy
# At this point in time, you must list all acceptable 'object'
# types.
[ policy_anything ]
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
####################################################################
[ req ]
default_bits = 2048
default_keyfile = privkey.pem
distinguished_name = req_distinguished_name
attributes = req_attributes
x509_extensions = v3_ca # The extensions to add to the self signed cert
# Passwords for private keys if not present they will be prompted for
# input_password = secret
# output_password = secret
# This sets a mask for permitted string types. There are several options.
# default: PrintableString, T61String, BMPString.
# pkix : PrintableString, BMPString (PKIX recommendation before 2004)
# utf8only: only UTF8Strings (PKIX recommendation after 2004).
# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings).
# MASK:XXXX a literal mask value.
# WARNING: ancient versions of Netscape crash on BMPStrings or UTF8Strings.
string_mask = utf8only
# req_extensions = v3_req # The extensions to add to a certificate request
[ req_distinguished_name ]
countryName = Country Name (2 letter code)
countryName_default = AU
countryName_min = 2
countryName_max = 2
stateOrProvinceName = State or Province Name (full name)
stateOrProvinceName_default = Some-State
localityName = Locality Name (eg, city)
0.organizationName = Organization Name (eg, company)
0.organizationName_default = Internet Widgits Pty Ltd
# we can do this but it is not needed normally :-)
#1.organizationName = Second Organization Name (eg, company)
#1.organizationName_default = World Wide Web Pty Ltd
organizationalUnitName = Organizational Unit Name (eg, section)
#organizationalUnitName_default =
commonName = Common Name (e.g. server FQDN or YOUR name)
commonName_max = 64
emailAddress = Email Address
emailAddress_max = 64
# SET-ex3 = SET extension number 3
[ req_attributes ]
challengePassword = A challenge password
challengePassword_min = 4
challengePassword_max = 20
unstructuredName = An optional company name
[ usr_cert ]
# These extensions are added when 'ca' signs a request.
# This goes against PKIX guidelines but some CAs do it and some software
# requires this to avoid interpreting an end user certificate as a CA.
basicConstraints=CA:FALSE
# Here are some examples of the usage of nsCertType. If it is omitted
# the certificate can be used for anything *except* object signing.
# This is OK for an SSL server.
# nsCertType = server
# For an object signing certificate this would be used.
# nsCertType = objsign
# For normal client use this is typical
# nsCertType = client, email
# and for everything including object signing:
# nsCertType = client, email, objsign
# This is typical in keyUsage for a client certificate.
# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
# This will be displayed in Netscape's comment listbox.
nsComment = "OpenSSL Generated Certificate"
# PKIX recommendations harmless if included in all certificates.
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
# This stuff is for subjectAltName and issuerAltname.
# Import the email address.
# subjectAltName=email:copy
# An alternative to produce certificates that aren't
# deprecated according to PKIX.
# subjectAltName=email:move
# Copy subject details
# issuerAltName=issuer:copy
#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem
#nsBaseUrl
#nsRevocationUrl
#nsRenewalUrl
#nsCaPolicyUrl
#nsSslServerName
# This is required for TSA certificates.
# extendedKeyUsage = critical,timeStamping
[ v3_req ]
# Extensions to add to a certificate request
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
[ v3_ca ]
# Extensions for a typical CA
# PKIX recommendation.
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer
basicConstraints = critical,CA:true
# Key usage: this is typical for a CA certificate. However since it will
# prevent it being used as an test self-signed certificate it is best
# left out by default.
# keyUsage = cRLSign, keyCertSign
# Some might want this also
# nsCertType = sslCA, emailCA
# Include email address in subject alt name: another PKIX recommendation
# subjectAltName=email:copy
# Copy issuer details
# issuerAltName=issuer:copy
# DER hex encoding of an extension: beware experts only!
# obj=DER:02:03
# Where 'obj' is a standard or added object
# You can even override a supported extension:
# basicConstraints= critical, DER:30:03:01:01:FF
[ crl_ext ]
# CRL extensions.
# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL.
# issuerAltName=issuer:copy
authorityKeyIdentifier=keyid:always
[ proxy_cert_ext ]
# These extensions should be added when creating a proxy certificate
# This goes against PKIX guidelines but some CAs do it and some software
# requires this to avoid interpreting an end user certificate as a CA.
basicConstraints=CA:FALSE
# Here are some examples of the usage of nsCertType. If it is omitted
# the certificate can be used for anything *except* object signing.
# This is OK for an SSL server.
# nsCertType = server
# For an object signing certificate this would be used.
# nsCertType = objsign
# For normal client use this is typical
# nsCertType = client, email
# and for everything including object signing:
# nsCertType = client, email, objsign
# This is typical in keyUsage for a client certificate.
# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
# This will be displayed in Netscape's comment listbox.
nsComment = "OpenSSL Generated Certificate"
# PKIX recommendations harmless if included in all certificates.
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
# This stuff is for subjectAltName and issuerAltname.
# Import the email address.
# subjectAltName=email:copy
# An alternative to produce certificates that aren't
# deprecated according to PKIX.
# subjectAltName=email:move
# Copy subject details
# issuerAltName=issuer:copy
#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem
#nsBaseUrl
#nsRevocationUrl
#nsRenewalUrl
#nsCaPolicyUrl
#nsSslServerName
# This really needs to be in place for it to be a proxy certificate.
proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo
####################################################################
[ tsa ]
default_tsa = tsa_config1 # the default TSA section
[ tsa_config1 ]
# These are used by the TSA reply generation only.
dir = ./demoCA # TSA root directory
serial = $dir/tsaserial # The current serial number (mandatory)
crypto_device = builtin # OpenSSL engine to use for signing
signer_cert = $dir/tsacert.pem # The TSA signing certificate
# (optional)
certs = $dir/cacert.pem # Certificate chain to include in reply
# (optional)
signer_key = $dir/private/tsakey.pem # The TSA private key (optional)
signer_digest = sha256 # Signing digest to use. (Optional)
default_policy = tsa_policy1 # Policy if request did not specify it
# (optional)
other_policies = tsa_policy2, tsa_policy3 # acceptable policies (optional)
digests = sha1, sha256, sha384, sha512 # Acceptable message digests (mandatory)
accuracy = secs:1, millisecs:500, microsecs:100 # (optional)
clock_precision_digits = 0 # number of digits after dot. (optional)
ordering = yes # Is ordering defined for timestamps?
# (optional, default: no)
tsa_name = yes # Must the TSA name be included in the reply?
# (optional, default: no)
ess_cert_id_chain = no # Must the ESS cert id chain be included?
# (optional, default: no)
ess_cert_id_alg = sha1 # algorithm to compute certificate
# identifier (optional, default: sha1)

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,218 +0,0 @@
#! /usr/bin/env perl
# Copyright 1995-2023 The OpenSSL Project Authors. All Rights Reserved.
#
# Licensed under the Apache License 2.0 (the "License"). You may not use
# this file except in compliance with the License. You can obtain a copy
# in the file LICENSE in the source distribution or at
# https://www.openssl.org/source/license.html
# Generate progs.h file by looking for command mains in list of C files
# passed on the command line.
use strict;
use warnings;
use lib '.';
use configdata qw/@disablables %unified_info/;
my $opt = shift @ARGV;
die "Unrecognised option, must be -C or -H\n"
unless ($opt eq '-H' || $opt eq '-C');
my %commands = ();
my $cmdre = qr/^\s*int\s+([a-z_][a-z0-9_]*)_main\(\s*int\s+argc\s*,/;
my $apps_openssl = shift @ARGV;
my $YEAR = [gmtime($ENV{SOURCE_DATE_EPOCH} || time())]->[5] + 1900;
# because the program apps/openssl has object files as sources, and
# they then have the corresponding C files as source, we need to chain
# the lookups in %unified_info
my @openssl_source =
map { @{$unified_info{sources}->{$_}} }
grep { /\.o$/
&& !$unified_info{attributes}->{sources}->{$apps_openssl}->{$_}->{nocheck} }
@{$unified_info{sources}->{$apps_openssl}};
foreach my $filename (@openssl_source) {
open F, $filename or die "Couldn't open $filename: $!\n";
foreach ( grep /$cmdre/, <F> ) {
my @foo = /$cmdre/;
$commands{$1} = 1;
}
close F;
}
@ARGV = sort keys %commands;
if ($opt eq '-H') {
print <<"EOF";
/*
* WARNING: do not edit!
* Generated by apps/progs.pl
*
* Copyright 1995-$YEAR The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
#include "function.h"
EOF
foreach (@ARGV) {
printf "extern int %s_main(int argc, char *argv[]);\n", $_;
}
print "\n";
foreach (@ARGV) {
printf "extern const OPTIONS %s_options[];\n", $_;
}
print "\n";
print "extern FUNCTION functions[];\n";
}
if ($opt eq '-C') {
print <<"EOF";
/*
* WARNING: do not edit!
* Generated by apps/progs.pl
*
* Copyright 1995-$YEAR The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
#include "progs.h"
EOF
my %cmd_disabler = (
ciphers => "sock",
genrsa => "rsa",
gendsa => "dsa",
dsaparam => "dsa",
gendh => "dh",
dhparam => "dh",
ecparam => "ec",
);
my %cmd_deprecated = (
# The format of this table is:
# [0] = alternative command to use instead
# [1] = deprecented in this version
# [2] = preprocessor conditional for excluding irrespective of deprecation
# rsa => [ "pkey", "3_0", "rsa" ],
# genrsa => [ "genpkey", "3_0", "rsa" ],
rsautl => [ "pkeyutl", "3_0", "rsa" ],
# dhparam => [ "pkeyparam", "3_0", "dh" ],
# dsaparam => [ "pkeyparam", "3_0", "dsa" ],
# dsa => [ "pkey", "3_0", "dsa" ],
# gendsa => [ "genpkey", "3_0", "dsa" ],
# ec => [ "pkey", "3_0", "ec" ],
# ecparam => [ "pkeyparam", "3_0", "ec" ],
);
print "FUNCTION functions[] = {\n";
foreach my $cmd ( @ARGV ) {
my $str =
" {FT_general, \"$cmd\", ${cmd}_main, ${cmd}_options, NULL, NULL},\n";
if ($cmd =~ /^s_/) {
print "#ifndef OPENSSL_NO_SOCK\n${str}#endif\n";
} elsif (my $deprecated = $cmd_deprecated{$cmd}) {
my @dep = @{$deprecated};
my $daltprg = $dep[0];
my $dver = $dep[1];
my $dsys = $dep[2];
print "#if !defined(OPENSSL_NO_DEPRECATED_" . $dver . ")";
if ($dsys) {
print " && !defined(OPENSSL_NO_" . uc($dsys) . ")";
}
$dver =~ s/_/./g;
my $dalt = "\"" . $daltprg . "\", \"" . $dver . "\"";
$str =~ s/NULL, NULL/$dalt/;
print "\n${str}#endif\n";
} elsif (grep { $cmd eq $_ } @disablables) {
print "#ifndef OPENSSL_NO_" . uc($cmd) . "\n${str}#endif\n";
} elsif (my $disabler = $cmd_disabler{$cmd}) {
print "#ifndef OPENSSL_NO_" . uc($disabler) . "\n${str}#endif\n";
} else {
print $str;
}
}
my %md_disabler = (
blake2b512 => "blake2",
blake2s256 => "blake2",
);
foreach my $cmd (
"md2", "md4", "md5",
"sha1", "sha224", "sha256", "sha384",
"sha512", "sha512-224", "sha512-256",
"sha3-224", "sha3-256", "sha3-384", "sha3-512",
"shake128", "shake256",
"mdc2", "rmd160", "blake2b512", "blake2s256",
"sm3"
) {
my $str = " {FT_md, \"$cmd\", dgst_main, NULL, NULL},\n";
if (grep { $cmd eq $_ } @disablables) {
print "#ifndef OPENSSL_NO_" . uc($cmd) . "\n${str}#endif\n";
} elsif (my $disabler = $md_disabler{$cmd}) {
print "#ifndef OPENSSL_NO_" . uc($disabler) . "\n${str}#endif\n";
} else {
print $str;
}
}
my %cipher_disabler = (
des3 => "des",
desx => "des",
cast5 => "cast",
);
foreach my $cmd (
"aes-128-cbc", "aes-128-ecb",
"aes-192-cbc", "aes-192-ecb",
"aes-256-cbc", "aes-256-ecb",
"aria-128-cbc", "aria-128-cfb",
"aria-128-ctr", "aria-128-ecb", "aria-128-ofb",
"aria-128-cfb1", "aria-128-cfb8",
"aria-192-cbc", "aria-192-cfb",
"aria-192-ctr", "aria-192-ecb", "aria-192-ofb",
"aria-192-cfb1", "aria-192-cfb8",
"aria-256-cbc", "aria-256-cfb",
"aria-256-ctr", "aria-256-ecb", "aria-256-ofb",
"aria-256-cfb1", "aria-256-cfb8",
"camellia-128-cbc", "camellia-128-ecb",
"camellia-192-cbc", "camellia-192-ecb",
"camellia-256-cbc", "camellia-256-ecb",
"base64", "zlib", "brotli", "zstd",
"des", "des3", "desx", "idea", "seed", "rc4", "rc4-40",
"rc2", "bf", "cast", "rc5",
"des-ecb", "des-ede", "des-ede3",
"des-cbc", "des-ede-cbc","des-ede3-cbc",
"des-cfb", "des-ede-cfb","des-ede3-cfb",
"des-ofb", "des-ede-ofb","des-ede3-ofb",
"idea-cbc","idea-ecb", "idea-cfb", "idea-ofb",
"seed-cbc","seed-ecb", "seed-cfb", "seed-ofb",
"rc2-cbc", "rc2-ecb", "rc2-cfb","rc2-ofb", "rc2-64-cbc", "rc2-40-cbc",
"bf-cbc", "bf-ecb", "bf-cfb", "bf-ofb",
"cast5-cbc","cast5-ecb", "cast5-cfb","cast5-ofb",
"cast-cbc", "rc5-cbc", "rc5-ecb", "rc5-cfb", "rc5-ofb",
"sm4-cbc", "sm4-ecb", "sm4-cfb", "sm4-ofb", "sm4-ctr"
) {
my $str = " {FT_cipher, \"$cmd\", enc_main, enc_options, NULL},\n";
(my $algo = $cmd) =~ s/-.*//g;
if (grep { $algo eq $_ } @disablables) {
print "#ifndef OPENSSL_NO_" . uc($algo) . "\n${str}#endif\n";
} elsif (my $disabler = $cipher_disabler{$algo}) {
print "#ifndef OPENSSL_NO_" . uc($disabler) . "\n${str}#endif\n";
} else {
print $str;
}
}
print " {0, NULL, NULL, NULL, NULL}\n};\n";
}

View File

@@ -1,200 +0,0 @@
#!/usr/bin/env perl
# Copyright 2002-2018 The OpenSSL Project Authors. All Rights Reserved.
# Copyright (c) 2002 The OpenTSA Project. All rights reserved.
#
# Licensed under the Apache License 2.0 (the "License"). You may not use
# this file except in compliance with the License. You can obtain a copy
# in the file LICENSE in the source distribution or at
# https://www.openssl.org/source/license.html
use strict;
use IO::Handle;
use Getopt::Std;
use File::Basename;
use WWW::Curl::Easy;
use vars qw(%options);
# Callback for reading the body.
sub read_body {
my ($maxlength, $state) = @_;
my $return_data = "";
my $data_len = length ${$state->{data}};
if ($state->{bytes} < $data_len) {
$data_len = $data_len - $state->{bytes};
$data_len = $maxlength if $data_len > $maxlength;
$return_data = substr ${$state->{data}}, $state->{bytes}, $data_len;
$state->{bytes} += $data_len;
}
return $return_data;
}
# Callback for writing the body into a variable.
sub write_body {
my ($data, $pointer) = @_;
${$pointer} .= $data;
return length($data);
}
# Initialise a new Curl object.
sub create_curl {
my $url = shift;
# Create Curl object.
my $curl = WWW::Curl::Easy::new();
# Error-handling related options.
$curl->setopt(CURLOPT_VERBOSE, 1) if $options{d};
$curl->setopt(CURLOPT_FAILONERROR, 1);
$curl->setopt(CURLOPT_USERAGENT,
"OpenTSA tsget.pl/openssl-3.3.1");
# Options for POST method.
$curl->setopt(CURLOPT_UPLOAD, 1);
$curl->setopt(CURLOPT_CUSTOMREQUEST, "POST");
$curl->setopt(CURLOPT_HTTPHEADER,
["Content-Type: application/timestamp-query",
"Accept: application/timestamp-reply,application/timestamp-response"]);
$curl->setopt(CURLOPT_READFUNCTION, \&read_body);
$curl->setopt(CURLOPT_HEADERFUNCTION, sub { return length($_[0]); });
# Options for getting the result.
$curl->setopt(CURLOPT_WRITEFUNCTION, \&write_body);
# SSL related options.
$curl->setopt(CURLOPT_SSLKEYTYPE, "PEM");
$curl->setopt(CURLOPT_SSL_VERIFYPEER, 1); # Verify server's certificate.
$curl->setopt(CURLOPT_SSL_VERIFYHOST, 2); # Check server's CN.
$curl->setopt(CURLOPT_SSLKEY, $options{k}) if defined($options{k});
$curl->setopt(CURLOPT_SSLKEYPASSWD, $options{p}) if defined($options{p});
$curl->setopt(CURLOPT_SSLCERT, $options{c}) if defined($options{c});
$curl->setopt(CURLOPT_CAINFO, $options{C}) if defined($options{C});
$curl->setopt(CURLOPT_CAPATH, $options{P}) if defined($options{P});
$curl->setopt(CURLOPT_RANDOM_FILE, $options{r}) if defined($options{r});
$curl->setopt(CURLOPT_EGDSOCKET, $options{g}) if defined($options{g});
# Setting destination.
$curl->setopt(CURLOPT_URL, $url);
return $curl;
}
# Send a request and returns the body back.
sub get_timestamp {
my $curl = shift;
my $body = shift;
my $ts_body;
local $::error_buf;
# Error-handling related options.
$curl->setopt(CURLOPT_ERRORBUFFER, "::error_buf");
# Options for POST method.
$curl->setopt(CURLOPT_INFILE, {data => $body, bytes => 0});
$curl->setopt(CURLOPT_INFILESIZE, length(${$body}));
# Options for getting the result.
$curl->setopt(CURLOPT_FILE, \$ts_body);
# Send the request...
my $error_code = $curl->perform();
my $error_string;
if ($error_code != 0) {
my $http_code = $curl->getinfo(CURLINFO_HTTP_CODE);
$error_string = "could not get timestamp";
$error_string .= ", http code: $http_code" unless $http_code == 0;
$error_string .= ", curl code: $error_code";
$error_string .= " ($::error_buf)" if defined($::error_buf);
} else {
my $ct = $curl->getinfo(CURLINFO_CONTENT_TYPE);
if (lc($ct) ne "application/timestamp-reply"
&& lc($ct) ne "application/timestamp-response") {
$error_string = "unexpected content type returned: $ct";
}
}
return ($ts_body, $error_string);
}
# Print usage information and exists.
sub usage {
print STDERR "usage: $0 -h <server_url> [-e <extension>] [-o <output>] ";
print STDERR "[-v] [-d] [-k <private_key.pem>] [-p <key_password>] ";
print STDERR "[-c <client_cert.pem>] [-C <CA_certs.pem>] [-P <CA_path>] ";
print STDERR "[-r <file:file...>] [-g <EGD_socket>] [<request>]...\n";
exit 1;
}
# ----------------------------------------------------------------------
# Main program
# ----------------------------------------------------------------------
# Getting command-line options (default comes from TSGET environment variable).
my $getopt_arg = "h:e:o:vdk:p:c:C:P:r:g:";
if (exists $ENV{TSGET}) {
my @old_argv = @ARGV;
@ARGV = split /\s+/, $ENV{TSGET};
getopts($getopt_arg, \%options) or usage;
@ARGV = @old_argv;
}
getopts($getopt_arg, \%options) or usage;
# Checking argument consistency.
if (!exists($options{h}) || (@ARGV == 0 && !exists($options{o}))
|| (@ARGV > 1 && exists($options{o}))) {
print STDERR "Inconsistent command line options.\n";
usage;
}
# Setting defaults.
@ARGV = ("-") unless @ARGV != 0;
$options{e} = ".tsr" unless defined($options{e});
# Processing requests.
my $curl = create_curl $options{h};
undef $/; # For reading whole files.
REQUEST: foreach (@ARGV) {
my $input = $_;
my ($base, $path) = fileparse($input, '\.[^.]*');
my $output_base = $base . $options{e};
my $output = defined($options{o}) ? $options{o} : $path . $output_base;
STDERR->printflush("$input: ") if $options{v};
# Read request.
my $body;
if ($input eq "-") {
# Read the request from STDIN;
$body = <STDIN>;
} else {
# Read the request from file.
open INPUT, "<" . $input
or warn("$input: could not open input file: $!\n"), next REQUEST;
$body = <INPUT>;
close INPUT
or warn("$input: could not close input file: $!\n"), next REQUEST;
}
# Send request.
STDERR->printflush("sending request") if $options{v};
my ($ts_body, $error) = get_timestamp $curl, \$body;
if (defined($error)) {
die "$input: fatal error: $error\n";
}
STDERR->printflush(", reply received") if $options{v};
# Write response.
if ($output eq "-") {
# Write to STDOUT.
print $ts_body;
} else {
# Write to file.
open OUTPUT, ">", $output
or warn("$output: could not open output file: $!\n"), next REQUEST;
print OUTPUT $ts_body;
close OUTPUT
or warn("$output: could not close output file: $!\n"), next REQUEST;
}
STDERR->printflush(", $output written.\n") if $options{v};
}
$curl->cleanup();

View File

@@ -1,26 +0,0 @@
<job id="test">
<script language="VBScript" src="util.vbs" />
<script language="VBScript" src="regUtil.vbs" />
<script language="VBScript">
CheckZeroArgs("type A as first arg!!")
DetermineOSArchitecture()
LoadRegistryImplementationByOSArchitecture()
'On Error Resume Next
Dim key: key = "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders"
Dim value: value = "AppData"
ParseHiveAndSubKey key, constHive, strSubKey
GetExpandedStringValue constHive, strSubKey, value, exResult
WriteLine "GetExpandedStringValue: " & exResult
GetStringValue constHive, strSubKey, value, nResult
WriteLine "GetStringValue: " & nResult
set ws = createobject("Wscript.shell")
WriteLine "regread: " & ws.regread(key & "\AppData")
</script>
</job>

View File

@@ -1,75 +0,0 @@
' Notes: wanted to implement this using a class but:
' 1. No matter what I did I could not assign the result of GetObject to a private member
' 2. It looks as if all methods were treated as subs from the outside world which is not good since
' some of these need to return a value
'
Set private_oReg = GetObject("winmgmts:\root\default:StdRegProv")
Function SetStringValue(constHive, strSubKey, strValueName, strValue)
SetStringValue = private_oReg.SetStringValue(constHive, strSubKey, strValueName, strValue)
End Function
Sub GetStringValue(constHive, strKey, strValueName, strValue)
private_oReg.GetStringValue constHive, strKey, strValueName, strValue
End Sub
Function SetExpandedStringValue(constHive, strSubKey, strValueName, strValue)
SetExpandedStringValue = private_oReg.SetExpandedStringValue(constHive, strSubKey, strValueName, strValue)
End Function
Sub GetExpandedStringValue(constHive, strKey, strValueName, strValue)
private_oReg.GetExpandedStringValue constHive, strKey, strValueName, strValue
End Sub
Function SetMultiStringValue(constHive, strSubKey, strValueName, arrValue)
SetMultiStringValue = private_oReg.SetMultiStringValue(constHive, strSubKey, strValueName, arrValue)
End Function
Sub GetMultiStringValue(constHive, strKey, strValueName, arrStrValue)
private_oReg.GetMultiStringValue constHive, strKey, strValueName, arrStrValue
End Sub
Function SetDWORDValue(constHive, strSubKey, strValueName, arrValue)
SetDWORDValue = private_oReg.SetDWORDValue(constHive, strSubKey, strValueName, arrValue)
End Function
Sub GetDWORDValue(constHive, strKey, strValueName, intDWordValue)
private_oReg.GetDWORDValue constHive, strKey, strValueName, intDWordValue
End Sub
Function SetQWORDValue(constHive, strSubKey, strValueName, strQWordValue)
SetQWORDValue = private_oReg.SetQWORDValue(constHive, strSubKey, strValueName, strQWordValue)
End Function
Sub GetQWORDValue(constHive, strKey, strValueName, intQWordValue)
private_oReg.GetQWORDValue constHive, strKey, strValueName, intQWordValue
End Sub
Function SetBinaryValue(constHive, strSubKey, strValueName, arrValue)
SetBinaryValue = private_oReg.SetBinaryValue(constHive, strSubKey, strValueName, arrValue)
End Function
Sub GetBinaryValue(constHive, strKey, strValueName, arrBinaryValue)
private_oReg.GetBinaryValue constHive, strKey, strValueName, arrBinaryValue
End Sub
Function EnumKey(constHive, strSubKey, arrKeyNames)
EnumKey = private_oReg.EnumKey(constHive, strSubKey, arrKeyNames)
End Function
Function EnumValues(constHive, strSubKey, arrValueNames, arrValueTypes)
EnumValues = private_oReg.EnumValues(constHive, strSubKey, arrValueNames, arrValueTypes)
End Function
Function CreateKey(constHive, strSubKey)
CreateKey = private_oReg.CreateKey(constHive, strSubKey)
End Function
Function DeleteKey(constHive, strSubKey)
DeleteKey = private_oReg.DeleteKey(constHive, strSubKey)
End Function
Function DeleteValue(constHive, strSubKey, strValue)
DeleteValue = private_oReg.DeleteValue(constHive, strSubKey, strValue)
End Function

View File

@@ -1,358 +0,0 @@
' Notes: wanted to implement this using a class but:
' 1. No matter what I did I could not assign the result of GetObject to a private member
' 2. It looks as if all methods were treated as subs from the outside world which is not good since
' some of these need to return a value
' should be removed when migration is complete
Set private_oReg = GetObject("winmgmts:\root\default:StdRegProv")
Set private_oCtx = CreateObject("WbemScripting.SWbemNamedValueSet")
private_oCtx.Add "__ProviderArchitecture", CInt(OSArchitecture)
Set private_oLocator = CreateObject("Wbemscripting.SWbemLocator")
Set private_oServices = private_oLocator.ConnectServer(".", "root\default","","",,,,private_oCtx)
Set private_oRegSpecific = private_oServices.Get("StdRegProv")
Function CheckAccess(hDefKey,sSubKeyName,uRequired, bGranted )
Set Inparams = private_oRegSpecific.Methods_("CheckAccess").Inparameters
Inparams.hDefKey = hDefKey
Inparams.sSubKeyName = sSubKeyName
Inparams.uRequired = uRequired
set Outparams = private_oRegSpecific.ExecMethod_("CheckAccess", Inparams,,private_oCtx)
bGranted = Outparams.bGranted
CheckAccess = 0
End Function
Function CreateKey(hDefKey,sSubKeyName)
Set Inparams = private_oRegSpecific.Methods_("CreateKey").Inparameters
Inparams.hDefKey = hDefKey
Inparams.sSubKeyName = sSubKeyName
set Outparams = private_oRegSpecific.ExecMethod_("CreateKey", Inparams,,private_oCtx)
CreateKey = 0
End Function
Function DeleteKey(hDefKey,sSubKeyName)
Set Inparams = private_oRegSpecific.Methods_("DeleteKey").Inparameters
Inparams.hDefKey = hDefKey
Inparams.sSubKeyName = sSubKeyName
set Outparams = private_oRegSpecific.ExecMethod_("DeleteKey", Inparams,,private_oCtx)
DeleteKey = 0
End Function
Function DeleteValue(hDefKey,sSubKeyName,sValueName)
Set Inparams = private_oRegSpecific.Methods_("DeleteValue").Inparameters
Inparams.hDefKey = hDefKey
Inparams.sSubKeyName = sSubKeyName
Inparams.sValueName = sValueName
set Outparams = private_oRegSpecific.ExecMethod_("DeleteValue", Inparams,,private_oCtx)
DeleteValue = 0
End Function
Function EnumKey(hDefKey,sSubKeyName, sNames )
Set Inparams = private_oRegSpecific.Methods_("EnumKey").Inparameters
Inparams.hDefKey = hDefKey
Inparams.sSubKeyName = sSubKeyName
set Outparams = private_oRegSpecific.ExecMethod_("EnumKey", Inparams,,private_oCtx)
sNames = Outparams.sNames
EnumKey = 0
End Function
Function EnumValues(hDefKey,sSubKeyName, sNames,Types )
Set Inparams = private_oRegSpecific.Methods_("EnumValues").Inparameters
Inparams.hDefKey = hDefKey
Inparams.sSubKeyName = sSubKeyName
set Outparams = private_oRegSpecific.ExecMethod_("EnumValues", Inparams,,private_oCtx)
sNames = Outparams.sNames
Types = Outparams.Types
EnumValues = 0
End Function
Function GetBinaryValue(hDefKey,sSubKeyName,sValueName, uValue )
Set Inparams = private_oRegSpecific.Methods_("GetBinaryValue").Inparameters
Inparams.hDefKey = hDefKey
Inparams.sSubKeyName = sSubKeyName
Inparams.sValueName = sValueName
set Outparams = private_oRegSpecific.ExecMethod_("GetBinaryValue", Inparams,,private_oCtx)
uValue = Outparams.uValue
GetBinaryValue = 0
End Function
Function GetDWORDValue(hDefKey,sSubKeyName,sValueName, uValue )
Set Inparams = private_oRegSpecific.Methods_("GetDWORDValue").Inparameters
Inparams.hDefKey = hDefKey
Inparams.sSubKeyName = sSubKeyName
Inparams.sValueName = sValueName
set Outparams = private_oRegSpecific.ExecMethod_("GetDWORDValue", Inparams,,private_oCtx)
uValue = Outparams.uValue
GetDWORDValue = 0
End Function
Function GetExpandedStringValue(hDefKey,sSubKeyName,sValueName, sValue )
Set Inparams = private_oRegSpecific.Methods_("GetExpandedStringValue").Inparameters
Inparams.hDefKey = hDefKey
Inparams.sSubKeyName = sSubKeyName
Inparams.sValueName = sValueName
set Outparams = private_oRegSpecific.ExecMethod_("GetExpandedStringValue", Inparams,,private_oCtx)
sValue = Outparams.sValue
GetExpandedStringValue = 0
End Function
Function GetMultiStringValue(hDefKey,sSubKeyName,sValueName, sValue )
Set Inparams = private_oRegSpecific.Methods_("GetMultiStringValue").Inparameters
Inparams.hDefKey = hDefKey
Inparams.sSubKeyName = sSubKeyName
Inparams.sValueName = sValueName
set Outparams = private_oRegSpecific.ExecMethod_("GetMultiStringValue", Inparams,,private_oCtx)
sValue = Outparams.sValue
GetMultiStringValue = 0
End Function
Function GetQWORDValue(hDefKey,sSubKeyName,sValueName, uValue )
Set Inparams = private_oRegSpecific.Methods_("GetQWORDValue").Inparameters
Inparams.hDefKey = hDefKey
Inparams.sSubKeyName = sSubKeyName
Inparams.sValueName = sValueName
set Outparams = private_oRegSpecific.ExecMethod_("GetQWORDValue", Inparams,,private_oCtx)
uValue = Outparams.uValue
GetQWORDValue = 0
End Function
Function GetSecurityDescriptor(hDefKey,sSubKeyName, Descriptor )
Set Inparams = private_oRegSpecific.Methods_("GetSecurityDescriptor").Inparameters
Inparams.hDefKey = hDefKey
Inparams.sSubKeyName = sSubKeyName
set Outparams = private_oRegSpecific.ExecMethod_("GetSecurityDescriptor", Inparams,,private_oCtx)
Descriptor = Outparams.Descriptor
GetSecurityDescriptor = 0
End Function
Function GetStringValue(hDefKey,sSubKeyName,sValueName, sValue )
Set Inparams = private_oRegSpecific.Methods_("GetStringValue").Inparameters
Inparams.hDefKey = hDefKey
Inparams.sSubKeyName = sSubKeyName
Inparams.sValueName = sValueName
set Outparams = private_oRegSpecific.ExecMethod_("GetStringValue", Inparams,,private_oCtx)
sValue = Outparams.sValue
GetStringValue = 0
End Function
Function SetBinaryValue(hDefKey,sSubKeyName,sValueName,uValue)
Set Inparams = private_oRegSpecific.Methods_("SetBinaryValue").Inparameters
Inparams.hDefKey = hDefKey
Inparams.sSubKeyName = sSubKeyName
Inparams.sValueName = sValueName
Inparams.uValue = uValue
set Outparams = private_oRegSpecific.ExecMethod_("SetBinaryValue", Inparams,,private_oCtx)
SetBinaryValue = 0
End Function
Function SetDWORDValue(hDefKey,sSubKeyName,sValueName,uValue)
Set Inparams = private_oRegSpecific.Methods_("SetDWORDValue").Inparameters
Inparams.hDefKey = hDefKey
Inparams.sSubKeyName = sSubKeyName
Inparams.sValueName = sValueName
Inparams.uValue = uValue
set Outparams = private_oRegSpecific.ExecMethod_("SetDWORDValue", Inparams,,private_oCtx)
SetDWORDValue = 0
End Function
Function SetExpandedStringValue(hDefKey,sSubKeyName,sValueName,sValue)
Set Inparams = private_oRegSpecific.Methods_("SetExpandedStringValue").Inparameters
Inparams.hDefKey = hDefKey
Inparams.sSubKeyName = sSubKeyName
Inparams.sValueName = sValueName
Inparams.sValue = sValue
set Outparams = private_oRegSpecific.ExecMethod_("SetExpandedStringValue", Inparams,,private_oCtx)
SetExpandedStringValue = 0
End Function
Function SetMultiStringValue(hDefKey,sSubKeyName,sValueName,sValue)
Set Inparams = private_oRegSpecific.Methods_("SetMultiStringValue").Inparameters
Inparams.hDefKey = hDefKey
Inparams.sSubKeyName = sSubKeyName
Inparams.sValueName = sValueName
Inparams.sValue = sValue
set Outparams = private_oRegSpecific.ExecMethod_("SetMultiStringValue", Inparams,,private_oCtx)
SetMultiStringValue = 0
End Function
Function SetQWORDValue(hDefKey,sSubKeyName,sValueName,uValue)
Set Inparams = private_oRegSpecific.Methods_("SetQWORDValue").Inparameters
Inparams.hDefKey = hDefKey
Inparams.sSubKeyName = sSubKeyName
Inparams.sValueName = sValueName
Inparams.uValue = uValue
set Outparams = private_oRegSpecific.ExecMethod_("SetQWORDValue", Inparams,,private_oCtx)
SetQWORDValue = 0
End Function
Function SetSecurityDescriptor(hDefKey,sSubKeyName,Descriptor)
Set Inparams = private_oRegSpecific.Methods_("SetSecurityDescriptor").Inparameters
Inparams.hDefKey = hDefKey
Inparams.sSubKeyName = sSubKeyName
Inparams.Descriptor = Descriptor
set Outparams = private_oRegSpecific.ExecMethod_("SetSecurityDescriptor", Inparams,,private_oCtx)
SetSecurityDescriptor = 0
End Function
Function SetStringValue(hDefKey,sSubKeyName,sValueName,sValue)
Set Inparams = private_oRegSpecific.Methods_("SetStringValue").Inparameters
Inparams.hDefKey = hDefKey
Inparams.sSubKeyName = sSubKeyName
Inparams.sValueName = sValueName
Inparams.sValue = sValue
set Outparams = private_oRegSpecific.ExecMethod_("SetStringValue", Inparams,,private_oCtx)
SetStringValue = 0
End Function

View File

@@ -1,7 +0,0 @@
<job id="JsonSafeStreamTest">
<script language="VBScript" src="util.vbs" />
<script language="VBScript">
str = """" & vbcrlf & "测试\"
Write("{ ""a"": """ & JsonSafe(str) & """}" & vbcrlf)
</script>
</job>

View File

@@ -1,32 +0,0 @@
<job id="createKeyStream">
<script language="VBScript" src="util.vbs" />
<script language="VBScript" src="regUtil.vbs" />
<script language="VBScript">
CheckZeroArgs("usage: cscript regCreateKey.wsf architecture")
DetermineOSArchitecture()
LoadRegistryImplementationByOSArchitecture()
Do While Not stdin.AtEndOfLine
strLine = stdin.ReadLine()
strLine = unescape(trim(strLine))
If IsNull(strLine) or strLine = "" Then
WScript.Quit 25127
End If
ParseHiveAndSubKey strLine, constHive, strSubKey
if IsNull(constHive) Then
WriteLineErr "unsupported hive " & strLine
WScript.Quit 25122
End If
Result = CreateKey(constHive, strSubKey)
If Not Result = 0 Then
WScript.Quit Result
End If
Loop
</script>
</job>

View File

@@ -1,29 +0,0 @@
<job id="deleteKey">
<script language="VBScript" src="util.vbs" />
<script language="VBScript" src="regUtil.vbs" />
<script language="VBScript">
CheckZeroArgs("usage: cscript regDeleteKey.wsf architecture")
DetermineOSArchitecture()
LoadRegistryImplementationByOSArchitecture()
Do While Not stdin.AtEndOfLine
strLine = stdin.ReadLine()
strLine = unescape(trim(strLine))
ParseHiveAndSubKey strLine, constHive, strSubKey
if IsNull(constHive) Then
WriteLineErr "unsupported hive " & strLine
WScript.Quit 25122
End If
Result = DeleteKey(constHive, strSubKey)
If Not Result = 0 Then
WScript.Quit Result
End If
Loop
</script>
</job>

View File

@@ -1,29 +0,0 @@
<job id="deleteValue">
<script language="VBScript" src="util.vbs" />
<script language="VBScript" src="regUtil.vbs" />
<script language="VBScript">
CheckZeroArgs("usage: cscript regDeleteValue.wsf architecture")
DetermineOSArchitecture()
LoadRegistryImplementationByOSArchitecture()
Do While Not stdin.AtEndOfLine
strLine = stdin.ReadLine()
strLine = unescape(trim(strLine))
ParseHiveAndSubKeyAndValue strLine, constHive, strSubKey, strValue
if IsNull(constHive) Then
WriteLineErr "unsupported hive " & strLine
WScript.Quit 25122
End If
Result = DeleteValue(constHive, strSubKey, strValue)
If Not Result = 0 Then
WScript.Quit Result
End If
Loop
</script>
</job>

View File

@@ -1,49 +0,0 @@
'
' Lists the sub keys and values of a given registry key
'
' cscript regList.wsg HKLM\Software
'
' Will Yield:
'
' {
' "hklm\software": {
' "keys": [ .. array of sub keys .. ],
' "values": {
' "moo": {
' "type": "REG_SZ",
' "value": "bar"
' }
' }
' }
' }
<job id="list">
<script language="VBScript" src="util.vbs" />
<script language="VBScript" src="regUtil.vbs" />
<script language="VBScript">
CheckZeroArgs("usage: cscript regList.wsf architecture regpath1 [regpath2] ... [regpathN]")
DetermineOSArchitecture()
LoadRegistryImplementationByOSArchitecture()
Write "{"
On Error Resume Next
For v = 1 To args.Count - 1
If (v > 1) Then
Write ","
End If
Dim key: key = trim(args(v))
Write """" & JsonSafe(key) & """: "
ParseHiveAndSubKey key, constHive, strSubKey
If IsNull(constHive) Then
WriteLineErr "unsupported hive " & args(v)
WScript.Quit 25122
End If
ListChildrenAsJson constHive, strSubKey
Next
Write "}"
</script>
</job>

View File

@@ -1,46 +0,0 @@
'
' Lists the sub keys and values of a given registry key, this script is slightly different
' than regList because it reads stdin for the keys to list
'
' echo HKLM\Software | cscript regListStream.wsf A
'
' Will Yield:
'
' {
' "hklm\software": {
' "keys": [ .. array of sub keys .. ],
' "values": {
' "moo": {
' "type": "REG_SZ",
' "value": "bar"
' }
' }
' }
' }
<job id="listStream">
<script language="VBScript" src="util.vbs" />
<script language="VBScript" src="regUtil.vbs" />
<script language="VBScript">
CheckZeroArgs("usage: echo KEY | cscript regListStream.wsf architecture")
DetermineOSArchitecture()
LoadRegistryImplementationByOSArchitecture()
Do While Not stdin.AtEndOfLine
strLine = stdin.ReadLine()
strLine = unescape(trim(strLine))
ParseHiveAndSubKey strLine, constHive, strSubKey
if IsNull(constHive) Then
WriteLineErr "unsupported hive " & strLine
WScript.Quit 25122
End If
Write "{ ""key"" : """ & JsonSafe(strLine) & """, ""data"": "
ListChildrenAsJson constHive, strSubKey
Write "}" & vbcrlf
Loop
</script>
</job>

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