mirror of
https://github.com/putyy/res-downloader.git
synced 2026-01-12 14:14:55 +08:00
Compare commits
14 Commits
3.0.3
...
04e4f0e9cc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04e4f0e9cc | ||
|
|
625cfbc474 | ||
|
|
d8857bd4a2 | ||
|
|
a247c708f6 | ||
|
|
85781a150a | ||
|
|
6086bd7086 | ||
|
|
0bb1a21a76 | ||
|
|
db21134550 | ||
|
|
3407490f82 | ||
|
|
dad06f6cd6 | ||
|
|
de70fc66b4 | ||
|
|
2282f72b2f | ||
|
|
e00a7c9044 | ||
|
|
e79a7ba2fe |
44
.github/ISSUE_TEMPLATE /bug_report.yml
vendored
44
.github/ISSUE_TEMPLATE /bug_report.yml
vendored
@@ -1,44 +0,0 @@
|
||||
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 '...' \ 1. 进入 '...'
|
||||
2. Click on '....' \ 2. 点击 '....'
|
||||
3. Scroll down to '....' \ 3. 向下滚动到 '....'
|
||||
4. See error \ 4. 查看错误
|
||||
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
|
||||
31
.github/ISSUE_TEMPLATE /feature_request.yml
vendored
31
.github/ISSUE_TEMPLATE /feature_request.yml
vendored
@@ -1,31 +0,0 @@
|
||||
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
|
||||
42
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
42
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal 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
|
||||
31
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
31
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal 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
|
||||
104
README-EN.md
Normal file
104
README-EN.md
Normal file
@@ -0,0 +1,104 @@
|
||||
<div align="center">
|
||||
|
||||
<a href="https://github.com/putyy/res-downloader"><img src="build/appicon.png" width="120"/></a>
|
||||
<h1>res-downloader</h1>
|
||||
<h4>📖 English | <a href="https://github.com/putyy/res-downloader/blob/master/README.md">中文</a></h4>
|
||||
|
||||
[](https://github.com/putyy/res-downloader/stargazers)
|
||||
[](https://github.com/putyy/res-downloader/fork)
|
||||
[](https://github.com/putyy/res-downloader/releases)
|
||||

|
||||
[](https://github.com/putyy/res-downloader/blob/master/LICENSE)
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
### 🎉 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.
|
||||
|
||||
## ✨ 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`*
|
||||
|
||||
|
||||
## 🖼️ Preview
|
||||
|
||||

|
||||
|
||||
## 🚀 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
|
||||
|
||||
---
|
||||
|
||||
## ❓ 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.
|
||||
126
README.md
126
README.md
@@ -1,51 +1,101 @@
|
||||
## res-downloader
|
||||
### 爱享素材下载器
|
||||
<div align="center">
|
||||
|
||||
🎯 基于Go + [wails](https://github.com/wailsapp/wails)
|
||||
📦 操作简单、可获取不同类型资源
|
||||
🖥️ 支持Windows、Mac、Linux
|
||||
🌐 支持视频、音频、图片、m3u8、直播流等常见网络资源
|
||||
💪 支持微信视频号、小程序、抖音、快手、小红书、酷狗音乐、qq音乐等网络资源下载
|
||||
👼 支持设置代理以获取特殊网络下的资源
|
||||
<a href="https://github.com/putyy/res-downloader"><img src="build/appicon.png" width="120"/></a>
|
||||
<h1>res-downloader</h1>
|
||||
<h4>📖 中文 | <a href="https://github.com/putyy/res-downloader/blob/master/README-EN.md">English</a></h4>
|
||||
|
||||
## [在线文档](https://res.putyy.com/)、[加入群聊](https://qm.qq.com/q/ImE37ayJmc)、[Electron版](https://github.com/putyy/res-downloader/tree/old)
|
||||
[](https://github.com/putyy/res-downloader/stargazers)
|
||||
[](https://github.com/putyy/res-downloader/fork)
|
||||
[](https://github.com/putyy/res-downloader/releases)
|
||||

|
||||
[](https://github.com/putyy/res-downloader/blob/master/LICENSE)
|
||||
|
||||
## 软件下载(Win7下载2.3.0版本)
|
||||
🆕 [github下载](https://github.com/putyy/res-downloader/releases)
|
||||
🆕 [蓝奏云下载 密码:9vs5](https://wwjv.lanzoum.com/b04wgtfyb)
|
||||
</div>
|
||||
|
||||
## 使用方法
|
||||
> 0. 安装时一定要同意安装证书文件、一定要允许网络访问
|
||||
> 1. 打开本软件 软件首页左上角点击 “启动代理”
|
||||
> 2. 软件首页选择要获取的资源类型(默认选中的全部)
|
||||
> 3. 打开要捕获的源, 如:视频号、网页、小程序等等
|
||||
> 4. 返回软件首页即可看到资源列表
|
||||
---
|
||||
|
||||
## 软件截图
|
||||

|
||||
### 🎉 爱享素材下载器
|
||||
|
||||
## 常见问题
|
||||
m3u8: 预览和下载:
|
||||
> [下载](https://m3u8-down.gowas.cn/) [预览](https://m3u8play.com/)
|
||||
> 一款基于 Go + [Wails](https://github.com/wailsapp/wails) 的跨平台资源下载工具,简洁易用,支持多种资源嗅探与下载。
|
||||
|
||||
直播流: 预览和录制:
|
||||
> [使用obs进行预览和录制 使用教程自行百度]( https://obsproject.com/)
|
||||
## ✨ 功能特色
|
||||
|
||||
下载慢、大视频下载失败
|
||||
> 推荐使用如下工具加速下载,视频号可以下载完成后再到对应视频操作项选择 “视频解密(视频号)” 按钮
|
||||
>> [Neat Download Manager](https://www.neatdownloadmanager.com/index.php/en/)、[Motrix](https://motrix.app/download)等软件进行下载
|
||||
- 🚀 **简单易用**:操作简单,界面清晰美观
|
||||
- 🖥️ **多平台支持**:Windows / macOS / Linux
|
||||
- 🌐 **多资源类型支持**:视频 / 音频 / 图片 / m3u8 / 直播流等
|
||||
- 📱 **平台兼容广泛**:支持微信视频号、小程序、抖音、快手、小红书、酷狗音乐、QQ音乐等
|
||||
- 🌍 **代理抓包**:支持设置代理获取受限网络下的资源
|
||||
|
||||
打开本软件,无法正常拦截获取
|
||||
> 检查系统代理是否正确设置 代理地址:127.0.0.1 端口:8899
|
||||
## 📚 文档 & 版本
|
||||
|
||||
关闭软件后无法正常上网
|
||||
> 手动关闭系统代理设置
|
||||
- 📘 [在线文档](https://res.putyy.com/)
|
||||
- 💬 [加入交流群](https://www.putyy.com/app/admin/upload/img/20250418/6801d9554dc7.webp)
|
||||
- 🧩 [最新版](https://github.com/putyy/res-downloader/releases) | [Mini版 使用默认浏览器展示UI](https://github.com/putyy/resd-mini) | [Electron旧版 支持Win7](https://github.com/putyy/res-downloader/tree/old)
|
||||
> *群满时可加微信 `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. 返回软件首页,即可看到资源列表
|
||||
|
||||
## ❓ 常见问题
|
||||
|
||||
### 📺 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 原理类似,但对资源进行了更友好的筛选、展示和处理,大幅度降低了使用门槛,更适合大众用户使用。
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 免责声明
|
||||
|
||||
> 本软件仅供学习与研究用途,禁止用于任何商业或违法用途。
|
||||
如因此产生的任何法律责任,概与作者无关!
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
```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).dmg"
|
||||
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 && mv -f "build/bin/res-downloader-amd64-installer.exe" "build/bin/res-downloader_$(jq -r '.info.productVersion' wails.json)_win_x64.exe"
|
||||
wails build -f -nsis -platform "windows/arm64" -webview2 Embed && mv -f "build/bin/res-downloader-arm64-installer.exe" "build/bin/res-downloader_$(jq -r '.info.productVersion' wails.json)_win_arm.exe"
|
||||
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"
|
||||
```
|
||||
|
||||
## Linux
|
||||
@@ -20,12 +20,12 @@ docker build --network host -f build/linux/dockerfile -t res-downloader-amd-linu
|
||||
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
|
||||
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)_x64.deb
|
||||
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/
|
||||
@@ -39,7 +39,9 @@ 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
|
||||
./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
|
||||
@@ -49,12 +51,14 @@ docker build --platform linux/arm64 --network host -f build/linux/dockerfile -t
|
||||
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
|
||||
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)_arm.deb
|
||||
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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.23.4-bookworm
|
||||
FROM golang:1.24.2-bookworm
|
||||
|
||||
WORKDIR /
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
!define INFO_PRODUCTNAME "res-downloader"
|
||||
!endif
|
||||
!ifndef INFO_PRODUCTVERSION
|
||||
!define INFO_PRODUCTVERSION "3.0.3"
|
||||
!define INFO_PRODUCTVERSION "3.0.5"
|
||||
!endif
|
||||
!ifndef INFO_COPYRIGHT
|
||||
!define INFO_COPYRIGHT "Copyright © 2023"
|
||||
|
||||
22
core/app.go
22
core/app.go
@@ -133,7 +133,7 @@ func (a *App) Startup(ctx context.Context) {
|
||||
if a.isInstall() {
|
||||
return
|
||||
}
|
||||
err := os.MkdirAll(a.UserDir, os.ModePerm)
|
||||
err := os.MkdirAll(a.UserDir, 0750)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -166,30 +166,28 @@ func (a *App) installCert() {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) OpenSystemProxy() bool {
|
||||
func (a *App) OpenSystemProxy() error {
|
||||
if a.IsProxy {
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
err := systemOnce.setProxy()
|
||||
if err == nil {
|
||||
a.IsProxy = true
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
DialogErr("设置失败:" + err.Error())
|
||||
return false
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *App) UnsetSystemProxy() bool {
|
||||
func (a *App) UnsetSystemProxy() error {
|
||||
if !a.IsProxy {
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
err := systemOnce.unsetProxy()
|
||||
if err == nil {
|
||||
a.IsProxy = false
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
DialogErr("设置失败:" + err.Error())
|
||||
return false
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *App) isInstall() bool {
|
||||
@@ -197,7 +195,7 @@ func (a *App) isInstall() bool {
|
||||
}
|
||||
|
||||
func (a *App) lock() error {
|
||||
err := os.WriteFile(a.LockFile, []byte("success"), 0777)
|
||||
err := os.WriteFile(a.LockFile, []byte("success"), 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
124
core/config.go
124
core/config.go
@@ -5,27 +5,39 @@ import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type MimeInfo struct {
|
||||
Type string `json:"Type"`
|
||||
Suffix string `json:"Suffix"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
FilenameLen int `json:"FilenameLen"`
|
||||
FilenameTime bool `json:"FilenameTime"`
|
||||
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"`
|
||||
Theme string `json:"Theme"`
|
||||
Host string `json:"Host"`
|
||||
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"`
|
||||
AutoProxy bool `json:"AutoProxy"`
|
||||
WxAction bool `json:"WxAction"`
|
||||
TaskNumber int `json:"TaskNumber"`
|
||||
UserAgent string `json:"UserAgent"`
|
||||
UseHeaders string `json:"UseHeaders"`
|
||||
MimeMap map[string]MimeInfo `json:"MimeMap"`
|
||||
}
|
||||
|
||||
var (
|
||||
mimeMux sync.RWMutex
|
||||
)
|
||||
|
||||
func initConfig() *Config {
|
||||
if globalConfig == nil {
|
||||
def := `
|
||||
@@ -43,7 +55,67 @@ func initConfig() *Config {
|
||||
"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",
|
||||
"MimeMap": {
|
||||
"image/png": { "Type": "image", "Suffix": ".png" },
|
||||
"image/webp": { "Type": "image", "Suffix": ".webp" },
|
||||
"image/jpeg": { "Type": "image", "Suffix": ".jpeg" },
|
||||
"image/jpg": { "Type": "image", "Suffix": ".jpg" },
|
||||
"image/gif": { "Type": "image", "Suffix": ".gif" },
|
||||
"image/avif": { "Type": "image", "Suffix": ".avif" },
|
||||
"image/bmp": { "Type": "image", "Suffix": ".bmp" },
|
||||
"image/tiff": { "Type": "image", "Suffix": ".tiff" },
|
||||
"image/heic": { "Type": "image", "Suffix": ".heic" },
|
||||
"image/x-icon": { "Type": "image", "Suffix": ".ico" },
|
||||
"image/svg+xml": { "Type": "image", "Suffix": ".svg" },
|
||||
"image/vnd.adobe.photoshop": { "Type": "image", "Suffix": ".psd" },
|
||||
"image/jp2": { "Type": "image", "Suffix": ".jp2" },
|
||||
"image/jpeg2000": { "Type": "image", "Suffix": ".jp2" },
|
||||
"image/apng": { "Type": "image", "Suffix": ".apng" },
|
||||
"audio/mpeg": { "Type": "audio", "Suffix": ".mp3" },
|
||||
"audio/mp3": { "Type": "audio", "Suffix": ".mp3" },
|
||||
"audio/wav": { "Type": "audio", "Suffix": ".wav" },
|
||||
"audio/aiff": { "Type": "audio", "Suffix": ".aiff" },
|
||||
"audio/x-aiff": { "Type": "audio", "Suffix": ".aiff" },
|
||||
"audio/aac": { "Type": "audio", "Suffix": ".aac" },
|
||||
"audio/ogg": { "Type": "audio", "Suffix": ".ogg" },
|
||||
"audio/flac": { "Type": "audio", "Suffix": ".flac" },
|
||||
"audio/midi": { "Type": "audio", "Suffix": ".mid" },
|
||||
"audio/x-midi": { "Type": "audio", "Suffix": ".mid" },
|
||||
"audio/x-ms-wma": { "Type": "audio", "Suffix": ".wma" },
|
||||
"audio/opus": { "Type": "audio", "Suffix": ".opus" },
|
||||
"audio/webm": { "Type": "audio", "Suffix": ".webm" },
|
||||
"audio/mp4": { "Type": "audio", "Suffix": ".m4a" },
|
||||
"audio/amr": { "Type": "audio", "Suffix": ".amr" },
|
||||
"video/mp4": { "Type": "video", "Suffix": ".mp4" },
|
||||
"video/webm": { "Type": "video", "Suffix": ".webm" },
|
||||
"video/ogg": { "Type": "video", "Suffix": ".ogv" },
|
||||
"video/x-msvideo": { "Type": "video", "Suffix": ".avi" },
|
||||
"video/mpeg": { "Type": "video", "Suffix": ".mpeg" },
|
||||
"video/quicktime": { "Type": "video", "Suffix": ".mov" },
|
||||
"video/x-ms-wmv": { "Type": "video", "Suffix": ".wmv" },
|
||||
"video/3gpp": { "Type": "video", "Suffix": ".3gp" },
|
||||
"video/x-matroska": { "Type": "video", "Suffix": ".mkv" },
|
||||
"audio/video": { "Type": "live", "Suffix": ".flv" },
|
||||
"video/x-flv": { "Type": "live", "Suffix": ".flv" },
|
||||
"application/dash+xml": { "Type": "live", "Suffix": ".mpd" },
|
||||
"application/vnd.apple.mpegurl": { "Type": "m3u8", "Suffix": ".m3u8" },
|
||||
"application/x-mpegurl": { "Type": "m3u8", "Suffix": ".m3u8" },
|
||||
"application/x-mpeg": { "Type": "m3u8", "Suffix": ".m3u8" },
|
||||
"application/pdf": { "Type": "pdf", "Suffix": ".pdf" },
|
||||
"application/vnd.ms-powerpoint": { "Type": "ppt", "Suffix": ".ppt" },
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation": { "Type": "ppt", "Suffix": ".pptx" },
|
||||
"application/vnd.ms-excel": { "Type": "xls", "Suffix": ".xls" },
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": { "Type": "xls", "Suffix": ".xlsx" },
|
||||
"text/csv": { "Type": "xls", "Suffix": ".csv" },
|
||||
"application/msword": { "Type": "doc", "Suffix": ".doc" },
|
||||
"application/rtf": { "Type": "doc", "Suffix": ".rtf" },
|
||||
"text/rtf": { "Type": "doc", "Suffix": ".rtf" },
|
||||
"application/vnd.oasis.opendocument.text": { "Type": "doc", "Suffix": ".odt" },
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": { "Type": "doc", "Suffix": ".docx" },
|
||||
"font/woff": { "Type": "font", "Suffix": ".woff" }
|
||||
}
|
||||
}
|
||||
`
|
||||
def = strings.ReplaceAll(def, "__TaskNumber__", strconv.Itoa(runtime.NumCPU()*2))
|
||||
@@ -51,9 +123,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")
|
||||
}
|
||||
@@ -77,9 +163,15 @@ 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()
|
||||
}
|
||||
|
||||
mimeMux.Lock()
|
||||
c.MimeMap = config.MimeMap
|
||||
mimeMux.Unlock()
|
||||
|
||||
jsonData, err := json.Marshal(c)
|
||||
if err == nil {
|
||||
_ = globalConfig.storage.Store(jsonData)
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -13,7 +12,7 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type ProgressCallback func(totalDownloaded float64)
|
||||
type ProgressCallback func(totalDownloaded float64, totalSize float64)
|
||||
|
||||
type DownloadTask struct {
|
||||
taskID int
|
||||
@@ -32,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,11 +53,16 @@ func (fd *FileDownloader) buildClient() *http.Client {
|
||||
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) setHeaders(request *http.Request) {
|
||||
for key, value := range fd.Headers {
|
||||
if strings.Contains(globalConfig.UseHeaders, key) {
|
||||
request.Header.Set(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,38 +82,44 @@ func (fd *FileDownloader) init() error {
|
||||
}
|
||||
}
|
||||
|
||||
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 HEAD request failed: %w", err)
|
||||
}
|
||||
|
||||
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("HEAD request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
fd.TotalSize = resp.ContentLength
|
||||
|
||||
if fd.TotalSize <= 0 {
|
||||
return fmt.Errorf("invalid file")
|
||||
return fmt.Errorf("invalid file size")
|
||||
}
|
||||
|
||||
if resp.Header.Get("Accept-Ranges") == "bytes" && fd.TotalSize > 10485760 {
|
||||
if resp.Header.Get("Accept-Ranges") == "bytes" && fd.TotalSize > 10*1024*1024 {
|
||||
fd.IsMultiPart = true
|
||||
}
|
||||
|
||||
resp.Body.Close()
|
||||
|
||||
fd.FileName = filepath.Clean(fd.FileName)
|
||||
|
||||
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)
|
||||
return fmt.Errorf("file open failed: %w", err)
|
||||
}
|
||||
|
||||
if err = fd.File.Truncate(fd.TotalSize); err != nil {
|
||||
if err := fd.File.Truncate(fd.TotalSize); err != nil {
|
||||
fd.File.Close()
|
||||
return fmt.Errorf("文件大小设置失败: %w", err)
|
||||
return fmt.Errorf("file truncate failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -118,96 +130,100 @@ func (fd *FileDownloader) createDownloadTasks() {
|
||||
fd.totalTasks = int(fd.TotalSize)
|
||||
}
|
||||
eachSize := fd.TotalSize / int64(fd.totalTasks)
|
||||
|
||||
for i := 0; i < fd.totalTasks; i++ {
|
||||
start := eachSize * int64(i)
|
||||
end := eachSize*int64(i+1) - 1
|
||||
if i == fd.totalTasks-1 {
|
||||
end = fd.TotalSize - 1
|
||||
}
|
||||
fd.DownloadTaskList = append(fd.DownloadTaskList, &DownloadTask{
|
||||
taskID: i,
|
||||
rangeStart: eachSize * int64(i),
|
||||
rangeEnd: eachSize*int64(i+1) - 1,
|
||||
downloadedSize: 0,
|
||||
isCompleted: false,
|
||||
taskID: i,
|
||||
rangeStart: start,
|
||||
rangeEnd: end,
|
||||
})
|
||||
}
|
||||
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,
|
||||
})
|
||||
fd.DownloadTaskList = append(fd.DownloadTaskList, &DownloadTask{taskID: 0})
|
||||
}
|
||||
}
|
||||
|
||||
func (fd *FileDownloader) startDownload() {
|
||||
waitGroup := &sync.WaitGroup{}
|
||||
wg := &sync.WaitGroup{}
|
||||
progressChan := make(chan int64)
|
||||
|
||||
for _, task := range fd.DownloadTaskList {
|
||||
go fd.startDownloadTask(waitGroup, progressChan, task)
|
||||
waitGroup.Add(1)
|
||||
wg.Add(1)
|
||||
go fd.startDownloadTask(wg, progressChan, task)
|
||||
}
|
||||
go func() {
|
||||
waitGroup.Wait()
|
||||
wg.Wait()
|
||||
close(progressChan)
|
||||
}()
|
||||
|
||||
if fd.progressCallback != nil {
|
||||
totalDownloaded := int64(0)
|
||||
for progress := range progressChan {
|
||||
totalDownloaded += progress
|
||||
fd.progressCallback(float64(totalDownloaded) * 100 / float64(fd.TotalSize))
|
||||
for p := range progressChan {
|
||||
totalDownloaded += p
|
||||
fd.progressCallback(float64(totalDownloaded), float64(fd.TotalSize))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (fd *FileDownloader) startDownloadTask(waitGroup *sync.WaitGroup, progressChan chan int64, task *DownloadTask) {
|
||||
defer waitGroup.Done()
|
||||
func (fd *FileDownloader) startDownloadTask(wg *sync.WaitGroup, progressChan chan int64, task *DownloadTask) {
|
||||
defer wg.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)
|
||||
fd.setHeaders(request)
|
||||
|
||||
if fd.IsMultiPart {
|
||||
request.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", task.rangeStart, task.rangeEnd))
|
||||
rangeHeader := fmt.Sprintf("bytes=%d-%d", task.rangeStart, task.rangeEnd)
|
||||
request.Header.Set("Range", rangeHeader)
|
||||
}
|
||||
|
||||
resp, err := fd.buildClient().Do(request)
|
||||
client := fd.buildClient()
|
||||
resp, err := client.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)
|
||||
remain := task.rangeEnd - (task.rangeStart + task.downloadedSize) + 1
|
||||
n64 := int64(n)
|
||||
if n64 > remain {
|
||||
n = int(remain)
|
||||
}
|
||||
_, writeErr := fd.File.WriteAt(buf[:n], task.rangeStart+task.downloadedSize)
|
||||
if writeErr != nil {
|
||||
log.Printf("任务%d写入文件时出现错误!位置:%d, err: %s\n", task.taskID, task.rangeStart+task.downloadedSize, writeErr)
|
||||
return
|
||||
}
|
||||
downSize := int64(n)
|
||||
task.downloadedSize += downSize
|
||||
progressChan <- downSize
|
||||
task.downloadedSize += n64
|
||||
progressChan <- n64
|
||||
|
||||
if task.rangeStart+task.downloadedSize-1 >= task.rangeEnd {
|
||||
task.isCompleted = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
task.isCompleted = true
|
||||
break
|
||||
}
|
||||
log.Printf("任务%d读取响应错误!%s", task.taskID, err)
|
||||
return
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (fd *FileDownloader) Start() error {
|
||||
err := fd.init()
|
||||
if err != nil {
|
||||
if err := fd.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
fd.createDownloadTasks()
|
||||
|
||||
167
core/http.go
167
core/http.go
@@ -10,11 +10,15 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
sysRuntime "runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type respData map[string]interface{}
|
||||
|
||||
type ResponseData struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
@@ -118,20 +122,54 @@ func (h *HttpServer) writeJson(w http.ResponseWriter, data ResponseData) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HttpServer) error(w http.ResponseWriter, args ...interface{}) {
|
||||
message := "ok"
|
||||
var data interface{}
|
||||
|
||||
if len(args) > 0 {
|
||||
message = args[0].(string)
|
||||
}
|
||||
if len(args) > 1 {
|
||||
data = args[1]
|
||||
}
|
||||
|
||||
h.writeJson(w, ResponseData{
|
||||
Code: 0,
|
||||
Message: message,
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *HttpServer) success(w http.ResponseWriter, args ...interface{}) {
|
||||
message := "ok"
|
||||
var data interface{}
|
||||
|
||||
if len(args) > 0 {
|
||||
data = args[0]
|
||||
}
|
||||
|
||||
if len(args) > 1 {
|
||||
message = args[1].(string)
|
||||
}
|
||||
|
||||
h.writeJson(w, ResponseData{
|
||||
Code: 1,
|
||||
Message: message,
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
||||
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()})
|
||||
h.error(w, err.Error())
|
||||
return
|
||||
}
|
||||
h.writeJson(w, ResponseData{
|
||||
Code: 1,
|
||||
Data: map[string]interface{}{
|
||||
"folder": folder,
|
||||
},
|
||||
h.success(w, respData{
|
||||
"folder": folder,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -146,14 +184,11 @@ func (h *HttpServer) openFileDialog(w http.ResponseWriter, r *http.Request) {
|
||||
Title: "Select a file",
|
||||
})
|
||||
if err != nil {
|
||||
h.writeJson(w, ResponseData{Code: 0, Message: err.Error()})
|
||||
h.error(w, err.Error())
|
||||
return
|
||||
}
|
||||
h.writeJson(w, ResponseData{
|
||||
Code: 1,
|
||||
Data: map[string]interface{}{
|
||||
"file": filePath,
|
||||
},
|
||||
h.success(w, respData{
|
||||
"file": filePath,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -188,77 +223,87 @@ func (h *HttpServer) openFolder(w http.ResponseWriter, r *http.Request) {
|
||||
cmd = exec.Command("pcmanfm", filePath)
|
||||
if err := cmd.Start(); err != nil {
|
||||
globalLogger.err(err)
|
||||
h.writeJson(w, ResponseData{Code: 0, Message: err.Error()})
|
||||
h.error(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
h.writeJson(w, ResponseData{Code: 0, Message: "unsupported platform"})
|
||||
h.error(w, "unsupported platform")
|
||||
return
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
globalLogger.err(err)
|
||||
h.writeJson(w, ResponseData{Code: 0, Message: err.Error()})
|
||||
h.error(w, err.Error())
|
||||
return
|
||||
}
|
||||
h.writeJson(w, ResponseData{Code: 1})
|
||||
h.success(w)
|
||||
}
|
||||
|
||||
func (h *HttpServer) setSystemPassword(w http.ResponseWriter, r *http.Request) {
|
||||
var data struct {
|
||||
Password string `json:"password"`
|
||||
}
|
||||
err := json.NewDecoder(r.Body).Decode(&data)
|
||||
if err != nil {
|
||||
h.error(w, err.Error())
|
||||
return
|
||||
}
|
||||
systemOnce.SetPassword(data.Password)
|
||||
h.success(w)
|
||||
}
|
||||
|
||||
func (h *HttpServer) openSystemProxy(w http.ResponseWriter, r *http.Request) {
|
||||
appOnce.OpenSystemProxy()
|
||||
h.writeJson(w, ResponseData{
|
||||
Code: 1,
|
||||
Data: map[string]bool{
|
||||
err := appOnce.OpenSystemProxy()
|
||||
if err != nil {
|
||||
h.error(w, err.Error(), respData{
|
||||
"isProxy": appOnce.IsProxy,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
h.success(w, respData{
|
||||
"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{
|
||||
err := appOnce.UnsetSystemProxy()
|
||||
if err != nil {
|
||||
h.error(w, err.Error(), respData{
|
||||
"isProxy": appOnce.IsProxy,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
h.success(w, respData{
|
||||
"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,
|
||||
},
|
||||
h.success(w, respData{
|
||||
"isProxy": appOnce.IsProxy,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *HttpServer) appInfo(w http.ResponseWriter, r *http.Request) {
|
||||
h.writeJson(w, ResponseData{
|
||||
Code: 1,
|
||||
Data: appOnce,
|
||||
})
|
||||
h.success(w, appOnce)
|
||||
}
|
||||
|
||||
func (h *HttpServer) getConfig(w http.ResponseWriter, r *http.Request) {
|
||||
h.writeJson(w, ResponseData{
|
||||
Code: 1,
|
||||
Data: globalConfig,
|
||||
})
|
||||
h.success(w, 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()})
|
||||
h.error(w, err.Error())
|
||||
return
|
||||
}
|
||||
globalConfig.setConfig(data)
|
||||
h.writeJson(w, ResponseData{Code: 1})
|
||||
h.success(w)
|
||||
}
|
||||
|
||||
func (h *HttpServer) setType(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -274,12 +319,12 @@ func (h *HttpServer) setType(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
h.writeJson(w, ResponseData{Code: 1})
|
||||
h.success(w)
|
||||
}
|
||||
|
||||
func (h *HttpServer) clear(w http.ResponseWriter, r *http.Request) {
|
||||
resourceOnce.clear()
|
||||
h.writeJson(w, ResponseData{Code: 1})
|
||||
h.success(w)
|
||||
}
|
||||
|
||||
func (h *HttpServer) delete(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -290,7 +335,7 @@ func (h *HttpServer) delete(w http.ResponseWriter, r *http.Request) {
|
||||
if err == nil && data.Sign != "" {
|
||||
resourceOnce.delete(data.Sign)
|
||||
}
|
||||
h.writeJson(w, ResponseData{Code: 1})
|
||||
h.success(w)
|
||||
}
|
||||
|
||||
func (h *HttpServer) download(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -299,11 +344,11 @@ func (h *HttpServer) download(w http.ResponseWriter, r *http.Request) {
|
||||
DecodeStr string `json:"decodeStr"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
|
||||
h.writeJson(w, ResponseData{Code: 0, Message: err.Error()})
|
||||
h.error(w, err.Error())
|
||||
return
|
||||
}
|
||||
resourceOnce.download(data.MediaInfo, data.DecodeStr)
|
||||
h.writeJson(w, ResponseData{Code: 1})
|
||||
h.success(w)
|
||||
}
|
||||
|
||||
func (h *HttpServer) wxFileDecode(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -313,18 +358,34 @@ func (h *HttpServer) wxFileDecode(w http.ResponseWriter, r *http.Request) {
|
||||
DecodeStr string `json:"decodeStr"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
|
||||
h.writeJson(w, ResponseData{Code: 0, Message: err.Error()})
|
||||
h.error(w, 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()})
|
||||
h.error(w, err.Error())
|
||||
return
|
||||
}
|
||||
h.writeJson(w, ResponseData{
|
||||
Code: 1,
|
||||
Data: map[string]string{
|
||||
"save_path": savePath,
|
||||
},
|
||||
h.success(w, respData{
|
||||
"save_path": savePath,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *HttpServer) batchImport(w http.ResponseWriter, r *http.Request) {
|
||||
var data struct {
|
||||
Content string `json:"content"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
|
||||
h.error(w, err.Error())
|
||||
return
|
||||
}
|
||||
fileName := filepath.Join(globalConfig.SaveDirectory, "res-downloader-"+GetCurrentDateTimeFormatted()+".txt")
|
||||
err := os.WriteFile(fileName, []byte(data.Content), 0644)
|
||||
if err != nil {
|
||||
h.error(w, err.Error())
|
||||
return
|
||||
}
|
||||
h.success(w, respData{
|
||||
"file_name": fileName,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func NewLogger(logFile bool, logPath string) *Logger {
|
||||
logfile *os.File
|
||||
err error
|
||||
)
|
||||
logfile, err = os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
logfile, err = os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ func HandleApi(w http.ResponseWriter, r *http.Request) bool {
|
||||
switch r.URL.Path {
|
||||
case "/api/preview":
|
||||
httpServerOnce.preview(w, r)
|
||||
case "/api/set-system-password":
|
||||
httpServerOnce.setSystemPassword(w, r)
|
||||
case "/api/proxy-open":
|
||||
httpServerOnce.openSystemProxy(w, r)
|
||||
case "/api/proxy-unset":
|
||||
@@ -54,6 +56,8 @@ func HandleApi(w http.ResponseWriter, r *http.Request) bool {
|
||||
httpServerOnce.download(w, r)
|
||||
case "/api/wx-file-decode":
|
||||
httpServerOnce.wxFileDecode(w, r)
|
||||
case "/api/batch-import":
|
||||
httpServerOnce.batchImport(w, r)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -148,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
|
||||
@@ -214,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)
|
||||
@@ -249,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") {
|
||||
@@ -292,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"))
|
||||
@@ -306,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 {
|
||||
@@ -335,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
|
||||
|
||||
@@ -2,6 +2,8 @@ package core
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -26,16 +28,14 @@ type WxFileDecodeResult struct {
|
||||
}
|
||||
|
||||
type Resource struct {
|
||||
mark map[string]bool
|
||||
markMu sync.RWMutex
|
||||
resType map[string]bool
|
||||
resTypeMu sync.RWMutex
|
||||
mediaMark sync.Map
|
||||
resType map[string]bool
|
||||
resTypeMux sync.RWMutex
|
||||
}
|
||||
|
||||
func initResource() *Resource {
|
||||
if resourceOnce == nil {
|
||||
resourceOnce = &Resource{
|
||||
mark: make(map[string]bool),
|
||||
resType: map[string]bool{
|
||||
"all": true,
|
||||
"image": true,
|
||||
@@ -52,29 +52,25 @@ 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) {
|
||||
r.resTypeMu.RLock()
|
||||
defer r.resTypeMu.RUnlock()
|
||||
r.resTypeMux.RLock()
|
||||
defer r.resTypeMux.RUnlock()
|
||||
value, ok := r.resType[key]
|
||||
return value, ok
|
||||
}
|
||||
|
||||
func (r *Resource) setResType(n []string) {
|
||||
r.resTypeMu.Lock()
|
||||
defer r.resTypeMu.Unlock()
|
||||
r.resTypeMux.Lock()
|
||||
defer r.resTypeMux.Unlock()
|
||||
r.resType = map[string]bool{
|
||||
"all": false,
|
||||
"image": false,
|
||||
@@ -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) {
|
||||
@@ -152,9 +144,11 @@ func (r *Resource) download(mediaInfo MediaInfo, decodeStr string) {
|
||||
}
|
||||
}
|
||||
|
||||
downloader := NewFileDownloader(rawUrl, mediaInfo.SavePath, globalConfig.TaskNumber)
|
||||
downloader.progressCallback = func(totalDownloaded float64) {
|
||||
r.progressEventsEmit(mediaInfo, strconv.Itoa(int(totalDownloaded))+"%", DownloadStatusRunning)
|
||||
headers, _ := r.parseHeaders(mediaInfo)
|
||||
|
||||
downloader := NewFileDownloader(rawUrl, mediaInfo.SavePath, globalConfig.TaskNumber, headers)
|
||||
downloader.progressCallback = func(totalDownloaded, totalSize float64) {
|
||||
r.progressEventsEmit(mediaInfo, strconv.Itoa(int(totalDownloaded*100/totalSize))+"%", DownloadStatusRunning)
|
||||
}
|
||||
err := downloader.Start()
|
||||
if err != nil {
|
||||
@@ -172,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 {
|
||||
|
||||
@@ -19,7 +19,7 @@ func NewStorage(filename string, def []byte) *Storage {
|
||||
|
||||
func (l *Storage) Load() ([]byte, error) {
|
||||
if !FileExist(l.fileName) {
|
||||
err := os.WriteFile(l.fileName, l.def, 0777)
|
||||
err := os.WriteFile(l.fileName, l.def, 0644)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -33,7 +33,7 @@ func (l *Storage) Load() ([]byte, error) {
|
||||
}
|
||||
|
||||
func (l *Storage) Store(data []byte) error {
|
||||
if err := os.WriteFile(l.fileName, data, 0777); err != nil {
|
||||
if err := os.WriteFile(l.fileName, data, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
type SystemSetup struct {
|
||||
CertFile string
|
||||
Password string
|
||||
}
|
||||
|
||||
func initSystem() *SystemSetup {
|
||||
@@ -24,7 +25,7 @@ func (s *SystemSetup) initCert() ([]byte, error) {
|
||||
return content, nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
err = os.WriteFile(s.CertFile, appOnce.PublicCrt, 0777)
|
||||
err = os.WriteFile(s.CertFile, appOnce.PublicCrt, 0750)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -33,3 +34,7 @@ func (s *SystemSetup) initCert() ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SystemSetup) SetPassword(password string) {
|
||||
s.Password = password
|
||||
}
|
||||
|
||||
@@ -9,15 +9,30 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (s *SystemSetup) runCommand(args []string) ([]byte, error) {
|
||||
if len(args) == 0 {
|
||||
return nil, fmt.Errorf("no command provided")
|
||||
}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
if s.Password != "" {
|
||||
cmd = exec.Command("sudo", append([]string{"-S"}, args...)...)
|
||||
cmd.Stdin = bytes.NewReader([]byte(s.Password + "\n"))
|
||||
} else {
|
||||
cmd = exec.Command(args[0], args[1:]...)
|
||||
}
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
return output, err
|
||||
}
|
||||
|
||||
func (s *SystemSetup) getNetworkServices() ([]string, error) {
|
||||
cmd := exec.Command("networksetup", "-listallnetworkservices")
|
||||
output, err := cmd.Output()
|
||||
output, err := s.runCommand([]string{"networksetup", "-listallnetworkservices"})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute command: %v", err)
|
||||
}
|
||||
|
||||
services := strings.Split(string(output), "\n")
|
||||
|
||||
var activeServices []string
|
||||
for _, service := range services {
|
||||
service = strings.TrimSpace(service)
|
||||
@@ -25,15 +40,12 @@ func (s *SystemSetup) getNetworkServices() ([]string, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 检查服务是否活动
|
||||
infoCmd := exec.Command("networksetup", "-getinfo", service)
|
||||
infoOutput, err := infoCmd.Output()
|
||||
infoOutput, err := s.runCommand([]string{"networksetup", "-getinfo", service})
|
||||
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)
|
||||
}
|
||||
@@ -53,16 +65,19 @@ func (s *SystemSetup) setProxy() error {
|
||||
}
|
||||
|
||||
is := false
|
||||
errs := ""
|
||||
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
|
||||
cmds := [][]string{
|
||||
{"networksetup", "-setwebproxy", serviceName, "127.0.0.1", globalConfig.Port},
|
||||
{"networksetup", "-setsecurewebproxy", serviceName, "127.0.0.1", globalConfig.Port},
|
||||
}
|
||||
if err := exec.Command("networksetup", "-setsecurewebproxy", serviceName, "127.0.0.1", globalConfig.Port).Run(); err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
is = true
|
||||
for _, args := range cmds {
|
||||
if output, err := s.runCommand(args); err != nil {
|
||||
errs = errs + "\n output:" + string(output) + "err:" + err.Error()
|
||||
fmt.Println("setProxy:", output, " err:", err.Error())
|
||||
} else {
|
||||
is = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +85,7 @@ func (s *SystemSetup) setProxy() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("failed to set proxy for any active network service")
|
||||
return fmt.Errorf("failed to set proxy for any active network service, errs: %s", errs)
|
||||
}
|
||||
|
||||
func (s *SystemSetup) unsetProxy() error {
|
||||
@@ -80,16 +95,19 @@ func (s *SystemSetup) unsetProxy() error {
|
||||
}
|
||||
|
||||
is := false
|
||||
errs := ""
|
||||
for _, serviceName := range services {
|
||||
if err := exec.Command("networksetup", "-setwebproxystate", serviceName, "off").Run(); err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
is = true
|
||||
cmds := [][]string{
|
||||
{"networksetup", "-setwebproxystate", serviceName, "off"},
|
||||
{"networksetup", "-setsecurewebproxystate", serviceName, "off"},
|
||||
}
|
||||
if err := exec.Command("networksetup", "-setsecurewebproxystate", serviceName, "off").Run(); err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
is = true
|
||||
for _, args := range cmds {
|
||||
if output, err := s.runCommand(args); err != nil {
|
||||
errs = errs + "\n output:" + string(output) + "err:" + err.Error()
|
||||
fmt.Println("unsetProxy:", output, " err:", err.Error())
|
||||
} else {
|
||||
is = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +115,7 @@ func (s *SystemSetup) unsetProxy() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("failed to set proxy for any active network service")
|
||||
return fmt.Errorf("failed to unset proxy for any active network service, errs: %s", errs)
|
||||
}
|
||||
|
||||
func (s *SystemSetup) installCert() (string, error) {
|
||||
@@ -112,10 +130,11 @@ func (s *SystemSetup) installCert() (string, error) {
|
||||
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)
|
||||
password := strings.TrimSpace(string(passwordOutput))
|
||||
s.SetPassword(password)
|
||||
|
||||
cmd.Stdin = bytes.NewReader(append(password, '\n'))
|
||||
cmd := exec.Command("sudo", "-S", "security", "add-trusted-cert", "-d", "-r", "trustRoot", "-k", "/Library/Keychains/System.keychain", s.CertFile)
|
||||
cmd.Stdin = bytes.NewReader([]byte(password + "\n"))
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return string(output), err
|
||||
|
||||
@@ -40,69 +40,17 @@ func FileExist(file string) bool {
|
||||
|
||||
func CreateDirIfNotExist(dir string) error {
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
return os.MkdirAll(dir, 0777)
|
||||
return os.MkdirAll(dir, 0750)
|
||||
}
|
||||
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"
|
||||
|
||||
mimeMux.RLock()
|
||||
defer mimeMux.RUnlock()
|
||||
mime = strings.ToLower(strings.Split(mime, ";")[0])
|
||||
if v, ok := globalConfig.MimeMap[mime]; ok {
|
||||
return v.Type, v.Suffix
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
|
||||
2
frontend/components.d.ts
vendored
2
frontend/components.d.ts
vendored
@@ -32,11 +32,13 @@ declare module 'vue' {
|
||||
NModalProvider: typeof import('naive-ui')['NModalProvider']
|
||||
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
|
||||
NRadio: typeof import('naive-ui')['NRadio']
|
||||
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
|
||||
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
||||
NSelect: typeof import('naive-ui')['NSelect']
|
||||
NSpace: typeof import('naive-ui')['NSpace']
|
||||
NSwitch: typeof import('naive-ui')['NSwitch']
|
||||
NTooltip: typeof import('naive-ui')['NTooltip']
|
||||
Password: typeof import('./src/components/Password.vue')['default']
|
||||
Preview: typeof import('./src/components/Preview.vue')['default']
|
||||
ResAction: typeof import('./src/components/ResAction.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import request from '@/api/request'
|
||||
|
||||
export default {
|
||||
setSystemPassword(data: object) {
|
||||
return request({
|
||||
url: 'api/set-system-password',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
},
|
||||
openSystemProxy() {
|
||||
return request({
|
||||
url: 'api/proxy-open',
|
||||
@@ -93,4 +100,11 @@ export default {
|
||||
data: data
|
||||
})
|
||||
},
|
||||
batchImport(data: object) {
|
||||
return request({
|
||||
url: 'api/batch-import',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
},
|
||||
}
|
||||
46
frontend/src/components/Password.vue
Normal file
46
frontend/src/components/Password.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<n-modal
|
||||
:show="showModal"
|
||||
:on-update:show="changeShow"
|
||||
style="--wails-draggable:no-drag"
|
||||
preset="dialog"
|
||||
title="管理员授权"
|
||||
content=""
|
||||
:show-icon="false"
|
||||
:mask-closable="false"
|
||||
class="rounded-lg"
|
||||
>
|
||||
<n-form>
|
||||
<div class="text-red-500 text-base">
|
||||
本操作需要管理员授权,仅对本次运行期间有效!
|
||||
</div>
|
||||
<n-form-item path="password" label="">
|
||||
<n-input
|
||||
v-model:value="password"
|
||||
type="password"
|
||||
placeholder="请输入你的电脑密码"
|
||||
class="w-full"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<template #action>
|
||||
<div class="flex justify-end gap-4">
|
||||
<n-button @click="emits('update:showModal', false)">取消操作</n-button>
|
||||
<n-button type="primary" @click="emits('submit', password)">确认操作</n-button>
|
||||
</div>
|
||||
</template>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, computed} from 'vue'
|
||||
import {NModal, NForm, NFormItem, NInput, NButton} from 'naive-ui'
|
||||
|
||||
const props = defineProps({
|
||||
showModal: Boolean,
|
||||
})
|
||||
const password = ref("")
|
||||
|
||||
const emits = defineEmits(["update:showModal", "submit"])
|
||||
const changeShow = (value: boolean) => emits("update:showModal", value)
|
||||
</script>
|
||||
@@ -12,7 +12,7 @@
|
||||
<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 type="info" :tertiary="true" size="small" @click="action('json')">
|
||||
复制数据
|
||||
</NButton>
|
||||
<NButton type="error" :tertiary="true" size="small" @click="action('delete')">
|
||||
@@ -22,8 +22,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {inject} from "vue"
|
||||
|
||||
const props = defineProps<{
|
||||
row: any,
|
||||
index: number,
|
||||
@@ -31,8 +29,6 @@ const props = defineProps<{
|
||||
|
||||
const emits = defineEmits(["action"])
|
||||
|
||||
const isDebug = inject('isDebug')
|
||||
|
||||
const action = (type: string) => {
|
||||
emits('action', props.row, props.index, type)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="flex pb-2 flex-col h-full min-w-[80px] border-r border-slate-100 dark:border-slate-900">
|
||||
<Screen v-if="envInfo.platform!=='darwin'"></Screen>
|
||||
<div class="w-full flex flex-row items-center justify-center pt-5 ml-[-5px]" :class="envInfo.platform==='darwin' ? 'pt-8' : 'pt-2'">
|
||||
<img class="w-12 h-12 cursor-pointer" src="@/assets/image/logo.png" alt="res-downloader logo"/>
|
||||
<img class="w-12 h-12 cursor-pointer" src="@/assets/image/logo.png" alt="res-downloader logo" @click="handleFooterUpdate('github')"/>
|
||||
</div>
|
||||
<main class="flex-1 flex-grow-1 mb-5 overflow-auto flex flex-col pt-1 items-center h-full">
|
||||
<NScrollbar :size="1">
|
||||
@@ -119,11 +119,11 @@ const footerOptions = ref([
|
||||
},
|
||||
])
|
||||
|
||||
const handleUpdateValue = (key: string, item: MenuOption) => {
|
||||
const handleUpdateValue = (key: string, item?: MenuOption) => {
|
||||
menuValue.value = key
|
||||
return router.push({path: "/" + key})
|
||||
}
|
||||
const handleFooterUpdate = (key: string, item: MenuOption) => {
|
||||
const handleFooterUpdate = (key: string, item?: MenuOption) => {
|
||||
if (key === "about") {
|
||||
showAppInfo.value = true
|
||||
return
|
||||
@@ -135,10 +135,10 @@ const handleFooterUpdate = (key: string, item: MenuOption) => {
|
||||
}
|
||||
if (key === "theme") {
|
||||
if (globalConfig.value.Theme === "darkTheme") {
|
||||
store.setConfig(Object.assign({}, globalConfig.value, {Theme: "lightTheme"}))
|
||||
store.setConfig({Theme: "lightTheme"})
|
||||
return
|
||||
}
|
||||
store.setConfig(Object.assign({}, globalConfig.value, {Theme: "darkTheme"}))
|
||||
store.setConfig({Theme: "darkTheme"})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ export const useIndexStore = defineStore("index-store", () => {
|
||||
WxAction: false,
|
||||
TaskNumber: 8,
|
||||
UserAgent: "",
|
||||
UseHeaders: "",
|
||||
MimeMap: {}
|
||||
})
|
||||
|
||||
const envInfo = ref({
|
||||
@@ -62,7 +64,7 @@ export const useIndexStore = defineStore("index-store", () => {
|
||||
})
|
||||
}
|
||||
|
||||
const setConfig = (formValue: appType.Config) => {
|
||||
const setConfig = (formValue: Object) => {
|
||||
globalConfig.value = Object.assign({}, globalConfig.value, formValue)
|
||||
appApi.setConfig(globalConfig.value)
|
||||
}
|
||||
|
||||
13
frontend/src/types/app.d.ts
vendored
13
frontend/src/types/app.d.ts
vendored
@@ -6,6 +6,11 @@ export namespace appType {
|
||||
Copyright: string
|
||||
}
|
||||
|
||||
interface MimeMap {
|
||||
Type: string
|
||||
Suffix: string
|
||||
}
|
||||
|
||||
interface Config {
|
||||
Theme: string
|
||||
Host: string
|
||||
@@ -21,6 +26,8 @@ export namespace appType {
|
||||
WxAction: boolean
|
||||
TaskNumber: number
|
||||
UserAgent: string
|
||||
UseHeaders: string
|
||||
MimeMap: {[key: string]: MimeMap}
|
||||
}
|
||||
|
||||
interface MediaInfo {
|
||||
@@ -56,4 +63,10 @@ export namespace appType {
|
||||
type: string
|
||||
event: any
|
||||
}
|
||||
|
||||
interface Res<T = any> {
|
||||
code: number;
|
||||
message: string;
|
||||
data: T; // T will be the specific type of your data
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
<template>
|
||||
<div class="flex flex-col px-5 py-5">
|
||||
<div class="pb-2 z-40" @click="triggerEvent">
|
||||
<div class="flex flex-col p-5">
|
||||
<div class="pb-2 z-40">
|
||||
<NSpace>
|
||||
<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>
|
||||
<NSelect style="min-width: 100px;--wails-draggable:no-drag" placeholder="拦截类型" v-model:value="resourcesType" multiple clearable :max-tag-count="3" :options="classify"></NSelect>
|
||||
<NButton tertiary type="info" @click.stop="batchDown" style="--wails-draggable:no-drag">批量下载</NButton>
|
||||
<NButton tertiary type="info" @click.stop="batchImport" style="--wails-draggable:no-drag">批量导出</NButton>
|
||||
<NButton tertiary type="info" @click.stop="showImport=true" style="--wails-draggable:no-drag">批量导入</NButton>
|
||||
</NSpace>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
@@ -27,12 +28,13 @@
|
||||
<Preview v-model:showModal="showPreviewRow" :previewRow="previewRow"/>
|
||||
<ShowLoading :loadingText="loadingText" :isLoading="loading"/>
|
||||
<ImportJson v-model:showModal="showImport" @submit="handleImport"/>
|
||||
<Password v-model:showModal="showPassword" @submit="handlePassword"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {NButton, NImage, NTooltip} from "naive-ui"
|
||||
import {computed, h, onMounted, ref, watch, provide} from "vue"
|
||||
import {computed, h, onMounted, ref, watch} from "vue"
|
||||
import type {appType} from "@/types/app"
|
||||
|
||||
import type {DataTableRowKey, ImageRenderToolbarProps} from "naive-ui"
|
||||
@@ -47,6 +49,7 @@ import ResAction from "@/components/ResAction.vue"
|
||||
import ImportJson from "@/components/ImportJson.vue"
|
||||
import {useEventStore} from "@/stores/event"
|
||||
import {BrowserOpenURL, ClipboardSetText} from "../../wailsjs/runtime"
|
||||
import Password from "@/components/Password.vue"
|
||||
|
||||
const eventStore = useEventStore()
|
||||
const isProxy = computed(() => {
|
||||
@@ -58,37 +61,24 @@ const tableHeight = computed(() => {
|
||||
return store.tableHeight - 132
|
||||
})
|
||||
const resourcesType = ref<string[]>(["all"])
|
||||
const options = [
|
||||
const classifyAlias: {[key: string]: string} = {
|
||||
image: "图片",
|
||||
audio: "音频",
|
||||
video: "视频",
|
||||
m3u8: "m3u8",
|
||||
live: "直播流",
|
||||
xls: "表格",
|
||||
doc: "文档",
|
||||
pdf: "pdf",
|
||||
font: "字体"
|
||||
}
|
||||
const classify = ref([
|
||||
{
|
||||
value: "all",
|
||||
label: "全部",
|
||||
},
|
||||
{
|
||||
value: "image",
|
||||
label: "图片",
|
||||
}, {
|
||||
value: "audio",
|
||||
label: "音频"
|
||||
}, {
|
||||
value: "video",
|
||||
label: "视频"
|
||||
}, {
|
||||
value: "m3u8",
|
||||
label: "m3u8"
|
||||
}, {
|
||||
value: "live",
|
||||
label: "直播流"
|
||||
}, {
|
||||
value: "xls",
|
||||
label: "表格"
|
||||
}, {
|
||||
value: "doc",
|
||||
label: "文档"
|
||||
}, {
|
||||
value: "pdf",
|
||||
label: "pdf"
|
||||
}
|
||||
]
|
||||
])
|
||||
|
||||
const columns = ref<any[]>([
|
||||
{
|
||||
type: "selection",
|
||||
@@ -100,15 +90,15 @@ const columns = ref<any[]>([
|
||||
{
|
||||
title: "类型",
|
||||
key: "Classify",
|
||||
filterOptions: Array.from(options).slice(1),
|
||||
filterOptions: Array.from(classify.value).slice(1),
|
||||
filterMultiple: true,
|
||||
filter: (value: string, row: appType.MediaInfo) => {
|
||||
return !!~row.Classify.indexOf(String(value))
|
||||
},
|
||||
render: (row: appType.MediaInfo) => {
|
||||
for (const key in options) {
|
||||
if (options[key].value === row.Classify) {
|
||||
return options[key].label;
|
||||
for (const key in classify.value) {
|
||||
if (classify.value[key].value === row.Classify) {
|
||||
return classify.value[key].label;
|
||||
}
|
||||
}
|
||||
return row.Classify;
|
||||
@@ -219,14 +209,12 @@ 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);
|
||||
const showPassword = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
buildClassify()
|
||||
|
||||
const temp = localStorage.getItem("resources-type")
|
||||
if (temp) {
|
||||
resourcesType.value = JSON.parse(temp).res
|
||||
@@ -281,11 +269,35 @@ onMounted(() => {
|
||||
})
|
||||
})
|
||||
|
||||
watch(()=>{
|
||||
return store.globalConfig.MimeMap
|
||||
}, ()=>{
|
||||
buildClassify()
|
||||
})
|
||||
|
||||
watch(resourcesType, (n, o) => {
|
||||
localStorage.setItem("resources-type", JSON.stringify({res: resourcesType.value}))
|
||||
appApi.setType(resourcesType.value)
|
||||
})
|
||||
|
||||
const buildClassify = ()=>{
|
||||
const mimeMap = store.globalConfig.MimeMap ?? {}
|
||||
const seen = new Set()
|
||||
classify.value = [
|
||||
{value: "all", label: "全部"},
|
||||
...Object.values(mimeMap)
|
||||
.filter(({Type}) => {
|
||||
if (seen.has(Type)) return false;
|
||||
seen.add(Type);
|
||||
return true;
|
||||
})
|
||||
.map(({Type}) => ({
|
||||
value: Type,
|
||||
label: classifyAlias[Type] ?? Type,
|
||||
})),
|
||||
]
|
||||
}
|
||||
|
||||
const dataAction = (row: appType.MediaInfo, index: number, type: string) => {
|
||||
switch (type) {
|
||||
case "down":
|
||||
@@ -361,6 +373,35 @@ const batchDown = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const batchImport = ()=>{
|
||||
if (checkedRowKeysValue.value.length <= 0) {
|
||||
window?.$message?.error('请选择需要导出的数据')
|
||||
return
|
||||
}
|
||||
if (!store.globalConfig.SaveDirectory) {
|
||||
window?.$message?.error("请设置保存目录")
|
||||
return
|
||||
}
|
||||
loadingText.value = "导出中"
|
||||
loading.value = true
|
||||
let jsonData = []
|
||||
for (let i = 0; i < data.value.length; i++) {
|
||||
jsonData.push(encodeURIComponent(JSON.stringify(data.value[i])))
|
||||
}
|
||||
appApi.batchImport({content: jsonData.join("\n")}).then((res: appType.Res) => {
|
||||
loading.value = false
|
||||
if (res.code === 0) {
|
||||
window?.$message?.error(res.message)
|
||||
return
|
||||
}
|
||||
window?.$message?.success("导出成功")
|
||||
window?.$message?.info("文件路径:" + res.data?.file_name, {
|
||||
duration: 5000
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
const uint8ArrayToBase64 = (bytes: any) => {
|
||||
let binary = '';
|
||||
const len = bytes.byteLength;
|
||||
@@ -390,14 +431,14 @@ const download = (row: appType.MediaInfo, index: number) => {
|
||||
loading.value = true
|
||||
downIndex.value = index
|
||||
if (row.DecodeKey) {
|
||||
appApi.download({...row, decodeStr: uint8ArrayToBase64(getDecryptionArray(row.DecodeKey))}).then((res: any) => {
|
||||
appApi.download({...row, decodeStr: uint8ArrayToBase64(getDecryptionArray(row.DecodeKey))}).then((res: appType.Res) => {
|
||||
if (res.code === 0) {
|
||||
loading.value = false
|
||||
window?.$message?.error(res.message)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
appApi.download({...row, decodeStr: ""}).then((res: any) => {
|
||||
appApi.download({...row, decodeStr: ""}).then((res: appType.Res) => {
|
||||
if (res.code === 0) {
|
||||
loading.value = false
|
||||
window?.$message?.error(res.message)
|
||||
@@ -407,13 +448,25 @@ const download = (row: appType.MediaInfo, index: number) => {
|
||||
}
|
||||
|
||||
const open = () => {
|
||||
appApi.openSystemProxy().then((res: any) => {
|
||||
appApi.openSystemProxy().then((res: appType.Res) => {
|
||||
if (res.code === 0 ){
|
||||
if (store.envInfo.platform === "darwin") {
|
||||
showPassword.value = true
|
||||
return
|
||||
}
|
||||
window?.$message?.error(res.message)
|
||||
return
|
||||
}
|
||||
store.updateProxyStatus(res.data)
|
||||
})
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
appApi.unsetSystemProxy().then((res: any) => {
|
||||
appApi.unsetSystemProxy().then((res: appType.Res) => {
|
||||
if (res.code === 0 ){
|
||||
window?.$message?.error(res.message)
|
||||
return
|
||||
}
|
||||
store.updateProxyStatus(res.data)
|
||||
})
|
||||
}
|
||||
@@ -429,7 +482,7 @@ const decodeWxFile = (row: appType.MediaInfo, index: number) => {
|
||||
window?.$message?.error("无法解密")
|
||||
return
|
||||
}
|
||||
appApi.openFileDialog().then((res: any) => {
|
||||
appApi.openFileDialog().then((res: appType.Res) => {
|
||||
if (res.code === 0) {
|
||||
window?.$message?.error(res.message)
|
||||
return
|
||||
@@ -441,7 +494,7 @@ const decodeWxFile = (row: appType.MediaInfo, index: number) => {
|
||||
...row,
|
||||
filename: res.data.file,
|
||||
decodeStr: uint8ArrayToBase64(getDecryptionArray(row.DecodeKey))
|
||||
}).then((res: any) => {
|
||||
}).then((res: appType.Res) => {
|
||||
loading.value = false
|
||||
if (res.code === 0) {
|
||||
window?.$message?.error(res.message)
|
||||
@@ -456,24 +509,6 @@ 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 {
|
||||
@@ -491,4 +526,14 @@ const handleImport = (content: string)=>{
|
||||
localStorage.setItem("resources-data", JSON.stringify(data.value))
|
||||
showImport.value = false
|
||||
}
|
||||
|
||||
const handlePassword = (password: string)=>{
|
||||
appApi.setSystemPassword({password: password}).then((res: appType.Res)=>{
|
||||
if (res.code === 0) {
|
||||
window?.$message?.error(res.message)
|
||||
return
|
||||
}
|
||||
open()
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="h-full relative">
|
||||
<div class="h-full relative p-5 overflow-y-auto [&::-webkit-scrollbar]:hidden">
|
||||
<NForm
|
||||
:model="formValue"
|
||||
size="medium"
|
||||
@@ -7,140 +7,175 @@
|
||||
label-width="auto"
|
||||
require-mark-placement="right-hanging"
|
||||
style="--wails-draggable:no-drag"
|
||||
class="px-5 py-5"
|
||||
class="w-[700px]"
|
||||
>
|
||||
<NFormItem label="代理Host" path="Port" size="small">
|
||||
<NInput v-model:value="formValue.Host" placeholder="127.0.0.1" style="width:256px"/>
|
||||
<NFormItem label="Host" path="Host">
|
||||
<NInput v-model:value="formValue.Host" placeholder="127.0.0.1"/>
|
||||
<NTooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<NIcon size="20" class="pl-1">
|
||||
<HelpCircleOutline />
|
||||
<NIcon size="18" class="ml-1 text-gray-500">
|
||||
<HelpCircleOutline/>
|
||||
</NIcon>
|
||||
</template>
|
||||
<span>如果不清楚保持默认就行,修改后请重启软件</span>
|
||||
如果不清楚请保持默认,修改后请重启软件
|
||||
</NTooltip>
|
||||
</NFormItem>
|
||||
<NFormItem label="代理端口" path="Port" size="small">
|
||||
<NInput v-model:value="formValue.Port" placeholder="8899" style="width:256px"/>
|
||||
|
||||
<NFormItem label="Port" path="Port">
|
||||
<NInput v-model:value="formValue.Port" placeholder="8899"/>
|
||||
<NTooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<NIcon size="20" class="pl-1">
|
||||
<HelpCircleOutline />
|
||||
<NIcon size="18" class="ml-1 text-gray-500">
|
||||
<HelpCircleOutline/>
|
||||
</NIcon>
|
||||
</template>
|
||||
<span>如果不清楚保持默认就行,修改后请重启软件</span>
|
||||
如果不清楚保持默认,修改后请重启软件
|
||||
</NTooltip>
|
||||
</NFormItem>
|
||||
<NFormItem label="保存位置" path="SaveDirectory" size="small">
|
||||
<NSpace>
|
||||
<NInput v-model:value="formValue.SaveDirectory" disabled 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>
|
||||
|
||||
<NFormItem label="上游代理" path="UpstreamProxy">
|
||||
<NInput v-model:value="formValue.UpstreamProxy" placeholder="例如: http://127.0.0.1:7890"/>
|
||||
<NSwitch v-model:value="formValue.OpenProxy" class="ml-1"/>
|
||||
<NTooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<NIcon size="20" class="pl-1">
|
||||
<HelpCircleOutline />
|
||||
<NIcon size="18" class="ml-1 text-gray-500">
|
||||
<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>
|
||||
</NFormItem>
|
||||
<NFormItem label="自动拦截" path="AutoProxy" size="small">
|
||||
<NSwitch v-model:value="formValue.AutoProxy" />
|
||||
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<NFormItem label="下载代理" path="DownloadProxy">
|
||||
<NSwitch v-model:value="formValue.DownloadProxy"/>
|
||||
<NTooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<NIcon size="18" class="ml-1 text-gray-500">
|
||||
<HelpCircleOutline/>
|
||||
</NIcon>
|
||||
</template>
|
||||
进行下载时使用代理请求
|
||||
</NTooltip>
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="自动拦截" path="AutoProxy">
|
||||
<NSwitch v-model:value="formValue.AutoProxy"/>
|
||||
<NTooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<NIcon size="18" class="ml-1 text-gray-500">
|
||||
<HelpCircleOutline/>
|
||||
</NIcon>
|
||||
</template>
|
||||
打开软件时自动启用拦截
|
||||
</NTooltip>
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="全量拦截" path="WxAction">
|
||||
<NSwitch v-model:value="formValue.WxAction"/>
|
||||
<NTooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<NIcon size="18" class="ml-1 text-gray-500">
|
||||
<HelpCircleOutline/>
|
||||
</NIcon>
|
||||
</template>
|
||||
微信视频号是否全量拦截,否:只拦截视频详情
|
||||
</NTooltip>
|
||||
</NFormItem>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<NFormItem label="保存位置" path="SaveDirectory">
|
||||
<NInput :value="formValue.SaveDirectory" placeholder="保存位置"/>
|
||||
<NButton strong secondary type="primary" @click="selectDir" class="ml-1">选择</NButton>
|
||||
</NFormItem>
|
||||
<NFormItem label="文件命名" path="FilenameLen">
|
||||
<NInputNumber v-model:value="formValue.FilenameLen" :min="0" :max="9999" placeholder="0"/>
|
||||
<NSwitch v-model:value="formValue.FilenameTime" class="ml-1"></NSwitch>
|
||||
<NTooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<NIcon size="18" class="ml-1 text-gray-500">
|
||||
<HelpCircleOutline/>
|
||||
</NIcon>
|
||||
</template>
|
||||
输入框控制文件命名的长度(不含时间、0为无效),开关控制文件末尾是否添加时间标识
|
||||
</NTooltip>
|
||||
</NFormItem>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<NFormItem label="主题" path="theme">
|
||||
<NRadioGroup v-model:value="formValue.Theme" name="theme">
|
||||
<NRadio value="lightTheme">浅色</NRadio>
|
||||
<NRadio value="darkTheme">深色</NRadio>
|
||||
</NRadioGroup>
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="清晰度" path="Quality">
|
||||
<NSelect v-model:value="formValue.Quality" :options="options"/>
|
||||
<NTooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<NIcon size="18" class="ml-1 text-gray-500">
|
||||
<HelpCircleOutline/>
|
||||
</NIcon>
|
||||
</template>
|
||||
视频号有效
|
||||
</NTooltip>
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="连接数" path="TaskNumber">
|
||||
<NInputNumber v-model:value="formValue.TaskNumber" :min="2" :max="64"/>
|
||||
<NTooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<NIcon size="18" class="ml-1 text-gray-500">
|
||||
<HelpCircleOutline/>
|
||||
</NIcon>
|
||||
</template>
|
||||
如不清楚请保持默认,通常CPU核心数*2,用于分片下载
|
||||
</NTooltip>
|
||||
</NFormItem>
|
||||
</div>
|
||||
|
||||
<NFormItem label="UserAgent" path="UserAgent">
|
||||
<NInput v-model:value="formValue.UserAgent" placeholder="默认UserAgent"/>
|
||||
<NTooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<NIcon size="20" class="pl-1">
|
||||
<HelpCircleOutline />
|
||||
<NIcon size="18" class="ml-1 text-gray-500">
|
||||
<HelpCircleOutline/>
|
||||
</NIcon>
|
||||
</template>
|
||||
<span>打开软件时动启用拦截</span>
|
||||
如不清楚请保持默认
|
||||
</NTooltip>
|
||||
</NFormItem>
|
||||
<NFormItem label="清晰度" path="Quality" size="small">
|
||||
<NSelect v-model:value="formValue.Quality" :options="options" class="w-64" />
|
||||
|
||||
<NFormItem label="Headers" path="Headers">
|
||||
<NInput v-model:value="formValue.UseHeaders" placeholder="User-Agent,Referer,Authorization,Cookie"/>
|
||||
<NTooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<NIcon size="20" class="pl-1">
|
||||
<HelpCircleOutline />
|
||||
<NIcon size="18" class="ml-1 text-gray-500">
|
||||
<HelpCircleOutline/>
|
||||
</NIcon>
|
||||
</template>
|
||||
<span>视频号有效</span>
|
||||
定义下载时可使用的header参数,逗号分割
|
||||
</NTooltip>
|
||||
</NFormItem>
|
||||
<NFormItem label="全量拦截" path="Quality" size="small">
|
||||
<NSwitch v-model:value="formValue.WxAction" />
|
||||
|
||||
<NFormItem label="拦截规则" path="MimeMap">
|
||||
<NInput
|
||||
v-model:value="MimeMap"
|
||||
type="textarea"
|
||||
rows="11"
|
||||
placeholder='{"content-type": { "Type": "分类名称","Suffix": "后缀"}}'
|
||||
/>
|
||||
<NTooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<NIcon size="20" class="pl-1">
|
||||
<HelpCircleOutline />
|
||||
<NIcon size="18" class="ml-1 text-gray-500">
|
||||
<HelpCircleOutline/>
|
||||
</NIcon>
|
||||
</template>
|
||||
<span>微信视频号是否全量拦截,否:只拦截视频详情</span>
|
||||
拦截规则JSON配置,不清楚请勿改动
|
||||
</NTooltip>
|
||||
</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">
|
||||
<HelpCircleOutline />
|
||||
</NIcon>
|
||||
</template>
|
||||
<span>可结合其他代理工具,用于访问国外网站、以及正常网络无法访问的资源(格式http://username:password@your.proxy.server:port)</span>
|
||||
</NTooltip>
|
||||
</NFormItem>
|
||||
<NFormItem label="下载代理" path="DownloadProxy" size="small">
|
||||
<NSwitch v-model:value="formValue.DownloadProxy" />
|
||||
<NTooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<NIcon size="20" class="pl-1">
|
||||
<HelpCircleOutline />
|
||||
</NIcon>
|
||||
</template>
|
||||
<span>进行下载时使用代理请求</span>
|
||||
</NTooltip>
|
||||
</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=""/>
|
||||
<NTooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<NIcon size="20" class="pl-1">
|
||||
<HelpCircleOutline />
|
||||
</NIcon>
|
||||
</template>
|
||||
<span>如不清楚请保持默认</span>
|
||||
</NTooltip>
|
||||
</NFormItem>
|
||||
<NFormItem label=" " path="UserAgent" size="small">
|
||||
<NButton strong secondary type="success" @click="save" class="w-20">保存</NButton>
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
</div>
|
||||
</template>
|
||||
@@ -175,15 +210,23 @@ const options = [
|
||||
|
||||
const formValue = ref<appType.Config>(Object.assign({}, store.globalConfig))
|
||||
|
||||
watch(()=>{
|
||||
return store.globalConfig.Theme
|
||||
}, ()=>{
|
||||
formValue.value.Theme = store.globalConfig.Theme
|
||||
const MimeMap = ref(formValue.value.MimeMap ? JSON.stringify(formValue.value.MimeMap, null, 2) : "")
|
||||
|
||||
watch(formValue.value, () => {
|
||||
store.setConfig(formValue.value)
|
||||
}, {deep: true})
|
||||
|
||||
watch(MimeMap, () => {
|
||||
store.setConfig({
|
||||
MimeMap: JSON.parse(MimeMap.value)
|
||||
})
|
||||
})
|
||||
|
||||
const handleChange = (e: Event)=>{
|
||||
formValue.value.Theme = (e.target as HTMLInputElement).value
|
||||
}
|
||||
watch(() => {
|
||||
return store.globalConfig.Theme
|
||||
}, () => {
|
||||
formValue.value.Theme = store.globalConfig.Theme
|
||||
})
|
||||
|
||||
const selectDir = () => {
|
||||
appApi.openDirectoryDialog().then((res: any) => {
|
||||
@@ -194,9 +237,4 @@ const selectDir = () => {
|
||||
window?.$message?.error(err)
|
||||
});
|
||||
}
|
||||
|
||||
const save = () => {
|
||||
store.setConfig(formValue.value)
|
||||
window?.$message?.success("保存成功")
|
||||
}
|
||||
</script>
|
||||
2
frontend/wailsjs/runtime/runtime.d.ts
vendored
2
frontend/wailsjs/runtime/runtime.d.ts
vendored
@@ -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.
|
||||
|
||||
14
go.mod
14
go.mod
@@ -9,8 +9,8 @@ require (
|
||||
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 (
|
||||
@@ -30,13 +30,13 @@ require (
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/samber/lo v1.47.0 // 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.18 // 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/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
|
||||
)
|
||||
|
||||
28
go.sum
28
go.sum
@@ -51,8 +51,8 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc
|
||||
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.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
|
||||
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
|
||||
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=
|
||||
@@ -63,17 +63,17 @@ github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQ
|
||||
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.18 h1:SSSCoLA+MYikSp1U0WmvELF/4c3x5kH8Vi31TKyZ4yk=
|
||||
github.com/wailsapp/go-webview2 v1.0.18/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
|
||||
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=
|
||||
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/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=
|
||||
@@ -81,12 +81,12 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
2
main.go
2
main.go
@@ -61,7 +61,7 @@ func main() {
|
||||
|_| \___| |___/ \__,_| \___/ \_/\_/ |_| |_| |_| \___/ \__ ,_| \__,_| \___| |_|`
|
||||
|
||||
log.Println(logo)
|
||||
fmt.Println("version", app.Version)
|
||||
fmt.Println("version:", app.Version)
|
||||
fmt.Println("lockfile:", app.LockFile)
|
||||
app.Startup(ctx)
|
||||
},
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
"info": {
|
||||
"companyName": "res-downloader",
|
||||
"productName": "res-downloader",
|
||||
"productVersion": "3.0.3",
|
||||
"productVersion": "3.0.6",
|
||||
"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."
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user