Parcourir la source

feat: add support for gzip & brotli

ayn2op il y a 7 mois
Parent
commit
ac669bccae
9 fichiers modifiés avec 172 ajouts et 133 suppressions
  1. 15 47
      cmd/state.go
  2. 1 0
      go.mod
  3. 2 0
      go.sum
  4. 0 85
      internal/consts/consts.go
  5. 17 0
      internal/http/client.go
  6. 28 0
      internal/http/headers.go
  7. 65 0
      internal/http/props.go
  8. 42 0
      internal/http/transport.go
  9. 2 1
      internal/login/form.go

+ 15 - 47
cmd/state.go

@@ -2,16 +2,17 @@ package cmd
 
 import (
 	"context"
-	"encoding/base64"
-	"encoding/json"
 	"log/slog"
-	"net/http"
 
-	"github.com/ayn2op/discordo/internal/consts"
+	"github.com/ayn2op/discordo/internal/http"
 	"github.com/ayn2op/discordo/internal/notifications"
 	"github.com/ayn2op/tview"
 	"github.com/diamondburned/arikawa/v3/api"
 	"github.com/diamondburned/arikawa/v3/gateway"
+	"github.com/diamondburned/arikawa/v3/session"
+	"github.com/diamondburned/arikawa/v3/state"
+	"github.com/diamondburned/arikawa/v3/state/store/defaultstore"
+	"github.com/diamondburned/arikawa/v3/utils/handler"
 	"github.com/diamondburned/arikawa/v3/utils/httputil"
 	"github.com/diamondburned/arikawa/v3/utils/httputil/httpdriver"
 	"github.com/diamondburned/arikawa/v3/utils/ws"
@@ -20,19 +21,20 @@ import (
 )
 
 func openState(token string) error {
-	props := consts.GetIdentifyProps()
-	if browserUserAgent, ok := props["browser_user_agent"]; ok {
-		if val, ok := browserUserAgent.(string); ok {
-			api.UserAgent = val
-		}
-	}
+	identifyProps := http.IdentifyProperties()
 
-	gateway.DefaultIdentity = props
+	api.UserAgent = http.BrowserUserAgent
+	gateway.DefaultIdentity = identifyProps
 	gateway.DefaultPresence = &gateway.UpdatePresenceCommand{
 		Status: app.cfg.Status,
 	}
 
-	discordState = ningen.New(token)
+	id := gateway.DefaultIdentifier(token)
+	id.Compress = false
+
+	session := session.NewCustom(id, http.NewClient(token), handler.New())
+	state := state.NewFromSession(session, defaultstore.New())
+	discordState = ningen.FromState(state)
 
 	// Handlers
 	discordState.AddHandler(onRaw)
@@ -54,44 +56,10 @@ func openState(token string) error {
 		slog.Error("state log", "err", err)
 	}
 
-	discordState.OnRequest = append(discordState.OnRequest, httputil.WithHeaders(getHeaders(props)), onRequest)
+	discordState.OnRequest = append(discordState.OnRequest, httputil.WithHeaders(http.Headers()), onRequest)
 	return discordState.Open(context.TODO())
 }
 
-func getHeaders(props gateway.IdentifyProperties) http.Header {
-	header := make(http.Header)
-
-	// These properties are only sent when identifying with the gateway and are not included in the X-Super-Properties header.
-	delete(props, "is_fast_connect")
-	delete(props, "gateway_connect_reasons")
-
-	if rawProps, err := json.Marshal(props); err == nil {
-		propsHeader := base64.StdEncoding.EncodeToString(rawProps)
-		header.Set("X-Super-Properties", propsHeader)
-	}
-
-	if systemLocale, ok := props["system_locale"]; ok {
-		if val, ok := systemLocale.(string); ok {
-			header.Set("X-Discord-Locale", string(val))
-		}
-	}
-
-	header.Set("X-Debug-Options", "bugReporterEnabled")
-
-	// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers
-	header.Set("Accept", "*/*")
-	header.Set("Accept-Encoding", "gzip, deflate, br, zstd")
-	header.Set("Accept-Language", "en-US,en;q=0.9")
-	header.Set("Origin", "https://discord.com")
-	header.Set("Priority", "u=0, i")
-	header.Set("Referer", "https://discord.com/channels/@me")
-	header.Set("Sec-Fetch-Dest", "empty")
-	header.Set("Sec-Fetch-Mode", "cors")
-	header.Set("Sec-Fetch-Site", "same-origin")
-
-	return header
-}
-
 func onRequest(r httpdriver.Request) error {
 	if req, ok := r.(*httpdriver.DefaultRequest); ok {
 		slog.Debug("new HTTP request", "method", req.Method, "url", req.URL)

+ 1 - 0
go.mod

@@ -4,6 +4,7 @@ go 1.25.0
 
 require (
 	github.com/BurntSushi/toml v1.5.0
+	github.com/andybalholm/brotli v1.2.0
 	github.com/ayn2op/tview v0.0.0-20250720032506-63f04e47b15d
 	github.com/deckarep/gosx-notifier v0.0.0-20180201035817-e127226297fb
 	github.com/diamondburned/arikawa/v3 v3.6.0

+ 2 - 0
go.sum

@@ -25,6 +25,8 @@ github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/akavel/rsrc v0.10.2 h1:Zxm8V5eI1hW4gGaYsJQUhxpjkENuG91ki8B4zCrvEsw=
 github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
+github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
+github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
 github.com/ayn2op/tview v0.0.0-20250720032506-63f04e47b15d h1:+Q2vA4jMu5/tU8uAyr7mqX63br1jWFEa8q/fZNPP96Y=
 github.com/ayn2op/tview v0.0.0-20250720032506-63f04e47b15d/go.mod h1:xqhotzuhTSxSudyNSHJfpD6S5C7+FFVv+JzJ8YaFNK4=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=

+ 0 - 85
internal/consts/consts.go

@@ -1,98 +1,13 @@
 package consts
 
 import (
-	"encoding/json"
 	"log/slog"
-	"maps"
-	"net/http"
 	"os"
 	"path/filepath"
-
-	"github.com/diamondburned/arikawa/v3/discord"
-	"github.com/diamondburned/arikawa/v3/gateway"
-	"github.com/google/uuid"
 )
 
 const Name = "discordo"
 
-const identifyPropertiesURL = "https://cordapi.dolfi.es/api/v2/properties/web"
-
-var defaultIdentifyProps = gateway.IdentifyProperties{
-	gateway.IdentifyDevice: "",
-
-	gateway.IdentifyBrowser: "Chrome",
-	"browser_version":       "140.0.0.0",
-	"browser_user_agent":    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36",
-
-	gateway.IdentifyOS: "Windows",
-	"os_version":       "10",
-
-	"client_build_number": 439729,
-	"client_event_source": nil,
-	"client_launch_id":    uuid.NewString(),
-	"client_app_state":    "focused",
-
-	"launch_signature":        uuid.NewString(),
-	"system_locale":           discord.EnglishUS,
-	"release_channel":         "stable",
-	"has_client_mods":         false,
-	"is_fast_connect":         false,
-	"gateway_connect_reasons": "AppSkeleton",
-
-	"referrer":                 "",
-	"referrer_current":         "",
-	"referring_domain":         "",
-	"referring_domain_current": "",
-}
-
-type Properties struct {
-	Client struct {
-		Type           string `json:"type"`
-		BuildNumber    int    `json:"build_number"`
-		BuildHash      string `json:"build_hash"`
-		ReleaseChannel string `json:"release_channel"`
-	} `json:"client"`
-
-	Browser struct {
-		Type      string `json:"type"`
-		UserAgent string `json:"user_agent"`
-		Version   string `json:"version"`
-		OS        struct {
-			Type    string `json:"type"`
-			Version string `json:"version"`
-		} `json:"os"`
-	} `json:"browser"`
-}
-
-func GetIdentifyProps() gateway.IdentifyProperties {
-	resp, err := http.Get(identifyPropertiesURL)
-	if err != nil {
-		return defaultIdentifyProps
-	}
-	defer resp.Body.Close()
-
-	if resp.StatusCode != http.StatusOK {
-		return defaultIdentifyProps
-	}
-
-	var props Properties
-	if err := json.NewDecoder(resp.Body).Decode(&props); err != nil {
-		return defaultIdentifyProps
-	}
-
-	p := maps.Clone(defaultIdentifyProps)
-	p[gateway.IdentifyBrowser] = props.Browser.Type
-	p["browser_version"] = props.Browser.Version
-	p["browser_user_agent"] = props.Browser.UserAgent
-
-	p[gateway.IdentifyOS] = props.Browser.OS.Type
-	p["os_version"] = props.Browser.OS.Version
-
-	p["release_channel"] = props.Client.ReleaseChannel
-	p["client_build_number"] = props.Client.BuildNumber
-	return p
-}
-
 var cacheDir string
 
 func CacheDir() string {

+ 17 - 0
internal/http/client.go

@@ -0,0 +1,17 @@
+package http
+
+import (
+	"net/http"
+
+	"github.com/diamondburned/arikawa/v3/api"
+	"github.com/diamondburned/arikawa/v3/utils/httputil"
+	"github.com/diamondburned/arikawa/v3/utils/httputil/httpdriver"
+)
+
+func NewClient(token string) *api.Client {
+	httpClient := httputil.NewClient()
+	stdClient := http.DefaultClient
+	stdClient.Transport = NewTransport()
+	httpClient.Client = httpdriver.WrapClient(*stdClient)
+	return api.NewCustomClient(token, httpClient)
+}

+ 28 - 0
internal/http/headers.go

@@ -0,0 +1,28 @@
+package http
+
+import (
+	stdHttp "net/http"
+)
+
+func Headers() stdHttp.Header {
+	headers := make(stdHttp.Header)
+	// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers
+	headers.Set("Accept", "*/*")
+	headers.Set("Accept-Encoding", "gzip, deflate, br, zstd")
+	headers.Set("Accept-Language", "en-US,en;q=0.9")
+	headers.Set("Origin", "https://discord.com")
+	headers.Set("Priority", "u=0, i")
+	headers.Set("Referer", "https://discord.com/channels/@me")
+	headers.Set("Sec-Fetch-Dest", "empty")
+	headers.Set("Sec-Fetch-Mode", "cors")
+	headers.Set("Sec-Fetch-Site", "same-origin")
+
+	headers.Set("X-Debug-Options", "bugReporterEnabled")
+	headers.Set("X-Discord-Locale", string(Locale))
+
+	if superProps, err := superProps(); err == nil {
+		headers.Set("X-Super-Properties", superProps)
+	}
+
+	return headers
+}

+ 65 - 0
internal/http/props.go

@@ -0,0 +1,65 @@
+package http
+
+import (
+	"encoding/base64"
+	"encoding/json"
+
+	"github.com/diamondburned/arikawa/v3/discord"
+	"github.com/diamondburned/arikawa/v3/gateway"
+	"github.com/google/uuid"
+)
+
+const (
+	Browser          = "Chrome"
+	BrowserVersion   = "140.0.0.0"
+	BrowserUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36"
+)
+
+var (
+	Locale = discord.EnglishUS
+)
+
+func IdentifyProperties() gateway.IdentifyProperties {
+	return gateway.IdentifyProperties{
+		gateway.IdentifyDevice: "",
+
+		gateway.IdentifyOS: "Windows",
+		"os_version":       "10",
+
+		gateway.IdentifyBrowser: Browser,
+		"browser_version":       BrowserVersion,
+		"browser_user_agent":    BrowserUserAgent,
+
+		"client_build_number": 447677,
+		"client_event_source": nil,
+		"client_launch_id":    uuid.NewString(),
+		"client_app_state":    "focused",
+
+		"launch_signature": uuid.NewString(),
+		"system_locale":    Locale,
+		"release_channel":  "stable",
+		"has_client_mods":  false,
+
+		"referrer":                 "",
+		"referrer_current":         "",
+		"referring_domain":         "",
+		"referring_domain_current": "",
+
+		// These properties are only sent when identifying with the gateway and are not included in the X-Super-Properties header.
+		"is_fast_connect":         false,
+		"gateway_connect_reasons": "AppSkeleton",
+	}
+}
+
+func superProps() (string, error) {
+	props := IdentifyProperties()
+	delete(props, "is_fast_connect")
+	delete(props, "gateway_connect_reasons")
+
+	raw, err := json.Marshal(props)
+	if err != nil {
+		return "", err
+	}
+
+	return base64.StdEncoding.EncodeToString(raw), nil
+}

+ 42 - 0
internal/http/transport.go

@@ -0,0 +1,42 @@
+package http
+
+import (
+	"compress/gzip"
+	"io"
+	"net/http"
+
+	"github.com/andybalholm/brotli"
+)
+
+type Transport struct {
+	base http.RoundTripper
+}
+
+func NewTransport() *Transport {
+	return &Transport{
+		base: http.DefaultTransport,
+	}
+}
+
+func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
+	resp, err := t.base.RoundTrip(req)
+	if err != nil {
+		return nil, err
+	}
+
+	switch resp.Header.Get("Content-Encoding") {
+	case "gzip":
+		resp.Body, err = gzip.NewReader(resp.Body)
+		if err != nil {
+			resp.Body.Close()
+			return nil, err
+		}
+
+		resp.Header.Del("Content-Encoding")
+	case "br":
+		resp.Body = io.NopCloser(brotli.NewReader(resp.Body))
+		resp.Header.Del("Content-Encoding")
+	}
+
+	return resp, nil
+}

+ 2 - 1
internal/login/form.go

@@ -6,6 +6,7 @@ import (
 
 	"github.com/ayn2op/discordo/internal/config"
 	"github.com/ayn2op/discordo/internal/consts"
+	"github.com/ayn2op/discordo/internal/http"
 	"github.com/ayn2op/discordo/internal/ui"
 	"github.com/ayn2op/tview"
 	"github.com/diamondburned/arikawa/v3/api"
@@ -52,7 +53,7 @@ func (f *Form) login() {
 
 	// Create an API client without an authentication token.
 	client := api.NewClient("")
-	props := consts.GetIdentifyProps()
+	props := http.IdentifyProperties()
 	if browserUserAgent, ok := props["browser_user_agent"]; ok {
 		if val, ok := browserUserAgent.(string); ok {
 			api.UserAgent = val