File size: 2,375 Bytes
cbe30d3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package proxy

import (
	"bufio"
	"context"
	"fmt"
	"math/rand"
	"net"
	"net/http"
	"os"
	"strings"
	"sync"

	"golang.org/x/net/proxy"

	"zai-proxy/internal/logger"
)

var (
	proxies []string
	mu      sync.RWMutex
)

// LoadProxies 从 proxies.txt 文件加载代理列表
// 格式: ip:port:username:password 或 ip:port
func LoadProxies(path string) {
	file, err := os.Open(path)
	if err != nil {
		logger.LogInfo("No proxies.txt found, running without proxy")
		return
	}
	defer file.Close()

	var loaded []string
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}
		loaded = append(loaded, line)
	}

	mu.Lock()
	proxies = loaded
	mu.Unlock()

	logger.LogInfo("Loaded %d proxies from %s", len(loaded), path)
}

type proxyInfo struct {
	addr     string
	username string
	password string
}

// getRandomProxyInfo 随机返回一个代理信息
func getRandomProxyInfo() *proxyInfo {
	mu.RLock()
	defer mu.RUnlock()

	if len(proxies) == 0 {
		return nil
	}

	line := proxies[rand.Intn(len(proxies))]
	parts := strings.Split(line, ":")

	switch len(parts) {
	case 2:
		return &proxyInfo{addr: fmt.Sprintf("%s:%s", parts[0], parts[1])}
	case 4:
		return &proxyInfo{
			addr:     fmt.Sprintf("%s:%s", parts[0], parts[1]),
			username: parts[2],
			password: parts[3],
		}
	default:
		logger.LogWarn("Invalid proxy format: %s", line)
		return nil
	}
}

// GetHTTPClient 返回一个配置了随机 SOCKS5 代理的 http.Client
func GetHTTPClient() *http.Client {
	info := getRandomProxyInfo()
	if info == nil {
		return &http.Client{}
	}

	logger.LogDebug("Using SOCKS5 proxy: %s", info.addr)

	var auth *proxy.Auth
	if info.username != "" {
		auth = &proxy.Auth{
			User:     info.username,
			Password: info.password,
		}
	}

	dialer, err := proxy.SOCKS5("tcp", info.addr, auth, proxy.Direct)
	if err != nil {
		logger.LogWarn("Failed to create SOCKS5 dialer: %v", err)
		return &http.Client{}
	}

	contextDialer, ok := dialer.(proxy.ContextDialer)
	if !ok {
		logger.LogWarn("SOCKS5 dialer does not support ContextDialer")
		return &http.Client{}
	}

	return &http.Client{
		Transport: &http.Transport{
			DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
				return contextDialer.DialContext(ctx, network, addr)
			},
		},
	}
}