feat: impersonate browser TLS fingerprints (#824)

This commit is contained in:
ayn2op 2026-08-04 21:04:22 -04:00 • committed by GitHub
parent c4136efef0
commit eab73a3b19
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 367 additions and 19 deletions

View file

@ -27,7 +27,16 @@ jobs:
go-version: stable
- name: Test
run: go test -v ./...
if: ${{ matrix.os != 'windows-11-arm' }}
run: |
go test -race -v ./...
go test -race -v -tags no_spoof_tls_fingerprint ./...
# -race is not supported on windows/arm64.
- name: Test
if: ${{ matrix.os == 'windows-11-arm' }}
run: |
go test -v ./...
go test -v -tags no_spoof_tls_fingerprint ./...
- name: Build
run: go build -trimpath -ldflags=-s .

11
go.mod
View file

@ -11,6 +11,8 @@ require (
github.com/ayn2op/arikawa/v3 v3.0.0-20260708075022-9cc760fe507e
github.com/ayn2op/ningen/v3 v3.0.1-0.20260708075321-0ba1ffc70bd6
github.com/ayn2op/tview v0.0.0-20260803165347-d8123f4ec53c
github.com/bogdanfinn/fhttp v0.6.8
github.com/bogdanfinn/tls-client v1.15.1
github.com/gdamore/tcell/v3 v3.4.1
github.com/google/go-cmp v0.6.0
github.com/google/uuid v1.6.0
@ -28,6 +30,11 @@ require (
require (
github.com/akavel/rsrc v0.10.2 // indirect
github.com/bdandy/go-errors v1.2.2 // indirect
github.com/bdandy/go-socks4 v1.2.3 // indirect
github.com/bogdanfinn/quic-go-utls v1.0.9-utls // indirect
github.com/bogdanfinn/utls v1.7.7-barnius // indirect
github.com/bogdanfinn/websocket v1.5.5-barnius // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/danieljoos/wincred v1.2.3 // indirect
@ -40,13 +47,17 @@ require (
github.com/josephspurrier/goversioninfo v1.7.0 // indirect
github.com/lucasb-eyer/go-colorful v1.4.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/randall77/makefat v0.0.0-20260406194835-1b91746796b7 // indirect
github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5 // indirect
github.com/twmb/murmur3 v1.1.8 // indirect
go4.org v0.0.0-20260112195520-a5071408f32f // indirect
golang.design/x/x11 v0.2.0 // indirect
golang.org/x/crypto v0.54.0 // indirect
golang.org/x/exp/shiny v0.0.0-20260727155853-b88d891fe743 // indirect
golang.org/x/image v0.44.0 // indirect
golang.org/x/mobile v0.0.0-20260730202154-c700fe717e6e // indirect
golang.org/x/net v0.57.0 // indirect
golang.org/x/sys v0.47.0 // indirect
golang.org/x/term v0.45.0 // indirect
golang.org/x/text v0.40.0 // indirect

27
go.sum
View file

@ -16,6 +16,20 @@ github.com/ayn2op/ningen/v3 v3.0.1-0.20260708075321-0ba1ffc70bd6 h1:/khHm6G+GWwX
github.com/ayn2op/ningen/v3 v3.0.1-0.20260708075321-0ba1ffc70bd6/go.mod h1:vqLIvETLkjc2vIgQzVEBqpGNCrgNe7YJqS9TUAJsXRI=
github.com/ayn2op/tview v0.0.0-20260803165347-d8123f4ec53c h1:ApOfdF/YBJ+oy5zG5yJW/IbgCOoWcfJ/qukTnfFlUXc=
github.com/ayn2op/tview v0.0.0-20260803165347-d8123f4ec53c/go.mod h1:bJZdl/FfspPLEW3nuX6ubt7YbmDoSj7f2IoWH37w1NE=
github.com/bdandy/go-errors v1.2.2 h1:WdFv/oukjTJCLa79UfkGmwX7ZxONAihKu4V0mLIs11Q=
github.com/bdandy/go-errors v1.2.2/go.mod h1:NkYHl4Fey9oRRdbB1CoC6e84tuqQHiqrOcZpqFEkBxM=
github.com/bdandy/go-socks4 v1.2.3 h1:Q6Y2heY1GRjCtHbmlKfnwrKVU/k81LS8mRGLRlmDlic=
github.com/bdandy/go-socks4 v1.2.3/go.mod h1:98kiVFgpdogR8aIGLWLvjDVZ8XcKPsSI/ypGrO+bqHI=
github.com/bogdanfinn/fhttp v0.6.8 h1:LiQyHOY3i0QoxxNB7nq27/nGNNbtPj0fuBPozhR7Ws4=
github.com/bogdanfinn/fhttp v0.6.8/go.mod h1:A+EKDzMx2hb4IUbMx4TlkoHnaJEiLl8r/1Ss1Y+5e5M=
github.com/bogdanfinn/quic-go-utls v1.0.9-utls h1:tV6eDEiRbRCcepALSzxR94JUVD3N3ACIiRLgyc2Ep8s=
github.com/bogdanfinn/quic-go-utls v1.0.9-utls/go.mod h1:aHph9B9H9yPOt5xnhWKSOum27DJAqpiHzwX+gjvaXcg=
github.com/bogdanfinn/tls-client v1.15.1 h1:KiFAlED55DJ8Fcocn+/1nX6PrDFcttIHAf/GDkV6KN8=
github.com/bogdanfinn/tls-client v1.15.1/go.mod h1:LsU6mXVn8MOFDwTkyRfI7V1BZM1p0wf2ZfZsICW/1fM=
github.com/bogdanfinn/utls v1.7.7-barnius h1:OuJ497cc7F3yKNVHRsYPQdGggmk5x6+V5ZlrCR7fOLU=
github.com/bogdanfinn/utls v1.7.7-barnius/go.mod h1:aAK1VZQlpKZClF1WEQeq6kyclbkPq4hz6xTbB5xSlmg=
github.com/bogdanfinn/websocket v1.5.5-barnius h1:bY+qnxpai1qe7Jmjx+Sds/cmOSpuuLoR8x61rWltjOI=
github.com/bogdanfinn/websocket v1.5.5-barnius/go.mod h1:gvvEw6pTKHb7yOiFvIfAFTStQWyrm25BMVCTj5wRSsI=
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
@ -60,6 +74,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/randall77/makefat v0.0.0-20260406194835-1b91746796b7 h1:m1yKMZwDSXkT5o2MKhd6ihdzb2dYb6eElNE04xjOSEY=
github.com/randall77/makefat v0.0.0-20260406194835-1b91746796b7/go.mod h1:T1TLSfyWVBRXVGzWd0o9BI4kfoO9InEgfQe4NV3mLz8=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
@ -74,6 +90,8 @@ github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5 h1:YqAladjX7xpA6BM04leXMWAEjS0mTZ5kUU9KRBriQJc=
github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5/go.mod h1:2JjD2zLQYH5HO74y5+aE3remJQvl6q4Sn6aWA2wD1Ng=
github.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg=
github.com/twmb/murmur3 v1.1.8/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
@ -85,6 +103,8 @@ github.com/zalando/go-keyring v0.2.8 h1:6sD/Ucpl7jNq10rM2pgqTs0sZ9V3qMrqfIIy5YPc
github.com/zalando/go-keyring v0.2.8/go.mod h1:tsMo+VpRq5NGyKfxoBVjCuMrG47yj8cmakZDO5QGii0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
go4.org v0.0.0-20260112195520-a5071408f32f h1:ziUVAjmTPwQMBmYR1tbdRFJPtTcQUI12fH9QQjfb0Sw=
go4.org v0.0.0-20260112195520-a5071408f32f/go.mod h1:ZRJnO5ZI4zAwMFp+dS1+V6J6MSyAowhRqAE+DPa1Xp0=
golang.design/x/clipboard v0.8.0 h1:6VEcH28wwcSgKc+vnxHHDWiRjrakTQIAJnPUrt3aOgg=
@ -93,6 +113,8 @@ golang.design/x/x11 v0.2.0 h1:Uiwu2guGihsJX/ZCzpoDPFz5gR/Qntm08mvoBCmRydo=
golang.design/x/x11 v0.2.0/go.mod h1:/5q1mFkdc1rL8mvB7DsQFi6as4tIkBv4FXjcP07mrkE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
golang.org/x/exp/shiny v0.0.0-20260727155853-b88d891fe743 h1:rQLtGtwpYzQUyxCqrT3i4lGgvC1Q4G72JTHYncg6AvY=
golang.org/x/exp/shiny v0.0.0-20260727155853-b88d891fe743/go.mod h1:JRiBNgSZB8uVKtHGrvbGDQ8p9Ghn3+lkFSk1KqRIiPc=
golang.org/x/image v0.44.0 h1:+tDekMZED9+LrtB3G5xzRggpVh9CARjZqROla3R3R+I=
@ -103,13 +125,17 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211104170005-ce137452f963/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
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=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -123,6 +149,7 @@ golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=

View file

@ -0,0 +1,18 @@
//go:build !no_spoof_tls_fingerprint
package gateway
import (
"github.com/ayn2op/discordo/internal/tls"
tls_client "github.com/bogdanfinn/tls-client"
"github.com/gorilla/websocket"
)
func NewDialer() websocket.Dialer {
client := tls.NewClient(tls_client.WithForceHttp1())
dialer := *websocket.DefaultDialer
dialer.Proxy = nil
dialer.EnableCompression = true
dialer.NetDialTLSContext = client.GetTLSDialer()
return dialer
}

View file

@ -0,0 +1,11 @@
//go:build no_spoof_tls_fingerprint
package gateway
import "github.com/gorilla/websocket"
func NewDialer() websocket.Dialer {
dialer := *websocket.DefaultDialer
dialer.EnableCompression = true
return dialer
}

View file

@ -0,0 +1,18 @@
//go:build no_spoof_tls_fingerprint
package gateway
import "testing"
func TestNewDialer(t *testing.T) {
dialer := NewDialer()
if dialer.NetDialTLSContext != nil {
t.Error("custom TLS dialer is enabled")
}
if dialer.Proxy == nil {
t.Error("environment proxy is disabled")
}
if !dialer.EnableCompression {
t.Error("compression is disabled")
}
}

View file

@ -0,0 +1,25 @@
//go:build !no_spoof_tls_fingerprint
package gateway
import (
"testing"
"github.com/gorilla/websocket"
)
func TestNewDialer(t *testing.T) {
dialer := NewDialer()
if dialer.NetDialTLSContext == nil {
t.Error("TLS dialer is nil")
}
if dialer.Proxy != nil {
t.Error("proxy is enabled")
}
if !dialer.EnableCompression {
t.Error("compression is disabled")
}
if websocket.DefaultDialer.NetDialTLSContext != nil {
t.Error("default dialer was modified")
}
}

View file

@ -0,0 +1,20 @@
package gateway
import (
"github.com/ayn2op/arikawa/v3/api"
discordgateway "github.com/ayn2op/arikawa/v3/gateway"
"github.com/ayn2op/arikawa/v3/utils/ws"
"github.com/ayn2op/discordo/internal/http"
)
const gatewayURL = "wss://gateway.discord.gg"
func New(id discordgateway.Identifier) *discordgateway.Gateway {
codec := ws.NewCodec(discordgateway.OpUnmarshalers)
codec.Headers.Set("Origin", api.BaseEndpoint)
codec.Headers.Set("User-Agent", http.BrowserUserAgent())
conn := ws.NewConnWithDialer(codec, NewDialer())
socket := ws.NewCustomWebsocket(conn, discordgateway.AddGatewayParams(gatewayURL))
return discordgateway.FromWebsocketGateway(ws.NewGateway(socket, nil), discordgateway.State{Identifier: id})
}

View file

@ -1,18 +1,95 @@
//go:build !no_spoof_tls_fingerprint
package http
import (
"context"
"io"
"maps"
"net/http"
"net/url"
"github.com/ayn2op/arikawa/v3/api"
"github.com/ayn2op/arikawa/v3/utils/httputil"
"github.com/ayn2op/arikawa/v3/utils/httputil/httpdriver"
"github.com/ayn2op/discordo/internal/tls"
fhttp "github.com/bogdanfinn/fhttp"
tls_client "github.com/bogdanfinn/tls-client"
)
func NewClient(token string) *api.Client {
stdClient := new(http.Client)
stdClient.Transport = NewTransport()
httpClient := httputil.NewClientWithDriver(httpdriver.WrapClient(*stdClient))
apiClient := api.NewCustomClient(token, httpClient)
apiClient.UserAgent = BrowserUserAgent
return apiClient
httpClient := httputil.NewClientWithDriver(wrapClient(tls.NewClient()))
client := api.NewCustomClient(token, httpClient)
client.UserAgent = BrowserUserAgent()
return client
}
type client struct {
tls_client.HttpClient
}
var _ httpdriver.Client = (*client)(nil)
func wrapClient(c tls_client.HttpClient) httpdriver.Client {
return &client{HttpClient: c}
}
func (c *client) NewRequest(ctx context.Context, method, url string) (httpdriver.Request, error) {
req, err := fhttp.NewRequestWithContext(ctx, method, url, nil)
if err != nil {
return nil, err
}
return (*request)(req), nil
}
func (c *client) Do(req httpdriver.Request) (httpdriver.Response, error) {
resp, err := c.HttpClient.Do((*fhttp.Request)(req.(*request)))
if err != nil {
return nil, err
}
return (*response)(resp), nil
}
type request fhttp.Request
var _ httpdriver.Request = (*request)(nil)
func (r *request) GetPath() string {
return r.URL.Path
}
func (r *request) GetContext() context.Context {
return (*fhttp.Request)(r).Context()
}
func (r *request) AddHeader(header http.Header) {
maps.Copy(r.Header, fhttp.Header(header))
}
func (r *request) AddQuery(values url.Values) {
query := r.URL.Query()
for key, value := range values {
query[key] = append(query[key], value...)
}
r.URL.RawQuery = query.Encode()
}
func (r *request) WithBody(body io.ReadCloser) {
r.Body = body
}
type response fhttp.Response
var _ httpdriver.Response = (*response)(nil)
func (r *response) GetStatus() int {
return r.StatusCode
}
func (r *response) GetHeader() http.Header {
return http.Header(r.Header)
}
func (r *response) GetBody() io.ReadCloser {
return r.Body
}

View file

@ -0,0 +1,19 @@
//go:build no_spoof_tls_fingerprint
package http
import (
"net/http"
"github.com/ayn2op/arikawa/v3/api"
"github.com/ayn2op/arikawa/v3/utils/httputil"
"github.com/ayn2op/arikawa/v3/utils/httputil/httpdriver"
)
func NewClient(token string) *api.Client {
stdClient := http.Client{Transport: NewTransport()}
httpClient := httputil.NewClientWithDriver(httpdriver.WrapClient(stdClient))
client := api.NewCustomClient(token, httpClient)
client.UserAgent = BrowserUserAgent()
return client
}

View file

@ -3,6 +3,7 @@ package http
import (
"github.com/ayn2op/arikawa/v3/discord"
"github.com/ayn2op/arikawa/v3/gateway"
"github.com/ayn2op/discordo/internal/tls"
"github.com/google/uuid"
)
@ -11,14 +12,13 @@ import (
const (
OS = "Windows"
OSVersion = "10"
Browser = "Chrome"
BrowserVersion = "143.0.0.0"
BrowserUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) " + Browser + "/" + BrowserVersion + " Safari/537.36"
Locale = discord.EnglishUS
Locale = discord.EnglishUS
)
func BrowserUserAgent() string {
return "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) " + tls.BrowserName() + "/" + tls.BrowserVersion() + " Safari/537.36"
}
func CommonProperties() gateway.IdentifyProperties {
return gateway.IdentifyProperties{
gateway.IdentifyDevice: "",
@ -26,9 +26,9 @@ func CommonProperties() gateway.IdentifyProperties {
gateway.IdentifyOS: OS,
"os_version": OSVersion,
gateway.IdentifyBrowser: Browser,
"browser_version": BrowserVersion,
"browser_user_agent": BrowserUserAgent,
gateway.IdentifyBrowser: tls.BrowserName(),
"browser_version": tls.BrowserVersion(),
"browser_user_agent": BrowserUserAgent(),
"client_build_number": ClientBuildNumber,
"client_event_source": nil,

View file

@ -0,0 +1,35 @@
package http
import (
"strings"
"testing"
"github.com/ayn2op/arikawa/v3/gateway"
"github.com/ayn2op/discordo/internal/tls"
)
func TestBrowserIdentity(t *testing.T) {
properties := CommonProperties()
tests := []struct {
name string
got any
want any
}{
{"properties browser", properties[gateway.IdentifyBrowser], tls.BrowserName()},
{"properties version", properties["browser_version"], tls.BrowserVersion()},
{"properties user agent", properties["browser_user_agent"], BrowserUserAgent()},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if test.got != test.want {
t.Fatalf("got = %v, want = %v", test.got, test.want)
}
})
}
if product := tls.BrowserName() + "/" + tls.BrowserVersion(); !strings.Contains(BrowserUserAgent(), product) {
t.Fatalf("user agent does not contain %q", product)
}
}

18
internal/tls/client.go Normal file
View file

@ -0,0 +1,18 @@
//go:build !no_spoof_tls_fingerprint
package tls
import (
"fmt"
tls_client "github.com/bogdanfinn/tls-client"
)
func NewClient(options ...tls_client.HttpClientOption) tls_client.HttpClient {
options = append([]tls_client.HttpClientOption{tls_client.WithClientProfile(clientProfile)}, options...)
client, err := tls_client.NewHttpClient(tls_client.NewNoopLogger(), options...)
if err != nil {
panic(fmt.Errorf("failed to create new tls http client: %w", err))
}
return client
}

View file

@ -0,0 +1,15 @@
//go:build !no_spoof_tls_fingerprint
package tls
import "testing"
func TestNewClient(t *testing.T) {
client := NewClient()
if client == nil {
t.Fatal("client is nil")
}
if client.GetTLSDialer() == nil {
t.Fatal("TLS dialer is nil")
}
}

13
internal/tls/profile.go Normal file
View file

@ -0,0 +1,13 @@
package tls
import "github.com/bogdanfinn/tls-client/profiles"
var clientProfile = profiles.Chrome_146
func BrowserName() string {
return clientProfile.GetClientHelloId().Client
}
func BrowserVersion() string {
return clientProfile.GetClientHelloId().Version + ".0.0.0"
}

View file

@ -0,0 +1,28 @@
package tls
import (
"testing"
"github.com/bogdanfinn/tls-client/profiles"
)
func TestBrowserIdentity(t *testing.T) {
hello := profiles.Chrome_146.GetClientHelloId()
tests := []struct {
name string
got string
want string
}{
{"name", BrowserName(), hello.Client},
{"version", BrowserVersion(), hello.Version + ".0.0.0"},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if test.got != test.want {
t.Fatalf("got = %q, want = %q", test.got, test.want)
}
})
}
}

View file

@ -15,6 +15,7 @@ import (
"github.com/ayn2op/arikawa/v3/utils/httputil"
"github.com/ayn2op/arikawa/v3/utils/ws"
"github.com/ayn2op/discordo/internal/config"
clientgateway "github.com/ayn2op/discordo/internal/gateway"
"github.com/ayn2op/discordo/internal/http"
"github.com/ayn2op/discordo/internal/ui"
"github.com/ayn2op/ningen/v3"
@ -85,7 +86,8 @@ func NewModel(app *tview.Application, cfg *config.Config, token string) *Model {
Properties: http.IdentifyProperties(),
})
session := session.NewCustom(id, http.NewClient(token), handler.New())
session := session.NewWithGateway(clientgateway.New(id), handler.New())
session.Client = http.NewClient(token)
state := state.NewFromSession(session, defaultstore.New())
m.state = ningen.FromState(state)
m.state.DontWaitForReady = true

View file

@ -12,6 +12,7 @@ import (
"time"
"github.com/ayn2op/arikawa/v3/utils/httputil"
"github.com/ayn2op/discordo/internal/gateway"
"github.com/ayn2op/discordo/internal/http"
"github.com/ayn2op/tview"
"github.com/gorilla/websocket"
@ -39,8 +40,9 @@ type connCloseMsg struct{}
func (m *Model) connect() tview.Cmd {
return func() tview.Msg {
headers := http.Headers()
headers.Set("User-Agent", http.BrowserUserAgent)
conn, _, err := websocket.DefaultDialer.Dial(remoteAuthGatewayURL, headers)
headers.Set("User-Agent", http.BrowserUserAgent())
dialer := gateway.NewDialer()
conn, _, err := dialer.Dial(remoteAuthGatewayURL, headers)
if err != nil {
return newErrMsg(err)
}