Skip to content

Skin Manager #21

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 6, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions cmd/mc/skin/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ type addSkinOpts struct {
var (
ErrInvalidVariant = errors.New("invalid variant")

validationMap = map[string]bool{"classic": true, "slim": true}
validationMap = map[string]bool{"classic": true, "slim": true, "": true}
)

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

o.account = account

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

skinData := args[0]

skin, err := o.app.SkinManager().CreateSkin(o.name, o.variant, skinData, o.cape)
skin, err := o.app.SkinManager().CreateSkin(o.name, o.variant, skinData, o.cape, client, ctx)
if err != nil {
return err
}
Expand Down
6 changes: 4 additions & 2 deletions internal/pkg/mojang/mojang.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ const (
)

var (
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 {
Expand Down
24 changes: 22 additions & 2 deletions internal/pkg/mojang/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func (c *Client) ChangeSkin(ctx context.Context, accountToken string, texture st
}

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

func (c *Client) DeleteCape(ctx context.Context, accountToken string) (*ProfileInformationResponse, error) {
endpoint := "/capes/active"
endpoint := "capes/active"
headers := http.Header{}
headers.Set("Authorization", "Bearer "+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

response, err := get[UsernameToUuidResponse](c, ctx, endpoint, http.Header{})
c.baseUrl = oldUrl
return response, err
}

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

response, err := get[UuidToProfileResponse](c, ctx, endpoint, http.Header{})
c.baseUrl = oldUrl
return response, err
}
37 changes: 37 additions & 0 deletions internal/pkg/mojang/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,40 @@ type ProfileCape struct {
URL string `json:"url"`
Alias string `json:"alias"`
}

type UsernameToUuidResponse struct {
Name string `json:"name"`
Id string `json:"id"`
}

type UuidToProfileResponse struct {
Id string `json:"id"`
Name string `json:"name"`
Properties []ProfileProperties `json:"properties"`
Legacy bool `json:"legacy"`
}

type ProfileProperties struct {
Name string `json:"name"`
Value string `json:"value"`
Signature string `json:"signature"`
}

type TextureInformation struct {
Timestamp int `json:"timestamp"`
ProfileId string `json:"profileId"`
ProfileName string `json:"profileName"`
Textures Textures `json:"textures"`
}

type Textures struct {
Skin struct {
Url string `json:"url"`
Metadata struct {
Model string `json:"model"`
} `json:"metadata"`
} `json:"SKIN"`
Cape struct {
Url string `json:"url"`
} `json:"CAPE"`
}
69 changes: 63 additions & 6 deletions internal/pkg/skin/skin.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"time"

"github.com/mworzala/mc/internal/pkg/mojang"
"github.com/mworzala/mc/internal/pkg/util"
)

var (
Expand Down Expand Up @@ -48,7 +49,7 @@ func isImage(data []byte) bool {
}

type Manager interface {
CreateSkin(name string, variant string, skinData string, capeData string) (*Skin, error)
CreateSkin(name string, variant string, skinData string, capeData string, client *mojang.Client, ctx context.Context) (*Skin, error)
Skins() []*Skin
GetSkin(name string) (*Skin, error)
ApplySkin(s *Skin, client *mojang.Client, ctx context.Context, accountToken string) error
Expand Down Expand Up @@ -90,7 +91,7 @@ func NewManager(dataDir string) (Manager, error) {
return &manager, nil
}

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

skin := &Skin{
Name: name,
Cape: capeData,
Variant: variant,
Name: name,
Cape: capeData,
}
if isFilePath(skinData) {
fileBytes, err := os.ReadFile(skinData)
Expand All @@ -116,7 +116,15 @@ func (m *fileManager) CreateSkin(name string, variant string, skinData string, c
base64Str := base64.StdEncoding.EncodeToString(fileBytes)
skin.Skin = base64Str
} else {
skin.Skin = skinData
texture, newVariant := getSkinInfo(skinData, variant, client, ctx)
skin.Skin = texture
skin.Variant = newVariant
}

if variant == "" && skin.Variant == "" {
skin.Variant = "classic"
} else if skin.Variant == "" {
skin.Variant = variant
}

skin.AddedDate = time.Now()
Expand All @@ -125,6 +133,55 @@ 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) {
if util.IsUUID(skinData) {
profile, err := client.UuidToProfile(ctx, skinData)
if err != nil {
return skinData, variant
}

base64TextureInfo, err := base64.StdEncoding.DecodeString(profile.Properties[0].Value)
if err != nil {
return skinData, variant
}
var textureInfo mojang.TextureInformation
err = json.Unmarshal(base64TextureInfo, &textureInfo)
if err != nil {
return skinData, variant
}
if variant == "" {
variant = textureInfo.Textures.Skin.Metadata.Model
}
return textureInfo.Textures.Skin.Url, variant

} else {
uuid, err := client.UsernameToUuid(ctx, skinData)
if err != nil {
return skinData, variant
}

profile, err := client.UuidToProfile(ctx, uuid.Id)
if err != nil {
return skinData, variant
}
base64texture := profile.Properties[0].Value

base64TextureInfo, err := base64.StdEncoding.DecodeString(base64texture)
if err != nil {
return skinData, variant
}
var textureInfo mojang.TextureInformation
err = json.Unmarshal(base64TextureInfo, &textureInfo)
if err != nil {
return skinData, variant
}
if variant == "" {
variant = textureInfo.Textures.Skin.Metadata.Model
}
return textureInfo.Textures.Skin.Url, variant
}
}

func (m *fileManager) Skins() (result []*Skin) {
for _, s := range m.AllSkins {
result = append(result, s)
Expand Down
Loading