package controller import ( "crypto/tls" "encoding/base64" "encoding/json" "fmt" "genspark2api/common" "genspark2api/common/config" logger "genspark2api/common/loggger" "genspark2api/model" "github.com/deanxv/CycleTLS/cycletls" "github.com/gin-gonic/gin" "github.com/samber/lo" "io" "net/http" "strings" "time" ) func VideosForOpenAI(c *gin.Context) { client := cycletls.Init() defer safeClose(client) var openAIReq model.VideosGenerationRequest if err := c.BindJSON(&openAIReq); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } if lo.Contains(common.VideoModelList, openAIReq.Model) == false { c.JSON(400, gin.H{"error": "Invalid model"}) return } resp, err := VideoProcess(c, client, openAIReq) if err != nil { logger.Errorf(c.Request.Context(), fmt.Sprintf("VideoProcess err %v\n", err)) c.JSON(http.StatusInternalServerError, model.OpenAIErrorResponse{ OpenAIError: model.OpenAIError{ Message: err.Error(), Type: "request_error", Code: "500", }, }) return } else { c.JSON(200, resp) } } func VideoProcess(c *gin.Context, client cycletls.CycleTLS, openAIReq model.VideosGenerationRequest) (*model.VideosGenerationResponse, error) { const ( errNoValidCookies = "No valid cookies available" errServerErrMsg = "An error occurred with the current request, please try again" errNoValidTaskIDs = "No valid task IDs received" ) var ( maxRetries int cookie string chatId string ) cookieManager := config.NewCookieManager() ctx := c.Request.Context() // Initialize session manager and get initial cookie if len(config.SessionImageChatMap) == 0 { //logger.Warnf(ctx, "未配置环境变量 SESSION_IMAGE_CHAT_MAP, 可能会生图失败!") maxRetries = len(cookieManager.Cookies) var err error cookie, err = cookieManager.GetRandomCookie() if err != nil { logger.Errorf(ctx, "Failed to get initial cookie: %v", err) return nil, fmt.Errorf(errNoValidCookies) } } for attempt := 0; attempt < maxRetries; attempt++ { // Create request body requestBody, err := createVideoRequestBody(c, cookie, &openAIReq, chatId) if err != nil { logger.Errorf(ctx, "Failed to create request body: %v", err) return nil, err } // Marshal request body jsonData, err := json.Marshal(requestBody) if err != nil { logger.Errorf(ctx, "Failed to marshal request body: %v", err) return nil, err } // Make request response, err := makeVideoRequest(client, jsonData, cookie) if err != nil { logger.Errorf(ctx, "Failed to make video request: %v", err) return nil, err } body := response.Body switch { case common.IsRateLimit(body): logger.Warnf(ctx, "Cookie rate limited, switching to next cookie, attempt %d/%d, COOKIE:%s", attempt+1, maxRetries, cookie) config.AddRateLimitCookie(cookie, time.Now().Add(time.Duration(config.RateLimitCookieLockDuration)*time.Second)) cookie, err = cookieManager.GetNextCookie() if err != nil { logger.Errorf(ctx, "No more valid cookies available after attempt %d", attempt+1) c.JSON(http.StatusInternalServerError, gin.H{"error": errNoValidCookies}) return nil, fmt.Errorf(errNoValidCookies) } continue case common.IsFreeLimit(body): logger.Warnf(ctx, "Cookie free rate limited, switching to next cookie, attempt %d/%d, COOKIE:%s", attempt+1, maxRetries, cookie) config.AddRateLimitCookie(cookie, time.Now().Add(24*60*60*time.Second)) cookie, err = cookieManager.GetNextCookie() if err != nil { logger.Errorf(ctx, "No more valid cookies available after attempt %d", attempt+1) c.JSON(http.StatusInternalServerError, gin.H{"error": errNoValidCookies}) return nil, fmt.Errorf(errNoValidCookies) } continue case common.IsNotLogin(body): logger.Warnf(ctx, "Cookie Not Login, switching to next cookie, attempt %d/%d, COOKIE:%s", attempt+1, maxRetries, cookie) cookie, err = cookieManager.GetNextCookie() if err != nil { logger.Errorf(ctx, "No more valid cookies available after attempt %d", attempt+1) c.JSON(http.StatusInternalServerError, gin.H{"error": errNoValidCookies}) return nil, fmt.Errorf(errNoValidCookies) } continue case common.IsServerError(body): logger.Errorf(ctx, errServerErrMsg) return nil, fmt.Errorf(errServerErrMsg) case common.IsServerOverloaded(body): logger.Errorf(ctx, fmt.Sprintf("Server overloaded, please try again later.%s", "官方服务超载")) return nil, fmt.Errorf("Server overloaded, please try again later.") } projectId, taskIDs := extractVideoTaskIDs(response.Body) if len(taskIDs) == 0 { logger.Errorf(ctx, "Response body: %s", response.Body) return nil, fmt.Errorf(errNoValidTaskIDs) } // Poll for image URLs imageURLs := pollVideoTaskStatus(c, client, taskIDs, cookie) if len(imageURLs) == 0 { logger.Warnf(ctx, "No image URLs received, retrying with next cookie") continue } // Create response object result := &model.VideosGenerationResponse{ Created: time.Now().Unix(), Data: make([]*model.VideosGenerationDataResponse, 0, len(imageURLs)), } // Process image URLs for _, url := range imageURLs { data := &model.VideosGenerationDataResponse{ URL: url, RevisedPrompt: openAIReq.Prompt, } //if openAIReq.ResponseFormat == "b64_json" { // base64Str, err := getBase64ByUrl(data.URL) // if err != nil { // logger.Errorf(ctx, "getBase64ByUrl error: %v", err) // continue // } // data.B64Json = "data:image/webp;base64," + base64Str //} result.Data = append(result.Data, data) } // Handle successful case if len(result.Data) > 0 { // Delete temporary session if needed if config.AutoDelChat == 1 { go func() { client := cycletls.Init() defer safeClose(client) makeDeleteRequest(client, cookie, projectId) }() } return result, nil } } // All retries exhausted logger.Errorf(ctx, "All cookies exhausted after %d attempts", maxRetries) return nil, fmt.Errorf("all cookies are temporarily unavailable") } func createVideoRequestBody(c *gin.Context, cookie string, openAIReq *model.VideosGenerationRequest, chatId string) (map[string]interface{}, error) { // 创建模型配置 modelConfigs := []map[string]interface{}{ { "model": openAIReq.Model, "aspect_ratio": openAIReq.AspectRatio, "reflection_enabled": openAIReq.AutoPrompt, "duration": openAIReq.Duration, }, } // 创建消息数组 var messages []map[string]interface{} if openAIReq.Image != "" { var base64Data string if strings.HasPrefix(openAIReq.Image, "http://") || strings.HasPrefix(openAIReq.Image, "https://") { // 下载文件 bytes, err := fetchImageBytes(openAIReq.Image) if err != nil { logger.Errorf(c.Request.Context(), fmt.Sprintf("fetchImageBytes err %v\n", err)) return nil, fmt.Errorf("fetchImageBytes err %v\n", err) } contentType := http.DetectContentType(bytes) if strings.HasPrefix(contentType, "image/") { // 是图片类型,转换为base64 base64Data = "data:image/jpeg;base64," + base64.StdEncoding.EncodeToString(bytes) } } else if common.IsImageBase64(openAIReq.Image) { // 如果已经是 base64 格式 if !strings.HasPrefix(openAIReq.Image, "data:image") { base64Data = "data:image/jpeg;base64," + openAIReq.Image } else { base64Data = openAIReq.Image } } // 构建包含图片的消息 if base64Data != "" { messages = []map[string]interface{}{ { "role": "user", "content": []map[string]interface{}{ { "type": "image_url", "image_url": map[string]interface{}{ "url": base64Data, }, }, { "type": "text", "text": openAIReq.Prompt, }, }, }, } } } // 如果没有图片或处理图片失败,使用纯文本消息 if len(messages) == 0 { messages = []map[string]interface{}{ { "role": "user", "content": openAIReq.Prompt, }, } } var currentQueryString string if len(chatId) != 0 { currentQueryString = fmt.Sprintf("id=%s&type=%s", chatId, videoType) } else { currentQueryString = fmt.Sprintf("type=%s", videoType) } // 创建请求体 requestBody := map[string]interface{}{ "type": "COPILOT_MOA_VIDEO", //"current_query_string": "type=COPILOT_MOA_IMAGE", "current_query_string": currentQueryString, "messages": messages, "user_s_input": openAIReq.Prompt, "action_params": map[string]interface{}{}, "extra_data": map[string]interface{}{ "model_configs": modelConfigs, "imageModelMap": map[string]interface{}{}, }, } logger.Debug(c.Request.Context(), fmt.Sprintf("RequestBody: %v", requestBody)) if strings.TrimSpace(config.RecaptchaProxyUrl) == "" || (!strings.HasPrefix(config.RecaptchaProxyUrl, "http://") && !strings.HasPrefix(config.RecaptchaProxyUrl, "https://")) { return requestBody, nil } else { tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } client := &http.Client{Transport: tr} // 检查并补充 RecaptchaProxyUrl 的末尾斜杠 if !strings.HasSuffix(config.RecaptchaProxyUrl, "/") { config.RecaptchaProxyUrl += "/" } // 创建请求 req, err := http.NewRequest("GET", fmt.Sprintf("%sgenspark", config.RecaptchaProxyUrl), nil) if err != nil { logger.Errorf(c.Request.Context(), fmt.Sprintf("创建/genspark请求失败 %v\n", err)) return nil, err } // 设置请求头 req.Header.Set("Content-Type", "application/json") req.Header.Set("Cookie", cookie) // 发送请求 resp, err := client.Do(req) if err != nil { logger.Errorf(c.Request.Context(), fmt.Sprintf("发送/genspark请求失败 %v\n", err)) return nil, err } defer resp.Body.Close() // 读取响应体 body, err := io.ReadAll(resp.Body) if err != nil { logger.Errorf(c.Request.Context(), fmt.Sprintf("读取/genspark响应失败 %v\n", err)) return nil, err } type Response struct { Code int `json:"code"` Token string `json:"token"` Message string `json:"message"` } if resp.StatusCode == 200 { var response Response if err := json.Unmarshal(body, &response); err != nil { logger.Errorf(c.Request.Context(), fmt.Sprintf("读取/genspark JSON 失败 %v\n", err)) return nil, err } if response.Code == 200 { logger.Debugf(c.Request.Context(), fmt.Sprintf("g_recaptcha_token: %v\n", response.Token)) requestBody["g_recaptcha_token"] = response.Token logger.Infof(c.Request.Context(), fmt.Sprintf("cheat success!")) return requestBody, nil } else { logger.Errorf(c.Request.Context(), fmt.Sprintf("读取/genspark token 失败 %v\n", err)) return nil, err } } else { logger.Errorf(c.Request.Context(), fmt.Sprintf("请求/genspark失败 %v\n", err)) return nil, err } } } func makeVideoRequest(client cycletls.CycleTLS, jsonData []byte, cookie string) (cycletls.Response, error) { accept := "*/*" return client.Do(apiEndpoint, cycletls.Options{ UserAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome", Timeout: 10 * 60 * 60, Proxy: config.ProxyUrl, // 在每个请求中设置代理 Body: string(jsonData), Method: "POST", Headers: map[string]string{ "Content-Type": "application/json", "Accept": accept, "Origin": baseURL, "Referer": baseURL + "/", "Cookie": cookie, "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome", }, }, "POST") } func extractVideoTaskIDs(responseBody string) (string, []string) { var taskIDs []string var projectId string // 分行处理响应 lines := strings.Split(responseBody, "\n") for _, line := range lines { // 找到包含project_id的行 if strings.Contains(line, "project_start") { // 去掉"data: "前缀 jsonStr := strings.TrimPrefix(line, "data: ") // 解析JSON var jsonResp struct { ProjectID string `json:"id"` } if err := json.Unmarshal([]byte(jsonStr), &jsonResp); err != nil { continue } // 保存project_id projectId = jsonResp.ProjectID } // 找到包含task_id的行 if strings.Contains(line, "task_id") { // 去掉"data: "前缀 jsonStr := strings.TrimPrefix(line, "data: ") // 解析外层JSON var outerJSON struct { Content string `json:"content"` } if err := json.Unmarshal([]byte(jsonStr), &outerJSON); err != nil { continue } // 解析内层JSON (content字段) var innerJSON struct { GeneratedVideos []struct { TaskID string `json:"task_id"` } `json:"generated_videos"` } if err := json.Unmarshal([]byte(outerJSON.Content), &innerJSON); err != nil { continue } // 提取所有task_id for _, img := range innerJSON.GeneratedVideos { if img.TaskID != "" { taskIDs = append(taskIDs, img.TaskID) } } } } return projectId, taskIDs } func pollVideoTaskStatus(c *gin.Context, client cycletls.CycleTLS, taskIDs []string, cookie string) []string { var imageURLs []string requestData := map[string]interface{}{ "task_ids": taskIDs, } jsonData, err := json.Marshal(requestData) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to marshal request data"}) return imageURLs } sseChan, err := client.DoSSE("https://www.genspark.ai/api/vg_tasks_status", cycletls.Options{ Timeout: 10 * 60 * 60, Proxy: config.ProxyUrl, // 在每个请求中设置代理 Body: string(jsonData), Method: "POST", Headers: map[string]string{ "Content-Type": "application/json", "Accept": "*/*", "Origin": baseURL, "Referer": baseURL + "/", "Cookie": cookie, "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome", }, }, "POST") if err != nil { logger.Errorf(c, "Failed to make stream request: %v", err) return imageURLs } for response := range sseChan { if response.Done { //logger.Warnf(c.Request.Context(), response.Data) return imageURLs } data := response.Data if data == "" { continue } logger.Debug(c.Request.Context(), strings.TrimSpace(data)) var responseData map[string]interface{} if err := json.Unmarshal([]byte(data), &responseData); err != nil { continue } if responseData["type"] == "TASKS_STATUS_COMPLETE" { if finalStatus, ok := responseData["final_status"].(map[string]interface{}); ok { for _, taskID := range taskIDs { if task, exists := finalStatus[taskID].(map[string]interface{}); exists { if status, ok := task["status"].(string); ok && status == "SUCCESS" { if urls, ok := task["video_urls"].([]interface{}); ok && len(urls) > 0 { if imageURL, ok := urls[0].(string); ok { imageURLs = append(imageURLs, imageURL) } } } } } } } } return imageURLs }