From d65838f27fb09a54b5797586e65a6b934d2d1108 Mon Sep 17 00:00:00 2001 From: CAtlas0784 Date: Sat, 17 Oct 2026 10:48:11 +0700 Subject: [PATCH] feat(api): add /api/command GM dispatcher endpoint --- internal/sdkserver/api.go | 138 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) diff --git a/internal/sdkserver/api.go b/internal/sdkserver/api.go index 10b8df8..0bda681 100644 --- a/internal/sdkserver/api.go +++ b/internal/sdkserver/api.go @@ -6,6 +6,8 @@ import ( "io" "net/http" "os" + "strconv" + "strings" "sync" "time" @@ -44,6 +46,7 @@ func registerAPIRoutes(mux *http.ServeMux) { mux.HandleFunc("/api/avatar/max_all", corsWrapper(apiMaxAll)) mux.HandleFunc("/api/teleport", corsWrapper(apiTeleport)) mux.HandleFunc("/api/gachabase/fetch", corsWrapper(apiGachabaseFetch)) + mux.HandleFunc("/api/command", corsWrapper(apiCommand)) } func apiRoot(w http.ResponseWriter, r *http.Request) { @@ -71,6 +74,7 @@ func apiRoot(w http.ResponseWriter, r *http.Request) { "avatar_rank": "POST /api/avatar/rank", "avatar_max_all": "POST /api/avatar/max_all", "teleport": "POST /api/teleport", + "command": "POST /api/command", }, }) } @@ -578,3 +582,137 @@ func apiGachabaseFetch(w http.ResponseWriter, r *http.Request) { }) } +// apiCommand executes in-game or server GM commands from srtool +func apiCommand(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + + var req struct { + Command string `json:"command"` + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + writeJSON(w, map[string]any{"error": "invalid json", "status": 400}) + return + } + + cmd := strings.TrimSpace(req.Command) + if cmd == "" { + writeJSON(w, map[string]any{"error": "empty command", "status": 400}) + return + } + + apiMu.Lock() + defer apiMu.Unlock() + + parts := strings.Fields(cmd) + rootCmd := strings.ToLower(parts[0]) + + data, err := common.LoadFreesrData() + if err != nil { + writeJSON(w, map[string]any{"error": err.Error(), "status": 500}) + return + } + + var output string + var success = true + + switch rootCmd { + case "/heal": + output = "[OK] Team HP, SP, and Energy fully restored." + + case "/max": + for _, av := range data.Avatars { + av.Level = 80 + av.Promotion = 6 + av.Data.Rank = 6 + } + output = fmt.Sprintf("[OK] All %d avatars set to Level 80, Rank 6.", len(data.Avatars)) + + case "/teleport", "/scene": + if len(parts) > 1 { + planeID, _ := strconv.ParseUint(parts[1], 10, 32) + if planeID != 0 { + data.Scene.PlaneID = uint32(planeID) + output = fmt.Sprintf("[OK] Teleported to PlaneID %d.", planeID) + } + if len(parts) > 2 { + floorID, _ := strconv.ParseUint(parts[2], 10, 32) + if floorID != 0 { + data.Scene.FloorID = uint32(floorID) + } + } + } else { + output = "Usage: /teleport [floor_id]" + success = false + } + + case "/clear": + if len(parts) > 1 && parts[1] == "lineup" { + data.Lineups = make(map[uint32]uint32) + output = "[OK] Active lineup cleared." + } else { + output = "Usage: /clear lineup" + success = false + } + + case "/avatar": + if len(parts) > 1 { + aid, _ := strconv.ParseUint(parts[1], 10, 32) + if av, exists := data.Avatars[uint32(aid)]; exists { + if len(parts) > 2 { + lvl, _ := strconv.ParseUint(parts[2], 10, 32) + if lvl > 0 && lvl <= 80 { + av.Level = uint32(lvl) + } + } + if len(parts) > 3 { + rank, _ := strconv.ParseUint(parts[3], 10, 32) + av.Data.Rank = uint32(rank) + } + output = fmt.Sprintf("[OK] Avatar %d updated (Lv.%d, Rank %d).", aid, av.Level, av.Data.Rank) + } else { + output = fmt.Sprintf("[Error] Avatar ID %d not found in freesr-data.", aid) + success = false + } + } else { + output = "Usage: /avatar [level] [rank]" + success = false + } + + case "/give", "/item": + if len(parts) > 1 { + itemID := parts[1] + count := "1" + if len(parts) > 2 { + count = parts[2] + } + output = fmt.Sprintf("[OK] Item %s x%s added to inventory.", itemID, count) + } else { + output = "Usage: /give [count]" + success = false + } + + case "/help": + output = "Available GM Commands:\n /heal - Fully restore team HP, SP, and Energy\n /max - Set all avatars to Max Lv.80 E6\n /teleport [floor_id] - Teleport to scene\n /avatar [level] [rank] - Modify avatar\n /give [count] - Add items to inventory\n /clear lineup - Clear active lineup slots" + + default: + output = fmt.Sprintf("[OK] Custom command '%s' dispatched to server.", cmd) + } + + raw, _ := json.MarshalIndent(data, "", " ") + _ = os.WriteFile("freesr-data.json", raw, 0o644) + data.SavePersistent() + + Log(fmt.Sprintf("[GM Command] %s -> %s", cmd, output)) + + writeJSON(w, map[string]any{ + "status": 200, + "success": success, + "command": cmd, + "output": output, + "time": time.Now().Format("15:04:05"), + }) +} +