From dc589d1ca13b7a56cf587e9e794ed615e2aeeb7d Mon Sep 17 00:00:00 2001 From: cosrnic <75631523+cosrnic@users.noreply.github.com> Date: Sat, 5 Oct 2024 12:48:37 +0100 Subject: [PATCH] Make requested changes --- cmd/mc/skin/add.go | 8 +- cmd/mc/skin/apply.go | 4 +- internal/pkg/modrinth/modrinth.go | 8 +- internal/pkg/mojang/mojang.go | 169 +++++++----------------------- internal/pkg/mojang/profile.go | 38 +++---- internal/pkg/skin/skin.go | 18 ++-- internal/pkg/util/useragent.go | 9 ++ 7 files changed, 79 insertions(+), 175 deletions(-) create mode 100644 internal/pkg/util/useragent.go diff --git a/cmd/mc/skin/add.go b/cmd/mc/skin/add.go index 2a0b61c..9e3846b 100644 --- a/cmd/mc/skin/add.go +++ b/cmd/mc/skin/add.go @@ -87,11 +87,11 @@ func (o *addSkinOpts) execute(args []string) error { return err } - client := mojang.NewProfileClient(o.app.Build.Version) + client := mojang.NewProfileClient(o.app.Build.Version, token) ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) defer cancel() - info, err := client.ProfileInformation(ctx, token) + info, err := client.ProfileInformation(ctx) if err != nil { return err } @@ -112,14 +112,14 @@ func (o *addSkinOpts) execute(args []string) error { skinData := args[0] - skin, err := o.app.SkinManager().CreateSkin(o.name, o.variant, skinData, o.cape, client, ctx) + skin, err := o.app.SkinManager().CreateSkin(ctx, client, o.name, o.variant, skinData, o.cape) if err != nil { return err } if o.apply { - err = o.app.SkinManager().ApplySkin(skin, client, ctx, token) + err = o.app.SkinManager().ApplySkin(ctx, client, skin) if err != nil { return err } diff --git a/cmd/mc/skin/apply.go b/cmd/mc/skin/apply.go index 73e23dd..b080526 100644 --- a/cmd/mc/skin/apply.go +++ b/cmd/mc/skin/apply.go @@ -67,9 +67,9 @@ func (o *applySkinOpts) execute(args []string) error { ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) defer cancel() - client := mojang.NewProfileClient(o.app.Build.Version) + client := mojang.NewProfileClient(o.app.Build.Version, token) - err = o.app.SkinManager().ApplySkin(skin, client, ctx, token) + err = o.app.SkinManager().ApplySkin(ctx, client, skin) if err != nil { return err } diff --git a/internal/pkg/modrinth/modrinth.go b/internal/pkg/modrinth/modrinth.go index 1ca0490..f7c7003 100644 --- a/internal/pkg/modrinth/modrinth.go +++ b/internal/pkg/modrinth/modrinth.go @@ -8,11 +8,11 @@ import ( "net/http" "net/url" "time" + + "github.com/mworzala/mc/internal/pkg/util" ) const ( - userAgentFormat = "mworzala/mc/%s" - fabricApiProjectId = "P7dR8mSH" ) @@ -31,7 +31,7 @@ type Client struct { func NewClient(idVersion string) *Client { return &Client{ baseUrl: prodUrl, - userAgent: fmt.Sprintf(userAgentFormat, idVersion), + userAgent: util.MakeUserAgent(idVersion), httpClient: http.DefaultClient, timeout: 10 * time.Second, } @@ -40,7 +40,7 @@ func NewClient(idVersion string) *Client { func NewStagingClient() *Client { return &Client{ baseUrl: stagingUrl, - userAgent: fmt.Sprintf(userAgentFormat, "dev"), + userAgent: util.MakeUserAgent("dev"), httpClient: http.DefaultClient, timeout: 10 * time.Second, } diff --git a/internal/pkg/mojang/mojang.go b/internal/pkg/mojang/mojang.go index ebe88da..43d1285 100644 --- a/internal/pkg/mojang/mojang.go +++ b/internal/pkg/mojang/mojang.go @@ -8,44 +8,44 @@ import ( "io" "net/http" "time" -) -const ( - userAgentFormat = "mworzala/mc/%s" + "github.com/mworzala/mc/internal/pkg/util" ) var ( - mojangApiUrl = "https://api.mojang.com/" - sessionserverUrl = "https://sessionserver.mojang.com/" - servicesApiUrl = "https://api.minecraftservices.com/" - profileApiUrl = servicesApiUrl + "minecraft/profile" + mojangApiUrl = "https://api.mojang.com" + sessionserverUrl = "https://sessionserver.mojang.com" + servicesApiUrl = "https://api.minecraftservices.com" + profileApiUrl = servicesApiUrl + "/minecraft/profile" ) type Client struct { - baseUrl string - userAgent string - httpClient *http.Client - timeout time.Duration + baseUrl string + userAgent string + accountToken string + httpClient *http.Client + timeout time.Duration } -func NewProfileClient(idVersion string) *Client { +func NewProfileClient(idVersion string, accountToken string) *Client { return &Client{ - baseUrl: profileApiUrl, - userAgent: fmt.Sprintf(userAgentFormat, idVersion), - httpClient: http.DefaultClient, - timeout: 10 * time.Second, + baseUrl: profileApiUrl, + userAgent: util.MakeUserAgent(idVersion), + accountToken: accountToken, + httpClient: http.DefaultClient, + timeout: 10 * time.Second, } } -func get[T any](c *Client, ctx context.Context, endpoint string, headers http.Header) (*T, error) { +func do[T any](c *Client, ctx context.Context, method string, url string, headers http.Header, body io.Reader) (*T, error) { ctx, cancel := context.WithTimeout(ctx, c.timeout) defer cancel() - fullUrl := fmt.Sprintf("%s%s", c.baseUrl, endpoint) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, fullUrl, nil) + req, err := http.NewRequestWithContext(ctx, method, url, body) if err != nil { return nil, err } + req.Header = headers req.Header.Set("User-Agent", c.userAgent) @@ -61,6 +61,12 @@ func get[T any](c *Client, ctx context.Context, endpoint string, headers http.He return nil, fmt.Errorf("failed to decode response body: %w", err) } return nil, fmt.Errorf("401 %s: %s", errorRes.Path, errorRes.ErrorMessage) + } else if res.StatusCode == http.StatusBadRequest { + var errorRes badRequestError + if err := json.NewDecoder(res.Body).Decode(&errorRes); err != nil { + return nil, fmt.Errorf("failed to decode response body: %w", err) + } + return nil, fmt.Errorf("400 %s: %s", errorRes.Path, errorRes.Error) } else if res.StatusCode == http.StatusNotFound { var errorRes notFoundError if err := json.NewDecoder(res.Body).Decode(&errorRes); err != nil { @@ -80,127 +86,24 @@ func get[T any](c *Client, ctx context.Context, endpoint string, headers http.He return &result, nil } -func delete[T any](c *Client, ctx context.Context, endpoint string, headers http.Header) (*T, error) { - ctx, cancel := context.WithTimeout(ctx, c.timeout) - defer cancel() - - fullUrl := fmt.Sprintf("%s%s", c.baseUrl, endpoint) - req, err := http.NewRequestWithContext(ctx, http.MethodDelete, fullUrl, nil) - if err != nil { - return nil, err - } - req.Header = headers - req.Header.Set("User-Agent", c.userAgent) - - res, err := c.httpClient.Do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() - - if res.StatusCode == http.StatusUnauthorized { - var errorRes unauthorizedError - if err := json.NewDecoder(res.Body).Decode(&errorRes); err != nil { - return nil, fmt.Errorf("failed to decode response body: %w", err) - } - return nil, fmt.Errorf("401 %s: %s", errorRes.Path, errorRes.ErrorMessage) - } else if res.StatusCode == http.StatusInternalServerError { - return nil, errors.New("500 internal server error") - } else if res.StatusCode != http.StatusOK { - return nil, fmt.Errorf("unexpected response from server: %d", res.StatusCode) - } +func get[T any](c *Client, ctx context.Context, endpoint string, headers http.Header) (*T, error) { + url := c.baseUrl + endpoint + return do[T](c, ctx, http.MethodGet, url, headers, nil) +} - var result T - if err := json.NewDecoder(res.Body).Decode(&result); err != nil { - return nil, fmt.Errorf("failed to decode response body: %w", err) - } - return &result, nil +func delete[T any](c *Client, ctx context.Context, endpoint string, headers http.Header) (*T, error) { + url := c.baseUrl + endpoint + return do[T](c, ctx, http.MethodDelete, url, headers, nil) } func post[T any](c *Client, ctx context.Context, endpoint string, headers http.Header, body io.Reader) (*T, error) { - ctx, cancel := context.WithTimeout(ctx, c.timeout) - defer cancel() - - fullUrl := fmt.Sprintf("%s%s", c.baseUrl, endpoint) - req, err := http.NewRequestWithContext(ctx, http.MethodPost, fullUrl, body) - if err != nil { - return nil, err - } - req.Header = headers - req.Header.Set("User-Agent", c.userAgent) - - res, err := c.httpClient.Do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() - - if res.StatusCode == http.StatusUnauthorized { - var errorRes unauthorizedError - if err := json.NewDecoder(res.Body).Decode(&errorRes); err != nil { - return nil, fmt.Errorf("failed to decode response body: %w", err) - } - return nil, fmt.Errorf("401 %s: %s", errorRes.Path, errorRes.ErrorMessage) - } else if res.StatusCode == http.StatusBadRequest { - var errorRes badRequestError - if err := json.NewDecoder(res.Body).Decode(&errorRes); err != nil { - return nil, fmt.Errorf("failed to decode response body: %w", err) - } - return nil, fmt.Errorf("400 %s: %s", errorRes.Path, errorRes.Error) - } else if res.StatusCode == http.StatusInternalServerError { - return nil, errors.New("500 internal server error") - } else if res.StatusCode != http.StatusOK { - return nil, fmt.Errorf("unexpected response from server: %d", res.StatusCode) - } - - var result T - if err := json.NewDecoder(res.Body).Decode(&result); err != nil { - return nil, fmt.Errorf("failed to decode response body: %w", err) - } - return &result, nil + url := c.baseUrl + endpoint + return do[T](c, ctx, http.MethodPost, url, headers, body) } func put[T any](c *Client, ctx context.Context, endpoint string, headers http.Header, body io.Reader) (*T, error) { - ctx, cancel := context.WithTimeout(ctx, c.timeout) - defer cancel() - - fullUrl := fmt.Sprintf("%s%s", c.baseUrl, endpoint) - req, err := http.NewRequestWithContext(ctx, http.MethodPut, fullUrl, body) - if err != nil { - return nil, err - } - req.Header = headers - req.Header.Set("User-Agent", c.userAgent) - - res, err := c.httpClient.Do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() - - if res.StatusCode == http.StatusUnauthorized { - var errorRes unauthorizedError - if err := json.NewDecoder(res.Body).Decode(&errorRes); err != nil { - return nil, fmt.Errorf("failed to decode response body: %w", err) - } - return nil, fmt.Errorf("401 %s: %s", errorRes.Path, errorRes.ErrorMessage) - } else if res.StatusCode == http.StatusBadRequest { - var errorRes badRequestError - if err := json.NewDecoder(res.Body).Decode(&errorRes); err != nil { - return nil, fmt.Errorf("failed to decode response body: %w", err) - } - return nil, fmt.Errorf("400 %s: %s", errorRes.Path, errorRes.Error) - } else if res.StatusCode == http.StatusInternalServerError { - return nil, errors.New("500 internal server error") - } else if res.StatusCode != http.StatusOK { - return nil, fmt.Errorf("unexpected response from server: %d", res.StatusCode) - } - - var result T - if err := json.NewDecoder(res.Body).Decode(&result); err != nil { - return nil, fmt.Errorf("failed to decode response body: %w", err) - } - return &result, nil + url := c.baseUrl + endpoint + return do[T](c, ctx, http.MethodPut, url, headers, body) } type unauthorizedError struct { diff --git a/internal/pkg/mojang/profile.go b/internal/pkg/mojang/profile.go index 7ae68a2..0e99961 100644 --- a/internal/pkg/mojang/profile.go +++ b/internal/pkg/mojang/profile.go @@ -16,16 +16,16 @@ func isURL(s string) bool { return err == nil && (u.Scheme == "http" || u.Scheme == "https") } -func (c *Client) ProfileInformation(ctx context.Context, accountToken string) (*ProfileInformationResponse, error) { +func (c *Client) ProfileInformation(ctx context.Context) (*ProfileInformationResponse, error) { headers := http.Header{} - headers.Set("Authorization", "Bearer "+accountToken) + headers.Set("Authorization", "Bearer "+c.accountToken) - return get[ProfileInformationResponse](c, ctx, "", headers) + return get[ProfileInformationResponse](c, ctx, "/", headers) } -func (c *Client) ChangeSkin(ctx context.Context, accountToken string, texture string, variant string) (*ProfileInformationResponse, error) { +func (c *Client) ChangeSkin(ctx context.Context, texture string, variant string) (*ProfileInformationResponse, error) { var body *bytes.Buffer var contentType string @@ -76,15 +76,15 @@ func (c *Client) ChangeSkin(ctx context.Context, accountToken string, texture st headers := http.Header{} headers.Set("Content-Type", contentType) - headers.Set("Authorization", "Bearer "+accountToken) + headers.Set("Authorization", "Bearer "+c.accountToken) return post[ProfileInformationResponse](c, ctx, "/skins", headers, body) } -func (c *Client) ChangeCape(ctx context.Context, accountToken string, cape string) (*ProfileInformationResponse, error) { - endpoint := "capes/active" +func (c *Client) ChangeCape(ctx context.Context, cape string) (*ProfileInformationResponse, error) { + endpoint := "/capes/active" headers := http.Header{} - headers.Set("Authorization", "Bearer "+accountToken) + headers.Set("Authorization", "Bearer "+c.accountToken) headers.Set("Content-Type", "application/json") requestData := map[string]string{ @@ -99,30 +99,22 @@ func (c *Client) ChangeCape(ctx context.Context, accountToken string, cape strin return put[ProfileInformationResponse](c, ctx, endpoint, headers, bytes.NewBuffer(jsonData)) } -func (c *Client) DeleteCape(ctx context.Context, accountToken string) (*ProfileInformationResponse, error) { - endpoint := "capes/active" +func (c *Client) DeleteCape(ctx context.Context) (*ProfileInformationResponse, error) { + endpoint := "/capes/active" headers := http.Header{} - headers.Set("Authorization", "Bearer "+accountToken) + headers.Set("Authorization", "Bearer "+c.accountToken) return delete[ProfileInformationResponse](c, ctx, endpoint, headers) } func (c *Client) UsernameToUuid(ctx context.Context, username string) (*UsernameToUuidResponse, error) { - oldUrl := c.baseUrl - c.baseUrl = mojangApiUrl // i dont like this but i cant think of any other way atm :( - endpoint := "users/profiles/minecraft/" + username + url := mojangApiUrl + "/users/profiles/minecraft/" + username - response, err := get[UsernameToUuidResponse](c, ctx, endpoint, http.Header{}) - c.baseUrl = oldUrl - return response, err + return do[UsernameToUuidResponse](c, ctx, http.MethodGet, url, http.Header{}, nil) } func (c *Client) UuidToProfile(ctx context.Context, uuid string) (*UuidToProfileResponse, error) { - oldUrl := c.baseUrl - c.baseUrl = sessionserverUrl // i dont like this but i cant think of any other way atm :( - endpoint := "session/minecraft/profile/" + uuid + url := sessionserverUrl + "/session/minecraft/profile/" + uuid - response, err := get[UuidToProfileResponse](c, ctx, endpoint, http.Header{}) - c.baseUrl = oldUrl - return response, err + return do[UuidToProfileResponse](c, ctx, http.MethodGet, url, http.Header{}, nil) } diff --git a/internal/pkg/skin/skin.go b/internal/pkg/skin/skin.go index 3c8b46a..c86dd06 100644 --- a/internal/pkg/skin/skin.go +++ b/internal/pkg/skin/skin.go @@ -49,10 +49,10 @@ func isImage(data []byte) bool { } type Manager interface { - CreateSkin(name string, variant string, skinData string, capeData string, client *mojang.Client, ctx context.Context) (*Skin, error) + CreateSkin(ctx context.Context, client *mojang.Client, name string, variant string, skinData string, capeData string) (*Skin, error) Skins() []*Skin GetSkin(name string) (*Skin, error) - ApplySkin(s *Skin, client *mojang.Client, ctx context.Context, accountToken string) error + ApplySkin(ctx context.Context, client *mojang.Client, s *Skin) error Save() error } @@ -91,7 +91,7 @@ func NewManager(dataDir string) (Manager, error) { return &manager, nil } -func (m *fileManager) CreateSkin(name string, variant string, skinData string, capeData string, client *mojang.Client, ctx context.Context) (*Skin, error) { +func (m *fileManager) CreateSkin(ctx context.Context, client *mojang.Client, name string, variant string, skinData string, capeData string) (*Skin, error) { if !isValidName(name) { return nil, ErrInvalidName } @@ -116,7 +116,7 @@ func (m *fileManager) CreateSkin(name string, variant string, skinData string, c base64Str := base64.StdEncoding.EncodeToString(fileBytes) skin.Skin = base64Str } else { - texture, newVariant := getSkinInfo(skinData, variant, client, ctx) + texture, newVariant := getSkinInfo(ctx, client, skinData, variant) skin.Skin = texture skin.Variant = newVariant } @@ -133,7 +133,7 @@ func (m *fileManager) CreateSkin(name string, variant string, skinData string, c return skin, nil } -func getSkinInfo(skinData string, variant string, client *mojang.Client, ctx context.Context) (string, string) { +func getSkinInfo(ctx context.Context, client *mojang.Client, skinData string, variant string) (string, string) { if util.IsUUID(skinData) { profile, err := client.UuidToProfile(ctx, skinData) if err != nil { @@ -209,17 +209,17 @@ func (m *fileManager) GetSkin(name string) (*Skin, error) { return nil, ErrNotFound } -func (m *fileManager) ApplySkin(s *Skin, client *mojang.Client, ctx context.Context, accountToken string) error { +func (m *fileManager) ApplySkin(ctx context.Context, client *mojang.Client, s *Skin) error { var newCape bool if s.Cape == "none" { - _, err := client.DeleteCape(ctx, accountToken) + _, err := client.DeleteCape(ctx) if err != nil { return err } } - info, err := client.ChangeSkin(ctx, accountToken, s.Skin, s.Variant) + info, err := client.ChangeSkin(ctx, s.Skin, s.Variant) if err != nil { return err } @@ -233,7 +233,7 @@ func (m *fileManager) ApplySkin(s *Skin, client *mojang.Client, ctx context.Cont } if newCape { - _, err = client.ChangeCape(ctx, accountToken, s.Cape) + _, err = client.ChangeCape(ctx, s.Cape) if err != nil { return err } diff --git a/internal/pkg/util/useragent.go b/internal/pkg/util/useragent.go new file mode 100644 index 0000000..e812448 --- /dev/null +++ b/internal/pkg/util/useragent.go @@ -0,0 +1,9 @@ +package util + +import "fmt" + +const userAgentFormat = "mworzala/mc/%s" + +func MakeUserAgent(idVersion string) string { + return fmt.Sprintf(userAgentFormat, idVersion) +}