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 @@ - + +