36 Commits
3.0.0 ... 3.0.5

Author SHA1 Message Date
putyy
3f07bae796 Merge pull request #176 from putyy/wails
feat: Added header cache, optional header settings...
2025-04-23 16:55:45 +08:00
putyy
6086bd7086 feat: Added header cache, optional header settings... 2025-04-23 16:55:24 +08:00
putyy
62bf0e2308 Merge pull request #175 from putyy/wails
优化拦截标识
2025-04-22 16:00:36 +08:00
putyy
0bb1a21a76 优化拦截标识 2025-04-22 15:59:59 +08:00
putyy
575e2d8904 Merge pull request #174 from putyy/wails
Wails
2025-04-22 14:54:11 +08:00
putyy
db21134550 优化下载、README等 2025-04-22 14:52:45 +08:00
putyy
3407490f82 Update README.md 2025-04-18 12:50:37 +08:00
putyy
dad06f6cd6 Merge pull request #158 from claviering/fix-NInput-disabled
input 组件disabled下内容看不清 #150
2025-03-13 16:11:58 +08:00
claviering
de70fc66b4 input 组件disabled下内容看不清 #150 2025-03-10 10:05:08 +08:00
putyy
2282f72b2f 更新readme 2025-03-06 22:05:53 +08:00
putyy
e00a7c9044 优化wx拦截 2025-02-13 09:24:00 +08:00
putyy
e79a7ba2fe 优化wx拦截 2025-02-13 09:23:39 +08:00
putyy
791e50411d Update issue templates 2025-02-11 21:45:52 +08:00
putyy
df8eb0e4cd Update issue templates 2025-02-11 21:44:28 +08:00
putyy
3e291171c2 Update issue templates 2025-02-11 21:43:48 +08:00
putyy
bd8f8e80c9 Merge pull request #143 from putyy/wails
Wails
2025-02-11 21:42:19 +08:00
putyy
8ed7e144e1 修改doc、优化Mac设置代理等 2025-02-11 21:40:29 +08:00
putyy
69f8224453 完善文档、新增一些功能 2025-02-10 15:40:32 +08:00
putyy
3c0e51a9e2 文档 2025-02-09 18:58:09 +08:00
putyy
0a6679b983 新增docsify 2025-02-08 17:29:36 +08:00
putyy
6d1024806c Merge pull request #131 from putyy/wails
Wails
2025-01-22 16:19:44 +08:00
putyy
388e3f46a4 更新md 2025-01-22 16:18:32 +08:00
putyy
f7c8e9f7db 更新host 2025-01-21 10:37:10 +08:00
putyy
7ca484f45d Merge pull request #130 from putyy/wails
up:wx rule、downloads
2025-01-14 14:16:21 +08:00
putyy
cecb13fa90 up:wx rule、downloads 2025-01-14 13:59:22 +08:00
putyy
331478d370 Merge pull request #127 from taotieren/update-aur
update README.md
2025-01-11 13:16:40 +08:00
taotieren
2481407093 update README.md 2025-01-11 11:45:37 +08:00
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
78 changed files with 1198 additions and 414 deletions

42
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
name: "Bug Report \\ 问题反馈"
description: "Create a report to help us improve \\ 帮助改进"
labels: ["Bug"]
body:
- type: input
id: title
attributes:
label: "Title \\ 标题"
description: "A brief summary of the bug. \\ 对于该错误的简要总结。"
placeholder: "Enter the bug title here. \\ 在此输入错误标题。"
validations:
required: true
- type: textarea
id: reproduce
attributes:
label: "To Reproduce \\ 操作流程"
description: "Steps to reproduce the behaviour. \\ 重现该行为的步骤。"
placeholder: |
1. Go to '...' \ 进入 '...'
2. Click on '....' \ 点击 '....'
validations:
required: true
- type: textarea
id: expected-behaviour
attributes:
label: "Expected Behaviour \\ 预期结果"
description: "A clear and concise description of what you expected to happen. \\ 对您期望发生的事情的清晰简明描述。"
placeholder: "A clear and concise description of what you expected to happen. \\ 对您期望发生的事情的清晰简明描述。"
validations:
required: true
- type: textarea
id: software-version
attributes:
label: "Software Version \\ 软件版本"
description: "Please specify the version of the software you are using. \\ 请指定您使用的软件版本。"
placeholder: "Enter the software version here. \\ 在此输入软件版本。"
validations:
required: true

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1 @@
blank_issues_enabled: true

View File

@@ -0,0 +1,31 @@
name: "Feature Request \\ 功能建议"
description: "Suggest an idea for this project \\ 为这个项目提出一个新想法"
labels: ["Enhancement"]
body:
- type: input
id: title
attributes:
label: "Title \\ 标题"
description: "A brief summary of your feature request. \\ 对您功能建议的简要总结。"
placeholder: "Enter the feature title here. \\ 在此输入功能标题。"
validations:
required: true
- type: textarea
id: feature-suggestion
attributes:
label: "Feature Suggestion \\ 功能建议"
description: "A clear and concise description of the feature you would like to suggest. \\ 对您想要建议的功能的清晰简明描述。"
placeholder: "Describe your feature suggestion here. \\ 在此描述您的功能建议。"
validations:
required: true
- type: textarea
id: proposed-solution
attributes:
label: "Proposed Solution \\ 你的方案"
description: "A clear and concise description of your proposed solution. \\ 对您提议的解决方案的清晰简明描述。"
placeholder: "Describe your proposed solution here. \\ 在此描述您的提议方案。"
validations:
required: true

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@ test
build/bin
node_modules
frontend/dist
.DS_Store

102
README-EN.md Normal file
View File

@@ -0,0 +1,102 @@
# res-downloader
### 🎉 Aixiang Resource Downloader
> A cross-platform resource downloader built with Go + [Wails](https://github.com/wailsapp/wails).
Clean UI, easy to use, and supports a wide range of resource sniffing and downloading.
### 📖 [中文](./README.md) | English
---
## ✨ Features
- 🚀 **User-Friendly**: Simple operation with an intuitive and beautiful UI
- 🖥️ **Cross-Platform**: Available on Windows / macOS / Linux
- 🌐 **Supports Multiple Resource Types**: Video / Audio / Images / m3u8 / Live streams, and more
- 📱 **Wide Platform Compatibility**: Works with WeChat Channels, Mini Programs, Douyin, Kuaishou, Xiaohongshu, KuGou Music, QQ Music, and more
- 🌍 **Proxy Capture**: Built-in proxy allows fetching resources behind network restrictions
---
## 📚 Docs & Versions
- 📘 [Online Documentation (Chinese)](https://res.putyy.com/)
- 🧩 [Mini Version Ui Display using default browser](https://github.com/putyy/res-downloader) [Old Electron Version Support Win7](https://github.com/putyy/res-downloader/tree/old)
- 💬 [Join the User Group (Chinese)](https://www.putyy.com/app/admin/upload/img/20250418/6801d9554dc7.webp)
> *If full, you can add WeChat `AmorousWorld` with a note “From GitHub”*
---
## 🧩 Download Links
- 🆕 [Download from GitHub](https://github.com/putyy/res-downloader/releases)
- 🆕 [Download via Lanzou Cloud (Password: 9vs5)](https://wwjv.lanzoum.com/b04wgtfyb)
- ⚠️ *Windows 7 users: Please use version `2.3.0`*
---
## 🚀 How to Use
> Follow these steps to use the software correctly:
1. During installation, be sure to **allow certificate installation** and **grant network access**
2. Launch the software → Click **"Start Proxy"** at the top left
3. Choose the resource types to capture (default is all)
4. Open the target content externally (WeChat, Mini App, Browser, etc.)
5. Return to the homepage to view the captured resource list
---
## 🖼️ Screenshot
![Screenshot](docs/images/show.webp)
---
## ❓ FAQ
### 📺 m3u8 Video Resources
- Online Preview: [m3u8play](https://m3u8play.com/)
- Download Tool: [m3u8-down](https://m3u8-down.gowas.cn/)
### 📡 Live Stream Resources
- We recommend [OBS](https://obsproject.com/) for recording (search for setup tutorials)
### 🐢 Slow Downloads or Large File Failures?
- Recommended download managers:
- [Neat Download Manager](https://www.neatdownloadmanager.com/index.php/en/)
- [Motrix](https://motrix.app/download)
- For WeChat videos, click `Decrypt Video` after download
### 🧩 Unable to Intercept Resources?
- Check your system proxy settings:
Address: 127.0.0.1
Port: 8899
### 🌐 Can't Access Internet After Closing the App?
- Manually disable the system proxy settings
### 🧠 More Questions?
- [GitHub Issues](https://github.com/putyy/res-downloader/issues)
- [Aixiang Forum Thread (Chinese)](https://s.gowas.cn/d/4089)
---
## 💡 Principles & Motivation
This tool captures traffic via a local proxy and filters useful resources.
Its working principle is similar to tools like Fiddler, Charles, or browser DevTools, but with a more user-friendly display and enhanced filtering, making it suitable for everyday users with minimal tech background.
---
## ⚠️ Disclaimer
> This software is for educational and research purposes only.
Commercial or illegal use is strictly prohibited.
The author is not responsible for any consequences arising from misuse.

124
README.md
View File

@@ -1,49 +1,99 @@
## res-downloader V3全新版来袭更快了
### 爱享素材下载器【[加入群聊](https://qm.qq.com/q/HS8FdhpZCK)】
🎯 基于Go + [wails](https://github.com/wailsapp/wails)
📦 操作简单、可获取不同类型资源
# res-downloader
### 🎉 爱享素材下载器
🖥️ 支持Windows、Mac、Linux
🌐 支持视频、音频、图片、m3u8、直播流等常见网络资源
💪 支持微信视频号、小程序、抖音、快手、小红书、酷狗音乐、qq音乐等网络资源下载
👼 支持设置代理以获取特殊网络下的资源
> 一款基于 Go + [Wails](https://github.com/wailsapp/wails) 的跨平台资源下载工具,简洁易用,支持多种资源嗅探与下载。
## 软件下载
🆕 [github下载](https://github.com/putyy/res-downloader/releases)
🆕 [蓝奏云下载 密码:9vs5](https://wwjv.lanzoum.com/b04wgtfyb)
### 📖 中文 | [English](./README-EN.md)
## 使用方法
> 0. 安装时一定要同意安装证书文件、一定要允许网络访问
> 1. 打开本软件 软件首页左上角点击 “启动代理”
> 2. 软件首页选择要获取的资源类型(默认选中的全部)
> 3. 打开要捕获的源, 如:视频号、网页、小程序等等
> 4. 返回软件首页即可看到资源列表
---
## 软件截图
![](preview/show.webp)
## ✨ 功能特色
## 常见问题
m3u8: 预览和下载:
> [下载](https://m3u8-down.gowas.cn/) [预览](https://m3u8play.com/)
- 🚀 **简单易用**:操作简单,界面清晰美观
- 🖥️ **多平台支持**Windows / macOS / Linux
- 🌐 **多资源类型支持**:视频 / 音频 / 图片 / m3u8 / 直播流等
- 📱 **平台兼容广泛**支持微信视频号、小程序、抖音、快手、小红书、酷狗音乐、QQ音乐等
- 🌍 **代理抓包**:支持设置代理获取受限网络下的资源
直播流: 预览和录制:
> [使用obs进行预览和录制 使用教程自行百度]( https://obsproject.com/)
---
下载慢、大视频下载失败
> 推荐使用如下工具加速下载,视频号可以下载完成后再到对应视频操作项选择 “视频解密(视频号)” 按钮
>> [Neat Download Manager](https://www.neatdownloadmanager.com/index.php/en/)、[Motrix](https://motrix.app/download)等软件进行下载
## 📚 文档 & 版本
打开本软件,无法正常拦截获取
> 检查系统代理是否正确设置 代理地址127.0.0.1 端口8899
- 📘 [在线文档](https://res.putyy.com/)
- 🧩 [Mini版 UI使用默认浏览器展示](https://github.com/putyy/resd-mini) [Electron旧版 支持Win7](https://github.com/putyy/res-downloader/tree/old)
- 💬 [加入交流群](https://www.putyy.com/app/admin/upload/img/20250418/6801d9554dc7.webp)
> *群满时可加微信 `AmorousWorld`,请备注“来源”*
关闭软件后无法正常上网
> 手动关闭系统代理设置
---
其他问题
[github](https://github.com/putyy/res-downloader/issues) 、 [爱享论坛](https://s.gowas.cn/d/4089)
## 🧩 下载地址
## 实现 & 初衷
通过代理网络抓包拦截响应,筛选出有用的资源, 同fiddler、charles等抓包软件、浏览器F12打开控制也能达到目的只不过这些软件需要手动进行筛选对于小白用户上手还是有点难度本软件对部分资源做了特殊处理更适合大众用户所以就有了本项目。
- 🆕 [GitHub 下载](https://github.com/putyy/res-downloader/releases)
- 🆕 [蓝奏云下载密码9vs5](https://wwjv.lanzoum.com/b04wgtfyb)
- ⚠️ *Win7 用户请下载 `2.3.0` 版本*
## 免责声明
本软件用于学习研究使用,若因使用本软件造成的一切法律责任均与本人无关!
---
## 🚀 使用方法
> 请按以下步骤操作以正确使用软件:
1. 安装时务必 **允许安装证书文件****允许网络访问**
2. 打开软件 → 首页左上角点击 **“启动代理”**
3. 选择要获取的资源类型(默认全部)
4. 在外部打开资源页面(如视频号、小程序、网页等)
5. 返回软件首页,即可看到资源列表
---
## 🖼️ 软件截图
![软件截图](docs/images/show.webp)
---
## ❓ 常见问题
### 📺 m3u8 视频资源
- 在线预览:[m3u8play](https://m3u8play.com/)
- 视频下载:[m3u8-down](https://m3u8-down.gowas.cn/)
### 📡 直播流资源
- 推荐使用 [OBS](https://obsproject.com/) 进行录制(教程请百度)
### 🐢 下载慢、大文件失败?
- 推荐工具:
- [Neat Download Manager](https://www.neatdownloadmanager.com/index.php/en/)
- [Motrix](https://motrix.app/download)
- 视频号资源下载后可在操作项点击 `视频解密(视频号)`
### 🧩 软件无法拦截资源?
- 检查是否正确设置系统代理:
地址127.0.0.1
端口8899
### 🌐 关闭软件后无法上网?
- 手动关闭系统代理设置
### 🧠 更多问题
- [GitHub Issues](https://github.com/putyy/res-downloader/issues)
- [爱享论坛讨论帖](https://s.gowas.cn/d/4089)
---
## 💡 实现原理 & 初衷
本工具通过代理方式实现网络抓包,并筛选可用资源。与 Fiddler、Charles、浏览器 DevTools 原理类似,但对资源进行了更友好的筛选、展示和处理,大幅度降低了使用门槛,更适合大众用户使用。
---
## ⚠️ 免责声明
> 本软件仅供学习与研究用途,禁止用于任何商业或违法用途。
如因此产生的任何法律责任,概与作者无关!

BIN
build/.DS_Store vendored

Binary file not shown.

View File

@@ -1,35 +1,86 @@
# Build Directory
The build directory is used to house all the build files and assets for your application.
The structure is:
* bin - Output directory
* darwin - macOS specific files
* windows - Windows specific files
## Mac
The `darwin` directory holds files specific to Mac builds.
These may be customised and used as part of the build. To return these files to the default state, simply delete them
and
build with `wails build`.
The directory contains the following files:
- `Info.plist` - the main plist file used for Mac builds. It is used when building using `wails build`.
- `Info.dev.plist` - same as the main plist file but used when building using `wails dev`.
```bash
wails build -platform "darwin/universal"
create-dmg 'build/bin/res-downloader.app' --overwrite ./build/bin
mv -f "build/bin/res-downloader $(jq -r '.info.productVersion' wails.json).dmg" "build/bin/res-downloader_$(jq -r '.info.productVersion' wails.json)_mac.dmg"
```
## Windows
```bash
wails build -f -nsis -platform "windows/amd64" -webview2 Embed -skipbindings && mv -f "build/bin/res-downloader-amd64-installer.exe" "build/bin/res-downloader_$(jq -r '.info.productVersion' wails.json)_win_amd64.exe"
wails build -f -nsis -platform "windows/arm64" -webview2 Embed -skipbindings && mv -f "build/bin/res-downloader-arm64-installer.exe" "build/bin/res-downloader_$(jq -r '.info.productVersion' wails.json)_win_arm64.exe"
```
The `windows` directory contains the manifest and rc files used when building with `wails build`.
These may be customised for your application. To return these files to the default state, simply delete them and
build with `wails build`.
## Linux
- `icon.ico` - The icon used for the application. This is used when building using `wails build`. If you wish to
use a different icon, simply replace this file with your own. If it is missing, a new `icon.ico` file
will be created using the `appicon.png` file in the build directory.
- `installer/*` - The files used to create the Windows installer. These are used when building using `wails build`.
- `info.json` - Application details used for Windows builds. The data here will be used by the Windows installer,
as well as the application itself (right click the exe -> properties -> details)
- `wails.exe.manifest` - The main application manifest file.
### 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 -platform "linux/amd64" -s -skipbindings
# 打包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)_linux_amd64.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)_linux_amd64.AppImage
mv -f build/bin/res-downloader build/bin/res-downloader_$(jq -r '.info.productVersion' wails.json)_linux_amd64
```
> 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 -platform "linux/arm64" -s -skipbindings
# 打包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)_linux_arm64.deb
mv -f build/bin/res-downloader build/bin/res-downloader_$(jq -r '.info.productVersion' wails.json)_linux_arm64
```
### Arch Linux
[![Packaging status](https://repology.org/badge/vertical-allrepos/res-downloader.svg)](https://repology.org/project/res-downloader/versions)
```bash
yay -Syu res-downloader
```
### Linux 本地编译
```bash
git clone https://github.com/putyy/res-downloader.git
cd res-downloader
# -- GO Proxy --
# 如果国内编译时 go 下载慢或报错,可以设置 go 国内代理加速,否则不用设置
export GO111MODULE=on
export GOPROXY=https://goproxy.cn,direct
# -- Go Proxy --
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 build/linux/Arch/res-downloader.desktop /usr/share/applications/res-downloader.desktop
```

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;

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

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=res-downloader
Icon=res-downloader.png
Terminal=false
Categories=Utility;

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.24.2-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

View File

@@ -14,7 +14,7 @@
!define INFO_PRODUCTNAME "res-downloader"
!endif
!ifndef INFO_PRODUCTVERSION
!define INFO_PRODUCTVERSION "3.0.0"
!define INFO_PRODUCTVERSION "3.0.5"
!endif
!ifndef INFO_COPYRIGHT
!define INFO_COPYRIGHT "Copyright © 2023"

View File

@@ -7,6 +7,7 @@ import (
"github.com/wailsapp/wails/v2/pkg/runtime"
"os"
"path/filepath"
"regexp"
sysRuntime "runtime"
"strconv"
"strings"
@@ -37,12 +38,18 @@ var (
httpServerOnce *HttpServer
)
func GetApp(assets embed.FS) *App {
func GetApp(assets embed.FS, wjs string) *App {
if appOnce == nil {
matches := regexp.MustCompile(`"productVersion":\s*"([\d.]+)"`).FindStringSubmatch(wjs)
version := "1.0.1"
if len(matches) > 0 {
version = matches[1]
}
appOnce = &App{
assets: assets,
AppName: "res-downloader",
Version: "3.0.0",
Version: version,
Description: "res-downloader是一款集网络资源嗅探 + 高速下载功能于一体的软件,高颜值、高性能和多样化,提供个人用户下载自己上传到各大平台的网络资源功能!",
Copyright: "Copyright © 2023~" + strconv.Itoa(time.Now().Year()),
PublicCrt: []byte(`
@@ -142,10 +149,12 @@ func (a *App) OnExit() {
func (a *App) installCert() {
if res, err := systemOnce.installCert(); err != nil {
if sysRuntime.GOOS == "darwin" {
DialogErr("证书安装失败,请手动执行安装命令(已复制到剪切板),err:" + err.Error() + ", " + res)
_ = runtime.ClipboardSetText(appOnce.ctx, `echo "输入本地登录密码" && sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "`+systemOnce.CertFile+`" && touch `+a.LockFile+` && echo "安装完成"`)
_ = 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)
@@ -166,7 +175,7 @@ func (a *App) OpenSystemProxy() bool {
a.IsProxy = true
return true
}
DialogErr("设置失败")
DialogErr("设置失败:" + err.Error())
return false
}
@@ -179,7 +188,7 @@ func (a *App) UnsetSystemProxy() bool {
a.IsProxy = false
return true
}
DialogErr("设置失败")
DialogErr("设置失败:" + err.Error())
return false
}

View File

@@ -15,6 +15,8 @@ type Config struct {
Port string `json:"Port"`
Quality int `json:"Quality"`
SaveDirectory string `json:"SaveDirectory"`
FilenameLen int `json:"FilenameLen"`
FilenameTime bool `json:"FilenameTime"`
UpstreamProxy string `json:"UpstreamProxy"`
OpenProxy bool `json:"OpenProxy"`
DownloadProxy bool `json:"DownloadProxy"`
@@ -22,24 +24,28 @@ type Config struct {
WxAction bool `json:"WxAction"`
TaskNumber int `json:"TaskNumber"`
UserAgent string `json:"UserAgent"`
UseHeaders string `json:"UseHeaders"`
}
func initConfig() *Config {
if globalConfig == nil {
def := `
{
"Host": "0.0.0.0",
"Host": "127.0.0.1",
"Port": "8899",
"Theme": "lightTheme",
"Quality": 0,
"SaveDirectory": "",
"FilenameLen": 0,
"FilenameTime": true,
"UpstreamProxy": "",
"OpenProxy": false,
"DownloadProxy": false,
"AutoProxy": false,
"WxAction": false,
"AutoProxy": true,
"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"
"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",
"UseHeaders": "User-Agent,Referer,Authorization,Cookie"
}
`
def = strings.ReplaceAll(def, "__TaskNumber__", strconv.Itoa(runtime.NumCPU()*2))
@@ -47,9 +53,23 @@ func initConfig() *Config {
storage: NewStorage("config.json", []byte(def)),
}
defaultMap := make(map[string]interface{})
_ = json.Unmarshal([]byte(def), &defaultMap)
data, err := globalConfig.storage.Load()
if err == nil {
_ = json.Unmarshal(data, &globalConfig)
var loadedMap map[string]interface{}
_ = json.Unmarshal(data, &loadedMap)
for key, val := range defaultMap {
if _, ok := loadedMap[key]; !ok {
loadedMap[key] = val
}
}
finalBytes, _ := json.Marshal(loadedMap)
_ = json.Unmarshal(finalBytes, &globalConfig)
} else {
globalLogger.Esg(err, "load config err")
}
@@ -59,9 +79,13 @@ func initConfig() *Config {
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.FilenameLen = config.FilenameLen
c.FilenameTime = config.FilenameTime
c.UpstreamProxy = config.UpstreamProxy
c.UserAgent = config.UserAgent
c.OpenProxy = config.OpenProxy
@@ -69,6 +93,7 @@ func (c *Config) setConfig(config Config) {
c.AutoProxy = config.AutoProxy
c.TaskNumber = config.TaskNumber
c.WxAction = config.WxAction
c.UseHeaders = config.UseHeaders
if oldProxy != c.UpstreamProxy {
proxyOnce.setTransport()
}

View File

@@ -5,10 +5,10 @@ import (
"io"
"log"
"net/http"
"net/http/cookiejar"
"net/url"
"os"
"path/filepath"
"strings"
"sync"
)
@@ -31,17 +31,19 @@ type FileDownloader struct {
totalTasks int
TotalSize int64
IsMultiPart bool
Headers map[string]string
DownloadTaskList []*DownloadTask
progressCallback ProgressCallback
}
func NewFileDownloader(url, filename string, totalTasks int) *FileDownloader {
func NewFileDownloader(url, filename string, totalTasks int, headers map[string]string) *FileDownloader {
return &FileDownloader{
Url: url,
FileName: filename,
totalTasks: totalTasks,
IsMultiPart: false,
TotalSize: 0,
Headers: headers,
DownloadTaskList: make([]*DownloadTask, 0),
}
}
@@ -52,10 +54,16 @@ func (fd *FileDownloader) buildClient() *http.Client {
transport.Proxy = http.ProxyURL(fd.ProxyUrl)
}
// Cookie handle
jar, _ := cookiejar.New(nil)
return &http.Client{
Transport: transport,
Jar: jar,
}
}
func (fd *FileDownloader) setHeaders(request *http.Request) {
for key, values := range fd.Headers {
if strings.Contains(globalConfig.UseHeaders, key) {
request.Header.Set(key, values)
}
}
}
@@ -68,42 +76,60 @@ func (fd *FileDownloader) init() error {
fd.Referer = parsedURL.Scheme + "://" + parsedURL.Host + "/"
}
if globalConfig.DownloadProxy && globalConfig.UpstreamProxy != "" {
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
request, err := http.NewRequest("HEAD", fd.Url, nil)
if err != nil {
return fmt.Errorf("create request failed")
}
if _, ok := fd.Headers["User-Agent"]; !ok {
fd.Headers["User-Agent"] = globalConfig.UserAgent
}
if _, ok := fd.Headers["Referer"]; !ok {
fd.Headers["Referer"] = fd.Referer
}
fd.setHeaders(request)
resp, err := fd.buildClient().Do(request)
if err != nil {
return fmt.Errorf("request failed" + err.Error())
}
defer resp.Body.Close()
fd.TotalSize = resp.ContentLength
if fd.TotalSize <= 0 {
return fmt.Errorf("request init failed: size 0")
}
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 {
dir := filepath.Dir(fd.FileName)
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
return err
}
fd.File, err = os.OpenFile(fd.FileName, os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
return fmt.Errorf("文件初始化失败: %w", err)
}
if err = fd.File.Truncate(fd.TotalSize); err != nil {
fd.File.Close()
return fmt.Errorf("文件大小设置失败: %w", err)
}
return nil
}
@@ -164,8 +190,9 @@ func (fd *FileDownloader) startDownloadTask(waitGroup *sync.WaitGroup, progressC
globalLogger.Error().Stack().Err(err).Msgf("任务%d创建请求出错", task.taskID)
return
}
request.Header.Set("User-Agent", globalConfig.UserAgent)
request.Header.Set("Referer", fd.Referer)
fd.setHeaders(request)
if fd.IsMultiPart {
request.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", task.rangeStart, task.rangeEnd))
}

View File

@@ -1,6 +1,7 @@
package core
import (
"bytes"
"encoding/json"
"fmt"
"github.com/wailsapp/wails/v2/pkg/runtime"
@@ -20,15 +21,11 @@ type ResponseData struct {
Data interface{} `json:"data"`
}
type HttpServer struct {
broadcast chan []byte
}
type HttpServer struct{}
func initHttpServer() *HttpServer {
if httpServerOnce == nil {
httpServerOnce = &HttpServer{
broadcast: make(chan []byte),
}
httpServerOnce = &HttpServer{}
}
return httpServerOnce
}
@@ -38,9 +35,19 @@ func (h *HttpServer) run() {
if err != nil {
log.Fatalf("无法启动监听: %v", err)
}
go h.handleMessages()
fmt.Println("服务已启动,监听 http://" + globalConfig.Host + ":" + globalConfig.Port)
if err := http.Serve(listener, proxyOnce.Proxy); err != nil {
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)
}
}
@@ -90,13 +97,6 @@ func (h *HttpServer) preview(w http.ResponseWriter, r *http.Request) {
return
}
func (h *HttpServer) handleMessages() {
for {
msg := <-h.broadcast
runtime.EventsEmit(appOnce.ctx, "event", string(msg))
}
}
func (h *HttpServer) send(t string, data interface{}) {
jsonData, err := json.Marshal(map[string]interface{}{
"type": t,
@@ -106,7 +106,7 @@ func (h *HttpServer) send(t string, data interface{}) {
fmt.Println("Error converting map to JSON:", err)
return
}
h.broadcast <- jsonData
runtime.EventsEmit(appOnce.ctx, "event", string(jsonData))
}
func (h *HttpServer) writeJson(w http.ResponseWriter, data ResponseData) {
@@ -179,13 +179,13 @@ func (h *HttpServer) openFolder(w http.ResponseWriter, r *http.Request) {
case "linux":
// linux
// 尝试使用不同的文件管理器
cmd = exec.Command("nautilus", filePath) // 尝试Nautilus
cmd = exec.Command("nautilus", filePath)
if err := cmd.Start(); err != nil {
cmd = exec.Command("thunar", filePath) // 尝试Thunar
cmd = exec.Command("thunar", filePath)
if err := cmd.Start(); err != nil {
cmd = exec.Command("dolphin", filePath) // 尝试Dolphin
cmd = exec.Command("dolphin", filePath)
if err := cmd.Start(); err != nil {
cmd = exec.Command("pcmanfm", filePath) // 尝试PCManFM
cmd = exec.Command("pcmanfm", filePath)
if err := cmd.Start(); err != nil {
globalLogger.err(err)
h.writeJson(w, ResponseData{Code: 0, Message: err.Error()})

View File

@@ -15,14 +15,14 @@ func Middleware(next http.Handler) http.Handler {
}
func HandleApi(w http.ResponseWriter, r *http.Request) bool {
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
}
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)

View File

@@ -95,7 +95,7 @@ func (p *Proxy) setTransport() {
IdleConnTimeout: 30 * time.Second,
}
if globalConfig.UpstreamProxy != "" && globalConfig.OpenProxy {
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)
@@ -105,40 +105,18 @@ func (p *Proxy) setTransport() {
}
func (p *Proxy) httpRequestEvent(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
if strings.Contains(r.Host, "res-downloader.666666.com") {
if 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)
}
}
if strings.Contains(r.URL.Path, "/cert") {
return p.handleCertRequest(r, ctx)
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) handleCertRequest(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
resp := &http.Response{
StatusCode: http.StatusOK,
ProtoMajor: 1,
ProtoMinor: 1,
Header: http.Header{
"Content-Type": []string{"application/x-x509-ca-data"},
"Content-Disposition": []string{"attachment;filename=z-der-data.crt"},
"Content-Transfer-Encoding": []string{"binary"},
},
Body: io.NopCloser(bytes.NewReader(appOnce.PublicCrt)),
Request: r,
}
return r, resp
}
func (p *Proxy) handleWechatRequest(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
body, err := io.ReadAll(r.Body)
if err != nil {
@@ -170,12 +148,12 @@ func (p *Proxy) handleWechatRequest(r *http.Request, ctx *goproxy.ProxyCtx) (*ht
if !ok {
return
}
resourceOnce.markMu.Lock()
defer resourceOnce.markMu.Unlock()
urlSign := Md5(rowUrl.(string))
if _, ok := resourceOnce.mark[urlSign]; ok {
if resourceOnce.mediaIsMarked(urlSign) {
return
}
id, err := gonanoid.New()
if err != nil {
id = urlSign
@@ -197,6 +175,12 @@ func (p *Proxy) handleWechatRequest(r *http.Request, ctx *goproxy.ProxyCtx) (*ht
ContentType: "video/mp4",
}
if mediaType, ok := firstMedia["mediaType"].(float64); ok && mediaType == 9 {
res.Classify = "image"
res.Suffix = ".png"
res.ContentType = "image/png"
}
if urlToken, ok := firstMedia["urlToken"].(string); ok {
res.Url = res.Url + urlToken
}
@@ -230,7 +214,7 @@ func (p *Proxy) handleWechatRequest(r *http.Request, ctx *goproxy.ProxyCtx) (*ht
res.OtherData["wx_file_formats"] = strings.Join(fileFormats, "#")
}
resourceOnce.mark[urlSign] = true
resourceOnce.markMedia(urlSign)
httpServerOnce.send("newResources", res)
}(body)
return r, p.buildEmptyResponse(r)
@@ -265,8 +249,10 @@ func (p *Proxy) httpResponseEvent(resp *http.Response, ctx *goproxy.ProxyCtx) *h
if strings.HasSuffix(host, "res.wx.qq.com") {
respTemp := resp
is := false
if strings.HasSuffix(respTemp.Request.URL.RequestURI(), ".js?v="+p.v()) {
respTemp = p.replaceWxJsContent(respTemp, ".js\"", ".js?v="+p.v()+"\"")
is = true
}
if strings.Contains(Path, "web/web-finder/res/js/virtual_svg-icons-register.publish") {
@@ -308,7 +294,9 @@ func (p *Proxy) httpResponseEvent(resp *http.Response, ctx *goproxy.ProxyCtx) *h
respTemp.Header.Set("Content-Length", fmt.Sprintf("%d", len(newBodyBytes)))
return respTemp
}
return respTemp
if is {
return respTemp
}
}
classify, suffix := TypeSuffix(resp.Header.Get("Content-Type"))
@@ -322,14 +310,11 @@ func (p *Proxy) httpResponseEvent(resp *http.Response, ctx *goproxy.ProxyCtx) *h
}
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) {
if ok := resourceOnce.mediaIsMarked(urlSign); !ok && (isAll || isClassify) {
value, _ := strconv.ParseFloat(resp.Header.Get("content-length"), 64)
id, err := gonanoid.New()
if err != nil {
@@ -351,7 +336,13 @@ func (p *Proxy) httpResponseEvent(resp *http.Response, ctx *goproxy.ProxyCtx) *h
Description: "",
ContentType: resp.Header.Get("Content-Type"),
}
resourceOnce.mark[urlSign] = true
// Store entire request headers as JSON
if headers, err := json.Marshal(resp.Request.Header); err == nil {
res.OtherData["headers"] = string(headers)
}
resourceOnce.markMedia(urlSign)
httpServerOnce.send("newResources", res)
}
return resp

View File

@@ -2,6 +2,8 @@ package core
import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/url"
"os"
@@ -26,8 +28,7 @@ type WxFileDecodeResult struct {
}
type Resource struct {
mark map[string]bool
markMu sync.RWMutex
mediaMark sync.Map
resType map[string]bool
resTypeMu sync.RWMutex
}
@@ -35,7 +36,6 @@ type Resource struct {
func initResource() *Resource {
if resourceOnce == nil {
resourceOnce = &Resource{
mark: make(map[string]bool),
resType: map[string]bool{
"all": true,
"image": true,
@@ -52,17 +52,13 @@ func initResource() *Resource {
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) mediaIsMarked(key string) bool {
_, loaded := r.mediaMark.Load(key)
return loaded
}
func (r *Resource) setMark(key string, value bool) {
r.markMu.Lock()
defer r.markMu.Unlock()
r.mark[key] = value
func (r *Resource) markMedia(key string) {
r.mediaMark.Store(key, true)
}
func (r *Resource) getResType(key string) (bool, bool) {
@@ -93,15 +89,11 @@ func (r *Resource) setResType(n []string) {
}
func (r *Resource) clear() {
r.markMu.Lock()
defer r.markMu.Unlock()
r.mark = make(map[string]bool)
r.mediaMark.Clear()
}
func (r *Resource) delete(sign string) {
r.markMu.Lock()
defer r.markMu.Unlock()
delete(r.mark, sign)
r.mediaMark.Delete(sign)
}
func (r *Resource) download(mediaInfo MediaInfo, decodeStr string) {
@@ -113,13 +105,22 @@ func (r *Resource) download(mediaInfo MediaInfo, decodeStr string) {
fileName := Md5(rawUrl)
if mediaInfo.Description != "" {
fileName = regexp.MustCompile(`[^\w\p{Han}]`).ReplaceAllString(mediaInfo.Description, "")
fileLen := globalConfig.FilenameLen
if fileLen <= 0 {
fileLen = 10
}
runes := []rune(fileName)
if len(runes) > 10 {
fileName = string(runes[:10])
if len(runes) > fileLen {
fileName = string(runes[:fileLen])
}
}
mediaInfo.SavePath = filepath.Join(globalConfig.SaveDirectory, fileName+"_"+GetCurrentDateTimeFormatted()+mediaInfo.Suffix)
if globalConfig.FilenameTime {
mediaInfo.SavePath = filepath.Join(globalConfig.SaveDirectory, fileName+"_"+GetCurrentDateTimeFormatted()+mediaInfo.Suffix)
} else {
mediaInfo.SavePath = filepath.Join(globalConfig.SaveDirectory, fileName+mediaInfo.Suffix)
}
if strings.Contains(rawUrl, "qq.com") {
if globalConfig.Quality == 1 &&
@@ -143,7 +144,9 @@ func (r *Resource) download(mediaInfo MediaInfo, decodeStr string) {
}
}
downloader := NewFileDownloader(rawUrl, mediaInfo.SavePath, globalConfig.TaskNumber)
headers, _ := r.parseHeaders(mediaInfo)
downloader := NewFileDownloader(rawUrl, mediaInfo.SavePath, globalConfig.TaskNumber, headers)
downloader.progressCallback = func(totalDownloaded float64) {
r.progressEventsEmit(mediaInfo, strconv.Itoa(int(totalDownloaded))+"%", DownloadStatusRunning)
}
@@ -163,6 +166,27 @@ func (r *Resource) download(mediaInfo MediaInfo, decodeStr string) {
}(mediaInfo)
}
// 解析并组装 headers
func (r *Resource) parseHeaders(mediaInfo MediaInfo) (map[string]string, error) {
headers := make(map[string]string)
if hh, ok := mediaInfo.OtherData["headers"]; ok {
var tempHeaders map[string][]string
// 解析 JSON 字符串为 map[string][]string
if err := json.Unmarshal([]byte(hh), &tempHeaders); err != nil {
return headers, fmt.Errorf("parse headers JSON err: %v", err)
}
for key, values := range tempHeaders {
if len(values) > 0 {
headers[key] = values[0]
}
}
}
return headers, nil
}
func (r *Resource) wxFileDecode(mediaInfo MediaInfo, fileName, decodeStr string) (string, error) {
sourceFile, err := os.Open(fileName)
if err != nil {

View File

@@ -5,94 +5,99 @@ package core
import (
"bytes"
"fmt"
"log"
"net"
"os/exec"
"strings"
)
func (s *SystemSetup) getActiveInterface() (string, error) {
interfaces, err := net.Interfaces()
func (s *SystemSetup) getNetworkServices() ([]string, error) {
cmd := exec.Command("networksetup", "-listallnetworkservices")
output, err := cmd.Output()
if err != nil {
return "", err
return nil, fmt.Errorf("failed to execute command: %v", err)
}
for _, inter := range interfaces {
if inter.Flags&net.FlagUp != 0 && inter.Flags&net.FlagLoopback == 0 {
return inter.Name, nil
}
}
return "", fmt.Errorf("no active network interface found")
}
services := strings.Split(string(output), "\n")
func (s *SystemSetup) getNetworkServiceName(interfaceName string) (string, error) {
cmd := exec.Command("networksetup", "-listallhardwareports")
var out bytes.Buffer
cmd.Stdout = &out
if err := cmd.Run(); err != nil {
return "", err
var activeServices []string
for _, service := range services {
service = strings.TrimSpace(service)
if service == "" || strings.Contains(service, "*") || strings.Contains(service, "Serial Port") {
continue
}
// 检查服务是否活动
infoCmd := exec.Command("networksetup", "-getinfo", service)
infoOutput, err := infoCmd.Output()
if err != nil {
fmt.Printf("failed to get info for service %s: %v\n", service, err)
continue
}
// 如果输出中包含 "IP address:",说明服务是活动的
if strings.Contains(string(infoOutput), "IP address:") {
activeServices = append(activeServices, service)
}
}
output := out.String()
lines := strings.Split(output, "\n")
var serviceName string
for _, line := range lines {
if strings.Contains(line, "Hardware Port:") {
serviceName = strings.TrimSpace(strings.Split(line, ":")[1])
}
if strings.Contains(line, "Device: "+interfaceName) {
return serviceName, nil
}
if len(activeServices) == 0 {
return nil, fmt.Errorf("no active network services found")
}
return "", fmt.Errorf("no matching network service found for interface %s", interfaceName)
return activeServices, nil
}
func (s *SystemSetup) setProxy() error {
interfaceName, err := s.getActiveInterface()
services, err := s.getNetworkServices()
if err != nil {
return err
}
serviceName, err := s.getNetworkServiceName(interfaceName)
if err != nil {
return err
}
commands := [][]string{
{"networksetup", "-setwebproxy", serviceName, "127.0.0.1", globalConfig.Port},
{"networksetup", "-setsecurewebproxy", serviceName, "127.0.0.1", globalConfig.Port},
}
for _, cmd := range commands {
if err := exec.Command(cmd[0], cmd[1:]...).Run(); err != nil {
return err
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
}
}
return nil
if is {
return nil
}
return fmt.Errorf("failed to set proxy for any active network service")
}
func (s *SystemSetup) unsetProxy() error {
interfaceName, err := s.getActiveInterface()
services, err := s.getNetworkServices()
if err != nil {
return err
}
serviceName, err := s.getNetworkServiceName(interfaceName)
if err != nil {
return err
}
commands := [][]string{
{"networksetup", "-setwebproxystate", serviceName, "off"},
{"networksetup", "-setsecurewebproxystate", serviceName, "off"},
}
for _, cmd := range commands {
if err := exec.Command(cmd[0], cmd[1:]...).Run(); err != nil {
log.Println("UnsetProxy failed:", err)
return err
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
}
}
return nil
if is {
return nil
}
return fmt.Errorf("failed to set proxy for any active network service")
}
func (s *SystemSetup) installCert() (string, error) {
@@ -101,7 +106,7 @@ func (s *SystemSetup) installCert() (string, error) {
return "", err
}
getPasswordCmd := exec.Command("osascript", "-e", `tell app "System Events" to display dialog "请输入密码,用于安装证书:" default answer "" with hidden answer`, "-e", `text returned of result`)
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

View File

@@ -3,7 +3,7 @@
package core
import (
"os"
"fmt"
"os/exec"
)
@@ -15,13 +15,18 @@ func (s *SystemSetup) setProxy() error {
{"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 {
return err
fmt.Println(err)
} else {
is = true
}
}
return nil
if is {
return nil
}
return fmt.Errorf("Failed to activate proxy")
}
func (s *SystemSetup) unsetProxy() error {
@@ -30,21 +35,40 @@ func (s *SystemSetup) unsetProxy() error {
}
func (s *SystemSetup) installCert() (string, error) {
certData, err := s.initCert()
if err != nil {
return "", err
}
destFile := "/usr/local/share/ca-certificates/" + appOnce.AppName + ".crt"
err = os.WriteFile(destFile, certData, 0644)
_, err := s.initCert()
if err != nil {
return "", err
}
cmd := exec.Command("sudo", "update-ca-certificates")
output, err := cmd.CombinedOutput()
if err != nil {
return string(output), 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
}

View File

@@ -4,6 +4,7 @@ import (
"crypto/md5"
"encoding/hex"
"fmt"
"github.com/wailsapp/wails/v2/pkg/runtime"
"net/url"
"os"
"strings"
@@ -14,9 +15,11 @@ func Empty(data interface{}) {
}
func DialogErr(message string) {
httpServerOnce.send("message", map[string]interface{}{
"code": 0,
"message": message,
_, _ = runtime.MessageDialog(appOnce.ctx, runtime.MessageDialogOptions{
Type: runtime.ErrorDialog,
Title: "Error",
Message: message,
DefaultButton: "Cancel",
})
}

0
docs/.nojekyll Normal file
View File

9
docs/_coverpage.md Normal file
View File

@@ -0,0 +1,9 @@
# res-downloader V3
> 全新技术栈,更新、更小、更快、更稳
### 简单、高效、轻便 (仅 ~10M)
[开始使用 Let Go](/readme.md)
[下载](/getting-started.md)

4
docs/_navbar.md Normal file
View File

@@ -0,0 +1,4 @@
* [论坛](https://s.gowas.cn/d/4089)
* [反馈](https://github.com/putyy/res-downloader/issues)
* [日志](https://github.com/putyy/res-downloader/releases)
* [QQ群](https://qm.qq.com/q/ImE37ayJmc)

6
docs/_sidebar.md Normal file
View File

@@ -0,0 +1,6 @@
* [简介](readme.md)
* [快速开始](getting-started.md)
* [安装指南](installation.md)
* [功能演示](examples.md)
* [更多说明](more.md)
* [常见问题](troubleshooting.md)

19
docs/examples.md Normal file
View File

@@ -0,0 +1,19 @@
## 开启代理
- 安装完成后开启代理,如图:
![](images/examples-1.png ':size=50%')
## 拦截资源
### 视频号
- 打开视频号即可看到本软件中拦截到的资源
![](images/examples-2.webp ':size=50%')
### 网页资源
- 浏览器打开或者其他软件内置浏览器打开的网页
- 这里演示打开百度这个网站https://www.baidu.com/
![](images/examples-4.png ':size=50%')
### 小程序、公众号、抖音、小红书、qq音乐、酷狗等应用内资源获取方式都差不多
## 下载资源到本地
- 选择你想下载的视频 点击下载即可
![](images/examples-3.png ':size=50%')

BIN
docs/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

14
docs/getting-started.md Normal file
View File

@@ -0,0 +1,14 @@
## 软件下载
🆕 [github下载](https://github.com/putyy/res-downloader/releases)
🆕 [蓝奏云下载 密码:9vs5](https://wwjv.lanzoum.com/b04wgtfyb)
!> Win7用户请使用2.3.0版本
## 使用方法
- 安装时一定要同意安装证书文件、一定要允许网络访问
- 打开本软件(win系统首次使用管理员打开-鼠标右键选择管理员打开)
- 打开后,左上角点击 “启动代理”
- 打开要捕获的源, 如:视频号、网页、小程序等等
- 返回软件首页即可看到资源列
![](images/show.webp ':size=50%')

BIN
docs/images/config.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
docs/images/examples-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
docs/images/examples-2.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
docs/images/examples-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

BIN
docs/images/examples-4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

BIN
docs/images/more-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

BIN
docs/images/more-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

BIN
docs/images/more-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

BIN
docs/images/more-4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

52
docs/index.html Normal file
View File

@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>res-downloader</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="keywords" content="res-downloader,视频号下载,抖音下载,快手下载,小红书下载,万能下载器,爱享素材,爱享素材下载器">
<meta name="description" content="res-downloader是一款集网络资源嗅探 + 高速下载功能于一体的软件,高颜值、高性能和多样化,提供个人用户下载自己上传到各大平台的网络资源功能!">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/vue.css">
</head>
<body>
<div id="app"></div>
<script>
window.$docsify = {
name: 'res-downloader',
repo: 'https://github.com/putyy/res-downloader',
// 侧边栏支持默认加载的是项目根目录下的_sidebar.md文件
loadSidebar: true,
// 导航栏支持默认加载的是项目根目录下的_navbar.md文件
loadNavbar: true,
// 封面支持默认加载的是项目根目录下的_coverpage.md文件
coverpage: true,
// 最大支持渲染的标题层级
maxLevel: 5,
// 自定义侧边栏后默认不会再生成目录设置生成目录的最大层级建议配置为2-4
subMaxLevel: 4,
// 小屏设备下合并导航栏到侧边栏
mergeNavbar: true,
search: {
maxAge: 86400000,// 过期时间,单位毫秒,默认一天
paths: 'auto',// 注意:仅适用于 paths: 'auto' 模式
placeholder: '搜索',
// 支持本地化
placeholder: {
'/zh-cn/': '搜索',
'/': 'Type to search'
},
noData: '找不到结果',
depth: 4,
hideOtherSidebarContent: false,
namespace: 'Docsify-Guide',
}
}
</script>
<script src="//cdn.jsdelivr.net/npm/docsify@4"></script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/emoji.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/zoom-image.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/search.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify-copy-code/dist/docsify-copy-code.min.js"></script>
</body>
</html>

18
docs/installation.md Normal file
View File

@@ -0,0 +1,18 @@
## 下载安装文件
- windows下载.exe结尾的根据自己的系统架构下载合适的安装文件通常下载带有“x64-installer.exe”结尾的文件
- Mac下载.dmg结尾即可
- Linux根据系统类型下载对应的执行文件或安装文件
## Windows安装过程
- 双击下载好的exe 正常安装即可,首次打开记得右键管理员运行
## Mac安装过程
- 双击下载好的dmg文件将res-downloader拖入应用即可如图:
![installation-mac-1.png](images/installation-mac-1.png ':size=50%')
## Linux安装过程
- 执行文件运行方式举例
> chmod +x ./res-downloader_3.0.2_linux_x64
> sudo ./res-downloader_3.0.2_linux_x64

21
docs/more.md Normal file
View File

@@ -0,0 +1,21 @@
## 隐藏功能
- 导出\导入 数据1秒内连续点击空白处5次即可开启(单次有效)
![more-4.png](images/more-4.png ':size=30%')
## 清空列表、类型筛选
- 当资源列表过大时,无法快速找到需要的资源,这时可以先清空列表再去刷新需要的资源页面
- 资源列表过多,可以快速根据需要的资源类型进行筛选
![more-4.png](images/more-1.png ':size=30%')
## 拦截想要的资源类型、批量下载
- 比如只需要视频时就选择视频类型,可以多选
![more-1.png](images/more-2.png ':size=30%')
## 复制链接、视频解密
- 复制链接可用于第三方软件进行下载,下载完成后对该视频解密,点击“视频解密”选择用其他软件下载完成后的视频文件进行解密
![more-3.png](images/more-3.png ':size=30%')
## 设置说明
!> 修改完成后记得点保存
- 几乎每项配置在软件中都有说明(鼠标悬浮在?处即可查看),此处就不进行多余讲解
![config.png](images/config.png ':size=30%')

15
docs/readme.md Normal file
View File

@@ -0,0 +1,15 @@
# res-downloader
## 爱享素材下载器
🎯 基于Go + [wails](https://github.com/wailsapp/wails)
📦 操作简单、可获取不同类型的网络资源
🖥️ 支持Windows、Mac、Linux
🌐 支持视频、音频、图片、m3u8、直播流等常见网络资源
💪 支持微信视频号、小程序、抖音、快手、小红书、酷狗音乐、qq音乐等网络资源下载
👼 支持设置代理以获取特殊网络下的资源
## 实现 & 初衷
?> 通过设置系统网络代理拦截响应,筛选出需要的资源, 同fiddler、charles等抓包软件、浏览器F12打开控制也能达到目的只不过这些软件需要手动进行筛选对于小白用户上手还是有点难度本软件对部分资源做了特殊处理更适合大众用户所以就有了本项目。
## 免责声明
?> 本软件用于学习研究使用,若因使用本软件造成的一切法律责任均与本人无关!

46
docs/troubleshooting.md Normal file
View File

@@ -0,0 +1,46 @@
## Mac 提示“已损坏,无法打开”, 打开命令行执行如下命令:
> sudo xattr -d com.apple.quarantine /Applications/res-downloader.app
## 打开本软件,无法正常拦截获取
> 检查系统证书是否安装
> 关闭网络防火墙
> 系统代理是否正确设置(代理地址127.0.0.1 端口8899)
## 关闭软件后无法正常上网
> 手动关闭系统代理设置
## 链接不是私密链接
> 通常是证书未正确安装,最新版证书下载:软件左下角?点击后有下载地址
> 根据自己系统进行安装证书操作(不懂的自行百度),手动安装需安装到受信任的根证书
- Mac手动安装证书(V3+版本支持),打开终端复制以下命令 粘贴到终端回车 按照提示输入密码,完成后再打开软件:
```shell
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain /Users/$(whoami)/Library/Preferences/res-downloader/cert.crt && touch /Users/$(whoami)/Library/Preferences/res-downloader/install.lock && echo "安装完成"
```
## 拦截不到小程序中的资源
清理微信缓存,删除小程序后,重新打开
> 设置->存储空间->缓存
## 只拦截打开的视频号视频
关闭全量拦截,打开视频号视频详情,通常分享好友后打开的页面属于详情页
## 拦截视频号账号视频
打开对应作者个人主页,浏览即可
## 下载慢、大视频下载失败
推荐使用如下工具加速下载,视频号可以下载完成后再到对应视频操作项选择 “视频解密” 按钮
> [Neat Download Manager](https://www.neatdownloadmanager.com/index.php/en/)、[Motrix](https://motrix.app/download)等软件进行下载
## 直播流: 预览和录制:
> [使用obs进行预览和录制 使用教程自行百度, 点击下载obs]( https://obsproject.com/)
## m3u8: 预览和下载:
> [在线下载](https://m3u8-down.gowas.cn/)、[在线预览](https://m3u8play.com/)
## 安装证书后还会提示安装
使用命令行打开本软件,查看 “lockfile:” 这串字符后面的锁文件路径,然后创建该文件即可
例如 mac系统下终端执行如下命令即可创建
> touch /Users/你的用户名/Library/Preferences/res-downloader/install.lock
## 更多问题 请前往github进行[反馈](https://github.com/putyy/res-downloader/issues)

View File

@@ -8,6 +8,7 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
Footer: typeof import('./src/components/Footer.vue')['default']
ImportJson: typeof import('./src/components/ImportJson.vue')['default']
Index: typeof import('./src/components/layout/Index.vue')['default']
NaiveProvider: typeof import('./src/components/NaiveProvider.vue')['default']
NButton: typeof import('naive-ui')['NButton']

BIN
frontend/src/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -12,9 +12,9 @@
import NaiveProvider from '@/components/NaiveProvider.vue'
import {darkTheme, lightTheme, zhCN} from 'naive-ui'
import {useIndexStore} from "@/stores"
import {computed, onMounted, watch} from "vue"
import type {wsType} from "@/types/ws"
import {computed, onMounted} from "vue"
import {useEventStore} from "@/stores/event"
import {appType} from "@/types/app";
const store = useIndexStore()
const eventStore = useEventStore()
@@ -33,7 +33,7 @@ onMounted(async () => {
eventStore.init()
eventStore.addHandle({
type: "message",
event: (res: wsType.Message)=>{
event: (res: appType.Message)=>{
switch (res?.code) {
case 0:
window?.$message?.error(res.message)

View File

@@ -47,6 +47,7 @@
<div>{{ store.appInfo.Copyright }}</div>
<div class="flex">
<button class="pl-4" @click="toWebsite('https://s.gowas.cn/d/4089')">论坛</button>
<button class="pl-4" @click="toWebsite('http://127.0.0.1:8899/cert')">证书</button>
<button class="pl-4" @click="toWebsite('https://github.com/putyy/res-downloader')">软件源码</button>
<button class="pl-4" @click="toWebsite('https://github.com/putyy/res-downloader/issues')">帮助支持</button>
<button class="pl-4" @click="toWebsite('https://github.com/putyy/res-downloader/releases')">更新日志</button>
@@ -58,7 +59,7 @@
<script lang="ts" setup>
import {useIndexStore} from "@/stores"
import {BrowserOpenURL} from "../../wailsjs/runtime";
import {BrowserOpenURL} from "../../wailsjs/runtime"
const store = useIndexStore()
const props = defineProps(["showModal"])
const emits = defineEmits(["update:showModal"])

View File

@@ -0,0 +1,36 @@
<template>
<NModal
:show="showModal"
:on-update:show="changeShow"
style="--wails-draggable:no-drag"
preset="card"
class="w-[640px]"
title="导入数据"
>
<NForm
size="medium"
label-placement="left"
label-width="auto"
require-mark-placement="right-hanging"
style="--wails-draggable:no-drag"
>
<NFormItem>
<NInput type="textarea" v-model:value="content" rows="8" :autosize="false" placeholder="添加多个时,请确保每行只有一个(每个链接回车换行)"></NInput>
</NFormItem>
<NFormItem>
<NButton strong secondary type="success" @click="emits('submit', content)" class="w-20">提交</NButton>
</NFormItem>
</NForm>
</NModal>
</template>
<script setup lang="ts">
import {ref} from "vue"
const content = ref("")
const props = defineProps<{
showModal: boolean
}>()
const emits = defineEmits(["update:showModal", "submit"])
const changeShow = (value: boolean) => emits("update:showModal", value)
</script>

View File

@@ -12,6 +12,9 @@
<NButton v-if="row.DecodeKey" type="warning" :tertiary="true" size="small" @click="action('decode')">
视频解密
</NButton>
<NButton v-if="isDebug" type="info" :tertiary="true" size="small" @click="action('json')">
复制数据
</NButton>
<NButton type="error" :tertiary="true" size="small" @click="action('delete')">
删除
</NButton>
@@ -19,6 +22,7 @@
</template>
<script setup lang="ts">
import {inject} from "vue"
const props = defineProps<{
row: any,
@@ -27,6 +31,8 @@ const props = defineProps<{
const emits = defineEmits(["action"])
const isDebug = inject('isDebug')
const action = (type: string) => {
emits('action', props.row, props.index, type)
}

View File

@@ -35,7 +35,6 @@ const minimizeWindow = () => {
WindowMinimise()
}
const maximizeWindow = () => {
console.log("maximizeWindow")
isMaximized.value = !isMaximized.value;
if (isMaximized.value) {
WindowFullscreen()

View File

@@ -47,7 +47,7 @@
</template>
<script lang="ts" setup>
import type {MenuOption} from "naive-ui"
import {MenuOption} from "naive-ui"
import {NIcon} from "naive-ui"
import {computed, h, ref, watch} from "vue"
import {useRoute, useRouter} from "vue-router"
@@ -55,11 +55,12 @@ import {
CloudOutline,
SettingsOutline,
HelpCircleOutline,
MoonOutline
MoonOutline, SunnyOutline, LogoGithub
} from "@vicons/ionicons5"
import {useIndexStore} from "@/stores"
import Footer from "@/components/Footer.vue"
import Screen from "@/components/Screen.vue";
import {BrowserOpenURL} from "../../../wailsjs/runtime";
const route = useRoute()
const router = useRouter()
@@ -75,6 +76,10 @@ const globalConfig = computed(()=>{
return store.globalConfig
})
const theme = computed(() => {
return store.globalConfig.Theme === "darkTheme" ? renderIcon(SunnyOutline) : renderIcon(MoonOutline)
})
watch(() => route.path, (newPath, oldPath) => {
menuValue.value = route.fullPath.substring(1)
});
@@ -100,7 +105,12 @@ const footerOptions = ref([
{
label: "主题",
key: 'theme',
icon: renderIcon(MoonOutline),
icon: theme,
},
{
label: "github",
key: 'github',
icon: renderIcon(LogoGithub),
},
{
label: "关于",
@@ -118,6 +128,11 @@ const handleFooterUpdate = (key: string, item: MenuOption) => {
showAppInfo.value = true
return
}
if (key === "github") {
BrowserOpenURL("https://github.com/putyy/res-downloader")
return
}
if (key === "theme") {
if (globalConfig.value.Theme === "darkTheme") {
store.setConfig(Object.assign({}, globalConfig.value, {Theme: "lightTheme"}))

View File

@@ -1,8 +1,6 @@
export const DwStatus = {
ready: "就绪",
running: "运行中",
pause: "暂停",
wait: "等待",
error: "错误",
done: "完成",
handle: "已下载,后续处理",

View File

@@ -1,7 +1,7 @@
import {defineStore} from "pinia"
import {ref} from "vue"
import type {wsType} from "@/types/ws"
import {EventsOn} from "../../wailsjs/runtime"
import {appType} from "@/types/app"
export const useEventStore = defineStore('ws-store', () => {
const handles = ref<any>({})
@@ -17,7 +17,7 @@ export const useEventStore = defineStore('ws-store', () => {
})
}
const addHandle = (handle: wsType.Handle) => {
const addHandle = (handle: appType.Handle) => {
handles.value[handle.type] = handle.event
}

View File

@@ -2,7 +2,7 @@ import {defineStore} from 'pinia'
import {ref} from "vue"
import type {appType} from "@/types/app"
import appApi from "@/api/app"
import {Environment} from "../../wailsjs/runtime";
import {Environment} from "../../wailsjs/runtime"
export const useIndexStore = defineStore("index-store", () => {
const appInfo = ref<appType.App>({
@@ -19,12 +19,15 @@ export const useIndexStore = defineStore("index-store", () => {
Quality: 0,
SaveDirectory: "",
UpstreamProxy: "",
FilenameLen: 0,
FilenameTime: false,
OpenProxy: false,
DownloadProxy: false,
AutoProxy: false,
WxAction: false,
TaskNumber: 8,
UserAgent: "",
UseHeaders: "",
})
const envInfo = ref({

View File

@@ -12,6 +12,8 @@ export namespace appType {
Port: string
Quality: number
SaveDirectory: string
FilenameLen: number
FilenameTime: boolean
UpstreamProxy: string
OpenProxy: boolean
DownloadProxy: boolean
@@ -19,6 +21,7 @@ export namespace appType {
WxAction: boolean
TaskNumber: number
UserAgent: string
UseHeaders: string
}
interface MediaInfo {
@@ -44,4 +47,14 @@ export namespace appType {
Status: string
Message: string
}
interface Message {
code: number
message: string
}
interface Handle {
type: string
event: any
}
}

View File

@@ -1,21 +0,0 @@
export namespace wsType {
interface Event {
type: string
event: any
data: any
}
interface Handle {
type: string
event: any
}
interface Message {
code: number
message: string
}
interface Clipboard {
content: string
}
}

View File

@@ -1,12 +1,13 @@
<template>
<div class="flex flex-col px-5 py-5">
<div class="pb-2 z-40">
<div class="pb-2 z-40" @click="triggerEvent">
<NSpace>
<NButton v-if="isProxy" secondary type="primary" @click="close" style="--wails-draggable:no-drag">关闭代理</NButton>
<NButton v-else tertiary type="tertiary" @click="open" style="--wails-draggable:no-drag">开启代理</NButton>
<NButton tertiary type="info" @click="batchDown" style="--wails-draggable:no-drag">批量下载</NButton>
<NButton tertiary type="error" @click="clear" style="--wails-draggable:no-drag">清空列表</NButton>
<NButton v-if="isProxy" secondary type="primary" @click.stop="close" style="--wails-draggable:no-drag">关闭代理</NButton>
<NButton v-else tertiary type="tertiary" @click.stop="open" style="--wails-draggable:no-drag">开启代理</NButton>
<NButton tertiary type="info" @click.stop="batchDown" style="--wails-draggable:no-drag">批量下载</NButton>
<NButton tertiary type="error" @click.stop="clear" style="--wails-draggable:no-drag">清空列表</NButton>
<NSelect style="min-width: 100px;--wails-draggable:no-drag" placeholder="拦截类型" v-model:value="resourcesType" multiple clearable :max-tag-count="3" :options="options"></NSelect>
<NButton v-if="isDebug" tertiary type="info" @click.stop="showImport=true" style="--wails-draggable:no-drag">导入数据</NButton>
</NSpace>
</div>
<div class="flex-1">
@@ -25,12 +26,13 @@
</div>
<Preview v-model:showModal="showPreviewRow" :previewRow="previewRow"/>
<ShowLoading :loadingText="loadingText" :isLoading="loading"/>
<ImportJson v-model:showModal="showImport" @submit="handleImport"/>
</div>
</template>
<script lang="ts" setup>
import {NButton, NImage, NTooltip} from "naive-ui"
import {computed, h, onMounted, ref, watch} from "vue"
import {computed, h, onMounted, ref, watch, provide} from "vue"
import type {appType} from "@/types/app"
import type {DataTableRowKey, ImageRenderToolbarProps} from "naive-ui"
@@ -40,25 +42,21 @@ import ShowLoading from "@/components/ShowLoading.vue"
import {getDecryptionArray} from '@/assets/js/decrypt.js'
import {useIndexStore} from "@/stores"
import appApi from "@/api/app"
import {DwStatus} from "@/const";
import {DwStatus} from "@/const"
import ResAction from "@/components/ResAction.vue"
import {useEventStore} from "@/stores/event";
import {BrowserOpenURL, ClipboardSetText} from "../../wailsjs/runtime";
import ImportJson from "@/components/ImportJson.vue"
import {useEventStore} from "@/stores/event"
import {BrowserOpenURL, ClipboardSetText} from "../../wailsjs/runtime"
const eventStore = useEventStore()
const isProxy = computed(() => {
return store.isProxy
})
const data = ref<any[]>([])
const store = useIndexStore()
const tableHeight = computed(() => {
return store.tableHeight - 132
})
const resourcesType = ref<string[]>(["all"])
const options = [
{
@@ -216,12 +214,17 @@ const columns = ref<any[]>([
}
])
const downIndex = ref(0)
const checkedRowKeysValue = ref<DataTableRowKey[]>([])
const showPreviewRow = ref(false)
const previewRow = ref<appType.MediaInfo>()
const loading = ref(false)
const loadingText = ref("")
const isDebug = ref(false)
const showImport = ref(false)
let clickCount = 0
let clickTimeout: any = null
provide('isDebug', isDebug);
onMounted(() => {
const temp = localStorage.getItem("resources-type")
@@ -297,6 +300,15 @@ const dataAction = (row: appType.MediaInfo, index: number, type: string) => {
}
})
break
case "json":
ClipboardSetText(encodeURIComponent(JSON.stringify(row))).then((is: boolean) => {
if (is) {
window?.$message?.success("复制成功")
} else {
window?.$message?.error("复制失败")
}
})
break
case "open":
BrowserOpenURL(row.Url)
break;
@@ -444,4 +456,39 @@ const decodeWxFile = (row: appType.MediaInfo, index: number) => {
})
}
const triggerEvent = ()=>{
if(isDebug.value) {
return
}
clickCount++
if (clickCount === 5) {
// 连续点击5次开启debug
isDebug.value = true
clickCount = 0
} else {
clearTimeout(clickTimeout);
clickTimeout = setTimeout(() => {
clickCount = 0
}, 1000)
}
}
const handleImport = (content: string)=>{
content.split("\n").forEach((line, index) => {
try {
let res = JSON.parse(decodeURIComponent(line))
if (res && res?.Id) {
res.Id = res.Id + Math.floor(Math.random() * 100000)
res.SavePath = ""
res.Status = "ready"
data.value.unshift(res)
}
}catch (e) {
console.log(e)
}
});
localStorage.setItem("resources-data", JSON.stringify(data.value))
showImport.value = false
}
</script>

View File

@@ -10,7 +10,7 @@
class="px-5 py-5"
>
<NFormItem label="代理Host" path="Port" size="small">
<NInput v-model:value="formValue.Host" placeholder="0.0.0.0" style="width:256px"/>
<NInput v-model:value="formValue.Host" placeholder="127.0.0.1" style="width:256px"/>
<NTooltip trigger="hover">
<template #trigger>
<NIcon size="20" class="pl-1">
@@ -33,10 +33,29 @@
</NFormItem>
<NFormItem label="保存位置" path="SaveDirectory" size="small">
<NSpace>
<NInput v-model:value="formValue.SaveDirectory" disabled placeholder="保存位置" style="width:256px"/>
<NInput :value="formValue.SaveDirectory" placeholder="保存位置" style="width:256px"/>
<NButton strong secondary type="success" @click="selectDir">选择</NButton>
</NSpace>
</NFormItem>
<NFormItem label="文件命名" path="FilenameLen" size="small">
<NInputNumber v-model:value="formValue.FilenameLen" :min="0" :max="9999" placeholder="0" style="width:256px"/>
<NSwitch class="pl-1" v-model:value="formValue.FilenameTime" aria-placeholder="随机数">
<template #checked>
</template>
<template #unchecked>
</template>
</NSwitch>
<NTooltip trigger="hover">
<template #trigger>
<NIcon size="20" class="pl-1">
<HelpCircleOutline />
</NIcon>
</template>
<span>输入框控制文件命名的长度(不含时间0为无效此选项有描述信息时有效)开关控制文件末尾是否添加时间标识</span>
</NTooltip>
</NFormItem>
<NFormItem label="主题" path="theme" size="small">
<NRadio :checked="formValue.Theme === 'lightTheme'" value="lightTheme" name="theme" @change="handleChange">浅色主题</NRadio>
<NRadio :checked="formValue.Theme === 'darkTheme'" value="darkTheme" name="theme" @change="handleChange">深色主题</NRadio>
@@ -76,6 +95,7 @@
</NFormItem>
<NFormItem label="上游代理" path="UpstreamProxy" size="small">
<NInput v-model:value="formValue.UpstreamProxy" placeholder="例如: http://127.0.0.1:7890" style="width:256px"/>
<NSwitch class="pl-1" v-model:value="formValue.OpenProxy" />
<NTooltip trigger="hover">
<template #trigger>
<NIcon size="20" class="pl-1">
@@ -85,9 +105,6 @@
<span>可结合其他代理工具用于访问国外网站以及正常网络无法访问的资源(格式http://username:password@your.proxy.server:port)</span>
</NTooltip>
</NFormItem>
<NFormItem label="开启上游代理" path="OpenProxy" size="small">
<NSwitch v-model:value="formValue.OpenProxy" />
</NFormItem>
<NFormItem label="下载代理" path="DownloadProxy" size="small">
<NSwitch v-model:value="formValue.DownloadProxy" />
<NTooltip trigger="hover">
@@ -101,6 +118,14 @@
</NFormItem>
<NFormItem label="连接数" path="TaskNumber" size="small">
<NInputNumber v-model:value="formValue.TaskNumber" :min="2" :max="64" class="w-64"/>
<NTooltip trigger="hover">
<template #trigger>
<NIcon size="20" class="pl-1">
<HelpCircleOutline />
</NIcon>
</template>
<span>如不清楚请保持默认通常CPU核心数*2用于分片下载</span>
</NTooltip>
</NFormItem>
<NFormItem label="UserAgent" path="UserAgent" size="small">
<NInput v-model:value="formValue.UserAgent" style="width:256px" placeholder=""/>
@@ -113,8 +138,16 @@
<span>如不清楚请保持默认</span>
</NTooltip>
</NFormItem>
<NFormItem label=" " path="UserAgent" size="small">
<NButton strong secondary type="success" @click="save" class="w-20">保存</NButton>
<NFormItem label="UseHeaders" path="UseHeaders" size="small">
<NInput v-model:value="formValue.UseHeaders" style="width:256px" placeholder=""/>
<NTooltip trigger="hover">
<template #trigger>
<NIcon size="20" class="pl-1">
<HelpCircleOutline />
</NIcon>
</template>
<span>3.0.4版本缓存了请求header信息这个参数定义在下载时可使用的header参数: ,分割</span>
</NTooltip>
</NFormItem>
</NForm>
</div>
@@ -150,6 +183,10 @@ const options = [
const formValue = ref<appType.Config>(Object.assign({}, store.globalConfig))
watch(formValue.value, () => {
store.setConfig(Object.assign({}, store.globalConfig, formValue.value))
}, {deep: true})
watch(()=>{
return store.globalConfig.Theme
}, ()=>{
@@ -169,9 +206,4 @@ const selectDir = () => {
window?.$message?.error(err)
});
}
const save = () => {
store.setConfig(formValue.value)
window?.$message?.success("保存成功")
}
</script>

View File

@@ -134,7 +134,7 @@ export function WindowIsFullscreen(): Promise<boolean>;
// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize)
// Sets the width and height of the window.
export function WindowSetSize(width: number, height: number): Promise<Size>;
export function WindowSetSize(width: number, height: number): void;
// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize)
// Gets the width and height of the window.

43
go.mod
View File

@@ -1,45 +1,42 @@
module res-downloader
go 1.21
go 1.22.0
toolchain go1.23.2
require (
github.com/elazarl/goproxy v0.0.0-20241221210044-9faedc2f9e9f
github.com/elazarl/goproxy v0.0.0-20241223171911-d5978cb8c956
github.com/matoous/go-nanoid/v2 v2.1.0
github.com/rs/zerolog v1.33.0
github.com/vrischmann/userdir v0.0.0-20151206171402-20f291cebd68
github.com/wailsapp/wails/v2 v2.9.2
golang.org/x/sys v0.28.0
github.com/wailsapp/wails/v2 v2.10.1
golang.org/x/sys v0.30.0
)
require (
github.com/bep/debounce v1.2.1 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
github.com/labstack/echo/v4 v4.10.2 // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/leaanthony/go-ansi-parser v1.6.0 // indirect
github.com/leaanthony/gosod v1.0.3 // indirect
github.com/labstack/echo/v4 v4.13.3 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
github.com/leaanthony/gosod v1.0.4 // indirect
github.com/leaanthony/slicer v1.6.0 // indirect
github.com/leaanthony/u v1.1.0 // indirect
github.com/leaanthony/u v1.1.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/samber/lo v1.38.1 // indirect
github.com/tkrajina/go-reflector v0.5.6 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/samber/lo v1.49.1 // indirect
github.com/tkrajina/go-reflector v0.5.8 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/wailsapp/go-webview2 v1.0.16 // indirect
github.com/wailsapp/go-webview2 v1.0.19 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/crypto v0.33.0 // indirect
golang.org/x/net v0.35.0 // indirect
golang.org/x/text v0.22.0 // indirect
)
// replace github.com/wailsapp/wails/v2 v2.9.2 => /Users/wgc/go/pkg/mod

100
go.sum
View File

@@ -1,106 +1,92 @@
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/elazarl/goproxy v0.0.0-20241221210044-9faedc2f9e9f h1:88OAVe185Z0YRj/KtiYdFxrEfGBlWBd6cspLJJvgMKo=
github.com/elazarl/goproxy v0.0.0-20241221210044-9faedc2f9e9f/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/elazarl/goproxy v0.0.0-20241223171911-d5978cb8c956 h1:HyPt0ZkHkpke+HFl/4dDMz55A/AjFn7ZnLSm8GfdnwU=
github.com/elazarl/goproxy v0.0.0-20241223171911-d5978cb8c956/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M=
github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k=
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc=
github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
github.com/leaanthony/go-ansi-parser v1.6.0 h1:T8TuMhFB6TUMIUm0oRrSbgJudTFw9csT3ZK09w0t4Pg=
github.com/leaanthony/go-ansi-parser v1.6.0/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
github.com/leaanthony/gosod v1.0.3 h1:Fnt+/B6NjQOVuCWOKYRREZnjGyvg+mEhd1nkkA04aTQ=
github.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4=
github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A=
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
github.com/leaanthony/gosod v1.0.4 h1:YLAbVyd591MRffDgxUOU1NwLhT9T1/YiwjKZpkNFeaI=
github.com/leaanthony/gosod v1.0.4/go.mod h1:GKuIL0zzPj3O1SdWQOdgURSuhkF+Urizzxh26t9f1cw=
github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js=
github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8=
github.com/leaanthony/u v1.1.0 h1:2n0d2BwPVXSUq5yhe8lJPHdxevE2qK5G99PMStMZMaI=
github.com/leaanthony/u v1.1.0/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M=
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
github.com/matoous/go-nanoid/v2 v2.1.0 h1:P64+dmq21hhWdtvZfEAofnvJULaRR1Yib0+PnU669bE=
github.com/matoous/go-nanoid/v2 v2.1.0/go.mod h1:KlbGNQ+FhrUNIHUxZdL63t7tl4LaPkZNpUULS8H4uVM=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQSepKdE=
github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ=
github.com/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/vrischmann/userdir v0.0.0-20151206171402-20f291cebd68 h1:Ah2/69Z24rwD6OByyOdpJDmttftz0FTF8Q4QZ/SF1E4=
github.com/vrischmann/userdir v0.0.0-20151206171402-20f291cebd68/go.mod h1:EqKqAeKddSL9XSGnfXd/7iLncccKhR16HBKVva7ENw8=
github.com/wailsapp/go-webview2 v1.0.16 h1:wffnvnkkLvhRex/aOrA3R7FP7rkvOqL/bir1br7BekU=
github.com/wailsapp/go-webview2 v1.0.16/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo=
github.com/wailsapp/go-webview2 v1.0.19 h1:7U3QcDj1PrBPaxJNCui2k1SkWml+Q5kvFUFyTImA6NU=
github.com/wailsapp/go-webview2 v1.0.19/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
github.com/wailsapp/wails/v2 v2.9.2 h1:Xb5YRTos1w5N7DTMyYegWaGukCP2fIaX9WF21kPPF2k=
github.com/wailsapp/wails/v2 v2.9.2/go.mod h1:uehvlCwJSFcBq7rMCGfk4rxca67QQGsbg5Nm4m9UnBs=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
github.com/wailsapp/wails/v2 v2.10.1 h1:QWHvWMXII2nI/nXz77gpPG8P3ehl6zKe+u4su5BWIns=
github.com/wailsapp/wails/v2 v2.10.1/go.mod h1:zrebnFV6MQf9kx8HI4iAv63vsR5v67oS7GTEZ7Pz1TY=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -23,9 +23,12 @@ var assets embed.FS
//go:embed build/appicon.png
var icon []byte
//go:embed wails.json
var wailsJson string
func main() {
// Create an instance of the app structure
app := core.GetApp(assets)
app := core.GetApp(assets, wailsJson)
isMac := runtime.GOOS == "darwin"
// menu
appMenu := menu.NewMenu()
@@ -58,6 +61,8 @@ func main() {
|_| \___| |___/ \__,_| \___/ \_/\_/ |_| |_| |_| \___/ \__ ,_| \__,_| \___| |_|`
log.Println(logo)
fmt.Println("version:", app.Version)
fmt.Println("lockfile:", app.LockFile)
app.Startup(ctx)
},
OnShutdown: func(ctx context.Context) {

View File

@@ -13,8 +13,8 @@
"info": {
"companyName": "res-downloader",
"productName": "res-downloader",
"productVersion": "3.0.0",
"productVersion": "3.0.5",
"copyright": "Copyright © 2023",
"comments": "This is a high-value, high-performance, and diverse resource downloader called res-downloader."
"comments": "This is a high-value high-performance and diverse resource downloader called res-downloader."
}
}