diff --git a/service/server/auth.go b/service/server/auth.go
index 4532691..07de66c 100644
--- a/service/server/auth.go
+++ b/service/server/auth.go
@@ -139,9 +139,12 @@ func handleRegister(c *gin.Context) {
func RequireUserLogin() gin.HandlerFunc {
return func(c *gin.Context) {
+ if c.Request.Method == "OPTIONS" {
+ c.Next()
+ return
+ }
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
- c.Header("X-Uni-Token-Error", "missing_authorization_header")
c.Status(http.StatusUnauthorized)
c.Abort()
return
@@ -149,7 +152,6 @@ func RequireUserLogin() gin.HandlerFunc {
tokenParts := strings.Split(authHeader, " ")
if len(tokenParts) != 2 || tokenParts[0] != "Bearer" {
- c.Header("X-Uni-Token-Error", "missing_authorization_header")
c.Status(http.StatusUnauthorized)
c.Abort()
return
@@ -157,7 +159,6 @@ func RequireUserLogin() gin.HandlerFunc {
claims, err := logic.ValidateJWT(tokenParts[1])
if err != nil {
- c.Header("X-Uni-Token-Error", "invalid_token")
c.Status(http.StatusUnauthorized)
c.Abort()
return
diff --git a/service/server/deep-seek/deep-seek.go b/service/server/deep-seek/deep-seek.go
deleted file mode 100644
index 87a23ee..0000000
--- a/service/server/deep-seek/deep-seek.go
+++ /dev/null
@@ -1,240 +0,0 @@
-package deepSeek
-
-import (
- "bytes"
- "encoding/json"
- "io"
- "net/http"
- "net/url"
- "strings"
-
- "uni-token-service/store"
-
- "github.com/gin-gonic/gin"
-)
-
-type session struct {
- Token string `json:"token"`
-}
-
-func loadSession() (session, error) {
- var s session
- data, err := store.Providers.Get("deepSeek")
- if err != nil {
- return session{}, err
- }
- json.Unmarshal(data, &s)
- return s, nil
-}
-
-func saveSession(s session) error {
- jsonData, err := json.Marshal(s)
- if err != nil {
- return err
- }
- store.Providers.Put("deepSeek", jsonData)
- return nil
-}
-
-func SetupAPI(routes gin.IRoutes) {
- // Login
- routes.POST("/login", handleLogin)
-
- // Send SMS
- routes.POST("/sms", func(ctx *gin.Context) {
- forward(ctx, false, "https://platform.deepseek.com/auth-api/v0/users/create_sms_verification_code")
- })
-
- // User status
- routes.GET("/status", func(ctx *gin.Context) {
- _, err := loadSession()
- if err != nil {
- ctx.JSON(http.StatusOK, gin.H{})
- return
- }
-
- forward(ctx, true, "https://platform.deepseek.com/auth-api/v0/users/current")
- })
-
- // Logout
- routes.POST("/logout", func(ctx *gin.Context) {
- // Clear stored session
- store.Providers.Delete("deepSeek")
- ctx.JSON(http.StatusOK, gin.H{"success": true})
- })
-
- // Identity verification
- routes.GET("/auth/info", func(ctx *gin.Context) {
- forward(ctx, true, "https://platform.deepseek.com/api/v1/my_identity_verification")
- })
-
- routes.POST("/auth/save", func(ctx *gin.Context) {
- forward(ctx, true, "https://platform.deepseek.com/api/v1/identity_verify")
- })
-
- // Payment
- routes.POST("/payment/create", func(ctx *gin.Context) {
- forward(ctx, true, "https://platform.deepseek.com/api/v1/payments")
- })
-
- routes.POST("/payment/status", func(ctx *gin.Context) {
- orderId := ctx.Query("order")
- if orderId == "" {
- ctx.JSON(http.StatusBadRequest, gin.H{"error": "order parameter is required"})
- return
- }
- target := "https://platform.deepseek.com/api/v1/payments/" + orderId + "/capture"
- forward(ctx, true, target)
- })
-
- // API Key management
- routes.POST("/apikey/create", func(ctx *gin.Context) {
- forward(ctx, true, "https://platform.deepseek.com/api/v0/users/edit_api_keys")
- })
-}
-
-// setCommonHeaders sets common HTTP headers for SiliconFlow requests
-func setCommonHeaders(req *http.Request) {
- req.Header.Set("accept", "*/*")
- req.Header.Set("accept-language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6")
- req.Header.Set("priority", "u=1, i")
- req.Header.Set("referer", "https://platform.deepseek.com/top_up")
- req.Header.Set("sec-ch-ua", `"Not)A;Brand";v="8", "Chromium";v="138", "Microsoft Edge";v="138"`)
- req.Header.Set("sec-ch-ua-mobile", "?0")
- req.Header.Set("sec-ch-ua-platform", `"Linux"`)
- req.Header.Set("sec-fetch-dest", "empty")
- req.Header.Set("sec-fetch-mode", "cors")
- req.Header.Set("sec-fetch-site", "same-origin")
- req.Header.Set("user-agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0")
-}
-
-func forward(c *gin.Context, requireLogin bool, target string) {
- method := c.Request.Method
-
- // Retrieve the latest stored session
- var session session
- var err error
- if requireLogin {
- session, err = loadSession()
- if err != nil {
- c.JSON(http.StatusUnauthorized, gin.H{"error": "No session found. Please login first."})
- return
- }
- }
-
- // Read request body for POST requests
- var reqBody io.Reader
- if method == "POST" {
- bodyBytes, err := io.ReadAll(c.Request.Body)
- if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to read request body"})
- return
- }
- reqBody = bytes.NewReader(bodyBytes)
- }
-
- // Create HTTP request with the specified method
- httpReq, err := http.NewRequest(method, target, reqBody)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create request"})
- return
- }
-
- // Set headers
- setCommonHeaders(httpReq)
- if requireLogin {
- httpReq.Header.Set("authorization", "Bearer "+session.Token)
- }
-
- // Copy Content-Type from original request if it's a POST
- if method == "POST" {
- if contentType := c.GetHeader("Content-Type"); contentType != "" {
- httpReq.Header.Set("Content-Type", contentType)
- } else {
- httpReq.Header.Set("Content-Type", "application/json")
- }
- }
-
- // Make the request
- client := &http.Client{}
- resp, err := client.Do(httpReq)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to send request"})
- return
- }
- defer resp.Body.Close()
-
- // Read response
- respBody, err := io.ReadAll(resp.Body)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read response"})
- return
- }
-
- c.Data(resp.StatusCode, "application/json", respBody)
-}
-
-func handleLogin(c *gin.Context) {
- var req struct {
- Phone string `json:"phone"`
- Code string `json:"code"`
- AreaCode string `json:"area_code"`
- }
- if err := c.ShouldBindJSON(&req); err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"})
- return
- }
-
- // Prepare form data for login
- formData := url.Values{}
- formData.Set("mobile_number", req.Phone)
- formData.Set("sms_code", req.Code)
- formData.Set("area_code", req.AreaCode)
-
- // Create login request
- httpReq, err := http.NewRequest("POST", "https://platform.deepseek.com/auth-api/v0/users/login_by_mobile_sms", strings.NewReader(formData.Encode()))
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create login request"})
- return
- }
-
- // Set headers for login
- setCommonHeaders(httpReq)
- httpReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
-
- // Make login request
- client := &http.Client{}
- resp, err := client.Do(httpReq)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to login"})
- return
- }
- defer resp.Body.Close()
-
- // Read response
- respBody, err := io.ReadAll(resp.Body)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read login response"})
- return
- }
-
- // Parse response to extract token
- var loginResp map[string]interface{}
- if err := json.Unmarshal(respBody, &loginResp); err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to parse login response"})
- return
- }
-
- // Extract token and save session
- if data, ok := loginResp["data"].(map[string]interface{}); ok {
- if token, ok := data["token"].(string); ok {
- session := session{Token: token}
- if err := saveSession(session); err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save session"})
- return
- }
- }
- }
-
- c.Data(resp.StatusCode, "application/json", respBody)
-}
diff --git a/service/server/open-router/open-router.go b/service/server/open-router/open-router.go
deleted file mode 100644
index 3526914..0000000
--- a/service/server/open-router/open-router.go
+++ /dev/null
@@ -1,131 +0,0 @@
-package openrouter
-
-import (
- "encoding/json"
- "io"
- "net/http"
- "time"
-
- "uni-token-service/store"
-
- "github.com/gin-gonic/gin"
-)
-
-type session struct {
- Key string `json:"key"`
- UserId string `json:"userId"`
-}
-
-func loadSession() (session, error) {
- var s session
- data, err := store.Providers.Get("openRouter")
- if err != nil {
- return session{}, err
- }
- json.Unmarshal(data, &s)
- return s, nil
-}
-
-func saveSession(s session) error {
- jsonData, err := json.Marshal(s)
- if err != nil {
- return err
- }
- store.Providers.Put("openRouter", jsonData)
- return nil
-}
-
-func SetupAPI(routes gin.IRoutes) {
- routes.POST("/authed", handleAuthed)
- routes.GET("/status", handleGetStatus)
- routes.GET("/key", handleGetKey)
- routes.POST("/logout", handleLogout)
-}
-
-func handleAuthed(c *gin.Context) {
- var req struct {
- Key string `json:"key"`
- UserId string `json:"userId"`
- }
- if err := c.BindJSON(&req); err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
- return
- }
- err := saveSession(session{
- Key: req.Key,
- UserId: req.UserId,
- })
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save session"})
- return
- }
- c.Status(http.StatusOK)
-}
-
-func handleGetStatus(c *gin.Context) {
- s, err := loadSession()
- if err != nil {
- c.JSON(http.StatusOK, gin.H{
- "authed": false,
- })
- return
- }
-
- req, err := http.NewRequest("GET", "https://openrouter.ai/api/v1/credits", nil)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create request"})
- return
- }
- req.Header.Set("Authorization", "Bearer "+s.Key)
- client := &http.Client{Timeout: 10 * time.Second}
- resp, err := client.Do(req)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch credits"})
- return
- }
- defer resp.Body.Close()
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read response"})
- return
- }
- if resp.StatusCode != http.StatusOK {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch credits"})
- return
- }
- var creditsResp struct {
- Data struct {
- TotalCredits float64 `json:"total_credits"`
- TotalUsage float64 `json:"total_usage"`
- } `json:"data"`
- }
- if err := json.Unmarshal(body, &creditsResp); err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to parse credits"})
- return
- }
- c.JSON(http.StatusOK, gin.H{
- "userId": s.UserId,
- "credits": creditsResp.Data.TotalCredits,
- "usage": creditsResp.Data.TotalUsage,
- })
-}
-
-func handleGetKey(c *gin.Context) {
- s, err := loadSession()
- if err != nil {
- c.Status(http.StatusUnauthorized)
- return
- }
- c.JSON(http.StatusOK, gin.H{"key": s.Key})
-}
-
-// handleLogout handles user logout request
-func handleLogout(c *gin.Context) {
- // Remove the stored session
- err := store.Providers.Delete("openRouter")
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to clear session"})
- return
- }
- c.Status(http.StatusOK)
-}
diff --git a/service/server/proxy.go b/service/server/proxy.go
new file mode 100644
index 0000000..29ac60b
--- /dev/null
+++ b/service/server/proxy.go
@@ -0,0 +1,107 @@
+package server
+
+import (
+ "bytes"
+ "encoding/base64"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+)
+
+func SetupProxyAPI(router gin.IRouter) {
+ router.POST("/proxy", handleProxy, RequireUserLogin())
+ router.GET("/proxy/:base/*paths", handleSimpleProxy)
+}
+
+type ProxyRequest struct {
+ Method string `json:"method" binding:"required"`
+ Url string `json:"url" binding:"required"`
+ Headers map[string]string `json:"headers"`
+ Body string `json:"body"`
+}
+
+type ProxyResponse struct {
+ Status int `json:"status"`
+ Headers map[string]string `json:"headers"`
+ Body string `json:"body"`
+}
+
+func handleProxy(c *gin.Context) {
+ var req ProxyRequest
+ if err := c.ShouldBindJSON(&req); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+ return
+ }
+
+ client := &http.Client{}
+ proxyReq, err := http.NewRequest(req.Method, req.Url, bytes.NewReader([]byte(req.Body)))
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create proxy request: " + err.Error()})
+ return
+ }
+
+ for key, value := range req.Headers {
+ proxyReq.Header.Set(key, value)
+ }
+
+ resp, err := client.Do(proxyReq)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to perform proxy request: " + err.Error()})
+ return
+ }
+ defer resp.Body.Close()
+
+ var respBody bytes.Buffer
+ _, err = respBody.ReadFrom(resp.Body)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read proxy response body: " + err.Error()})
+ return
+ }
+
+ respHeaders := make(map[string]string)
+ for key, values := range resp.Header {
+ if len(values) > 0 {
+ respHeaders[key] = values[0]
+ }
+ }
+
+ c.JSON(http.StatusOK, ProxyResponse{
+ Status: resp.StatusCode,
+ Headers: respHeaders,
+ Body: respBody.String(),
+ })
+}
+
+func handleSimpleProxy(c *gin.Context) {
+ base, err := base64.StdEncoding.DecodeString(c.Param("base"))
+ if err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid base64 URL"})
+ return
+ }
+
+ paths := c.Param("paths")
+ targetUrl := string(base) + paths
+
+ client := &http.Client{}
+ proxyReq, err := http.NewRequest(c.Request.Method, targetUrl, c.Request.Body)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create proxy request: " + err.Error()})
+ return
+ }
+
+ resp, err := client.Do(proxyReq)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to perform proxy request: " + err.Error()})
+ return
+ }
+ defer resp.Body.Close()
+
+ var respBody bytes.Buffer
+ _, err = respBody.ReadFrom(resp.Body)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read proxy response body: " + err.Error()})
+ return
+ }
+
+ c.Data(resp.StatusCode, resp.Header.Get("Content-Type"), respBody.Bytes())
+}
diff --git a/service/server/server.go b/service/server/server.go
index f2e05d2..cab8dfe 100644
--- a/service/server/server.go
+++ b/service/server/server.go
@@ -7,9 +7,6 @@ import (
"strconv"
"time"
"uni-token-service/logic"
- deepSeek "uni-token-service/server/deep-seek"
- siliconFlow "uni-token-service/server/silicon-flow"
- openRouter "uni-token-service/server/open-router"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
@@ -22,10 +19,16 @@ func SetupAPIServer() (int, error) {
router := gin.Default()
router.Use(cors.New(cors.Config{
- AllowOrigins: []string{"http://localhost:*", "https://uni-token.app"},
- AllowWildcard: true,
- AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
- AllowHeaders: []string{"*"},
+ AllowOrigins: []string{"http://localhost:*", "https://uni-token.app"},
+ AllowWildcard: true,
+ AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
+ AllowHeaders: []string{
+ "Origin",
+ "Content-Length",
+ "Content-Type",
+ "Accept",
+ "Authorization",
+ },
ExposeHeaders: []string{"*"},
AllowCredentials: true,
AllowPrivateNetwork: true,
@@ -50,10 +53,8 @@ func setupRoutes(router *gin.Engine) {
SetupPresetsAPI(router)
SetupUsageAPI(router)
SetupAuthAPI(router)
-
- siliconFlow.SetupAPI(router.Group("/siliconflow").Use(RequireUserLogin()))
- deepSeek.SetupAPI(router.Group("/deepseek").Use(RequireUserLogin()))
- openRouter.SetupAPI(router.Group("/openrouter").Use(RequireUserLogin()))
+ SetupProxyAPI(router)
+ SetupStoreAPI(router)
}
func isPortAvailable(port int) bool {
diff --git a/service/server/silicon-flow/silicon-flow.go b/service/server/silicon-flow/silicon-flow.go
deleted file mode 100644
index 231305f..0000000
--- a/service/server/silicon-flow/silicon-flow.go
+++ /dev/null
@@ -1,721 +0,0 @@
-package siliconFlow
-
-import (
- "bytes"
- "encoding/json"
- "io"
- "net/http"
- "strings"
- "time"
-
- "uni-token-service/store"
-
- "github.com/gin-gonic/gin"
-)
-
-type session struct {
- Cookie string `json:"cookie"`
- SubjectID string `json:"subjectId"`
- CreatedAt time.Time `json:"createdAt"`
-}
-
-func loadSession() (session, error) {
- var s session
- data, err := store.Providers.Get("siliconFlow")
- if err != nil {
- return session{}, err
- }
- json.Unmarshal(data, &s)
- return s, nil
-}
-
-func saveSession(s session) error {
- jsonData, err := json.Marshal(s)
- if err != nil {
- return err
- }
- store.Providers.Put("siliconFlow", jsonData)
- return nil
-}
-
-func SetupAPI(routes gin.IRoutes) {
- routes.GET("/status", handleGetStatus)
- routes.POST("/sms", handleSendSMS)
- routes.POST("/email", handleSendEmail)
- routes.POST("/login", handleSiliconLogin)
- routes.POST("/login/email", handleSiliconLoginEmail)
- routes.POST("/logout", handleLogout)
- routes.POST("/apikey/create", handleCreateAPIKey)
- routes.POST("/payment/create", handleCreatePayment)
- routes.GET("/payment/status", handleCheckPaymentStatus)
- routes.GET("/auth/info", handleGetAuthInfo)
- routes.POST("/auth/save", handleSaveAuth)
-}
-
-// setCommonHeaders sets common HTTP headers for SiliconFlow requests
-func setCommonHeaders(req *http.Request) {
- req.Header.Set("Accept", "*/*")
- req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
- req.Header.Set("Priority", "u=1, i")
- req.Header.Set("Sec-CH-UA", `"Not)A;Brand";v="8", "Chromium";v="138", "Microsoft Edge";v="138"`)
- req.Header.Set("Sec-CH-UA-Mobile", "?0")
- req.Header.Set("Sec-CH-UA-Platform", `"Linux"`)
- req.Header.Set("Sec-Fetch-Dest", "empty")
- req.Header.Set("Sec-Fetch-Mode", "cors")
- req.Header.Set("Sec-Fetch-Site", "same-origin")
- req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0")
- req.Header.Set("Origin", "https://cloud.siliconflow.cn")
-}
-
-// handleSendSMS handles SMS sending request
-func handleSendSMS(c *gin.Context) {
- // Read the raw request body
- body, err := io.ReadAll(c.Request.Body)
- if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to read request body"})
- return
- }
-
- // Create HTTP request
- httpReq, err := http.NewRequest("POST", "https://account.siliconflow.cn/api/open/sms", bytes.NewBuffer(body))
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create request"})
- return
- }
-
- // Set headers
- setCommonHeaders(httpReq)
- httpReq.Header.Set("Content-Type", "text/plain;charset=UTF-8")
-
- // Make the request
- client := &http.Client{}
- resp, err := client.Do(httpReq)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to send SMS request"})
- return
- }
- defer resp.Body.Close()
-
- // Read response
- respBody, err := io.ReadAll(resp.Body)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read response"})
- return
- }
-
- c.Data(resp.StatusCode, "application/json", respBody)
-}
-
-// handleSendEmail handles Email sending request
-func handleSendEmail(c *gin.Context) {
- // Read the raw request body
- body, err := io.ReadAll(c.Request.Body)
- if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to read request body"})
- return
- }
-
- // Create HTTP request
- httpReq, err := http.NewRequest("POST", "https://account.siliconflow.cn/api/open/email", bytes.NewBuffer(body))
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create request"})
- return
- }
-
- // Set headers
- setCommonHeaders(httpReq)
- httpReq.Header.Set("Content-Type", "text/plain;charset=UTF-8")
-
- // Make the request
- client := &http.Client{}
- resp, err := client.Do(httpReq)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to send SMS request"})
- return
- }
- defer resp.Body.Close()
-
- // Read response
- respBody, err := io.ReadAll(resp.Body)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read response"})
- return
- }
-
- c.Data(resp.StatusCode, "application/json", respBody)
-}
-
-// handleSiliconLogin handles user login request
-func handleSiliconLogin(c *gin.Context) {
- // Read the raw request body
- body, err := io.ReadAll(c.Request.Body)
- if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to read request body"})
- return
- }
-
- // Create HTTP request
- httpReq, err := http.NewRequest("POST", "https://account.siliconflow.cn/api/open/login/user", bytes.NewBuffer(body))
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create request"})
- return
- }
-
- // Set headers
- setCommonHeaders(httpReq)
- httpReq.Header.Set("Content-Type", "text/plain;charset=UTF-8")
-
- // Make the request
- client := &http.Client{}
- resp, err := client.Do(httpReq)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to send login request"})
- return
- }
- defer resp.Body.Close()
-
- // Read response
- respBody, err := io.ReadAll(resp.Body)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read response"})
- return
- }
-
- // Store cookies if login was successful
- if resp.StatusCode >= 200 && resp.StatusCode < 300 {
- cookies := resp.Header["Set-Cookie"]
- var subjectID string
-
- if len(cookies) > 0 {
- // Parse Set-Cookie headers and extract only name=value pairs
- var cookiePairs []string
- for _, cookie := range cookies {
- // Split by semicolon and take only the first part (name=value)
- parts := strings.Split(cookie, ";")
- if len(parts) > 0 {
- nameValue := strings.TrimSpace(parts[0])
- if nameValue != "" {
- cookiePairs = append(cookiePairs, nameValue)
- }
- }
- }
- cookieStr := strings.Join(cookiePairs, "; ")
-
- // Make a request to /me to get the subject ID
- meReq, err := http.NewRequest("GET", "https://cloud.siliconflow.cn/me", nil)
- if err == nil {
- // Set headers for /me request
- meReq.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
- meReq.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
- meReq.Header.Set("Priority", "u=0, i")
- meReq.Header.Set("Referer", "https://account.siliconflow.cn/")
- meReq.Header.Set("Sec-CH-UA", `"Not)A;Brand";v="8", "Chromium";v="138", "Microsoft Edge";v="138"`)
- meReq.Header.Set("Sec-CH-UA-Mobile", "?0")
- meReq.Header.Set("Sec-CH-UA-Platform", `"Linux"`)
- meReq.Header.Set("Sec-Fetch-Dest", "document")
- meReq.Header.Set("Sec-Fetch-Mode", "navigate")
- meReq.Header.Set("Sec-Fetch-Site", "same-site")
- meReq.Header.Set("Sec-Fetch-User", "?1")
- meReq.Header.Set("Upgrade-Insecure-Requests", "1")
- meReq.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0")
- meReq.Header.Set("Cookie", cookieStr)
-
- meResp, err := client.Do(meReq)
- if err == nil {
- defer meResp.Body.Close()
- // Extract X-Subject-ID from response headers
- if xSubjectID := meResp.Header.Get("X-Subject-ID"); xSubjectID != "" {
- subjectID = xSubjectID
- }
- }
- }
-
- err = saveSession(session{
- Cookie: cookieStr,
- SubjectID: subjectID,
- CreatedAt: time.Now(),
- })
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save session"})
- }
- }
- }
-
- c.Data(resp.StatusCode, "application/json", respBody)
-}
-
-// handleSiliconLoginEmail handles user login request via email
-func handleSiliconLoginEmail(c *gin.Context) {
- // Read the raw request body
- body, err := io.ReadAll(c.Request.Body)
- if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to read request body"})
- return
- }
-
- // Create HTTP request
- httpReq, err := http.NewRequest("POST", "https://account.siliconflow.cn/api/open/login/email", bytes.NewBuffer(body))
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create request"})
- return
- }
-
- // Set headers
- setCommonHeaders(httpReq)
- httpReq.Header.Set("Content-Type", "text/plain;charset=UTF-8")
-
- // Make the request
- client := &http.Client{}
- resp, err := client.Do(httpReq)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to send login request"})
- return
- }
- defer resp.Body.Close()
-
- // Read response
- respBody, err := io.ReadAll(resp.Body)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read response"})
- return
- }
-
- // Store cookies if login was successful
- if resp.StatusCode >= 200 && resp.StatusCode < 300 {
- cookies := resp.Header["Set-Cookie"]
- var subjectID string
-
- if len(cookies) > 0 {
- // Parse Set-Cookie headers and extract only name=value pairs
- var cookiePairs []string
- for _, cookie := range cookies {
- // Split by semicolon and take only the first part (name=value)
- parts := strings.Split(cookie, ";")
- if len(parts) > 0 {
- nameValue := strings.TrimSpace(parts[0])
- if nameValue != "" {
- cookiePairs = append(cookiePairs, nameValue)
- }
- }
- }
- cookieStr := strings.Join(cookiePairs, "; ")
-
- // Make a request to /me to get the subject ID
- meReq, err := http.NewRequest("GET", "https://cloud.siliconflow.cn/me", nil)
- if err == nil {
- // Set headers for /me request
- meReq.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
- meReq.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
- meReq.Header.Set("Priority", "u=0, i")
- meReq.Header.Set("Referer", "https://account.siliconflow.cn/")
- meReq.Header.Set("Sec-CH-UA", `"Not)A;Brand";v="8", "Chromium";v="138", "Microsoft Edge";v="138"`)
- meReq.Header.Set("Sec-CH-UA-Mobile", "?0")
- meReq.Header.Set("Sec-CH-UA-Platform", `"Linux"`)
- meReq.Header.Set("Sec-Fetch-Dest", "document")
- meReq.Header.Set("Sec-Fetch-Mode", "navigate")
- meReq.Header.Set("Sec-Fetch-Site", "same-site")
- meReq.Header.Set("Sec-Fetch-User", "?1")
- meReq.Header.Set("Upgrade-Insecure-Requests", "1")
- meReq.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0")
- meReq.Header.Set("Cookie", cookieStr)
-
- meResp, err := client.Do(meReq)
- if err == nil {
- defer meResp.Body.Close()
- // Extract X-Subject-ID from response headers
- if xSubjectID := meResp.Header.Get("X-Subject-ID"); xSubjectID != "" {
- subjectID = xSubjectID
- }
- }
- }
-
- err = saveSession(session{
- Cookie: cookieStr,
- SubjectID: subjectID,
- CreatedAt: time.Now(),
- })
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save session"})
- return
- }
- }
- }
-
- c.Data(resp.StatusCode, "application/json", respBody)
-}
-
-// handleCreateAPIKey handles API key creation request
-func handleCreateAPIKey(c *gin.Context) {
- // Retrieve the latest stored session
- session, err := loadSession()
- if err != nil {
- c.JSON(http.StatusUnauthorized, gin.H{"error": "No session found. Please login first."})
- return
- }
-
- // Read the raw request body
- body, err := io.ReadAll(c.Request.Body)
- if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to read request body"})
- return
- }
-
- // Create HTTP request
- httpReq, err := http.NewRequest("POST", "https://cloud.siliconflow.cn/biz-server/api/v1/apikey/create", bytes.NewBuffer(body))
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create request"})
- return
- }
-
- // Set headers
- setCommonHeaders(httpReq)
- httpReq.Header.Set("Content-Type", "application/json")
- httpReq.Header.Set("Referer", "https://cloud.siliconflow.cn/me/account/ak")
-
- // Add X-Subject-ID if available
- if session.SubjectID != "" {
- httpReq.Header.Set("X-Subject-ID", session.SubjectID)
- }
-
- // Set the stored cookie
- httpReq.Header.Set("Cookie", session.Cookie)
-
- // Make the request
- client := &http.Client{}
- resp, err := client.Do(httpReq)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to send API key creation request"})
- return
- }
- defer resp.Body.Close()
-
- // Read response
- respBody, err := io.ReadAll(resp.Body)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read response"})
- return
- }
-
- c.Data(resp.StatusCode, "application/json", respBody)
-}
-
-// handleCreatePayment handles payment QR code creation request
-func handleCreatePayment(c *gin.Context) {
- // Retrieve the latest stored session
- session, err := loadSession()
- if err != nil {
- c.JSON(http.StatusUnauthorized, gin.H{"error": "No session found. Please login first."})
- return
- }
-
- // Read the raw request body
- body, err := io.ReadAll(c.Request.Body)
- if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to read request body"})
- return
- }
-
- // Create HTTP request
- httpReq, err := http.NewRequest("POST", "https://cloud.siliconflow.cn/biz-server/api/v1/pay/transactions", bytes.NewBuffer(body))
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create request"})
- return
- }
-
- // Set headers
- setCommonHeaders(httpReq)
- httpReq.Header.Set("Content-Type", "application/json")
- httpReq.Header.Set("Referer", "https://cloud.siliconflow.cn/me/expensebill")
-
- // Add X-Subject-ID if available
- if session.SubjectID != "" {
- httpReq.Header.Set("X-Subject-ID", session.SubjectID)
- }
-
- // Set the stored cookie
- httpReq.Header.Set("Cookie", session.Cookie)
-
- // Make the request
- client := &http.Client{}
- resp, err := client.Do(httpReq)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to send payment creation request"})
- return
- }
- defer resp.Body.Close()
-
- // Read response
- respBody, err := io.ReadAll(resp.Body)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read response"})
- return
- }
-
- c.Data(resp.StatusCode, "application/json", respBody)
-}
-
-// handleCheckPaymentStatus handles payment status checking request
-func handleCheckPaymentStatus(c *gin.Context) {
- // Retrieve the latest stored session
- session, err := loadSession()
- if err != nil {
- c.JSON(http.StatusUnauthorized, gin.H{"error": "No session found. Please login first."})
- return
- }
-
- // Get order parameter from query
- order := c.Query("order")
- if order == "" {
- c.JSON(http.StatusBadRequest, gin.H{"error": "Order parameter is required"})
- return
- }
-
- // Create HTTP request
- url := "https://cloud.siliconflow.cn/biz-server/api/v1/pay/status?order=" + order
- httpReq, err := http.NewRequest("GET", url, nil)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create request"})
- return
- }
-
- // Set headers
- setCommonHeaders(httpReq)
- httpReq.Header.Set("Content-Type", "application/json")
- httpReq.Header.Set("Referer", "https://cloud.siliconflow.cn/me/expensebill")
-
- // Add X-Subject-ID if available
- if session.SubjectID != "" {
- httpReq.Header.Set("X-Subject-ID", session.SubjectID)
- }
-
- // Set the stored cookie
- httpReq.Header.Set("Cookie", session.Cookie)
-
- // Make the request
- client := &http.Client{}
- resp, err := client.Do(httpReq)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to send payment status request"})
- return
- }
- defer resp.Body.Close()
-
- // Read response
- respBody, err := io.ReadAll(resp.Body)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read response"})
- return
- }
-
- c.Data(resp.StatusCode, "application/json", respBody)
-}
-
-// handleGetStatus handles getting current login status and user info
-func handleGetStatus(c *gin.Context) {
- // Retrieve the latest stored session
- session, err := loadSession()
- if err != nil {
- c.JSON(http.StatusOK, gin.H{
- "code": 40001,
- "message": "No session found",
- "status": false,
- "data": nil,
- })
- return
- }
-
- // Make a request to /me to get user info
- client := &http.Client{}
- meReq, err := http.NewRequest("GET", "https://cloud.siliconflow.cn/me", nil)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create request"})
- return
- }
-
- // Set headers for /me request
- meReq.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
- meReq.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
- meReq.Header.Set("Priority", "u=0, i")
- meReq.Header.Set("Referer", "https://account.siliconflow.cn/")
- meReq.Header.Set("Sec-CH-UA", `"Not)A;Brand";v="8", "Chromium";v="138", "Microsoft Edge";v="138"`)
- meReq.Header.Set("Sec-CH-UA-Mobile", "?0")
- meReq.Header.Set("Sec-CH-UA-Platform", `"Linux"`)
- meReq.Header.Set("Sec-Fetch-Dest", "document")
- meReq.Header.Set("Sec-Fetch-Mode", "navigate")
- meReq.Header.Set("Sec-Fetch-Site", "same-site")
- meReq.Header.Set("Sec-Fetch-User", "?1")
- meReq.Header.Set("Upgrade-Insecure-Requests", "1")
- meReq.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0")
- meReq.Header.Set("Cookie", session.Cookie)
-
- meResp, err := client.Do(meReq)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to check login status"})
- return
- }
- defer meResp.Body.Close()
-
- // If the request is successful, user is logged in
- if meResp.StatusCode >= 200 && meResp.StatusCode < 300 {
- // Try to get user info from API
- userInfoReq, err := http.NewRequest("GET", "https://cloud.siliconflow.cn/biz-server/api/v1/user/info", nil)
- if err == nil {
- setCommonHeaders(userInfoReq)
- userInfoReq.Header.Set("Origin", "https://cloud.siliconflow.cn")
- userInfoReq.Header.Set("Referer", "https://cloud.siliconflow.cn/me/account/info")
- userInfoReq.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0")
- if session.SubjectID != "" {
- userInfoReq.Header.Set("X-Subject-ID", session.SubjectID)
- }
- userInfoReq.Header.Set("Cookie", session.Cookie)
-
- userInfoResp, err := client.Do(userInfoReq)
- if err == nil {
- defer userInfoResp.Body.Close()
- if userInfoResp.StatusCode >= 200 && userInfoResp.StatusCode < 300 {
- userInfoBody, err := io.ReadAll(userInfoResp.Body)
- if err == nil {
- // Return the user info response directly since it already has the right format
- c.Data(http.StatusOK, "application/json", userInfoBody)
- return
- }
- }
- }
- }
-
- // If we can't get detailed user info, return basic login status with compatible format
- c.JSON(http.StatusOK, gin.H{
- "code": 20000,
- "message": "User is logged in",
- "status": true,
- "data": gin.H{
- "name": "User",
- },
- })
- } else {
- c.JSON(http.StatusOK, gin.H{
- "code": 40001,
- "message": "Session expired or invalid",
- "status": false,
- "data": nil,
- })
- }
-}
-
-// handleLogout handles user logout request
-func handleLogout(c *gin.Context) {
- // Remove the stored session
- err := store.Providers.Delete("siliconFlow")
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to clear session"})
- return
- }
-
- c.JSON(http.StatusOK, gin.H{
- "success": true,
- "message": "Logged out successfully",
- })
-}
-
-// handleGetAuthInfo handles getting real name authentication info
-func handleGetAuthInfo(c *gin.Context) {
- // Retrieve the latest stored session
- session, err := loadSession()
- if err != nil {
- c.JSON(http.StatusUnauthorized, gin.H{"error": "No session found. Please login first."})
- return
- }
-
- // Create HTTP request
- httpReq, err := http.NewRequest("GET", "https://cloud.siliconflow.cn/biz-server/api/v1/subject/auth/info", nil)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create request"})
- return
- }
-
- // Set headers
- setCommonHeaders(httpReq)
- httpReq.Header.Set("Content-Type", "application/json")
- httpReq.Header.Set("Referer", "https://cloud.siliconflow.cn/me/account/authentication/personal")
-
- // Add X-Subject-ID if available
- if session.SubjectID != "" {
- httpReq.Header.Set("X-Subject-ID", session.SubjectID)
- }
-
- // Set the stored cookie
- httpReq.Header.Set("Cookie", session.Cookie)
-
- // Make the request
- client := &http.Client{}
- resp, err := client.Do(httpReq)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to send auth info request"})
- return
- }
- defer resp.Body.Close()
-
- // Read response
- respBody, err := io.ReadAll(resp.Body)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read response"})
- return
- }
-
- c.Data(resp.StatusCode, "application/json", respBody)
-}
-
-// handleSaveAuth handles saving real name authentication
-func handleSaveAuth(c *gin.Context) {
- // Retrieve the latest stored session
- session, err := loadSession()
- if err != nil {
- c.JSON(http.StatusUnauthorized, gin.H{"error": "No session found. Please login first."})
- return
- }
-
- // Read the raw request body
- body, err := io.ReadAll(c.Request.Body)
- if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to read request body"})
- return
- }
-
- // Create HTTP request
- httpReq, err := http.NewRequest("POST", "https://cloud.siliconflow.cn/biz-server/api/v1/subject/auth/save", bytes.NewBuffer(body))
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create request"})
- return
- }
-
- // Set headers
- setCommonHeaders(httpReq)
- httpReq.Header.Set("Content-Type", "application/json")
- httpReq.Header.Set("Referer", "https://cloud.siliconflow.cn/me/account/authentication/personal")
-
- // Add X-Subject-ID if available
- if session.SubjectID != "" {
- httpReq.Header.Set("X-Subject-ID", session.SubjectID)
- }
-
- // Set the stored cookie
- httpReq.Header.Set("Cookie", session.Cookie)
-
- // Make the request
- client := &http.Client{}
- resp, err := client.Do(httpReq)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to send auth save request"})
- return
- }
- defer resp.Body.Close()
-
- // Read response
- respBody, err := io.ReadAll(resp.Body)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read response"})
- return
- }
-
- c.Data(resp.StatusCode, "application/json", respBody)
-}
diff --git a/service/server/store.go b/service/server/store.go
new file mode 100644
index 0000000..6e188ec
--- /dev/null
+++ b/service/server/store.go
@@ -0,0 +1,109 @@
+package server
+
+import (
+ "io"
+ "uni-token-service/store"
+
+ "github.com/gin-gonic/gin"
+ "go.etcd.io/bbolt"
+)
+
+func SetupStoreAPI(router gin.IRouter) {
+ api := router.Group("/store").Use(RequireUserLogin())
+ {
+ api.DELETE("/:name", handleStoreDeleteAll)
+ api.GET("/:name/:key", handleStoreGet)
+ api.PUT("/:name/:key", handleStorePut)
+ api.DELETE("/:name/:key", handleStoreDelete)
+ }
+}
+
+var createdBuckets = map[string]bool{}
+
+func ensureBucket(name string) error {
+ if created, exists := createdBuckets[name]; exists && created {
+ return nil
+ }
+ return store.Db.Update(func(tx *bbolt.Tx) error {
+ createdBuckets[name] = true
+ _, err := tx.CreateBucketIfNotExists([]byte(name))
+ return err
+ })
+}
+
+func handleStoreDeleteAll(c *gin.Context) {
+ name := c.Param("name")
+ err := store.DeleteBucket(name)
+ if err != nil {
+ c.JSON(500, gin.H{"error": err.Error()})
+ return
+ }
+ c.Status(200)
+}
+
+func handleStoreGet(c *gin.Context) {
+ name := c.Param("name")
+ key := c.Param("key")
+ err := ensureBucket(name)
+ if err != nil {
+ c.JSON(500, gin.H{"error": err.Error()})
+ return
+ }
+ err = store.Db.View(func(tx *bbolt.Tx) error {
+ b := tx.Bucket([]byte(name))
+ v := b.Get([]byte(key))
+ if v == nil {
+ c.JSON(200, nil)
+ return nil
+ }
+ c.Data(200, "application/json", v)
+ return nil
+ })
+ if err != nil {
+ c.JSON(500, gin.H{"error": err.Error()})
+ return
+ }
+}
+
+func handleStorePut(c *gin.Context) {
+ name := c.Param("name")
+ key := c.Param("key")
+ err := ensureBucket(name)
+ if err != nil {
+ c.JSON(500, gin.H{"error": err.Error()})
+ return
+ }
+ body, err := io.ReadAll(c.Request.Body)
+ if err != nil {
+ c.JSON(500, gin.H{"error": err.Error()})
+ return
+ }
+ err = store.Db.Update(func(tx *bbolt.Tx) error {
+ b := tx.Bucket([]byte(name))
+ return b.Put([]byte(key), body)
+ })
+ if err != nil {
+ c.JSON(500, gin.H{"error": err.Error()})
+ return
+ }
+ c.Status(200)
+}
+
+func handleStoreDelete(c *gin.Context) {
+ name := c.Param("name")
+ key := c.Param("key")
+ err := ensureBucket(name)
+ if err != nil {
+ c.JSON(500, gin.H{"error": err.Error()})
+ return
+ }
+ err = store.Db.Update(func(tx *bbolt.Tx) error {
+ b := tx.Bucket([]byte(name))
+ return b.Delete([]byte(key))
+ })
+ if err != nil {
+ c.JSON(500, gin.H{"error": err.Error()})
+ return
+ }
+ c.Status(200)
+}
diff --git a/service/store/store.go b/service/store/store.go
index 4d2711d..9fc399c 100644
--- a/service/store/store.go
+++ b/service/store/store.go
@@ -10,18 +10,27 @@ import (
type Bucket[T any] struct {
bucketName string
- db *bbolt.DB
}
-func NewBucket[T any](bucketName string, db *bbolt.DB) Bucket[T] {
+func CreateBucket[T any](bucketName string) (Bucket[T], error) {
b := Bucket[T]{
bucketName: bucketName,
- db: db,
}
- err := b.db.Update(func(tx *bbolt.Tx) error {
+ err := Db.Update(func(tx *bbolt.Tx) error {
_, err := tx.CreateBucketIfNotExists([]byte(b.bucketName))
return err
})
+ return b, err
+}
+
+func DeleteBucket(bucketName string) error {
+ return Db.Update(func(tx *bbolt.Tx) error {
+ return tx.DeleteBucket([]byte(bucketName))
+ })
+}
+
+func InitBucket[T any](bucketName string) Bucket[T] {
+ b, err := CreateBucket[T](bucketName)
if err != nil {
log.Fatal("Failed to create bucket:", err)
}
@@ -30,7 +39,7 @@ func NewBucket[T any](bucketName string, db *bbolt.DB) Bucket[T] {
func (b *Bucket[T]) List() ([]T, error) {
result := make([]T, 0)
- return result, b.db.View(func(tx *bbolt.Tx) error {
+ return result, Db.View(func(tx *bbolt.Tx) error {
b := tx.Bucket([]byte(b.bucketName))
return b.ForEach(func(k, v []byte) error {
var data T
@@ -50,7 +59,7 @@ func (b *Bucket[T]) Put(key string, data T) error {
return err
}
- return b.db.Update(func(tx *bbolt.Tx) error {
+ return Db.Update(func(tx *bbolt.Tx) error {
b := tx.Bucket([]byte(b.bucketName))
return b.Put([]byte(key), v)
})
@@ -58,7 +67,7 @@ func (b *Bucket[T]) Put(key string, data T) error {
func (b *Bucket[T]) Get(key string) (T, error) {
var data T
- err := b.db.View(func(tx *bbolt.Tx) error {
+ err := Db.View(func(tx *bbolt.Tx) error {
b := tx.Bucket([]byte(b.bucketName))
v := b.Get([]byte(key))
return json.Unmarshal(v, &data)
@@ -67,14 +76,14 @@ func (b *Bucket[T]) Get(key string) (T, error) {
}
func (b *Bucket[T]) Delete(key string) error {
- return b.db.Update(func(tx *bbolt.Tx) error {
+ return Db.Update(func(tx *bbolt.Tx) error {
b := tx.Bucket([]byte(b.bucketName))
return b.Delete([]byte(key))
})
}
func (b *Bucket[T]) Clear() error {
- return b.db.Update(func(tx *bbolt.Tx) error {
+ return Db.Update(func(tx *bbolt.Tx) error {
err := tx.DeleteBucket([]byte(b.bucketName))
if err != nil {
return err
@@ -86,7 +95,7 @@ func (b *Bucket[T]) Clear() error {
func (b *Bucket[T]) Count() (int, error) {
var count int
- err := b.db.View(func(tx *bbolt.Tx) error {
+ err := Db.View(func(tx *bbolt.Tx) error {
b := tx.Bucket([]byte(b.bucketName))
count = b.Inspect().KeyN
return nil
@@ -95,7 +104,7 @@ func (b *Bucket[T]) Count() (int, error) {
}
var (
- db *bbolt.DB
+ Db *bbolt.DB
Users Bucket[UserInfo]
Apps Bucket[AppInfo]
@@ -107,17 +116,17 @@ var (
func Init(dbPath string) {
var err error
- db, err = bbolt.Open(dbPath, 0600, &bbolt.Options{Timeout: 1 * time.Second})
+ Db, err = bbolt.Open(dbPath, 0600, &bbolt.Options{Timeout: 1 * time.Second})
if err != nil {
log.Fatal("Failed to open database:", err)
}
- Users = NewBucket[UserInfo]("users", db)
- Usage = NewBucket[TokenUsage]("usage", db)
- Apps = NewBucket[AppInfo]("apps", db)
- LLMKeys = NewBucket[LLMKey]("llm_keys", db)
- AppPresets = NewBucket[AppPreset]("app_presets", db)
- Providers = NewBucket[[]byte]("providers", db)
+ Users = InitBucket[UserInfo]("users")
+ Usage = InitBucket[TokenUsage]("usage")
+ Apps = InitBucket[AppInfo]("apps")
+ LLMKeys = InitBucket[LLMKey]("llm_keys")
+ AppPresets = InitBucket[AppPreset]("app_presets")
+ Providers = InitBucket[[]byte]("providers")
count, err := AppPresets.Count()
if err != nil {
diff --git a/ui/src/App.vue b/ui/src/App.vue
index 57a9b20..b96e597 100644
--- a/ui/src/App.vue
+++ b/ui/src/App.vue
@@ -48,7 +48,7 @@ async function onUIOpened() {
async function pingActive() {
try {
- const resp = await serviceStore.fetch('ui/active', {
+ const resp = await serviceStore.api('ui/active', {
method: 'POST',
body: JSON.stringify({ session }),
})
diff --git a/ui/src/components/DeepSeekLoginCard.vue b/ui/src/components/DeepSeekLoginCard.vue
index fba5f8f..749b06e 100644
--- a/ui/src/components/DeepSeekLoginCard.vue
+++ b/ui/src/components/DeepSeekLoginCard.vue
@@ -6,13 +6,12 @@ import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
import { Input } from '@/components/ui/input'
import { useDeepSeekProvider } from '@/lib/providers/deepseek'
-import { useKeysStore, useServiceStore } from '@/stores'
-import ShumeiCaptcha from './ShumeiCaptcha.vue'
+import { useKeysStore } from '@/stores'
+import ShumeiCaptcha, { deviceId } from './ShumeiCaptcha.vue'
const { t, locale } = useI18n()
const keysStore = useKeysStore()
const provider = useDeepSeekProvider()
-const { fetch } = useServiceStore()
const captchaConfig = {
organization: 'P9usCUBauxft8eAmUXaZ',
@@ -58,26 +57,19 @@ async function sendSMS(rid: string) {
isSendingCode.value = true
// Send SMS with captcha verification result
- const res = await fetch('deepseek/sms', {
- body: JSON.stringify({
- locale: locale.value === 'zh-CN' ? 'zh_CN' : 'en_US',
- mobile_number: phoneNumber.value,
- turnstile_token: '',
- shumei_verification: { region: 'GLOBAL', rid },
- device_id: 'BpeI75x/8jEyx0Cf8+ceENFycckj5NmfAgbRg/za+xaDDzFfBlTiLwSJAqAg0PpFarvtePSmNZWgonTdCjntvWw==',
- }),
- method: 'POST',
+ const json = await provider.apis.sendSMS({
+ locale: locale.value === 'zh-CN' ? 'zh_CN' : 'en_US',
+ turnstile_token: '',
+ shumei_verification: { region: 'CN', rid },
+ device_id: await deviceId,
+ scenario: 'login',
+ mobile_number: phoneNumber.value,
})
- if (res.ok) {
- const { data } = await res.json()
- if (data.code === 0) {
- toast.success(t('smsSent'))
- startCountdown()
- }
- else {
- toast.error(t('smsFailure'))
- }
+ const { data } = json
+ if (data.code === 0) {
+ toast.success(t('smsSent'))
+ startCountdown()
}
else {
toast.error(t('smsFailure'))
@@ -99,29 +91,24 @@ async function login() {
try {
isLoading.value = true
- const res = await fetch('deepseek/login', {
- body: JSON.stringify({
- phone: phoneNumber.value,
- code: smsCode.value,
- area_code: '+86',
- }),
- method: 'POST',
+ await provider.apis.loginWithSMS({
+ region: 'CN',
+ locale: 'zh_CN',
+ mobile_number: phoneNumber.value,
+ area_code: '+86',
+ sms_verification_code: smsCode.value,
+ device_id: await deviceId,
+ os: 'web',
})
- if (res.ok) {
- await provider.refreshUser()
- // Clear login form
- phoneNumber.value = ''
- smsCode.value = ''
- toast.success(t('loginSuccess'))
+ await provider.refreshUser()
+ // Clear login form
+ phoneNumber.value = ''
+ smsCode.value = ''
+ toast.success(t('loginSuccess'))
- // Automatically create API key after successful login
- await keysStore.createAndAddKey(provider)
- }
- else {
- const errorData = await res.json()
- toast.error(errorData.message || t('loginFailure'))
- }
+ // Automatically create API key after successful login
+ await keysStore.createAndAddKey(provider)
}
catch (error) {
console.error('Login error:', error)
diff --git a/ui/src/components/OpenRouterLoginCard.vue b/ui/src/components/OpenRouterLoginCard.vue
index a3fb61d..cc66e74 100644
--- a/ui/src/components/OpenRouterLoginCard.vue
+++ b/ui/src/components/OpenRouterLoginCard.vue
@@ -4,10 +4,9 @@ import { toast } from 'vue-sonner'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { useOpenRouterProvider } from '@/lib/providers/openrouter'
-import { useKeysStore, useServiceStore } from '@/stores'
+import { useKeysStore } from '@/stores'
const { t } = useI18n()
-const { fetch } = useServiceStore()
const provider = useOpenRouterProvider()
const keysStore = useKeysStore()
@@ -30,12 +29,9 @@ function handleLogin() {
code: event.data.code,
}),
})
- const { key, user_id } = await response.json()
+ const { key, user_id: userId } = await response.json()
- await fetch('openrouter/authed', {
- method: 'POST',
- body: JSON.stringify({ key, userId: user_id }),
- })
+ await provider.apis.session.put({ key, userId })
await provider.refreshUser()
await keysStore.createAndAddKey(provider)
}
diff --git a/ui/src/components/ProviderRealNameDialog.vue b/ui/src/components/ProviderRealNameDialog.vue
index 939d9cb..ddb2691 100644
--- a/ui/src/components/ProviderRealNameDialog.vue
+++ b/ui/src/components/ProviderRealNameDialog.vue
@@ -304,7 +304,7 @@ zh-CN:
description: 应硅基流动要求,充值前需要先完成实名认证
authStatusDescription: 您的实名认证状态
verified: 已认证
- realName: 真实姓名
+ realName: 姓名
realNamePlaceholder: 请输入您的真实姓名
cardType: 证件类型
idCard: 身份证
diff --git a/ui/src/components/ShumeiCaptcha.vue b/ui/src/components/ShumeiCaptcha.vue
index 235a64e..eaf60f8 100644
--- a/ui/src/components/ShumeiCaptcha.vue
+++ b/ui/src/components/ShumeiCaptcha.vue
@@ -1,10 +1,46 @@
-
+
+