mirror of
https://github.com/elyby/chrly.git
synced 2024-12-25 14:39:55 +05:30
264 lines
7.4 KiB
Go
264 lines
7.4 KiB
Go
package http
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/gorilla/mux"
|
|
"go.opentelemetry.io/otel/metric"
|
|
"go.uber.org/multierr"
|
|
|
|
"ely.by/chrly/internal/db"
|
|
"ely.by/chrly/internal/mojang"
|
|
"ely.by/chrly/internal/otel"
|
|
)
|
|
|
|
type ProfilesProvider interface {
|
|
FindProfileByUsername(ctx context.Context, username string, allowProxy bool) (*db.Profile, error)
|
|
}
|
|
|
|
func NewSkinsystemApi(
|
|
profilesProvider ProfilesProvider,
|
|
texturesExtraParamName string,
|
|
texturesExtraParamValue string,
|
|
) (*Skinsystem, error) {
|
|
metrics, err := newSkinsystemMetrics(otel.GetMeter())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Skinsystem{
|
|
ProfilesProvider: profilesProvider,
|
|
TexturesExtraParamName: texturesExtraParamName,
|
|
TexturesExtraParamValue: texturesExtraParamValue,
|
|
metrics: metrics,
|
|
}, nil
|
|
}
|
|
|
|
type Skinsystem struct {
|
|
ProfilesProvider
|
|
TexturesExtraParamName string
|
|
TexturesExtraParamValue string
|
|
metrics *skinsystemApiMetrics
|
|
}
|
|
|
|
func (s *Skinsystem) Handler() *mux.Router {
|
|
router := mux.NewRouter().StrictSlash(true)
|
|
|
|
router.HandleFunc("/skins/{username}", s.skinHandler).Methods(http.MethodGet)
|
|
router.HandleFunc("/cloaks/{username}", s.capeHandler).Methods(http.MethodGet)
|
|
// TODO: alias /capes/{username}?
|
|
router.HandleFunc("/textures/{username}", s.texturesHandler).Methods(http.MethodGet)
|
|
router.HandleFunc("/textures/signed/{username}", s.signedTexturesHandler).Methods(http.MethodGet)
|
|
// Legacy
|
|
router.HandleFunc("/skins", s.legacySkinHandler).Methods(http.MethodGet)
|
|
router.HandleFunc("/cloaks", s.legacyCapeHandler).Methods(http.MethodGet)
|
|
|
|
return router
|
|
}
|
|
|
|
func (s *Skinsystem) skinHandler(response http.ResponseWriter, request *http.Request) {
|
|
s.metrics.SkinRequest.Add(request.Context(), 1)
|
|
|
|
s.skinHandlerWithUsername(response, request, mux.Vars(request)["username"])
|
|
}
|
|
|
|
func (s *Skinsystem) legacySkinHandler(response http.ResponseWriter, request *http.Request) {
|
|
s.metrics.LegacySkinRequest.Add(request.Context(), 1)
|
|
|
|
username := request.URL.Query().Get("name")
|
|
if username == "" {
|
|
response.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
s.skinHandlerWithUsername(response, request, username)
|
|
}
|
|
|
|
func (s *Skinsystem) skinHandlerWithUsername(resp http.ResponseWriter, req *http.Request, username string) {
|
|
profile, err := s.ProfilesProvider.FindProfileByUsername(req.Context(), parseUsername(username), true)
|
|
if err != nil {
|
|
apiServerError(resp, req, fmt.Errorf("unable to retrieve a profile: %w", err))
|
|
return
|
|
}
|
|
|
|
if profile == nil || profile.SkinUrl == "" {
|
|
resp.WriteHeader(http.StatusNotFound)
|
|
}
|
|
|
|
http.Redirect(resp, req, profile.SkinUrl, http.StatusMovedPermanently)
|
|
}
|
|
|
|
func (s *Skinsystem) capeHandler(response http.ResponseWriter, request *http.Request) {
|
|
s.metrics.CapeRequest.Add(request.Context(), 1)
|
|
|
|
s.capeHandlerWithUsername(response, request, mux.Vars(request)["username"])
|
|
}
|
|
|
|
func (s *Skinsystem) legacyCapeHandler(response http.ResponseWriter, request *http.Request) {
|
|
s.metrics.CapeRequest.Add(request.Context(), 1)
|
|
|
|
username := request.URL.Query().Get("name")
|
|
if username == "" {
|
|
response.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
s.capeHandlerWithUsername(response, request, username)
|
|
}
|
|
|
|
func (s *Skinsystem) capeHandlerWithUsername(resp http.ResponseWriter, req *http.Request, username string) {
|
|
profile, err := s.ProfilesProvider.FindProfileByUsername(req.Context(), parseUsername(username), true)
|
|
if err != nil {
|
|
apiServerError(resp, req, fmt.Errorf("unable to retrieve a profile: %w", err))
|
|
return
|
|
}
|
|
|
|
if profile == nil || profile.CapeUrl == "" {
|
|
resp.WriteHeader(http.StatusNotFound)
|
|
}
|
|
|
|
http.Redirect(resp, req, profile.CapeUrl, http.StatusMovedPermanently)
|
|
}
|
|
|
|
func (s *Skinsystem) texturesHandler(resp http.ResponseWriter, req *http.Request) {
|
|
s.metrics.TexturesRequest.Add(req.Context(), 1)
|
|
|
|
profile, err := s.ProfilesProvider.FindProfileByUsername(req.Context(), mux.Vars(req)["username"], true)
|
|
if err != nil {
|
|
apiServerError(resp, req, fmt.Errorf("unable to retrieve a profile: %w", err))
|
|
return
|
|
}
|
|
|
|
if profile == nil {
|
|
resp.WriteHeader(http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
if profile.SkinUrl == "" && profile.CapeUrl == "" {
|
|
resp.WriteHeader(http.StatusNoContent)
|
|
return
|
|
}
|
|
|
|
textures := texturesFromProfile(profile)
|
|
|
|
responseData, _ := json.Marshal(textures)
|
|
resp.Header().Set("Content-Type", "application/json")
|
|
_, _ = resp.Write(responseData)
|
|
}
|
|
|
|
func (s *Skinsystem) signedTexturesHandler(resp http.ResponseWriter, req *http.Request) {
|
|
s.metrics.SignedTexturesRequest.Add(req.Context(), 1)
|
|
|
|
profile, err := s.ProfilesProvider.FindProfileByUsername(
|
|
req.Context(),
|
|
mux.Vars(req)["username"],
|
|
getToBool(req.URL.Query().Get("proxy")),
|
|
)
|
|
if err != nil {
|
|
apiServerError(resp, req, fmt.Errorf("unable to retrieve a profile: %w", err))
|
|
return
|
|
}
|
|
|
|
if profile == nil {
|
|
resp.WriteHeader(http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
if profile.MojangTextures == "" {
|
|
resp.WriteHeader(http.StatusNoContent)
|
|
return
|
|
}
|
|
|
|
profileResponse := &mojang.ProfileResponse{
|
|
Id: profile.Uuid,
|
|
Name: profile.Username,
|
|
Props: []*mojang.Property{
|
|
{
|
|
Name: "textures",
|
|
Signature: profile.MojangSignature,
|
|
Value: profile.MojangTextures,
|
|
},
|
|
{
|
|
Name: s.TexturesExtraParamName,
|
|
Value: s.TexturesExtraParamValue,
|
|
},
|
|
},
|
|
}
|
|
|
|
responseJson, _ := json.Marshal(profileResponse)
|
|
resp.Header().Set("Content-Type", "application/json")
|
|
_, _ = resp.Write(responseJson)
|
|
}
|
|
|
|
func parseUsername(username string) string {
|
|
return strings.TrimSuffix(username, ".png")
|
|
}
|
|
|
|
func getToBool(v string) bool {
|
|
return v == "1" || v == "true" || v == "yes"
|
|
}
|
|
|
|
func texturesFromProfile(profile *db.Profile) *mojang.TexturesResponse {
|
|
var skin *mojang.SkinTexturesResponse
|
|
if profile.SkinUrl != "" {
|
|
skin = &mojang.SkinTexturesResponse{
|
|
Url: profile.SkinUrl,
|
|
}
|
|
if profile.SkinModel != "" {
|
|
skin.Metadata = &mojang.SkinTexturesMetadata{
|
|
Model: profile.SkinModel,
|
|
}
|
|
}
|
|
}
|
|
|
|
var cape *mojang.CapeTexturesResponse
|
|
if profile.CapeUrl != "" {
|
|
cape = &mojang.CapeTexturesResponse{
|
|
Url: profile.CapeUrl,
|
|
}
|
|
}
|
|
|
|
return &mojang.TexturesResponse{
|
|
Skin: skin,
|
|
Cape: cape,
|
|
}
|
|
}
|
|
|
|
func newSkinsystemMetrics(meter metric.Meter) (*skinsystemApiMetrics, error) {
|
|
m := &skinsystemApiMetrics{}
|
|
var errors, err error
|
|
|
|
m.SkinRequest, err = meter.Int64Counter("chrly.app.skinsystem.skin.request", metric.WithUnit("{request}"))
|
|
errors = multierr.Append(errors, err)
|
|
|
|
m.LegacySkinRequest, err = meter.Int64Counter("chrly.app.skinsystem.legacy_skin.request", metric.WithUnit("{request}"))
|
|
errors = multierr.Append(errors, err)
|
|
|
|
m.CapeRequest, err = meter.Int64Counter("chrly.app.skinsystem.cape.request", metric.WithUnit("{request}"))
|
|
errors = multierr.Append(errors, err)
|
|
|
|
m.LegacyCapeRequest, err = meter.Int64Counter("chrly.app.skinsystem.legacy_cape.request", metric.WithUnit("{request}"))
|
|
errors = multierr.Append(errors, err)
|
|
|
|
m.TexturesRequest, err = meter.Int64Counter("chrly.app.skinsystem.textures.request", metric.WithUnit("{request}"))
|
|
errors = multierr.Append(errors, err)
|
|
|
|
m.SignedTexturesRequest, err = meter.Int64Counter("chrly.app.skinsystem.signed_textures.request", metric.WithUnit("{request}"))
|
|
errors = multierr.Append(errors, err)
|
|
|
|
return m, errors
|
|
}
|
|
|
|
type skinsystemApiMetrics struct {
|
|
SkinRequest metric.Int64Counter
|
|
LegacySkinRequest metric.Int64Counter
|
|
CapeRequest metric.Int64Counter
|
|
LegacyCapeRequest metric.Int64Counter
|
|
TexturesRequest metric.Int64Counter
|
|
SignedTexturesRequest metric.Int64Counter
|
|
}
|