From 5cc9492a00ba78a7b3e1adb0b168ff0a80d41a33 Mon Sep 17 00:00:00 2001 From: Jacob Gunther Date: Sun, 31 Jul 2022 22:44:27 -0500 Subject: [PATCH] Rewrite v2 --- config.example.yml | 9 +- go.mod | 29 +- go.sum | 51 ++-- src/config.go | 24 +- src/{default-icon.png => icon.png} | Bin src/main.go | 99 ++----- src/redis.go | 108 ++------ src/routes.go | 300 +++------------------ src/status.go | 414 ++++++++++++++++++----------- src/util.go | 64 +---- 10 files changed, 388 insertions(+), 710 deletions(-) rename src/{default-icon.png => icon.png} (100%) diff --git a/config.example.yml b/config.example.yml index 87bab60..c5dd470 100644 --- a/config.example.yml +++ b/config.example.yml @@ -1,7 +1,8 @@ -environment: development host: 127.0.0.1 port: 3001 redis: redis://127.0.0.1:6379/0 -cache_enable: false -status_cache_ttl: 5m -favicon_cache_ttl: 24h \ No newline at end of file +cache: + enable: false + java_cache_duration: 5m + bedrock_cache_duration: 5m + icon_cache_duration: 15m \ No newline at end of file diff --git a/go.mod b/go.mod index ec469f6..95cf0f8 100644 --- a/go.mod +++ b/go.mod @@ -3,31 +3,22 @@ module main go 1.18 require ( - github.com/PassTheMayo/mcstatus/v4 v4.0.0 - github.com/go-yaml/yaml v2.1.0+incompatible -) - -require ( - github.com/buaazp/fasthttprouter v0.1.1 - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + github.com/PassTheMayo/mcstatus/v4 v4.0.2 + github.com/go-redis/redis/v8 v8.11.5 + github.com/gofiber/fiber/v2 v2.35.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/andybalholm/brotli v1.0.4 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/go-redis/redis/v8 v8.11.5 - github.com/klauspost/compress v1.15.6 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasthttp v1.37.0 -) - -require ( github.com/fatih/color v1.13.0 // indirect - github.com/kr/pretty v0.3.0 // indirect - github.com/mattn/go-colorable v0.1.9 // indirect + github.com/klauspost/compress v1.15.9 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect - github.com/rogpeppe/go-internal v1.8.1 // indirect - golang.org/x/net v0.0.0-20220622184535-263ec571b305 // indirect - golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.38.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + golang.org/x/sys v0.0.0-20220730100132-1609e554cd39 // indirect ) diff --git a/go.sum b/go.sum index 85c4088..287e122 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,9 @@ -github.com/PassTheMayo/mcstatus/v4 v4.0.0 h1:SgIAwpLkH2Vt7KDZZ7tAsAufyI9XRy9YdgMUJVAw1o0= -github.com/PassTheMayo/mcstatus/v4 v4.0.0/go.mod h1:62Iagw0yGRyufsvEqPI4T8fJpIf6UUh14junH8ZR2LY= +github.com/PassTheMayo/mcstatus/v4 v4.0.2 h1:Tn+ULG+1e9eXjJf/WUrqF1JPAs0dDRjTyELF/ieQpiE= +github.com/PassTheMayo/mcstatus/v4 v4.0.2/go.mod h1:62Iagw0yGRyufsvEqPI4T8fJpIf6UUh14junH8ZR2LY= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/buaazp/fasthttprouter v0.1.1 h1:4oAnN0C3xZjylvZJdP35cxfclyn4TYkW6Y+DSvS+h8Q= -github.com/buaazp/fasthttprouter v0.1.1/go.mod h1:h/Ap5oRVLeItGKTVBb+heQPks+HdIUtGmI4H5WCYijM= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= @@ -14,60 +11,50 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= -github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o= -github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= +github.com/gofiber/fiber/v2 v2.35.0 h1:ct+jKw8Qb24WEIZx3VV3zz9VXyBZL7mcEjNaqj3g0h0= +github.com/gofiber/fiber/v2 v2.35.0/go.mod h1:tgCr+lierLwLoVHHO/jn3Niannv34WRkQETU8wiL9fQ= github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.15.6 h1:6D9PcO8QWu0JyaQ2zUMmu16T1T+zjjEpP91guRsvDfY= -github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= +github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= -github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.37.0 h1:7WHCyI7EAkQMVmrfBhWTCOaeROb1aCBiTopx63LkMbE= -github.com/valyala/fasthttp v1.37.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= +github.com/valyala/fasthttp v1.38.0 h1:yTjSSNjuDi2PPvXY2836bIwLmiTS2T4T9p1coQshpco= +github.com/valyala/fasthttp v1.38.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220622184535-263ec571b305 h1:dAgbJ2SP4jD6XYfMNLVj0BF21jo2PjChrtGaAvF5M3I= -golang.org/x/net v0.0.0-20220622184535-263ec571b305/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664 h1:wEZYwx+kK+KlZ0hpvP2Ls1Xr4+RWnlzGFwPP0aiDjIU= -golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220730100132-1609e554cd39 h1:aNCnH+Fiqs7ZDTFH6oEFjIfbX2HvgQXJ6uQuUbTobjk= +golang.org/x/sys v0.0.0-20220730100132-1609e554cd39/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/config.go b/src/config.go index 143ae6b..ec1205a 100644 --- a/src/config.go +++ b/src/config.go @@ -4,21 +4,23 @@ import ( "io/ioutil" "time" - "github.com/go-yaml/yaml" + "gopkg.in/yaml.v3" ) -type Configuration struct { - Environment string `yaml:"environment"` - Host string `yaml:"host"` - Port uint16 `yaml:"port"` - Redis string `yaml:"redis"` - CacheEnable bool `yaml:"cache_enable"` - StatusCacheTTL time.Duration `yaml:"status_cache_ttl"` - FaviconCacheTTL time.Duration `yaml:"favicon_cache_ttl"` +type Config struct { + Host string `yaml:"host"` + Port uint16 `yaml:"port"` + Redis string `yaml:"redis"` + Cache struct { + Enable bool `yaml:"enable"` + JavaCacheDuration time.Duration `yaml:"java_cache_duration"` + BedrockCacheDuration time.Duration `yaml:"bedrock_cache_duration"` + IconCacheDuration time.Duration `yaml:"icon_cache_duration"` + } `yaml:"cache"` } -func (c *Configuration) ReadFile(path string) error { - data, err := ioutil.ReadFile(path) +func (c *Config) ReadFile(file string) error { + data, err := ioutil.ReadFile(file) if err != nil { return err diff --git a/src/default-icon.png b/src/icon.png similarity index 100% rename from src/default-icon.png rename to src/icon.png diff --git a/src/main.go b/src/main.go index 3673efa..53d0998 100644 --- a/src/main.go +++ b/src/main.go @@ -3,22 +3,20 @@ package main import ( "fmt" "log" - "net/http" "os" - "os/signal" "strconv" "sync" - "syscall" - "github.com/buaazp/fasthttprouter" - "github.com/valyala/fasthttp" + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/cors" ) var ( - config *Configuration = &Configuration{} - r *Redis = &Redis{} - blockedServers []string = nil - blockedServersMutex *sync.Mutex = &sync.Mutex{} + app *fiber.App = fiber.New(fiber.Config{DisableStartupMessage: true}) + r *Redis = &Redis{} + config *Config = &Config{} + blockedServers []string = nil + blockedServersMutex *sync.Mutex = &sync.Mutex{} ) func init() { @@ -32,42 +30,12 @@ func init() { log.Println("Successfully connected to Redis") - if Contains(os.Args, "--flush-cache") { - keys, err := r.Keys("java:*") - - if err != nil { - log.Fatal(err) - } - - if err = r.Delete(keys...); err != nil { - log.Fatal(err) - } - - keys, err = r.Keys("bedrock:*") - - if err != nil { - log.Fatal(err) - } - - if err = r.Delete(keys...); err != nil { - log.Fatal(err) - } - - keys, err = r.Keys("favicon:*") - - if err != nil { - log.Fatal(err) - } - - if err = r.Delete(keys...); err != nil { - log.Fatal(err) - } - - log.Println("Successfully flushed all cache keys") - - os.Exit(0) + if err := GetBlockedServerList(); err != nil { + log.Fatal(err) } + log.Println("Successfully retrieved EULA blocked servers") + if instanceID := os.Getenv("INSTANCE_ID"); len(instanceID) > 0 { value, err := strconv.ParseUint(instanceID, 10, 16) @@ -78,48 +46,15 @@ func init() { config.Port += uint16(value) } - if err := GetBlockedServerList(); err != nil { - log.Fatal(err) - } -} - -func middleware(next fasthttp.RequestHandler) fasthttp.RequestHandler { - return func(ctx *fasthttp.RequestCtx) { - if config.Environment == "development" { - log.Printf("GET %s - %s \"%s\"", ctx.Request.URI().Path(), ctx.RemoteAddr(), ctx.UserAgent()) - } - - ctx.Response.Header.Set("Access-Control-Allow-Headers", "*") - ctx.Response.Header.Set("Access-Control-Allow-Methods", "HEAD,GET,POST,OPTIONS") - ctx.Response.Header.Set("Access-Control-Allow-Origin", "*") - ctx.Response.Header.Set("Access-Control-Expose-Headers", "X-Cache-Hit,X-Cache-Time-Remaining,X-Server-Status,Content-Disposition") - - next(ctx) - } + app.Use(cors.New(cors.Config{ + AllowOrigins: "*", + AllowMethods: "HEAD,OPTIONS,GET", + ExposeHeaders: "Content-Type", + })) } func main() { - defer r.Close() - - router := fasthttprouter.New() - router.GET("/ping", PingHandler) - router.GET("/status/java/:address", JavaStatusHandler) - router.GET("/status/bedrock/:address", BedrockStatusHandler) - router.GET("/favicon/:address", FaviconNoExtensionHandler) - router.GET("/favicon/:address/*filename", FaviconHandler) - - router.PanicHandler = func(rc *fasthttp.RequestCtx, i interface{}) { - log.Println(i) - } - - router.NotFound = func(ctx *fasthttp.RequestCtx) { - WriteError(ctx, nil, http.StatusNotFound) - } - log.Printf("Listening on %s:%d\n", config.Host, config.Port) - log.Fatal(fasthttp.ListenAndServe(fmt.Sprintf("%s:%d", config.Host, config.Port), middleware(router.Handler))) - s := make(chan os.Signal) - signal.Notify(s, os.Interrupt, syscall.SIGTERM) - <-s + log.Fatal(app.Listen(fmt.Sprintf("%s:%d", config.Host, config.Port))) } diff --git a/src/redis.go b/src/redis.go index 40b2f7d..1f3db96 100644 --- a/src/redis.go +++ b/src/redis.go @@ -2,14 +2,14 @@ package main import ( "context" - "errors" + "encoding/json" "time" "github.com/go-redis/redis/v8" ) type Redis struct { - Conn *redis.Client + Client *redis.Client } func (r *Redis) Connect(uri string) error { @@ -19,37 +19,13 @@ func (r *Redis) Connect(uri string) error { return err } - conn := redis.NewClient(opts) + r.Client = redis.NewClient(opts) ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() - if err = conn.Ping(ctx).Err(); err != nil { - return err - } - - r.Conn = conn - - return nil -} - -func (r *Redis) TTL(key string) (time.Duration, error) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - - defer cancel() - - val, err := r.Conn.TTL(ctx, key).Result() - - if err != nil { - if errors.Is(err, redis.Nil) { - return 0, nil - } - - return 0, err - } - - return val, nil + return r.Client.Ping(ctx).Err() } func (r *Redis) Exists(key string) (bool, error) { @@ -57,27 +33,29 @@ func (r *Redis) Exists(key string) (bool, error) { defer cancel() - val, err := r.Conn.Exists(ctx, key).Result() + res := r.Client.Exists(ctx, key) + + if err := res.Err(); err != nil { + return false, err + } + + val, err := res.Result() return val == 1, err } -func (r *Redis) Get(key string) (string, error) { +func (r *Redis) GetString(key string) (string, error) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() - val, err := r.Conn.Get(ctx, key).Result() - - if err != nil { - if errors.Is(err, redis.Nil) { - return "", nil - } + res := r.Client.Get(ctx, key) + if err := res.Err(); err != nil { return "", err } - return val, nil + return res.Result() } func (r *Redis) GetBytes(key string) ([]byte, error) { @@ -85,17 +63,13 @@ func (r *Redis) GetBytes(key string) ([]byte, error) { defer cancel() - result := r.Conn.Get(ctx, key) - - if err := result.Err(); err != nil { - if errors.Is(err, redis.Nil) { - return nil, nil - } + res := r.Client.Get(ctx, key) + if err := res.Err(); err != nil { return nil, err } - return result.Bytes() + return res.Bytes() } func (r *Redis) Set(key string, value interface{}, ttl time.Duration) error { @@ -103,53 +77,19 @@ func (r *Redis) Set(key string, value interface{}, ttl time.Duration) error { defer cancel() - return r.Conn.Set(ctx, key, value, ttl).Err() + return r.Client.Set(ctx, key, value, ttl).Err() } -func (r *Redis) GetValueAndTTL(key string) (bool, string, time.Duration, error) { - exists, err := r.Exists(key) +func (r *Redis) SetJSON(key string, value interface{}, ttl time.Duration) error { + data, err := json.Marshal(value) if err != nil { - return false, "", 0, err + return err } - if !exists { - return false, "", 0, nil - } - - value, err := r.Get(key) - - if err != nil { - return false, "", 0, err - } - - ttl, err := r.TTL(key) - - return true, value, ttl, err -} - -func (r *Redis) Keys(pattern string) ([]string, error) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - - defer cancel() - - res := r.Conn.Keys(ctx, pattern) - - if err := res.Err(); err != nil { - return nil, err - } - - return res.Result() -} - -func (r *Redis) Delete(keys ...string) error { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - - defer cancel() - - return r.Conn.Del(ctx, keys...).Err() + return r.Set(key, data, ttl) } func (r *Redis) Close() error { - return r.Conn.Close() + return r.Client.Close() } diff --git a/src/routes.go b/src/routes.go index 6ade466..519f0f4 100644 --- a/src/routes.go +++ b/src/routes.go @@ -1,308 +1,74 @@ package main import ( - "encoding/base64" - "encoding/json" - "fmt" + "log" "net/http" - "strconv" - "strings" - "github.com/valyala/fasthttp" + "github.com/gofiber/fiber/v2" ) -func PingHandler(ctx *fasthttp.RequestCtx) { - ctx.SetBodyString(http.StatusText(http.StatusOK)) +func init() { + app.Get("/status/java/:address", JavaStatusHandler) + app.Get("/status/bedrock/:address", BedrockStatusHandler) + app.Get("/icon/:address", IconHandler) } -func JavaStatusHandler(ctx *fasthttp.RequestCtx) { - host, port, err := ParseAddress(ctx.UserValue("address").(string), 25565) +func JavaStatusHandler(ctx *fiber.Ctx) error { + host, port, err := ParseAddress(ctx.Params("address"), 25565) if err != nil { - WriteError(ctx, nil, http.StatusBadRequest, "Invalid/malformed address") + log.Println(err) - return + return ctx.Status(http.StatusBadRequest).SendString("Invalid address value") } - cacheEnabled, cacheKey, err := IsCacheEnabled(ctx, "java", host, port) + response, err := GetJavaStatus(host, port) if err != nil { - WriteError(ctx, err, http.StatusInternalServerError) - - return + return err } - if cacheEnabled { - exists, cache, ttl, err := r.GetValueAndTTL(cacheKey) - - if err != nil { - WriteError(ctx, err, http.StatusInternalServerError) - - return - } - - if exists { - ctx.Response.Header.Set("X-Cache-Hit", "TRUE") - ctx.Response.Header.Set("X-Cache-Time-Remaining", strconv.Itoa(int(ttl.Seconds()))) - ctx.SetContentType("application/json") - ctx.SetBodyString(cache) - - return - } + switch v := response.(type) { + case string: + return ctx.Type("json").SendString(v) + default: + return ctx.JSON(response) } - - ctx.Response.Header.Set("X-Cache-Hit", "FALSE") - - status := GetJavaStatus(host, port) - - if status.Online && status.Response.Favicon != nil { - data, err := base64.StdEncoding.DecodeString(strings.Replace(*status.Response.Favicon, "data:image/png;base64,", "", 1)) - - if err != nil { - WriteError(ctx, err, http.StatusInternalServerError) - - return - } - - if err := r.Set(fmt.Sprintf("favicon:%s-%d", host, port), data, config.FaviconCacheTTL); err != nil { - WriteError(ctx, err, http.StatusInternalServerError) - - return - } - } - - data, err := json.Marshal(status) - - if err != nil { - WriteError(ctx, err, http.StatusInternalServerError) - - return - } - - if err = r.Set(cacheKey, data, config.StatusCacheTTL); err != nil { - WriteError(ctx, err, http.StatusInternalServerError) - - return - } - - ctx.SetContentType("application/json") - ctx.SetBody(data) } -func BedrockStatusHandler(ctx *fasthttp.RequestCtx) { - host, port, err := ParseAddress(ctx.UserValue("address").(string), 19132) +func BedrockStatusHandler(ctx *fiber.Ctx) error { + host, port, err := ParseAddress(ctx.Params("address"), 19132) if err != nil { - WriteError(ctx, nil, http.StatusBadRequest, "Invalid/malformed address") - - return + return ctx.Status(http.StatusBadRequest).SendString("Invalid address value") } - cacheEnabled, cacheKey, err := IsCacheEnabled(ctx, "bedrock", host, port) + response, err := GetBedrockStatus(host, port) if err != nil { - WriteError(ctx, err, http.StatusInternalServerError) - - return + return err } - if cacheEnabled { - exists, cache, ttl, err := r.GetValueAndTTL(cacheKey) - - if err != nil { - WriteError(ctx, err, http.StatusInternalServerError) - - return - } - - if exists { - ctx.Response.Header.Set("X-Cache-Hit", "TRUE") - ctx.Response.Header.Set("X-Cache-Time-Remaining", strconv.Itoa(int(ttl.Seconds()))) - ctx.SetContentType("application/json") - ctx.SetBodyString(cache) - - return - } + switch v := response.(type) { + case string: + return ctx.Type("json").SendString(v) + default: + return ctx.JSON(response) } - - ctx.Response.Header.Set("X-Cache-Hit", "FALSE") - - data, err := json.Marshal(GetBedrockStatus(host, port)) - - if err != nil { - WriteError(ctx, err, http.StatusInternalServerError) - - return - } - - if err = r.Set(cacheKey, data, config.StatusCacheTTL); err != nil { - WriteError(ctx, err, http.StatusInternalServerError) - - return - } - - ctx.SetContentType("application/json") - ctx.SetBody(data) } -func FaviconNoExtensionHandler(ctx *fasthttp.RequestCtx) { - host, port, err := ParseAddress(ctx.UserValue("address").(string), 25565) +func IconHandler(ctx *fiber.Ctx) error { + host, port, err := ParseAddress(ctx.Params("address"), 25565) if err != nil { - WriteError(ctx, nil, http.StatusBadRequest, "Invalid/malformed address") - - return + return ctx.Status(http.StatusBadRequest).SendString("Invalid address value") } - cacheKey := fmt.Sprintf("favicon:%s-%d", host, port) - - exists, err := r.Exists(cacheKey) + icon, err := GetServerIcon(host, port) if err != nil { - WriteError(ctx, err, http.StatusInternalServerError) - - return + return err } - if exists { - value, err := r.GetBytes(cacheKey) - - if err != nil { - WriteError(ctx, err, http.StatusInternalServerError) - - return - } - - ctx.Response.Header.Set("X-Cache-Hit", "TRUE") - ctx.Response.Header.Set("X-Server-Status", "online-cache") - ctx.SetContentType("image/png") - ctx.SetBody(value) - - return - } - - ctx.Response.Header.Set("X-Cache-Hit", "FALSE") - - status := GetJavaStatus(host, port) - - if !status.Online { - ctx.Response.Header.Set("X-Server-Status", "offline") - ctx.SetContentType("image/png") - ctx.SetBody(defaultIconBytes) - - return - } - - if status.Response.Favicon == nil { - ctx.Response.Header.Set("X-Server-Status", "online-no-icon") - ctx.SetContentType("image/png") - ctx.SetBody(defaultIconBytes) - - return - } - - data, err := base64.StdEncoding.DecodeString(strings.Replace(*status.Response.Favicon, "data:image/png;base64,", "", 1)) - - if err != nil { - WriteError(ctx, err, http.StatusInternalServerError) - - return - } - - if err := r.Set(cacheKey, data, config.FaviconCacheTTL); err != nil { - WriteError(ctx, err, http.StatusInternalServerError) - - return - } - - ctx.Response.Header.Set("X-Server-Status", "online") - ctx.SetContentType("image/png") - ctx.SetBody(data) -} - -func FaviconHandler(ctx *fasthttp.RequestCtx) { - filename := ctx.UserValue("filename").(string) - - if !strings.HasSuffix(filename, ".png") { - WriteError(ctx, nil, http.StatusBadRequest, "Filename must end with .png") - - return - } - - host, port, err := ParseAddress(ctx.UserValue("address").(string), 25565) - - if err != nil { - WriteError(ctx, nil, http.StatusBadRequest, "Invalid/malformed address") - - return - } - - cacheKey := fmt.Sprintf("favicon:%s-%d", host, port) - - exists, err := r.Exists(cacheKey) - - if err != nil { - WriteError(ctx, err, http.StatusInternalServerError) - - return - } - - if exists { - value, err := r.GetBytes(cacheKey) - - if err != nil { - WriteError(ctx, err, http.StatusInternalServerError) - - return - } - - ctx.Response.Header.Set("X-Cache-Hit", "TRUE") - ctx.Response.Header.Set("X-Server-Status", "online-cache") - ctx.Response.Header.Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", filename)) - ctx.SetContentType("image/png") - ctx.SetBody(value) - - return - } - - ctx.Response.Header.Set("X-Cache-Hit", "FALSE") - - status := GetJavaStatus(host, port) - - if !status.Online { - ctx.Response.Header.Set("X-Server-Status", "offline") - ctx.Response.Header.Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", filename)) - ctx.SetContentType("image/png") - ctx.SetBody(defaultIconBytes) - - return - } - - if status.Response.Favicon == nil { - ctx.Response.Header.Set("X-Server-Status", "online-no-icon") - ctx.Response.Header.Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", filename)) - ctx.SetContentType("image/png") - ctx.SetBody(defaultIconBytes) - - return - } - - data, err := base64.StdEncoding.DecodeString(strings.Replace(*status.Response.Favicon, "data:image/png;base64,", "", 1)) - - if err != nil { - WriteError(ctx, err, http.StatusInternalServerError) - - return - } - - if err := r.Set(cacheKey, data, config.FaviconCacheTTL); err != nil { - WriteError(ctx, err, http.StatusInternalServerError) - - return - } - - ctx.Response.Header.Set("X-Server-Status", "online") - ctx.Response.Header.Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", filename)) - ctx.SetContentType("image/png") - ctx.SetBody(data) + return ctx.Type("png").Send(icon) } diff --git a/src/status.go b/src/status.go index 6d3865d..42bc5dc 100644 --- a/src/status.go +++ b/src/status.go @@ -1,61 +1,73 @@ package main -import "github.com/PassTheMayo/mcstatus/v4" +import ( + "encoding/base64" + "fmt" + "strings" -type StatusResponse[T JavaStatus | BedrockStatus] struct { + "github.com/PassTheMayo/mcstatus/v4" +) + +type StatusOffline struct { Online bool `json:"online"` Host string `json:"host"` Port uint16 `json:"port"` EULABlocked bool `json:"eula_blocked"` - Response *T `json:"response"` } -type JavaStatus struct { - Version *Version `json:"version"` - Players Players `json:"players"` - MOTD MOTD `json:"motd"` - Favicon *string `json:"favicon"` - ModInfo *ModInfo `json:"mod_info"` - SRVRecord *SRVRecord `json:"srv_record"` +type JavaStatusResponse struct { + Online bool `json:"online"` + Host string `json:"host"` + Port uint16 `json:"port"` + EULABlocked bool `json:"eula_blocked"` + Version *JavaVersion `json:"version"` + Players JavaPlayers `json:"players"` + MOTD MOTD `json:"motd"` + Icon *string `json:"icon"` + Mods []Mod `json:"mods"` } -type Players struct { - Online int `json:"online"` - Max int `json:"max"` - Sample []SamplePlayer `json:"sample"` +type BedrockStatusResponse struct { + Online bool `json:"online"` + Host string `json:"host"` + Port uint16 `json:"port"` + EULABlocked bool `json:"eula_blocked"` + Version *BedrockVersion `json:"version"` + Players *BedrockPlayers `json:"players"` + MOTD *MOTD `json:"motd"` + Gamemode *string `json:"gamemode"` + ServerID *string `json:"server_id"` + Edition *string `json:"edition"` } -type SamplePlayer struct { - ID string `json:"id"` - Name string `json:"name"` - Clean string `json:"clean"` - HTML string `json:"html"` +type JavaVersion struct { + NameRaw string `json:"name_raw"` + NameClean string `json:"name_clean"` + NameHTML string `json:"name_html"` + Protocol int `json:"protocol"` } -type ModInfo struct { - Type string `json:"type"` - Mods []Mod `json:"mods"` +type BedrockVersion struct { + Name *string `json:"name"` + Protocol *int64 `json:"protocol"` } -type Mod struct { - ID string `json:"id"` - Version string `json:"version"` +type JavaPlayers struct { + Online int `json:"online"` + Max int `json:"max"` + List []Player `json:"list"` } -type BedrockStatus struct { - ServerGUID int64 `json:"server_guid"` - Edition *string `json:"edition"` - MOTD *MOTD `json:"motd"` - ProtocolVersion *int64 `json:"protocol_version"` - Version *string `json:"version"` - OnlinePlayers *int64 `json:"online_players"` - MaxPlayers *int64 `json:"max_players"` - ServerID *string `json:"server_id"` - Gamemode *string `json:"gamemode"` - GamemodeID *int64 `json:"gamemode_id"` - PortIPv4 *uint16 `json:"port_ipv4"` - PortIPv6 *uint16 `json:"port_ipv6"` - SRVRecord *SRVRecord `json:"srv_record"` +type BedrockPlayers struct { + Online *int64 `json:"online"` + Max *int64 `json:"max"` +} + +type Player struct { + UUID string `json:"uuid"` + NameRaw string `json:"name_raw"` + NameClean string `json:"name_clean"` + NameHTML string `json:"name_html"` } type MOTD struct { @@ -64,188 +76,274 @@ type MOTD struct { HTML string `json:"html"` } -type Version struct { - Name string `json:"name"` - Protocol int `json:"protocol"` +type Mod struct { + Name string `json:"name"` + Version string `json:"version"` } -type SRVRecord struct { - Host string `json:"host"` - Port uint16 `json:"port"` +func GetJavaStatus(host string, port uint16) (interface{}, error) { + cacheKey := fmt.Sprintf("java:%s-%d", host, port) + + if config.Cache.Enable { + exists, err := r.Exists(cacheKey) + + if err != nil { + return nil, err + } + + if exists { + return r.GetString(cacheKey) + } + } + + response := FetchJavaStatus(host, port) + + if config.Cache.Enable { + if err := r.SetJSON(cacheKey, response, config.Cache.JavaCacheDuration); err != nil { + return nil, err + } + } + + return response, nil } -func GetJavaStatus(host string, port uint16) (resp StatusResponse[JavaStatus]) { +func GetBedrockStatus(host string, port uint16) (interface{}, error) { + cacheKey := fmt.Sprintf("bedrock:%s-%d", host, port) + + if config.Cache.Enable { + exists, err := r.Exists(cacheKey) + + if err != nil { + return nil, err + } + + if exists { + return r.GetString(cacheKey) + } + } + + response := FetchBedrockStatus(host, port) + + if config.Cache.Enable { + if err := r.SetJSON(cacheKey, response, config.Cache.BedrockCacheDuration); err != nil { + return nil, err + } + } + + return response, nil +} + +func GetServerIcon(host string, port uint16) ([]byte, error) { + cacheKey := fmt.Sprintf("icon:%s-%d", host, port) + + if config.Cache.Enable { + exists, err := r.Exists(cacheKey) + + if err != nil { + return nil, err + } + + if exists { + return r.GetBytes(cacheKey) + } + } + + icon := defaultIconBytes + + status, err := mcstatus.Status(host, port) + + if err == nil && status.Favicon != nil && strings.HasPrefix(*status.Favicon, "data:image/png;base64,") { + data, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(*status.Favicon, "data:image/png;base64,")) + + if err != nil { + return nil, err + } + + icon = data + } + + if config.Cache.Enable { + if err := r.Set(cacheKey, icon, config.Cache.IconCacheDuration); err != nil { + return nil, err + } + } + + return icon, nil +} + +func FetchJavaStatus(host string, port uint16) interface{} { status, err := mcstatus.Status(host, port) if err != nil { statusLegacy, err := mcstatus.StatusLegacy(host, port) if err != nil { - resp = StatusResponse[JavaStatus]{ + return StatusOffline{ Online: false, Host: host, Port: port, EULABlocked: IsBlockedAddress(host), - Response: nil, } - - return } - resp = StatusResponse[JavaStatus]{ + response := JavaStatusResponse{ Online: true, Host: host, Port: port, EULABlocked: IsBlockedAddress(host), - Response: &JavaStatus{ - Version: nil, - Players: Players{ - Online: statusLegacy.Players.Online, - Max: statusLegacy.Players.Max, - Sample: make([]SamplePlayer, 0), - }, - MOTD: MOTD{ - Raw: statusLegacy.MOTD.Raw, - Clean: statusLegacy.MOTD.Clean, - HTML: statusLegacy.MOTD.HTML, - }, - Favicon: nil, - ModInfo: nil, - SRVRecord: nil, + Version: nil, + Players: JavaPlayers{ + Online: statusLegacy.Players.Online, + Max: statusLegacy.Players.Max, + List: make([]Player, 0), }, + MOTD: MOTD{ + Raw: statusLegacy.MOTD.Raw, + Clean: statusLegacy.MOTD.Clean, + HTML: statusLegacy.MOTD.HTML, + }, + Icon: nil, + Mods: make([]Mod, 0), } if statusLegacy.Version != nil { - resp.Response.Version = &Version{ - Name: statusLegacy.Version.Name, - Protocol: statusLegacy.Version.Protocol, + response.Version = &JavaVersion{ + NameRaw: statusLegacy.Version.Name, + NameClean: statusLegacy.Version.Clean, + NameHTML: statusLegacy.Version.HTML, + Protocol: statusLegacy.Version.Protocol, } } - if statusLegacy.SRVResult != nil { - resp.Response.SRVRecord = &SRVRecord{ - Host: statusLegacy.SRVResult.Host, - Port: statusLegacy.SRVResult.Port, - } + return response + } + + playerList := make([]Player, 0) + + if status.Players.Sample != nil { + for _, player := range status.Players.Sample { + playerList = append(playerList, Player{ + UUID: player.ID, + NameRaw: player.Name, + NameClean: player.Clean, + NameHTML: player.HTML, + }) } - - return } - samplePlayers := make([]SamplePlayer, 0) + modList := make([]Mod, 0) - for _, player := range status.Players.Sample { - samplePlayers = append(samplePlayers, SamplePlayer{ - ID: player.ID, - Name: player.Name, - Clean: player.Clean, - HTML: player.HTML, - }) + if status.ModInfo != nil { + for _, mod := range status.ModInfo.Mods { + modList = append(modList, Mod{ + Name: mod.ID, + Version: mod.Version, + }) + } } - resp = StatusResponse[JavaStatus]{ + return JavaStatusResponse{ Online: true, Host: host, Port: port, EULABlocked: IsBlockedAddress(host), - Response: &JavaStatus{ - Version: &Version{ - Name: status.Version.Name, - Protocol: status.Version.Protocol, - }, - Players: Players{ - Online: status.Players.Online, - Max: status.Players.Max, - Sample: samplePlayers, - }, - MOTD: MOTD{ - Raw: status.MOTD.Raw, - Clean: status.MOTD.Clean, - HTML: status.MOTD.HTML, - }, - Favicon: status.Favicon, - ModInfo: nil, - SRVRecord: nil, + Version: &JavaVersion{ + NameRaw: status.Version.Name, + NameClean: status.Version.Clean, + NameHTML: status.Version.HTML, + Protocol: status.Version.Protocol, }, + Players: JavaPlayers{ + Online: status.Players.Online, + Max: status.Players.Max, + List: playerList, + }, + MOTD: MOTD{ + Raw: status.MOTD.Raw, + Clean: status.MOTD.Clean, + HTML: status.MOTD.HTML, + }, + Icon: status.Favicon, + Mods: modList, } - - if status.ModInfo != nil { - mods := make([]Mod, 0) - - for _, mod := range status.ModInfo.Mods { - mods = append(mods, Mod{ - ID: mod.ID, - Version: mod.Version, - }) - } - - resp.Response.ModInfo = &ModInfo{ - Type: status.ModInfo.Type, - Mods: mods, - } - } - - if status.SRVResult != nil { - resp.Response.SRVRecord = &SRVRecord{ - Host: status.SRVResult.Host, - Port: status.SRVResult.Port, - } - } - - return } -func GetBedrockStatus(host string, port uint16) (resp StatusResponse[BedrockStatus]) { +func FetchBedrockStatus(host string, port uint16) interface{} { status, err := mcstatus.StatusBedrock(host, port) if err != nil { - resp = StatusResponse[BedrockStatus]{ + return StatusOffline{ Online: false, Host: host, Port: port, EULABlocked: IsBlockedAddress(host), - Response: nil, } - - return } - resp = StatusResponse[BedrockStatus]{ + response := BedrockStatusResponse{ Online: true, Host: host, Port: port, EULABlocked: IsBlockedAddress(host), - Response: &BedrockStatus{ - ServerGUID: status.ServerGUID, - Edition: status.Edition, - MOTD: nil, - ProtocolVersion: status.ProtocolVersion, - Version: status.Version, - OnlinePlayers: status.OnlinePlayers, - MaxPlayers: status.MaxPlayers, - ServerID: status.ServerID, - Gamemode: status.Gamemode, - GamemodeID: status.GamemodeID, - PortIPv4: status.PortIPv4, - PortIPv6: status.PortIPv6, - SRVRecord: nil, - }, + Version: nil, + Players: nil, + MOTD: nil, + Gamemode: status.Gamemode, + ServerID: status.ServerID, + Edition: status.Edition, + } + + if status.Version != nil { + if response.Version == nil { + response.Version = &BedrockVersion{ + Name: nil, + Protocol: nil, + } + } + + response.Version.Name = status.Version + } + + if status.ProtocolVersion != nil { + if response.Version == nil { + response.Version = &BedrockVersion{ + Name: nil, + Protocol: nil, + } + } + + response.Version.Protocol = status.ProtocolVersion + } + + if status.OnlinePlayers != nil { + if response.Players == nil { + response.Players = &BedrockPlayers{ + Online: nil, + Max: nil, + } + } + + response.Players.Online = status.OnlinePlayers + } + + if status.MaxPlayers != nil { + if response.Players == nil { + response.Players = &BedrockPlayers{ + Online: nil, + Max: nil, + } + } + + response.Players.Max = status.MaxPlayers } if status.MOTD != nil { - resp.Response.MOTD = &MOTD{ + response.MOTD = &MOTD{ Raw: status.MOTD.Raw, Clean: status.MOTD.Clean, HTML: status.MOTD.HTML, } } - if status.SRVResult != nil { - resp.Response.SRVRecord = &SRVRecord{ - Host: status.SRVResult.Host, - Port: status.SRVResult.Port, - } - } - - return + return response } diff --git a/src/util.go b/src/util.go index cbe8f5b..67c51c7 100644 --- a/src/util.go +++ b/src/util.go @@ -4,27 +4,29 @@ import ( "crypto/sha1" _ "embed" "encoding/hex" - "errors" "fmt" "io/ioutil" - "log" "net/http" "regexp" "strconv" "strings" - - "github.com/valyala/fasthttp" ) var ( - //go:embed default-icon.png + //go:embed icon.png defaultIconBytes []byte ipAddressRegExp = regexp.MustCompile("^\\d{1,3}(\\.\\d{1,3}){3}$") ) -var ( - ErrNoAddressMatch = errors.New("address does not match any known format") -) +func Contains[T comparable](arr []T, v T) bool { + for _, value := range arr { + if v == value { + return true + } + } + + return false +} func GetBlockedServerList() error { resp, err := http.Get("https://sessionserver.mojang.com/blockedservers") @@ -100,7 +102,7 @@ func ParseAddress(address string, defaultPort uint16) (string, uint16, error) { result := strings.SplitN(address, ":", 2) if len(result) < 1 { - return "", 0, ErrNoAddressMatch + return "", 0, fmt.Errorf("'%s' does not match any known address", address) } if len(result) < 2 { @@ -115,47 +117,3 @@ func ParseAddress(address string, defaultPort uint16) (string, uint16, error) { return result[0], uint16(port), nil } - -func WriteError(ctx *fasthttp.RequestCtx, err error, statusCode int, body ...string) { - ctx.SetStatusCode(statusCode) - - if len(body) > 0 { - ctx.SetBodyString(body[0]) - } else { - ctx.SetBodyString(http.StatusText(statusCode)) - } - - if err != nil { - log.Println(err) - } -} - -func IsCacheEnabled(ctx *fasthttp.RequestCtx, cacheType, host string, port uint16) (bool, string, error) { - key := fmt.Sprintf("%s:%s-%d", cacheType, host, port) - - if authKey := ctx.Request.Header.Peek("Authorization"); len(authKey) > 0 { - exists, err := r.Exists(fmt.Sprintf("auth_key:%s", authKey)) - - if err != nil { - WriteError(ctx, err, http.StatusInternalServerError) - - return config.CacheEnable, key, err - } - - if exists { - return false, key, nil - } - } - - return config.CacheEnable, key, nil -} - -func Contains[T comparable](arr []T, x T) bool { - for _, v := range arr { - if v == x { - return true - } - } - - return false -}