From a6f5ca677978b4d0fca06cf71641bbe32110b67d Mon Sep 17 00:00:00 2001 From: CAtlas0784 Date: Sat, 17 Oct 2026 03:45:06 +0700 Subject: [PATCH] feat(sdkserver): add /api/gachabase/fetch proxy endpoint for Gachabase integration --- internal/sdkserver/api.go | 62 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/internal/sdkserver/api.go b/internal/sdkserver/api.go index 8b1573a..9325abf 100644 --- a/internal/sdkserver/api.go +++ b/internal/sdkserver/api.go @@ -7,6 +7,7 @@ import ( "net/http" "os" "sync" + "time" "gac-hkrpg-go/internal/common" ) @@ -42,6 +43,7 @@ func registerAPIRoutes(mux *http.ServeMux) { mux.HandleFunc("/api/avatar/rank", corsWrapper(apiAvatarRank)) mux.HandleFunc("/api/avatar/max_all", corsWrapper(apiMaxAll)) mux.HandleFunc("/api/teleport", corsWrapper(apiTeleport)) + mux.HandleFunc("/api/gachabase/fetch", corsWrapper(apiGachabaseFetch)) } func apiRoot(w http.ResponseWriter, r *http.Request) { @@ -506,3 +508,63 @@ func apiTeleport(w http.ResponseWriter, r *http.Request) { data.SavePersistent() writeJSON(w, map[string]any{"message": "teleported successfully", "status": 200}) } + +// apiGachabaseFetch proxies requests to hsr.gachabase.net to bypass browser CORS restrictions. +func apiGachabaseFetch(w http.ResponseWriter, r *http.Request) { + targetURL := r.URL.Query().Get("url") + targetType := r.URL.Query().Get("type") // "character" | "lightcone" | "relic" + targetID := r.URL.Query().Get("id") + branch := r.URL.Query().Get("branch") + if branch == "" { + branch = "beta" + } + lang := r.URL.Query().Get("lang") + if lang == "" { + lang = "en" + } + + if targetURL == "" && targetID != "" && targetType != "" { + switch targetType { + case "character", "avatar": + targetURL = fmt.Sprintf("https://hsr.gachabase.net/characters/%s/slug/%s?lang=%s", targetID, branch, lang) + case "lightcone", "lc": + targetURL = fmt.Sprintf("https://hsr.gachabase.net/lightcones/%s/slug/%s?lang=%s", targetID, branch, lang) + case "relic": + targetURL = fmt.Sprintf("https://hsr.gachabase.net/relics/%s/slug/%s?lang=%s", targetID, branch, lang) + } + } + + if targetURL == "" { + writeJSON(w, map[string]any{"error": "missing url or id/type parameters", "status": 400}) + return + } + + client := &http.Client{Timeout: 12 * time.Second} + req, err := http.NewRequestWithContext(r.Context(), http.MethodGet, targetURL, nil) + if err != nil { + writeJSON(w, map[string]any{"error": err.Error(), "status": 500}) + return + } + req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36") + req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") + + resp, err := client.Do(req) + if err != nil { + writeJSON(w, map[string]any{"error": "failed to connect to gachabase: " + err.Error(), "status": 502}) + return + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + writeJSON(w, map[string]any{"error": "failed to read gachabase response: " + err.Error(), "status": 500}) + return + } + + writeJSON(w, map[string]any{ + "status": resp.StatusCode, + "html": string(body), + "url": targetURL, + }) +} +