channel-billing.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. package controller
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "one-api/common"
  9. "one-api/model"
  10. "one-api/service"
  11. "strconv"
  12. "time"
  13. "github.com/gin-gonic/gin"
  14. )
  15. // https://github.com/songquanpeng/one-api/issues/79
  16. type OpenAISubscriptionResponse struct {
  17. Object string `json:"object"`
  18. HasPaymentMethod bool `json:"has_payment_method"`
  19. SoftLimitUSD float64 `json:"soft_limit_usd"`
  20. HardLimitUSD float64 `json:"hard_limit_usd"`
  21. SystemHardLimitUSD float64 `json:"system_hard_limit_usd"`
  22. AccessUntil int64 `json:"access_until"`
  23. }
  24. type OpenAIUsageDailyCost struct {
  25. Timestamp float64 `json:"timestamp"`
  26. LineItems []struct {
  27. Name string `json:"name"`
  28. Cost float64 `json:"cost"`
  29. }
  30. }
  31. type OpenAICreditGrants struct {
  32. Object string `json:"object"`
  33. TotalGranted float64 `json:"total_granted"`
  34. TotalUsed float64 `json:"total_used"`
  35. TotalAvailable float64 `json:"total_available"`
  36. }
  37. type OpenAIUsageResponse struct {
  38. Object string `json:"object"`
  39. //DailyCosts []OpenAIUsageDailyCost `json:"daily_costs"`
  40. TotalUsage float64 `json:"total_usage"` // unit: 0.01 dollar
  41. }
  42. type OpenAISBUsageResponse struct {
  43. Msg string `json:"msg"`
  44. Data *struct {
  45. Credit string `json:"credit"`
  46. } `json:"data"`
  47. }
  48. type AIProxyUserOverviewResponse struct {
  49. Success bool `json:"success"`
  50. Message string `json:"message"`
  51. ErrorCode int `json:"error_code"`
  52. Data struct {
  53. TotalPoints float64 `json:"totalPoints"`
  54. } `json:"data"`
  55. }
  56. type API2GPTUsageResponse struct {
  57. Object string `json:"object"`
  58. TotalGranted float64 `json:"total_granted"`
  59. TotalUsed float64 `json:"total_used"`
  60. TotalRemaining float64 `json:"total_remaining"`
  61. }
  62. type APGC2DGPTUsageResponse struct {
  63. //Grants interface{} `json:"grants"`
  64. Object string `json:"object"`
  65. TotalAvailable float64 `json:"total_available"`
  66. TotalGranted float64 `json:"total_granted"`
  67. TotalUsed float64 `json:"total_used"`
  68. }
  69. type SiliconFlowUsageResponse struct {
  70. Code int `json:"code"`
  71. Message string `json:"message"`
  72. Status bool `json:"status"`
  73. Data struct {
  74. ID string `json:"id"`
  75. Name string `json:"name"`
  76. Image string `json:"image"`
  77. Email string `json:"email"`
  78. IsAdmin bool `json:"isAdmin"`
  79. Balance string `json:"balance"`
  80. Status string `json:"status"`
  81. Introduction string `json:"introduction"`
  82. Role string `json:"role"`
  83. ChargeBalance string `json:"chargeBalance"`
  84. TotalBalance string `json:"totalBalance"`
  85. Category string `json:"category"`
  86. } `json:"data"`
  87. }
  88. type DeepSeekUsageResponse struct {
  89. IsAvailable bool `json:"is_available"`
  90. BalanceInfos []struct {
  91. Currency string `json:"currency"`
  92. TotalBalance string `json:"total_balance"`
  93. GrantedBalance string `json:"granted_balance"`
  94. ToppedUpBalance string `json:"topped_up_balance"`
  95. } `json:"balance_infos"`
  96. }
  97. type OpenRouterResponse struct {
  98. Data struct {
  99. TotalCredits float64 `json:"total_credits"`
  100. TotalUsage float64 `json:"total_usage"`
  101. } `json:"data"`
  102. }
  103. // GetAuthHeader get auth header
  104. func GetAuthHeader(token string) http.Header {
  105. h := http.Header{}
  106. h.Add("Authorization", fmt.Sprintf("Bearer %s", token))
  107. return h
  108. }
  109. func GetResponseBody(method, url string, channel *model.Channel, headers http.Header) ([]byte, error) {
  110. req, err := http.NewRequest(method, url, nil)
  111. if err != nil {
  112. return nil, err
  113. }
  114. for k := range headers {
  115. req.Header.Add(k, headers.Get(k))
  116. }
  117. res, err := service.GetHttpClient().Do(req)
  118. if err != nil {
  119. return nil, err
  120. }
  121. if res.StatusCode != http.StatusOK {
  122. return nil, fmt.Errorf("status code: %d", res.StatusCode)
  123. }
  124. body, err := io.ReadAll(res.Body)
  125. if err != nil {
  126. return nil, err
  127. }
  128. err = res.Body.Close()
  129. if err != nil {
  130. return nil, err
  131. }
  132. return body, nil
  133. }
  134. func updateChannelCloseAIBalance(channel *model.Channel) (float64, error) {
  135. url := fmt.Sprintf("%s/dashboard/billing/credit_grants", channel.GetBaseURL())
  136. body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
  137. if err != nil {
  138. return 0, err
  139. }
  140. response := OpenAICreditGrants{}
  141. err = json.Unmarshal(body, &response)
  142. if err != nil {
  143. return 0, err
  144. }
  145. channel.UpdateBalance(response.TotalAvailable)
  146. return response.TotalAvailable, nil
  147. }
  148. func updateChannelOpenAISBBalance(channel *model.Channel) (float64, error) {
  149. url := fmt.Sprintf("https://api.openai-sb.com/sb-api/user/status?api_key=%s", channel.Key)
  150. body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
  151. if err != nil {
  152. return 0, err
  153. }
  154. response := OpenAISBUsageResponse{}
  155. err = json.Unmarshal(body, &response)
  156. if err != nil {
  157. return 0, err
  158. }
  159. if response.Data == nil {
  160. return 0, errors.New(response.Msg)
  161. }
  162. balance, err := strconv.ParseFloat(response.Data.Credit, 64)
  163. if err != nil {
  164. return 0, err
  165. }
  166. channel.UpdateBalance(balance)
  167. return balance, nil
  168. }
  169. func updateChannelAIProxyBalance(channel *model.Channel) (float64, error) {
  170. url := "https://aiproxy.io/api/report/getUserOverview"
  171. headers := http.Header{}
  172. headers.Add("Api-Key", channel.Key)
  173. body, err := GetResponseBody("GET", url, channel, headers)
  174. if err != nil {
  175. return 0, err
  176. }
  177. response := AIProxyUserOverviewResponse{}
  178. err = json.Unmarshal(body, &response)
  179. if err != nil {
  180. return 0, err
  181. }
  182. if !response.Success {
  183. return 0, fmt.Errorf("code: %d, message: %s", response.ErrorCode, response.Message)
  184. }
  185. channel.UpdateBalance(response.Data.TotalPoints)
  186. return response.Data.TotalPoints, nil
  187. }
  188. func updateChannelAPI2GPTBalance(channel *model.Channel) (float64, error) {
  189. url := "https://api.api2gpt.com/dashboard/billing/credit_grants"
  190. body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
  191. if err != nil {
  192. return 0, err
  193. }
  194. response := API2GPTUsageResponse{}
  195. err = json.Unmarshal(body, &response)
  196. if err != nil {
  197. return 0, err
  198. }
  199. channel.UpdateBalance(response.TotalRemaining)
  200. return response.TotalRemaining, nil
  201. }
  202. func updateChannelSiliconFlowBalance(channel *model.Channel) (float64, error) {
  203. url := "https://api.siliconflow.cn/v1/user/info"
  204. body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
  205. if err != nil {
  206. return 0, err
  207. }
  208. response := SiliconFlowUsageResponse{}
  209. err = json.Unmarshal(body, &response)
  210. if err != nil {
  211. return 0, err
  212. }
  213. if response.Code != 20000 {
  214. return 0, fmt.Errorf("code: %d, message: %s", response.Code, response.Message)
  215. }
  216. balance, err := strconv.ParseFloat(response.Data.TotalBalance, 64)
  217. if err != nil {
  218. return 0, err
  219. }
  220. channel.UpdateBalance(balance)
  221. return balance, nil
  222. }
  223. func updateChannelDeepSeekBalance(channel *model.Channel) (float64, error) {
  224. url := "https://api.deepseek.com/user/balance"
  225. body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
  226. if err != nil {
  227. return 0, err
  228. }
  229. response := DeepSeekUsageResponse{}
  230. err = json.Unmarshal(body, &response)
  231. if err != nil {
  232. return 0, err
  233. }
  234. index := -1
  235. for i, balanceInfo := range response.BalanceInfos {
  236. if balanceInfo.Currency == "CNY" {
  237. index = i
  238. break
  239. }
  240. }
  241. if index == -1 {
  242. return 0, errors.New("currency CNY not found")
  243. }
  244. balance, err := strconv.ParseFloat(response.BalanceInfos[index].TotalBalance, 64)
  245. if err != nil {
  246. return 0, err
  247. }
  248. channel.UpdateBalance(balance)
  249. return balance, nil
  250. }
  251. func updateChannelAIGC2DBalance(channel *model.Channel) (float64, error) {
  252. url := "https://api.aigc2d.com/dashboard/billing/credit_grants"
  253. body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
  254. if err != nil {
  255. return 0, err
  256. }
  257. response := APGC2DGPTUsageResponse{}
  258. err = json.Unmarshal(body, &response)
  259. if err != nil {
  260. return 0, err
  261. }
  262. channel.UpdateBalance(response.TotalAvailable)
  263. return response.TotalAvailable, nil
  264. }
  265. func updateChannelOpenRouterBalance(channel *model.Channel) (float64, error) {
  266. url := "https://openrouter.ai/api/v1/credits"
  267. body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
  268. if err != nil {
  269. return 0, err
  270. }
  271. response := OpenRouterResponse{}
  272. err = json.Unmarshal(body, &response)
  273. if err != nil {
  274. return 0, err
  275. }
  276. balance := response.Data.TotalCredits - response.Data.TotalUsage
  277. channel.UpdateBalance(balance)
  278. return balance, nil
  279. }
  280. func updateChannelBalance(channel *model.Channel) (float64, error) {
  281. baseURL := common.ChannelBaseURLs[channel.Type]
  282. if channel.GetBaseURL() == "" {
  283. channel.BaseURL = &baseURL
  284. }
  285. switch channel.Type {
  286. case common.ChannelTypeOpenAI:
  287. if channel.GetBaseURL() != "" {
  288. baseURL = channel.GetBaseURL()
  289. }
  290. case common.ChannelTypeAzure:
  291. return 0, errors.New("尚未实现")
  292. case common.ChannelTypeCustom:
  293. baseURL = channel.GetBaseURL()
  294. //case common.ChannelTypeOpenAISB:
  295. // return updateChannelOpenAISBBalance(channel)
  296. case common.ChannelTypeAIProxy:
  297. return updateChannelAIProxyBalance(channel)
  298. case common.ChannelTypeAPI2GPT:
  299. return updateChannelAPI2GPTBalance(channel)
  300. case common.ChannelTypeAIGC2D:
  301. return updateChannelAIGC2DBalance(channel)
  302. case common.ChannelTypeSiliconFlow:
  303. return updateChannelSiliconFlowBalance(channel)
  304. case common.ChannelTypeDeepSeek:
  305. return updateChannelDeepSeekBalance(channel)
  306. case common.ChannelTypeOpenRouter:
  307. return updateChannelOpenRouterBalance(channel)
  308. default:
  309. return 0, errors.New("尚未实现")
  310. }
  311. url := fmt.Sprintf("%s/v1/dashboard/billing/subscription", baseURL)
  312. body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
  313. if err != nil {
  314. return 0, err
  315. }
  316. subscription := OpenAISubscriptionResponse{}
  317. err = json.Unmarshal(body, &subscription)
  318. if err != nil {
  319. return 0, err
  320. }
  321. now := time.Now()
  322. startDate := fmt.Sprintf("%s-01", now.Format("2006-01"))
  323. endDate := now.Format("2006-01-02")
  324. if !subscription.HasPaymentMethod {
  325. startDate = now.AddDate(0, 0, -100).Format("2006-01-02")
  326. }
  327. url = fmt.Sprintf("%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s", baseURL, startDate, endDate)
  328. body, err = GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
  329. if err != nil {
  330. return 0, err
  331. }
  332. usage := OpenAIUsageResponse{}
  333. err = json.Unmarshal(body, &usage)
  334. if err != nil {
  335. return 0, err
  336. }
  337. balance := subscription.HardLimitUSD - usage.TotalUsage/100
  338. channel.UpdateBalance(balance)
  339. return balance, nil
  340. }
  341. func UpdateChannelBalance(c *gin.Context) {
  342. id, err := strconv.Atoi(c.Param("id"))
  343. if err != nil {
  344. c.JSON(http.StatusOK, gin.H{
  345. "success": false,
  346. "message": err.Error(),
  347. })
  348. return
  349. }
  350. channel, err := model.GetChannelById(id, true)
  351. if err != nil {
  352. c.JSON(http.StatusOK, gin.H{
  353. "success": false,
  354. "message": err.Error(),
  355. })
  356. return
  357. }
  358. balance, err := updateChannelBalance(channel)
  359. if err != nil {
  360. c.JSON(http.StatusOK, gin.H{
  361. "success": false,
  362. "message": err.Error(),
  363. })
  364. return
  365. }
  366. c.JSON(http.StatusOK, gin.H{
  367. "success": true,
  368. "message": "",
  369. "balance": balance,
  370. })
  371. return
  372. }
  373. func updateAllChannelsBalance() error {
  374. channels, err := model.GetAllChannels(0, 0, true, false)
  375. if err != nil {
  376. return err
  377. }
  378. for _, channel := range channels {
  379. if channel.Status != common.ChannelStatusEnabled {
  380. continue
  381. }
  382. // TODO: support Azure
  383. //if channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {
  384. // continue
  385. //}
  386. balance, err := updateChannelBalance(channel)
  387. if err != nil {
  388. continue
  389. } else {
  390. // err is nil & balance <= 0 means quota is used up
  391. if balance <= 0 {
  392. service.DisableChannel(channel.Id, channel.Name, "余额不足")
  393. }
  394. }
  395. time.Sleep(common.RequestInterval)
  396. }
  397. return nil
  398. }
  399. func UpdateAllChannelsBalance(c *gin.Context) {
  400. // TODO: make it async
  401. err := updateAllChannelsBalance()
  402. if err != nil {
  403. c.JSON(http.StatusOK, gin.H{
  404. "success": false,
  405. "message": err.Error(),
  406. })
  407. return
  408. }
  409. c.JSON(http.StatusOK, gin.H{
  410. "success": true,
  411. "message": "",
  412. })
  413. return
  414. }
  415. func AutomaticallyUpdateChannels(frequency int) {
  416. for {
  417. time.Sleep(time.Duration(frequency) * time.Minute)
  418. common.SysLog("updating all channels")
  419. _ = updateAllChannelsBalance()
  420. common.SysLog("channels update done")
  421. }
  422. }