Skip to content

Commit fa11519

Browse files
committed
feat: skins from username/uuid
1 parent bfae4b2 commit fa11519

File tree

5 files changed

+129
-13
lines changed

5 files changed

+129
-13
lines changed

cmd/mc/skin/add.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ type addSkinOpts struct {
2626
var (
2727
ErrInvalidVariant = errors.New("invalid variant")
2828

29-
validationMap = map[string]bool{"classic": true, "slim": true}
29+
validationMap = map[string]bool{"classic": true, "slim": true, "": true}
3030
)
3131

3232
func newAddCmd(app *cli.App, account string) *cobra.Command {
@@ -47,7 +47,7 @@ func newAddCmd(app *cli.App, account string) *cobra.Command {
4747

4848
o.account = account
4949

50-
cmd.Flags().StringVar(&o.variant, "variant", "classic", "Skin variant [classic/slim]")
50+
cmd.Flags().StringVar(&o.variant, "variant", "", "Skin variant [classic/slim] (defaults to classic)")
5151
cmd.Flags().StringVar(&o.cape, "cape", "", "Cape name, 'none' to remove")
5252
cmd.Flags().BoolVar(&o.apply, "apply", false, "Apply the skin")
5353
cmd.Flags().BoolVar(&o.apply, "set", false, "Apply the skin")
@@ -112,7 +112,7 @@ func (o *addSkinOpts) execute(args []string) error {
112112

113113
skinData := args[0]
114114

115-
skin, err := o.app.SkinManager().CreateSkin(o.name, o.variant, skinData, o.cape)
115+
skin, err := o.app.SkinManager().CreateSkin(o.name, o.variant, skinData, o.cape, client, ctx)
116116
if err != nil {
117117
return err
118118
}

internal/pkg/mojang/mojang.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ const (
1515
)
1616

1717
var (
18-
servicesApiUrl = "https://api.minecraftservices.com/"
19-
profileApiUrl = servicesApiUrl + "minecraft/profile"
18+
mojangApiUrl = "https://api.mojang.com/"
19+
sessionserverUrl = "https://sessionserver.mojang.com/"
20+
servicesApiUrl = "https://api.minecraftservices.com/"
21+
profileApiUrl = servicesApiUrl + "minecraft/profile"
2022
)
2123

2224
type Client struct {

internal/pkg/mojang/profile.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func (c *Client) ChangeSkin(ctx context.Context, accountToken string, texture st
8282
}
8383

8484
func (c *Client) ChangeCape(ctx context.Context, accountToken string, cape string) (*ProfileInformationResponse, error) {
85-
endpoint := "/capes/active"
85+
endpoint := "capes/active"
8686
headers := http.Header{}
8787
headers.Set("Authorization", "Bearer "+accountToken)
8888
headers.Set("Content-Type", "application/json")
@@ -100,9 +100,29 @@ func (c *Client) ChangeCape(ctx context.Context, accountToken string, cape strin
100100
}
101101

102102
func (c *Client) DeleteCape(ctx context.Context, accountToken string) (*ProfileInformationResponse, error) {
103-
endpoint := "/capes/active"
103+
endpoint := "capes/active"
104104
headers := http.Header{}
105105
headers.Set("Authorization", "Bearer "+accountToken)
106106

107107
return delete[ProfileInformationResponse](c, ctx, endpoint, headers)
108108
}
109+
110+
func (c *Client) UsernameToUuid(ctx context.Context, username string) (*UsernameToUuidResponse, error) {
111+
oldUrl := c.baseUrl
112+
c.baseUrl = mojangApiUrl // i dont like this but i cant think of any other way atm :(
113+
endpoint := "users/profiles/minecraft/" + username
114+
115+
response, err := get[UsernameToUuidResponse](c, ctx, endpoint, http.Header{})
116+
c.baseUrl = oldUrl
117+
return response, err
118+
}
119+
120+
func (c *Client) UuidToProfile(ctx context.Context, uuid string) (*UuidToProfileResponse, error) {
121+
oldUrl := c.baseUrl
122+
c.baseUrl = sessionserverUrl // i dont like this but i cant think of any other way atm :(
123+
endpoint := "session/minecraft/profile/" + uuid
124+
125+
response, err := get[UuidToProfileResponse](c, ctx, endpoint, http.Header{})
126+
c.baseUrl = oldUrl
127+
return response, err
128+
}

internal/pkg/mojang/types.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,40 @@ type ProfileCape struct {
2323
URL string `json:"url"`
2424
Alias string `json:"alias"`
2525
}
26+
27+
type UsernameToUuidResponse struct {
28+
Name string `json:"name"`
29+
Id string `json:"id"`
30+
}
31+
32+
type UuidToProfileResponse struct {
33+
Id string `json:"id"`
34+
Name string `json:"name"`
35+
Properties []ProfileProperties `json:"properties"`
36+
Legacy bool `json:"legacy"`
37+
}
38+
39+
type ProfileProperties struct {
40+
Name string `json:"name"`
41+
Value string `json:"value"`
42+
Signature string `json:"signature"`
43+
}
44+
45+
type TextureInformation struct {
46+
Timestamp int `json:"timestamp"`
47+
ProfileId string `json:"profileId"`
48+
ProfileName string `json:"profileName"`
49+
Textures Textures `json:"textures"`
50+
}
51+
52+
type Textures struct {
53+
Skin struct {
54+
Url string `json:"url"`
55+
Metadata struct {
56+
Model string `json:"model"`
57+
} `json:"metadata"`
58+
} `json:"SKIN"`
59+
Cape struct {
60+
Url string `json:"url"`
61+
} `json:"CAPE"`
62+
}

internal/pkg/skin/skin.go

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"time"
1616

1717
"github.com/mworzala/mc/internal/pkg/mojang"
18+
"github.com/mworzala/mc/internal/pkg/util"
1819
)
1920

2021
var (
@@ -48,7 +49,7 @@ func isImage(data []byte) bool {
4849
}
4950

5051
type Manager interface {
51-
CreateSkin(name string, variant string, skinData string, capeData string) (*Skin, error)
52+
CreateSkin(name string, variant string, skinData string, capeData string, client *mojang.Client, ctx context.Context) (*Skin, error)
5253
Skins() []*Skin
5354
GetSkin(name string) (*Skin, error)
5455
ApplySkin(s *Skin, client *mojang.Client, ctx context.Context, accountToken string) error
@@ -90,7 +91,7 @@ func NewManager(dataDir string) (Manager, error) {
9091
return &manager, nil
9192
}
9293

93-
func (m *fileManager) CreateSkin(name string, variant string, skinData string, capeData string) (*Skin, error) {
94+
func (m *fileManager) CreateSkin(name string, variant string, skinData string, capeData string, client *mojang.Client, ctx context.Context) (*Skin, error) {
9495
if !isValidName(name) {
9596
return nil, ErrInvalidName
9697
}
@@ -99,9 +100,8 @@ func (m *fileManager) CreateSkin(name string, variant string, skinData string, c
99100
}
100101

101102
skin := &Skin{
102-
Name: name,
103-
Cape: capeData,
104-
Variant: variant,
103+
Name: name,
104+
Cape: capeData,
105105
}
106106
if isFilePath(skinData) {
107107
fileBytes, err := os.ReadFile(skinData)
@@ -116,7 +116,15 @@ func (m *fileManager) CreateSkin(name string, variant string, skinData string, c
116116
base64Str := base64.StdEncoding.EncodeToString(fileBytes)
117117
skin.Skin = base64Str
118118
} else {
119-
skin.Skin = skinData
119+
texture, newVariant := getSkinInfo(skinData, variant, client, ctx)
120+
skin.Skin = texture
121+
skin.Variant = newVariant
122+
}
123+
124+
if variant == "" && skin.Variant == "" {
125+
skin.Variant = "classic"
126+
} else if skin.Variant == "" {
127+
skin.Variant = variant
120128
}
121129

122130
skin.AddedDate = time.Now()
@@ -125,6 +133,55 @@ func (m *fileManager) CreateSkin(name string, variant string, skinData string, c
125133
return skin, nil
126134
}
127135

136+
func getSkinInfo(skinData string, variant string, client *mojang.Client, ctx context.Context) (string, string) {
137+
if util.IsUUID(skinData) {
138+
profile, err := client.UuidToProfile(ctx, skinData)
139+
if err != nil {
140+
return skinData, variant
141+
}
142+
143+
base64TextureInfo, err := base64.StdEncoding.DecodeString(profile.Properties[0].Value)
144+
if err != nil {
145+
return skinData, variant
146+
}
147+
var textureInfo mojang.TextureInformation
148+
err = json.Unmarshal(base64TextureInfo, &textureInfo)
149+
if err != nil {
150+
return skinData, variant
151+
}
152+
if variant == "" {
153+
variant = textureInfo.Textures.Skin.Metadata.Model
154+
}
155+
return textureInfo.Textures.Skin.Url, variant
156+
157+
} else {
158+
uuid, err := client.UsernameToUuid(ctx, skinData)
159+
if err != nil {
160+
return skinData, variant
161+
}
162+
163+
profile, err := client.UuidToProfile(ctx, uuid.Id)
164+
if err != nil {
165+
return skinData, variant
166+
}
167+
base64texture := profile.Properties[0].Value
168+
169+
base64TextureInfo, err := base64.StdEncoding.DecodeString(base64texture)
170+
if err != nil {
171+
return skinData, variant
172+
}
173+
var textureInfo mojang.TextureInformation
174+
err = json.Unmarshal(base64TextureInfo, &textureInfo)
175+
if err != nil {
176+
return skinData, variant
177+
}
178+
if variant == "" {
179+
variant = textureInfo.Textures.Skin.Metadata.Model
180+
}
181+
return textureInfo.Textures.Skin.Url, variant
182+
}
183+
}
184+
128185
func (m *fileManager) Skins() (result []*Skin) {
129186
for _, s := range m.AllSkins {
130187
result = append(result, s)

0 commit comments

Comments
 (0)