feat: support multi-threaded download

This commit is contained in:
ltaoo committed 2025-10-18 18:03:31 +08:00
1 parent b352181270
commit 53636d465e
11 files changed
+636 -462

No files matched your search

+60
View File
@@ -0,0 +1,60 @@
package cmd
import (
"fmt"
"os"
"wx_channel/pkg/decrypt"
"github.com/spf13/cobra"
)
var (
file_path string
video_decrypt_key2 int
)
var decrypt_cmd = &cobra.Command{
Use: "decrypt",
Short: "解密视频",
Long: "使用 key 对本地加密视频进行解密",
Run: func(cmd *cobra.Command, args []string) {
command := cmd.Name()
if command != "decrypt" {
return
}
decrypt_command(DecryptCOmmandArgs{
Filepath: file_path,
DecryptKey: video_decrypt_key2,
})
},
}
func init() {
decrypt_cmd.Flags().StringVar(&file_path, "filepath", "", "视频地址(必需)")
decrypt_cmd.Flags().IntVar(&video_decrypt_key2, "key", 0, "解密密钥(必需)")
decrypt_cmd.MarkFlagRequired("filepath")
root_cmd.AddCommand(decrypt_cmd)
}
type DecryptCOmmandArgs struct {
Filepath string
DecryptKey int
}
func decrypt_command(args DecryptCOmmandArgs) {
fmt.Printf("开始对文件解密 %s\n", args.Filepath)
length := uint32(131072)
key := uint64(args.DecryptKey)
data, err := os.ReadFile(args.Filepath)
if err != nil {
fmt.Printf("[ERROR]读取已下载的文件失败 %v\n", err.Error())
return
}
decrypt.DecryptData(data, length, key)
err = os.WriteFile(args.Filepath, data, 0644)
if err != nil {
fmt.Printf("[ERROR]写入文件失败 %v\n", err.Error())
return
}
fmt.Printf("解密完成 %s\n", args.Filepath)
}
+107
View File
@@ -0,0 +1,107 @@
package cmd
import (
"fmt"
"os"
"path/filepath"
"strconv"
"time"
"github.com/spf13/cobra"
"wx_channel/pkg/decrypt"
"wx_channel/pkg/download"
)
var (
video_url string
filename string
video_decrypt_key int
)
var download_cmd = &cobra.Command{
Use: "download",
Short: "下载视频",
Long: "从指定URL下载视频文件",
Run: func(cmd *cobra.Command, args []string) {
command := cmd.Name()
if command != "download" {
return
}
download_command(DownloadCommandArgs{
URL: video_url,
DecryptKey: video_decrypt_key,
Filename: filename,
})
},
}
func init() {
now := int(time.Now().Unix())
download_cmd.Flags().StringVar(&video_url, "url", "", "视频URL(必需)")
download_cmd.Flags().IntVar(&video_decrypt_key, "key", 0, "解密密钥(未加密的视频不用传该参数)")
download_cmd.Flags().StringVar(&filename, "filename", strconv.Itoa(now)+".mp4", "下载后的文件名")
download_cmd.MarkFlagRequired("url")
root_cmd.AddCommand(download_cmd)
}
type DownloadCommandArgs struct {
URL string
Filename string
DecryptKey int
}
func download_command(args DownloadCommandArgs) {
url := args.URL
homedir, err := os.UserHomeDir()
if err != nil {
fmt.Printf("[ERROR]获取下载路径失败 %v\n", err.Error())
return
}
tmp_filename := "tmp_wx_" + strconv.Itoa(int(time.Now().Unix()))
tmp_dest_filepath := filepath.Join(homedir, "Downloads", tmp_filename)
dest_filepath := filepath.Join(homedir, "Downloads", args.Filename)
if err := download.MultiThreadingDownload(url, 4, tmp_dest_filepath, tmp_dest_filepath); err != nil {
fmt.Printf("[ERROR]%v\n", err.Error())
return
}
// elapsed := time.Since(start_time)
// speed := float64(file_size) / elapsed.Seconds() / 1024 / 1024
fmt.Printf("\n临时文件下载完成!\n")
// fmt.Printf("文件大小: %.2f MB\n", float64(file_size)/1024/1024)
// fmt.Printf("耗时: %.2f 秒\n", elapsed.Seconds())
// fmt.Printf("平均速度: %.2f MB/s\n", speed)
fmt.Println()
if args.DecryptKey != 0 {
fmt.Printf("开始对临时文件解密 %s\n", tmp_dest_filepath)
length := uint32(131072)
key := uint64(args.DecryptKey)
data, err := os.ReadFile(tmp_dest_filepath)
if err != nil {
fmt.Printf("[ERROR]读取临时文件失败 %v\n", err.Error())
return
}
decrypt.DecryptData(data, length, key)
err = os.WriteFile(dest_filepath, data, 0644)
if err != nil {
fmt.Printf("[ERROR]写入文件失败 %v\n", err.Error())
return
}
fmt.Printf("删除临时文件 %s\n", tmp_dest_filepath)
if err := os.Remove(tmp_dest_filepath); err != nil {
if os.IsNotExist(err) {
fmt.Println("[ERROR]临时文件不存在")
} else if os.IsPermission(err) {
fmt.Println("[ERROR]没有权限删除临时文件")
} else {
fmt.Printf("[ERROR]临时文件删除失败 %v\n", err.Error())
}
}
fmt.Printf("解密完成,文件路径为 %s\n", dest_filepath)
return
}
fmt.Printf("下载完成,文件路径为 %s\n", dest_filepath)
}
+168
View File
@@ -0,0 +1,168 @@
package cmd
import (
"fmt"
"net/http"
"os"
"os/signal"
"runtime"
"strconv"
"syscall"
"time"
"github.com/fatih/color"
"github.com/spf13/cobra"
"wx_channel/internal/application"
"wx_channel/internal/handler"
"wx_channel/pkg/certificate"
"wx_channel/pkg/echo"
"wx_channel/pkg/proxy"
)
var (
device string
port int
debug bool
version string
files *application.BizFiles
cert_file_name string
)
var root_cmd = &cobra.Command{
Use: "wx_video_download",
Short: "启动下载程序",
Long: "\n启动后将对网络请求进行代理,在微信视频号详情页面注入下载按钮",
Run: func(cmd *cobra.Command, args []string) {
root_command(RootCommandArg{
Device: device,
Port: port,
Debug: debug,
})
},
}
func init() {
root_cmd.Flags().StringVar(&device, "dev", "", "代理服务器网络设备")
root_cmd.Flags().IntVar(&port, "port", 2023, "代理服务器端口")
root_cmd.Flags().BoolVar(&debug, "debug", false, "是否开启调试")
}
func Initialize(app_ver string, cert_file_name string, files *application.BizFiles) {
version = app_ver
cert_file_name = cert_file_name
files = files
}
func Register(cmd *cobra.Command) {
root_cmd.AddCommand(cmd)
}
func Execute() error {
cobra.MousetrapHelpText = ""
return root_cmd.Execute()
}
type RootCommandArg struct {
Device string
Port int
Debug bool
}
func root_command(args RootCommandArg) {
os_env := runtime.GOOS
signal_chan := make(chan os.Signal, 1)
// Notify the signal channel on SIGINT (Ctrl+C) and SIGTERM
signal.Notify(signal_chan, syscall.SIGINT, syscall.SIGTERM)
go func() {
sig := <-signal_chan
fmt.Printf("\n正在关闭服务...%v\n\n", sig)
arg := proxy.ProxySettings{
Device: args.Device,
Hostname: "127.0.0.1",
Port: strconv.Itoa(args.Port),
}
err := proxy.DisableProxy(arg)
if err != nil {
fmt.Printf("⚠️ 关闭系统代理失败: %v\n", err)
}
os.Exit(0)
}()
fmt.Printf("\nv" + version)
fmt.Printf("\n问题反馈 https://github.com/ltaoo/wx_channels_download/issues\n")
existing, err1 := certificate.CheckHasCertificate(cert_file_name)
if err1 != nil {
fmt.Printf("\nERROR %v\v", err1.Error())
fmt.Printf("按 Ctrl+C 退出...\n")
select {}
}
if !existing {
fmt.Printf("\n\n正在安装证书...\n")
err := certificate.InstallCertificate(files.CertFile)
time.Sleep(3 * time.Second)
if err != nil {
fmt.Printf("\nERROR %v\n", err.Error())
fmt.Printf("按 Ctrl+C 退出...\n")
select {}
}
}
biz := application.NewBiz(version, files)
echo, err := echo.NewEcho(files.CertFile, files.PrivateKeyFile)
if err != nil {
fmt.Printf("\nERROR %v\n", err.Error())
fmt.Printf("按 Ctrl+C 退出...\n")
select {}
}
echo.SetHTTPHandler(handler.HandleHttpRequestEcho(biz))
// Sunny.SetPort(args.Port).Start()
go func() {
server := &http.Server{
Addr: ":" + strconv.Itoa(port),
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
echo.ServeHTTP(w, r)
}),
}
fmt.Printf("Proxy server listening on :%v\n", port)
err := server.ListenAndServe()
if err != nil {
fmt.Printf("\nERROR %v\n", err.Error())
fmt.Printf("按 Ctrl+C 退出...\n")
}
}()
if os_env == "windows" {
// ok := Sunny.OpenDrive(true)
// if !ok {
// fmt.Printf("\nERROR 启动进程代理失败,检查是否以管理员身份运行\n")
// fmt.Printf("按 Ctrl+C 退出...\n")
// select {}
// }
// Sunny.ProcessAddName("WeChatAppEx.exe")
err = proxy.EnableProxy(proxy.ProxySettings{
Device: args.Device,
Hostname: "127.0.0.1",
Port: strconv.Itoa(args.Port),
})
if err != nil {
fmt.Printf("\nERROR 设置代理失败 %v\n", err.Error())
fmt.Printf("按 Ctrl+C 退出...\n")
select {}
}
} else {
err = proxy.EnableProxy(proxy.ProxySettings{
Device: args.Device,
Hostname: "127.0.0.1",
Port: strconv.Itoa(args.Port),
})
if err != nil {
fmt.Printf("\nERROR 设置代理失败 %v\n", err.Error())
fmt.Printf("按 Ctrl+C 退出...\n")
select {}
}
}
color.Green(fmt.Sprintf("\n\n服务已正确启动,请打开需要下载的视频号页面进行下载"))
fmt.Println("\n\n服务正在运行,按 Ctrl+C 退出...")
select {}
}
+44
View File
@@ -0,0 +1,44 @@
package cmd
import (
"fmt"
"github.com/fatih/color"
"github.com/spf13/cobra"
"wx_channel/pkg/certificate"
"wx_channel/pkg/proxy"
)
var uninstall_certificate_cmd = &cobra.Command{
Use: "uninstall",
Short: "删除证书",
Long: "删除初始化时自动安装的证书",
Run: func(cmd *cobra.Command, args []string) {
command := cmd.Name()
if command != "uninstall" {
return
}
uninstall_certificate_command(UninstallCertificateCommandArgs{})
},
}
func init() {
root_cmd.AddCommand(uninstall_certificate_cmd)
}
type UninstallCertificateCommandArgs struct {
}
func uninstall_certificate_command(args UninstallCertificateCommandArgs) {
settings := proxy.ProxySettings{}
if err := proxy.DisableProxy(settings); err != nil {
fmt.Printf("\nERROR 取消代理失败 %v\n", err.Error())
return
}
if err := certificate.UninstallCertificate(cert_file_name); err != nil {
fmt.Printf("\nERROR 删除根证书失败 %v\n", err.Error())
return
}
color.Green(fmt.Sprintf("\n\n删除根证书 '%v' 成功\n", cert_file_name))
}
+13 -24
View File
@@ -16,17 +16,15 @@ import (
"wx_channel/pkg/proxy"
)
var RootCertificateName = "SunnyNet"
type BizFiles struct {
Cert []byte
PrivateKey []byte
JSFileSave []byte
JSZip []byte
JSPageSpy1 []byte
JSPageSpy2 []byte
JSError []byte
JSMain []byte
CertFile []byte
PrivateKeyFile []byte
JSFileSaver []byte
JSZip []byte
JSPageSpy1 []byte
JSPageSpy2 []byte
JSError []byte
JSMain []byte
}
type Biz struct {
@@ -35,36 +33,27 @@ type Biz struct {
Debug bool
}
func NewBiz(version string, cert []byte, private_key []byte, js_file_save []byte, js_zip []byte, js_page_spy1 []byte, js_page_spy2 []byte, js_error []byte, js_main []byte) *Biz {
func NewBiz(version string, files *BizFiles) *Biz {
return &Biz{
Version: version,
Files: &BizFiles{
Cert: cert,
PrivateKey: private_key,
JSFileSave: js_file_save,
JSZip: js_zip,
JSPageSpy1: js_page_spy1,
JSPageSpy2: js_page_spy2,
JSMain: js_main,
JSError: js_error,
},
Files: files,
}
}
func (a *Biz) SetDebug(debug bool) {
a.Debug = debug
}
func (a *Biz) UninstallCertificate() {
func (a *Biz) UninstallCertificate(cert_file_name string) {
settings := proxy.ProxySettings{}
if err := proxy.DisableProxy(settings); err != nil {
fmt.Printf("\nERROR 取消代理失败 %v\n", err.Error())
return
}
if err := certificate.UninstallCertificate(RootCertificateName); err != nil {
if err := certificate.UninstallCertificate(cert_file_name); err != nil {
fmt.Printf("\nERROR 删除根证书失败 %v\n", err.Error())
return
}
color.Green(fmt.Sprintf("\n\n删除根证书 '%v' 成功\n", RootCertificateName))
color.Green(fmt.Sprintf("\n\n删除根证书 '%v' 成功\n", cert_file_name))
}
type DecryptCOmmandArgs struct {
+15 -345
View File
@@ -3,32 +3,16 @@ package main
import (
_ "embed"
"fmt"
"io"
"net/http"
"os"
"os/signal"
"path/filepath"
"runtime"
"strconv"
"syscall"
"time"
"github.com/fatih/color"
"github.com/spf13/cobra"
"wx_channel/cmd"
"wx_channel/internal/application"
"wx_channel/internal/handler"
"wx_channel/pkg/certificate"
"wx_channel/pkg/decrypt"
"wx_channel/pkg/echo"
"wx_channel/pkg/proxy"
)
//go:embed certs/SunnyRoot.cer
var cert_data []byte
var cert_file []byte
//go:embed certs/private.key
var private_key_data []byte
var private_key_file []byte
//go:embed lib/FileSaver.min.js
var js_file_saver []byte
@@ -48,338 +32,24 @@ var js_error []byte
//go:embed inject/main.js
var js_main []byte
var version = "250913"
var DefaultPort = 2023
var RootCertificateName = "SunnyNet"
var AppVer = "250913"
func main() {
cobra.MousetrapHelpText = ""
var (
device string
port int
debug bool
)
root_cmd := &cobra.Command{
Use: "wx_video_download",
Short: "启动下载程序",
Long: "启动后将对网络请求进行代理,在微信视频号详情页面注入下载按钮",
Run: func(cmd *cobra.Command, args []string) {
root_command(RootCommandArg{
Device: device,
Port: port,
Debug: debug,
})
},
files := &application.BizFiles{
CertFile: cert_file,
PrivateKeyFile: private_key_file,
JSFileSaver: js_file_saver,
JSZip: js_zip,
JSPageSpy1: js_pagespy1,
JSPageSpy2: js_pagespy2,
JSMain: js_main,
JSError: js_error,
}
root_cmd.Flags().StringVar(&device, "dev", "", "代理服务器网络设备")
root_cmd.Flags().IntVar(&port, "port", DefaultPort, "代理服务器端口")
root_cmd.Flags().BoolVar(&debug, "debug", false, "是否开启调试")
uninstall_certificate_cmd := &cobra.Command{
Use: "uninstall",
Short: "删除证书",
Long: "删除初始化时自动安装的证书",
Run: func(cmd *cobra.Command, args []string) {
command := cmd.Name()
if command != "uninstall" {
return
}
uninstall_certificate_command(UninstallCertificateCommandArgs{})
},
}
var (
video_url string
filename string
video_decrypt_key int
)
download_cmd := &cobra.Command{
Use: "download",
Short: "下载视频",
Long: "从指定URL下载视频文件",
Run: func(cmd *cobra.Command, args []string) {
command := cmd.Name()
if command != "download" {
return
}
download_command(DownloadCommandArgs{
URL: video_url,
DecryptKey: video_decrypt_key,
Filename: filename,
})
},
}
now := int(time.Now().Unix())
download_cmd.Flags().StringVar(&video_url, "url", "", "视频URL(必需)")
download_cmd.Flags().IntVar(&video_decrypt_key, "key", 0, "解密密钥(未加密的视频不用传该参数)")
download_cmd.Flags().StringVar(&filename, "filename", strconv.Itoa(now)+".mp4", "下载后的文件名")
download_cmd.MarkFlagRequired("url")
var (
filepath string
video_decrypt_key2 int
)
decrypt_cmd := &cobra.Command{
Use: "decrypt",
Short: "解密视频",
Long: "使用 key 对本地加密视频进行解密",
Run: func(cmd *cobra.Command, args []string) {
command := cmd.Name()
if command != "decrypt" {
return
}
decrypt_command(DecryptCOmmandArgs{
Filepath: filepath,
DecryptKey: video_decrypt_key2,
})
},
}
decrypt_cmd.Flags().StringVar(&filepath, "filepath", "", "视频地址(必需)")
decrypt_cmd.Flags().IntVar(&video_decrypt_key2, "key", 0, "解密密钥(必需)")
decrypt_cmd.MarkFlagRequired("filepath")
root_cmd.AddCommand(uninstall_certificate_cmd)
root_cmd.AddCommand(download_cmd)
root_cmd.AddCommand(decrypt_cmd)
if err := root_cmd.Execute(); err != nil {
cmd.Initialize(AppVer, RootCertificateName, files)
if err := cmd.Execute(); err != nil {
fmt.Printf("初始化失败 %v", err.Error())
fmt.Printf("按 Ctrl+C 退出...\n")
select {}
}
}
type RootCommandArg struct {
Device string
Port int
Debug bool
}
func root_command(args RootCommandArg) {
os_env := runtime.GOOS
signal_chan := make(chan os.Signal, 1)
// Notify the signal channel on SIGINT (Ctrl+C) and SIGTERM
signal.Notify(signal_chan, syscall.SIGINT, syscall.SIGTERM)
go func() {
sig := <-signal_chan
fmt.Printf("\n正在关闭服务...%v\n\n", sig)
arg := proxy.ProxySettings{
Device: args.Device,
Hostname: "127.0.0.1",
Port: strconv.Itoa(args.Port),
}
err := proxy.DisableProxy(arg)
if err != nil {
fmt.Printf("⚠️ 关闭系统代理失败: %v\n", err)
}
os.Exit(0)
}()
fmt.Printf("\nv" + version)
fmt.Printf("\n问题反馈 https://github.com/ltaoo/wx_channels_download/issues\n")
existing, err1 := certificate.CheckHasCertificate(RootCertificateName)
if err1 != nil {
fmt.Printf("\nERROR %v\v", err1.Error())
fmt.Printf("按 Ctrl+C 退出...\n")
select {}
}
if !existing {
fmt.Printf("\n\n正在安装证书...\n")
err := certificate.InstallCertificate(cert_data)
time.Sleep(3 * time.Second)
if err != nil {
fmt.Printf("\nERROR %v\n", err.Error())
fmt.Printf("按 Ctrl+C 退出...\n")
select {}
}
}
biz := application.NewBiz(version, cert_data, private_key_data, js_file_saver, js_zip, js_pagespy1, js_pagespy2, js_error, js_main)
echo, err := echo.NewEcho(cert_data, private_key_data)
if err != nil {
fmt.Printf("\nERROR %v\n", err.Error())
fmt.Printf("按 Ctrl+C 退出...\n")
select {}
}
echo.SetHTTPHandler(handler.HandleHttpRequestEcho(biz))
// Sunny.SetPort(args.Port).Start()
go func() {
server := &http.Server{
Addr: ":" + strconv.Itoa(DefaultPort),
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
echo.ServeHTTP(w, r)
}),
}
fmt.Printf("Proxy server listening on :%v\n", DefaultPort)
err := server.ListenAndServe()
if err != nil {
fmt.Printf("\nERROR %v\n", err.Error())
fmt.Printf("按 Ctrl+C 退出...\n")
}
}()
if os_env == "windows" {
// ok := Sunny.OpenDrive(true)
// if !ok {
// fmt.Printf("\nERROR 启动进程代理失败,检查是否以管理员身份运行\n")
// fmt.Printf("按 Ctrl+C 退出...\n")
// select {}
// }
// Sunny.ProcessAddName("WeChatAppEx.exe")
} else {
err = proxy.EnableProxy(proxy.ProxySettings{
Device: args.Device,
Hostname: "127.0.0.1",
Port: strconv.Itoa(args.Port),
})
if err != nil {
fmt.Printf("\nERROR 设置代理失败 %v\n", err.Error())
fmt.Printf("按 Ctrl+C 退出...\n")
select {}
}
}
color.Green(fmt.Sprintf("\n\n服务已正确启动,请打开需要下载的视频号页面进行下载"))
fmt.Println("\n\n服务正在运行,按 Ctrl+C 退出...")
select {}
}
type UninstallCertificateCommandArgs struct {
}
func uninstall_certificate_command(args UninstallCertificateCommandArgs) {
settings := proxy.ProxySettings{}
if err := proxy.DisableProxy(settings); err != nil {
fmt.Printf("\nERROR 取消代理失败 %v\n", err.Error())
return
}
if err := certificate.UninstallCertificate(RootCertificateName); err != nil {
fmt.Printf("\nERROR 删除根证书失败 %v\n", err.Error())
return
}
color.Green(fmt.Sprintf("\n\n删除根证书 '%v' 成功\n", RootCertificateName))
}
type DownloadCommandArgs struct {
URL string
Filename string
DecryptKey int
}
func download_command(args DownloadCommandArgs) {
url := args.URL
homedir, err := os.UserHomeDir()
if err != nil {
fmt.Printf("[ERROR]获取下载路径失败 %v\n", err.Error())
return
}
tmp_filename := "tmp_wx_" + strconv.Itoa(int(time.Now().Unix()))
tmp_dest_filepath := filepath.Join(homedir, "Downloads", tmp_filename)
dest_filepath := filepath.Join(homedir, "Downloads", args.Filename)
resp, err := http.Get(url)
if err != nil {
fmt.Printf("[ERROR]下载失败 %v\n", err.Error())
return
}
defer resp.Body.Close()
file, err := os.Create(tmp_dest_filepath)
if err != nil {
fmt.Printf("[ERROR]下载文件失败 %v\n", err.Error())
os.Exit(0)
return
}
defer file.Close()
content_length := resp.Header.Get("Content-Length")
total_size := int64(-1)
if content_length != "" {
total_size, _ = strconv.ParseInt(content_length, 10, 64)
}
buf := make([]byte, 32*1024) // 32KB buffer
var downloaded int64 = 0
for {
n, err := resp.Body.Read(buf)
if n > 0 {
_, werr := file.Write(buf[:n])
if werr != nil {
fmt.Printf("[ERROR]写入文件失败 %v\n", werr.Error())
return
}
downloaded += int64(n)
if total_size > 0 {
percent := float64(downloaded) / float64(total_size) * 100
fmt.Printf("\r\033[K已下载: %d/%d 字节 (%.2f%%)", downloaded, total_size, percent)
} else {
fmt.Printf("\r\033[K已下载: %d 字节", downloaded)
}
}
if err == io.EOF {
break
}
if err != nil {
fmt.Printf("[ERROR]下载文件失败2 %v\n", err.Error())
return
}
}
fmt.Println()
if args.DecryptKey != 0 {
fmt.Printf("开始对文件解密 %s\n", tmp_dest_filepath)
length := uint32(131072)
enclen_str := resp.Header.Get("X-enclen")
if enclen_str != "" {
v, err := strconv.ParseUint(enclen_str, 10, 32)
if err == nil {
length = uint32(v)
}
}
key := uint64(args.DecryptKey)
data, err := os.ReadFile(tmp_dest_filepath)
if err != nil {
fmt.Printf("[ERROR]读取已下载的文件失败 %v\n", err.Error())
return
}
decrypt.DecryptData(data, length, key)
err = os.WriteFile(dest_filepath, data, 0644)
if err != nil {
fmt.Printf("[ERROR]写入文件失败 %v\n", err.Error())
return
}
file.Close()
if err := os.Remove(tmp_dest_filepath); err != nil {
if os.IsNotExist(err) {
fmt.Println("[ERROR]临时文件不存在")
} else if os.IsPermission(err) {
fmt.Println("[ERROR]没有权限删除临时文件")
} else {
fmt.Printf("[ERROR]临时文件删除失败 %v\n", err.Error())
}
}
fmt.Printf("解密完成,文件路径为 %s\n", dest_filepath)
return
}
fmt.Printf("下载完成,文件路径为 %s\n", dest_filepath)
}
type DecryptCOmmandArgs struct {
Filepath string
DecryptKey int
}
func decrypt_command(args DecryptCOmmandArgs) {
fmt.Printf("开始对文件解密 %s\n", args.Filepath)
length := uint32(131072)
key := uint64(args.DecryptKey)
data, err := os.ReadFile(args.Filepath)
if err != nil {
fmt.Printf("[ERROR]读取已下载的文件失败 %v\n", err.Error())
return
}
decrypt.DecryptData(data, length, key)
err = os.WriteFile(args.Filepath, data, 0644)
if err != nil {
fmt.Printf("[ERROR]写入文件失败 %v\n", err.Error())
return
}
fmt.Printf("解密完成 %s\n", args.Filepath)
}
+156 -61
View File
@@ -1,6 +1,7 @@
package download
import (
"bytes"
"crypto/tls"
"fmt"
"io"
@@ -11,6 +12,8 @@ import (
"time"
)
var file_mutex sync.Mutex
type FileDownloadProgress struct {
Current int64
Total int64
@@ -96,6 +99,49 @@ func progress_bar(percentage float64, width int) string {
return bar
}
func calculate_chunks(total_size, preferred_chunk_size int64) []struct{ start, end int64 } {
const (
minChunkSize = 1 * 1024 * 1024 // 1MB最小分块
maxChunks = 8 // 最大分块数
minChunks = 2 // 最小分块数
)
// 计算初始分块数
num_chunks := total_size / preferred_chunk_size
if num_chunks < minChunks {
num_chunks = minChunks
} else if num_chunks > maxChunks {
num_chunks = maxChunks
}
// 重新计算分块大小
chunk_size := total_size / num_chunks
// 确保分块不小于最小值
if chunk_size < minChunkSize {
chunk_size = minChunkSize
num_chunks = total_size / chunk_size
if num_chunks == 0 {
num_chunks = 1
}
}
var chunks []struct{ start, end int64 }
for i := int64(0); i < num_chunks; i++ {
start := i * chunk_size
end := start + chunk_size - 1
// 最后一个分块包含剩余所有数据
if i == num_chunks-1 {
end = total_size - 1
}
chunks = append(chunks, struct{ start, end int64 }{start, end})
}
return chunks
}
// 带进度显示的文件分块下载
func download_part_with_progress(url string, file *os.File, start, end int64, thread_idx int, progress_chan chan<- FileDownloadProgress) error {
client := &http.Client{Timeout: 0} // 无超时限制
@@ -121,12 +167,6 @@ func download_part_with_progress(url string, file *os.File, start, end int64, th
return fmt.Errorf("服务器返回错误状态码: %d", resp.StatusCode)
}
// 定位到文件的指定位置
// _, err = file.Seek(start, io.SeekStart)
// if err != nil {
// return err
// }
// 创建带进度统计的Reader
total_size := end - start + 1
progress_reader := &ProgressReader{
@@ -136,12 +176,23 @@ func download_part_with_progress(url string, file *os.File, start, end int64, th
Channel: progress_chan,
}
buf := new(bytes.Buffer)
if _, err := io.Copy(buf, progress_reader); err != nil {
return err
}
// 将下载的数据写入文件的指定位置
_, err = io.Copy(file, progress_reader)
// 同步写入文件
file_mutex.Lock()
defer file_mutex.Unlock()
// 定位到文件的指定位置
_, err = file.Seek(start, io.SeekStart)
if err != nil {
return err
}
if _, err := file.Write(buf.Bytes()); err != nil {
return err
}
return nil
}
@@ -191,81 +242,134 @@ func (pr *ProgressReader) Read(p []byte) (int, error) {
return n, err
}
func download(url string, threads int, tmp_dest_filepath string) {
type PartialFileDownloadProgress struct {
DownloadedSize int64
TotalSize int64
Percent float64
}
func SingleThreadingDownload(url string, dest_filepath string, on_progress func(progress *PartialFileDownloadProgress)) error {
resp, err := http.Get(url)
if err != nil {
return fmt.Errorf("下载失败 %v", err.Error())
}
defer resp.Body.Close()
file, err := os.Create(dest_filepath)
if err != nil {
return fmt.Errorf("创建文件失败 %v", err.Error())
}
defer file.Close()
content_length := resp.Header.Get("Content-Length")
total_size := int64(-1)
if content_length != "" {
total_size, _ = strconv.ParseInt(content_length, 10, 64)
}
buf := make([]byte, 32*1024) // 32KB buffer
var downloaded int64 = 0
for {
n, err := resp.Body.Read(buf)
if n > 0 {
_, werr := file.Write(buf[:n])
if werr != nil {
return fmt.Errorf("写入文件失败 %v", werr.Error())
}
downloaded += int64(n)
if total_size > 0 {
percent := float64(downloaded) / float64(total_size) * 100
on_progress(&PartialFileDownloadProgress{
DownloadedSize: downloaded,
TotalSize: total_size,
Percent: percent,
})
// fmt.Printf("\r\033[K已下载: %d/%d 字节 (%.2f%%)", downloaded, total_size, percent)
} else {
on_progress(&PartialFileDownloadProgress{
DownloadedSize: downloaded,
TotalSize: 0,
Percent: 0,
})
// fmt.Printf("\r\033[K已下载: %d 字节", downloaded)
}
}
if err == io.EOF {
break
}
if err != nil {
return fmt.Errorf("读取文件流失败 %v", err.Error())
}
}
return nil
}
func MultiThreadingDownload(url string, threads int, dest_filepath string, tmp_dest_filepath string) error {
tr := &http.Transport{
TLSNextProto: make(map[string]func(authority string, c *tls.Conn) http.RoundTripper),
}
client := &http.Client{Transport: tr, Timeout: 30 * time.Second}
// 发送HEAD请求获取文件信息
resp, err := client.Head(url)
if err != nil {
fmt.Printf("[ERROR]获取文件信息失败 %v\n", err.Error())
os.Exit(0)
return
return fmt.Errorf("获取文件信息失败 %v", err.Error())
}
defer resp.Body.Close()
fmt.Print("\033c")
// 检查是否支持断点续传
if resp.Header.Get("Accept-Ranges") != "bytes" {
fmt.Printf("[ERROR]服务器不支持断点续传")
os.Exit(0)
return
return fmt.Errorf("服务器不支持并发下载")
}
content_length := resp.Header.Get("Content-Length")
if content_length == "" {
fmt.Printf("[ERROR]无法获取文件大小")
os.Exit(0)
return
return fmt.Errorf("无法获取总文件大小,服务器不支持并发下载")
}
file_size, err := strconv.ParseInt(content_length, 10, 64)
if err != nil {
fmt.Printf("[ERROR]解析文件大小失败: %v", err)
return
return fmt.Errorf("解析文件大小失败: %v", err)
}
fmt.Printf("文件大小: %.2f MB\n", float64(file_size)/1024/1024)
start_time := time.Now()
// start_time := time.Now()
var wg sync.WaitGroup
errors := make(chan error, threads)
// 计算每个分块的大小
part_size := file_size / int64(threads)
// remainder := file_size % int64(threads)
chunks := calculate_chunks(file_size, part_size)
// 创建进度通道,每个线程一个
progress_chans := make([]chan FileDownloadProgress, threads)
progress_chans := make([]chan FileDownloadProgress, len(chunks))
for i := range progress_chans {
progress_chans[i] = make(chan FileDownloadProgress, 10)
}
// 计算每个分块的大小
part_size := file_size / int64(threads)
remainder := file_size % int64(threads)
// 启动进度显示器
stop_progress := make(chan bool)
go display_progress(progress_chans, stop_progress)
file, err := os.Create(dest_filepath)
if err != nil {
return fmt.Errorf("创建文件失败 %v\n", err)
}
defer file.Close()
if err := file.Truncate(file_size); err != nil {
return fmt.Errorf("设置文件大小失败: %v", err)
}
// fmt.Println("the chunk size is", len(chunks))
// 启动并发下载
for i := 0; i < threads; i++ {
for i, chunk := range chunks {
wg.Add(1)
// 计算当前分块的起始和结束位置
start := int64(i) * part_size
end := start + part_size - 1
// 最后一个线程处理剩余的部分
if i == threads-1 {
end += remainder
}
// fmt.Println(i, chunk)
go func(thread_idx int, start, end int64) {
defer wg.Done()
file, err := os.Create(tmp_dest_filepath + "_" + strconv.Itoa(thread_idx))
if err != nil {
errors <- fmt.Errorf("创建文件 %d 失败: %v", thread_idx+1, err)
return
}
defer file.Close()
// file, err := os.Create(tmp_dest_filepath + "_" + strconv.Itoa(thread_idx))
// if err != nil {
// errors <- fmt.Errorf("创建文件 %d 失败: %v", thread_idx+1, err)
// return
// }
// defer file.Close()
if err := download_part_with_progress(
url,
file,
@@ -276,7 +380,7 @@ func download(url string, threads int, tmp_dest_filepath string) {
); err != nil {
errors <- fmt.Errorf("线程 %d 下载失败: %v", thread_idx+1, err)
}
}(i, start, end)
}(i, chunk.start, chunk.end)
}
// 等待所有下载完成
wg.Wait()
@@ -285,19 +389,10 @@ func download(url string, threads int, tmp_dest_filepath string) {
// 检查错误
if len(errors) > 0 {
for err := range errors {
fmt.Println(err)
}
fmt.Println("下载失败")
return
// for err := range errors {
// fmt.Println(err)
// }
return fmt.Errorf("下载失败,共%v个错误", len(errors))
}
elapsed := time.Since(start_time)
speed := float64(file_size) / elapsed.Seconds() / 1024 / 1024
fmt.Printf("\n下载完成!\n")
// fmt.Printf("文件名: %s\n", tmp_filename)
fmt.Printf("文件大小: %.2f MB\n", float64(file_size)/1024/1024)
fmt.Printf("耗时: %.2f 秒\n", elapsed.Seconds())
fmt.Printf("平均速度: %.2f MB/s\n", speed)
return nil
}
+20 -2
View File
@@ -12,10 +12,28 @@ type HardwarePort struct {
Interface string
}
func merge_default_settings(p ProxySettings) ProxySettings {
if p.Device == "" {
p.Device = "Wi-Fi" // 默认使用 Wi-Fi 设备
device, err := get_network_interfaces()
if err == nil {
p.Device = device.Port
}
}
if p.Hostname == "" {
p.Hostname = "127.0.0.1"
}
if p.Port == "" {
p.Port = "2023"
}
return p
}
func EnableProxy(arg ProxySettings) error {
return enableProxy(arg)
return enable_proxy(arg)
}
func DisableProxy(arg ProxySettings) error {
return disableProxy(arg)
return disable_proxy(arg)
}
+5 -23
View File
@@ -9,26 +9,8 @@ import (
"strings"
)
func mergeDefaultSettings(p ProxySettings) ProxySettings {
if p.Device == "" {
p.Device = "Wi-Fi" // 默认使用 Wi-Fi 设备
device, err := getNetworkInterfaces()
if err == nil {
p.Device = device.Port
}
}
if p.Hostname == "" {
p.Hostname = "127.0.0.1"
}
if p.Port == "" {
p.Port = "2023"
}
return p
}
func enableProxy(args ProxySettings) error {
args = mergeDefaultSettings(args)
func enable_proxy(args ProxySettings) error {
args = merge_default_settings(args)
cmd1 := exec.Command("networksetup", "-setwebproxy", args.Device, args.Hostname, args.Port)
_, err1 := cmd1.Output()
if err1 != nil {
@@ -42,8 +24,8 @@ func enableProxy(args ProxySettings) error {
return nil
}
func disableProxy(args ProxySettings) error {
args = mergeDefaultSettings(args)
func disable_proxy(args ProxySettings) error {
args = merge_default_settings(args)
cmd1 := exec.Command("networksetup", "-setwebproxystate", args.Device, "off")
_, err1 := cmd1.Output()
if err1 != nil {
@@ -57,7 +39,7 @@ func disableProxy(args ProxySettings) error {
return nil
}
func getNetworkInterfaces() (*HardwarePort, error) {
func get_network_interfaces() (*HardwarePort, error) {
// 获取所有硬件端口信息
cmd := exec.Command("networksetup", "-listallhardwareports")
output, err := cmd.Output()
+7 -2
View File
@@ -3,6 +3,7 @@
package proxy
import (
"errors"
"fmt"
"os"
"os/exec"
@@ -11,7 +12,7 @@ import (
)
// EnableProxyInLinux sets GNOME / Deepin proxy with correct user context
func enableProxy(ps ProxySettings) error {
func enable_proxy(ps ProxySettings) error {
if ps.Hostname == "" {
ps.Hostname = "127.0.0.1"
}
@@ -76,7 +77,7 @@ func enableProxy(ps ProxySettings) error {
}
// DisableProxyInLinux 关闭 GNOME / Deepin 的系统代理
func disableProxy(arg ProxySettings) error {
func disable_proxy(arg ProxySettings) error {
loginUserBytes, err := exec.Command("logname").Output()
if err != nil {
return fmt.Errorf("获取登录用户失败(logname): %v", err)
@@ -118,3 +119,7 @@ func disableProxy(arg ProxySettings) error {
fmt.Println("✅ 已关闭系统代理(Linux)")
return nil
}
func get_network_interfaces() (*HardwarePort, error) {
return nil, errors.New("not support")
}
+41 -5
View File
@@ -2,12 +2,48 @@
package proxy
import "errors"
import (
"errors"
"fmt"
"os/exec"
)
func enableProxy(ps ProxySettings) error {
return errors.New("Windows 平台暂不支持")
func enable_proxy(args ProxySettings) error {
args = merge_default_settings(args)
// netsh winhttp set proxy 127.0.0.1:8080
path := `HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings`
proxy_server_url := fmt.Sprintf("%v:%v", args.Hostname, args.Port)
// # 启用代理
// Set-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings" -Name ProxyEnable -Value 1
// Set-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings" -Name ProxyServer -Value "127.0.0.1:8080"
// cmd1 := exec.Command("netsh", "winhttp", "set", "proxy", fmt.Sprintf("%v:%v", args.Hostname, args.Port))
cmd1 := exec.Command("Set-ItemProperty", "-Path", path, "-Name", "ProxyEnable", "-Value", "1")
// fmt.Sprintf("%v:%v", args.Hostname, args.Port)
_, err := cmd1.Output()
if err != nil {
return fmt.Errorf("设置 HTTP 代理失败,%v", err.Error())
}
cmd2 := exec.Command("Set-ItemProperty", "-Path", path, "-Name", "ProxyServer", "-Value", proxy_server_url)
_, err = cmd2.Output()
if err != nil {
return fmt.Errorf("设置 HTTP 代理失败,%v", err.Error())
}
return nil
}
func disableProxy(ps ProxySettings) error {
return errors.New("Windows 平台暂不支持")
func disable_proxy(args ProxySettings) error {
path := `HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings`
// # 禁用代理
// Set-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings" -Name ProxyEnable -Value 0
cmd1 := exec.Command("Set-ItemProperty", "-Path", path, "-Name", "ProxyEnable", "-Value", "0")
// fmt.Sprintf("%v:%v", args.Hostname, args.Port)
_, err := cmd1.Output()
if err != nil {
return fmt.Errorf("设置 HTTP 代理失败,%v", err.Error())
}
return nil
}
func get_network_interfaces() (*HardwarePort, error) {
return nil, errors.New("not support")
}