feat: publish sanitized custom.97 source

This commit is contained in:
cneicy committed 2026-10-27 20:43:39 +08:00
1 parent 8e6ed0317b
commit 86c0276156
1022 files changed
+69903 -19207

No files matched your search

+121 -11
View File
@@ -157,7 +157,8 @@ func GetResponseBody(method, url string, channel *model.Channel, headers http.He
for k := range headers {
req.Header.Add(k, headers.Get(k))
}
client, err := service.GetHttpClientWithProxy(channel.GetSetting().Proxy)
setting := channel.GetSetting()
client, err := service.GetHttpClientWithProxySettings(setting.Proxy, setting)
if err != nil {
return nil, err
}
@@ -420,6 +421,113 @@ func updateChannelMoonshotBalance(channel *model.Channel) (float64, error) {
return availableBalanceUsd, nil
}
func updateChannelSub2APIBalance(channel *model.Channel) (float64, error) {
baseURL := channel.GetBaseURL()
if baseURL == "" {
return 0, errors.New("Sub2API channel is missing base_url")
}
url := fmt.Sprintf("%s/v1/usage", strings.TrimRight(baseURL, "/"))
body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
if err != nil {
return 0, err
}
type sub2apiUsageResponse struct {
Balance *float64 `json:"balance"`
Remaining *float64 `json:"remaining"`
IsValid *bool `json:"isValid"`
Subscription *struct {
DailyLimitUSD *float64 `json:"daily_limit_usd"`
WeeklyLimitUSD *float64 `json:"weekly_limit_usd"`
MonthlyLimitUSD *float64 `json:"monthly_limit_usd"`
} `json:"subscription"`
}
usage := sub2apiUsageResponse{}
if err := common.Unmarshal(body, &usage); err != nil {
return 0, err
}
if usage.IsValid != nil && !*usage.IsValid {
return 0, errors.New("Sub2API key is invalid (isValid=false)")
}
balance := usage.Remaining
if balance == nil {
balance = usage.Balance
}
if balance == nil {
return 0, errors.New("Sub2API /v1/usage response is missing remaining and balance fields (the key may belong to a subscription group without an available balance)")
}
// Sub2API reports remaining=-1 for a subscription group with no daily, weekly,
// or monthly limit. A wallet balance can legitimately be negative, so detect
// the unlimited case from the subscription limits rather than the value.
subscription := usage.Subscription
if subscription != nil && subscription.DailyLimitUSD == nil && subscription.WeeklyLimitUSD == nil && subscription.MonthlyLimitUSD == nil {
return 0, errors.New("Sub2API /v1/usage reports an unlimited subscription without a finite balance")
}
channel.UpdateBalance(*balance)
return *balance, nil
}
// updateChannelNewAPIBalance reads the upstream key's remaining quota. When the
// upstream also returns its owner's quota, the smaller of the two is what the
// key can still spend. Quota converts to USD with the upstream's quota_per_unit.
func updateChannelNewAPIBalance(channel *model.Channel) (float64, error) {
baseURL := strings.TrimRight(channel.GetBaseURL(), "/")
if baseURL == "" {
return 0, errors.New("New API channel is missing base_url")
}
body, err := GetResponseBody("GET", baseURL+"/api/usage/token/", channel, GetAuthHeader(channel.Key))
if err != nil {
return 0, err
}
var usage struct {
Code bool `json:"code"`
Message string `json:"message"`
Data struct {
TotalAvailable float64 `json:"total_available"`
UnlimitedQuota bool `json:"unlimited_quota"`
UserQuota *struct {
TotalAvailable float64 `json:"total_available"`
} `json:"user_quota"`
} `json:"data"`
}
if err := common.Unmarshal(body, &usage); err != nil {
return 0, err
}
if !usage.Code {
return 0, fmt.Errorf("New API usage query failed: %s", usage.Message)
}
remaining := usage.Data.TotalAvailable
if usage.Data.UnlimitedQuota {
remaining = math.Inf(1)
}
if userQuota := usage.Data.UserQuota; userQuota != nil {
remaining = min(remaining, userQuota.TotalAvailable)
}
if math.IsInf(remaining, 1) {
return 0, common.NewMessage(`The upstream key has unlimited quota. Turn on "Allow querying user quota" for this key on the upstream to see the real balance.`)
}
body, err = GetResponseBody("GET", baseURL+"/api/status", channel, nil)
if err != nil {
return 0, err
}
var status struct {
Data struct {
QuotaPerUnit float64 `json:"quota_per_unit"`
} `json:"data"`
}
if err := common.Unmarshal(body, &status); err != nil {
return 0, err
}
if status.Data.QuotaPerUnit <= 0 {
return 0, errors.New("New API /api/status response is missing quota_per_unit")
}
balance := remaining / status.Data.QuotaPerUnit
channel.UpdateBalance(balance)
return balance, nil
}
func fetchAdvancedCustomBalance(channel *model.Channel) (channelBalanceResult, error) {
key := strings.TrimSpace(channel.Key)
info := &relaycommon.RelayInfo{
@@ -453,7 +561,8 @@ func fetchAdvancedCustomBalance(channel *model.Channel) (channelBalanceResult, e
request.Host = headers.Get(name)
}
}
client, err := service.GetHttpClientWithProxy(channel.GetSetting().Proxy)
setting := channel.GetSetting()
client, err := service.GetHttpClientWithProxySettings(setting.Proxy, setting)
if err != nil {
return channelBalanceResult{}, sanitizeFetchModelsError(err, key)
}
@@ -524,7 +633,7 @@ func updateStandardChannelBalance(channel *model.Channel) (float64, error) {
baseURL = channel.GetBaseURL()
}
case constant.ChannelTypeAzure:
return 0, errors.New("尚未实现")
return 0, common.NewMessage("Not implemented yet")
case constant.ChannelTypeCustom:
baseURL = channel.GetBaseURL()
//case common.ChannelTypeOpenAISB:
@@ -543,8 +652,12 @@ func updateStandardChannelBalance(channel *model.Channel) (float64, error) {
return updateChannelOpenRouterBalance(channel)
case constant.ChannelTypeMoonshot:
return updateChannelMoonshotBalance(channel)
case constant.ChannelTypeSub2API:
return updateChannelSub2APIBalance(channel)
case constant.ChannelTypeNewAPI:
return updateChannelNewAPIBalance(channel)
default:
return 0, errors.New("尚未实现")
return 0, common.NewMessage("Not implemented yet")
}
url := fmt.Sprintf("%s/v1/dashboard/billing/subscription", baseURL)
@@ -594,10 +707,7 @@ func UpdateChannelBalance(c *gin.Context) {
return
}
if channel.ChannelInfo.IsMultiKey {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "多密钥渠道不支持余额查询",
})
common.ApiErrorT(c, "Multi-key channels do not support balance queries")
return
}
result, err := updateChannelBalance(channel)
@@ -639,7 +749,7 @@ func updateAllChannelsBalance() error {
} else if result.RawResponse == "" {
// err is nil & balance <= 0 means quota is used up
if result.Balance <= 0 {
service.DisableChannel(*types.NewChannelError(channel.Id, channel.Type, channel.Name, channel.ChannelInfo.IsMultiKey, "", channel.GetAutoBan()), "余额不足")
service.DisableChannel(*types.NewChannelError(channel.Id, channel.Type, channel.Name, channel.ChannelInfo.IsMultiKey, "", channel.GetAutoBan()), "Insufficient balance")
}
}
time.Sleep(common.RequestInterval)
@@ -664,8 +774,8 @@ func UpdateAllChannelsBalance(c *gin.Context) {
func AutomaticallyUpdateChannels(frequency int) {
for {
time.Sleep(time.Duration(frequency) * time.Minute)
common.SysLog("updating all channels")
common.SysLog(common.LogText("updating all channels"))
_ = updateAllChannelsBalance()
common.SysLog("channels update done")
common.SysLog(common.LogText("channels update done"))
}
}