Files

1 line
222 KiB
JSON

{"repo": "6uu1/veloera-cftun", "files": {"controller/channel-billing.go": {"2c61fa7dd510621226666bc1243390e96880095b": {"t": 1785746062.804276, "n": 12191, "h": "e1585bb15b163657", "c": "// Copyright (c) 2025 Tethys Plex\n//\n// This file is part of Veloera.\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see <https://www.gnu.org/licenses/>.\npackage controller\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\t\"veloera/common\"\n\t\"veloera/model\"\n\t\"veloera/service\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// https://github.com/songquanpeng/veloera/issues/79\n\ntype OpenAISubscriptionResponse struct {\n\tObject string `json:\"object\"`\n\tHasPaymentMethod bool `json:\"has_payment_method\"`\n\tSoftLimitUSD float64 `json:\"soft_limit_usd\"`\n\tHardLimitUSD float64 `json:\"hard_limit_usd\"`\n\tSystemHardLimitUSD float64 `json:\"system_hard_limit_usd\"`\n\tAccessUntil int64 `json:\"access_until\"`\n}\n\ntype OpenAIUsageDailyCost struct {\n\tTimestamp float64 `json:\"timestamp\"`\n\tLineItems []struct {\n\t\tName string `json:\"name\"`\n\t\tCost float64 `json:\"cost\"`\n\t}\n}\n\ntype OpenAICreditGrants struct {\n\tObject string `json:\"object\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n\tTotalAvailable float64 `json:\"total_available\"`\n}\n\ntype OpenAIUsageResponse struct {\n\tObject string `json:\"object\"`\n\t//DailyCosts []OpenAIUsageDailyCost `json:\"daily_costs\"`\n\tTotalUsage float64 `json:\"total_usage\"` // unit: 0.01 dollar\n}\n\ntype OpenAISBUsageResponse struct {\n\tMsg string `json:\"msg\"`\n\tData *struct {\n\t\tCredit string `json:\"credit\"`\n\t} `json:\"data\"`\n}\n\ntype AIProxyUserOverviewResponse struct {\n\tSuccess bool `json:\"success\"`\n\tMessage string `json:\"message\"`\n\tErrorCode int `json:\"error_code\"`\n\tData struct {\n\t\tTotalPoints float64 `json:\"totalPoints\"`\n\t} `json:\"data\"`\n}\n\ntype API2GPTUsageResponse struct {\n\tObject string `json:\"object\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n\tTotalRemaining float64 `json:\"total_remaining\"`\n}\n\ntype APGC2DGPTUsageResponse struct {\n\t//Grants interface{} `json:\"grants\"`\n\tObject string `json:\"object\"`\n\tTotalAvailable float64 `json:\"total_available\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n}\n\ntype SiliconFlowUsageResponse struct {\n\tCode int `json:\"code\"`\n\tMessage string `json:\"message\"`\n\tStatus bool `json:\"status\"`\n\tData struct {\n\t\tID string `json:\"id\"`\n\t\tName string `json:\"name\"`\n\t\tImage string `json:\"image\"`\n\t\tEmail string `json:\"email\"`\n\t\tIsAdmin bool `json:\"isAdmin\"`\n\t\tBalance string `json:\"balance\"`\n\t\tStatus string `json:\"status\"`\n\t\tIntroduction string `json:\"introduction\"`\n\t\tRole string `json:\"role\"`\n\t\tChargeBalance string `json:\"chargeBalance\"`\n\t\tTotalBalance string `json:\"totalBalance\"`\n\t\tCategory string `json:\"category\"`\n\t} `json:\"data\"`\n}\n\ntype DeepSeekUsageResponse struct {\n\tIsAvailable bool `json:\"is_available\"`\n\tBalanceInfos []struct {\n\t\tCurrency string `json:\"currency\"`\n\t\tTotalBalance string `json:\"total_balance\"`\n\t\tGrantedBalance string `json:\"granted_balance\"`\n\t\tToppedUpBalance string `json:\"topped_up_balance\"`\n\t} `json:\"balance_infos\"`\n}\n\n// GetAuthHeader get auth header\nfunc GetAuthHeader(token string) http.Header {\n\th := http.Header{}\n\th.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\treturn h\n}\n\nfunc GetResponseBody(method, url string, channel *model.Channel, headers http.Header) ([]byte, error) {\n\treq, err := http.NewRequest(method, url, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor k := range headers {\n\t\treq.Header.Add(k, headers.Get(k))\n\t}\n\tres, err := service.GetHttpClient().Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif res.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"status code: %d\", res.StatusCode)\n\t}\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn body, nil\n}\n\nfunc updateChannelCloseAIBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"%s/dashboard/billing/credit_grants\", channel.GetBaseURL())\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAICreditGrants{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalAvailable)\n\treturn response.TotalAvailable, nil\n}\n\nfunc updateChannelOpenAISBBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"https://api.openai-sb.com/sb-api/user/status?api_key=%s\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAISBUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif response.Data == nil {\n\t\treturn 0, errors.New(response.Msg)\n\t}\n\tbalance, err := strconv.ParseFloat(response.Data.Credit, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelAIProxyBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://aiproxy.io/api/report/getUserOverview\"\n\theaders := http.Header{}\n\theaders.Add(\"Api-Key\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, headers)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := AIProxyUserOverviewResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif !response.Success {\n\t\treturn 0, fmt.Errorf(\"code: %d, message: %s\", response.ErrorCode, response.Message)\n\t}\n\tchannel.UpdateBalance(response.Data.TotalPoints)\n\treturn response.Data.TotalPoints, nil\n}\n\nfunc updateChannelAPI2GPTBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.api2gpt.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := API2GPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalRemaining)\n\treturn response.TotalRemaining, nil\n}\n\nfunc updateChannelSiliconFlowBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.siliconflow.cn/v1/user/info\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := SiliconFlowUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif response.Code != 20000 {\n\t\treturn 0, fmt.Errorf(\"code: %d, message: %s\", response.Code, response.Message)\n\t}\n\tbalance, err := strconv.ParseFloat(response.Data.TotalBalance, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelDeepSeekBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.deepseek.com/user/balance\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := DeepSeekUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tindex := -1\n\tfor i, balanceInfo := range response.BalanceInfos {\n\t\tif balanceInfo.Currency == \"CNY\" {\n\t\t\tindex = i\n\t\t\tbreak\n\t\t}\n\t}\n\tif index == -1 {\n\t\treturn 0, errors.New(\"currency CNY not found\")\n\t}\n\tbalance, err := strconv.ParseFloat(response.BalanceInfos[index].TotalBalance, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelAIGC2DBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.aigc2d.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := APGC2DGPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalAvailable)\n\treturn response.TotalAvailable, nil\n}\n\nfunc updateChannelBalance(channel *model.Channel) (float64, error) {\n\tbaseURL := common.ChannelBaseURLs[channel.Type]\n\tif channel.GetBaseURL() == \"\" {\n\t\tchannel.BaseURL = &baseURL\n\t}\n\tswitch channel.Type {\n\tcase common.ChannelTypeOpenAI:\n\t\tif channel.GetBaseURL() != \"\" {\n\t\t\tbaseURL = channel.GetBaseURL()\n\t\t}\n\tcase common.ChannelTypeAzure:\n\t\treturn 0, errors.New(\"尚未实现\")\n\tcase common.ChannelTypeCustom:\n\t\tbaseURL = channel.GetBaseURL()\n\t//case common.ChannelTypeOpenAISB:\n\t//\treturn updateChannelOpenAISBBalance(channel)\n\tcase common.ChannelTypeAIProxy:\n\t\treturn updateChannelAIProxyBalance(channel)\n\tcase common.ChannelTypeAPI2GPT:\n\t\treturn updateChannelAPI2GPTBalance(channel)\n\tcase common.ChannelTypeAIGC2D:\n\t\treturn updateChannelAIGC2DBalance(channel)\n\tcase common.ChannelTypeSiliconFlow:\n\t\treturn updateChannelSiliconFlowBalance(channel)\n\tcase common.ChannelTypeDeepSeek:\n\t\treturn updateChannelDeepSeekBalance(channel)\n\tdefault:\n\t\treturn 0, errors.New(\"尚未实现\")\n\t}\n\turl := fmt.Sprintf(\"%s/v1/dashboard/billing/subscription\", baseURL)\n\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tsubscription := OpenAISubscriptionResponse{}\n\terr = json.Unmarshal(body, &subscription)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tnow := time.Now()\n\tstartDate := fmt.Sprintf(\"%s-01\", now.Format(\"2006-01\"))\n\tendDate := now.Format(\"2006-01-02\")\n\tif !subscription.HasPaymentMethod {\n\t\tstartDate = now.AddDate(0, 0, -100).Format(\"2006-01-02\")\n\t}\n\turl = fmt.Sprintf(\"%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s\", baseURL, startDate, endDate)\n\tbody, err = GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tusage := OpenAIUsageResponse{}\n\terr = json.Unmarshal(body, &usage)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbalance := subscription.HardLimitUSD - usage.TotalUsage/100\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc UpdateChannelBalance(c *gin.Context) {\n\tid, err := strconv.Atoi(c.Param(\"id\"))\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tchannel, err := model.GetChannelById(id, true)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tbalance, err := updateChannelBalance(channel)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t\t\"balance\": balance,\n\t})\n\treturn\n}\n\nfunc updateAllChannelsBalance() error {\n\tchannels, err := model.GetAllChannels(0, 0, true, false)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, channel := range channels {\n\t\tif channel.Status != common.ChannelStatusEnabled {\n\t\t\tcontinue\n\t\t}\n\t\t// TODO: support Azure\n\t\t//if channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {\n\t\t//\tcontinue\n\t\t//}\n\t\tbalance, err := updateChannelBalance(channel)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t} else {\n\t\t\t// err is nil & balance <= 0 means quota is used up\n\t\t\tif balance <= 0 {\n\t\t\t\tservice.DisableChannel(channel.Id, channel.Name, \"余额不足\")\n\t\t\t}\n\t\t}\n\t\ttime.Sleep(common.RequestInterval)\n\t}\n\treturn nil\n}\n\nfunc UpdateAllChannelsBalance(c *gin.Context) {\n\t// TODO: make it async\n\terr := updateAllChannelsBalance()\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t})\n\treturn\n}\n\nfunc AutomaticallyUpdateChannels(frequency int) {\n\tfor {\n\t\ttime.Sleep(time.Duration(frequency) * time.Minute)\n\t\tcommon.SysLog(\"updating all channels\")\n\t\t_ = updateAllChannelsBalance()\n\t\tcommon.SysLog(\"channels update done\")\n\t}\n}\n"}, "ffc8c43a562624a9e65a8d2e7ea7c9ebf1626d76": {"t": 1785759385.434503, "n": 12191, "h": "e1585bb15b163657", "c": "// Copyright (c) 2025 Tethys Plex\n//\n// This file is part of Veloera.\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see <https://www.gnu.org/licenses/>.\npackage controller\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\t\"veloera/common\"\n\t\"veloera/model\"\n\t\"veloera/service\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// https://github.com/songquanpeng/veloera/issues/79\n\ntype OpenAISubscriptionResponse struct {\n\tObject string `json:\"object\"`\n\tHasPaymentMethod bool `json:\"has_payment_method\"`\n\tSoftLimitUSD float64 `json:\"soft_limit_usd\"`\n\tHardLimitUSD float64 `json:\"hard_limit_usd\"`\n\tSystemHardLimitUSD float64 `json:\"system_hard_limit_usd\"`\n\tAccessUntil int64 `json:\"access_until\"`\n}\n\ntype OpenAIUsageDailyCost struct {\n\tTimestamp float64 `json:\"timestamp\"`\n\tLineItems []struct {\n\t\tName string `json:\"name\"`\n\t\tCost float64 `json:\"cost\"`\n\t}\n}\n\ntype OpenAICreditGrants struct {\n\tObject string `json:\"object\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n\tTotalAvailable float64 `json:\"total_available\"`\n}\n\ntype OpenAIUsageResponse struct {\n\tObject string `json:\"object\"`\n\t//DailyCosts []OpenAIUsageDailyCost `json:\"daily_costs\"`\n\tTotalUsage float64 `json:\"total_usage\"` // unit: 0.01 dollar\n}\n\ntype OpenAISBUsageResponse struct {\n\tMsg string `json:\"msg\"`\n\tData *struct {\n\t\tCredit string `json:\"credit\"`\n\t} `json:\"data\"`\n}\n\ntype AIProxyUserOverviewResponse struct {\n\tSuccess bool `json:\"success\"`\n\tMessage string `json:\"message\"`\n\tErrorCode int `json:\"error_code\"`\n\tData struct {\n\t\tTotalPoints float64 `json:\"totalPoints\"`\n\t} `json:\"data\"`\n}\n\ntype API2GPTUsageResponse struct {\n\tObject string `json:\"object\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n\tTotalRemaining float64 `json:\"total_remaining\"`\n}\n\ntype APGC2DGPTUsageResponse struct {\n\t//Grants interface{} `json:\"grants\"`\n\tObject string `json:\"object\"`\n\tTotalAvailable float64 `json:\"total_available\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n}\n\ntype SiliconFlowUsageResponse struct {\n\tCode int `json:\"code\"`\n\tMessage string `json:\"message\"`\n\tStatus bool `json:\"status\"`\n\tData struct {\n\t\tID string `json:\"id\"`\n\t\tName string `json:\"name\"`\n\t\tImage string `json:\"image\"`\n\t\tEmail string `json:\"email\"`\n\t\tIsAdmin bool `json:\"isAdmin\"`\n\t\tBalance string `json:\"balance\"`\n\t\tStatus string `json:\"status\"`\n\t\tIntroduction string `json:\"introduction\"`\n\t\tRole string `json:\"role\"`\n\t\tChargeBalance string `json:\"chargeBalance\"`\n\t\tTotalBalance string `json:\"totalBalance\"`\n\t\tCategory string `json:\"category\"`\n\t} `json:\"data\"`\n}\n\ntype DeepSeekUsageResponse struct {\n\tIsAvailable bool `json:\"is_available\"`\n\tBalanceInfos []struct {\n\t\tCurrency string `json:\"currency\"`\n\t\tTotalBalance string `json:\"total_balance\"`\n\t\tGrantedBalance string `json:\"granted_balance\"`\n\t\tToppedUpBalance string `json:\"topped_up_balance\"`\n\t} `json:\"balance_infos\"`\n}\n\n// GetAuthHeader get auth header\nfunc GetAuthHeader(token string) http.Header {\n\th := http.Header{}\n\th.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\treturn h\n}\n\nfunc GetResponseBody(method, url string, channel *model.Channel, headers http.Header) ([]byte, error) {\n\treq, err := http.NewRequest(method, url, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor k := range headers {\n\t\treq.Header.Add(k, headers.Get(k))\n\t}\n\tres, err := service.GetHttpClient().Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif res.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"status code: %d\", res.StatusCode)\n\t}\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn body, nil\n}\n\nfunc updateChannelCloseAIBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"%s/dashboard/billing/credit_grants\", channel.GetBaseURL())\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAICreditGrants{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalAvailable)\n\treturn response.TotalAvailable, nil\n}\n\nfunc updateChannelOpenAISBBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"https://api.openai-sb.com/sb-api/user/status?api_key=%s\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAISBUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif response.Data == nil {\n\t\treturn 0, errors.New(response.Msg)\n\t}\n\tbalance, err := strconv.ParseFloat(response.Data.Credit, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelAIProxyBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://aiproxy.io/api/report/getUserOverview\"\n\theaders := http.Header{}\n\theaders.Add(\"Api-Key\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, headers)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := AIProxyUserOverviewResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif !response.Success {\n\t\treturn 0, fmt.Errorf(\"code: %d, message: %s\", response.ErrorCode, response.Message)\n\t}\n\tchannel.UpdateBalance(response.Data.TotalPoints)\n\treturn response.Data.TotalPoints, nil\n}\n\nfunc updateChannelAPI2GPTBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.api2gpt.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := API2GPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalRemaining)\n\treturn response.TotalRemaining, nil\n}\n\nfunc updateChannelSiliconFlowBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.siliconflow.cn/v1/user/info\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := SiliconFlowUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif response.Code != 20000 {\n\t\treturn 0, fmt.Errorf(\"code: %d, message: %s\", response.Code, response.Message)\n\t}\n\tbalance, err := strconv.ParseFloat(response.Data.TotalBalance, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelDeepSeekBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.deepseek.com/user/balance\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := DeepSeekUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tindex := -1\n\tfor i, balanceInfo := range response.BalanceInfos {\n\t\tif balanceInfo.Currency == \"CNY\" {\n\t\t\tindex = i\n\t\t\tbreak\n\t\t}\n\t}\n\tif index == -1 {\n\t\treturn 0, errors.New(\"currency CNY not found\")\n\t}\n\tbalance, err := strconv.ParseFloat(response.BalanceInfos[index].TotalBalance, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelAIGC2DBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.aigc2d.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := APGC2DGPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalAvailable)\n\treturn response.TotalAvailable, nil\n}\n\nfunc updateChannelBalance(channel *model.Channel) (float64, error) {\n\tbaseURL := common.ChannelBaseURLs[channel.Type]\n\tif channel.GetBaseURL() == \"\" {\n\t\tchannel.BaseURL = &baseURL\n\t}\n\tswitch channel.Type {\n\tcase common.ChannelTypeOpenAI:\n\t\tif channel.GetBaseURL() != \"\" {\n\t\t\tbaseURL = channel.GetBaseURL()\n\t\t}\n\tcase common.ChannelTypeAzure:\n\t\treturn 0, errors.New(\"尚未实现\")\n\tcase common.ChannelTypeCustom:\n\t\tbaseURL = channel.GetBaseURL()\n\t//case common.ChannelTypeOpenAISB:\n\t//\treturn updateChannelOpenAISBBalance(channel)\n\tcase common.ChannelTypeAIProxy:\n\t\treturn updateChannelAIProxyBalance(channel)\n\tcase common.ChannelTypeAPI2GPT:\n\t\treturn updateChannelAPI2GPTBalance(channel)\n\tcase common.ChannelTypeAIGC2D:\n\t\treturn updateChannelAIGC2DBalance(channel)\n\tcase common.ChannelTypeSiliconFlow:\n\t\treturn updateChannelSiliconFlowBalance(channel)\n\tcase common.ChannelTypeDeepSeek:\n\t\treturn updateChannelDeepSeekBalance(channel)\n\tdefault:\n\t\treturn 0, errors.New(\"尚未实现\")\n\t}\n\turl := fmt.Sprintf(\"%s/v1/dashboard/billing/subscription\", baseURL)\n\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tsubscription := OpenAISubscriptionResponse{}\n\terr = json.Unmarshal(body, &subscription)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tnow := time.Now()\n\tstartDate := fmt.Sprintf(\"%s-01\", now.Format(\"2006-01\"))\n\tendDate := now.Format(\"2006-01-02\")\n\tif !subscription.HasPaymentMethod {\n\t\tstartDate = now.AddDate(0, 0, -100).Format(\"2006-01-02\")\n\t}\n\turl = fmt.Sprintf(\"%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s\", baseURL, startDate, endDate)\n\tbody, err = GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tusage := OpenAIUsageResponse{}\n\terr = json.Unmarshal(body, &usage)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbalance := subscription.HardLimitUSD - usage.TotalUsage/100\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc UpdateChannelBalance(c *gin.Context) {\n\tid, err := strconv.Atoi(c.Param(\"id\"))\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tchannel, err := model.GetChannelById(id, true)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tbalance, err := updateChannelBalance(channel)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t\t\"balance\": balance,\n\t})\n\treturn\n}\n\nfunc updateAllChannelsBalance() error {\n\tchannels, err := model.GetAllChannels(0, 0, true, false)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, channel := range channels {\n\t\tif channel.Status != common.ChannelStatusEnabled {\n\t\t\tcontinue\n\t\t}\n\t\t// TODO: support Azure\n\t\t//if channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {\n\t\t//\tcontinue\n\t\t//}\n\t\tbalance, err := updateChannelBalance(channel)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t} else {\n\t\t\t// err is nil & balance <= 0 means quota is used up\n\t\t\tif balance <= 0 {\n\t\t\t\tservice.DisableChannel(channel.Id, channel.Name, \"余额不足\")\n\t\t\t}\n\t\t}\n\t\ttime.Sleep(common.RequestInterval)\n\t}\n\treturn nil\n}\n\nfunc UpdateAllChannelsBalance(c *gin.Context) {\n\t// TODO: make it async\n\terr := updateAllChannelsBalance()\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t})\n\treturn\n}\n\nfunc AutomaticallyUpdateChannels(frequency int) {\n\tfor {\n\t\ttime.Sleep(time.Duration(frequency) * time.Minute)\n\t\tcommon.SysLog(\"updating all channels\")\n\t\t_ = updateAllChannelsBalance()\n\t\tcommon.SysLog(\"channels update done\")\n\t}\n}\n"}, "abdb966a8b2a3b1be3607cbc023276a2dc33df01": {"t": 1785759387.2331746, "n": 11473, "h": "caee779c75516e48", "c": "package controller\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\t\"veloera/common\"\n\t\"veloera/model\"\n\t\"veloera/service\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// https://github.com/songquanpeng/veloera/issues/79\n\ntype OpenAISubscriptionResponse struct {\n\tObject string `json:\"object\"`\n\tHasPaymentMethod bool `json:\"has_payment_method\"`\n\tSoftLimitUSD float64 `json:\"soft_limit_usd\"`\n\tHardLimitUSD float64 `json:\"hard_limit_usd\"`\n\tSystemHardLimitUSD float64 `json:\"system_hard_limit_usd\"`\n\tAccessUntil int64 `json:\"access_until\"`\n}\n\ntype OpenAIUsageDailyCost struct {\n\tTimestamp float64 `json:\"timestamp\"`\n\tLineItems []struct {\n\t\tName string `json:\"name\"`\n\t\tCost float64 `json:\"cost\"`\n\t}\n}\n\ntype OpenAICreditGrants struct {\n\tObject string `json:\"object\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n\tTotalAvailable float64 `json:\"total_available\"`\n}\n\ntype OpenAIUsageResponse struct {\n\tObject string `json:\"object\"`\n\t//DailyCosts []OpenAIUsageDailyCost `json:\"daily_costs\"`\n\tTotalUsage float64 `json:\"total_usage\"` // unit: 0.01 dollar\n}\n\ntype OpenAISBUsageResponse struct {\n\tMsg string `json:\"msg\"`\n\tData *struct {\n\t\tCredit string `json:\"credit\"`\n\t} `json:\"data\"`\n}\n\ntype AIProxyUserOverviewResponse struct {\n\tSuccess bool `json:\"success\"`\n\tMessage string `json:\"message\"`\n\tErrorCode int `json:\"error_code\"`\n\tData struct {\n\t\tTotalPoints float64 `json:\"totalPoints\"`\n\t} `json:\"data\"`\n}\n\ntype API2GPTUsageResponse struct {\n\tObject string `json:\"object\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n\tTotalRemaining float64 `json:\"total_remaining\"`\n}\n\ntype APGC2DGPTUsageResponse struct {\n\t//Grants interface{} `json:\"grants\"`\n\tObject string `json:\"object\"`\n\tTotalAvailable float64 `json:\"total_available\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n}\n\ntype SiliconFlowUsageResponse struct {\n\tCode int `json:\"code\"`\n\tMessage string `json:\"message\"`\n\tStatus bool `json:\"status\"`\n\tData struct {\n\t\tID string `json:\"id\"`\n\t\tName string `json:\"name\"`\n\t\tImage string `json:\"image\"`\n\t\tEmail string `json:\"email\"`\n\t\tIsAdmin bool `json:\"isAdmin\"`\n\t\tBalance string `json:\"balance\"`\n\t\tStatus string `json:\"status\"`\n\t\tIntroduction string `json:\"introduction\"`\n\t\tRole string `json:\"role\"`\n\t\tChargeBalance string `json:\"chargeBalance\"`\n\t\tTotalBalance string `json:\"totalBalance\"`\n\t\tCategory string `json:\"category\"`\n\t} `json:\"data\"`\n}\n\ntype DeepSeekUsageResponse struct {\n\tIsAvailable bool `json:\"is_available\"`\n\tBalanceInfos []struct {\n\t\tCurrency string `json:\"currency\"`\n\t\tTotalBalance string `json:\"total_balance\"`\n\t\tGrantedBalance string `json:\"granted_balance\"`\n\t\tToppedUpBalance string `json:\"topped_up_balance\"`\n\t} `json:\"balance_infos\"`\n}\n\n// GetAuthHeader get auth header\nfunc GetAuthHeader(token string) http.Header {\n\th := http.Header{}\n\th.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\treturn h\n}\n\nfunc GetResponseBody(method, url string, channel *model.Channel, headers http.Header) ([]byte, error) {\n\treq, err := http.NewRequest(method, url, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor k := range headers {\n\t\treq.Header.Add(k, headers.Get(k))\n\t}\n\tres, err := service.GetHttpClient().Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif res.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"status code: %d\", res.StatusCode)\n\t}\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn body, nil\n}\n\nfunc updateChannelCloseAIBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"%s/dashboard/billing/credit_grants\", channel.GetBaseURL())\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAICreditGrants{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalAvailable)\n\treturn response.TotalAvailable, nil\n}\n\nfunc updateChannelOpenAISBBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"https://api.openai-sb.com/sb-api/user/status?api_key=%s\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAISBUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif response.Data == nil {\n\t\treturn 0, errors.New(response.Msg)\n\t}\n\tbalance, err := strconv.ParseFloat(response.Data.Credit, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelAIProxyBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://aiproxy.io/api/report/getUserOverview\"\n\theaders := http.Header{}\n\theaders.Add(\"Api-Key\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, headers)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := AIProxyUserOverviewResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif !response.Success {\n\t\treturn 0, fmt.Errorf(\"code: %d, message: %s\", response.ErrorCode, response.Message)\n\t}\n\tchannel.UpdateBalance(response.Data.TotalPoints)\n\treturn response.Data.TotalPoints, nil\n}\n\nfunc updateChannelAPI2GPTBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.api2gpt.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := API2GPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalRemaining)\n\treturn response.TotalRemaining, nil\n}\n\nfunc updateChannelSiliconFlowBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.siliconflow.cn/v1/user/info\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := SiliconFlowUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif response.Code != 20000 {\n\t\treturn 0, fmt.Errorf(\"code: %d, message: %s\", response.Code, response.Message)\n\t}\n\tbalance, err := strconv.ParseFloat(response.Data.TotalBalance, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelDeepSeekBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.deepseek.com/user/balance\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := DeepSeekUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tindex := -1\n\tfor i, balanceInfo := range response.BalanceInfos {\n\t\tif balanceInfo.Currency == \"CNY\" {\n\t\t\tindex = i\n\t\t\tbreak\n\t\t}\n\t}\n\tif index == -1 {\n\t\treturn 0, errors.New(\"currency CNY not found\")\n\t}\n\tbalance, err := strconv.ParseFloat(response.BalanceInfos[index].TotalBalance, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelAIGC2DBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.aigc2d.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := APGC2DGPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalAvailable)\n\treturn response.TotalAvailable, nil\n}\n\nfunc updateChannelBalance(channel *model.Channel) (float64, error) {\n\tbaseURL := common.ChannelBaseURLs[channel.Type]\n\tif channel.GetBaseURL() == \"\" {\n\t\tchannel.BaseURL = &baseURL\n\t}\n\tswitch channel.Type {\n\tcase common.ChannelTypeOpenAI:\n\t\tif channel.GetBaseURL() != \"\" {\n\t\t\tbaseURL = channel.GetBaseURL()\n\t\t}\n\tcase common.ChannelTypeAzure:\n\t\treturn 0, errors.New(\"尚未实现\")\n\tcase common.ChannelTypeCustom:\n\t\tbaseURL = channel.GetBaseURL()\n\t//case common.ChannelTypeOpenAISB:\n\t//\treturn updateChannelOpenAISBBalance(channel)\n\tcase common.ChannelTypeAIProxy:\n\t\treturn updateChannelAIProxyBalance(channel)\n\tcase common.ChannelTypeAPI2GPT:\n\t\treturn updateChannelAPI2GPTBalance(channel)\n\tcase common.ChannelTypeAIGC2D:\n\t\treturn updateChannelAIGC2DBalance(channel)\n\tcase common.ChannelTypeSiliconFlow:\n\t\treturn updateChannelSiliconFlowBalance(channel)\n\tcase common.ChannelTypeDeepSeek:\n\t\treturn updateChannelDeepSeekBalance(channel)\n\tdefault:\n\t\treturn 0, errors.New(\"尚未实现\")\n\t}\n\turl := fmt.Sprintf(\"%s/v1/dashboard/billing/subscription\", baseURL)\n\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tsubscription := OpenAISubscriptionResponse{}\n\terr = json.Unmarshal(body, &subscription)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tnow := time.Now()\n\tstartDate := fmt.Sprintf(\"%s-01\", now.Format(\"2006-01\"))\n\tendDate := now.Format(\"2006-01-02\")\n\tif !subscription.HasPaymentMethod {\n\t\tstartDate = now.AddDate(0, 0, -100).Format(\"2006-01-02\")\n\t}\n\turl = fmt.Sprintf(\"%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s\", baseURL, startDate, endDate)\n\tbody, err = GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tusage := OpenAIUsageResponse{}\n\terr = json.Unmarshal(body, &usage)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbalance := subscription.HardLimitUSD - usage.TotalUsage/100\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc UpdateChannelBalance(c *gin.Context) {\n\tid, err := strconv.Atoi(c.Param(\"id\"))\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tchannel, err := model.GetChannelById(id, true)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tbalance, err := updateChannelBalance(channel)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t\t\"balance\": balance,\n\t})\n\treturn\n}\n\nfunc updateAllChannelsBalance() error {\n\tchannels, err := model.GetAllChannels(0, 0, true, false)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, channel := range channels {\n\t\tif channel.Status != common.ChannelStatusEnabled {\n\t\t\tcontinue\n\t\t}\n\t\t// TODO: support Azure\n\t\t//if channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {\n\t\t//\tcontinue\n\t\t//}\n\t\tbalance, err := updateChannelBalance(channel)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t} else {\n\t\t\t// err is nil & balance <= 0 means quota is used up\n\t\t\tif balance <= 0 {\n\t\t\t\tservice.DisableChannel(channel.Id, channel.Name, \"余额不足\")\n\t\t\t}\n\t\t}\n\t\ttime.Sleep(common.RequestInterval)\n\t}\n\treturn nil\n}\n\nfunc UpdateAllChannelsBalance(c *gin.Context) {\n\t// TODO: make it async\n\terr := updateAllChannelsBalance()\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t})\n\treturn\n}\n\nfunc AutomaticallyUpdateChannels(frequency int) {\n\tfor {\n\t\ttime.Sleep(time.Duration(frequency) * time.Minute)\n\t\tcommon.SysLog(\"updating all channels\")\n\t\t_ = updateAllChannelsBalance()\n\t\tcommon.SysLog(\"channels update done\")\n\t}\n}\n"}, "5df1fc6a516a87ed44810a286c964fa2ce2106a5": {"t": 1785759388.5851626, "n": 11473, "h": "064e6666a0aed435", "c": "package controller\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"veloera/common\"\n\t\"veloera/model\"\n\t\"veloera/service\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// https://github.com/songquanpeng/veloera/issues/79\n\ntype OpenAISubscriptionResponse struct {\n\tObject string `json:\"object\"`\n\tHasPaymentMethod bool `json:\"has_payment_method\"`\n\tSoftLimitUSD float64 `json:\"soft_limit_usd\"`\n\tHardLimitUSD float64 `json:\"hard_limit_usd\"`\n\tSystemHardLimitUSD float64 `json:\"system_hard_limit_usd\"`\n\tAccessUntil int64 `json:\"access_until\"`\n}\n\ntype OpenAIUsageDailyCost struct {\n\tTimestamp float64 `json:\"timestamp\"`\n\tLineItems []struct {\n\t\tName string `json:\"name\"`\n\t\tCost float64 `json:\"cost\"`\n\t}\n}\n\ntype OpenAICreditGrants struct {\n\tObject string `json:\"object\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n\tTotalAvailable float64 `json:\"total_available\"`\n}\n\ntype OpenAIUsageResponse struct {\n\tObject string `json:\"object\"`\n\t//DailyCosts []OpenAIUsageDailyCost `json:\"daily_costs\"`\n\tTotalUsage float64 `json:\"total_usage\"` // unit: 0.01 dollar\n}\n\ntype OpenAISBUsageResponse struct {\n\tMsg string `json:\"msg\"`\n\tData *struct {\n\t\tCredit string `json:\"credit\"`\n\t} `json:\"data\"`\n}\n\ntype AIProxyUserOverviewResponse struct {\n\tSuccess bool `json:\"success\"`\n\tMessage string `json:\"message\"`\n\tErrorCode int `json:\"error_code\"`\n\tData struct {\n\t\tTotalPoints float64 `json:\"totalPoints\"`\n\t} `json:\"data\"`\n}\n\ntype API2GPTUsageResponse struct {\n\tObject string `json:\"object\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n\tTotalRemaining float64 `json:\"total_remaining\"`\n}\n\ntype APGC2DGPTUsageResponse struct {\n\t//Grants interface{} `json:\"grants\"`\n\tObject string `json:\"object\"`\n\tTotalAvailable float64 `json:\"total_available\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n}\n\ntype SiliconFlowUsageResponse struct {\n\tCode int `json:\"code\"`\n\tMessage string `json:\"message\"`\n\tStatus bool `json:\"status\"`\n\tData struct {\n\t\tID string `json:\"id\"`\n\t\tName string `json:\"name\"`\n\t\tImage string `json:\"image\"`\n\t\tEmail string `json:\"email\"`\n\t\tIsAdmin bool `json:\"isAdmin\"`\n\t\tBalance string `json:\"balance\"`\n\t\tStatus string `json:\"status\"`\n\t\tIntroduction string `json:\"introduction\"`\n\t\tRole string `json:\"role\"`\n\t\tChargeBalance string `json:\"chargeBalance\"`\n\t\tTotalBalance string `json:\"totalBalance\"`\n\t\tCategory string `json:\"category\"`\n\t} `json:\"data\"`\n}\n\ntype DeepSeekUsageResponse struct {\n\tIsAvailable bool `json:\"is_available\"`\n\tBalanceInfos []struct {\n\t\tCurrency string `json:\"currency\"`\n\t\tTotalBalance string `json:\"total_balance\"`\n\t\tGrantedBalance string `json:\"granted_balance\"`\n\t\tToppedUpBalance string `json:\"topped_up_balance\"`\n\t} `json:\"balance_infos\"`\n}\n\n// GetAuthHeader get auth header\nfunc GetAuthHeader(token string) http.Header {\n\th := http.Header{}\n\th.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\treturn h\n}\n\nfunc GetResponseBody(method, url string, channel *model.Channel, headers http.Header) ([]byte, error) {\n\treq, err := http.NewRequest(method, url, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor k := range headers {\n\t\treq.Header.Add(k, headers.Get(k))\n\t}\n\tres, err := service.GetHttpClient().Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif res.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"status code: %d\", res.StatusCode)\n\t}\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn body, nil\n}\n\nfunc updateChannelCloseAIBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"%s/dashboard/billing/credit_grants\", channel.GetBaseURL())\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAICreditGrants{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalAvailable)\n\treturn response.TotalAvailable, nil\n}\n\nfunc updateChannelOpenAISBBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"https://api.openai-sb.com/sb-api/user/status?api_key=%s\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAISBUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif response.Data == nil {\n\t\treturn 0, errors.New(response.Msg)\n\t}\n\tbalance, err := strconv.ParseFloat(response.Data.Credit, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelAIProxyBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://aiproxy.io/api/report/getUserOverview\"\n\theaders := http.Header{}\n\theaders.Add(\"Api-Key\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, headers)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := AIProxyUserOverviewResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif !response.Success {\n\t\treturn 0, fmt.Errorf(\"code: %d, message: %s\", response.ErrorCode, response.Message)\n\t}\n\tchannel.UpdateBalance(response.Data.TotalPoints)\n\treturn response.Data.TotalPoints, nil\n}\n\nfunc updateChannelAPI2GPTBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.api2gpt.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := API2GPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalRemaining)\n\treturn response.TotalRemaining, nil\n}\n\nfunc updateChannelSiliconFlowBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.siliconflow.cn/v1/user/info\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := SiliconFlowUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif response.Code != 20000 {\n\t\treturn 0, fmt.Errorf(\"code: %d, message: %s\", response.Code, response.Message)\n\t}\n\tbalance, err := strconv.ParseFloat(response.Data.TotalBalance, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelDeepSeekBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.deepseek.com/user/balance\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := DeepSeekUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tindex := -1\n\tfor i, balanceInfo := range response.BalanceInfos {\n\t\tif balanceInfo.Currency == \"CNY\" {\n\t\t\tindex = i\n\t\t\tbreak\n\t\t}\n\t}\n\tif index == -1 {\n\t\treturn 0, errors.New(\"currency CNY not found\")\n\t}\n\tbalance, err := strconv.ParseFloat(response.BalanceInfos[index].TotalBalance, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelAIGC2DBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.aigc2d.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := APGC2DGPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalAvailable)\n\treturn response.TotalAvailable, nil\n}\n\nfunc updateChannelBalance(channel *model.Channel) (float64, error) {\n\tbaseURL := common.ChannelBaseURLs[channel.Type]\n\tif channel.GetBaseURL() == \"\" {\n\t\tchannel.BaseURL = &baseURL\n\t}\n\tswitch channel.Type {\n\tcase common.ChannelTypeOpenAI:\n\t\tif channel.GetBaseURL() != \"\" {\n\t\t\tbaseURL = channel.GetBaseURL()\n\t\t}\n\tcase common.ChannelTypeAzure:\n\t\treturn 0, errors.New(\"尚未实现\")\n\tcase common.ChannelTypeCustom:\n\t\tbaseURL = channel.GetBaseURL()\n\t//case common.ChannelTypeOpenAISB:\n\t//\treturn updateChannelOpenAISBBalance(channel)\n\tcase common.ChannelTypeAIProxy:\n\t\treturn updateChannelAIProxyBalance(channel)\n\tcase common.ChannelTypeAPI2GPT:\n\t\treturn updateChannelAPI2GPTBalance(channel)\n\tcase common.ChannelTypeAIGC2D:\n\t\treturn updateChannelAIGC2DBalance(channel)\n\tcase common.ChannelTypeSiliconFlow:\n\t\treturn updateChannelSiliconFlowBalance(channel)\n\tcase common.ChannelTypeDeepSeek:\n\t\treturn updateChannelDeepSeekBalance(channel)\n\tdefault:\n\t\treturn 0, errors.New(\"尚未实现\")\n\t}\n\turl := fmt.Sprintf(\"%s/v1/dashboard/billing/subscription\", baseURL)\n\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tsubscription := OpenAISubscriptionResponse{}\n\terr = json.Unmarshal(body, &subscription)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tnow := time.Now()\n\tstartDate := fmt.Sprintf(\"%s-01\", now.Format(\"2006-01\"))\n\tendDate := now.Format(\"2006-01-02\")\n\tif !subscription.HasPaymentMethod {\n\t\tstartDate = now.AddDate(0, 0, -100).Format(\"2006-01-02\")\n\t}\n\turl = fmt.Sprintf(\"%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s\", baseURL, startDate, endDate)\n\tbody, err = GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tusage := OpenAIUsageResponse{}\n\terr = json.Unmarshal(body, &usage)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbalance := subscription.HardLimitUSD - usage.TotalUsage/100\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc UpdateChannelBalance(c *gin.Context) {\n\tid, err := strconv.Atoi(c.Param(\"id\"))\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tchannel, err := model.GetChannelById(id, true)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tbalance, err := updateChannelBalance(channel)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t\t\"balance\": balance,\n\t})\n\treturn\n}\n\nfunc updateAllChannelsBalance() error {\n\tchannels, err := model.GetAllChannels(0, 0, true, false)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, channel := range channels {\n\t\tif channel.Status != common.ChannelStatusEnabled {\n\t\t\tcontinue\n\t\t}\n\t\t// TODO: support Azure\n\t\t//if channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {\n\t\t//\tcontinue\n\t\t//}\n\t\tbalance, err := updateChannelBalance(channel)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t} else {\n\t\t\t// err is nil & balance <= 0 means quota is used up\n\t\t\tif balance <= 0 {\n\t\t\t\tservice.DisableChannel(channel.Id, channel.Name, \"余额不足\")\n\t\t\t}\n\t\t}\n\t\ttime.Sleep(common.RequestInterval)\n\t}\n\treturn nil\n}\n\nfunc UpdateAllChannelsBalance(c *gin.Context) {\n\t// TODO: make it async\n\terr := updateAllChannelsBalance()\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t})\n\treturn\n}\n\nfunc AutomaticallyUpdateChannels(frequency int) {\n\tfor {\n\t\ttime.Sleep(time.Duration(frequency) * time.Minute)\n\t\tcommon.SysLog(\"updating all channels\")\n\t\t_ = updateAllChannelsBalance()\n\t\tcommon.SysLog(\"channels update done\")\n\t}\n}\n"}, "fc33f2f0a53398a568f6d3d13131afeca1f1dea5": {"t": 1785759390.08394, "n": 11473, "h": "c4f71850dfaedc27", "c": "package controller\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"one-api/common\"\n\t\"one-api/model\"\n\t\"one-api/service\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// https://github.com/songquanpeng/one-api/issues/79\n\ntype OpenAISubscriptionResponse struct {\n\tObject string `json:\"object\"`\n\tHasPaymentMethod bool `json:\"has_payment_method\"`\n\tSoftLimitUSD float64 `json:\"soft_limit_usd\"`\n\tHardLimitUSD float64 `json:\"hard_limit_usd\"`\n\tSystemHardLimitUSD float64 `json:\"system_hard_limit_usd\"`\n\tAccessUntil int64 `json:\"access_until\"`\n}\n\ntype OpenAIUsageDailyCost struct {\n\tTimestamp float64 `json:\"timestamp\"`\n\tLineItems []struct {\n\t\tName string `json:\"name\"`\n\t\tCost float64 `json:\"cost\"`\n\t}\n}\n\ntype OpenAICreditGrants struct {\n\tObject string `json:\"object\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n\tTotalAvailable float64 `json:\"total_available\"`\n}\n\ntype OpenAIUsageResponse struct {\n\tObject string `json:\"object\"`\n\t//DailyCosts []OpenAIUsageDailyCost `json:\"daily_costs\"`\n\tTotalUsage float64 `json:\"total_usage\"` // unit: 0.01 dollar\n}\n\ntype OpenAISBUsageResponse struct {\n\tMsg string `json:\"msg\"`\n\tData *struct {\n\t\tCredit string `json:\"credit\"`\n\t} `json:\"data\"`\n}\n\ntype AIProxyUserOverviewResponse struct {\n\tSuccess bool `json:\"success\"`\n\tMessage string `json:\"message\"`\n\tErrorCode int `json:\"error_code\"`\n\tData struct {\n\t\tTotalPoints float64 `json:\"totalPoints\"`\n\t} `json:\"data\"`\n}\n\ntype API2GPTUsageResponse struct {\n\tObject string `json:\"object\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n\tTotalRemaining float64 `json:\"total_remaining\"`\n}\n\ntype APGC2DGPTUsageResponse struct {\n\t//Grants interface{} `json:\"grants\"`\n\tObject string `json:\"object\"`\n\tTotalAvailable float64 `json:\"total_available\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n}\n\ntype SiliconFlowUsageResponse struct {\n\tCode int `json:\"code\"`\n\tMessage string `json:\"message\"`\n\tStatus bool `json:\"status\"`\n\tData struct {\n\t\tID string `json:\"id\"`\n\t\tName string `json:\"name\"`\n\t\tImage string `json:\"image\"`\n\t\tEmail string `json:\"email\"`\n\t\tIsAdmin bool `json:\"isAdmin\"`\n\t\tBalance string `json:\"balance\"`\n\t\tStatus string `json:\"status\"`\n\t\tIntroduction string `json:\"introduction\"`\n\t\tRole string `json:\"role\"`\n\t\tChargeBalance string `json:\"chargeBalance\"`\n\t\tTotalBalance string `json:\"totalBalance\"`\n\t\tCategory string `json:\"category\"`\n\t} `json:\"data\"`\n}\n\ntype DeepSeekUsageResponse struct {\n\tIsAvailable bool `json:\"is_available\"`\n\tBalanceInfos []struct {\n\t\tCurrency string `json:\"currency\"`\n\t\tTotalBalance string `json:\"total_balance\"`\n\t\tGrantedBalance string `json:\"granted_balance\"`\n\t\tToppedUpBalance string `json:\"topped_up_balance\"`\n\t} `json:\"balance_infos\"`\n}\n\n// GetAuthHeader get auth header\nfunc GetAuthHeader(token string) http.Header {\n\th := http.Header{}\n\th.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\treturn h\n}\n\nfunc GetResponseBody(method, url string, channel *model.Channel, headers http.Header) ([]byte, error) {\n\treq, err := http.NewRequest(method, url, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor k := range headers {\n\t\treq.Header.Add(k, headers.Get(k))\n\t}\n\tres, err := service.GetHttpClient().Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif res.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"status code: %d\", res.StatusCode)\n\t}\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn body, nil\n}\n\nfunc updateChannelCloseAIBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"%s/dashboard/billing/credit_grants\", channel.GetBaseURL())\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAICreditGrants{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalAvailable)\n\treturn response.TotalAvailable, nil\n}\n\nfunc updateChannelOpenAISBBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"https://api.openai-sb.com/sb-api/user/status?api_key=%s\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAISBUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif response.Data == nil {\n\t\treturn 0, errors.New(response.Msg)\n\t}\n\tbalance, err := strconv.ParseFloat(response.Data.Credit, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelAIProxyBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://aiproxy.io/api/report/getUserOverview\"\n\theaders := http.Header{}\n\theaders.Add(\"Api-Key\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, headers)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := AIProxyUserOverviewResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif !response.Success {\n\t\treturn 0, fmt.Errorf(\"code: %d, message: %s\", response.ErrorCode, response.Message)\n\t}\n\tchannel.UpdateBalance(response.Data.TotalPoints)\n\treturn response.Data.TotalPoints, nil\n}\n\nfunc updateChannelAPI2GPTBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.api2gpt.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := API2GPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalRemaining)\n\treturn response.TotalRemaining, nil\n}\n\nfunc updateChannelSiliconFlowBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.siliconflow.cn/v1/user/info\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := SiliconFlowUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif response.Code != 20000 {\n\t\treturn 0, fmt.Errorf(\"code: %d, message: %s\", response.Code, response.Message)\n\t}\n\tbalance, err := strconv.ParseFloat(response.Data.TotalBalance, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelDeepSeekBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.deepseek.com/user/balance\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := DeepSeekUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tindex := -1\n\tfor i, balanceInfo := range response.BalanceInfos {\n\t\tif balanceInfo.Currency == \"CNY\" {\n\t\t\tindex = i\n\t\t\tbreak\n\t\t}\n\t}\n\tif index == -1 {\n\t\treturn 0, errors.New(\"currency CNY not found\")\n\t}\n\tbalance, err := strconv.ParseFloat(response.BalanceInfos[index].TotalBalance, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelAIGC2DBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.aigc2d.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := APGC2DGPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalAvailable)\n\treturn response.TotalAvailable, nil\n}\n\nfunc updateChannelBalance(channel *model.Channel) (float64, error) {\n\tbaseURL := common.ChannelBaseURLs[channel.Type]\n\tif channel.GetBaseURL() == \"\" {\n\t\tchannel.BaseURL = &baseURL\n\t}\n\tswitch channel.Type {\n\tcase common.ChannelTypeOpenAI:\n\t\tif channel.GetBaseURL() != \"\" {\n\t\t\tbaseURL = channel.GetBaseURL()\n\t\t}\n\tcase common.ChannelTypeAzure:\n\t\treturn 0, errors.New(\"尚未实现\")\n\tcase common.ChannelTypeCustom:\n\t\tbaseURL = channel.GetBaseURL()\n\t//case common.ChannelTypeOpenAISB:\n\t//\treturn updateChannelOpenAISBBalance(channel)\n\tcase common.ChannelTypeAIProxy:\n\t\treturn updateChannelAIProxyBalance(channel)\n\tcase common.ChannelTypeAPI2GPT:\n\t\treturn updateChannelAPI2GPTBalance(channel)\n\tcase common.ChannelTypeAIGC2D:\n\t\treturn updateChannelAIGC2DBalance(channel)\n\tcase common.ChannelTypeSiliconFlow:\n\t\treturn updateChannelSiliconFlowBalance(channel)\n\tcase common.ChannelTypeDeepSeek:\n\t\treturn updateChannelDeepSeekBalance(channel)\n\tdefault:\n\t\treturn 0, errors.New(\"尚未实现\")\n\t}\n\turl := fmt.Sprintf(\"%s/v1/dashboard/billing/subscription\", baseURL)\n\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tsubscription := OpenAISubscriptionResponse{}\n\terr = json.Unmarshal(body, &subscription)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tnow := time.Now()\n\tstartDate := fmt.Sprintf(\"%s-01\", now.Format(\"2006-01\"))\n\tendDate := now.Format(\"2006-01-02\")\n\tif !subscription.HasPaymentMethod {\n\t\tstartDate = now.AddDate(0, 0, -100).Format(\"2006-01-02\")\n\t}\n\turl = fmt.Sprintf(\"%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s\", baseURL, startDate, endDate)\n\tbody, err = GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tusage := OpenAIUsageResponse{}\n\terr = json.Unmarshal(body, &usage)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbalance := subscription.HardLimitUSD - usage.TotalUsage/100\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc UpdateChannelBalance(c *gin.Context) {\n\tid, err := strconv.Atoi(c.Param(\"id\"))\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tchannel, err := model.GetChannelById(id, true)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tbalance, err := updateChannelBalance(channel)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t\t\"balance\": balance,\n\t})\n\treturn\n}\n\nfunc updateAllChannelsBalance() error {\n\tchannels, err := model.GetAllChannels(0, 0, true, false)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, channel := range channels {\n\t\tif channel.Status != common.ChannelStatusEnabled {\n\t\t\tcontinue\n\t\t}\n\t\t// TODO: support Azure\n\t\t//if channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {\n\t\t//\tcontinue\n\t\t//}\n\t\tbalance, err := updateChannelBalance(channel)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t} else {\n\t\t\t// err is nil & balance <= 0 means quota is used up\n\t\t\tif balance <= 0 {\n\t\t\t\tservice.DisableChannel(channel.Id, channel.Name, \"余额不足\")\n\t\t\t}\n\t\t}\n\t\ttime.Sleep(common.RequestInterval)\n\t}\n\treturn nil\n}\n\nfunc UpdateAllChannelsBalance(c *gin.Context) {\n\t// TODO: make it async\n\terr := updateAllChannelsBalance()\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t})\n\treturn\n}\n\nfunc AutomaticallyUpdateChannels(frequency int) {\n\tfor {\n\t\ttime.Sleep(time.Duration(frequency) * time.Minute)\n\t\tcommon.SysLog(\"updating all channels\")\n\t\t_ = updateAllChannelsBalance()\n\t\tcommon.SysLog(\"channels update done\")\n\t}\n}\n"}, "37c0c8ebdd252e5c9dcd205200e416e758dd8dc2": {"t": 1785759391.4215996, "n": 8960, "h": "42c412014e1a82f1", "c": "package controller\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"one-api/common\"\n\t\"one-api/model\"\n\t\"one-api/service\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// https://github.com/songquanpeng/one-api/issues/79\n\ntype OpenAISubscriptionResponse struct {\n\tObject string `json:\"object\"`\n\tHasPaymentMethod bool `json:\"has_payment_method\"`\n\tSoftLimitUSD float64 `json:\"soft_limit_usd\"`\n\tHardLimitUSD float64 `json:\"hard_limit_usd\"`\n\tSystemHardLimitUSD float64 `json:\"system_hard_limit_usd\"`\n\tAccessUntil int64 `json:\"access_until\"`\n}\n\ntype OpenAIUsageDailyCost struct {\n\tTimestamp float64 `json:\"timestamp\"`\n\tLineItems []struct {\n\t\tName string `json:\"name\"`\n\t\tCost float64 `json:\"cost\"`\n\t}\n}\n\ntype OpenAICreditGrants struct {\n\tObject string `json:\"object\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n\tTotalAvailable float64 `json:\"total_available\"`\n}\n\ntype OpenAIUsageResponse struct {\n\tObject string `json:\"object\"`\n\t//DailyCosts []OpenAIUsageDailyCost `json:\"daily_costs\"`\n\tTotalUsage float64 `json:\"total_usage\"` // unit: 0.01 dollar\n}\n\ntype OpenAISBUsageResponse struct {\n\tMsg string `json:\"msg\"`\n\tData *struct {\n\t\tCredit string `json:\"credit\"`\n\t} `json:\"data\"`\n}\n\ntype AIProxyUserOverviewResponse struct {\n\tSuccess bool `json:\"success\"`\n\tMessage string `json:\"message\"`\n\tErrorCode int `json:\"error_code\"`\n\tData struct {\n\t\tTotalPoints float64 `json:\"totalPoints\"`\n\t} `json:\"data\"`\n}\n\ntype API2GPTUsageResponse struct {\n\tObject string `json:\"object\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n\tTotalRemaining float64 `json:\"total_remaining\"`\n}\n\ntype APGC2DGPTUsageResponse struct {\n\t//Grants interface{} `json:\"grants\"`\n\tObject string `json:\"object\"`\n\tTotalAvailable float64 `json:\"total_available\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n}\n\n// GetAuthHeader get auth header\nfunc GetAuthHeader(token string) http.Header {\n\th := http.Header{}\n\th.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\treturn h\n}\n\nfunc GetResponseBody(method, url string, channel *model.Channel, headers http.Header) ([]byte, error) {\n\treq, err := http.NewRequest(method, url, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor k := range headers {\n\t\treq.Header.Add(k, headers.Get(k))\n\t}\n\tres, err := service.GetHttpClient().Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif res.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"status code: %d\", res.StatusCode)\n\t}\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn body, nil\n}\n\nfunc updateChannelCloseAIBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"%s/dashboard/billing/credit_grants\", channel.GetBaseURL())\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAICreditGrants{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalAvailable)\n\treturn response.TotalAvailable, nil\n}\n\nfunc updateChannelOpenAISBBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"https://api.openai-sb.com/sb-api/user/status?api_key=%s\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAISBUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif response.Data == nil {\n\t\treturn 0, errors.New(response.Msg)\n\t}\n\tbalance, err := strconv.ParseFloat(response.Data.Credit, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelAIProxyBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://aiproxy.io/api/report/getUserOverview\"\n\theaders := http.Header{}\n\theaders.Add(\"Api-Key\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, headers)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := AIProxyUserOverviewResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif !response.Success {\n\t\treturn 0, fmt.Errorf(\"code: %d, message: %s\", response.ErrorCode, response.Message)\n\t}\n\tchannel.UpdateBalance(response.Data.TotalPoints)\n\treturn response.Data.TotalPoints, nil\n}\n\nfunc updateChannelAPI2GPTBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.api2gpt.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := API2GPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalRemaining)\n\treturn response.TotalRemaining, nil\n}\n\nfunc updateChannelAIGC2DBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.aigc2d.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := APGC2DGPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalAvailable)\n\treturn response.TotalAvailable, nil\n}\n\nfunc updateChannelBalance(channel *model.Channel) (float64, error) {\n\tbaseURL := common.ChannelBaseURLs[channel.Type]\n\tif channel.GetBaseURL() == \"\" {\n\t\tchannel.BaseURL = &baseURL\n\t}\n\tswitch channel.Type {\n\tcase common.ChannelTypeOpenAI:\n\t\tif channel.GetBaseURL() != \"\" {\n\t\t\tbaseURL = channel.GetBaseURL()\n\t\t}\n\tcase common.ChannelTypeAzure:\n\t\treturn 0, errors.New(\"尚未实现\")\n\tcase common.ChannelTypeCustom:\n\t\tbaseURL = channel.GetBaseURL()\n\t//case common.ChannelTypeOpenAISB:\n\t//\treturn updateChannelOpenAISBBalance(channel)\n\tcase common.ChannelTypeAIProxy:\n\t\treturn updateChannelAIProxyBalance(channel)\n\tcase common.ChannelTypeAPI2GPT:\n\t\treturn updateChannelAPI2GPTBalance(channel)\n\tcase common.ChannelTypeAIGC2D:\n\t\treturn updateChannelAIGC2DBalance(channel)\n\tdefault:\n\t\treturn 0, errors.New(\"尚未实现\")\n\t}\n\turl := fmt.Sprintf(\"%s/v1/dashboard/billing/subscription\", baseURL)\n\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tsubscription := OpenAISubscriptionResponse{}\n\terr = json.Unmarshal(body, &subscription)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tnow := time.Now()\n\tstartDate := fmt.Sprintf(\"%s-01\", now.Format(\"2006-01\"))\n\tendDate := now.Format(\"2006-01-02\")\n\tif !subscription.HasPaymentMethod {\n\t\tstartDate = now.AddDate(0, 0, -100).Format(\"2006-01-02\")\n\t}\n\turl = fmt.Sprintf(\"%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s\", baseURL, startDate, endDate)\n\tbody, err = GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tusage := OpenAIUsageResponse{}\n\terr = json.Unmarshal(body, &usage)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbalance := subscription.HardLimitUSD - usage.TotalUsage/100\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc UpdateChannelBalance(c *gin.Context) {\n\tid, err := strconv.Atoi(c.Param(\"id\"))\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tchannel, err := model.GetChannelById(id, true)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tbalance, err := updateChannelBalance(channel)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t\t\"balance\": balance,\n\t})\n\treturn\n}\n\nfunc updateAllChannelsBalance() error {\n\tchannels, err := model.GetAllChannels(0, 0, true, false)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, channel := range channels {\n\t\tif channel.Status != common.ChannelStatusEnabled {\n\t\t\tcontinue\n\t\t}\n\t\t// TODO: support Azure\n\t\tif channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {\n\t\t\tcontinue\n\t\t}\n\t\tbalance, err := updateChannelBalance(channel)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t} else {\n\t\t\t// err is nil & balance <= 0 means quota is used up\n\t\t\tif balance <= 0 {\n\t\t\t\tservice.DisableChannel(channel.Id, channel.Name, \"余额不足\")\n\t\t\t}\n\t\t}\n\t\ttime.Sleep(common.RequestInterval)\n\t}\n\treturn nil\n}\n\nfunc UpdateAllChannelsBalance(c *gin.Context) {\n\t// TODO: make it async\n\terr := updateAllChannelsBalance()\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t})\n\treturn\n}\n\nfunc AutomaticallyUpdateChannels(frequency int) {\n\tfor {\n\t\ttime.Sleep(time.Duration(frequency) * time.Minute)\n\t\tcommon.SysLog(\"updating all channels\")\n\t\t_ = updateAllChannelsBalance()\n\t\tcommon.SysLog(\"channels update done\")\n\t}\n}\n"}, "d53d3386e9fdcbffb5d9327a6dc1a0bcfcab162b": {"t": 1785759392.7067828, "n": 8956, "h": "51cbf86fc072358c", "c": "package controller\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"one-api/common\"\n\t\"one-api/model\"\n\t\"one-api/service\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// https://github.com/songquanpeng/one-api/issues/79\n\ntype OpenAISubscriptionResponse struct {\n\tObject string `json:\"object\"`\n\tHasPaymentMethod bool `json:\"has_payment_method\"`\n\tSoftLimitUSD float64 `json:\"soft_limit_usd\"`\n\tHardLimitUSD float64 `json:\"hard_limit_usd\"`\n\tSystemHardLimitUSD float64 `json:\"system_hard_limit_usd\"`\n\tAccessUntil int64 `json:\"access_until\"`\n}\n\ntype OpenAIUsageDailyCost struct {\n\tTimestamp float64 `json:\"timestamp\"`\n\tLineItems []struct {\n\t\tName string `json:\"name\"`\n\t\tCost float64 `json:\"cost\"`\n\t}\n}\n\ntype OpenAICreditGrants struct {\n\tObject string `json:\"object\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n\tTotalAvailable float64 `json:\"total_available\"`\n}\n\ntype OpenAIUsageResponse struct {\n\tObject string `json:\"object\"`\n\t//DailyCosts []OpenAIUsageDailyCost `json:\"daily_costs\"`\n\tTotalUsage float64 `json:\"total_usage\"` // unit: 0.01 dollar\n}\n\ntype OpenAISBUsageResponse struct {\n\tMsg string `json:\"msg\"`\n\tData *struct {\n\t\tCredit string `json:\"credit\"`\n\t} `json:\"data\"`\n}\n\ntype AIProxyUserOverviewResponse struct {\n\tSuccess bool `json:\"success\"`\n\tMessage string `json:\"message\"`\n\tErrorCode int `json:\"error_code\"`\n\tData struct {\n\t\tTotalPoints float64 `json:\"totalPoints\"`\n\t} `json:\"data\"`\n}\n\ntype API2GPTUsageResponse struct {\n\tObject string `json:\"object\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n\tTotalRemaining float64 `json:\"total_remaining\"`\n}\n\ntype APGC2DGPTUsageResponse struct {\n\t//Grants interface{} `json:\"grants\"`\n\tObject string `json:\"object\"`\n\tTotalAvailable float64 `json:\"total_available\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n}\n\n// GetAuthHeader get auth header\nfunc GetAuthHeader(token string) http.Header {\n\th := http.Header{}\n\th.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\treturn h\n}\n\nfunc GetResponseBody(method, url string, channel *model.Channel, headers http.Header) ([]byte, error) {\n\treq, err := http.NewRequest(method, url, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor k := range headers {\n\t\treq.Header.Add(k, headers.Get(k))\n\t}\n\tres, err := service.GetHttpClient().Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif res.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"status code: %d\", res.StatusCode)\n\t}\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn body, nil\n}\n\nfunc updateChannelCloseAIBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"%s/dashboard/billing/credit_grants\", channel.GetBaseURL())\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAICreditGrants{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalAvailable)\n\treturn response.TotalAvailable, nil\n}\n\nfunc updateChannelOpenAISBBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"https://api.openai-sb.com/sb-api/user/status?api_key=%s\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAISBUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif response.Data == nil {\n\t\treturn 0, errors.New(response.Msg)\n\t}\n\tbalance, err := strconv.ParseFloat(response.Data.Credit, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelAIProxyBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://aiproxy.io/api/report/getUserOverview\"\n\theaders := http.Header{}\n\theaders.Add(\"Api-Key\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, headers)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := AIProxyUserOverviewResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif !response.Success {\n\t\treturn 0, fmt.Errorf(\"code: %d, message: %s\", response.ErrorCode, response.Message)\n\t}\n\tchannel.UpdateBalance(response.Data.TotalPoints)\n\treturn response.Data.TotalPoints, nil\n}\n\nfunc updateChannelAPI2GPTBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.api2gpt.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := API2GPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalRemaining)\n\treturn response.TotalRemaining, nil\n}\n\nfunc updateChannelAIGC2DBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.aigc2d.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := APGC2DGPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalAvailable)\n\treturn response.TotalAvailable, nil\n}\n\nfunc updateChannelBalance(channel *model.Channel) (float64, error) {\n\tbaseURL := common.ChannelBaseURLs[channel.Type]\n\tif channel.GetBaseURL() == \"\" {\n\t\tchannel.BaseURL = &baseURL\n\t}\n\tswitch channel.Type {\n\tcase common.ChannelTypeOpenAI:\n\t\tif channel.GetBaseURL() != \"\" {\n\t\t\tbaseURL = channel.GetBaseURL()\n\t\t}\n\tcase common.ChannelTypeAzure:\n\t\treturn 0, errors.New(\"尚未实现\")\n\tcase common.ChannelTypeCustom:\n\t\tbaseURL = channel.GetBaseURL()\n\tcase common.ChannelTypeOpenAISB:\n\t\treturn updateChannelOpenAISBBalance(channel)\n\tcase common.ChannelTypeAIProxy:\n\t\treturn updateChannelAIProxyBalance(channel)\n\tcase common.ChannelTypeAPI2GPT:\n\t\treturn updateChannelAPI2GPTBalance(channel)\n\tcase common.ChannelTypeAIGC2D:\n\t\treturn updateChannelAIGC2DBalance(channel)\n\tdefault:\n\t\treturn 0, errors.New(\"尚未实现\")\n\t}\n\turl := fmt.Sprintf(\"%s/v1/dashboard/billing/subscription\", baseURL)\n\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tsubscription := OpenAISubscriptionResponse{}\n\terr = json.Unmarshal(body, &subscription)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tnow := time.Now()\n\tstartDate := fmt.Sprintf(\"%s-01\", now.Format(\"2006-01\"))\n\tendDate := now.Format(\"2006-01-02\")\n\tif !subscription.HasPaymentMethod {\n\t\tstartDate = now.AddDate(0, 0, -100).Format(\"2006-01-02\")\n\t}\n\turl = fmt.Sprintf(\"%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s\", baseURL, startDate, endDate)\n\tbody, err = GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tusage := OpenAIUsageResponse{}\n\terr = json.Unmarshal(body, &usage)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbalance := subscription.HardLimitUSD - usage.TotalUsage/100\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc UpdateChannelBalance(c *gin.Context) {\n\tid, err := strconv.Atoi(c.Param(\"id\"))\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tchannel, err := model.GetChannelById(id, true)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tbalance, err := updateChannelBalance(channel)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t\t\"balance\": balance,\n\t})\n\treturn\n}\n\nfunc updateAllChannelsBalance() error {\n\tchannels, err := model.GetAllChannels(0, 0, true, false)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, channel := range channels {\n\t\tif channel.Status != common.ChannelStatusEnabled {\n\t\t\tcontinue\n\t\t}\n\t\t// TODO: support Azure\n\t\tif channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {\n\t\t\tcontinue\n\t\t}\n\t\tbalance, err := updateChannelBalance(channel)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t} else {\n\t\t\t// err is nil & balance <= 0 means quota is used up\n\t\t\tif balance <= 0 {\n\t\t\t\tservice.DisableChannel(channel.Id, channel.Name, \"余额不足\")\n\t\t\t}\n\t\t}\n\t\ttime.Sleep(common.RequestInterval)\n\t}\n\treturn nil\n}\n\nfunc UpdateAllChannelsBalance(c *gin.Context) {\n\t// TODO: make it async\n\terr := updateAllChannelsBalance()\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t})\n\treturn\n}\n\nfunc AutomaticallyUpdateChannels(frequency int) {\n\tfor {\n\t\ttime.Sleep(time.Duration(frequency) * time.Minute)\n\t\tcommon.SysLog(\"updating all channels\")\n\t\t_ = updateAllChannelsBalance()\n\t\tcommon.SysLog(\"channels update done\")\n\t}\n}\n"}, "5b18cd6b0ad5c96978314e31e0e51bbef6097c18": {"t": 1785759394.0440164, "n": 9035, "h": "eb132a7acce0f7a1", "c": "package controller\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"one-api/common\"\n\t\"one-api/model\"\n\t\"one-api/service\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// https://github.com/songquanpeng/one-api/issues/79\n\ntype OpenAISubscriptionResponse struct {\n\tObject string `json:\"object\"`\n\tHasPaymentMethod bool `json:\"has_payment_method\"`\n\tSoftLimitUSD float64 `json:\"soft_limit_usd\"`\n\tHardLimitUSD float64 `json:\"hard_limit_usd\"`\n\tSystemHardLimitUSD float64 `json:\"system_hard_limit_usd\"`\n\tAccessUntil int64 `json:\"access_until\"`\n}\n\ntype OpenAIUsageDailyCost struct {\n\tTimestamp float64 `json:\"timestamp\"`\n\tLineItems []struct {\n\t\tName string `json:\"name\"`\n\t\tCost float64 `json:\"cost\"`\n\t}\n}\n\ntype OpenAICreditGrants struct {\n\tObject string `json:\"object\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n\tTotalAvailable float64 `json:\"total_available\"`\n}\n\ntype OpenAIUsageResponse struct {\n\tObject string `json:\"object\"`\n\t//DailyCosts []OpenAIUsageDailyCost `json:\"daily_costs\"`\n\tTotalUsage float64 `json:\"total_usage\"` // unit: 0.01 dollar\n}\n\ntype OpenAISBUsageResponse struct {\n\tMsg string `json:\"msg\"`\n\tData *struct {\n\t\tCredit string `json:\"credit\"`\n\t} `json:\"data\"`\n}\n\ntype AIProxyUserOverviewResponse struct {\n\tSuccess bool `json:\"success\"`\n\tMessage string `json:\"message\"`\n\tErrorCode int `json:\"error_code\"`\n\tData struct {\n\t\tTotalPoints float64 `json:\"totalPoints\"`\n\t} `json:\"data\"`\n}\n\ntype API2GPTUsageResponse struct {\n\tObject string `json:\"object\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n\tTotalRemaining float64 `json:\"total_remaining\"`\n}\n\ntype APGC2DGPTUsageResponse struct {\n\t//Grants interface{} `json:\"grants\"`\n\tObject string `json:\"object\"`\n\tTotalAvailable float64 `json:\"total_available\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n}\n\n// GetAuthHeader get auth header\nfunc GetAuthHeader(token string) http.Header {\n\th := http.Header{}\n\th.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\treturn h\n}\n\nfunc GetResponseBody(method, url string, channel *model.Channel, headers http.Header) ([]byte, error) {\n\treq, err := http.NewRequest(method, url, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor k := range headers {\n\t\treq.Header.Add(k, headers.Get(k))\n\t}\n\tres, err := service.GetHttpClient().Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif res.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"status code: %d\", res.StatusCode)\n\t}\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn body, nil\n}\n\nfunc updateChannelCloseAIBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"%s/dashboard/billing/credit_grants\", channel.GetBaseURL())\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAICreditGrants{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalAvailable)\n\treturn response.TotalAvailable, nil\n}\n\nfunc updateChannelOpenAISBBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"https://api.openai-sb.com/sb-api/user/status?api_key=%s\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAISBUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif response.Data == nil {\n\t\treturn 0, errors.New(response.Msg)\n\t}\n\tbalance, err := strconv.ParseFloat(response.Data.Credit, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelAIProxyBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://aiproxy.io/api/report/getUserOverview\"\n\theaders := http.Header{}\n\theaders.Add(\"Api-Key\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, headers)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := AIProxyUserOverviewResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif !response.Success {\n\t\treturn 0, fmt.Errorf(\"code: %d, message: %s\", response.ErrorCode, response.Message)\n\t}\n\tchannel.UpdateBalance(response.Data.TotalPoints)\n\treturn response.Data.TotalPoints, nil\n}\n\nfunc updateChannelAPI2GPTBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.api2gpt.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := API2GPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalRemaining)\n\treturn response.TotalRemaining, nil\n}\n\nfunc updateChannelAIGC2DBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.aigc2d.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := APGC2DGPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalAvailable)\n\treturn response.TotalAvailable, nil\n}\n\nfunc updateChannelBalance(channel *model.Channel) (float64, error) {\n\tbaseURL := common.ChannelBaseURLs[channel.Type]\n\tif channel.GetBaseURL() == \"\" {\n\t\tchannel.BaseURL = &baseURL\n\t}\n\tswitch channel.Type {\n\tcase common.ChannelTypeOpenAI:\n\t\tif channel.GetBaseURL() != \"\" {\n\t\t\tbaseURL = channel.GetBaseURL()\n\t\t}\n\tcase common.ChannelTypeAzure:\n\t\treturn 0, errors.New(\"尚未实现\")\n\tcase common.ChannelTypeCustom:\n\t\tbaseURL = channel.GetBaseURL()\n\tcase common.ChannelTypeCloseAI:\n\t\treturn updateChannelCloseAIBalance(channel)\n\tcase common.ChannelTypeOpenAISB:\n\t\treturn updateChannelOpenAISBBalance(channel)\n\tcase common.ChannelTypeAIProxy:\n\t\treturn updateChannelAIProxyBalance(channel)\n\tcase common.ChannelTypeAPI2GPT:\n\t\treturn updateChannelAPI2GPTBalance(channel)\n\tcase common.ChannelTypeAIGC2D:\n\t\treturn updateChannelAIGC2DBalance(channel)\n\tdefault:\n\t\treturn 0, errors.New(\"尚未实现\")\n\t}\n\turl := fmt.Sprintf(\"%s/v1/dashboard/billing/subscription\", baseURL)\n\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tsubscription := OpenAISubscriptionResponse{}\n\terr = json.Unmarshal(body, &subscription)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tnow := time.Now()\n\tstartDate := fmt.Sprintf(\"%s-01\", now.Format(\"2006-01\"))\n\tendDate := now.Format(\"2006-01-02\")\n\tif !subscription.HasPaymentMethod {\n\t\tstartDate = now.AddDate(0, 0, -100).Format(\"2006-01-02\")\n\t}\n\turl = fmt.Sprintf(\"%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s\", baseURL, startDate, endDate)\n\tbody, err = GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tusage := OpenAIUsageResponse{}\n\terr = json.Unmarshal(body, &usage)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbalance := subscription.HardLimitUSD - usage.TotalUsage/100\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc UpdateChannelBalance(c *gin.Context) {\n\tid, err := strconv.Atoi(c.Param(\"id\"))\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tchannel, err := model.GetChannelById(id, true)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tbalance, err := updateChannelBalance(channel)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t\t\"balance\": balance,\n\t})\n\treturn\n}\n\nfunc updateAllChannelsBalance() error {\n\tchannels, err := model.GetAllChannels(0, 0, true, false)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, channel := range channels {\n\t\tif channel.Status != common.ChannelStatusEnabled {\n\t\t\tcontinue\n\t\t}\n\t\t// TODO: support Azure\n\t\tif channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {\n\t\t\tcontinue\n\t\t}\n\t\tbalance, err := updateChannelBalance(channel)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t} else {\n\t\t\t// err is nil & balance <= 0 means quota is used up\n\t\t\tif balance <= 0 {\n\t\t\t\tservice.DisableChannel(channel.Id, channel.Name, \"余额不足\")\n\t\t\t}\n\t\t}\n\t\ttime.Sleep(common.RequestInterval)\n\t}\n\treturn nil\n}\n\nfunc UpdateAllChannelsBalance(c *gin.Context) {\n\t// TODO: make it async\n\terr := updateAllChannelsBalance()\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t})\n\treturn\n}\n\nfunc AutomaticallyUpdateChannels(frequency int) {\n\tfor {\n\t\ttime.Sleep(time.Duration(frequency) * time.Minute)\n\t\tcommon.SysLog(\"updating all channels\")\n\t\t_ = updateAllChannelsBalance()\n\t\tcommon.SysLog(\"channels update done\")\n\t}\n}\n"}, "7dc8b0ea9374fdc45104216cc0d676cbe54e2dee": {"t": 1785759397.528576, "n": 8995, "h": "2d49ef41332e45af", "c": "package controller\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"one-api/common\"\n\t\"one-api/model\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// https://github.com/songquanpeng/one-api/issues/79\n\ntype OpenAISubscriptionResponse struct {\n\tObject string `json:\"object\"`\n\tHasPaymentMethod bool `json:\"has_payment_method\"`\n\tSoftLimitUSD float64 `json:\"soft_limit_usd\"`\n\tHardLimitUSD float64 `json:\"hard_limit_usd\"`\n\tSystemHardLimitUSD float64 `json:\"system_hard_limit_usd\"`\n\tAccessUntil int64 `json:\"access_until\"`\n}\n\ntype OpenAIUsageDailyCost struct {\n\tTimestamp float64 `json:\"timestamp\"`\n\tLineItems []struct {\n\t\tName string `json:\"name\"`\n\t\tCost float64 `json:\"cost\"`\n\t}\n}\n\ntype OpenAICreditGrants struct {\n\tObject string `json:\"object\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n\tTotalAvailable float64 `json:\"total_available\"`\n}\n\ntype OpenAIUsageResponse struct {\n\tObject string `json:\"object\"`\n\t//DailyCosts []OpenAIUsageDailyCost `json:\"daily_costs\"`\n\tTotalUsage float64 `json:\"total_usage\"` // unit: 0.01 dollar\n}\n\ntype OpenAISBUsageResponse struct {\n\tMsg string `json:\"msg\"`\n\tData *struct {\n\t\tCredit string `json:\"credit\"`\n\t} `json:\"data\"`\n}\n\ntype AIProxyUserOverviewResponse struct {\n\tSuccess bool `json:\"success\"`\n\tMessage string `json:\"message\"`\n\tErrorCode int `json:\"error_code\"`\n\tData struct {\n\t\tTotalPoints float64 `json:\"totalPoints\"`\n\t} `json:\"data\"`\n}\n\ntype API2GPTUsageResponse struct {\n\tObject string `json:\"object\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n\tTotalRemaining float64 `json:\"total_remaining\"`\n}\n\ntype APGC2DGPTUsageResponse struct {\n\t//Grants interface{} `json:\"grants\"`\n\tObject string `json:\"object\"`\n\tTotalAvailable float64 `json:\"total_available\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n}\n\n// GetAuthHeader get auth header\nfunc GetAuthHeader(token string) http.Header {\n\th := http.Header{}\n\th.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\treturn h\n}\n\nfunc GetResponseBody(method, url string, channel *model.Channel, headers http.Header) ([]byte, error) {\n\treq, err := http.NewRequest(method, url, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor k := range headers {\n\t\treq.Header.Add(k, headers.Get(k))\n\t}\n\tres, err := httpClient.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif res.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"status code: %d\", res.StatusCode)\n\t}\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn body, nil\n}\n\nfunc updateChannelCloseAIBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"%s/dashboard/billing/credit_grants\", channel.GetBaseURL())\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAICreditGrants{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalAvailable)\n\treturn response.TotalAvailable, nil\n}\n\nfunc updateChannelOpenAISBBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"https://api.openai-sb.com/sb-api/user/status?api_key=%s\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAISBUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif response.Data == nil {\n\t\treturn 0, errors.New(response.Msg)\n\t}\n\tbalance, err := strconv.ParseFloat(response.Data.Credit, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelAIProxyBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://aiproxy.io/api/report/getUserOverview\"\n\theaders := http.Header{}\n\theaders.Add(\"Api-Key\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, headers)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := AIProxyUserOverviewResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif !response.Success {\n\t\treturn 0, fmt.Errorf(\"code: %d, message: %s\", response.ErrorCode, response.Message)\n\t}\n\tchannel.UpdateBalance(response.Data.TotalPoints)\n\treturn response.Data.TotalPoints, nil\n}\n\nfunc updateChannelAPI2GPTBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.api2gpt.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := API2GPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalRemaining)\n\treturn response.TotalRemaining, nil\n}\n\nfunc updateChannelAIGC2DBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.aigc2d.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := APGC2DGPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalAvailable)\n\treturn response.TotalAvailable, nil\n}\n\nfunc updateChannelBalance(channel *model.Channel) (float64, error) {\n\tbaseURL := common.ChannelBaseURLs[channel.Type]\n\tif channel.GetBaseURL() == \"\" {\n\t\tchannel.BaseURL = &baseURL\n\t}\n\tswitch channel.Type {\n\tcase common.ChannelTypeOpenAI:\n\t\tif channel.GetBaseURL() != \"\" {\n\t\t\tbaseURL = channel.GetBaseURL()\n\t\t}\n\tcase common.ChannelTypeAzure:\n\t\treturn 0, errors.New(\"尚未实现\")\n\tcase common.ChannelTypeCustom:\n\t\tbaseURL = channel.GetBaseURL()\n\tcase common.ChannelTypeCloseAI:\n\t\treturn updateChannelCloseAIBalance(channel)\n\tcase common.ChannelTypeOpenAISB:\n\t\treturn updateChannelOpenAISBBalance(channel)\n\tcase common.ChannelTypeAIProxy:\n\t\treturn updateChannelAIProxyBalance(channel)\n\tcase common.ChannelTypeAPI2GPT:\n\t\treturn updateChannelAPI2GPTBalance(channel)\n\tcase common.ChannelTypeAIGC2D:\n\t\treturn updateChannelAIGC2DBalance(channel)\n\tdefault:\n\t\treturn 0, errors.New(\"尚未实现\")\n\t}\n\turl := fmt.Sprintf(\"%s/v1/dashboard/billing/subscription\", baseURL)\n\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tsubscription := OpenAISubscriptionResponse{}\n\terr = json.Unmarshal(body, &subscription)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tnow := time.Now()\n\tstartDate := fmt.Sprintf(\"%s-01\", now.Format(\"2006-01\"))\n\tendDate := now.Format(\"2006-01-02\")\n\tif !subscription.HasPaymentMethod {\n\t\tstartDate = now.AddDate(0, 0, -100).Format(\"2006-01-02\")\n\t}\n\turl = fmt.Sprintf(\"%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s\", baseURL, startDate, endDate)\n\tbody, err = GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tusage := OpenAIUsageResponse{}\n\terr = json.Unmarshal(body, &usage)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbalance := subscription.HardLimitUSD - usage.TotalUsage/100\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc UpdateChannelBalance(c *gin.Context) {\n\tid, err := strconv.Atoi(c.Param(\"id\"))\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tchannel, err := model.GetChannelById(id, true)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tbalance, err := updateChannelBalance(channel)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t\t\"balance\": balance,\n\t})\n\treturn\n}\n\nfunc updateAllChannelsBalance() error {\n\tchannels, err := model.GetAllChannels(0, 0, true, false)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, channel := range channels {\n\t\tif channel.Status != common.ChannelStatusEnabled {\n\t\t\tcontinue\n\t\t}\n\t\t// TODO: support Azure\n\t\tif channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {\n\t\t\tcontinue\n\t\t}\n\t\tbalance, err := updateChannelBalance(channel)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t} else {\n\t\t\t// err is nil & balance <= 0 means quota is used up\n\t\t\tif balance <= 0 {\n\t\t\t\tdisableChannel(channel.Id, channel.Name, \"余额不足\")\n\t\t\t}\n\t\t}\n\t\ttime.Sleep(common.RequestInterval)\n\t}\n\treturn nil\n}\n\nfunc UpdateAllChannelsBalance(c *gin.Context) {\n\t// TODO: make it async\n\terr := updateAllChannelsBalance()\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t})\n\treturn\n}\n\nfunc AutomaticallyUpdateChannels(frequency int) {\n\tfor {\n\t\ttime.Sleep(time.Duration(frequency) * time.Minute)\n\t\tcommon.SysLog(\"updating all channels\")\n\t\t_ = updateAllChannelsBalance()\n\t\tcommon.SysLog(\"channels update done\")\n\t}\n}\n"}, "159b9e3369fdca0fb90cafdc96317c0f299d26bb": {"t": 1785759399.415188, "n": 8988, "h": "fe6901e271cdc246", "c": "package controller\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"one-api/common\"\n\t\"one-api/model\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// https://github.com/songquanpeng/one-api/issues/79\n\ntype OpenAISubscriptionResponse struct {\n\tObject string `json:\"object\"`\n\tHasPaymentMethod bool `json:\"has_payment_method\"`\n\tSoftLimitUSD float64 `json:\"soft_limit_usd\"`\n\tHardLimitUSD float64 `json:\"hard_limit_usd\"`\n\tSystemHardLimitUSD float64 `json:\"system_hard_limit_usd\"`\n\tAccessUntil int64 `json:\"access_until\"`\n}\n\ntype OpenAIUsageDailyCost struct {\n\tTimestamp float64 `json:\"timestamp\"`\n\tLineItems []struct {\n\t\tName string `json:\"name\"`\n\t\tCost float64 `json:\"cost\"`\n\t}\n}\n\ntype OpenAICreditGrants struct {\n\tObject string `json:\"object\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n\tTotalAvailable float64 `json:\"total_available\"`\n}\n\ntype OpenAIUsageResponse struct {\n\tObject string `json:\"object\"`\n\t//DailyCosts []OpenAIUsageDailyCost `json:\"daily_costs\"`\n\tTotalUsage float64 `json:\"total_usage\"` // unit: 0.01 dollar\n}\n\ntype OpenAISBUsageResponse struct {\n\tMsg string `json:\"msg\"`\n\tData *struct {\n\t\tCredit string `json:\"credit\"`\n\t} `json:\"data\"`\n}\n\ntype AIProxyUserOverviewResponse struct {\n\tSuccess bool `json:\"success\"`\n\tMessage string `json:\"message\"`\n\tErrorCode int `json:\"error_code\"`\n\tData struct {\n\t\tTotalPoints float64 `json:\"totalPoints\"`\n\t} `json:\"data\"`\n}\n\ntype API2GPTUsageResponse struct {\n\tObject string `json:\"object\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n\tTotalRemaining float64 `json:\"total_remaining\"`\n}\n\ntype APGC2DGPTUsageResponse struct {\n\t//Grants interface{} `json:\"grants\"`\n\tObject string `json:\"object\"`\n\tTotalAvailable float64 `json:\"total_available\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n}\n\n// GetAuthHeader get auth header\nfunc GetAuthHeader(token string) http.Header {\n\th := http.Header{}\n\th.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\treturn h\n}\n\nfunc GetResponseBody(method, url string, channel *model.Channel, headers http.Header) ([]byte, error) {\n\treq, err := http.NewRequest(method, url, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor k := range headers {\n\t\treq.Header.Add(k, headers.Get(k))\n\t}\n\tres, err := httpClient.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif res.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"status code: %d\", res.StatusCode)\n\t}\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn body, nil\n}\n\nfunc updateChannelCloseAIBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"%s/dashboard/billing/credit_grants\", channel.GetBaseURL())\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAICreditGrants{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalAvailable)\n\treturn response.TotalAvailable, nil\n}\n\nfunc updateChannelOpenAISBBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"https://api.openai-sb.com/sb-api/user/status?api_key=%s\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAISBUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif response.Data == nil {\n\t\treturn 0, errors.New(response.Msg)\n\t}\n\tbalance, err := strconv.ParseFloat(response.Data.Credit, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelAIProxyBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://aiproxy.io/api/report/getUserOverview\"\n\theaders := http.Header{}\n\theaders.Add(\"Api-Key\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, headers)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := AIProxyUserOverviewResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif !response.Success {\n\t\treturn 0, fmt.Errorf(\"code: %d, message: %s\", response.ErrorCode, response.Message)\n\t}\n\tchannel.UpdateBalance(response.Data.TotalPoints)\n\treturn response.Data.TotalPoints, nil\n}\n\nfunc updateChannelAPI2GPTBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.api2gpt.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := API2GPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalRemaining)\n\treturn response.TotalRemaining, nil\n}\n\nfunc updateChannelAIGC2DBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.aigc2d.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := APGC2DGPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalAvailable)\n\treturn response.TotalAvailable, nil\n}\n\nfunc updateChannelBalance(channel *model.Channel) (float64, error) {\n\tbaseURL := common.ChannelBaseURLs[channel.Type]\n\tif channel.GetBaseURL() == \"\" {\n\t\tchannel.BaseURL = &baseURL\n\t}\n\tswitch channel.Type {\n\tcase common.ChannelTypeOpenAI:\n\t\tif channel.GetBaseURL() != \"\" {\n\t\t\tbaseURL = channel.GetBaseURL()\n\t\t}\n\tcase common.ChannelTypeAzure:\n\t\treturn 0, errors.New(\"尚未实现\")\n\tcase common.ChannelTypeCustom:\n\t\tbaseURL = channel.GetBaseURL()\n\tcase common.ChannelTypeCloseAI:\n\t\treturn updateChannelCloseAIBalance(channel)\n\tcase common.ChannelTypeOpenAISB:\n\t\treturn updateChannelOpenAISBBalance(channel)\n\tcase common.ChannelTypeAIProxy:\n\t\treturn updateChannelAIProxyBalance(channel)\n\tcase common.ChannelTypeAPI2GPT:\n\t\treturn updateChannelAPI2GPTBalance(channel)\n\tcase common.ChannelTypeAIGC2D:\n\t\treturn updateChannelAIGC2DBalance(channel)\n\tdefault:\n\t\treturn 0, errors.New(\"尚未实现\")\n\t}\n\turl := fmt.Sprintf(\"%s/v1/dashboard/billing/subscription\", baseURL)\n\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tsubscription := OpenAISubscriptionResponse{}\n\terr = json.Unmarshal(body, &subscription)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tnow := time.Now()\n\tstartDate := fmt.Sprintf(\"%s-01\", now.Format(\"2006-01\"))\n\tendDate := now.Format(\"2006-01-02\")\n\tif !subscription.HasPaymentMethod {\n\t\tstartDate = now.AddDate(0, 0, -100).Format(\"2006-01-02\")\n\t}\n\turl = fmt.Sprintf(\"%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s\", baseURL, startDate, endDate)\n\tbody, err = GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tusage := OpenAIUsageResponse{}\n\terr = json.Unmarshal(body, &usage)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbalance := subscription.HardLimitUSD - usage.TotalUsage/100\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc UpdateChannelBalance(c *gin.Context) {\n\tid, err := strconv.Atoi(c.Param(\"id\"))\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tchannel, err := model.GetChannelById(id, true)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tbalance, err := updateChannelBalance(channel)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t\t\"balance\": balance,\n\t})\n\treturn\n}\n\nfunc updateAllChannelsBalance() error {\n\tchannels, err := model.GetAllChannels(0, 0, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, channel := range channels {\n\t\tif channel.Status != common.ChannelStatusEnabled {\n\t\t\tcontinue\n\t\t}\n\t\t// TODO: support Azure\n\t\tif channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {\n\t\t\tcontinue\n\t\t}\n\t\tbalance, err := updateChannelBalance(channel)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t} else {\n\t\t\t// err is nil & balance <= 0 means quota is used up\n\t\t\tif balance <= 0 {\n\t\t\t\tdisableChannel(channel.Id, channel.Name, \"余额不足\")\n\t\t\t}\n\t\t}\n\t\ttime.Sleep(common.RequestInterval)\n\t}\n\treturn nil\n}\n\nfunc UpdateAllChannelsBalance(c *gin.Context) {\n\t// TODO: make it async\n\terr := updateAllChannelsBalance()\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t})\n\treturn\n}\n\nfunc AutomaticallyUpdateChannels(frequency int) {\n\tfor {\n\t\ttime.Sleep(time.Duration(frequency) * time.Minute)\n\t\tcommon.SysLog(\"updating all channels\")\n\t\t_ = updateAllChannelsBalance()\n\t\tcommon.SysLog(\"channels update done\")\n\t}\n}\n"}, "3da119efbafc9515b1b2b79e042d3baa7e0b5267": {"t": 1785759401.3865123, "n": 8962, "h": "8945e3af80b3e82d", "c": "package controller\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"one-api/common\"\n\t\"one-api/model\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// https://github.com/songquanpeng/one-api/issues/79\n\ntype OpenAISubscriptionResponse struct {\n\tObject string `json:\"object\"`\n\tHasPaymentMethod bool `json:\"has_payment_method\"`\n\tSoftLimitUSD float64 `json:\"soft_limit_usd\"`\n\tHardLimitUSD float64 `json:\"hard_limit_usd\"`\n\tSystemHardLimitUSD float64 `json:\"system_hard_limit_usd\"`\n\tAccessUntil int64 `json:\"access_until\"`\n}\n\ntype OpenAIUsageDailyCost struct {\n\tTimestamp float64 `json:\"timestamp\"`\n\tLineItems []struct {\n\t\tName string `json:\"name\"`\n\t\tCost float64 `json:\"cost\"`\n\t}\n}\n\ntype OpenAICreditGrants struct {\n\tObject string `json:\"object\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n\tTotalAvailable float64 `json:\"total_available\"`\n}\n\ntype OpenAIUsageResponse struct {\n\tObject string `json:\"object\"`\n\t//DailyCosts []OpenAIUsageDailyCost `json:\"daily_costs\"`\n\tTotalUsage float64 `json:\"total_usage\"` // unit: 0.01 dollar\n}\n\ntype OpenAISBUsageResponse struct {\n\tMsg string `json:\"msg\"`\n\tData *struct {\n\t\tCredit string `json:\"credit\"`\n\t} `json:\"data\"`\n}\n\ntype AIProxyUserOverviewResponse struct {\n\tSuccess bool `json:\"success\"`\n\tMessage string `json:\"message\"`\n\tErrorCode int `json:\"error_code\"`\n\tData struct {\n\t\tTotalPoints float64 `json:\"totalPoints\"`\n\t} `json:\"data\"`\n}\n\ntype API2GPTUsageResponse struct {\n\tObject string `json:\"object\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n\tTotalRemaining float64 `json:\"total_remaining\"`\n}\n\ntype APGC2DGPTUsageResponse struct {\n\t//Grants interface{} `json:\"grants\"`\n\tObject string `json:\"object\"`\n\tTotalAvailable float64 `json:\"total_available\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n}\n\n// GetAuthHeader get auth header\nfunc GetAuthHeader(token string) http.Header {\n\th := http.Header{}\n\th.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\treturn h\n}\n\nfunc GetResponseBody(method, url string, channel *model.Channel, headers http.Header) ([]byte, error) {\n\treq, err := http.NewRequest(method, url, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor k := range headers {\n\t\treq.Header.Add(k, headers.Get(k))\n\t}\n\tres, err := httpClient.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif res.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"status code: %d\", res.StatusCode)\n\t}\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn body, nil\n}\n\nfunc updateChannelCloseAIBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"%s/dashboard/billing/credit_grants\", channel.BaseURL)\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAICreditGrants{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalAvailable)\n\treturn response.TotalAvailable, nil\n}\n\nfunc updateChannelOpenAISBBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"https://api.openai-sb.com/sb-api/user/status?api_key=%s\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAISBUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif response.Data == nil {\n\t\treturn 0, errors.New(response.Msg)\n\t}\n\tbalance, err := strconv.ParseFloat(response.Data.Credit, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelAIProxyBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://aiproxy.io/api/report/getUserOverview\"\n\theaders := http.Header{}\n\theaders.Add(\"Api-Key\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, headers)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := AIProxyUserOverviewResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif !response.Success {\n\t\treturn 0, fmt.Errorf(\"code: %d, message: %s\", response.ErrorCode, response.Message)\n\t}\n\tchannel.UpdateBalance(response.Data.TotalPoints)\n\treturn response.Data.TotalPoints, nil\n}\n\nfunc updateChannelAPI2GPTBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.api2gpt.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := API2GPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalRemaining)\n\treturn response.TotalRemaining, nil\n}\n\nfunc updateChannelAIGC2DBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.aigc2d.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := APGC2DGPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalAvailable)\n\treturn response.TotalAvailable, nil\n}\n\nfunc updateChannelBalance(channel *model.Channel) (float64, error) {\n\tbaseURL := common.ChannelBaseURLs[channel.Type]\n\tif channel.BaseURL == \"\" {\n\t\tchannel.BaseURL = baseURL\n\t}\n\tswitch channel.Type {\n\tcase common.ChannelTypeOpenAI:\n\t\tif channel.BaseURL != \"\" {\n\t\t\tbaseURL = channel.BaseURL\n\t\t}\n\tcase common.ChannelTypeAzure:\n\t\treturn 0, errors.New(\"尚未实现\")\n\tcase common.ChannelTypeCustom:\n\t\tbaseURL = channel.BaseURL\n\tcase common.ChannelTypeCloseAI:\n\t\treturn updateChannelCloseAIBalance(channel)\n\tcase common.ChannelTypeOpenAISB:\n\t\treturn updateChannelOpenAISBBalance(channel)\n\tcase common.ChannelTypeAIProxy:\n\t\treturn updateChannelAIProxyBalance(channel)\n\tcase common.ChannelTypeAPI2GPT:\n\t\treturn updateChannelAPI2GPTBalance(channel)\n\tcase common.ChannelTypeAIGC2D:\n\t\treturn updateChannelAIGC2DBalance(channel)\n\tdefault:\n\t\treturn 0, errors.New(\"尚未实现\")\n\t}\n\turl := fmt.Sprintf(\"%s/v1/dashboard/billing/subscription\", baseURL)\n\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tsubscription := OpenAISubscriptionResponse{}\n\terr = json.Unmarshal(body, &subscription)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tnow := time.Now()\n\tstartDate := fmt.Sprintf(\"%s-01\", now.Format(\"2006-01\"))\n\tendDate := now.Format(\"2006-01-02\")\n\tif !subscription.HasPaymentMethod {\n\t\tstartDate = now.AddDate(0, 0, -100).Format(\"2006-01-02\")\n\t}\n\turl = fmt.Sprintf(\"%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s\", baseURL, startDate, endDate)\n\tbody, err = GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tusage := OpenAIUsageResponse{}\n\terr = json.Unmarshal(body, &usage)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbalance := subscription.HardLimitUSD - usage.TotalUsage/100\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc UpdateChannelBalance(c *gin.Context) {\n\tid, err := strconv.Atoi(c.Param(\"id\"))\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tchannel, err := model.GetChannelById(id, true)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tbalance, err := updateChannelBalance(channel)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t\t\"balance\": balance,\n\t})\n\treturn\n}\n\nfunc updateAllChannelsBalance() error {\n\tchannels, err := model.GetAllChannels(0, 0, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, channel := range channels {\n\t\tif channel.Status != common.ChannelStatusEnabled {\n\t\t\tcontinue\n\t\t}\n\t\t// TODO: support Azure\n\t\tif channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {\n\t\t\tcontinue\n\t\t}\n\t\tbalance, err := updateChannelBalance(channel)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t} else {\n\t\t\t// err is nil & balance <= 0 means quota is used up\n\t\t\tif balance <= 0 {\n\t\t\t\tdisableChannel(channel.Id, channel.Name, \"余额不足\")\n\t\t\t}\n\t\t}\n\t\ttime.Sleep(common.RequestInterval)\n\t}\n\treturn nil\n}\n\nfunc UpdateAllChannelsBalance(c *gin.Context) {\n\t// TODO: make it async\n\terr := updateAllChannelsBalance()\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t})\n\treturn\n}\n\nfunc AutomaticallyUpdateChannels(frequency int) {\n\tfor {\n\t\ttime.Sleep(time.Duration(frequency) * time.Minute)\n\t\tcommon.SysLog(\"updating all channels\")\n\t\t_ = updateAllChannelsBalance()\n\t\tcommon.SysLog(\"channels update done\")\n\t}\n}\n"}, "dccd66b852e902d141db05171bbfb6eca812fc64": {"t": 1785759402.9949236, "n": 8984, "h": "c27cbee80dc36fff", "c": "package controller\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"one-api/common\"\n\t\"one-api/model\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// https://github.com/songquanpeng/one-api/issues/79\n\ntype OpenAISubscriptionResponse struct {\n\tObject string `json:\"object\"`\n\tHasPaymentMethod bool `json:\"has_payment_method\"`\n\tSoftLimitUSD float64 `json:\"soft_limit_usd\"`\n\tHardLimitUSD float64 `json:\"hard_limit_usd\"`\n\tSystemHardLimitUSD float64 `json:\"system_hard_limit_usd\"`\n\tAccessUntil int64 `json:\"access_until\"`\n}\n\ntype OpenAIUsageDailyCost struct {\n\tTimestamp float64 `json:\"timestamp\"`\n\tLineItems []struct {\n\t\tName string `json:\"name\"`\n\t\tCost float64 `json:\"cost\"`\n\t}\n}\n\ntype OpenAICreditGrants struct {\n\tObject string `json:\"object\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n\tTotalAvailable float64 `json:\"total_available\"`\n}\n\ntype OpenAIUsageResponse struct {\n\tObject string `json:\"object\"`\n\t//DailyCosts []OpenAIUsageDailyCost `json:\"daily_costs\"`\n\tTotalUsage float64 `json:\"total_usage\"` // unit: 0.01 dollar\n}\n\ntype OpenAISBUsageResponse struct {\n\tMsg string `json:\"msg\"`\n\tData *struct {\n\t\tCredit string `json:\"credit\"`\n\t} `json:\"data\"`\n}\n\ntype AIProxyUserOverviewResponse struct {\n\tSuccess bool `json:\"success\"`\n\tMessage string `json:\"message\"`\n\tErrorCode int `json:\"error_code\"`\n\tData struct {\n\t\tTotalPoints float64 `json:\"totalPoints\"`\n\t} `json:\"data\"`\n}\n\ntype API2GPTUsageResponse struct {\n\tObject string `json:\"object\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n\tTotalRemaining float64 `json:\"total_remaining\"`\n}\n\ntype APGC2DGPTUsageResponse struct {\n\t//Grants interface{} `json:\"grants\"`\n\tObject string `json:\"object\"`\n\tTotalAvailable float64 `json:\"total_available\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n}\n\n// GetAuthHeader get auth header\nfunc GetAuthHeader(token string) http.Header {\n\th := http.Header{}\n\th.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\treturn h\n}\n\nfunc GetResponseBody(method, url string, channel *model.Channel, headers http.Header) ([]byte, error) {\n\tclient := &http.Client{}\n\treq, err := http.NewRequest(method, url, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor k := range headers {\n\t\treq.Header.Add(k, headers.Get(k))\n\t}\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif res.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"status code: %d\", res.StatusCode)\n\t}\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn body, nil\n}\n\nfunc updateChannelCloseAIBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"%s/dashboard/billing/credit_grants\", channel.BaseURL)\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAICreditGrants{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalAvailable)\n\treturn response.TotalAvailable, nil\n}\n\nfunc updateChannelOpenAISBBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"https://api.openai-sb.com/sb-api/user/status?api_key=%s\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAISBUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif response.Data == nil {\n\t\treturn 0, errors.New(response.Msg)\n\t}\n\tbalance, err := strconv.ParseFloat(response.Data.Credit, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelAIProxyBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://aiproxy.io/api/report/getUserOverview\"\n\theaders := http.Header{}\n\theaders.Add(\"Api-Key\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, headers)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := AIProxyUserOverviewResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif !response.Success {\n\t\treturn 0, fmt.Errorf(\"code: %d, message: %s\", response.ErrorCode, response.Message)\n\t}\n\tchannel.UpdateBalance(response.Data.TotalPoints)\n\treturn response.Data.TotalPoints, nil\n}\n\nfunc updateChannelAPI2GPTBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.api2gpt.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := API2GPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalRemaining)\n\treturn response.TotalRemaining, nil\n}\n\nfunc updateChannelAIGC2DBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.aigc2d.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := APGC2DGPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalAvailable)\n\treturn response.TotalAvailable, nil\n}\n\nfunc updateChannelBalance(channel *model.Channel) (float64, error) {\n\tbaseURL := common.ChannelBaseURLs[channel.Type]\n\tif channel.BaseURL == \"\" {\n\t\tchannel.BaseURL = baseURL\n\t}\n\tswitch channel.Type {\n\tcase common.ChannelTypeOpenAI:\n\t\tif channel.BaseURL != \"\" {\n\t\t\tbaseURL = channel.BaseURL\n\t\t}\n\tcase common.ChannelTypeAzure:\n\t\treturn 0, errors.New(\"尚未实现\")\n\tcase common.ChannelTypeCustom:\n\t\tbaseURL = channel.BaseURL\n\tcase common.ChannelTypeCloseAI:\n\t\treturn updateChannelCloseAIBalance(channel)\n\tcase common.ChannelTypeOpenAISB:\n\t\treturn updateChannelOpenAISBBalance(channel)\n\tcase common.ChannelTypeAIProxy:\n\t\treturn updateChannelAIProxyBalance(channel)\n\tcase common.ChannelTypeAPI2GPT:\n\t\treturn updateChannelAPI2GPTBalance(channel)\n\tcase common.ChannelTypeAIGC2D:\n\t\treturn updateChannelAIGC2DBalance(channel)\n\tdefault:\n\t\treturn 0, errors.New(\"尚未实现\")\n\t}\n\turl := fmt.Sprintf(\"%s/v1/dashboard/billing/subscription\", baseURL)\n\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tsubscription := OpenAISubscriptionResponse{}\n\terr = json.Unmarshal(body, &subscription)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tnow := time.Now()\n\tstartDate := fmt.Sprintf(\"%s-01\", now.Format(\"2006-01\"))\n\tendDate := now.Format(\"2006-01-02\")\n\tif !subscription.HasPaymentMethod {\n\t\tstartDate = now.AddDate(0, 0, -100).Format(\"2006-01-02\")\n\t}\n\turl = fmt.Sprintf(\"%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s\", baseURL, startDate, endDate)\n\tbody, err = GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tusage := OpenAIUsageResponse{}\n\terr = json.Unmarshal(body, &usage)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbalance := subscription.HardLimitUSD - usage.TotalUsage/100\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc UpdateChannelBalance(c *gin.Context) {\n\tid, err := strconv.Atoi(c.Param(\"id\"))\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tchannel, err := model.GetChannelById(id, true)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tbalance, err := updateChannelBalance(channel)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t\t\"balance\": balance,\n\t})\n\treturn\n}\n\nfunc updateAllChannelsBalance() error {\n\tchannels, err := model.GetAllChannels(0, 0, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, channel := range channels {\n\t\tif channel.Status != common.ChannelStatusEnabled {\n\t\t\tcontinue\n\t\t}\n\t\t// TODO: support Azure\n\t\tif channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {\n\t\t\tcontinue\n\t\t}\n\t\tbalance, err := updateChannelBalance(channel)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t} else {\n\t\t\t// err is nil & balance <= 0 means quota is used up\n\t\t\tif balance <= 0 {\n\t\t\t\tdisableChannel(channel.Id, channel.Name, \"余额不足\")\n\t\t\t}\n\t\t}\n\t\ttime.Sleep(common.RequestInterval)\n\t}\n\treturn nil\n}\n\nfunc UpdateAllChannelsBalance(c *gin.Context) {\n\t// TODO: make it async\n\terr := updateAllChannelsBalance()\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t})\n\treturn\n}\n\nfunc AutomaticallyUpdateChannels(frequency int) {\n\tfor {\n\t\ttime.Sleep(time.Duration(frequency) * time.Minute)\n\t\tcommon.SysLog(\"updating all channels\")\n\t\t_ = updateAllChannelsBalance()\n\t\tcommon.SysLog(\"channels update done\")\n\t}\n}\n"}, "d383302e8ae10131e7539921e1530520404b516b": {"t": 1785759404.3800552, "n": 8833, "h": "17c5928c80ab92fd", "c": "package controller\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"one-api/common\"\n\t\"one-api/model\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// https://github.com/songquanpeng/one-api/issues/79\n\ntype OpenAISubscriptionResponse struct {\n\tObject string `json:\"object\"`\n\tHasPaymentMethod bool `json:\"has_payment_method\"`\n\tSoftLimitUSD float64 `json:\"soft_limit_usd\"`\n\tHardLimitUSD float64 `json:\"hard_limit_usd\"`\n\tSystemHardLimitUSD float64 `json:\"system_hard_limit_usd\"`\n}\n\ntype OpenAIUsageDailyCost struct {\n\tTimestamp float64 `json:\"timestamp\"`\n\tLineItems []struct {\n\t\tName string `json:\"name\"`\n\t\tCost float64 `json:\"cost\"`\n\t}\n}\n\ntype OpenAICreditGrants struct {\n\tObject string `json:\"object\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n\tTotalAvailable float64 `json:\"total_available\"`\n}\n\ntype OpenAIUsageResponse struct {\n\tObject string `json:\"object\"`\n\t//DailyCosts []OpenAIUsageDailyCost `json:\"daily_costs\"`\n\tTotalUsage float64 `json:\"total_usage\"` // unit: 0.01 dollar\n}\n\ntype OpenAISBUsageResponse struct {\n\tMsg string `json:\"msg\"`\n\tData *struct {\n\t\tCredit string `json:\"credit\"`\n\t} `json:\"data\"`\n}\n\ntype AIProxyUserOverviewResponse struct {\n\tSuccess bool `json:\"success\"`\n\tMessage string `json:\"message\"`\n\tErrorCode int `json:\"error_code\"`\n\tData struct {\n\t\tTotalPoints float64 `json:\"totalPoints\"`\n\t} `json:\"data\"`\n}\n\ntype API2GPTUsageResponse struct {\n\tObject string `json:\"object\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n\tTotalRemaining float64 `json:\"total_remaining\"`\n}\n\ntype APGC2DGPTUsageResponse struct {\n\t//Grants interface{} `json:\"grants\"`\n\tObject string `json:\"object\"`\n\tTotalAvailable float64 `json:\"total_available\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n}\n\n// GetAuthHeader get auth header\nfunc GetAuthHeader(token string) http.Header {\n\th := http.Header{}\n\th.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\treturn h\n}\n\nfunc GetResponseBody(method, url string, channel *model.Channel, headers http.Header) ([]byte, error) {\n\tclient := &http.Client{}\n\treq, err := http.NewRequest(method, url, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor k := range headers {\n\t\treq.Header.Add(k, headers.Get(k))\n\t}\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn body, nil\n}\n\nfunc updateChannelCloseAIBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"%s/dashboard/billing/credit_grants\", channel.BaseURL)\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAICreditGrants{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalAvailable)\n\treturn response.TotalAvailable, nil\n}\n\nfunc updateChannelOpenAISBBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"https://api.openai-sb.com/sb-api/user/status?api_key=%s\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAISBUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif response.Data == nil {\n\t\treturn 0, errors.New(response.Msg)\n\t}\n\tbalance, err := strconv.ParseFloat(response.Data.Credit, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelAIProxyBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://aiproxy.io/api/report/getUserOverview\"\n\theaders := http.Header{}\n\theaders.Add(\"Api-Key\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, headers)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := AIProxyUserOverviewResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif !response.Success {\n\t\treturn 0, fmt.Errorf(\"code: %d, message: %s\", response.ErrorCode, response.Message)\n\t}\n\tchannel.UpdateBalance(response.Data.TotalPoints)\n\treturn response.Data.TotalPoints, nil\n}\n\nfunc updateChannelAPI2GPTBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.api2gpt.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := API2GPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalRemaining)\n\treturn response.TotalRemaining, nil\n}\n\nfunc updateChannelAIGC2DBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.aigc2d.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := APGC2DGPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalAvailable)\n\treturn response.TotalAvailable, nil\n}\n\nfunc updateChannelBalance(channel *model.Channel) (float64, error) {\n\tbaseURL := common.ChannelBaseURLs[channel.Type]\n\tif channel.BaseURL == \"\" {\n\t\tchannel.BaseURL = baseURL\n\t}\n\tswitch channel.Type {\n\tcase common.ChannelTypeOpenAI:\n\t\tif channel.BaseURL != \"\" {\n\t\t\tbaseURL = channel.BaseURL\n\t\t}\n\tcase common.ChannelTypeAzure:\n\t\treturn 0, errors.New(\"尚未实现\")\n\tcase common.ChannelTypeCustom:\n\t\tbaseURL = channel.BaseURL\n\tcase common.ChannelTypeCloseAI:\n\t\treturn updateChannelCloseAIBalance(channel)\n\tcase common.ChannelTypeOpenAISB:\n\t\treturn updateChannelOpenAISBBalance(channel)\n\tcase common.ChannelTypeAIProxy:\n\t\treturn updateChannelAIProxyBalance(channel)\n\tcase common.ChannelTypeAPI2GPT:\n\t\treturn updateChannelAPI2GPTBalance(channel)\n\tcase common.ChannelTypeAIGC2D:\n\t\treturn updateChannelAIGC2DBalance(channel)\n\tdefault:\n\t\treturn 0, errors.New(\"尚未实现\")\n\t}\n\turl := fmt.Sprintf(\"%s/v1/dashboard/billing/subscription\", baseURL)\n\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tsubscription := OpenAISubscriptionResponse{}\n\terr = json.Unmarshal(body, &subscription)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tnow := time.Now()\n\tstartDate := fmt.Sprintf(\"%s-01\", now.Format(\"2006-01\"))\n\tendDate := now.Format(\"2006-01-02\")\n\tif !subscription.HasPaymentMethod {\n\t\tstartDate = now.AddDate(0, 0, -100).Format(\"2006-01-02\")\n\t}\n\turl = fmt.Sprintf(\"%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s\", baseURL, startDate, endDate)\n\tbody, err = GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tusage := OpenAIUsageResponse{}\n\terr = json.Unmarshal(body, &usage)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbalance := subscription.HardLimitUSD - usage.TotalUsage/100\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc UpdateChannelBalance(c *gin.Context) {\n\tid, err := strconv.Atoi(c.Param(\"id\"))\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tchannel, err := model.GetChannelById(id, true)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tbalance, err := updateChannelBalance(channel)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t\t\"balance\": balance,\n\t})\n\treturn\n}\n\nfunc updateAllChannelsBalance() error {\n\tchannels, err := model.GetAllChannels(0, 0, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, channel := range channels {\n\t\tif channel.Status != common.ChannelStatusEnabled {\n\t\t\tcontinue\n\t\t}\n\t\t// TODO: support Azure\n\t\tif channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {\n\t\t\tcontinue\n\t\t}\n\t\tbalance, err := updateChannelBalance(channel)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t} else {\n\t\t\t// err is nil & balance <= 0 means quota is used up\n\t\t\tif balance <= 0 {\n\t\t\t\tdisableChannel(channel.Id, channel.Name, \"余额不足\")\n\t\t\t}\n\t\t}\n\t\ttime.Sleep(common.RequestInterval)\n\t}\n\treturn nil\n}\n\nfunc UpdateAllChannelsBalance(c *gin.Context) {\n\t// TODO: make it async\n\terr := updateAllChannelsBalance()\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t})\n\treturn\n}\n\nfunc AutomaticallyUpdateChannels(frequency int) {\n\tfor {\n\t\ttime.Sleep(time.Duration(frequency) * time.Minute)\n\t\tcommon.SysLog(\"updating all channels\")\n\t\t_ = updateAllChannelsBalance()\n\t\tcommon.SysLog(\"channels update done\")\n\t}\n}\n"}, "b1b3651e84a624266321425599c29a1b6ed92094": {"t": 1785759405.922681, "n": 8015, "h": "fb58f37331fabe1c", "c": "package controller\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"one-api/common\"\n\t\"one-api/model\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// https://github.com/songquanpeng/one-api/issues/79\n\ntype OpenAISubscriptionResponse struct {\n\tObject string `json:\"object\"`\n\tHasPaymentMethod bool `json:\"has_payment_method\"`\n\tSoftLimitUSD float64 `json:\"soft_limit_usd\"`\n\tHardLimitUSD float64 `json:\"hard_limit_usd\"`\n\tSystemHardLimitUSD float64 `json:\"system_hard_limit_usd\"`\n}\n\ntype OpenAIUsageDailyCost struct {\n\tTimestamp float64 `json:\"timestamp\"`\n\tLineItems []struct {\n\t\tName string `json:\"name\"`\n\t\tCost float64 `json:\"cost\"`\n\t}\n}\n\ntype OpenAIUsageResponse struct {\n\tObject string `json:\"object\"`\n\t//DailyCosts []OpenAIUsageDailyCost `json:\"daily_costs\"`\n\tTotalUsage float64 `json:\"total_usage\"` // unit: 0.01 dollar\n}\n\ntype OpenAISBUsageResponse struct {\n\tMsg string `json:\"msg\"`\n\tData *struct {\n\t\tCredit string `json:\"credit\"`\n\t} `json:\"data\"`\n}\n\ntype AIProxyUserOverviewResponse struct {\n\tSuccess bool `json:\"success\"`\n\tMessage string `json:\"message\"`\n\tErrorCode int `json:\"error_code\"`\n\tData struct {\n\t\tTotalPoints float64 `json:\"totalPoints\"`\n\t} `json:\"data\"`\n}\n\ntype API2GPTUsageResponse struct {\n\tObject string `json:\"object\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n\tTotalRemaining float64 `json:\"total_remaining\"`\n}\n\ntype APGC2DGPTUsageResponse struct {\n\t//Grants interface{} `json:\"grants\"`\n\tObject string `json:\"object\"`\n\tTotalAvailable float64 `json:\"total_available\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n}\n\n// GetAuthHeader get auth header\nfunc GetAuthHeader(token string) http.Header {\n\th := http.Header{}\n\th.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\treturn h\n}\n\nfunc GetResponseBody(method, url string, channel *model.Channel, headers http.Header) ([]byte, error) {\n\tclient := &http.Client{}\n\treq, err := http.NewRequest(method, url, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor k := range headers {\n\t\treq.Header.Add(k, headers.Get(k))\n\t}\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn body, nil\n}\n\nfunc updateChannelOpenAISBBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"https://api.openai-sb.com/sb-api/user/status?api_key=%s\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAISBUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif response.Data == nil {\n\t\treturn 0, errors.New(response.Msg)\n\t}\n\tbalance, err := strconv.ParseFloat(response.Data.Credit, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelAIProxyBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://aiproxy.io/api/report/getUserOverview\"\n\theaders := http.Header{}\n\theaders.Add(\"Api-Key\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, headers)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := AIProxyUserOverviewResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif !response.Success {\n\t\treturn 0, fmt.Errorf(\"code: %d, message: %s\", response.ErrorCode, response.Message)\n\t}\n\tchannel.UpdateBalance(response.Data.TotalPoints)\n\treturn response.Data.TotalPoints, nil\n}\n\nfunc updateChannelAPI2GPTBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.api2gpt.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := API2GPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalRemaining)\n\treturn response.TotalRemaining, nil\n}\n\nfunc updateChannelAIGC2DBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.aigc2d.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := APGC2DGPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalAvailable)\n\treturn response.TotalAvailable, nil\n}\n\nfunc updateChannelBalance(channel *model.Channel) (float64, error) {\n\tbaseURL := common.ChannelBaseURLs[channel.Type]\n\tswitch channel.Type {\n\tcase common.ChannelTypeOpenAI:\n\t\tif channel.BaseURL != \"\" {\n\t\t\tbaseURL = channel.BaseURL\n\t\t}\n\tcase common.ChannelTypeAzure:\n\t\treturn 0, errors.New(\"尚未实现\")\n\tcase common.ChannelTypeCustom:\n\t\tbaseURL = channel.BaseURL\n\tcase common.ChannelTypeOpenAISB:\n\t\treturn updateChannelOpenAISBBalance(channel)\n\tcase common.ChannelTypeAIProxy:\n\t\treturn updateChannelAIProxyBalance(channel)\n\tcase common.ChannelTypeAPI2GPT:\n\t\treturn updateChannelAPI2GPTBalance(channel)\n\tcase common.ChannelTypeAIGC2D:\n\t\treturn updateChannelAIGC2DBalance(channel)\n\tdefault:\n\t\treturn 0, errors.New(\"尚未实现\")\n\t}\n\turl := fmt.Sprintf(\"%s/v1/dashboard/billing/subscription\", baseURL)\n\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tsubscription := OpenAISubscriptionResponse{}\n\terr = json.Unmarshal(body, &subscription)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tnow := time.Now()\n\tstartDate := fmt.Sprintf(\"%s-01\", now.Format(\"2006-01\"))\n\tendDate := now.Format(\"2006-01-02\")\n\tif !subscription.HasPaymentMethod {\n\t\tstartDate = now.AddDate(0, 0, -100).Format(\"2006-01-02\")\n\t}\n\turl = fmt.Sprintf(\"%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s\", baseURL, startDate, endDate)\n\tbody, err = GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tusage := OpenAIUsageResponse{}\n\terr = json.Unmarshal(body, &usage)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbalance := subscription.HardLimitUSD - usage.TotalUsage/100\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc UpdateChannelBalance(c *gin.Context) {\n\tid, err := strconv.Atoi(c.Param(\"id\"))\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tchannel, err := model.GetChannelById(id, true)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tbalance, err := updateChannelBalance(channel)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t\t\"balance\": balance,\n\t})\n\treturn\n}\n\nfunc updateAllChannelsBalance() error {\n\tchannels, err := model.GetAllChannels(0, 0, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, channel := range channels {\n\t\tif channel.Status != common.ChannelStatusEnabled {\n\t\t\tcontinue\n\t\t}\n\t\t// TODO: support Azure\n\t\tif channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {\n\t\t\tcontinue\n\t\t}\n\t\tbalance, err := updateChannelBalance(channel)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t} else {\n\t\t\t// err is nil & balance <= 0 means quota is used up\n\t\t\tif balance <= 0 {\n\t\t\t\tdisableChannel(channel.Id, channel.Name, \"余额不足\")\n\t\t\t}\n\t\t}\n\t\ttime.Sleep(common.RequestInterval)\n\t}\n\treturn nil\n}\n\nfunc UpdateAllChannelsBalance(c *gin.Context) {\n\t// TODO: make it async\n\terr := updateAllChannelsBalance()\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t})\n\treturn\n}\n\nfunc AutomaticallyUpdateChannels(frequency int) {\n\tfor {\n\t\ttime.Sleep(time.Duration(frequency) * time.Minute)\n\t\tcommon.SysLog(\"updating all channels\")\n\t\t_ = updateAllChannelsBalance()\n\t\tcommon.SysLog(\"channels update done\")\n\t}\n}\n"}, "4463224f0494f573e28f646fc24a5d76e16294aa": {"t": 1785759407.3853626, "n": 7216, "h": "b6c2654e5130b77c", "c": "package controller\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"one-api/common\"\n\t\"one-api/model\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// https://github.com/songquanpeng/one-api/issues/79\n\ntype OpenAISubscriptionResponse struct {\n\tObject string `json:\"object\"`\n\tHasPaymentMethod bool `json:\"has_payment_method\"`\n\tSoftLimitUSD float64 `json:\"soft_limit_usd\"`\n\tHardLimitUSD float64 `json:\"hard_limit_usd\"`\n\tSystemHardLimitUSD float64 `json:\"system_hard_limit_usd\"`\n}\n\ntype OpenAIUsageDailyCost struct {\n\tTimestamp float64 `json:\"timestamp\"`\n\tLineItems []struct {\n\t\tName string `json:\"name\"`\n\t\tCost float64 `json:\"cost\"`\n\t}\n}\n\ntype OpenAIUsageResponse struct {\n\tObject string `json:\"object\"`\n\t//DailyCosts []OpenAIUsageDailyCost `json:\"daily_costs\"`\n\tTotalUsage float64 `json:\"total_usage\"` // unit: 0.01 dollar\n}\n\ntype OpenAISBUsageResponse struct {\n\tMsg string `json:\"msg\"`\n\tData *struct {\n\t\tCredit string `json:\"credit\"`\n\t} `json:\"data\"`\n}\n\ntype AIProxyUserOverviewResponse struct {\n\tSuccess bool `json:\"success\"`\n\tMessage string `json:\"message\"`\n\tErrorCode int `json:\"error_code\"`\n\tData struct {\n\t\tTotalPoints float64 `json:\"totalPoints\"`\n\t} `json:\"data\"`\n}\n\ntype API2GPTUsageResponse struct {\n\tObject string `json:\"object\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n\tTotalRemaining float64 `json:\"total_remaining\"`\n}\n\n// GetAuthHeader get auth header\nfunc GetAuthHeader(token string) http.Header {\n\th := http.Header{}\n\th.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\treturn h\n}\n\nfunc GetResponseBody(method, url string, channel *model.Channel, headers http.Header) ([]byte, error) {\n\tclient := &http.Client{}\n\treq, err := http.NewRequest(method, url, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor k := range headers {\n\t\treq.Header.Add(k, headers.Get(k))\n\t}\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn body, nil\n}\n\nfunc updateChannelOpenAISBBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"https://api.openai-sb.com/sb-api/user/status?api_key=%s\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAISBUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif response.Data == nil {\n\t\treturn 0, errors.New(response.Msg)\n\t}\n\tbalance, err := strconv.ParseFloat(response.Data.Credit, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelAIProxyBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://aiproxy.io/api/report/getUserOverview\"\n\theaders := http.Header{}\n\theaders.Add(\"Api-Key\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, headers)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := AIProxyUserOverviewResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif !response.Success {\n\t\treturn 0, fmt.Errorf(\"code: %d, message: %s\", response.ErrorCode, response.Message)\n\t}\n\tchannel.UpdateBalance(response.Data.TotalPoints)\n\treturn response.Data.TotalPoints, nil\n}\n\nfunc updateChannelAPI2GPTBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.api2gpt.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := API2GPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalRemaining)\n\treturn response.TotalRemaining, nil\n}\n\nfunc updateChannelBalance(channel *model.Channel) (float64, error) {\n\tbaseURL := common.ChannelBaseURLs[channel.Type]\n\tswitch channel.Type {\n\tcase common.ChannelTypeOpenAI:\n\t\tif channel.BaseURL != \"\" {\n\t\t\tbaseURL = channel.BaseURL\n\t\t}\n\tcase common.ChannelTypeAzure:\n\t\treturn 0, errors.New(\"尚未实现\")\n\tcase common.ChannelTypeCustom:\n\t\tbaseURL = channel.BaseURL\n\tcase common.ChannelTypeOpenAISB:\n\t\treturn updateChannelOpenAISBBalance(channel)\n\tcase common.ChannelTypeAIProxy:\n\t\treturn updateChannelAIProxyBalance(channel)\n\tcase common.ChannelTypeAPI2GPT:\n\t\treturn updateChannelAPI2GPTBalance(channel)\n\tdefault:\n\t\treturn 0, errors.New(\"尚未实现\")\n\t}\n\turl := fmt.Sprintf(\"%s/v1/dashboard/billing/subscription\", baseURL)\n\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tsubscription := OpenAISubscriptionResponse{}\n\terr = json.Unmarshal(body, &subscription)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tnow := time.Now()\n\tstartDate := fmt.Sprintf(\"%s-01\", now.Format(\"2006-01\"))\n\tendDate := now.Format(\"2006-01-02\")\n\tif !subscription.HasPaymentMethod {\n\t\tstartDate = now.AddDate(0, 0, -100).Format(\"2006-01-02\")\n\t}\n\turl = fmt.Sprintf(\"%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s\", baseURL, startDate, endDate)\n\tbody, err = GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tusage := OpenAIUsageResponse{}\n\terr = json.Unmarshal(body, &usage)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbalance := subscription.HardLimitUSD - usage.TotalUsage/100\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc UpdateChannelBalance(c *gin.Context) {\n\tid, err := strconv.Atoi(c.Param(\"id\"))\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tchannel, err := model.GetChannelById(id, true)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tbalance, err := updateChannelBalance(channel)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t\t\"balance\": balance,\n\t})\n\treturn\n}\n\nfunc updateAllChannelsBalance() error {\n\tchannels, err := model.GetAllChannels(0, 0, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, channel := range channels {\n\t\tif channel.Status != common.ChannelStatusEnabled {\n\t\t\tcontinue\n\t\t}\n\t\t// TODO: support Azure\n\t\tif channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {\n\t\t\tcontinue\n\t\t}\n\t\tbalance, err := updateChannelBalance(channel)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t} else {\n\t\t\t// err is nil & balance <= 0 means quota is used up\n\t\t\tif balance <= 0 {\n\t\t\t\tdisableChannel(channel.Id, channel.Name, \"余额不足\")\n\t\t\t}\n\t\t}\n\t\ttime.Sleep(common.RequestInterval)\n\t}\n\treturn nil\n}\n\nfunc UpdateAllChannelsBalance(c *gin.Context) {\n\t// TODO: make it async\n\terr := updateAllChannelsBalance()\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t})\n\treturn\n}\n\nfunc AutomaticallyUpdateChannels(frequency int) {\n\tfor {\n\t\ttime.Sleep(time.Duration(frequency) * time.Minute)\n\t\tcommon.SysLog(\"updating all channels\")\n\t\t_ = updateAllChannelsBalance()\n\t\tcommon.SysLog(\"channels update done\")\n\t}\n}\n"}, "dc94765d3291faf0e168ecb49c562840ce4799b0": {"t": 1785759409.0604396, "n": 6949, "h": "7bde3a4709bca967", "c": "package controller\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"one-api/common\"\n\t\"one-api/model\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// https://github.com/songquanpeng/one-api/issues/79\n\ntype OpenAISubscriptionResponse struct {\n\tObject string `json:\"object\"`\n\tHasPaymentMethod bool `json:\"has_payment_method\"`\n\tSoftLimitUSD float64 `json:\"soft_limit_usd\"`\n\tHardLimitUSD float64 `json:\"hard_limit_usd\"`\n\tSystemHardLimitUSD float64 `json:\"system_hard_limit_usd\"`\n}\n\ntype OpenAIUsageDailyCost struct {\n\tTimestamp float64 `json:\"timestamp\"`\n\tLineItems []struct {\n\t\tName string `json:\"name\"`\n\t\tCost float64 `json:\"cost\"`\n\t}\n}\n\ntype OpenAIUsageResponse struct {\n\tObject string `json:\"object\"`\n\t//DailyCosts []OpenAIUsageDailyCost `json:\"daily_costs\"`\n\tTotalUsage float64 `json:\"total_usage\"` // unit: 0.01 dollar\n}\n\ntype OpenAISBUsageResponse struct {\n\tMsg string `json:\"msg\"`\n\tData *struct {\n\t\tCredit string `json:\"credit\"`\n\t} `json:\"data\"`\n}\n\ntype AIProxyUserOverviewResponse struct {\n\tSuccess bool `json:\"success\"`\n\tMessage string `json:\"message\"`\n\tErrorCode int `json:\"error_code\"`\n\tData struct {\n\t\tTotalPoints float64 `json:\"totalPoints\"`\n\t} `json:\"data\"`\n}\n\ntype API2GPTUsageResponse struct {\n\tObject string `json:\"object\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n\tTotalRemaining float64 `json:\"total_remaining\"`\n}\n\n// GetAuthHeader get auth header\nfunc GetAuthHeader(token string) http.Header {\n\th := http.Header{}\n\th.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\treturn h\n}\n\nfunc GetResponseBody(method, url string, channel *model.Channel, headers http.Header) ([]byte, error) {\n\tclient := &http.Client{}\n\treq, err := http.NewRequest(method, url, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor k := range headers {\n\t\treq.Header.Add(k, headers.Get(k))\n\t}\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn body, nil\n}\n\nfunc updateChannelOpenAISBBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"https://api.openai-sb.com/sb-api/user/status?api_key=%s\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAISBUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif response.Data == nil {\n\t\treturn 0, errors.New(response.Msg)\n\t}\n\tbalance, err := strconv.ParseFloat(response.Data.Credit, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelAIProxyBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://aiproxy.io/api/report/getUserOverview\"\n\theaders := http.Header{}\n\theaders.Add(\"Api-Key\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, headers)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := AIProxyUserOverviewResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif !response.Success {\n\t\treturn 0, fmt.Errorf(\"code: %d, message: %s\", response.ErrorCode, response.Message)\n\t}\n\tchannel.UpdateBalance(response.Data.TotalPoints)\n\treturn response.Data.TotalPoints, nil\n}\n\nfunc updateChannelAPI2GPTBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.api2gpt.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := API2GPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalRemaining)\n\treturn response.TotalRemaining, nil\n}\n\nfunc updateChannelBalance(channel *model.Channel) (float64, error) {\n\tbaseURL := common.ChannelBaseURLs[channel.Type]\n\tswitch channel.Type {\n\tcase common.ChannelTypeOpenAI:\n\t\tif channel.BaseURL != \"\" {\n\t\t\tbaseURL = channel.BaseURL\n\t\t}\n\tcase common.ChannelTypeAzure:\n\t\treturn 0, errors.New(\"尚未实现\")\n\tcase common.ChannelTypeCustom:\n\t\tbaseURL = channel.BaseURL\n\tcase common.ChannelTypeOpenAISB:\n\t\treturn updateChannelOpenAISBBalance(channel)\n\tcase common.ChannelTypeAIProxy:\n\t\treturn updateChannelAIProxyBalance(channel)\n\tcase common.ChannelTypeAPI2GPT:\n\t\treturn updateChannelAPI2GPTBalance(channel)\n\tdefault:\n\t\treturn 0, errors.New(\"尚未实现\")\n\t}\n\turl := fmt.Sprintf(\"%s/v1/dashboard/billing/subscription\", baseURL)\n\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tsubscription := OpenAISubscriptionResponse{}\n\terr = json.Unmarshal(body, &subscription)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tnow := time.Now()\n\tstartDate := fmt.Sprintf(\"%s-01\", now.Format(\"2006-01\"))\n\tendDate := now.Format(\"2006-01-02\")\n\tif !subscription.HasPaymentMethod {\n\t\tstartDate = now.AddDate(0, 0, -100).Format(\"2006-01-02\")\n\t}\n\turl = fmt.Sprintf(\"%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s\", baseURL, startDate, endDate)\n\tbody, err = GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tusage := OpenAIUsageResponse{}\n\terr = json.Unmarshal(body, &usage)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbalance := subscription.HardLimitUSD - usage.TotalUsage/100\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc UpdateChannelBalance(c *gin.Context) {\n\tid, err := strconv.Atoi(c.Param(\"id\"))\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tchannel, err := model.GetChannelById(id, true)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tbalance, err := updateChannelBalance(channel)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t\t\"balance\": balance,\n\t})\n\treturn\n}\n\nfunc updateAllChannelsBalance() error {\n\tchannels, err := model.GetAllChannels(0, 0, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, channel := range channels {\n\t\tif channel.Status != common.ChannelStatusEnabled {\n\t\t\tcontinue\n\t\t}\n\t\t// TODO: support Azure\n\t\tif channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {\n\t\t\tcontinue\n\t\t}\n\t\tbalance, err := updateChannelBalance(channel)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t} else {\n\t\t\t// err is nil & balance <= 0 means quota is used up\n\t\t\tif balance <= 0 {\n\t\t\t\tdisableChannel(channel.Id, channel.Name, \"余额不足\")\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc UpdateAllChannelsBalance(c *gin.Context) {\n\t// TODO: make it async\n\terr := updateAllChannelsBalance()\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t})\n\treturn\n}\n"}, "a43b1e2add1c8ae417a84b19780b8195e788d9f9": {"t": 1785759411.0370703, "n": 6970, "h": "bc212995d00ab4d7", "c": "package controller\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"one-api/common\"\n\t\"one-api/model\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// https://github.com/songquanpeng/one-api/issues/79\n\ntype OpenAISubscriptionResponse struct {\n\tObject string `json:\"object\"`\n\tHasPaymentMethod bool `json:\"has_payment_method\"`\n\tSoftLimitUSD float64 `json:\"soft_limit_usd\"`\n\tHardLimitUSD float64 `json:\"hard_limit_usd\"`\n\tSystemHardLimitUSD float64 `json:\"system_hard_limit_usd\"`\n}\n\ntype OpenAIUsageDailyCost struct {\n\tTimestamp float64 `json:\"timestamp\"`\n\tLineItems []struct {\n\t\tName string `json:\"name\"`\n\t\tCost float64 `json:\"cost\"`\n\t}\n}\n\ntype OpenAIUsageResponse struct {\n\tObject string `json:\"object\"`\n\t//DailyCosts []OpenAIUsageDailyCost `json:\"daily_costs\"`\n\tTotalUsage float64 `json:\"total_usage\"` // unit: 0.01 dollar\n}\n\ntype OpenAISBUsageResponse struct {\n\tMsg string `json:\"msg\"`\n\tData *struct {\n\t\tCredit string `json:\"credit\"`\n\t} `json:\"data\"`\n}\n\ntype AIProxyUserOverviewResponse struct {\n\tSuccess bool `json:\"success\"`\n\tMessage string `json:\"message\"`\n\tErrorCode int `json:\"error_code\"`\n\tData struct {\n\t\tTotalPoints float64 `json:\"totalPoints\"`\n\t} `json:\"data\"`\n}\n\ntype API2GPTUsageResponse struct {\n\tObject string `json:\"object\"`\n\tTotalGranted float64 `json:\"total_granted\"`\n\tTotalUsed float64 `json:\"total_used\"`\n\tTotalRemaining float64 `json:\"total_remaining\"`\n}\n\n// GetAuthHeader get auth header\nfunc GetAuthHeader(token string) http.Header {\n\th := http.Header{}\n\th.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\treturn h\n}\n\nfunc GetResponseBody(method, url string, channel *model.Channel, headers http.Header) ([]byte, error) {\n\tclient := &http.Client{}\n\treq, err := http.NewRequest(method, url, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor k := range headers {\n\t\treq.Header.Add(k, headers.Get(k))\n\t}\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn body, nil\n}\n\nfunc updateChannelOpenAISBBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"https://api.openai-sb.com/sb-api/user/status?api_key=%s\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAISBUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif response.Data == nil {\n\t\treturn 0, errors.New(response.Msg)\n\t}\n\tbalance, err := strconv.ParseFloat(response.Data.Credit, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelAIProxyBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://aiproxy.io/api/report/getUserOverview\"\n\theaders := http.Header{}\n\theaders.Add(\"Api-Key\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, headers)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := AIProxyUserOverviewResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif !response.Success {\n\t\treturn 0, fmt.Errorf(\"code: %d, message: %s\", response.ErrorCode, response.Message)\n\t}\n\tchannel.UpdateBalance(response.Data.TotalPoints)\n\treturn response.Data.TotalPoints, nil\n}\n\nfunc updateChannelAPI2GPTBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.api2gpt.com/dashboard/billing/credit_grants\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := API2GPTUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tfmt.Print(response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(response.TotalRemaining)\n\treturn response.TotalRemaining, nil\n}\n\nfunc updateChannelBalance(channel *model.Channel) (float64, error) {\n\tbaseURL := common.ChannelBaseURLs[channel.Type]\n\tswitch channel.Type {\n\tcase common.ChannelTypeOpenAI:\n\t\tif channel.BaseURL != \"\" {\n\t\t\tbaseURL = channel.BaseURL\n\t\t}\n\tcase common.ChannelTypeAzure:\n\t\treturn 0, errors.New(\"尚未实现\")\n\tcase common.ChannelTypeCustom:\n\t\tbaseURL = channel.BaseURL\n\tcase common.ChannelTypeOpenAISB:\n\t\treturn updateChannelOpenAISBBalance(channel)\n\tcase common.ChannelTypeAIProxy:\n\t\treturn updateChannelAIProxyBalance(channel)\n\tcase common.ChannelTypeAPI2GPT:\n\t\treturn updateChannelAPI2GPTBalance(channel)\n\tdefault:\n\t\treturn 0, errors.New(\"尚未实现\")\n\t}\n\turl := fmt.Sprintf(\"%s/v1/dashboard/billing/subscription\", baseURL)\n\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tsubscription := OpenAISubscriptionResponse{}\n\terr = json.Unmarshal(body, &subscription)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tnow := time.Now()\n\tstartDate := fmt.Sprintf(\"%s-01\", now.Format(\"2006-01\"))\n\tendDate := now.Format(\"2006-01-02\")\n\tif !subscription.HasPaymentMethod {\n\t\tstartDate = now.AddDate(0, 0, -100).Format(\"2006-01-02\")\n\t}\n\turl = fmt.Sprintf(\"%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s\", baseURL, startDate, endDate)\n\tbody, err = GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tusage := OpenAIUsageResponse{}\n\terr = json.Unmarshal(body, &usage)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbalance := subscription.HardLimitUSD - usage.TotalUsage/100\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc UpdateChannelBalance(c *gin.Context) {\n\tid, err := strconv.Atoi(c.Param(\"id\"))\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tchannel, err := model.GetChannelById(id, true)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tbalance, err := updateChannelBalance(channel)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t\t\"balance\": balance,\n\t})\n\treturn\n}\n\nfunc updateAllChannelsBalance() error {\n\tchannels, err := model.GetAllChannels(0, 0, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, channel := range channels {\n\t\tif channel.Status != common.ChannelStatusEnabled {\n\t\t\tcontinue\n\t\t}\n\t\t// TODO: support Azure\n\t\tif channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {\n\t\t\tcontinue\n\t\t}\n\t\tbalance, err := updateChannelBalance(channel)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t} else {\n\t\t\t// err is nil & balance <= 0 means quota is used up\n\t\t\tif balance <= 0 {\n\t\t\t\tdisableChannel(channel.Id, channel.Name, \"余额不足\")\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc UpdateAllChannelsBalance(c *gin.Context) {\n\t// TODO: make it async\n\terr := updateAllChannelsBalance()\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t})\n\treturn\n}\n"}, "b7d71b4f0aef9e9f68c0e3a2dd709bd0ec0c2f67": {"t": 1785759413.2435045, "n": 6195, "h": "59f8283798983ebc", "c": "package controller\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"one-api/common\"\n\t\"one-api/model\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// https://github.com/songquanpeng/one-api/issues/79\n\ntype OpenAISubscriptionResponse struct {\n\tObject string `json:\"object\"`\n\tHasPaymentMethod bool `json:\"has_payment_method\"`\n\tSoftLimitUSD float64 `json:\"soft_limit_usd\"`\n\tHardLimitUSD float64 `json:\"hard_limit_usd\"`\n\tSystemHardLimitUSD float64 `json:\"system_hard_limit_usd\"`\n}\n\ntype OpenAIUsageDailyCost struct {\n\tTimestamp float64 `json:\"timestamp\"`\n\tLineItems []struct {\n\t\tName string `json:\"name\"`\n\t\tCost float64 `json:\"cost\"`\n\t}\n}\n\ntype OpenAIUsageResponse struct {\n\tObject string `json:\"object\"`\n\t//DailyCosts []OpenAIUsageDailyCost `json:\"daily_costs\"`\n\tTotalUsage float64 `json:\"total_usage\"` // unit: 0.01 dollar\n}\n\ntype OpenAISBUsageResponse struct {\n\tMsg string `json:\"msg\"`\n\tData *struct {\n\t\tCredit string `json:\"credit\"`\n\t} `json:\"data\"`\n}\n\ntype AIProxyUserOverviewResponse struct {\n\tSuccess bool `json:\"success\"`\n\tMessage string `json:\"message\"`\n\tErrorCode int `json:\"error_code\"`\n\tData struct {\n\t\tTotalPoints float64 `json:\"totalPoints\"`\n\t} `json:\"data\"`\n}\n\n// GetAuthHeader get auth header\nfunc GetAuthHeader(token string) http.Header {\n\th := http.Header{}\n\th.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\treturn h\n}\n\nfunc GetResponseBody(method, url string, channel *model.Channel, headers http.Header) ([]byte, error) {\n\tclient := &http.Client{}\n\treq, err := http.NewRequest(method, url, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor k := range headers {\n\t\treq.Header.Add(k, headers.Get(k))\n\t}\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn body, nil\n}\n\nfunc updateChannelOpenAISBBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"https://api.openai-sb.com/sb-api/user/status?api_key=%s\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAISBUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif response.Data == nil {\n\t\treturn 0, errors.New(response.Msg)\n\t}\n\tbalance, err := strconv.ParseFloat(response.Data.Credit, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelAIProxyBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://aiproxy.io/api/report/getUserOverview\"\n\theaders := http.Header{}\n\theaders.Add(\"Api-Key\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel, headers)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := AIProxyUserOverviewResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif !response.Success {\n\t\treturn 0, fmt.Errorf(\"code: %d, message: %s\", response.ErrorCode, response.Message)\n\t}\n\tchannel.UpdateBalance(response.Data.TotalPoints)\n\treturn response.Data.TotalPoints, nil\n}\n\nfunc updateChannelBalance(channel *model.Channel) (float64, error) {\n\tbaseURL := common.ChannelBaseURLs[channel.Type]\n\tswitch channel.Type {\n\tcase common.ChannelTypeOpenAI:\n\t\tif channel.BaseURL != \"\" {\n\t\t\tbaseURL = channel.BaseURL\n\t\t}\n\tcase common.ChannelTypeAzure:\n\t\treturn 0, errors.New(\"尚未实现\")\n\tcase common.ChannelTypeCustom:\n\t\tbaseURL = channel.BaseURL\n\tcase common.ChannelTypeOpenAISB:\n\t\treturn updateChannelOpenAISBBalance(channel)\n\tcase common.ChannelTypeAIProxy:\n\t\treturn updateChannelAIProxyBalance(channel)\n\tdefault:\n\t\treturn 0, errors.New(\"尚未实现\")\n\t}\n\turl := fmt.Sprintf(\"%s/v1/dashboard/billing/subscription\", baseURL)\n\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tsubscription := OpenAISubscriptionResponse{}\n\terr = json.Unmarshal(body, &subscription)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tnow := time.Now()\n\tstartDate := fmt.Sprintf(\"%s-01\", now.Format(\"2006-01\"))\n\tendDate := now.Format(\"2006-01-02\")\n\tif !subscription.HasPaymentMethod {\n\t\tstartDate = now.AddDate(0, 0, -100).Format(\"2006-01-02\")\n\t}\n\turl = fmt.Sprintf(\"%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s\", baseURL, startDate, endDate)\n\tbody, err = GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tusage := OpenAIUsageResponse{}\n\terr = json.Unmarshal(body, &usage)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbalance := subscription.HardLimitUSD - usage.TotalUsage/100\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc UpdateChannelBalance(c *gin.Context) {\n\tid, err := strconv.Atoi(c.Param(\"id\"))\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tchannel, err := model.GetChannelById(id, true)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tbalance, err := updateChannelBalance(channel)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t\t\"balance\": balance,\n\t})\n\treturn\n}\n\nfunc updateAllChannelsBalance() error {\n\tchannels, err := model.GetAllChannels(0, 0, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, channel := range channels {\n\t\tif channel.Status != common.ChannelStatusEnabled {\n\t\t\tcontinue\n\t\t}\n\t\t// TODO: support Azure\n\t\tif channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {\n\t\t\tcontinue\n\t\t}\n\t\tbalance, err := updateChannelBalance(channel)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t} else {\n\t\t\t// err is nil & balance <= 0 means quota is used up\n\t\t\tif balance <= 0 {\n\t\t\t\tdisableChannel(channel.Id, channel.Name, \"余额不足\")\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc UpdateAllChannelsBalance(c *gin.Context) {\n\t// TODO: make it async\n\terr := updateAllChannelsBalance()\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t})\n\treturn\n}\n"}, "47ca449e327f66a78ade36ddd3a3ddf48f9da6c4": {"t": 1785759414.9435544, "n": 5014, "h": "6e6de0ca7ec31344", "c": "package controller\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/gin-gonic/gin\"\n\t\"io\"\n\t\"net/http\"\n\t\"one-api/common\"\n\t\"one-api/model\"\n\t\"strconv\"\n\t\"time\"\n)\n\n// https://github.com/songquanpeng/one-api/issues/79\n\ntype OpenAISubscriptionResponse struct {\n\tObject string `json:\"object\"`\n\tHasPaymentMethod bool `json:\"has_payment_method\"`\n\tSoftLimitUSD float64 `json:\"soft_limit_usd\"`\n\tHardLimitUSD float64 `json:\"hard_limit_usd\"`\n\tSystemHardLimitUSD float64 `json:\"system_hard_limit_usd\"`\n}\n\ntype OpenAIUsageDailyCost struct {\n\tTimestamp float64 `json:\"timestamp\"`\n\tLineItems []struct {\n\t\tName string `json:\"name\"`\n\t\tCost float64 `json:\"cost\"`\n\t}\n}\n\ntype OpenAIUsageResponse struct {\n\tObject string `json:\"object\"`\n\t//DailyCosts []OpenAIUsageDailyCost `json:\"daily_costs\"`\n\tTotalUsage float64 `json:\"total_usage\"` // unit: 0.01 dollar\n}\n\ntype OpenAISBUsageResponse struct {\n\tMsg string `json:\"msg\"`\n\tData *struct {\n\t\tCredit string `json:\"credit\"`\n\t} `json:\"data\"`\n}\n\nfunc GetResponseBody(method, url string, channel *model.Channel) ([]byte, error) {\n\tclient := &http.Client{}\n\treq, err := http.NewRequest(method, url, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tauth := fmt.Sprintf(\"Bearer %s\", channel.Key)\n\treq.Header.Add(\"Authorization\", auth)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn body, nil\n}\n\nfunc updateChannelOpenAISBBalance(channel *model.Channel) (float64, error) {\n\turl := fmt.Sprintf(\"https://api.openai-sb.com/sb-api/user/status?api_key=%s\", channel.Key)\n\tbody, err := GetResponseBody(\"GET\", url, channel)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenAISBUsageResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif response.Data == nil {\n\t\treturn 0, errors.New(response.Msg)\n\t}\n\tbalance, err := strconv.ParseFloat(response.Data.Credit, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelBalance(channel *model.Channel) (float64, error) {\n\tbaseURL := common.ChannelBaseURLs[channel.Type]\n\tswitch channel.Type {\n\tcase common.ChannelTypeOpenAI:\n\t\tif channel.BaseURL != \"\" {\n\t\t\tbaseURL = channel.BaseURL\n\t\t}\n\tcase common.ChannelTypeAzure:\n\t\treturn 0, errors.New(\"尚未实现\")\n\tcase common.ChannelTypeCustom:\n\t\tbaseURL = channel.BaseURL\n\tcase common.ChannelTypeOpenAISB:\n\t\treturn updateChannelOpenAISBBalance(channel)\n\tdefault:\n\t\treturn 0, errors.New(\"尚未实现\")\n\t}\n\turl := fmt.Sprintf(\"%s/v1/dashboard/billing/subscription\", baseURL)\n\n\tbody, err := GetResponseBody(\"GET\", url, channel)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tsubscription := OpenAISubscriptionResponse{}\n\terr = json.Unmarshal(body, &subscription)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tnow := time.Now()\n\tstartDate := fmt.Sprintf(\"%s-01\", now.Format(\"2006-01\"))\n\tendDate := now.Format(\"2006-01-02\")\n\tif !subscription.HasPaymentMethod {\n\t\tstartDate = now.AddDate(0, 0, -100).Format(\"2006-01-02\")\n\t}\n\turl = fmt.Sprintf(\"%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s\", baseURL, startDate, endDate)\n\tbody, err = GetResponseBody(\"GET\", url, channel)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tusage := OpenAIUsageResponse{}\n\terr = json.Unmarshal(body, &usage)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbalance := subscription.HardLimitUSD - usage.TotalUsage/100\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc UpdateChannelBalance(c *gin.Context) {\n\tid, err := strconv.Atoi(c.Param(\"id\"))\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tchannel, err := model.GetChannelById(id, true)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tbalance, err := updateChannelBalance(channel)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t\t\"balance\": balance,\n\t})\n\treturn\n}\n\nfunc updateAllChannelsBalance() error {\n\tchannels, err := model.GetAllChannels(0, 0, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, channel := range channels {\n\t\tif channel.Status != common.ChannelStatusEnabled {\n\t\t\tcontinue\n\t\t}\n\t\t// TODO: support Azure\n\t\tif channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {\n\t\t\tcontinue\n\t\t}\n\t\tbalance, err := updateChannelBalance(channel)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t} else {\n\t\t\t// err is nil & balance <= 0 means quota is used up\n\t\t\tif balance <= 0 {\n\t\t\t\tdisableChannel(channel.Id, channel.Name, \"余额不足\")\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc UpdateAllChannelsBalance(c *gin.Context) {\n\t// TODO: make it async\n\terr := updateAllChannelsBalance()\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t})\n\treturn\n}\n"}, "145bb14cb27beadff23475e673aba6803b5c1763": {"t": 1785759417.5113173, "n": 4241, "h": "63f1ec86af544448", "c": "package controller\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/gin-gonic/gin\"\n\t\"io\"\n\t\"net/http\"\n\t\"one-api/common\"\n\t\"one-api/model\"\n\t\"strconv\"\n\t\"time\"\n)\n\n// https://github.com/songquanpeng/one-api/issues/79\n\ntype OpenAISubscriptionResponse struct {\n\tObject string `json:\"object\"`\n\tHasPaymentMethod bool `json:\"has_payment_method\"`\n\tSoftLimitUSD float64 `json:\"soft_limit_usd\"`\n\tHardLimitUSD float64 `json:\"hard_limit_usd\"`\n\tSystemHardLimitUSD float64 `json:\"system_hard_limit_usd\"`\n}\n\ntype OpenAIUsageDailyCost struct {\n\tTimestamp float64 `json:\"timestamp\"`\n\tLineItems []struct {\n\t\tName string `json:\"name\"`\n\t\tCost float64 `json:\"cost\"`\n\t}\n}\n\ntype OpenAIUsageResponse struct {\n\tObject string `json:\"object\"`\n\t//DailyCosts []OpenAIUsageDailyCost `json:\"daily_costs\"`\n\tTotalUsage float64 `json:\"total_usage\"` // unit: 0.01 dollar\n}\n\nfunc updateChannelBalance(channel *model.Channel) (float64, error) {\n\tbaseURL := common.ChannelBaseURLs[channel.Type]\n\tswitch channel.Type {\n\tcase common.ChannelTypeOpenAI:\n\t\tif channel.BaseURL != \"\" {\n\t\t\tbaseURL = channel.BaseURL\n\t\t}\n\tcase common.ChannelTypeAzure:\n\t\treturn 0, errors.New(\"尚未实现\")\n\tcase common.ChannelTypeCustom:\n\t\tbaseURL = channel.BaseURL\n\tdefault:\n\t\treturn 0, errors.New(\"尚未实现\")\n\t}\n\turl := fmt.Sprintf(\"%s/v1/dashboard/billing/subscription\", baseURL)\n\n\tclient := &http.Client{}\n\treq, err := http.NewRequest(\"GET\", url, nil)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tauth := fmt.Sprintf(\"Bearer %s\", channel.Key)\n\treq.Header.Add(\"Authorization\", auth)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tsubscription := OpenAISubscriptionResponse{}\n\terr = json.Unmarshal(body, &subscription)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tnow := time.Now()\n\tstartDate := fmt.Sprintf(\"%s-01\", now.Format(\"2006-01\"))\n\tendDate := now.Format(\"2006-01-02\")\n\tif !subscription.HasPaymentMethod {\n\t\tstartDate = now.AddDate(0, 0, -100).Format(\"2006-01-02\")\n\t}\n\turl = fmt.Sprintf(\"%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s\", baseURL, startDate, endDate)\n\treq, err = http.NewRequest(\"GET\", url, nil)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treq.Header.Add(\"Authorization\", auth)\n\tres, err = client.Do(req)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbody, err = io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tusage := OpenAIUsageResponse{}\n\terr = json.Unmarshal(body, &usage)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbalance := subscription.HardLimitUSD - usage.TotalUsage/100\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc UpdateChannelBalance(c *gin.Context) {\n\tid, err := strconv.Atoi(c.Param(\"id\"))\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tchannel, err := model.GetChannelById(id, true)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tbalance, err := updateChannelBalance(channel)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t\t\"balance\": balance,\n\t})\n\treturn\n}\n\nfunc updateAllChannelsBalance() error {\n\tchannels, err := model.GetAllChannels(0, 0, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, channel := range channels {\n\t\tif channel.Status != common.ChannelStatusEnabled {\n\t\t\tcontinue\n\t\t}\n\t\t// TODO: support Azure\n\t\tif channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {\n\t\t\tcontinue\n\t\t}\n\t\tbalance, err := updateChannelBalance(channel)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t} else {\n\t\t\t// err is nil & balance <= 0 means quota is used up\n\t\t\tif balance <= 0 {\n\t\t\t\tdisableChannel(channel.Id, channel.Name, \"余额不足\")\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc UpdateAllChannelsBalance(c *gin.Context) {\n\t// TODO: make it async\n\terr := updateAllChannelsBalance()\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t})\n\treturn\n}\n"}, "1e1c6a828f0d14b921fd0946916b0ec344719509": {"t": 1785759419.3286548, "n": 4195, "h": "f5dcaf66371402a1", "c": "package controller\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/gin-gonic/gin\"\n\t\"io\"\n\t\"net/http\"\n\t\"one-api/common\"\n\t\"one-api/model\"\n\t\"strconv\"\n\t\"time\"\n)\n\n// https://github.com/songquanpeng/one-api/issues/79\n\ntype OpenAISubscriptionResponse struct {\n\tObject string `json:\"object\"`\n\tHasPaymentMethod bool `json:\"has_payment_method\"`\n\tSoftLimitUSD float64 `json:\"soft_limit_usd\"`\n\tHardLimitUSD float64 `json:\"hard_limit_usd\"`\n\tSystemHardLimitUSD float64 `json:\"system_hard_limit_usd\"`\n}\n\ntype OpenAIUsageDailyCost struct {\n\tTimestamp float64 `json:\"timestamp\"`\n\tLineItems []struct {\n\t\tName string `json:\"name\"`\n\t\tCost float64 `json:\"cost\"`\n\t}\n}\n\ntype OpenAIUsageResponse struct {\n\tObject string `json:\"object\"`\n\t//DailyCosts []OpenAIUsageDailyCost `json:\"daily_costs\"`\n\tTotalUsage float64 `json:\"total_usage\"` // unit: 0.01 dollar\n}\n\nfunc updateChannelBalance(channel *model.Channel) (float64, error) {\n\tbaseURL := common.ChannelBaseURLs[channel.Type]\n\tswitch channel.Type {\n\tcase common.ChannelTypeOpenAI:\n\t\t// do nothing\n\tcase common.ChannelTypeAzure:\n\t\treturn 0, errors.New(\"尚未实现\")\n\tcase common.ChannelTypeCustom:\n\t\tbaseURL = channel.BaseURL\n\tdefault:\n\t\treturn 0, errors.New(\"尚未实现\")\n\t}\n\turl := fmt.Sprintf(\"%s/v1/dashboard/billing/subscription\", baseURL)\n\n\tclient := &http.Client{}\n\treq, err := http.NewRequest(\"GET\", url, nil)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tauth := fmt.Sprintf(\"Bearer %s\", channel.Key)\n\treq.Header.Add(\"Authorization\", auth)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tsubscription := OpenAISubscriptionResponse{}\n\terr = json.Unmarshal(body, &subscription)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tnow := time.Now()\n\tstartDate := fmt.Sprintf(\"%s-01\", now.Format(\"2006-01\"))\n\tendDate := now.Format(\"2006-01-02\")\n\tif !subscription.HasPaymentMethod {\n\t\tstartDate = now.AddDate(0, 0, -100).Format(\"2006-01-02\")\n\t}\n\turl = fmt.Sprintf(\"%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s\", baseURL, startDate, endDate)\n\treq, err = http.NewRequest(\"GET\", url, nil)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treq.Header.Add(\"Authorization\", auth)\n\tres, err = client.Do(req)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbody, err = io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tusage := OpenAIUsageResponse{}\n\terr = json.Unmarshal(body, &usage)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbalance := subscription.HardLimitUSD - usage.TotalUsage/100\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc UpdateChannelBalance(c *gin.Context) {\n\tid, err := strconv.Atoi(c.Param(\"id\"))\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tchannel, err := model.GetChannelById(id, true)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tbalance, err := updateChannelBalance(channel)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t\t\"balance\": balance,\n\t})\n\treturn\n}\n\nfunc updateAllChannelsBalance() error {\n\tchannels, err := model.GetAllChannels(0, 0, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, channel := range channels {\n\t\tif channel.Status != common.ChannelStatusEnabled {\n\t\t\tcontinue\n\t\t}\n\t\t// TODO: support Azure\n\t\tif channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {\n\t\t\tcontinue\n\t\t}\n\t\tbalance, err := updateChannelBalance(channel)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t} else {\n\t\t\t// err is nil & balance <= 0 means quota is used up\n\t\t\tif balance <= 0 {\n\t\t\t\tdisableChannel(channel.Id, channel.Name, \"余额不足\")\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc UpdateAllChannelsBalance(c *gin.Context) {\n\t// TODO: make it async\n\terr := updateAllChannelsBalance()\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t})\n\treturn\n}\n"}, "98f1a627f0709a38c779dfb10037ef77bbc998ad": {"t": 1785759420.6933312, "n": 4106, "h": "5771dff83e9c9523", "c": "package controller\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/gin-gonic/gin\"\n\t\"io\"\n\t\"net/http\"\n\t\"one-api/common\"\n\t\"one-api/model\"\n\t\"strconv\"\n\t\"time\"\n)\n\n// https://github.com/songquanpeng/one-api/issues/79\n\ntype OpenAISubscriptionResponse struct {\n\tObject string `json:\"object\"`\n\tHasPaymentMethod bool `json:\"has_payment_method\"`\n\tSoftLimitUSD float64 `json:\"soft_limit_usd\"`\n\tHardLimitUSD float64 `json:\"hard_limit_usd\"`\n\tSystemHardLimitUSD float64 `json:\"system_hard_limit_usd\"`\n}\n\ntype OpenAIUsageDailyCost struct {\n\tTimestamp float64 `json:\"timestamp\"`\n\tLineItems []struct {\n\t\tName string `json:\"name\"`\n\t\tCost float64 `json:\"cost\"`\n\t}\n}\n\ntype OpenAIUsageResponse struct {\n\tObject string `json:\"object\"`\n\t//DailyCosts []OpenAIUsageDailyCost `json:\"daily_costs\"`\n\tTotalUsage float64 `json:\"total_usage\"` // unit: 0.01 dollar\n}\n\nfunc updateChannelBalance(channel *model.Channel) (float64, error) {\n\tbaseURL := common.ChannelBaseURLs[channel.Type]\n\tswitch channel.Type {\n\tcase common.ChannelTypeAzure:\n\t\treturn 0, errors.New(\"尚未实现\")\n\tcase common.ChannelTypeCustom:\n\t\tbaseURL = channel.BaseURL\n\t}\n\turl := fmt.Sprintf(\"%s/v1/dashboard/billing/subscription\", baseURL)\n\n\tclient := &http.Client{}\n\treq, err := http.NewRequest(\"GET\", url, nil)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tauth := fmt.Sprintf(\"Bearer %s\", channel.Key)\n\treq.Header.Add(\"Authorization\", auth)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tsubscription := OpenAISubscriptionResponse{}\n\terr = json.Unmarshal(body, &subscription)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tnow := time.Now()\n\tstartDate := fmt.Sprintf(\"%s-01\", now.Format(\"2006-01\"))\n\tendDate := now.Format(\"2006-01-02\")\n\tif !subscription.HasPaymentMethod {\n\t\tstartDate = now.AddDate(0, 0, -100).Format(\"2006-01-02\")\n\t}\n\turl = fmt.Sprintf(\"%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s\", baseURL, startDate, endDate)\n\treq, err = http.NewRequest(\"GET\", url, nil)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treq.Header.Add(\"Authorization\", auth)\n\tres, err = client.Do(req)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbody, err = io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tusage := OpenAIUsageResponse{}\n\terr = json.Unmarshal(body, &usage)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbalance := subscription.HardLimitUSD - usage.TotalUsage/100\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc UpdateChannelBalance(c *gin.Context) {\n\tid, err := strconv.Atoi(c.Param(\"id\"))\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tchannel, err := model.GetChannelById(id, true)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tbalance, err := updateChannelBalance(channel)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t\t\"balance\": balance,\n\t})\n\treturn\n}\n\nfunc updateAllChannelsBalance() error {\n\tchannels, err := model.GetAllChannels(0, 0, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, channel := range channels {\n\t\tif channel.Status != common.ChannelStatusEnabled {\n\t\t\tcontinue\n\t\t}\n\t\t// TODO: support Azure\n\t\tif channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {\n\t\t\tcontinue\n\t\t}\n\t\tbalance, err := updateChannelBalance(channel)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t} else {\n\t\t\t// err is nil & balance <= 0 means quota is used up\n\t\t\tif balance <= 0 {\n\t\t\t\tdisableChannel(channel.Id, channel.Name, \"余额不足\")\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc UpdateAllChannelsBalance(c *gin.Context) {\n\t// TODO: make it async\n\terr := updateAllChannelsBalance()\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t})\n\treturn\n}\n"}, "333e4216d282188fe9a7fe08c8b503f8cf26dce7": {"t": 1785759421.9350255, "n": 4007, "h": "e501699b99d0e82d", "c": "package controller\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/gin-gonic/gin\"\n\t\"io\"\n\t\"net/http\"\n\t\"one-api/common\"\n\t\"one-api/model\"\n\t\"strconv\"\n\t\"time\"\n)\n\n// https://github.com/songquanpeng/one-api/issues/79\n\ntype OpenAISubscriptionResponse struct {\n\tObject string `json:\"object\"`\n\tHasPaymentMethod bool `json:\"has_payment_method\"`\n\tSoftLimitUSD float64 `json:\"soft_limit_usd\"`\n\tHardLimitUSD float64 `json:\"hard_limit_usd\"`\n\tSystemHardLimitUSD float64 `json:\"system_hard_limit_usd\"`\n}\n\ntype OpenAIUsageDailyCost struct {\n\tTimestamp float64 `json:\"timestamp\"`\n\tLineItems []struct {\n\t\tName string `json:\"name\"`\n\t\tCost float64 `json:\"cost\"`\n\t}\n}\n\ntype OpenAIUsageResponse struct {\n\tObject string `json:\"object\"`\n\t//DailyCosts []OpenAIUsageDailyCost `json:\"daily_costs\"`\n\tTotalUsage float64 `json:\"total_usage\"` // unit: 0.01 dollar\n}\n\nfunc updateChannelBalance(channel *model.Channel) (float64, error) {\n\tbaseURL := common.ChannelBaseURLs[channel.Type]\n\tswitch channel.Type {\n\tcase common.ChannelTypeAzure:\n\t\treturn 0, errors.New(\"尚未实现\")\n\tcase common.ChannelTypeCustom:\n\t\tbaseURL = channel.BaseURL\n\t}\n\turl := fmt.Sprintf(\"%s/v1/dashboard/billing/subscription\", baseURL)\n\n\tclient := &http.Client{}\n\treq, err := http.NewRequest(\"GET\", url, nil)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tauth := fmt.Sprintf(\"Bearer %s\", channel.Key)\n\treq.Header.Add(\"Authorization\", auth)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tsubscription := OpenAISubscriptionResponse{}\n\terr = json.Unmarshal(body, &subscription)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tnow := time.Now()\n\tstartDate := fmt.Sprintf(\"%s-01\", now.Format(\"2006-01\"))\n\tendDate := now.Format(\"2006-01-02\")\n\turl = fmt.Sprintf(\"%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s\", baseURL, startDate, endDate)\n\treq, err = http.NewRequest(\"GET\", url, nil)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treq.Header.Add(\"Authorization\", auth)\n\tres, err = client.Do(req)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbody, err = io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tusage := OpenAIUsageResponse{}\n\terr = json.Unmarshal(body, &usage)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbalance := subscription.HardLimitUSD - usage.TotalUsage/100\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc UpdateChannelBalance(c *gin.Context) {\n\tid, err := strconv.Atoi(c.Param(\"id\"))\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tchannel, err := model.GetChannelById(id, true)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tbalance, err := updateChannelBalance(channel)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t\t\"balance\": balance,\n\t})\n\treturn\n}\n\nfunc updateAllChannelsBalance() error {\n\tchannels, err := model.GetAllChannels(0, 0, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, channel := range channels {\n\t\tif channel.Status != common.ChannelStatusEnabled {\n\t\t\tcontinue\n\t\t}\n\t\t// TODO: support Azure\n\t\tif channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {\n\t\t\tcontinue\n\t\t}\n\t\tbalance, err := updateChannelBalance(channel)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t} else {\n\t\t\t// err is nil & balance <= 0 means quota is used up\n\t\t\tif balance <= 0 {\n\t\t\t\tdisableChannel(channel.Id, channel.Name, \"余额不足\")\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc UpdateAllChannelsBalance(c *gin.Context) {\n\t// TODO: make it async\n\terr := updateAllChannelsBalance()\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t})\n\treturn\n}\n"}, "f9f42997b2176558ac033407c822d853485bb779": {"t": 1785759423.5068843, "n": 4014, "h": "d9e39aa79eec1721", "c": "package controller\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/gin-gonic/gin\"\n\t\"io\"\n\t\"net/http\"\n\t\"one-api/common\"\n\t\"one-api/model\"\n\t\"strconv\"\n\t\"time\"\n)\n\n// https://github.com/songquanpeng/one-api/issues/79\n\ntype OpenAISubscriptionResponse struct {\n\tObject string `json:\"object\"`\n\tHasPaymentMethod bool `json:\"has_payment_method\"`\n\tSoftLimitUSD float64 `json:\"soft_limit_usd\"`\n\tHardLimitUSD float64 `json:\"hard_limit_usd\"`\n\tSystemHardLimitUSD float64 `json:\"system_hard_limit_usd\"`\n}\n\ntype OpenAIUsageDailyCost struct {\n\tTimestamp float64 `json:\"timestamp\"`\n\tLineItems []struct {\n\t\tName string `json:\"name\"`\n\t\tCost float64 `json:\"cost\"`\n\t}\n}\n\ntype OpenAIUsageResponse struct {\n\tObject string `json:\"object\"`\n\t//DailyCosts []OpenAIUsageDailyCost `json:\"daily_costs\"`\n\tTotalUsage float64 `json:\"total_usage\"` // unit: 0.01 dollar\n}\n\nfunc updateChannelBalance(channel *model.Channel) (float64, error) {\n\tbaseURL := common.ChannelBaseURLs[channel.Type]\n\tswitch channel.Type {\n\tcase common.ChannelTypeAzure:\n\t\treturn 0, errors.New(\"尚未实现\")\n\tcase common.ChannelTypeCustom:\n\t\tbaseURL = channel.BaseURL\n\t}\n\turl := fmt.Sprintf(\"%s/v1/dashboard/billing/subscription\", baseURL)\n\n\tclient := &http.Client{}\n\treq, err := http.NewRequest(\"GET\", url, nil)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tauth := fmt.Sprintf(\"Bearer %s\", channel.Key)\n\treq.Header.Add(\"Authorization\", auth)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tsubscription := OpenAISubscriptionResponse{}\n\terr = json.Unmarshal(body, &subscription)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tnow := time.Now()\n\tstartDate := fmt.Sprintf(\"%s-01\", now.Format(\"2006-01\"))\n\t//endDate := now.Format(\"2006-01-02\")\n\turl = fmt.Sprintf(\"%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s\", baseURL, startDate, \"2023-06-01\")\n\treq, err = http.NewRequest(\"GET\", url, nil)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treq.Header.Add(\"Authorization\", auth)\n\tres, err = client.Do(req)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbody, err = io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tusage := OpenAIUsageResponse{}\n\terr = json.Unmarshal(body, &usage)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbalance := subscription.HardLimitUSD - usage.TotalUsage/100\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc UpdateChannelBalance(c *gin.Context) {\n\tid, err := strconv.Atoi(c.Param(\"id\"))\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tchannel, err := model.GetChannelById(id, true)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tbalance, err := updateChannelBalance(channel)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t\t\"balance\": balance,\n\t})\n\treturn\n}\n\nfunc updateAllChannelsBalance() error {\n\tchannels, err := model.GetAllChannels(0, 0, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, channel := range channels {\n\t\tif channel.Status != common.ChannelStatusEnabled {\n\t\t\tcontinue\n\t\t}\n\t\t// TODO: support Azure\n\t\tif channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {\n\t\t\tcontinue\n\t\t}\n\t\tbalance, err := updateChannelBalance(channel)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t} else {\n\t\t\t// err is nil & balance <= 0 means quota is used up\n\t\t\tif balance <= 0 {\n\t\t\t\tdisableChannel(channel.Id, channel.Name, \"余额不足\")\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc UpdateAllChannelsBalance(c *gin.Context) {\n\t// TODO: make it async\n\terr := updateAllChannelsBalance()\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t})\n\treturn\n}\n"}, "d4794fc0519600eb274f7728067e3ecacf30562b": {"t": 1785759425.4851277, "n": 3881, "h": "2b5d846d46fdfb8d", "c": "package controller\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/gin-gonic/gin\"\n\t\"io\"\n\t\"net/http\"\n\t\"one-api/common\"\n\t\"one-api/model\"\n\t\"strconv\"\n\t\"time\"\n)\n\n// https://github.com/songquanpeng/one-api/issues/79\n\ntype OpenAISubscriptionResponse struct {\n\tObject string `json:\"object\"`\n\tHasPaymentMethod bool `json:\"has_payment_method\"`\n\tSoftLimitUSD float64 `json:\"soft_limit_usd\"`\n\tHardLimitUSD float64 `json:\"hard_limit_usd\"`\n\tSystemHardLimitUSD float64 `json:\"system_hard_limit_usd\"`\n}\n\ntype OpenAIUsageDailyCost struct {\n\tTimestamp float64 `json:\"timestamp\"`\n\tLineItems []struct {\n\t\tName string `json:\"name\"`\n\t\tCost float64 `json:\"cost\"`\n\t}\n}\n\ntype OpenAIUsageResponse struct {\n\tObject string `json:\"object\"`\n\t//DailyCosts []OpenAIUsageDailyCost `json:\"daily_costs\"`\n\tTotalUsage float64 `json:\"total_usage\"` // unit: 0.01 dollar\n}\n\nfunc updateChannelBalance(channel *model.Channel) (float64, error) {\n\tbaseURL := common.ChannelBaseURLs[channel.Type]\n\tswitch channel.Type {\n\tcase common.ChannelTypeAzure:\n\t\treturn 0, errors.New(\"尚未实现\")\n\tcase common.ChannelTypeCustom:\n\t\tbaseURL = channel.BaseURL\n\t}\n\turl := fmt.Sprintf(\"%s/v1/dashboard/billing/subscription\", baseURL)\n\n\tclient := &http.Client{}\n\treq, err := http.NewRequest(\"GET\", url, nil)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tauth := fmt.Sprintf(\"Bearer %s\", channel.Key)\n\treq.Header.Add(\"Authorization\", auth)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tsubscription := OpenAISubscriptionResponse{}\n\terr = json.Unmarshal(body, &subscription)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tnow := time.Now()\n\tstartDate := fmt.Sprintf(\"%s-01\", now.Format(\"2006-01\"))\n\t//endDate := now.Format(\"2006-01-02\")\n\turl = fmt.Sprintf(\"%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s\", baseURL, startDate, \"2023-06-01\")\n\treq, err = http.NewRequest(\"GET\", url, nil)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treq.Header.Add(\"Authorization\", auth)\n\tres, err = client.Do(req)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbody, err = io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tusage := OpenAIUsageResponse{}\n\terr = json.Unmarshal(body, &usage)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbalance := subscription.HardLimitUSD - usage.TotalUsage/100\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc UpdateChannelBalance(c *gin.Context) {\n\tid, err := strconv.Atoi(c.Param(\"id\"))\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tchannel, err := model.GetChannelById(id, true)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tbalance, err := updateChannelBalance(channel)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t\t\"balance\": balance,\n\t})\n\treturn\n}\n\nfunc updateAllChannelsBalance() error {\n\tchannels, err := model.GetAllChannels(0, 0, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, channel := range channels {\n\t\tif channel.Status != common.ChannelStatusEnabled {\n\t\t\tcontinue\n\t\t}\n\t\tbalance, err := updateChannelBalance(channel)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t} else {\n\t\t\t// err is nil & balance <= 0 means quota is used up\n\t\t\tif balance <= 0 {\n\t\t\t\tdisableChannel(channel.Id, channel.Name, \"余额不足\")\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc UpdateAllChannelsBalance(c *gin.Context) {\n\t// TODO: make it async\n\terr := updateAllChannelsBalance()\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t})\n\treturn\n}\n"}, "17b7646c12f281902b8cb4813ff17b0b8926bf76": {"t": 1785759427.6842043, "n": 3420, "h": "31e32cdd49fdbf76", "c": "package controller\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/gin-gonic/gin\"\n\t\"io\"\n\t\"net/http\"\n\t\"one-api/common\"\n\t\"one-api/model\"\n\t\"strconv\"\n\t\"time\"\n)\n\ntype OpenAISubscriptionResponse struct {\n\tHasPaymentMethod bool `json:\"has_payment_method\"`\n\tHardLimitUSD float64 `json:\"hard_limit_usd\"`\n}\n\ntype OpenAIUsageResponse struct {\n\tTotalUsage float64 `json:\"total_usage\"` // unit: 0.01 dollar\n}\n\nfunc updateChannelBalance(channel *model.Channel) (float64, error) {\n\tbaseURL := common.ChannelBaseURLs[channel.Type]\n\tswitch channel.Type {\n\tcase common.ChannelTypeAzure:\n\t\treturn 0, errors.New(\"尚未实现\")\n\tcase common.ChannelTypeCustom:\n\t\tbaseURL = channel.BaseURL\n\t}\n\turl := fmt.Sprintf(\"%s/v1/dashboard/billing/subscription\", baseURL)\n\n\tclient := &http.Client{}\n\treq, err := http.NewRequest(\"GET\", url, nil)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tauth := fmt.Sprintf(\"Bearer %s\", channel.Key)\n\treq.Header.Add(\"Authorization\", auth)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tsubscription := OpenAISubscriptionResponse{}\n\terr = json.Unmarshal(body, &subscription)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tnow := time.Now()\n\tstartDate := fmt.Sprintf(\"%s-01\", now.Format(\"2006-01\"))\n\t//endDate := now.Format(\"2006-01-02\")\n\turl = fmt.Sprintf(\"%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s\", baseURL, startDate, \"2023-06-01\")\n\treq, err = http.NewRequest(\"GET\", url, nil)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treq.Header.Add(\"Authorization\", auth)\n\tres, err = client.Do(req)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbody, err = io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tusage := OpenAIUsageResponse{}\n\terr = json.Unmarshal(body, &usage)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbalance := subscription.HardLimitUSD - usage.TotalUsage/100\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc UpdateChannelBalance(c *gin.Context) {\n\tid, err := strconv.Atoi(c.Param(\"id\"))\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tchannel, err := model.GetChannelById(id, true)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tbalance, err := updateChannelBalance(channel)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t\t\"balance\": balance,\n\t})\n\treturn\n}\n\nfunc updateAllChannelsBalance() error {\n\tchannels, err := model.GetAllChannels(0, 0, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, channel := range channels {\n\t\tif channel.Status != common.ChannelStatusEnabled {\n\t\t\tcontinue\n\t\t}\n\t\tbalance, err := updateChannelBalance(channel)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t} else {\n\t\t\t// err is nil & balance <= 0 means quota is used up\n\t\t\tif balance <= 0 {\n\t\t\t\tdisableChannel(channel.Id, channel.Name, \"余额不足\")\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc UpdateAllChannelsBalance(c *gin.Context) {\n\t// TODO: make it async\n\terr := updateAllChannelsBalance()\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t})\n\treturn\n}\n"}, "171b8185041479d9530bbacb69ce8bfc430bb027": {"t": 1785759429.0165637, "n": 3360, "h": "c61c53684c0a7ea2", "c": "package controller\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/gin-gonic/gin\"\n\t\"io\"\n\t\"net/http\"\n\t\"one-api/common\"\n\t\"one-api/model\"\n\t\"strconv\"\n\t\"time\"\n)\n\ntype OpenAISubscriptionResponse struct {\n\tHasPaymentMethod bool `json:\"has_payment_method\"`\n\tHardLimitUSD float64 `json:\"hard_limit_usd\"`\n}\n\ntype OpenAIUsageResponse struct {\n\tTotalUsage float64 `json:\"total_usage\"` // unit: 0.01 dollar\n}\n\nfunc updateChannelBalance(channel *model.Channel) (float64, error) {\n\tbaseURL := common.ChannelBaseURLs[channel.Type]\n\tswitch channel.Type {\n\tcase common.ChannelTypeAzure:\n\t\treturn 0, errors.New(\"尚未实现\")\n\t}\n\turl := fmt.Sprintf(\"%s/v1/dashboard/billing/subscription\", baseURL)\n\n\tclient := &http.Client{}\n\treq, err := http.NewRequest(\"GET\", url, nil)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tauth := fmt.Sprintf(\"Bearer %s\", channel.Key)\n\treq.Header.Add(\"Authorization\", auth)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tsubscription := OpenAISubscriptionResponse{}\n\terr = json.Unmarshal(body, &subscription)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tnow := time.Now()\n\tstartDate := fmt.Sprintf(\"%s-01\", now.Format(\"2006-01\"))\n\t//endDate := now.Format(\"2006-01-02\")\n\turl = fmt.Sprintf(\"%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s\", baseURL, startDate, \"2023-06-01\")\n\treq, err = http.NewRequest(\"GET\", url, nil)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treq.Header.Add(\"Authorization\", auth)\n\tres, err = client.Do(req)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbody, err = io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\terr = res.Body.Close()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tusage := OpenAIUsageResponse{}\n\terr = json.Unmarshal(body, &usage)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbalance := subscription.HardLimitUSD - usage.TotalUsage/100\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc UpdateChannelBalance(c *gin.Context) {\n\tid, err := strconv.Atoi(c.Param(\"id\"))\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tchannel, err := model.GetChannelById(id, true)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tbalance, err := updateChannelBalance(channel)\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t\t\"balance\": balance,\n\t})\n\treturn\n}\n\nfunc updateAllChannelsBalance() error {\n\tchannels, err := model.GetAllChannels(0, 0, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, channel := range channels {\n\t\tif channel.Status != common.ChannelStatusEnabled {\n\t\t\tcontinue\n\t\t}\n\t\tbalance, err := updateChannelBalance(channel)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t} else {\n\t\t\t// err is nil & balance <= 0 means quota is used up\n\t\t\tif balance <= 0 {\n\t\t\t\tdisableChannel(channel.Id, channel.Name, \"余额不足\")\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc UpdateAllChannelsBalance(c *gin.Context) {\n\t// TODO: make it async\n\terr := updateAllChannelsBalance()\n\tif err != nil {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": err.Error(),\n\t\t})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"success\": true,\n\t\t\"message\": \"\",\n\t})\n\treturn\n}\n"}}}}