From 0686fad440d13ee406012f8b3481b1383a44dcad Mon Sep 17 00:00:00 2001 From: Yannik <80621863+vaporvee@users.noreply.github.com> Date: Tue, 29 Aug 2023 12:44:29 +0200 Subject: [PATCH] added caching (not updating yet) --- extended-user-info-api/.gitignore | 4 +- extended-user-info-api/go.mod | 6 +- extended-user-info-api/go.sum | 8 ++ extended-user-info-api/main.go | 163 +++++++++++++++++++++--------- 4 files changed, 128 insertions(+), 53 deletions(-) diff --git a/extended-user-info-api/.gitignore b/extended-user-info-api/.gitignore index 2eea525..350fbbd 100644 --- a/extended-user-info-api/.gitignore +++ b/extended-user-info-api/.gitignore @@ -1 +1,3 @@ -.env \ No newline at end of file +.env +user_cache.json +vendor/ \ No newline at end of file diff --git a/extended-user-info-api/go.mod b/extended-user-info-api/go.mod index b46595e..80cfb36 100644 --- a/extended-user-info-api/go.mod +++ b/extended-user-info-api/go.mod @@ -2,7 +2,10 @@ module discord-sdk-godot.vercel.app go 1.21.0 -require github.com/gin-gonic/gin v1.9.1 +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/joho/godotenv v1.5.1 +) require ( github.com/bytedance/sonic v1.9.1 // indirect @@ -13,7 +16,6 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.14.0 // indirect github.com/goccy/go-json v0.10.2 // indirect - github.com/joho/godotenv v1.5.1 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/leodido/go-urn v1.2.4 // indirect diff --git a/extended-user-info-api/go.sum b/extended-user-info-api/go.sum index f2890d0..f7dd2cc 100644 --- a/extended-user-info-api/go.sum +++ b/extended-user-info-api/go.sum @@ -5,6 +5,7 @@ github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= @@ -12,6 +13,8 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= @@ -21,6 +24,7 @@ github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QX github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= @@ -41,6 +45,7 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -51,6 +56,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= @@ -69,10 +75,12 @@ golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/extended-user-info-api/main.go b/extended-user-info-api/main.go index f427127..3ed0969 100644 --- a/extended-user-info-api/main.go +++ b/extended-user-info-api/main.go @@ -3,10 +3,12 @@ package main import ( "encoding/json" "io" + "io/ioutil" "net/http" "os" "strconv" "strings" + "sync" "time" "github.com/gin-gonic/gin" @@ -23,6 +25,45 @@ type User struct { BannerColor string `json:"banner_color"` } +var cacheMutex sync.Mutex + +const cacheFilePath = "user_cache.json" + +func readCacheFile() (map[string]User, error) { + cacheMutex.Lock() + defer cacheMutex.Unlock() + + data, err := ioutil.ReadFile(cacheFilePath) + if err != nil { + return nil, err + } + + var cacheData map[string]User + err = json.Unmarshal(data, &cacheData) + if err != nil { + return nil, err + } + + return cacheData, nil +} + +func writeCacheFile(cacheData map[string]User) error { + cacheMutex.Lock() + defer cacheMutex.Unlock() + + data, err := json.Marshal(cacheData) + if err != nil { + return err + } + + err = ioutil.WriteFile(cacheFilePath, data, 0644) + if err != nil { + return err + } + + return nil +} + func main() { r := gin.Default() r.GET("/:userIDs", func(c *gin.Context) { @@ -33,71 +74,93 @@ func main() { return } + // Load cached data if available + userResponses := make(map[string]User) + cacheData, err := readCacheFile() + if err == nil { + userResponses = cacheData + } + godotenv.Load() botToken := os.Getenv("BOT_TOKEN") client := &http.Client{} - userResponses := make(map[string]User) - for _, userID := range userIDs { - for { - req, err := http.NewRequest("GET", "https://discord.com/api/v10/users/"+userID, nil) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - req.Header.Add("Authorization", "Bot "+botToken) - req.Header.Add("Content-Type", "application/json") - - res, err := client.Do(req) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - defer res.Body.Close() - - if res.StatusCode == http.StatusTooManyRequests { - retryAfter := parseRetryAfterHeader(res.Header) - if retryAfter > 0 { - time.Sleep(retryAfter) - continue // Retry the request after waiting + if cachedUser, found := userResponses[userID]; found { + userResponses[userID] = cachedUser + } else { + for { + req, err := http.NewRequest("GET", "https://discord.com/api/v10/users/"+userID, nil) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return } - } + req.Header.Add("Authorization", "Bot "+botToken) + req.Header.Add("Content-Type", "application/json") - body, err := io.ReadAll(res.Body) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } + res, err := client.Do(req) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + defer res.Body.Close() - var jsonMap map[string]interface{} - if err := json.Unmarshal(body, &jsonMap); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } + if res.StatusCode == http.StatusTooManyRequests { + retryAfter := parseRetryAfterHeader(res.Header) + if retryAfter > 0 { + time.Sleep(retryAfter) + continue // Retry the request after waiting + } + } - user := User{ - GlobalName: getStringFromMap(jsonMap, "global_name"), - PublicFlags: getIntFromMap(jsonMap, "public_flags"), - Flags: getIntFromMap(jsonMap, "flags"), - AccentColor: getStringFromMap(jsonMap, "accent_color"), - BannerColor: getStringFromMap(jsonMap, "banner_color"), - } + body, err := io.ReadAll(res.Body) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } - if avatarDecoration, ok := jsonMap["avatar_decoration"].(string); ok { - user.AvatarDecorationURL = "https://cdn.discordapp.com/avatar-decorations/" + userID + "/" + avatarDecoration + ".png" - } + var jsonMap map[string]interface{} + if err := json.Unmarshal(body, &jsonMap); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } - if banner, ok := jsonMap["banner"].(string); ok { - user.BannerURL = "https://cdn.discordapp.com/banners/" + userID + "/" + banner + ".png" - } + user := User{ + GlobalName: getStringFromMap(jsonMap, "global_name"), + PublicFlags: getIntFromMap(jsonMap, "public_flags"), + Flags: getIntFromMap(jsonMap, "flags"), + AccentColor: getStringFromMap(jsonMap, "accent_color"), + BannerColor: getStringFromMap(jsonMap, "banner_color"), + } - userResponses[userID] = user - break // Exit the loop and process the next user ID + if avatarDecoration, ok := jsonMap["avatar_decoration"].(string); ok { + user.AvatarDecorationURL = "https://cdn.discordapp.com/avatar-decorations/" + userID + "/" + avatarDecoration + ".png" + } + + if banner, ok := jsonMap["banner"].(string); ok { + user.BannerURL = "https://cdn.discordapp.com/banners/" + userID + "/" + banner + ".png" + } + + userResponses[userID] = user + break // Exit the loop and process the next user ID + } } } - c.JSON(http.StatusOK, userResponses) + // Save the cache data to file + err = writeCacheFile(userResponses) + if err != nil { + // Handle error + } + + responseData := make(map[string]User) + for _, userID := range userIDs { + if user, found := userResponses[userID]; found { + responseData[userID] = user + } + } + + c.JSON(http.StatusOK, responseData) }) r.GET("/", func(c *gin.Context) {