channel-billing.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  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. "strconv"
  11. "time"
  12. "github.com/gin-gonic/gin"
  13. )
  14. // https://github.com/songquanpeng/one-api/issues/79
  15. type OpenAISubscriptionResponse struct {
  16. Object string `json:"object"`
  17. HasPaymentMethod bool `json:"has_payment_method"`
  18. SoftLimitUSD float64 `json:"soft_limit_usd"`
  19. HardLimitUSD float64 `json:"hard_limit_usd"`
  20. SystemHardLimitUSD float64 `json:"system_hard_limit_usd"`
  21. }
  22. type OpenAIUsageDailyCost struct {
  23. Timestamp float64 `json:"timestamp"`
  24. LineItems []struct {
  25. Name string `json:"name"`
  26. Cost float64 `json:"cost"`
  27. }
  28. }
  29. type OpenAICreditGrants struct {
  30. Object string `json:"object"`
  31. TotalGranted float64 `json:"total_granted"`
  32. TotalUsed float64 `json:"total_used"`
  33. TotalAvailable float64 `json:"total_available"`
  34. }
  35. type OpenAIUsageResponse struct {
  36. Object string `json:"object"`
  37. //DailyCosts []OpenAIUsageDailyCost `json:"daily_costs"`
  38. TotalUsage float64 `json:"total_usage"` // unit: 0.01 dollar
  39. }
  40. type OpenAISBUsageResponse struct {
  41. Msg string `json:"msg"`
  42. Data *struct {
  43. Credit string `json:"credit"`
  44. } `json:"data"`
  45. }
  46. type AIProxyUserOverviewResponse struct {
  47. Success bool `json:"success"`
  48. Message string `json:"message"`
  49. ErrorCode int `json:"error_code"`
  50. Data struct {
  51. TotalPoints float64 `json:"totalPoints"`
  52. } `json:"data"`
  53. }
  54. type API2GPTUsageResponse struct {
  55. Object string `json:"object"`
  56. TotalGranted float64 `json:"total_granted"`
  57. TotalUsed float64 `json:"total_used"`
  58. TotalRemaining float64 `json:"total_remaining"`
  59. }
  60. type APGC2DGPTUsageResponse struct {
  61. //Grants interface{} `json:"grants"`
  62. Object string `json:"object"`
  63. TotalAvailable float64 `json:"total_available"`
  64. TotalGranted float64 `json:"total_granted"`
  65. TotalUsed float64 `json:"total_used"`
  66. }
  67. // GetAuthHeader get auth header
  68. func GetAuthHeader(token string) http.Header {
  69. h := http.Header{}
  70. h.Add("Authorization", fmt.Sprintf("Bearer %s", token))
  71. return h
  72. }
  73. func GetResponseBody(method, url string, channel *model.Channel, headers http.Header) ([]byte, error) {
  74. client := &http.Client{}
  75. req, err := http.NewRequest(method, url, nil)
  76. if err != nil {
  77. return nil, err
  78. }
  79. for k := range headers {
  80. req.Header.Add(k, headers.Get(k))
  81. }
  82. res, err := client.Do(req)
  83. if err != nil {
  84. return nil, err
  85. }
  86. body, err := io.ReadAll(res.Body)
  87. if err != nil {
  88. return nil, err
  89. }
  90. err = res.Body.Close()
  91. if err != nil {
  92. return nil, err
  93. }
  94. return body, nil
  95. }
  96. func updateChannelCloseAIBalance(channel *model.Channel) (float64, error) {
  97. url := fmt.Sprintf("%s/dashboard/billing/credit_grants", channel.BaseURL)
  98. body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
  99. if err != nil {
  100. return 0, err
  101. }
  102. response := OpenAICreditGrants{}
  103. err = json.Unmarshal(body, &response)
  104. if err != nil {
  105. return 0, err
  106. }
  107. channel.UpdateBalance(response.TotalAvailable)
  108. return response.TotalAvailable, nil
  109. }
  110. func updateChannelOpenAISBBalance(channel *model.Channel) (float64, error) {
  111. url := fmt.Sprintf("https://api.openai-sb.com/sb-api/user/status?api_key=%s", channel.Key)
  112. body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
  113. if err != nil {
  114. return 0, err
  115. }
  116. response := OpenAISBUsageResponse{}
  117. err = json.Unmarshal(body, &response)
  118. if err != nil {
  119. return 0, err
  120. }
  121. if response.Data == nil {
  122. return 0, errors.New(response.Msg)
  123. }
  124. balance, err := strconv.ParseFloat(response.Data.Credit, 64)
  125. if err != nil {
  126. return 0, err
  127. }
  128. channel.UpdateBalance(balance)
  129. return balance, nil
  130. }
  131. func updateChannelAIProxyBalance(channel *model.Channel) (float64, error) {
  132. url := "https://aiproxy.io/api/report/getUserOverview"
  133. headers := http.Header{}
  134. headers.Add("Api-Key", channel.Key)
  135. body, err := GetResponseBody("GET", url, channel, headers)
  136. if err != nil {
  137. return 0, err
  138. }
  139. response := AIProxyUserOverviewResponse{}
  140. err = json.Unmarshal(body, &response)
  141. if err != nil {
  142. return 0, err
  143. }
  144. if !response.Success {
  145. return 0, fmt.Errorf("code: %d, message: %s", response.ErrorCode, response.Message)
  146. }
  147. channel.UpdateBalance(response.Data.TotalPoints)
  148. return response.Data.TotalPoints, nil
  149. }
  150. func updateChannelAPI2GPTBalance(channel *model.Channel) (float64, error) {
  151. url := "https://api.api2gpt.com/dashboard/billing/credit_grants"
  152. body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
  153. if err != nil {
  154. return 0, err
  155. }
  156. response := API2GPTUsageResponse{}
  157. err = json.Unmarshal(body, &response)
  158. if err != nil {
  159. return 0, err
  160. }
  161. channel.UpdateBalance(response.TotalRemaining)
  162. return response.TotalRemaining, nil
  163. }
  164. func updateChannelAIGC2DBalance(channel *model.Channel) (float64, error) {
  165. url := "https://api.aigc2d.com/dashboard/billing/credit_grants"
  166. body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
  167. if err != nil {
  168. return 0, err
  169. }
  170. response := APGC2DGPTUsageResponse{}
  171. err = json.Unmarshal(body, &response)
  172. if err != nil {
  173. return 0, err
  174. }
  175. channel.UpdateBalance(response.TotalAvailable)
  176. return response.TotalAvailable, nil
  177. }
  178. func updateChannelBalance(channel *model.Channel) (float64, error) {
  179. baseURL := common.ChannelBaseURLs[channel.Type]
  180. if channel.BaseURL == "" {
  181. channel.BaseURL = baseURL
  182. }
  183. switch channel.Type {
  184. case common.ChannelTypeOpenAI:
  185. if channel.BaseURL != "" {
  186. baseURL = channel.BaseURL
  187. }
  188. case common.ChannelTypeAzure:
  189. return 0, errors.New("尚未实现")
  190. case common.ChannelTypeCustom:
  191. baseURL = channel.BaseURL
  192. case common.ChannelTypeCloseAI:
  193. return updateChannelCloseAIBalance(channel)
  194. case common.ChannelTypeOpenAISB:
  195. return updateChannelOpenAISBBalance(channel)
  196. case common.ChannelTypeAIProxy:
  197. return updateChannelAIProxyBalance(channel)
  198. case common.ChannelTypeAPI2GPT:
  199. return updateChannelAPI2GPTBalance(channel)
  200. case common.ChannelTypeAIGC2D:
  201. return updateChannelAIGC2DBalance(channel)
  202. default:
  203. return 0, errors.New("尚未实现")
  204. }
  205. url := fmt.Sprintf("%s/v1/dashboard/billing/subscription", baseURL)
  206. body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
  207. if err != nil {
  208. return 0, err
  209. }
  210. subscription := OpenAISubscriptionResponse{}
  211. err = json.Unmarshal(body, &subscription)
  212. if err != nil {
  213. return 0, err
  214. }
  215. now := time.Now()
  216. startDate := fmt.Sprintf("%s-01", now.Format("2006-01"))
  217. endDate := now.Format("2006-01-02")
  218. if !subscription.HasPaymentMethod {
  219. startDate = now.AddDate(0, 0, -100).Format("2006-01-02")
  220. }
  221. url = fmt.Sprintf("%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s", baseURL, startDate, endDate)
  222. body, err = GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
  223. if err != nil {
  224. return 0, err
  225. }
  226. usage := OpenAIUsageResponse{}
  227. err = json.Unmarshal(body, &usage)
  228. if err != nil {
  229. return 0, err
  230. }
  231. balance := subscription.HardLimitUSD - usage.TotalUsage/100
  232. channel.UpdateBalance(balance)
  233. return balance, nil
  234. }
  235. func UpdateChannelBalance(c *gin.Context) {
  236. id, err := strconv.Atoi(c.Param("id"))
  237. if err != nil {
  238. c.JSON(http.StatusOK, gin.H{
  239. "success": false,
  240. "message": err.Error(),
  241. })
  242. return
  243. }
  244. channel, err := model.GetChannelById(id, true)
  245. if err != nil {
  246. c.JSON(http.StatusOK, gin.H{
  247. "success": false,
  248. "message": err.Error(),
  249. })
  250. return
  251. }
  252. balance, err := updateChannelBalance(channel)
  253. if err != nil {
  254. c.JSON(http.StatusOK, gin.H{
  255. "success": false,
  256. "message": err.Error(),
  257. })
  258. return
  259. }
  260. c.JSON(http.StatusOK, gin.H{
  261. "success": true,
  262. "message": "",
  263. "balance": balance,
  264. })
  265. return
  266. }
  267. func updateAllChannelsBalance() error {
  268. channels, err := model.GetAllChannels(0, 0, true)
  269. if err != nil {
  270. return err
  271. }
  272. for _, channel := range channels {
  273. if channel.Status != common.ChannelStatusEnabled {
  274. continue
  275. }
  276. // TODO: support Azure
  277. if channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {
  278. continue
  279. }
  280. balance, err := updateChannelBalance(channel)
  281. if err != nil {
  282. continue
  283. } else {
  284. // err is nil & balance <= 0 means quota is used up
  285. if balance <= 0 {
  286. disableChannel(channel.Id, channel.Name, "余额不足")
  287. }
  288. }
  289. time.Sleep(common.RequestInterval)
  290. }
  291. return nil
  292. }
  293. func UpdateAllChannelsBalance(c *gin.Context) {
  294. // TODO: make it async
  295. err := updateAllChannelsBalance()
  296. if err != nil {
  297. c.JSON(http.StatusOK, gin.H{
  298. "success": false,
  299. "message": err.Error(),
  300. })
  301. return
  302. }
  303. c.JSON(http.StatusOK, gin.H{
  304. "success": true,
  305. "message": "",
  306. })
  307. return
  308. }
  309. func AutomaticallyUpdateChannels(frequency int) {
  310. for {
  311. time.Sleep(time.Duration(frequency) * time.Minute)
  312. common.SysLog("updating all channels")
  313. _ = updateAllChannelsBalance()
  314. common.SysLog("channels update done")
  315. }
  316. }