mirror of
https://github.com/elyby/chrly.git
synced 2025-01-05 11:41:49 +05:30
#1: Integrate queue to the application
This commit is contained in:
parent
f3690686ec
commit
f7cdab243f
@ -16,6 +16,28 @@ type SignedTexturesResponse struct {
|
|||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Props []*Property `json:"properties"`
|
Props []*Property `json:"properties"`
|
||||||
|
decodedTextures *TexturesProp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *SignedTexturesResponse) DecodeTextures() *TexturesProp {
|
||||||
|
if t.decodedTextures == nil {
|
||||||
|
var texturesProp string
|
||||||
|
for _, prop := range t.Props {
|
||||||
|
if prop.Name == "textures" {
|
||||||
|
texturesProp = prop.Value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if texturesProp == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedTextures, _ := DecodeTextures(texturesProp)
|
||||||
|
t.decodedTextures = decodedTextures
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.decodedTextures
|
||||||
}
|
}
|
||||||
|
|
||||||
type Property struct {
|
type Property struct {
|
||||||
|
@ -8,6 +8,33 @@ import (
|
|||||||
"gopkg.in/h2non/gock.v1"
|
"gopkg.in/h2non/gock.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestSignedTexturesResponse(t *testing.T) {
|
||||||
|
t.Run("DecodeTextures", func(t *testing.T) {
|
||||||
|
obj := &SignedTexturesResponse{
|
||||||
|
Id: "00000000000000000000000000000000",
|
||||||
|
Name: "mock",
|
||||||
|
Props: []*Property{
|
||||||
|
{
|
||||||
|
Name: "textures",
|
||||||
|
Value: "eyJ0aW1lc3RhbXAiOjE1NTU4NTYzMDc0MTIsInByb2ZpbGVJZCI6IjNlM2VlNmMzNWFmYTQ4YWJiNjFlOGNkOGM0MmZjMGQ5IiwicHJvZmlsZU5hbWUiOiJFcmlja1NrcmF1Y2giLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZmMxNzU3NjMzN2ExMDZkOWMyMmFjNzgyZTM2MmMxNmM0ZTBlNDliZTUzZmFhNDE4NTdiZmYzMzJiNzc5MjgxZSJ9fX0=",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
textures := obj.DecodeTextures()
|
||||||
|
testify.Equal(t, "3e3ee6c35afa48abb61e8cd8c42fc0d9", textures.ProfileID)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("DecodedTextures without textures prop", func(t *testing.T) {
|
||||||
|
obj := &SignedTexturesResponse{
|
||||||
|
Id: "00000000000000000000000000000000",
|
||||||
|
Name: "mock",
|
||||||
|
Props: []*Property{},
|
||||||
|
}
|
||||||
|
textures := obj.DecodeTextures()
|
||||||
|
testify.Nil(t, textures)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestUsernamesToUuids(t *testing.T) {
|
func TestUsernamesToUuids(t *testing.T) {
|
||||||
t.Run("exchange usernames to uuids", func(t *testing.T) {
|
t.Run("exchange usernames to uuids", func(t *testing.T) {
|
||||||
assert := testify.New(t)
|
assert := testify.New(t)
|
||||||
|
@ -33,6 +33,7 @@ type JobsQueue struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *JobsQueue) GetTexturesForUsername(username string) chan *mojang.SignedTexturesResponse {
|
func (ctx *JobsQueue) GetTexturesForUsername(username string) chan *mojang.SignedTexturesResponse {
|
||||||
|
// TODO: convert username to lower case
|
||||||
ctx.onFirstCall.Do(func() {
|
ctx.onFirstCall.Do(func() {
|
||||||
ctx.queue.New()
|
ctx.queue.New()
|
||||||
ctx.broadcast = newBroadcaster()
|
ctx.broadcast = newBroadcaster()
|
||||||
|
@ -20,6 +20,29 @@ type Storage interface {
|
|||||||
TexturesStorage
|
TexturesStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SplittedStorage allows you to use separate storage engines to satisfy
|
||||||
|
// the Storage interface
|
||||||
|
type SplittedStorage struct {
|
||||||
|
UuidsStorage
|
||||||
|
TexturesStorage
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SplittedStorage) GetUuid(username string) (string, error) {
|
||||||
|
return s.UuidsStorage.GetUuid(username)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SplittedStorage) StoreUuid(username string, uuid string) {
|
||||||
|
s.UuidsStorage.StoreUuid(username, uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SplittedStorage) GetTextures(uuid string) (*mojang.SignedTexturesResponse, error) {
|
||||||
|
return s.TexturesStorage.GetTextures(uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SplittedStorage) StoreTextures(textures *mojang.SignedTexturesResponse) {
|
||||||
|
s.TexturesStorage.StoreTextures(textures)
|
||||||
|
}
|
||||||
|
|
||||||
// This error can be used to indicate, that requested
|
// This error can be used to indicate, that requested
|
||||||
// value doesn't exists in the storage
|
// value doesn't exists in the storage
|
||||||
type ValueNotFound struct {
|
type ValueNotFound struct {
|
||||||
|
88
api/mojang/queue/storage_test.go
Normal file
88
api/mojang/queue/storage_test.go
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
package queue
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/elyby/chrly/api/mojang"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type uuidsStorageMock struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *uuidsStorageMock) GetUuid(username string) (string, error) {
|
||||||
|
args := m.Called(username)
|
||||||
|
return args.String(0), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *uuidsStorageMock) StoreUuid(username string, uuid string) {
|
||||||
|
m.Called(username, uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
type texturesStorageMock struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *texturesStorageMock) GetTextures(uuid string) (*mojang.SignedTexturesResponse, error) {
|
||||||
|
args := m.Called(uuid)
|
||||||
|
var result *mojang.SignedTexturesResponse
|
||||||
|
if casted, ok := args.Get(0).(*mojang.SignedTexturesResponse); ok {
|
||||||
|
result = casted
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *texturesStorageMock) StoreTextures(textures *mojang.SignedTexturesResponse) {
|
||||||
|
m.Called(textures)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSplittedStorage(t *testing.T) {
|
||||||
|
createMockedStorage := func() (*SplittedStorage, *uuidsStorageMock, *texturesStorageMock) {
|
||||||
|
uuidsStorage := &uuidsStorageMock{}
|
||||||
|
texturesStorage := &texturesStorageMock{}
|
||||||
|
|
||||||
|
return &SplittedStorage{uuidsStorage, texturesStorage}, uuidsStorage, texturesStorage
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("GetUuid", func(t *testing.T) {
|
||||||
|
storage, uuidsMock, _ := createMockedStorage()
|
||||||
|
uuidsMock.On("GetUuid", "username").Once().Return("find me", nil)
|
||||||
|
result, err := storage.GetUuid("username")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "find me", result)
|
||||||
|
uuidsMock.AssertExpectations(t)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("StoreUuid", func(t *testing.T) {
|
||||||
|
storage, uuidsMock, _ := createMockedStorage()
|
||||||
|
uuidsMock.On("StoreUuid", "username", "result").Once()
|
||||||
|
storage.StoreUuid("username", "result")
|
||||||
|
uuidsMock.AssertExpectations(t)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("GetTextures", func(t *testing.T) {
|
||||||
|
result := &mojang.SignedTexturesResponse{Id: "mock id"}
|
||||||
|
storage, _, texturesMock := createMockedStorage()
|
||||||
|
texturesMock.On("GetTextures", "uuid").Once().Return(result, nil)
|
||||||
|
returned, err := storage.GetTextures("uuid")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, result, returned)
|
||||||
|
texturesMock.AssertExpectations(t)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("StoreTextures", func(t *testing.T) {
|
||||||
|
toStore := &mojang.SignedTexturesResponse{Id: "mock id"}
|
||||||
|
storage, _, texturesMock := createMockedStorage()
|
||||||
|
texturesMock.On("StoreTextures", toStore).Once()
|
||||||
|
storage.StoreTextures(toStore)
|
||||||
|
texturesMock.AssertExpectations(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValueNotFound_Error(t *testing.T) {
|
||||||
|
err := &ValueNotFound{}
|
||||||
|
assert.Equal(t, "value not found in the storage", err.Error())
|
||||||
|
}
|
27
cmd/serve.go
27
cmd/serve.go
@ -4,11 +4,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/elyby/chrly/auth"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
|
"github.com/elyby/chrly/api/mojang/queue"
|
||||||
|
"github.com/elyby/chrly/auth"
|
||||||
"github.com/elyby/chrly/bootstrap"
|
"github.com/elyby/chrly/bootstrap"
|
||||||
"github.com/elyby/chrly/db"
|
"github.com/elyby/chrly/db"
|
||||||
"github.com/elyby/chrly/http"
|
"github.com/elyby/chrly/http"
|
||||||
@ -27,7 +27,8 @@ var serveCmd = &cobra.Command{
|
|||||||
storageFactory := db.StorageFactory{Config: viper.GetViper()}
|
storageFactory := db.StorageFactory{Config: viper.GetViper()}
|
||||||
|
|
||||||
logger.Info("Initializing skins repository")
|
logger.Info("Initializing skins repository")
|
||||||
skinsRepo, err := storageFactory.CreateFactory("redis").CreateSkinsRepository()
|
redisFactory := storageFactory.CreateFactory("redis")
|
||||||
|
skinsRepo, err := redisFactory.CreateSkinsRepository()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Emergency(fmt.Sprintf("Error on creating skins repo: %+v", err))
|
logger.Emergency(fmt.Sprintf("Error on creating skins repo: %+v", err))
|
||||||
return
|
return
|
||||||
@ -35,17 +36,35 @@ var serveCmd = &cobra.Command{
|
|||||||
logger.Info("Skins repository successfully initialized")
|
logger.Info("Skins repository successfully initialized")
|
||||||
|
|
||||||
logger.Info("Initializing capes repository")
|
logger.Info("Initializing capes repository")
|
||||||
capesRepo, err := storageFactory.CreateFactory("filesystem").CreateCapesRepository()
|
filesystemFactory := storageFactory.CreateFactory("filesystem")
|
||||||
|
capesRepo, err := filesystemFactory.CreateCapesRepository()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Emergency(fmt.Sprintf("Error on creating capes repo: %v", err))
|
logger.Emergency(fmt.Sprintf("Error on creating capes repo: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Info("Capes repository successfully initialized")
|
logger.Info("Capes repository successfully initialized")
|
||||||
|
|
||||||
|
logger.Info("Preparing Mojang's textures queue")
|
||||||
|
mojangUuidsRepository, err := redisFactory.CreateMojangUuidsRepository()
|
||||||
|
if err != nil {
|
||||||
|
logger.Emergency(fmt.Sprintf("Error on creating mojang uuids repo: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mojangTexturesQueue := &queue.JobsQueue{
|
||||||
|
Logger: logger,
|
||||||
|
Storage: &queue.SplittedStorage{
|
||||||
|
UuidsStorage: mojangUuidsRepository,
|
||||||
|
TexturesStorage: queue.CreateInMemoryTexturesStorage(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
logger.Info("Mojang's textures queue is successfully initialized")
|
||||||
|
|
||||||
cfg := &http.Config{
|
cfg := &http.Config{
|
||||||
ListenSpec: fmt.Sprintf("%s:%d", viper.GetString("server.host"), viper.GetInt("server.port")),
|
ListenSpec: fmt.Sprintf("%s:%d", viper.GetString("server.host"), viper.GetInt("server.port")),
|
||||||
SkinsRepo: skinsRepo,
|
SkinsRepo: skinsRepo,
|
||||||
CapesRepo: capesRepo,
|
CapesRepo: capesRepo,
|
||||||
|
MojangTexturesQueue: mojangTexturesQueue,
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
Auth: &auth.JwtAuth{Key: []byte(viper.GetString("chrly.secret"))},
|
Auth: &auth.JwtAuth{Key: []byte(viper.GetString("chrly.secret"))},
|
||||||
}
|
}
|
||||||
|
21
http/cape.go
21
http/cape.go
@ -14,13 +14,26 @@ func (cfg *Config) Cape(response http.ResponseWriter, request *http.Request) {
|
|||||||
|
|
||||||
username := parseUsername(mux.Vars(request)["username"])
|
username := parseUsername(mux.Vars(request)["username"])
|
||||||
rec, err := cfg.CapesRepo.FindByUsername(username)
|
rec, err := cfg.CapesRepo.FindByUsername(username)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
http.Redirect(response, request, "http://skins.minecraft.net/MinecraftCloaks/" + username + ".png", 301)
|
request.Header.Set("Content-Type", "image/png")
|
||||||
|
io.Copy(response, rec.File)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
request.Header.Set("Content-Type", "image/png")
|
mojangTextures := <-cfg.MojangTexturesQueue.GetTexturesForUsername(username)
|
||||||
io.Copy(response, rec.File)
|
if mojangTextures == nil {
|
||||||
|
response.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
texturesProp := mojangTextures.DecodeTextures()
|
||||||
|
cape := texturesProp.Textures.Cape
|
||||||
|
if cape == nil {
|
||||||
|
response.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Redirect(response, request, cape.Url, 301)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *Config) CapeGET(response http.ResponseWriter, request *http.Request) {
|
func (cfg *Config) CapeGET(response http.ResponseWriter, request *http.Request) {
|
||||||
|
@ -21,6 +21,7 @@ type Config struct {
|
|||||||
|
|
||||||
SkinsRepo interfaces.SkinsRepository
|
SkinsRepo interfaces.SkinsRepository
|
||||||
CapesRepo interfaces.CapesRepository
|
CapesRepo interfaces.CapesRepository
|
||||||
|
MojangTexturesQueue interfaces.MojangTexturesQueue
|
||||||
Logger wd.Watchdog
|
Logger wd.Watchdog
|
||||||
Auth interfaces.AuthChecker
|
Auth interfaces.AuthChecker
|
||||||
}
|
}
|
||||||
|
@ -5,21 +5,20 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/elyby/chrly/api/mojang"
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
|
"github.com/elyby/chrly/api/mojang"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (cfg *Config) SignedTextures(response http.ResponseWriter, request *http.Request) {
|
func (cfg *Config) SignedTextures(response http.ResponseWriter, request *http.Request) {
|
||||||
cfg.Logger.IncCounter("signed_textures.request", 1)
|
cfg.Logger.IncCounter("signed_textures.request", 1)
|
||||||
username := parseUsername(mux.Vars(request)["username"])
|
username := parseUsername(mux.Vars(request)["username"])
|
||||||
|
|
||||||
rec, err := cfg.SkinsRepo.FindByUsername(username)
|
var responseData *mojang.SignedTexturesResponse
|
||||||
if err != nil || rec.SkinId == 0 || rec.MojangTextures == "" {
|
|
||||||
response.WriteHeader(http.StatusNoContent)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
responseData := mojang.SignedTexturesResponse{
|
rec, err := cfg.SkinsRepo.FindByUsername(username)
|
||||||
|
if err == nil && rec.SkinId != 0 && rec.MojangTextures != "" {
|
||||||
|
responseData = &mojang.SignedTexturesResponse{
|
||||||
Id: strings.Replace(rec.Uuid, "-", "", -1),
|
Id: strings.Replace(rec.Uuid, "-", "", -1),
|
||||||
Name: rec.Username,
|
Name: rec.Username,
|
||||||
Props: []*mojang.Property{
|
Props: []*mojang.Property{
|
||||||
@ -34,6 +33,14 @@ func (cfg *Config) SignedTextures(response http.ResponseWriter, request *http.Re
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
} else if request.URL.Query().Get("proxy") != "" {
|
||||||
|
responseData = <-cfg.MojangTexturesQueue.GetTexturesForUsername(username)
|
||||||
|
}
|
||||||
|
|
||||||
|
if responseData == nil {
|
||||||
|
response.WriteHeader(http.StatusNoContent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
responseJson, _ := json.Marshal(responseData)
|
responseJson, _ := json.Marshal(responseData)
|
||||||
response.Header().Set("Content-Type", "application/json")
|
response.Header().Set("Content-Type", "application/json")
|
||||||
|
19
http/skin.go
19
http/skin.go
@ -13,12 +13,25 @@ func (cfg *Config) Skin(response http.ResponseWriter, request *http.Request) {
|
|||||||
|
|
||||||
username := parseUsername(mux.Vars(request)["username"])
|
username := parseUsername(mux.Vars(request)["username"])
|
||||||
rec, err := cfg.SkinsRepo.FindByUsername(username)
|
rec, err := cfg.SkinsRepo.FindByUsername(username)
|
||||||
if err != nil || rec.SkinId == 0 {
|
if err == nil && rec.SkinId != 0 {
|
||||||
http.Redirect(response, request, "http://skins.minecraft.net/MinecraftSkins/" + username + ".png", 301)
|
http.Redirect(response, request, rec.Url, 301)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Redirect(response, request, rec.Url, 301)
|
mojangTextures := <-cfg.MojangTexturesQueue.GetTexturesForUsername(username)
|
||||||
|
if mojangTextures == nil {
|
||||||
|
response.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
texturesProp := mojangTextures.DecodeTextures()
|
||||||
|
skin := texturesProp.Textures.Skin
|
||||||
|
if skin == nil {
|
||||||
|
response.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Redirect(response, request, skin.Url, 301)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *Config) SkinGET(response http.ResponseWriter, request *http.Request) {
|
func (cfg *Config) SkinGET(response http.ResponseWriter, request *http.Request) {
|
||||||
|
@ -1,102 +1,64 @@
|
|||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
"github.com/elyby/chrly/model"
|
"github.com/elyby/chrly/api/mojang"
|
||||||
)
|
)
|
||||||
|
|
||||||
type texturesResponse struct {
|
|
||||||
Skin *Skin `json:"SKIN"`
|
|
||||||
Cape *Cape `json:"CAPE,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Skin struct {
|
|
||||||
Url string `json:"url"`
|
|
||||||
Hash string `json:"hash"`
|
|
||||||
Metadata *skinMetadata `json:"metadata,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type skinMetadata struct {
|
|
||||||
Model string `json:"model"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Cape struct {
|
|
||||||
Url string `json:"url"`
|
|
||||||
Hash string `json:"hash"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *Config) Textures(response http.ResponseWriter, request *http.Request) {
|
func (cfg *Config) Textures(response http.ResponseWriter, request *http.Request) {
|
||||||
cfg.Logger.IncCounter("textures.request", 1)
|
cfg.Logger.IncCounter("textures.request", 1)
|
||||||
username := parseUsername(mux.Vars(request)["username"])
|
username := parseUsername(mux.Vars(request)["username"])
|
||||||
|
|
||||||
|
var textures *mojang.TexturesResponse
|
||||||
skin, err := cfg.SkinsRepo.FindByUsername(username)
|
skin, err := cfg.SkinsRepo.FindByUsername(username)
|
||||||
if err != nil || skin.SkinId == 0 {
|
if err == nil && skin.SkinId != 0 {
|
||||||
if skin == nil {
|
textures = &mojang.TexturesResponse{
|
||||||
skin = &model.Skin{}
|
Skin: &mojang.SkinTexturesResponse{
|
||||||
}
|
|
||||||
|
|
||||||
skin.Url = "http://skins.minecraft.net/MinecraftSkins/" + username + ".png"
|
|
||||||
skin.Hash = string(buildNonElyTexturesHash(username))
|
|
||||||
}
|
|
||||||
|
|
||||||
textures := texturesResponse{
|
|
||||||
Skin: &Skin{
|
|
||||||
Url: skin.Url,
|
Url: skin.Url,
|
||||||
Hash: skin.Hash,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if skin.IsSlim {
|
if skin.IsSlim {
|
||||||
textures.Skin.Metadata = &skinMetadata{
|
textures.Skin.Metadata = &mojang.SkinTexturesMetadata{
|
||||||
Model: "slim",
|
Model: "slim",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cape, err := cfg.CapesRepo.FindByUsername(username)
|
_, err = cfg.CapesRepo.FindByUsername(username)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
var scheme string = "http://"
|
var scheme = "http://"
|
||||||
if request.TLS != nil {
|
if request.TLS != nil {
|
||||||
scheme = "https://"
|
scheme = "https://"
|
||||||
}
|
}
|
||||||
|
|
||||||
textures.Cape = &Cape{
|
textures.Cape = &mojang.CapeTexturesResponse{
|
||||||
Url: scheme + request.Host + "/cloaks/" + username,
|
Url: scheme + request.Host + "/cloaks/" + username,
|
||||||
Hash: calculateCapeHash(cape),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
mojangTextures := <-cfg.MojangTexturesQueue.GetTexturesForUsername(username)
|
||||||
|
if mojangTextures == nil {
|
||||||
|
// TODO: test compatibility with exists authlibs
|
||||||
|
response.WriteHeader(http.StatusNoContent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
texturesProp := mojangTextures.DecodeTextures()
|
||||||
|
if texturesProp == nil {
|
||||||
|
// TODO: test compatibility with exists authlibs
|
||||||
|
response.WriteHeader(http.StatusInternalServerError)
|
||||||
|
cfg.Logger.Error("Unable to find textures property")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
textures = texturesProp.Textures
|
||||||
|
}
|
||||||
|
|
||||||
responseData, _ := json.Marshal(textures)
|
responseData, _ := json.Marshal(textures)
|
||||||
response.Header().Set("Content-Type", "application/json")
|
response.Header().Set("Content-Type", "application/json")
|
||||||
response.Write(responseData)
|
response.Write(responseData)
|
||||||
}
|
}
|
||||||
|
|
||||||
func calculateCapeHash(cape *model.Cape) string {
|
|
||||||
hasher := md5.New()
|
|
||||||
io.Copy(hasher, cape.File)
|
|
||||||
|
|
||||||
return hex.EncodeToString(hasher.Sum(nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildNonElyTexturesHash(username string) string {
|
|
||||||
hour := getCurrentHour()
|
|
||||||
hasher := md5.New()
|
|
||||||
hasher.Write([]byte("non-ely-" + strconv.FormatInt(hour, 10) + "-" + username))
|
|
||||||
|
|
||||||
return hex.EncodeToString(hasher.Sum(nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
var timeNow = time.Now
|
|
||||||
|
|
||||||
func getCurrentHour() int64 {
|
|
||||||
n := timeNow()
|
|
||||||
return time.Date(n.Year(), n.Month(), n.Day(), n.Hour(), 0, 0, 0, time.UTC).Unix()
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package interfaces
|
package interfaces
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/elyby/chrly/api/mojang"
|
||||||
"github.com/elyby/chrly/model"
|
"github.com/elyby/chrly/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,3 +16,7 @@ type SkinsRepository interface {
|
|||||||
type CapesRepository interface {
|
type CapesRepository interface {
|
||||||
FindByUsername(username string) (*model.Cape, error)
|
FindByUsername(username string) (*model.Cape, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MojangTexturesQueue interface {
|
||||||
|
GetTexturesForUsername(username string) chan *mojang.SignedTexturesResponse
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user