diff --git a/api/account.go b/api/account.go index f820543..b48b50a 100644 --- a/api/account.go +++ b/api/account.go @@ -107,9 +107,182 @@ func (a *Account) JoinServer(serverId string) (err error) { return } +type NameChangResp struct { + ChangedAt string `json:"changedAt"` + CreatedAt string `json:"createdAt"` + NameChangeAllowed bool `json:"nameChangeAllowed"` +} + +func (a *Account) NameChangeInfo() (r NameChangResp, err error) { + err = a.doMcApiRequest("GET", "https://api.minecraftservices.com/minecraft/profile/namechange", nil, &r) + return +} + func NewOfflineAccount(name string) *Account { return &Account{ Name: name, Uuid: crypto.FromOfflinePlayer(name), } } + +// PlayerProfile represents the player's profile information +type PlayerProfile struct { + ID string `json:"id"` + Name string `json:"name"` + Skins []struct { + ID string `json:"id"` + State string `json:"state"` + URL string `json:"url"` + Variant string `json:"variant"` + } `json:"skins"` + Capes []struct { + ID string `json:"id"` + State string `json:"state"` + URL string `json:"url"` + Alias string `json:"alias"` + } `json:"capes"` +} + +// QueryProfile queries the player's profile +func (a *Account) QueryProfile() (profile PlayerProfile, err error) { + err = a.doMcApiRequest("GET", BaseUrlApi+"/minecraft/profile", nil, &profile) + return +} + +// PlayerAttributes represents the player's attributes +type PlayerAttributes struct { + Privileges struct { + OnlineChat struct { + Enabled bool `json:"enabled"` + } `json:"onlineChat"` + MultiplayerServer struct { + Enabled bool `json:"enabled"` + } `json:"multiplayerServer"` + MultiplayerRealms struct { + Enabled bool `json:"enabled"` + } `json:"multiplayerRealms"` + Telemetry struct { + Enabled bool `json:"enabled"` + } `json:"telemetry"` + OptionalTelemetry struct { + Enabled bool `json:"enabled"` + } `json:"optionalTelemetry"` + } `json:"privileges"` + ProfanityFilterPreferences struct { + ProfanityFilterOn bool `json:"profanityFilterOn"` + } `json:"profanityFilterPreferences"` + BanStatus struct { + BannedScopes map[string]struct { + BanID string `json:"banId"` + Expires string `json:"expires,omitempty"` + Reason string `json:"reason"` + ReasonMessage string `json:"reasonMessage"` + } `json:"bannedScopes"` + } `json:"banStatus"` +} + +// QueryAttributes queries the player's attributes +func (a *Account) QueryAttributes() (attrs PlayerAttributes, err error) { + err = a.doMcApiRequest("GET", BaseUrlApi+"/player/attributes", nil, &attrs) + return +} + +// ModifyAttributesRequest represents the request to modify player attributes +type ModifyAttributesRequest struct { + ProfanityFilterPreferences struct { + ProfanityFilterOn bool `json:"profanityFilterOn"` + } `json:"profanityFilterPreferences"` +} + +// ModifyAttributes modifies the player's attributes +func (a *Account) ModifyAttributes(req ModifyAttributesRequest) (attrs PlayerAttributes, err error) { + err = a.doMcApiRequest("POST", BaseUrlApi+"/player/attributes", req, &attrs) + return +} + +// BlockedUsers represents the list of blocked users +type BlockedUsers struct { + BlockedProfiles []string `json:"blockedProfiles"` +} + +// GetBlocklist gets the list of blocked users +func (a *Account) GetBlocklist() (blocklist BlockedUsers, err error) { + err = a.doMcApiRequest("GET", BaseUrlApi+"/privacy/blocklist", nil, &blocklist) + return +} + +// CheckNameAvailability checks if a name is available +type NameAvailability struct { + Status string `json:"status"` // DUPLICATE, AVAILABLE, NOT_ALLOWED +} + +// CheckNameAvailability checks if a name is available +func (a *Account) CheckNameAvailability(name string) (avail NameAvailability, err error) { + err = a.doMcApiRequest("GET", BaseUrlApi+"/minecraft/profile/name/"+name+"/available", nil, &avail) + return +} + +// ChangeName changes the player's name +func (a *Account) ChangeName(newName string) (profile PlayerProfile, err error) { + err = a.doMcApiRequest("PUT", BaseUrlApi+"/minecraft/profile/name/"+newName, nil, &profile) + return +} + +// ChangeSkinRequest represents the request to change skin +type ChangeSkinRequest struct { + Variant string `json:"variant"` // "classic" or "slim" + URL string `json:"url"` +} + +// ChangeSkin changes the player's skin +func (a *Account) ChangeSkin(req ChangeSkinRequest) (profile PlayerProfile, err error) { + err = a.doMcApiRequest("POST", BaseUrlApi+"/minecraft/profile/skins", req, &profile) + return +} + +// ResetSkin resets the player's skin to default +func (a *Account) ResetSkin() (profile PlayerProfile, err error) { + err = a.doMcApiRequest("DELETE", BaseUrlApi+"/minecraft/profile/skins/active", nil, &profile) + return +} + +// HideCape hides the player's active cape +func (a *Account) HideCape() (profile PlayerProfile, err error) { + err = a.doMcApiRequest("DELETE", BaseUrlApi+"/minecraft/profile/capes/active", nil, &profile) + return +} + +// ShowCapeRequest represents the request to show a cape +type ShowCapeRequest struct { + CapeID string `json:"capeId"` +} + +// ShowCape shows a specific cape +func (a *Account) ShowCape(capeID string) (profile PlayerProfile, err error) { + req := ShowCapeRequest{CapeID: capeID} + err = a.doMcApiRequest("PUT", BaseUrlApi+"/minecraft/profile/capes/active", req, &profile) + return +} + +// CheckGiftCodeValidity checks if a gift code is valid +func (a *Account) CheckGiftCodeValidity(giftCode string) (valid bool, err error) { + url := BaseUrlApi + "/productvoucher/giftcode" + r, err := http.NewRequest("GET", url, nil) + if err != nil { + return false, err + } + + r.Header.Set("Authorization", "Bearer "+a.Ygg) + + resp, err := http.DefaultClient.Do(r) + if err != nil { + return false, err + } + defer resp.Body.Close() + + // Returns HTTP 200 or 204 if valid, 404 if invalid + if resp.StatusCode == 200 || resp.StatusCode == 204 { + return true, nil + } + return false, nil +} diff --git a/proto/connection_test.go b/proto/connection_test.go index d626d2d..78194c5 100644 --- a/proto/connection_test.go +++ b/proto/connection_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/admin-else/strom/proto" + "github.com/admin-else/strom/proto_base" "github.com/admin-else/strom/server" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" @@ -19,11 +20,13 @@ var nanIgnorer64 = cmp.Comparer(func(x, y float64) bool { return (math.IsNaN(x) && math.IsNaN(y)) || x == y }) -func FuzzConn(f *testing.F) { - f.Fuzz(func(t *testing.T, inbytes []byte) { +func NewStateFuzzer(state proto_base.State, actor proto_base.Actor) func(t *testing.T, inbytes []byte) { + return func(t *testing.T, inbytes []byte) { b1 := bytes.NewBuffer(inbytes) b2 := bytes.NewBuffer(nil) conn := server.Servee(nil) + conn.State = state + conn.Actor = actor conn.R = b1 conn.W = b2 _ = conn.SetVersion("1.21.8") // cant fail @@ -55,5 +58,45 @@ func FuzzConn(f *testing.F) { ) } return - }) + } +} + +func FuzzConnHandshakingToServer(f *testing.F) { + f.Fuzz(NewStateFuzzer(proto_base.Handshaking, proto_base.Server)) +} + +func FuzzConnHandshakingToClient(f *testing.F) { + f.Fuzz(NewStateFuzzer(proto_base.Handshaking, proto_base.Client)) +} + +func FuzzConnStatusToServer(f *testing.F) { + f.Fuzz(NewStateFuzzer(proto_base.Status, proto_base.Server)) +} + +func FuzzConnStatusToClient(f *testing.F) { + f.Fuzz(NewStateFuzzer(proto_base.Status, proto_base.Client)) +} + +func FuzzConnLoginToServer(f *testing.F) { + f.Fuzz(NewStateFuzzer(proto_base.Login, proto_base.Server)) +} + +func FuzzConnLoginToClient(f *testing.F) { + f.Fuzz(NewStateFuzzer(proto_base.Login, proto_base.Client)) +} + +func FuzzConnConfigToServer(f *testing.F) { + f.Fuzz(NewStateFuzzer(proto_base.Configuration, proto_base.Server)) +} + +func FuzzConnConfigToClient(f *testing.F) { + f.Fuzz(NewStateFuzzer(proto_base.Configuration, proto_base.Client)) +} + +func FuzzConnPlayToServer(f *testing.F) { + f.Fuzz(NewStateFuzzer(proto_base.Play, proto_base.Server)) +} + +func FuzzConnPlayToClient(f *testing.F) { + f.Fuzz(NewStateFuzzer(proto_base.Play, proto_base.Client)) }