@@ -19,7 +19,9 @@ import (
19
19
"sync/atomic"
20
20
"time"
21
21
22
+ "github.com/gorilla/websocket"
22
23
"go.mau.fi/util/random"
24
+ "golang.org/x/net/proxy"
23
25
24
26
"go.mau.fi/whatsmeow/appstate"
25
27
waBinary "go.mau.fi/whatsmeow/binary"
@@ -155,8 +157,10 @@ type Client struct {
155
157
uniqueID string
156
158
idCounter atomic.Uint64
157
159
158
- proxy socket.Proxy
159
- http * http.Client
160
+ proxy Proxy
161
+ socksProxy proxy.Dialer
162
+ proxyOnlyLogin bool
163
+ http * http.Client
160
164
161
165
// This field changes the client to act like a Messenger client instead of a WhatsApp one.
162
166
//
@@ -249,19 +253,35 @@ func NewClient(deviceStore *store.Device, log waLog.Logger) *Client {
249
253
return cli
250
254
}
251
255
252
- // SetProxyAddress is a helper method that parses a URL string and calls SetProxy.
256
+ // SetProxyAddress is a helper method that parses a URL string and calls SetProxy or SetSOCKSProxy based on the URL scheme .
253
257
//
254
258
// Returns an error if url.Parse fails to parse the given address.
255
259
func (cli * Client ) SetProxyAddress (addr string ) error {
260
+ if addr == "" {
261
+ cli .SetProxy (nil )
262
+ return nil
263
+ }
256
264
parsed , err := url .Parse (addr )
257
265
if err != nil {
258
266
return err
259
267
}
260
- cli .SetProxy (http .ProxyURL (parsed ))
268
+ if parsed .Scheme == "http" || parsed .Scheme == "https" {
269
+ cli .SetProxy (http .ProxyURL (parsed ))
270
+ } else if parsed .Scheme == "socks5" {
271
+ px , err := proxy .FromURL (parsed , proxy .Direct )
272
+ if err != nil {
273
+ return err
274
+ }
275
+ cli .SetSOCKSProxy (px )
276
+ } else {
277
+ return fmt .Errorf ("unsupported proxy scheme %q" , parsed .Scheme )
278
+ }
261
279
return nil
262
280
}
263
281
264
- // SetProxy sets the proxy to use for WhatsApp web websocket connections and media uploads/downloads.
282
+ type Proxy = func (* http.Request ) (* url.URL , error )
283
+
284
+ // SetProxy sets a HTTP proxy to use for WhatsApp web websocket connections and media uploads/downloads.
265
285
//
266
286
// Must be called before Connect() to take effect in the websocket connection.
267
287
// If you want to change the proxy after connecting, you must call Disconnect() and then Connect() again manually.
@@ -281,9 +301,51 @@ func (cli *Client) SetProxyAddress(addr string) error {
281
301
// return mediaProxyURL, nil
282
302
// }
283
303
// })
284
- func (cli * Client ) SetProxy (proxy socket. Proxy ) {
304
+ func (cli * Client ) SetProxy (proxy Proxy ) {
285
305
cli .proxy = proxy
286
- cli .http .Transport .(* http.Transport ).Proxy = proxy
306
+ cli .socksProxy = nil
307
+ transport := cli .http .Transport .(* http.Transport )
308
+ transport .Proxy = proxy
309
+ transport .Dial = nil
310
+ transport .DialContext = nil
311
+ }
312
+
313
+ type SetProxyOptions struct {
314
+ // If NoWebsocket is true, the proxy won't be used for the websocket
315
+ NoWebsocket bool
316
+ // If NoMedia is true, the proxy won't be used for media uploads/downloads
317
+ NoMedia bool
318
+ }
319
+
320
+ // SetSOCKSProxy sets a SOCKS5 proxy to use for WhatsApp web websocket connections and media uploads/downloads.
321
+ //
322
+ // Same details as SetProxy apply, but using a different proxy for the websocket and media is not currently supported.
323
+ func (cli * Client ) SetSOCKSProxy (px proxy.Dialer , opts ... SetProxyOptions ) {
324
+ var opt SetProxyOptions
325
+ if len (opts ) > 0 {
326
+ opt = opts [0 ]
327
+ }
328
+ if ! opt .NoWebsocket {
329
+ cli .socksProxy = px
330
+ cli .proxy = nil
331
+ }
332
+ if ! opt .NoMedia {
333
+ transport := cli .http .Transport .(* http.Transport )
334
+ transport .Proxy = nil
335
+ transport .Dial = cli .socksProxy .Dial
336
+ contextDialer , ok := cli .socksProxy .(proxy.ContextDialer )
337
+ if ok {
338
+ transport .DialContext = contextDialer .DialContext
339
+ } else {
340
+ transport .DialContext = nil
341
+ }
342
+ }
343
+ }
344
+
345
+ // ToggleProxyOnlyForLogin changes whether the proxy set with SetProxy or related methods
346
+ // is only used for the pre-login websocket and not authenticated websockets.
347
+ func (cli * Client ) ToggleProxyOnlyForLogin (only bool ) {
348
+ cli .proxyOnlyLogin = only
287
349
}
288
350
289
351
func (cli * Client ) getSocketWaitChan () <- chan struct {} {
@@ -339,7 +401,19 @@ func (cli *Client) Connect() error {
339
401
}
340
402
341
403
cli .resetExpectedDisconnect ()
342
- fs := socket .NewFrameSocket (cli .Log .Sub ("Socket" ), cli .proxy )
404
+ wsDialer := websocket.Dialer {}
405
+ if ! cli .proxyOnlyLogin || cli .Store .ID == nil {
406
+ if cli .proxy != nil {
407
+ wsDialer .Proxy = cli .proxy
408
+ } else if cli .socksProxy != nil {
409
+ wsDialer .NetDial = cli .socksProxy .Dial
410
+ contextDialer , ok := cli .socksProxy .(proxy.ContextDialer )
411
+ if ok {
412
+ wsDialer .NetDialContext = contextDialer .DialContext
413
+ }
414
+ }
415
+ }
416
+ fs := socket .NewFrameSocket (cli .Log .Sub ("Socket" ), wsDialer )
343
417
if cli .MessengerConfig != nil {
344
418
fs .URL = "wss://web-chat-e2ee.facebook.com/ws/chat"
345
419
fs .HTTPHeaders .Set ("Origin" , cli .MessengerConfig .BaseURL )
0 commit comments