diff --git a/api/mojang/mojang.go b/api/mojang/mojang.go new file mode 100644 index 0000000..c25c61b --- /dev/null +++ b/api/mojang/mojang.go @@ -0,0 +1,87 @@ +package mojang + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "net/http" +) + +var HttpClient = &http.Client{} + +type SignedTexturesResponse struct { + Id string `json:"id"` + Name string `json:"name"` + Props []Property `json:"properties"` +} + +type Property struct { + Name string `json:"name"` + Signature string `json:"signature,omitempty"` + Value string `json:"value"` +} + +type ProfileInfo struct { + Id string `json:"id"` + Name string `json:"name"` + IsLegacy bool `json:"legacy,omitempty"` + IsDemo bool `json:"demo,omitempty"` +} + +func UsernamesToUuids(usernames []string) ([]*ProfileInfo, error) { + requestBody, _ := json.Marshal(usernames) + request, err := http.NewRequest("POST", "https://api.mojang.com/profiles/minecraft", bytes.NewBuffer(requestBody)) + if err != nil { + panic(err) + } + + request.Header.Set("Content-Type", "application/json") + + response, err := HttpClient.Do(request) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if response.StatusCode == 429 { + return nil, &TooManyRequestsError{} + } + + var result []*ProfileInfo + + body, _ := ioutil.ReadAll(response.Body) + _ = json.Unmarshal(body, &result) + + return result, nil +} + +func UuidToTextures(uuid string) (*SignedTexturesResponse, error) { + request, err := http.NewRequest("GET", "https://sessionserver.mojang.com/session/minecraft/profile/"+uuid, nil) + if err != nil { + panic(err) + } + + response, err := HttpClient.Do(request) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if response.StatusCode == 429 { + return nil, &TooManyRequestsError{} + } + + var result *SignedTexturesResponse + + body, _ := ioutil.ReadAll(response.Body) + _ = json.Unmarshal(body, &result) + + return result, nil +} + +type TooManyRequestsError struct { +} + +func (e *TooManyRequestsError) Error() string { + return "Too Many Requests" +} diff --git a/api/mojang/mojang_test.go b/api/mojang/mojang_test.go new file mode 100644 index 0000000..83ae2ab --- /dev/null +++ b/api/mojang/mojang_test.go @@ -0,0 +1,130 @@ +package mojang + +import ( + "net/http" + "testing" + + testify "github.com/stretchr/testify/assert" + "gopkg.in/h2non/gock.v1" +) + +func TestUsernamesToUuidsCorrectResponse(t *testing.T) { + assert := testify.New(t) + + defer gock.Off() + gock.New("https://api.mojang.com"). + Post("/profiles/minecraft"). + JSON([]string{"Thinkofdeath", "maksimkurb"}). + Reply(200). + JSON([]map[string]interface{}{ + { + "id": "4566e69fc90748ee8d71d7ba5aa00d20", + "name": "Thinkofdeath", + "legacy": false, + "demo": true, + }, + { + "id": "0d252b7218b648bfb86c2ae476954d32", + "name": "maksimkurb", + // There is no legacy or demo fields + }, + }) + + client := &http.Client{} + gock.InterceptClient(client) + + HttpClient = client + + result, err := UsernamesToUuids([]string{"Thinkofdeath", "maksimkurb"}) + if assert.NoError(err) { + assert.Len(result, 2) + assert.Equal("4566e69fc90748ee8d71d7ba5aa00d20", result[0].Id) + assert.Equal("Thinkofdeath", result[0].Name) + assert.False(result[0].IsLegacy) + assert.True(result[0].IsDemo) + + assert.Equal("0d252b7218b648bfb86c2ae476954d32", result[1].Id) + assert.Equal("maksimkurb", result[1].Name) + assert.False(result[1].IsLegacy) + assert.False(result[1].IsDemo) + } +} + +func TestUsernamesToUuidsTooManyRequests(t *testing.T) { + assert := testify.New(t) + + defer gock.Off() + gock.New("https://api.mojang.com"). + Post("/profiles/minecraft"). + Reply(429). + JSON(map[string]interface{}{ + "error": "TooManyRequestsException", + "errorMessage": "The client has sent too many requests within a certain amount of time", + }) + + client := &http.Client{} + gock.InterceptClient(client) + + HttpClient = client + + result, err := UsernamesToUuids([]string{"Thinkofdeath", "maksimkurb"}) + assert.Nil(result) + assert.IsType(&TooManyRequestsError{}, err) + assert.EqualError(err, "Too Many Requests") +} + +func TestUuidToTexturesCorrectResponse(t *testing.T) { + assert := testify.New(t) + + defer gock.Off() + gock.New("https://sessionserver.mojang.com"). + Get("/session/minecraft/profile/4566e69fc90748ee8d71d7ba5aa00d20"). + Reply(200). + JSON(map[string]interface{}{ + "id": "4566e69fc90748ee8d71d7ba5aa00d20", + "name": "Thinkofdeath", + "properties": []interface{}{ + map[string]interface{}{ + "name": "textures", + "value": "eyJ0aW1lc3RhbXAiOjE1NDMxMDczMDExODUsInByb2ZpbGVJZCI6IjQ1NjZlNjlmYzkwNzQ4ZWU4ZDcxZDdiYTVhYTAwZDIwIiwicHJvZmlsZU5hbWUiOiJUaGlua29mZGVhdGgiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzRkMWUwOGIwYmI3ZTlmNTkwYWYyNzc1ODEyNWJiZWQxNzc4YWM2Y2VmNzI5YWVkZmNiOTYxM2U5OTExYWU3NSJ9LCJDQVBFIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYjBjYzA4ODQwNzAwNDQ3MzIyZDk1M2EwMmI5NjVmMWQ2NWExM2E2MDNiZjY0YjE3YzgwM2MyMTQ0NmZlMTYzNSJ9fX0=", + }, + }, + }) + + client := &http.Client{} + gock.InterceptClient(client) + + HttpClient = client + + result, err := UuidToTextures("4566e69fc90748ee8d71d7ba5aa00d20") + if assert.NoError(err) { + assert.Equal("4566e69fc90748ee8d71d7ba5aa00d20", result.Id) + assert.Equal("Thinkofdeath", result.Name) + assert.Equal(1, len(result.Props)) + assert.Equal("textures", result.Props[0].Name) + assert.Equal(476, len(result.Props[0].Value)) + } +} + +func TestUuidToTexturesTooManyRequests(t *testing.T) { + assert := testify.New(t) + + defer gock.Off() + gock.New("https://sessionserver.mojang.com"). + Get("/session/minecraft/profile/4566e69fc90748ee8d71d7ba5aa00d20"). + Reply(429). + JSON(map[string]interface{}{ + "error": "TooManyRequestsException", + "errorMessage": "The client has sent too many requests within a certain amount of time", + }) + + client := &http.Client{} + gock.InterceptClient(client) + + HttpClient = client + + result, err := UuidToTextures("4566e69fc90748ee8d71d7ba5aa00d20") + assert.Nil(result) + assert.IsType(&TooManyRequestsError{}, err) + assert.EqualError(err, "Too Many Requests") +} diff --git a/http/signed_textures.go b/http/signed_textures.go index 158cdaa..4f127db 100644 --- a/http/signed_textures.go +++ b/http/signed_textures.go @@ -5,21 +5,10 @@ import ( "net/http" "strings" + "github.com/elyby/chrly/api/mojang" "github.com/gorilla/mux" ) -type signedTexturesResponse struct { - Id string `json:"id"` - Name string `json:"name"` - Props []property `json:"properties"` -} - -type property struct { - Name string `json:"name"` - Signature string `json:"signature,omitempty"` - Value string `json:"value"` -} - func (cfg *Config) SignedTextures(response http.ResponseWriter, request *http.Request) { cfg.Logger.IncCounter("signed_textures.request", 1) username := parseUsername(mux.Vars(request)["username"]) @@ -30,23 +19,23 @@ func (cfg *Config) SignedTextures(response http.ResponseWriter, request *http.Re return } - responseData:= signedTexturesResponse{ - Id: strings.Replace(rec.Uuid, "-", "", -1), + responseData := mojang.SignedTexturesResponse{ + Id: strings.Replace(rec.Uuid, "-", "", -1), Name: rec.Username, - Props: []property{ + Props: []mojang.Property{ { - Name: "textures", + Name: "textures", Signature: rec.MojangSignature, - Value: rec.MojangTextures, + Value: rec.MojangTextures, }, { - Name: "chrly", + Name: "chrly", Value: "how do you tame a horse in Minecraft?", }, }, } - responseJson,_ := json.Marshal(responseData) + responseJson, _ := json.Marshal(responseData) response.Header().Set("Content-Type", "application/json") response.Write(responseJson) }