feat(api): add /api/command GM dispatcher endpoint
This commit is contained in:
1 parent
5159d7ef14
commit
d65838f27f
1 file changed
+138
@@ -6,6 +6,8 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -44,6 +46,7 @@ func registerAPIRoutes(mux *http.ServeMux) {
|
|||||||
mux.HandleFunc("/api/avatar/max_all", corsWrapper(apiMaxAll))
|
mux.HandleFunc("/api/avatar/max_all", corsWrapper(apiMaxAll))
|
||||||
mux.HandleFunc("/api/teleport", corsWrapper(apiTeleport))
|
mux.HandleFunc("/api/teleport", corsWrapper(apiTeleport))
|
||||||
mux.HandleFunc("/api/gachabase/fetch", corsWrapper(apiGachabaseFetch))
|
mux.HandleFunc("/api/gachabase/fetch", corsWrapper(apiGachabaseFetch))
|
||||||
|
mux.HandleFunc("/api/command", corsWrapper(apiCommand))
|
||||||
}
|
}
|
||||||
|
|
||||||
func apiRoot(w http.ResponseWriter, r *http.Request) {
|
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_rank": "POST /api/avatar/rank",
|
||||||
"avatar_max_all": "POST /api/avatar/max_all",
|
"avatar_max_all": "POST /api/avatar/max_all",
|
||||||
"teleport": "POST /api/teleport",
|
"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 <plane_id> [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 <avatar_id> [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 <item_id> [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 <plane_id> [floor_id] - Teleport to scene\n /avatar <id> [level] [rank] - Modify avatar\n /give <item_id> [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"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
Reference in new issue
Block a user