package controller import ( "crypto/sha256" "crypto/subtle" "math" "net/http" "net/mail" "os" "strings" "github.com/QuantumNous/new-api/common" "github.com/QuantumNous/new-api/model" "github.com/QuantumNous/new-api/setting/operation_setting" "github.com/gin-gonic/gin" "github.com/shopspring/decimal" ) // BotBalanceQuery is a dedicated read-only API; its key grants no dashboard access. func BotBalanceQuery(c *gin.Context) { c.Header("Cache-Control", "no-store") key := os.Getenv("BOT_BALANCE_KEY") raw, bearer := strings.CutPrefix(c.GetHeader("Authorization"), "Bearer ") expected, supplied := sha256.Sum256([]byte(key)), sha256.Sum256([]byte(raw)) if len(key) < 32 || !bearer || subtle.ConstantTimeCompare(expected[:], supplied[:]) != 1 { c.JSON(http.StatusUnauthorized, gin.H{"success": false, "code": "unauthorized"}) return } c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 8192) var request struct { Email string `json:"email"` IDs []int `json:"ids"` } if err := c.ShouldBindJSON(&request); err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "code": "invalid_request"}) return } email := model.NormalizeEmail(request.Email) if (email == "") == (len(request.IDs) == 0) || len(request.IDs) > 100 { c.JSON(http.StatusBadRequest, gin.H{"success": false, "code": "invalid_request"}) return } if email != "" { parsed, err := mail.ParseAddress(email) if err != nil || parsed.Address != email || len(email) > 50 { c.JSON(http.StatusBadRequest, gin.H{"success": false, "code": "invalid_email"}) return } } for _, id := range request.IDs { if id <= 0 { c.JSON(http.StatusBadRequest, gin.H{"success": false, "code": "invalid_request"}) return } } accounts, err := model.FindBotBalanceAccounts(email, request.IDs) if err != nil { c.JSON(http.StatusServiceUnavailable, gin.H{"success": false, "code": "query_failed"}) return } if email != "" && len(accounts) != 1 { code := "not_found" if len(accounts) > 1 { code = "ambiguous_email" } c.JSON(http.StatusConflict, gin.H{"success": false, "code": code}) return } unit, rate := common.QuotaPerUnit, operation_setting.USDExchangeRate if unit <= 0 || rate <= 0 || math.IsNaN(unit) || math.IsNaN(rate) || math.IsInf(unit, 0) || math.IsInf(rate, 0) { c.JSON(http.StatusServiceUnavailable, gin.H{"success": false, "code": "invalid_currency"}) return } items := make([]gin.H, 0, len(accounts)) for _, account := range accounts { active := account.Status == common.UserStatusEnabled item := gin.H{"id": account.ID, "active": active} if active { item["balance_cny"] = decimal.NewFromInt(int64(account.Quota)).Mul(decimal.NewFromFloat(rate)).DivRound(decimal.NewFromFloat(unit), 18).String() } items = append(items, item) } c.JSON(http.StatusOK, gin.H{"success": true, "data": items}) }