File size: 4,138 Bytes
da590a7
 
 
 
 
 
f5fca4e
 
9628c68
da590a7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9628c68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f5fca4e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package utils

import (
	"context"
	"net"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"
)

// NewHTTPClientWithGoogleDNS creates an HTTP client that forces DNS resolution via 8.8.8.8
func NewHTTPClientWithGoogleDNS() *http.Client {
	dialer := &net.Dialer{
		Resolver: &net.Resolver{
			PreferGo: true,
			Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
				d := net.Dialer{
					Timeout: 10 * time.Second,
				}
				return d.DialContext(ctx, "udp", "8.8.8.8:53")
			},
		},
	}

	transport := &http.Transport{
		DialContext:           dialer.DialContext,
		ForceAttemptHTTP2:     true,
		MaxIdleConns:          100,
		IdleConnTimeout:       90 * time.Second,
		TLSHandshakeTimeout:   10 * time.Second,
		ExpectContinueTimeout: 1 * time.Second,
	}

	return &http.Client{
		Transport: transport,
	}
}

// NewIPv4OnlyHTTPClient creates an HTTP client that forces IPv4 only (disables IPv6)
func NewIPv4OnlyHTTPClient() *http.Client {
	dialer := &net.Dialer{
		Resolver: &net.Resolver{
			PreferGo: true,
			Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
				// Force IPv4 by dialing 8.8.8.8:53 (Google DNS)
				d := net.Dialer{
					Timeout: 10 * time.Second,
				}
				return d.DialContext(ctx, "udp", "8.8.8.8:53")
			},
		},
	}

	transport := &http.Transport{
		DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
			// Force IPv4 by replacing any IPv6 addresses with IPv4 lookup
			// If address contains colon (IPv6), resolve hostname separately and pick IPv4
			if strings.Contains(address, ":") {
				// Extract host from "host:port" or "[ipv6]:port"
				host, port, err := net.SplitHostPort(address)
				if err != nil {
					return nil, err
				}
				// Resolve host to IPv4 addresses only
				addrs, err := net.DefaultResolver.LookupIPAddr(ctx, host)
				if err != nil {
					return nil, err
				}
				// Find first IPv4 address
				for _, addr := range addrs {
					if addr.IP.To4() != nil {
						return dialer.DialContext(ctx, "tcp", net.JoinHostPort(addr.IP.String(), port))
					}
				}
				return nil, &net.DNSError{Err: "no IPv4 address found for " + host}
			}
			// Already IPv4 or hostname, use default dialer
			return dialer.DialContext(ctx, "tcp", address)
		},
		ForceAttemptHTTP2:     true,
		MaxIdleConns:          100,
		IdleConnTimeout:       90 * time.Second,
		TLSHandshakeTimeout:   10 * time.Second,
		ExpectContinueTimeout: 1 * time.Second,
	}

	return &http.Client{
		Transport: transport,
	}
}

// NewProxiedHTTPClient creates an HTTP client that uses a proxy if configured
// It respects standard proxy environment variables and adds proxy support for HTTPS
func NewProxiedHTTPClient() *http.Client {
	// Check for proxy configuration
	proxyURL := os.Getenv("TELEGRAM_PROXY_URL")
	if proxyURL == "" {
		proxyURL = os.Getenv("HTTPS_PROXY")
		if proxyURL == "" {
			proxyURL = os.Getenv("HTTP_PROXY")
		}
	}

	transport := &http.Transport{
		ForceAttemptHTTP2:     true,
		MaxIdleConns:          100,
		IdleConnTimeout:       90 * time.Second,
		TLSHandshakeTimeout:   10 * time.Second,
		ExpectContinueTimeout: 1 * time.Second,
	}

	// If proxy is configured, set it up
	if proxyURL != "" {
		u, err := url.Parse(proxyURL)
		if err != nil {
			// If proxy URL is invalid, log and continue without proxy
			// In production, we'd use a logger but this is a low-level utils function
			// So we'll just proceed without proxy
		} else {
			transport.Proxy = http.ProxyURL(u)
		}
	}

	return &http.Client{
		Transport: transport,
	}
}

// IsProxyOrNetworkError checks if an error indicates proxy or network policy block
func IsProxyOrNetworkError(err error) bool {
	if err == nil {
		return false
	}
	errStr := err.Error()
	// Common network/block indicators
	indicators := []string{
		"operation not permitted",
		"permission denied",
		"network is unreachable",
		"connection refused",
		"timeout",
		"no route to host",
		"i/o timeout",
		"dial tcp",
		"connect:",
		"proxy",
	}
	for _, indicator := range indicators {
		if strings.Contains(strings.ToLower(errStr), indicator) {
			return true
		}
	}
	return false
}