1 line
39 KiB
JSON
1 line
39 KiB
JSON
{"repo": "aiprodcoder/mixapi", "files": {"constant/channel.go": {"751a7a0af94a7ec42bc802458445b2c78357bccc": {"t": 1785746051.5734103, "n": 4203, "h": "a0b895ae8cf3b9bf", "c": "package constant\n\nconst (\n\tChannelTypeUnknown = 0\n\tChannelTypeOpenAI = 1\n\tChannelTypeMidjourney = 2\n\tChannelTypeAzure = 3\n\tChannelTypeOllama = 4\n\tChannelTypeMidjourneyPlus = 5\n\tChannelTypeOpenAIMax = 6\n\tChannelTypeOhMyGPT = 7\n\tChannelTypeCustom = 8\n\tChannelTypeAILS = 9\n\tChannelTypeAIProxy = 10\n\tChannelTypePaLM = 11\n\tChannelTypeAPI2GPT = 12\n\tChannelTypeAIGC2D = 13\n\tChannelTypeAnthropic = 14\n\tChannelTypeBaidu = 15\n\tChannelTypeZhipu = 16\n\tChannelTypeAli = 17\n\tChannelTypeXunfei = 18\n\tChannelType360 = 19\n\tChannelTypeOpenRouter = 20\n\tChannelTypeAIProxyLibrary = 21\n\tChannelTypeFastGPT = 22\n\tChannelTypeTencent = 23\n\tChannelTypeGemini = 24\n\tChannelTypeMoonshot = 25\n\tChannelTypeZhipu_v4 = 26\n\tChannelTypePerplexity = 27\n\tChannelTypeLingYiWanWu = 31\n\tChannelTypeAws = 33\n\tChannelTypeCohere = 34\n\tChannelTypeMiniMax = 35\n\tChannelTypeSunoAPI = 36\n\tChannelTypeDify = 37\n\tChannelTypeJina = 38\n\tChannelCloudflare = 39\n\tChannelTypeSiliconFlow = 40\n\tChannelTypeVertexAi = 41\n\tChannelTypeMistral = 42\n\tChannelTypeDeepSeek = 43\n\tChannelTypeMokaAI = 44\n\tChannelTypeVolcEngine = 45\n\tChannelTypeBaiduV2 = 46\n\tChannelTypeXinference = 47\n\tChannelTypeXai = 48\n\tChannelTypeCoze = 49\n\tChannelTypeKling = 50\n\tChannelTypeJimeng = 51\n\tChannelTypeDummy // this one is only for count, do not add any channel after this\n\n)\n\nvar ChannelBaseURLs = []string{\n\t\"\", // 0\n\t\"https://api.openai.com\", // 1\n\t\"https://oa.api2d.net\", // 2\n\t\"\", // 3\n\t\"http://localhost:11434\", // 4\n\t\"https://api.openai-sb.com\", // 5\n\t\"https://api.openaimax.com\", // 6\n\t\"https://api.ohmygpt.com\", // 7\n\t\"\", // 8\n\t\"https://api.caipacity.com\", // 9\n\t\"https://api.aiproxy.io\", // 10\n\t\"\", // 11\n\t\"https://api.api2gpt.com\", // 12\n\t\"https://api.aigc2d.com\", // 13\n\t\"https://api.anthropic.com\", // 14\n\t\"https://aip.baidubce.com\", // 15\n\t\"https://open.bigmodel.cn\", // 16\n\t\"https://dashscope.aliyuncs.com\", // 17\n\t\"\", // 18\n\t\"https://api.360.cn\", // 19\n\t\"https://openrouter.ai/api\", // 20\n\t\"https://api.aiproxy.io\", // 21\n\t\"https://fastgpt.run/api/openapi\", // 22\n\t\"https://hunyuan.tencentcloudapi.com\", //23\n\t\"https://generativelanguage.googleapis.com\", //24\n\t\"https://api.moonshot.cn\", //25\n\t\"https://open.bigmodel.cn\", //26\n\t\"https://api.perplexity.ai\", //27\n\t\"\", //28\n\t\"\", //29\n\t\"\", //30\n\t\"https://api.lingyiwanwu.com\", //31\n\t\"\", //32\n\t\"\", //33\n\t\"https://api.cohere.ai\", //34\n\t\"https://api.minimax.chat\", //35\n\t\"\", //36\n\t\"https://api.dify.ai\", //37\n\t\"https://api.jina.ai\", //38\n\t\"https://api.cloudflare.com\", //39\n\t\"https://api.siliconflow.cn\", //40\n\t\"\", //41\n\t\"https://api.mistral.ai\", //42\n\t\"https://api.deepseek.com\", //43\n\t\"https://api.moka.ai\", //44\n\t\"https://ark.cn-beijing.volces.com\", //45\n\t\"https://qianfan.baidubce.com\", //46\n\t\"\", //47\n\t\"https://api.x.ai\", //48\n\t\"https://api.coze.cn\", //49\n\t\"https://api.klingai.com\", //50\n\t\"https://visual.volcengineapi.com\", //51\n}\n"}, "02c1fe7ae33389ab677c88543eea7fecf6e56b73": {"t": 1785759234.0943146, "n": 4203, "h": "a0b895ae8cf3b9bf", "c": "package constant\n\nconst (\n\tChannelTypeUnknown = 0\n\tChannelTypeOpenAI = 1\n\tChannelTypeMidjourney = 2\n\tChannelTypeAzure = 3\n\tChannelTypeOllama = 4\n\tChannelTypeMidjourneyPlus = 5\n\tChannelTypeOpenAIMax = 6\n\tChannelTypeOhMyGPT = 7\n\tChannelTypeCustom = 8\n\tChannelTypeAILS = 9\n\tChannelTypeAIProxy = 10\n\tChannelTypePaLM = 11\n\tChannelTypeAPI2GPT = 12\n\tChannelTypeAIGC2D = 13\n\tChannelTypeAnthropic = 14\n\tChannelTypeBaidu = 15\n\tChannelTypeZhipu = 16\n\tChannelTypeAli = 17\n\tChannelTypeXunfei = 18\n\tChannelType360 = 19\n\tChannelTypeOpenRouter = 20\n\tChannelTypeAIProxyLibrary = 21\n\tChannelTypeFastGPT = 22\n\tChannelTypeTencent = 23\n\tChannelTypeGemini = 24\n\tChannelTypeMoonshot = 25\n\tChannelTypeZhipu_v4 = 26\n\tChannelTypePerplexity = 27\n\tChannelTypeLingYiWanWu = 31\n\tChannelTypeAws = 33\n\tChannelTypeCohere = 34\n\tChannelTypeMiniMax = 35\n\tChannelTypeSunoAPI = 36\n\tChannelTypeDify = 37\n\tChannelTypeJina = 38\n\tChannelCloudflare = 39\n\tChannelTypeSiliconFlow = 40\n\tChannelTypeVertexAi = 41\n\tChannelTypeMistral = 42\n\tChannelTypeDeepSeek = 43\n\tChannelTypeMokaAI = 44\n\tChannelTypeVolcEngine = 45\n\tChannelTypeBaiduV2 = 46\n\tChannelTypeXinference = 47\n\tChannelTypeXai = 48\n\tChannelTypeCoze = 49\n\tChannelTypeKling = 50\n\tChannelTypeJimeng = 51\n\tChannelTypeDummy // this one is only for count, do not add any channel after this\n\n)\n\nvar ChannelBaseURLs = []string{\n\t\"\", // 0\n\t\"https://api.openai.com\", // 1\n\t\"https://oa.api2d.net\", // 2\n\t\"\", // 3\n\t\"http://localhost:11434\", // 4\n\t\"https://api.openai-sb.com\", // 5\n\t\"https://api.openaimax.com\", // 6\n\t\"https://api.ohmygpt.com\", // 7\n\t\"\", // 8\n\t\"https://api.caipacity.com\", // 9\n\t\"https://api.aiproxy.io\", // 10\n\t\"\", // 11\n\t\"https://api.api2gpt.com\", // 12\n\t\"https://api.aigc2d.com\", // 13\n\t\"https://api.anthropic.com\", // 14\n\t\"https://aip.baidubce.com\", // 15\n\t\"https://open.bigmodel.cn\", // 16\n\t\"https://dashscope.aliyuncs.com\", // 17\n\t\"\", // 18\n\t\"https://api.360.cn\", // 19\n\t\"https://openrouter.ai/api\", // 20\n\t\"https://api.aiproxy.io\", // 21\n\t\"https://fastgpt.run/api/openapi\", // 22\n\t\"https://hunyuan.tencentcloudapi.com\", //23\n\t\"https://generativelanguage.googleapis.com\", //24\n\t\"https://api.moonshot.cn\", //25\n\t\"https://open.bigmodel.cn\", //26\n\t\"https://api.perplexity.ai\", //27\n\t\"\", //28\n\t\"\", //29\n\t\"\", //30\n\t\"https://api.lingyiwanwu.com\", //31\n\t\"\", //32\n\t\"\", //33\n\t\"https://api.cohere.ai\", //34\n\t\"https://api.minimax.chat\", //35\n\t\"\", //36\n\t\"https://api.dify.ai\", //37\n\t\"https://api.jina.ai\", //38\n\t\"https://api.cloudflare.com\", //39\n\t\"https://api.siliconflow.cn\", //40\n\t\"\", //41\n\t\"https://api.mistral.ai\", //42\n\t\"https://api.deepseek.com\", //43\n\t\"https://api.moka.ai\", //44\n\t\"https://ark.cn-beijing.volces.com\", //45\n\t\"https://qianfan.baidubce.com\", //46\n\t\"\", //47\n\t\"https://api.x.ai\", //48\n\t\"https://api.coze.cn\", //49\n\t\"https://api.klingai.com\", //50\n\t\"https://visual.volcengineapi.com\", //51\n}\n"}}, "controller/channel-billing.go": {"751a7a0af94a7ec42bc802458445b2c78357bccc": {"t": 1785746051.8271627, "n": 13682, "h": "a29c4bdd27273aa4", "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/constant\"\n\t\"one-api/model\"\n\t\"one-api/service\"\n\t\"one-api/setting\"\n\t\"one-api/types\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/shopspring/decimal\"\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\ntype OpenRouterCreditResponse struct {\n\tData struct {\n\t\tTotalCredits float64 `json:\"total_credits\"`\n\t\tTotalUsage float64 `json:\"total_usage\"`\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\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 updateChannelOpenRouterBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://openrouter.ai/api/v1/credits\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenRouterCreditResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbalance := response.Data.TotalCredits - response.Data.TotalUsage\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelMoonshotBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.moonshot.cn/v1/users/me/balance\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\ttype MoonshotBalanceData struct {\n\t\tAvailableBalance float64 `json:\"available_balance\"`\n\t\tVoucherBalance float64 `json:\"voucher_balance\"`\n\t\tCashBalance float64 `json:\"cash_balance\"`\n\t}\n\n\ttype MoonshotBalanceResponse struct {\n\t\tCode int `json:\"code\"`\n\t\tData MoonshotBalanceData `json:\"data\"`\n\t\tScode string `json:\"scode\"`\n\t\tStatus bool `json:\"status\"`\n\t}\n\n\tresponse := MoonshotBalanceResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif !response.Status || response.Code != 0 {\n\t\treturn 0, fmt.Errorf(\"failed to update moonshot balance, status: %v, code: %d, scode: %s\", response.Status, response.Code, response.Scode)\n\t}\n\tavailableBalanceCny := response.Data.AvailableBalance\n\tavailableBalanceUsd := decimal.NewFromFloat(availableBalanceCny).Div(decimal.NewFromFloat(setting.Price)).InexactFloat64()\n\tchannel.UpdateBalance(availableBalanceUsd)\n\treturn availableBalanceUsd, nil\n}\n\nfunc updateChannelBalance(channel *model.Channel) (float64, error) {\n\tbaseURL := constant.ChannelBaseURLs[channel.Type]\n\tif channel.GetBaseURL() == \"\" {\n\t\tchannel.BaseURL = &baseURL\n\t}\n\tswitch channel.Type {\n\tcase constant.ChannelTypeOpenAI:\n\t\tif channel.GetBaseURL() != \"\" {\n\t\t\tbaseURL = channel.GetBaseURL()\n\t\t}\n\tcase constant.ChannelTypeAzure:\n\t\treturn 0, errors.New(\"尚未实现\")\n\tcase constant.ChannelTypeCustom:\n\t\tbaseURL = channel.GetBaseURL()\n\t//case common.ChannelTypeOpenAISB:\n\t//\treturn updateChannelOpenAISBBalance(channel)\n\tcase constant.ChannelTypeAIProxy:\n\t\treturn updateChannelAIProxyBalance(channel)\n\tcase constant.ChannelTypeAPI2GPT:\n\t\treturn updateChannelAPI2GPTBalance(channel)\n\tcase constant.ChannelTypeAIGC2D:\n\t\treturn updateChannelAIGC2DBalance(channel)\n\tcase constant.ChannelTypeSiliconFlow:\n\t\treturn updateChannelSiliconFlowBalance(channel)\n\tcase constant.ChannelTypeDeepSeek:\n\t\treturn updateChannelDeepSeekBalance(channel)\n\tcase constant.ChannelTypeOpenRouter:\n\t\treturn updateChannelOpenRouterBalance(channel)\n\tcase constant.ChannelTypeMoonshot:\n\t\treturn updateChannelMoonshotBalance(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\tcommon.ApiError(c, err)\n\t\treturn\n\t}\n\tchannel, err := model.CacheGetChannel(id)\n\tif err != nil {\n\t\tcommon.ApiError(c, err)\n\t\treturn\n\t}\n\tif channel.ChannelInfo.IsMultiKey {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": \"多密钥渠道不支持余额查询\",\n\t\t})\n\t\treturn\n\t}\n\tbalance, err := updateChannelBalance(channel)\n\tif err != nil {\n\t\tcommon.ApiError(c, err)\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}\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\tif channel.ChannelInfo.IsMultiKey {\n\t\t\tcontinue // skip multi-key channels\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(*types.NewChannelError(channel.Id, channel.Type, channel.Name, channel.ChannelInfo.IsMultiKey, \"\", channel.GetAutoBan()), \"余额不足\")\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\tcommon.ApiError(c, err)\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"}, "02c1fe7ae33389ab677c88543eea7fecf6e56b73": {"t": 1785759233.9488802, "n": 13682, "h": "a29c4bdd27273aa4", "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/constant\"\n\t\"one-api/model\"\n\t\"one-api/service\"\n\t\"one-api/setting\"\n\t\"one-api/types\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/shopspring/decimal\"\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\ntype OpenRouterCreditResponse struct {\n\tData struct {\n\t\tTotalCredits float64 `json:\"total_credits\"`\n\t\tTotalUsage float64 `json:\"total_usage\"`\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\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 updateChannelOpenRouterBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://openrouter.ai/api/v1/credits\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tresponse := OpenRouterCreditResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbalance := response.Data.TotalCredits - response.Data.TotalUsage\n\tchannel.UpdateBalance(balance)\n\treturn balance, nil\n}\n\nfunc updateChannelMoonshotBalance(channel *model.Channel) (float64, error) {\n\turl := \"https://api.moonshot.cn/v1/users/me/balance\"\n\tbody, err := GetResponseBody(\"GET\", url, channel, GetAuthHeader(channel.Key))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\ttype MoonshotBalanceData struct {\n\t\tAvailableBalance float64 `json:\"available_balance\"`\n\t\tVoucherBalance float64 `json:\"voucher_balance\"`\n\t\tCashBalance float64 `json:\"cash_balance\"`\n\t}\n\n\ttype MoonshotBalanceResponse struct {\n\t\tCode int `json:\"code\"`\n\t\tData MoonshotBalanceData `json:\"data\"`\n\t\tScode string `json:\"scode\"`\n\t\tStatus bool `json:\"status\"`\n\t}\n\n\tresponse := MoonshotBalanceResponse{}\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif !response.Status || response.Code != 0 {\n\t\treturn 0, fmt.Errorf(\"failed to update moonshot balance, status: %v, code: %d, scode: %s\", response.Status, response.Code, response.Scode)\n\t}\n\tavailableBalanceCny := response.Data.AvailableBalance\n\tavailableBalanceUsd := decimal.NewFromFloat(availableBalanceCny).Div(decimal.NewFromFloat(setting.Price)).InexactFloat64()\n\tchannel.UpdateBalance(availableBalanceUsd)\n\treturn availableBalanceUsd, nil\n}\n\nfunc updateChannelBalance(channel *model.Channel) (float64, error) {\n\tbaseURL := constant.ChannelBaseURLs[channel.Type]\n\tif channel.GetBaseURL() == \"\" {\n\t\tchannel.BaseURL = &baseURL\n\t}\n\tswitch channel.Type {\n\tcase constant.ChannelTypeOpenAI:\n\t\tif channel.GetBaseURL() != \"\" {\n\t\t\tbaseURL = channel.GetBaseURL()\n\t\t}\n\tcase constant.ChannelTypeAzure:\n\t\treturn 0, errors.New(\"尚未实现\")\n\tcase constant.ChannelTypeCustom:\n\t\tbaseURL = channel.GetBaseURL()\n\t//case common.ChannelTypeOpenAISB:\n\t//\treturn updateChannelOpenAISBBalance(channel)\n\tcase constant.ChannelTypeAIProxy:\n\t\treturn updateChannelAIProxyBalance(channel)\n\tcase constant.ChannelTypeAPI2GPT:\n\t\treturn updateChannelAPI2GPTBalance(channel)\n\tcase constant.ChannelTypeAIGC2D:\n\t\treturn updateChannelAIGC2DBalance(channel)\n\tcase constant.ChannelTypeSiliconFlow:\n\t\treturn updateChannelSiliconFlowBalance(channel)\n\tcase constant.ChannelTypeDeepSeek:\n\t\treturn updateChannelDeepSeekBalance(channel)\n\tcase constant.ChannelTypeOpenRouter:\n\t\treturn updateChannelOpenRouterBalance(channel)\n\tcase constant.ChannelTypeMoonshot:\n\t\treturn updateChannelMoonshotBalance(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\tcommon.ApiError(c, err)\n\t\treturn\n\t}\n\tchannel, err := model.CacheGetChannel(id)\n\tif err != nil {\n\t\tcommon.ApiError(c, err)\n\t\treturn\n\t}\n\tif channel.ChannelInfo.IsMultiKey {\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": false,\n\t\t\t\"message\": \"多密钥渠道不支持余额查询\",\n\t\t})\n\t\treturn\n\t}\n\tbalance, err := updateChannelBalance(channel)\n\tif err != nil {\n\t\tcommon.ApiError(c, err)\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}\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\tif channel.ChannelInfo.IsMultiKey {\n\t\t\tcontinue // skip multi-key channels\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(*types.NewChannelError(channel.Id, channel.Type, channel.Name, channel.ChannelInfo.IsMultiKey, \"\", channel.GetAutoBan()), \"余额不足\")\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\tcommon.ApiError(c, err)\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"}}}} |