Major code cleanup
This commit is contained in:
@@ -24,7 +24,6 @@ var (
|
||||
Face: RouteConfig{
|
||||
DefaultOverlay: true,
|
||||
DefaultDownload: false,
|
||||
DefaultFallback: true,
|
||||
DefaultScale: 4,
|
||||
MinScale: 1,
|
||||
MaxScale: 64,
|
||||
@@ -32,7 +31,6 @@ var (
|
||||
Head: RouteConfig{
|
||||
DefaultOverlay: true,
|
||||
DefaultDownload: false,
|
||||
DefaultFallback: true,
|
||||
DefaultScale: 4,
|
||||
MinScale: 1,
|
||||
MaxScale: 64,
|
||||
@@ -40,7 +38,6 @@ var (
|
||||
FullBody: RouteConfig{
|
||||
DefaultOverlay: true,
|
||||
DefaultDownload: false,
|
||||
DefaultFallback: true,
|
||||
DefaultScale: 4,
|
||||
MinScale: 1,
|
||||
MaxScale: 64,
|
||||
@@ -48,7 +45,6 @@ var (
|
||||
FrontBody: RouteConfig{
|
||||
DefaultOverlay: true,
|
||||
DefaultDownload: false,
|
||||
DefaultFallback: true,
|
||||
DefaultScale: 4,
|
||||
MinScale: 1,
|
||||
MaxScale: 64,
|
||||
@@ -56,7 +52,6 @@ var (
|
||||
BackBody: RouteConfig{
|
||||
DefaultOverlay: true,
|
||||
DefaultDownload: false,
|
||||
DefaultFallback: true,
|
||||
DefaultScale: 4,
|
||||
MinScale: 1,
|
||||
MaxScale: 64,
|
||||
@@ -64,7 +59,6 @@ var (
|
||||
LeftBody: RouteConfig{
|
||||
DefaultOverlay: true,
|
||||
DefaultDownload: false,
|
||||
DefaultFallback: true,
|
||||
DefaultScale: 4,
|
||||
MinScale: 1,
|
||||
MaxScale: 64,
|
||||
@@ -72,20 +66,17 @@ var (
|
||||
RightBody: RouteConfig{
|
||||
DefaultOverlay: true,
|
||||
DefaultDownload: false,
|
||||
DefaultFallback: true,
|
||||
DefaultScale: 4,
|
||||
MinScale: 1,
|
||||
MaxScale: 64,
|
||||
},
|
||||
RawSkin: RouteConfig{
|
||||
DefaultDownload: false,
|
||||
DefaultFallback: true,
|
||||
},
|
||||
},
|
||||
Cache: CacheConfig{
|
||||
UUIDCacheDuration: time.Hour * 168,
|
||||
SkinCacheDuration: time.Hour * 12,
|
||||
RenderCacheDuration: time.Hour * 12,
|
||||
SkinCacheDuration: PointerOf(time.Hour * 12),
|
||||
RenderCacheDuration: PointerOf(time.Hour * 12),
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -116,7 +107,6 @@ type RoutesConfig struct {
|
||||
type RouteConfig struct {
|
||||
DefaultScale int `yaml:"default_scale"`
|
||||
DefaultOverlay bool `yaml:"default_overlay"`
|
||||
DefaultFallback bool `yaml:"default_fallback"`
|
||||
DefaultDownload bool `yaml:"default_download"`
|
||||
MinScale int `yaml:"min_scale"`
|
||||
MaxScale int `yaml:"max_scale"`
|
||||
@@ -133,9 +123,8 @@ type RedisConfig struct {
|
||||
|
||||
// CacheConfig is the configuration data used to set TTL values for Redis keys.
|
||||
type CacheConfig struct {
|
||||
UUIDCacheDuration time.Duration `yaml:"uuid_cache_duration"`
|
||||
SkinCacheDuration time.Duration `yaml:"skin_cache_duration"`
|
||||
RenderCacheDuration time.Duration `yaml:"render_cache_duration"`
|
||||
SkinCacheDuration *time.Duration `yaml:"skin_cache_duration"`
|
||||
RenderCacheDuration *time.Duration `yaml:"render_cache_duration"`
|
||||
}
|
||||
|
||||
// ReadFile reads the configuration from the file and parses it as YAML.
|
||||
|
||||
26
src/main.go
26
src/main.go
@@ -1,11 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"strconv"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||
@@ -61,13 +61,23 @@ func init() {
|
||||
}
|
||||
|
||||
func main() {
|
||||
if v := os.Getenv("PROFILE"); len(v) > 0 {
|
||||
port, err := strconv.ParseUint(v, 10, 16)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
go Profile(uint16(port))
|
||||
|
||||
log.Printf("Profiler is listening on :%d\n", port)
|
||||
}
|
||||
|
||||
defer r.Close()
|
||||
|
||||
go ListenAndServe(conf.Host, conf.Port+instanceID)
|
||||
log.Printf("Listening on %s:%d\n", conf.Host, conf.Port+instanceID)
|
||||
|
||||
defer app.Shutdown()
|
||||
|
||||
s := make(chan os.Signal, 1)
|
||||
signal.Notify(s, os.Interrupt, syscall.SIGTERM)
|
||||
<-s
|
||||
if err := app.Listen(fmt.Sprintf("%s:%d", conf.Host, conf.Port+instanceID)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
16
src/profiler.go
Normal file
16
src/profiler.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
)
|
||||
|
||||
// Profile is a Goroutine for automatically profiling the program for optimization and debug reasons.
|
||||
func Profile(port uint16) {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", pprof.Profile)
|
||||
|
||||
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), mux))
|
||||
}
|
||||
561
src/routes.go
561
src/routes.go
@@ -7,7 +7,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/mineatar-io/skin-render"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -64,347 +63,6 @@ func ListHandler(ctx *fiber.Ctx) error {
|
||||
return ctx.JSON(result)
|
||||
}
|
||||
|
||||
// FullBodyHandler is the API handler used for the `/body/full/:uuid` route.
|
||||
func FullBodyHandler(ctx *fiber.Ctx) error {
|
||||
opts := ParseQueryParams(ctx, conf.Routes.FullBody)
|
||||
|
||||
uuid, ok := ParseUUID(ExtractUUID(ctx))
|
||||
|
||||
if !ok {
|
||||
return SendUsernameDeprecation(ctx)
|
||||
}
|
||||
|
||||
cacheKey := fmt.Sprintf("result:fullbody-%d-%t-%s", opts.Scale, opts.Overlay, uuid)
|
||||
|
||||
data, err := r.GetBytes(cacheKey)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Set("X-Cache-Hit", strconv.FormatBool(data != nil))
|
||||
|
||||
if data == nil {
|
||||
rawSkin, slim, err := GetPlayerSkin(uuid)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if data, err = EncodePNG(skin.RenderBody(rawSkin, skin.Options{
|
||||
Overlay: opts.Overlay,
|
||||
Slim: slim,
|
||||
Scale: opts.Scale,
|
||||
})); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = r.Set(cacheKey, data, conf.Cache.RenderCacheDuration); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Download {
|
||||
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, uuid))
|
||||
}
|
||||
|
||||
return ctx.Type("png").Send(data)
|
||||
}
|
||||
|
||||
// FrontBodyHandler is the API handler used for the `/body/front/:uuid` route.
|
||||
func FrontBodyHandler(ctx *fiber.Ctx) error {
|
||||
opts := ParseQueryParams(ctx, conf.Routes.FrontBody)
|
||||
|
||||
uuid, ok := ParseUUID(ExtractUUID(ctx))
|
||||
|
||||
if !ok {
|
||||
return SendUsernameDeprecation(ctx)
|
||||
}
|
||||
|
||||
cacheKey := fmt.Sprintf("result:frontbody-%d-%t-%s", opts.Scale, opts.Overlay, uuid)
|
||||
|
||||
data, err := r.GetBytes(cacheKey)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Set("X-Cache-Hit", strconv.FormatBool(data != nil))
|
||||
|
||||
if data == nil {
|
||||
rawSkin, slim, err := GetPlayerSkin(uuid)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err = EncodePNG(skin.RenderFrontBody(rawSkin, skin.Options{
|
||||
Overlay: opts.Overlay,
|
||||
Slim: slim,
|
||||
Scale: opts.Scale,
|
||||
}))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = r.Set(cacheKey, data, conf.Cache.RenderCacheDuration); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Download {
|
||||
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, uuid))
|
||||
}
|
||||
|
||||
return ctx.Type("png").Send(data)
|
||||
}
|
||||
|
||||
// BackBodyHandler is the API handler used for the `/body/back/:uuid` route.
|
||||
func BackBodyHandler(ctx *fiber.Ctx) error {
|
||||
opts := ParseQueryParams(ctx, conf.Routes.BackBody)
|
||||
|
||||
uuid, ok := ParseUUID(ExtractUUID(ctx))
|
||||
|
||||
if !ok {
|
||||
return SendUsernameDeprecation(ctx)
|
||||
}
|
||||
|
||||
cacheKey := fmt.Sprintf("result:backbody-%d-%t-%s", opts.Scale, opts.Overlay, uuid)
|
||||
|
||||
data, err := r.GetBytes(cacheKey)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Set("X-Cache-Hit", strconv.FormatBool(data != nil))
|
||||
|
||||
if data == nil {
|
||||
rawSkin, slim, err := GetPlayerSkin(uuid)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err = EncodePNG(skin.RenderBackBody(rawSkin, skin.Options{
|
||||
Overlay: opts.Overlay,
|
||||
Slim: slim,
|
||||
Scale: opts.Scale,
|
||||
}))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = r.Set(cacheKey, data, conf.Cache.RenderCacheDuration); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Download {
|
||||
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, uuid))
|
||||
}
|
||||
|
||||
return ctx.Type("png").Send(data)
|
||||
}
|
||||
|
||||
// LeftBodyHandler is the API handler used for the `/body/left/:uuid` route.
|
||||
func LeftBodyHandler(ctx *fiber.Ctx) error {
|
||||
opts := ParseQueryParams(ctx, conf.Routes.LeftBody)
|
||||
|
||||
uuid, ok := ParseUUID(ExtractUUID(ctx))
|
||||
|
||||
if !ok {
|
||||
return SendUsernameDeprecation(ctx)
|
||||
}
|
||||
|
||||
cacheKey := fmt.Sprintf("result:leftbody-%d-%t-%s", opts.Scale, opts.Overlay, uuid)
|
||||
|
||||
data, err := r.GetBytes(cacheKey)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Set("X-Cache-Hit", strconv.FormatBool(data != nil))
|
||||
|
||||
if data == nil {
|
||||
rawSkin, slim, err := GetPlayerSkin(uuid)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err = EncodePNG(skin.RenderLeftBody(rawSkin, skin.Options{
|
||||
Overlay: opts.Overlay,
|
||||
Slim: slim,
|
||||
Scale: opts.Scale,
|
||||
}))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = r.Set(cacheKey, data, conf.Cache.RenderCacheDuration); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Download {
|
||||
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, uuid))
|
||||
}
|
||||
|
||||
return ctx.Type("png").Send(data)
|
||||
}
|
||||
|
||||
// RightBodyHandler is the API handler used for the `/body/right/:uuid` route.
|
||||
func RightBodyHandler(ctx *fiber.Ctx) error {
|
||||
opts := ParseQueryParams(ctx, conf.Routes.RightBody)
|
||||
|
||||
uuid, ok := ParseUUID(ExtractUUID(ctx))
|
||||
|
||||
if !ok {
|
||||
return SendUsernameDeprecation(ctx)
|
||||
}
|
||||
|
||||
cacheKey := fmt.Sprintf("result:rightbody-%d-%t-%s", opts.Scale, opts.Overlay, uuid)
|
||||
|
||||
data, err := r.GetBytes(cacheKey)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Set("X-Cache-Hit", strconv.FormatBool(data != nil))
|
||||
|
||||
if data == nil {
|
||||
rawSkin, slim, err := GetPlayerSkin(uuid)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err = EncodePNG(skin.RenderRightBody(rawSkin, skin.Options{
|
||||
Overlay: opts.Overlay,
|
||||
Slim: slim,
|
||||
Scale: opts.Scale,
|
||||
}))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = r.Set(cacheKey, data, conf.Cache.RenderCacheDuration); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Download {
|
||||
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, uuid))
|
||||
}
|
||||
|
||||
return ctx.Type("png").Send(data)
|
||||
}
|
||||
|
||||
// FaceHandler is the API handler used for the `/face/:uuid` route.
|
||||
func FaceHandler(ctx *fiber.Ctx) error {
|
||||
opts := ParseQueryParams(ctx, conf.Routes.Face)
|
||||
|
||||
uuid, ok := ParseUUID(ExtractUUID(ctx))
|
||||
|
||||
if !ok {
|
||||
return SendUsernameDeprecation(ctx)
|
||||
}
|
||||
|
||||
cacheKey := fmt.Sprintf("result:face-%d-%t-%s", opts.Scale, opts.Overlay, uuid)
|
||||
|
||||
data, err := r.GetBytes(cacheKey)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Set("X-Cache-Hit", strconv.FormatBool(data != nil))
|
||||
|
||||
if data == nil {
|
||||
rawSkin, slim, err := GetPlayerSkin(uuid)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err = EncodePNG(skin.RenderFace(rawSkin, skin.Options{
|
||||
Overlay: opts.Overlay,
|
||||
Slim: slim,
|
||||
Scale: opts.Scale,
|
||||
}))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = r.Set(cacheKey, data, conf.Cache.RenderCacheDuration); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Download {
|
||||
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, uuid))
|
||||
}
|
||||
|
||||
return ctx.Type("png").Send(data)
|
||||
}
|
||||
|
||||
// HeadHandler is the API handler used for the `/head/:uuid` route.
|
||||
func HeadHandler(ctx *fiber.Ctx) error {
|
||||
opts := ParseQueryParams(ctx, conf.Routes.Head)
|
||||
|
||||
uuid, ok := ParseUUID(ExtractUUID(ctx))
|
||||
|
||||
if !ok {
|
||||
return SendUsernameDeprecation(ctx)
|
||||
}
|
||||
|
||||
cacheKey := fmt.Sprintf("result:head-%d-%t-%s", opts.Scale, opts.Overlay, uuid)
|
||||
|
||||
data, err := r.GetBytes(cacheKey)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Set("X-Cache-Hit", strconv.FormatBool(data != nil))
|
||||
|
||||
if data == nil {
|
||||
rawSkin, slim, err := GetPlayerSkin(uuid)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err = EncodePNG(skin.RenderHead(rawSkin, skin.Options{
|
||||
Overlay: opts.Overlay,
|
||||
Slim: slim,
|
||||
Scale: opts.Scale,
|
||||
}))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = r.Set(cacheKey, data, conf.Cache.RenderCacheDuration); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Download {
|
||||
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, uuid))
|
||||
}
|
||||
|
||||
return ctx.Type("png").Send(data)
|
||||
}
|
||||
|
||||
// SkinHandler is the API handler used for the `/skin/:uuid` route.
|
||||
func SkinHandler(ctx *fiber.Ctx) error {
|
||||
opts := ParseQueryParams(ctx, conf.Routes.RawSkin)
|
||||
@@ -412,7 +70,7 @@ func SkinHandler(ctx *fiber.Ctx) error {
|
||||
uuid, ok := ParseUUID(ctx.Params("uuid"))
|
||||
|
||||
if !ok {
|
||||
return SendUsernameDeprecation(ctx)
|
||||
return ctx.Status(http.StatusBadRequest).SendString("Invalid UUID")
|
||||
}
|
||||
|
||||
rawSkin, _, err := GetPlayerSkin(uuid)
|
||||
@@ -434,6 +92,223 @@ func SkinHandler(ctx *fiber.Ctx) error {
|
||||
return ctx.Type("png").Send(data)
|
||||
}
|
||||
|
||||
// FaceHandler is the API handler used for the `/face/:uuid` route.
|
||||
func FaceHandler(ctx *fiber.Ctx) error {
|
||||
opts := ParseQueryParams(ctx, conf.Routes.Face)
|
||||
|
||||
uuid, ok := ParseUUID(ExtractUUID(ctx))
|
||||
|
||||
if !ok {
|
||||
return ctx.Status(http.StatusBadRequest).SendString("Invalid UUID")
|
||||
}
|
||||
|
||||
rawSkin, isSlim, err := GetPlayerSkin(uuid)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result, cache, err := Render(RenderTypeFace, uuid, rawSkin, isSlim, opts)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Set("X-Cache-Hit", strconv.FormatBool(cache))
|
||||
|
||||
if opts.Download {
|
||||
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, uuid))
|
||||
}
|
||||
|
||||
return ctx.Type("png").Send(result)
|
||||
}
|
||||
|
||||
// HeadHandler is the API handler used for the `/head/:uuid` route.
|
||||
func HeadHandler(ctx *fiber.Ctx) error {
|
||||
opts := ParseQueryParams(ctx, conf.Routes.Head)
|
||||
|
||||
uuid, ok := ParseUUID(ExtractUUID(ctx))
|
||||
|
||||
if !ok {
|
||||
return ctx.Status(http.StatusBadRequest).SendString("Invalid UUID")
|
||||
}
|
||||
|
||||
rawSkin, isSlim, err := GetPlayerSkin(uuid)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result, cache, err := Render(RenderTypeHead, uuid, rawSkin, isSlim, opts)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Set("X-Cache-Hit", strconv.FormatBool(cache))
|
||||
|
||||
if opts.Download {
|
||||
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, uuid))
|
||||
}
|
||||
|
||||
return ctx.Type("png").Send(result)
|
||||
}
|
||||
|
||||
// FullBodyHandler is the API handler used for the `/body/full/:uuid` route.
|
||||
func FullBodyHandler(ctx *fiber.Ctx) error {
|
||||
opts := ParseQueryParams(ctx, conf.Routes.FullBody)
|
||||
|
||||
uuid, ok := ParseUUID(ExtractUUID(ctx))
|
||||
|
||||
if !ok {
|
||||
return ctx.Status(http.StatusBadRequest).SendString("Invalid UUID")
|
||||
}
|
||||
|
||||
rawSkin, isSlim, err := GetPlayerSkin(uuid)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result, cache, err := Render(RenderTypeFullBody, uuid, rawSkin, isSlim, opts)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Set("X-Cache-Hit", strconv.FormatBool(cache))
|
||||
|
||||
if opts.Download {
|
||||
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, uuid))
|
||||
}
|
||||
|
||||
return ctx.Type("png").Send(result)
|
||||
}
|
||||
|
||||
// FrontBodyHandler is the API handler used for the `/body/front/:uuid` route.
|
||||
func FrontBodyHandler(ctx *fiber.Ctx) error {
|
||||
opts := ParseQueryParams(ctx, conf.Routes.FrontBody)
|
||||
|
||||
uuid, ok := ParseUUID(ExtractUUID(ctx))
|
||||
|
||||
if !ok {
|
||||
return ctx.Status(http.StatusBadRequest).SendString("Invalid UUID")
|
||||
}
|
||||
|
||||
rawSkin, isSlim, err := GetPlayerSkin(uuid)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result, cache, err := Render(RenderTypeFrontBody, uuid, rawSkin, isSlim, opts)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Set("X-Cache-Hit", strconv.FormatBool(cache))
|
||||
|
||||
if opts.Download {
|
||||
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, uuid))
|
||||
}
|
||||
|
||||
return ctx.Type("png").Send(result)
|
||||
}
|
||||
|
||||
// BackBodyHandler is the API handler used for the `/body/back/:uuid` route.
|
||||
func BackBodyHandler(ctx *fiber.Ctx) error {
|
||||
opts := ParseQueryParams(ctx, conf.Routes.BackBody)
|
||||
|
||||
uuid, ok := ParseUUID(ExtractUUID(ctx))
|
||||
|
||||
if !ok {
|
||||
return ctx.Status(http.StatusBadRequest).SendString("Invalid UUID")
|
||||
}
|
||||
|
||||
rawSkin, isSlim, err := GetPlayerSkin(uuid)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result, cache, err := Render(RenderTypeBackBody, uuid, rawSkin, isSlim, opts)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Set("X-Cache-Hit", strconv.FormatBool(cache))
|
||||
|
||||
if opts.Download {
|
||||
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, uuid))
|
||||
}
|
||||
|
||||
return ctx.Type("png").Send(result)
|
||||
}
|
||||
|
||||
// LeftBodyHandler is the API handler used for the `/body/left/:uuid` route.
|
||||
func LeftBodyHandler(ctx *fiber.Ctx) error {
|
||||
opts := ParseQueryParams(ctx, conf.Routes.LeftBody)
|
||||
|
||||
uuid, ok := ParseUUID(ExtractUUID(ctx))
|
||||
|
||||
if !ok {
|
||||
return ctx.Status(http.StatusBadRequest).SendString("Invalid UUID")
|
||||
}
|
||||
|
||||
rawSkin, isSlim, err := GetPlayerSkin(uuid)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result, cache, err := Render(RenderTypeLeftBody, uuid, rawSkin, isSlim, opts)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Set("X-Cache-Hit", strconv.FormatBool(cache))
|
||||
|
||||
if opts.Download {
|
||||
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, uuid))
|
||||
}
|
||||
|
||||
return ctx.Type("png").Send(result)
|
||||
}
|
||||
|
||||
// RightBodyHandler is the API handler used for the `/body/right/:uuid` route.
|
||||
func RightBodyHandler(ctx *fiber.Ctx) error {
|
||||
opts := ParseQueryParams(ctx, conf.Routes.RightBody)
|
||||
|
||||
uuid, ok := ParseUUID(ExtractUUID(ctx))
|
||||
|
||||
if !ok {
|
||||
return ctx.Status(http.StatusBadRequest).SendString("Invalid UUID")
|
||||
}
|
||||
|
||||
rawSkin, isSlim, err := GetPlayerSkin(uuid)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result, cache, err := Render(RenderTypeRightBody, uuid, rawSkin, isSlim, opts)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Set("X-Cache-Hit", strconv.FormatBool(cache))
|
||||
|
||||
if opts.Download {
|
||||
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, uuid))
|
||||
}
|
||||
|
||||
return ctx.Type("png").Send(result)
|
||||
}
|
||||
|
||||
// NotFoundHandler is the API handler used for any requests that do not match an existing route.
|
||||
func NotFoundHandler(ctx *fiber.Ctx) error {
|
||||
return ctx.SendStatus(http.StatusNotFound)
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
func ListenAndServe(host string, port uint16) {
|
||||
log.Printf("Listening on %s:%d\n", host, port)
|
||||
|
||||
if err := app.Listen(fmt.Sprintf("%s:%d", host, port)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
194
src/util.go
194
src/util.go
@@ -20,6 +20,14 @@ import (
|
||||
var (
|
||||
//go:embed favicon.ico
|
||||
favicon []byte
|
||||
|
||||
RenderTypeFullBody = "fullbody"
|
||||
RenderTypeFrontBody = "frontbody"
|
||||
RenderTypeBackBody = "backbody"
|
||||
RenderTypeLeftBody = "leftbody"
|
||||
RenderTypeRightBody = "rightbody"
|
||||
RenderTypeFace = "face"
|
||||
RenderTypeHead = "head"
|
||||
)
|
||||
|
||||
// QueryParams is used by most all API routes as options for how the image should be rendered, or how errors should be handled.
|
||||
@@ -27,6 +35,119 @@ type QueryParams struct {
|
||||
Scale int `query:"scale"`
|
||||
Download bool `query:"download"`
|
||||
Overlay bool `query:"overlay"`
|
||||
Fallback bool `query:"fallback"`
|
||||
}
|
||||
|
||||
// PointerOf returns the value of the first argument as a pointer.
|
||||
func PointerOf[T any](v T) *T {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Render will render the image using the specified details and return the result.
|
||||
func Render(renderType, uuid string, rawSkin *image.NRGBA, isSlim bool, opts *QueryParams) ([]byte, bool, error) {
|
||||
cache, err := GetCachedRenderResult(renderType, uuid, opts)
|
||||
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if cache != nil {
|
||||
if conf.Environment == "development" {
|
||||
log.Printf("Retrieved render from cache (type=%s, uuid=%s, slim=%v, scale=%d)\n", renderType, uuid, isSlim, opts.Scale)
|
||||
}
|
||||
|
||||
return cache, true, nil
|
||||
}
|
||||
|
||||
var (
|
||||
result *image.NRGBA
|
||||
renderOpts skin.Options = skin.Options{
|
||||
Overlay: opts.Overlay,
|
||||
Slim: isSlim,
|
||||
Scale: opts.Scale,
|
||||
}
|
||||
)
|
||||
|
||||
switch renderType {
|
||||
case RenderTypeFullBody:
|
||||
{
|
||||
result = skin.RenderBody(rawSkin, renderOpts)
|
||||
|
||||
break
|
||||
}
|
||||
case RenderTypeFrontBody:
|
||||
{
|
||||
result = skin.RenderFrontBody(rawSkin, renderOpts)
|
||||
|
||||
break
|
||||
}
|
||||
case RenderTypeBackBody:
|
||||
{
|
||||
result = skin.RenderBackBody(rawSkin, renderOpts)
|
||||
|
||||
break
|
||||
}
|
||||
case RenderTypeLeftBody:
|
||||
{
|
||||
result = skin.RenderLeftBody(rawSkin, renderOpts)
|
||||
|
||||
break
|
||||
}
|
||||
case RenderTypeRightBody:
|
||||
{
|
||||
result = skin.RenderRightBody(rawSkin, renderOpts)
|
||||
|
||||
break
|
||||
}
|
||||
case RenderTypeHead:
|
||||
{
|
||||
result = skin.RenderHead(rawSkin, renderOpts)
|
||||
|
||||
break
|
||||
}
|
||||
case RenderTypeFace:
|
||||
{
|
||||
result = skin.RenderFace(rawSkin, renderOpts)
|
||||
|
||||
break
|
||||
}
|
||||
default:
|
||||
panic(fmt.Errorf("unknown render type: %s", renderType))
|
||||
}
|
||||
|
||||
data, err := EncodePNG(result)
|
||||
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if err = SetCachedRenderResult(renderType, uuid, opts, data); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if conf.Environment == "development" {
|
||||
log.Printf("Rendered image (type=%s, uuid=%s, slim=%v, scale=%d)\n", renderType, uuid, isSlim, opts.Scale)
|
||||
}
|
||||
|
||||
return data, false, nil
|
||||
}
|
||||
|
||||
// GetCachedRenderResult returns the render result from Redis cache, or nil if it does not exist or cache is disabled.
|
||||
func GetCachedRenderResult(renderType, uuid string, opts *QueryParams) ([]byte, error) {
|
||||
if conf.Cache.RenderCacheDuration == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return r.GetBytes(fmt.Sprintf("result:%s-%d-%t-%s", renderType, opts.Scale, opts.Overlay, uuid))
|
||||
}
|
||||
|
||||
// SetCachedRenderResult puts the render result into cache, or does nothing is cache is disabled.
|
||||
func SetCachedRenderResult(renderType, uuid string, opts *QueryParams, data []byte) error {
|
||||
if conf.Cache.RenderCacheDuration == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return r.Set(fmt.Sprintf("result:%s-%d-%t-%s", renderType, opts.Scale, opts.Overlay, uuid), data, *conf.Cache.RenderCacheDuration)
|
||||
}
|
||||
|
||||
// FormatUUID returns the UUID string without any dashes.
|
||||
@@ -74,32 +195,38 @@ func FetchImage(url string) (*image.NRGBA, error) {
|
||||
|
||||
// GetPlayerSkin fetches the skin of the Minecraft player by the UUID.
|
||||
func GetPlayerSkin(uuid string) (*image.NRGBA, bool, error) {
|
||||
cache, ok, err := r.GetNRGBA(fmt.Sprintf("skin:%s", uuid))
|
||||
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if ok {
|
||||
slim, err := r.Exists(fmt.Sprintf("slim:%s", uuid))
|
||||
if conf.Cache.SkinCacheDuration != nil {
|
||||
cache, ok, err := r.GetNRGBA(fmt.Sprintf("skin:%s", uuid))
|
||||
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
return cache, slim, nil
|
||||
if ok {
|
||||
slim, err := r.Exists(fmt.Sprintf("slim:%s", uuid))
|
||||
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if conf.Environment == "development" {
|
||||
log.Printf("Retrieved player skin from cache (uuid=%s, slim=%v)\n", uuid, slim)
|
||||
}
|
||||
|
||||
return cache, slim, nil
|
||||
}
|
||||
}
|
||||
|
||||
isSlimFromUUID := skin.IsSlimFromUUID(uuid)
|
||||
|
||||
textures, err := GetProfileTextures(uuid)
|
||||
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
return skin.GetDefaultSkin(isSlimFromUUID), true, nil
|
||||
}
|
||||
|
||||
if textures == nil {
|
||||
slim := skin.IsSlimFromUUID(uuid)
|
||||
|
||||
return skin.GetDefaultSkin(slim), slim, nil
|
||||
return skin.GetDefaultSkin(isSlimFromUUID), isSlimFromUUID, nil
|
||||
}
|
||||
|
||||
if err = r.Set(fmt.Sprintf("unique:%s", textures.UUID), "0", 0); err != nil {
|
||||
@@ -117,9 +244,7 @@ func GetPlayerSkin(uuid string) (*image.NRGBA, bool, error) {
|
||||
}
|
||||
|
||||
if len(value) < 1 {
|
||||
slim := skin.IsSlimFromUUID(uuid)
|
||||
|
||||
return skin.GetDefaultSkin(slim), slim, nil
|
||||
return skin.GetDefaultSkin(isSlimFromUUID), isSlimFromUUID, nil
|
||||
}
|
||||
|
||||
texturesResult, err := GetDecodedTexturesValue(value)
|
||||
@@ -129,9 +254,7 @@ func GetPlayerSkin(uuid string) (*image.NRGBA, bool, error) {
|
||||
}
|
||||
|
||||
if len(texturesResult.Textures.Skin.URL) < 1 {
|
||||
slim := skin.IsSlimFromUUID(uuid)
|
||||
|
||||
return skin.GetDefaultSkin(slim), slim, nil
|
||||
return skin.GetDefaultSkin(isSlimFromUUID), isSlimFromUUID, nil
|
||||
}
|
||||
|
||||
slim := texturesResult.Textures.Skin.Metadata.Model == "slim"
|
||||
@@ -148,18 +271,24 @@ func GetPlayerSkin(uuid string) (*image.NRGBA, bool, error) {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if err = r.Set(fmt.Sprintf("skin:%s", uuid), encodedSkin, conf.Cache.SkinCacheDuration); err != nil {
|
||||
return nil, false, err
|
||||
if conf.Cache.SkinCacheDuration != nil {
|
||||
if err = r.Set(fmt.Sprintf("skin:%s", uuid), encodedSkin, *conf.Cache.SkinCacheDuration); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if slim {
|
||||
if err = r.Set(fmt.Sprintf("slim:%s", uuid), "true", *conf.Cache.SkinCacheDuration); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
} else {
|
||||
if err = r.Delete(fmt.Sprintf("slim:%s", uuid)); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if slim {
|
||||
if err = r.Set(fmt.Sprintf("slim:%s", uuid), "true", conf.Cache.SkinCacheDuration); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
} else {
|
||||
if err = r.Delete(fmt.Sprintf("slim:%s", uuid)); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if conf.Environment == "development" {
|
||||
log.Printf("Fetched player skin from Mojang (uuid=%s, slim=%v)\n", uuid, slim)
|
||||
}
|
||||
|
||||
return skin, slim, nil
|
||||
@@ -167,7 +296,7 @@ func GetPlayerSkin(uuid string) (*image.NRGBA, bool, error) {
|
||||
|
||||
// Clamp clamps the input value between the minimum and maximum values.
|
||||
// This method is preferred over `math.Min()` and `math.Max()` to prevent any type coercion between floats and integers.
|
||||
func Clamp(value, min, max int) int {
|
||||
func Clamp[T int | uint | int8 | uint8 | int16 | uint16 | int32 | uint32 | int64 | uint64](value, min, max T) T {
|
||||
if value > max {
|
||||
return max
|
||||
}
|
||||
@@ -236,8 +365,3 @@ func GetInstanceID() (uint16, error) {
|
||||
func ExtractUUID(ctx *fiber.Ctx) string {
|
||||
return strings.Split(ctx.Params("uuid"), ".")[0]
|
||||
}
|
||||
|
||||
// SendUsernameDeprecation sends a deprecation warning about usernames.
|
||||
func SendUsernameDeprecation(ctx *fiber.Ctx) error {
|
||||
return ctx.Status(http.StatusBadRequest).SendString("Deprecated: Username support has been deprecated since 23 April 2023, please use a valid UUID instead.")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user