package controller import ( "bufio" "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" "io/ioutil" "net/http" "strings" "time" ) const ( errNoValidCookies = "No valid cookies available" ) const ( baseURL = "https://www.genspark.ai" apiEndpoint = baseURL + "/api/copilot/ask" deleteEndpoint = baseURL + "/api/project/delete?project_id=%s" uploadEndpoint = baseURL + "/api/get_upload_personal_image_url" chatType = "COPILOT_MOA_CHAT" imageType = "COPILOT_MOA_IMAGE" videoType = "COPILOT_MOA_VIDEO" responseIDFormat = "chatcmpl-%s" ) type OpenAIChatMessage struct { Role string `json:"role"` Content interface{} `json:"content"` } type OpenAIChatCompletionRequest struct { Messages []OpenAIChatMessage Model string } // ChatForOpenAI 处理OpenAI聊天请求 func ChatForOpenAI(c *gin.Context) { client := cycletls.Init() defer safeClose(client) var openAIReq model.OpenAIChatCompletionRequest if err := c.BindJSON(&openAIReq); err != nil { logger.Errorf(c.Request.Context(), err.Error()) c.JSON(http.StatusInternalServerError, model.OpenAIErrorResponse{ OpenAIError: model.OpenAIError{ Message: "Invalid request parameters", Type: "request_error", Code: "500", }, }) return } // 模型映射 if strings.HasPrefix(openAIReq.Model, "deepseek") { openAIReq.Model = strings.Replace(openAIReq.Model, "deepseek", "deep-seek", 1) } // 初始化cookie cookieManager := config.NewCookieManager() cookie, err := cookieManager.GetRandomCookie() if err != nil { logger.Errorf(c.Request.Context(), "Failed to get initial cookie: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": errNoValidCookies}) return } if lo.Contains(common.ImageModelList, openAIReq.Model) { responseId := fmt.Sprintf(responseIDFormat, time.Now().Format("20060102150405")) if len(openAIReq.GetUserContent()) == 0 { logger.Errorf(c.Request.Context(), "user content is null") c.JSON(http.StatusInternalServerError, model.OpenAIErrorResponse{ OpenAIError: model.OpenAIError{ Message: "Invalid request parameters", Type: "request_error", Code: "500", }, }) return } jsonData, err := json.Marshal(openAIReq.GetUserContent()[0]) if err != nil { logger.Errorf(c.Request.Context(), err.Error()) c.JSON(500, gin.H{"error": "Failed to marshal request body"}) return } resp, err := ImageProcess(c, client, model.OpenAIImagesGenerationRequest{ Model: openAIReq.Model, Prompt: openAIReq.GetUserContent()[0], }) if err != nil { logger.Errorf(c.Request.Context(), err.Error()) c.JSON(http.StatusInternalServerError, model.OpenAIErrorResponse{ OpenAIError: model.OpenAIError{ Message: err.Error(), Type: "request_error", Code: "500", }, }) return } else { data := resp.Data var content []string for _, item := range data { content = append(content, fmt.Sprintf("![Image](%s)", item.URL)) } if openAIReq.Stream { streamResp := createStreamResponse(responseId, openAIReq.Model, jsonData, model.OpenAIDelta{Content: strings.Join(content, "\n"), Role: "assistant"}, nil) err := sendSSEvent(c, streamResp) if err != nil { logger.Errorf(c.Request.Context(), err.Error()) c.JSON(http.StatusInternalServerError, model.OpenAIErrorResponse{ OpenAIError: model.OpenAIError{ Message: err.Error(), Type: "request_error", Code: "500", }, }) return } c.SSEvent("", " [DONE]") return } else { jsonBytes, _ := json.Marshal(openAIReq.Messages) promptTokens := common.CountTokenText(string(jsonBytes), openAIReq.Model) completionTokens := common.CountTokenText(strings.Join(content, "\n"), openAIReq.Model) finishReason := "stop" // 创建并返回 OpenAIChatCompletionResponse 结构 resp := model.OpenAIChatCompletionResponse{ ID: fmt.Sprintf(responseIDFormat, time.Now().Format("20060102150405")), Object: "chat.completion", Created: time.Now().Unix(), Model: openAIReq.Model, Choices: []model.OpenAIChoice{ { Message: model.OpenAIMessage{ Role: "assistant", Content: strings.Join(content, "\n"), }, FinishReason: &finishReason, }, }, Usage: model.OpenAIUsage{ PromptTokens: promptTokens, CompletionTokens: completionTokens, TotalTokens: promptTokens + completionTokens, }, } c.JSON(200, resp) return } } } var isSearchModel bool if strings.HasSuffix(openAIReq.Model, "-search") { isSearchModel = true } requestBody, err := createRequestBody(c, client, cookie, &openAIReq) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } //jsonData, err := json.Marshal(requestBody) //if err != nil { // c.JSON(500, gin.H{"error": "Failed to marshal request body"}) // return //} if openAIReq.Stream { handleStreamRequest(c, client, cookie, cookieManager, requestBody, openAIReq.Model, isSearchModel) } else { handleNonStreamRequest(c, client, cookie, cookieManager, requestBody, openAIReq.Model, isSearchModel) } } func processMessages(c *gin.Context, client cycletls.CycleTLS, cookie string, messages []model.OpenAIChatMessage) error { //client := cycletls.Init() //defer client.Close() for i, message := range messages { if contentArray, ok := message.Content.([]interface{}); ok { for j, content := range contentArray { if contentMap, ok := content.(map[string]interface{}); ok { if contentType, ok := contentMap["type"].(string); ok && contentType == "image_url" { if imageMap, ok := contentMap["image_url"].(map[string]interface{}); ok { if url, ok := imageMap["url"].(string); ok { err := processUrl(c, client, cookie, url, imageMap, j, contentArray) if err != nil { logger.Errorf(c.Request.Context(), fmt.Sprintf("processUrl err %v\n", err)) return fmt.Errorf("processUrl err: %v", err) } } } } } } messages[i].Content = contentArray } } return nil } func processUrl(c *gin.Context, client cycletls.CycleTLS, cookie string, url string, imageMap map[string]interface{}, index int, contentArray []interface{}) error { // 判断是否为URL if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") { // 下载文件 bytes, err := fetchImageBytes(url) if err != nil { logger.Errorf(c.Request.Context(), fmt.Sprintf("fetchImageBytes err %v\n", err)) return fmt.Errorf("fetchImageBytes err %v\n", err) } err = processBytes(c, client, cookie, bytes, imageMap, index, contentArray) if err != nil { logger.Errorf(c.Request.Context(), fmt.Sprintf("processBytes err %v\n", err)) return fmt.Errorf("processBytes err %v\n", err) } } else { // 尝试解析base64 var bytes []byte var err error // 处理可能包含 data:image/ 前缀的base64 base64Str := url if strings.Contains(url, ";base64,") { base64Str = strings.Split(url, ";base64,")[1] } bytes, err = base64.StdEncoding.DecodeString(base64Str) if err != nil { logger.Errorf(c.Request.Context(), fmt.Sprintf("base64.StdEncoding.DecodeString err %v\n", err)) return fmt.Errorf("base64.StdEncoding.DecodeString err: %v\n", err) } err = processBytes(c, client, cookie, bytes, imageMap, index, contentArray) if err != nil { logger.Errorf(c.Request.Context(), fmt.Sprintf("processBytes err %v\n", err)) return fmt.Errorf("processBytes err: %v\n", err) } } return nil } func processBytes(c *gin.Context, client cycletls.CycleTLS, cookie string, bytes []byte, imageMap map[string]interface{}, index int, contentArray []interface{}) error { // 检查是否为图片类型 contentType := http.DetectContentType(bytes) if strings.HasPrefix(contentType, "image/") { // 是图片类型,转换为base64 base64Data := "data:image/jpeg;base64," + base64.StdEncoding.EncodeToString(bytes) imageMap["url"] = base64Data } else { response, err := makeGetUploadUrlRequest(client, cookie) if err != nil { logger.Errorf(c.Request.Context(), fmt.Sprintf("makeGetUploadUrlRequest err %v\n", err)) return fmt.Errorf("makeGetUploadUrlRequest err: %v\n", err) } var jsonResponse map[string]interface{} if err := json.Unmarshal([]byte(response.Body), &jsonResponse); err != nil { logger.Errorf(c.Request.Context(), fmt.Sprintf("Unmarshal err %v\n", err)) return fmt.Errorf("Unmarshal err: %v\n", err) } uploadImageUrl, ok := jsonResponse["data"].(map[string]interface{})["upload_image_url"].(string) privateStorageUrl, ok := jsonResponse["data"].(map[string]interface{})["private_storage_url"].(string) if !ok { //fmt.Println("Failed to extract upload_image_url") return fmt.Errorf("Failed to extract upload_image_url") } // 发送OPTIONS预检请求 //_, err = makeOptionsRequest(client, uploadImageUrl) //if err != nil { // return //} // 上传文件 _, err = makeUploadRequest(client, uploadImageUrl, bytes) if err != nil { logger.Errorf(c.Request.Context(), fmt.Sprintf("makeUploadRequest err %v\n", err)) return fmt.Errorf("makeUploadRequest err: %v\n", err) } //fmt.Println(resp) // 创建新的 private_file 格式的内容 privateFile := map[string]interface{}{ "type": "private_file", "private_file": map[string]interface{}{ "name": "file", // 你可能需要从原始文件名或其他地方获取 "type": contentType, "size": len(bytes), "ext": strings.Split(contentType, "/")[1], // 简单处理,可能需要更复杂的逻辑 "private_storage_url": privateStorageUrl, }, } // 替换数组中的元素 contentArray[index] = privateFile } return nil } // 获取文件字节数组的函数 func fetchImageBytes(url string) ([]byte, error) { resp, err := http.Get(url) if err != nil { return nil, fmt.Errorf("http.Get err: %v\n", err) } defer resp.Body.Close() return ioutil.ReadAll(resp.Body) } func createRequestBody(c *gin.Context, client cycletls.CycleTLS, cookie string, openAIReq *model.OpenAIChatCompletionRequest) (map[string]interface{}, error) { openAIReq.SystemMessagesProcess(openAIReq.Model) if config.PRE_MESSAGES_JSON != "" { err := openAIReq.PrependMessagesFromJSON(config.PRE_MESSAGES_JSON) if err != nil { return nil, fmt.Errorf("PrependMessagesFromJSON err: %v PrependMessagesFromJSON:", err, config.PRE_MESSAGES_JSON) } } // 处理消息中的图像 URL err := processMessages(c, client, cookie, openAIReq.Messages) if err != nil { logger.Errorf(c.Request.Context(), "processMessages err: %v", err) return nil, fmt.Errorf("processMessages err: %v", err) } currentQueryString := fmt.Sprintf("type=%s", chatType) //查找 key 对应的 value if chatId, ok := config.ModelChatMap[openAIReq.Model]; ok { currentQueryString = fmt.Sprintf("id=%s&type=%s", chatId, chatType) } else if chatId, ok := config.GlobalSessionManager.GetChatID(cookie, openAIReq.Model); ok { currentQueryString = fmt.Sprintf("id=%s&type=%s", chatId, chatType) } else { openAIReq.FilterUserMessage() } requestWebKnowledge := false models := []string{openAIReq.Model} if strings.HasSuffix(openAIReq.Model, "-search") { openAIReq.Model = strings.Replace(openAIReq.Model, "-search", "", 1) requestWebKnowledge = true models = []string{openAIReq.Model} } if !lo.Contains(common.TextModelList, openAIReq.Model) { models = common.MixtureModelList } // 创建请求体 requestBody := map[string]interface{}{ "type": chatType, "current_query_string": currentQueryString, "messages": openAIReq.Messages, "action_params": map[string]interface{}{}, "extra_data": map[string]interface{}{ "models": models, "run_with_another_model": false, "writingContent": nil, "request_web_knowledge": requestWebKnowledge, }, } logger.Debug(c.Request.Context(), fmt.Sprintf("RequestBody: %v", requestBody)) return requestBody, nil } func createImageRequestBody(c *gin.Context, cookie string, openAIReq *model.OpenAIImagesGenerationRequest, chatId string) (map[string]interface{}, error) { if openAIReq.Model == "dall-e-3" { openAIReq.Model = "dalle-3" } // 创建模型配置 modelConfigs := []map[string]interface{}{ { "model": openAIReq.Model, "aspect_ratio": "auto", "use_personalized_models": false, "fashion_profile_id": nil, "hd": false, "reflection_enabled": false, "style": "auto", }, } // 创建消息数组 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, imageType) } else { currentQueryString = fmt.Sprintf("type=%s", imageType) } // 创建请求体 requestBody := map[string]interface{}{ "type": "COPILOT_MOA_IMAGE", //"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, "llm_model": "gpt-4o", "imageModelMap": map[string]interface{}{}, "writingContent": nil, }, } 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 } } } // createStreamResponse 创建流式响应 func createStreamResponse(responseId, modelName string, jsonData []byte, delta model.OpenAIDelta, finishReason *string) model.OpenAIChatCompletionResponse { promptTokens := common.CountTokenText(string(jsonData), modelName) completionTokens := common.CountTokenText(delta.Content, modelName) return model.OpenAIChatCompletionResponse{ ID: responseId, Object: "chat.completion.chunk", Created: time.Now().Unix(), Model: modelName, Choices: []model.OpenAIChoice{ { Index: 0, Delta: delta, FinishReason: finishReason, }, }, Usage: model.OpenAIUsage{ PromptTokens: promptTokens, CompletionTokens: completionTokens, TotalTokens: promptTokens + completionTokens, }, } } // handleMessageFieldDelta 处理消息字段增量 func handleMessageFieldDelta(c *gin.Context, event map[string]interface{}, responseId, modelName string, jsonData []byte) error { fieldName, ok := event["field_name"].(string) if !ok { return nil } // 基础允许列表(所有配置下都需要处理的字段) baseAllowed := fieldName == "session_state.answer" || strings.Contains(fieldName, "session_state.streaming_detail_answer") || fieldName == "session_state.streaming_markmap" // 需要显示思考过程时需要额外处理的字段 if config.ReasoningHide != 1 { baseAllowed = baseAllowed || fieldName == "session_state.answerthink_is_started" || fieldName == "session_state.answerthink" || fieldName == "session_state.answerthink_is_finished" } if !baseAllowed { return nil } // 获取 delta 内容 var delta string switch { case (modelName == "o1" || modelName == "o3-mini-high") && fieldName == "session_state.answer": delta, _ = event["field_value"].(string) default: delta, _ = event["delta"].(string) } // 创建基础响应 createResponse := func(content string) model.OpenAIChatCompletionResponse { return createStreamResponse( responseId, modelName, jsonData, model.OpenAIDelta{Content: content, Role: "assistant"}, nil, ) } // 发送基础事件 var err error if err = sendSSEvent(c, createResponse(delta)); err != nil { return err } // 处理思考过程标记 if config.ReasoningHide != 1 { switch fieldName { case "session_state.answerthink_is_started": err = sendSSEvent(c, createResponse("\n")) case "session_state.answerthink_is_finished": err = sendSSEvent(c, createResponse("\n")) } } return err } type Content struct { DetailAnswer string `json:"detailAnswer"` } func getDetailAnswer(eventMap map[string]interface{}) (string, error) { // 获取 content 字段的值 contentStr, ok := eventMap["content"].(string) if !ok { return "", fmt.Errorf("content is not a string") } // 解析内层的 JSON var content Content if err := json.Unmarshal([]byte(contentStr), &content); err != nil { return "", err } return content.DetailAnswer, nil } // handleMessageResult 处理消息结果 func handleMessageResult(c *gin.Context, event map[string]interface{}, responseId, modelName string, jsonData []byte, searchModel bool) bool { finishReason := "stop" var delta string var err error if modelName == "o1" && searchModel { delta, err = getDetailAnswer(event) if err != nil { logger.Errorf(c.Request.Context(), "getDetailAnswer err: %v", err) return false } } streamResp := createStreamResponse(responseId, modelName, jsonData, model.OpenAIDelta{Content: delta, Role: "assistant"}, &finishReason) if err := sendSSEvent(c, streamResp); err != nil { logger.Warnf(c.Request.Context(), "sendSSEvent err: %v", err) return false } c.SSEvent("", " [DONE]") return false } // sendSSEvent 发送SSE事件 func sendSSEvent(c *gin.Context, response model.OpenAIChatCompletionResponse) error { jsonResp, err := json.Marshal(response) if err != nil { logger.Errorf(c.Request.Context(), "Failed to marshal response: %v", err) return err } c.SSEvent("", " "+string(jsonResp)) c.Writer.Flush() return nil } // makeRequest 发送HTTP请求 func makeRequest(client cycletls.CycleTLS, jsonData []byte, cookie string, isStream bool) (cycletls.Response, error) { accept := "application/json" if isStream { accept = "text/event-stream" } return client.Do(apiEndpoint, cycletls.Options{ 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") } // makeRequest 发送HTTP请求 func makeImageRequest(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 makeDeleteRequest(client cycletls.CycleTLS, cookie, projectId string) (cycletls.Response, error) { // 不删除环境变量中的map中的对话 for _, v := range config.ModelChatMap { if v == projectId { return cycletls.Response{}, nil } } for _, v := range config.GlobalSessionManager.GetChatIDsByCookie(cookie) { if v == projectId { return cycletls.Response{}, nil } } for _, v := range config.SessionImageChatMap { if v == projectId { return cycletls.Response{}, nil } } accept := "application/json" return client.Do(fmt.Sprintf(deleteEndpoint, projectId), cycletls.Options{ Timeout: 10 * 60 * 60, Proxy: config.ProxyUrl, // 在每个请求中设置代理 Method: "GET", 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", }, }, "GET") } func makeGetUploadUrlRequest(client cycletls.CycleTLS, cookie string) (cycletls.Response, error) { accept := "*/*" return client.Do(fmt.Sprintf(uploadEndpoint), cycletls.Options{ Timeout: 10 * 60 * 60, Proxy: config.ProxyUrl, // 在每个请求中设置代理 Method: "GET", 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", }, }, "GET") } //func makeOptionsRequest(client cycletls.CycleTLS, uploadUrl string) (cycletls.Response, error) { // return client.Do(uploadUrl, cycletls.Options{ // Method: "OPTIONS", // Headers: map[string]string{ // "Accept": "*/*", // "Access-Control-Request-Headers": "x-ms-blob-type", // "Access-Control-Request-Method": "PUT", // "Origin": "https://www.genspark.ai", // "Sec-Fetch-Dest": "empty", // "Sec-Fetch-Mode": "cors", // "Sec-Fetch-Site": "cross-site", // }, // UserAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36", // }, "OPTIONS") //} func makeUploadRequest(client cycletls.CycleTLS, uploadUrl string, fileBytes []byte) (cycletls.Response, error) { return client.Do(uploadUrl, cycletls.Options{ Timeout: 10 * 60 * 60, Proxy: config.ProxyUrl, // 在每个请求中设置代理 Method: "PUT", Body: string(fileBytes), Headers: map[string]string{ "Accept": "*/*", "x-ms-blob-type": "BlockBlob", "Content-Type": "application/octet-stream", "Content-Length": fmt.Sprintf("%d", len(fileBytes)), "Origin": "https://www.genspark.ai", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "cross-site", }, }, "PUT") } // handleStreamRequest 处理流式请求 //func handleStreamRequest(c *gin.Context, client cycletls.CycleTLS, cookie string, jsonData []byte, model string) { // c.Header("Content-Type", "text/event-stream") // c.Header("Cache-Control", "no-cache") // c.Header("Connection", "keep-alive") // // responseId := fmt.Sprintf(responseIDFormat, time.Now().Format("20060102150405")) // // c.Stream(func(w io.Writer) bool { // sseChan, err := makeStreamRequest(c, client, jsonData, cookie) // if err != nil { // logger.Errorf(c.Request.Context(), "makeStreamRequest err: %v", err) // return false // } // // return handleStreamResponse(c, sseChan, responseId, cookie, model, jsonData) // }) //} func handleStreamRequest(c *gin.Context, client cycletls.CycleTLS, cookie string, cookieManager *config.CookieManager, requestBody map[string]interface{}, modelName string, searchModel bool) { const ( errNoValidCookies = "No valid cookies available" errCloudflareChallengeMsg = "Detected Cloudflare Challenge Page" errCloudflareBlock = "CloudFlare: Sorry, you have been blocked" errServerErrMsg = "An error occurred with the current request, please try again." errServiceUnavailable = "Genspark Service Unavailable" ) c.Header("Content-Type", "text/event-stream") c.Header("Cache-Control", "no-cache") c.Header("Connection", "keep-alive") responseId := fmt.Sprintf(responseIDFormat, time.Now().Format("20060102150405")) ctx := c.Request.Context() maxRetries := len(cookieManager.Cookies) c.Stream(func(w io.Writer) bool { for attempt := 0; attempt < maxRetries; attempt++ { requestBody, err := cheat(requestBody, c, cookie) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return false } jsonData, err := json.Marshal(requestBody) if err != nil { c.JSON(500, gin.H{"error": "Failed to marshal request body"}) return false } sseChan, err := makeStreamRequest(c, client, jsonData, cookie) if err != nil { logger.Errorf(ctx, "makeStreamRequest err on attempt %d: %v", attempt+1, err) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return false } var projectId string isRateLimit := false SSELoop: for response := range sseChan { if response.Done { logger.Debugf(ctx, response.Data) return false } data := response.Data if data == "" { continue } logger.Debug(ctx, strings.TrimSpace(data)) switch { case common.IsCloudflareChallenge(data): logger.Errorf(ctx, errCloudflareChallengeMsg) c.JSON(http.StatusInternalServerError, gin.H{"error": errCloudflareChallengeMsg}) return false case common.IsCloudflareBlock(data): logger.Errorf(ctx, errCloudflareBlock) c.JSON(http.StatusInternalServerError, gin.H{"error": errCloudflareBlock}) return false case common.IsServiceUnavailablePage(data): logger.Errorf(ctx, errServiceUnavailable) c.JSON(http.StatusInternalServerError, gin.H{"error": errServiceUnavailable}) return false case common.IsServerError(data): logger.Errorf(ctx, errServerErrMsg) c.JSON(http.StatusInternalServerError, gin.H{"error": errServerErrMsg}) return false case common.IsRateLimit(data): isRateLimit = true 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)) break SSELoop // 使用 label 跳出 SSE 循环 case common.IsFreeLimit(data): isRateLimit = true 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 //config.RemoveCookie(cookie) break SSELoop // 使用 label 跳出 SSE 循环 case common.IsNotLogin(data): isRateLimit = true logger.Warnf(ctx, "Cookie Not Login, switching to next cookie, attempt %d/%d, COOKIE:%s", attempt+1, maxRetries, cookie) // 删除cookie config.RemoveCookie(cookie) break SSELoop // 使用 label 跳出 SSE 循环 } // 处理事件流数据 if shouldContinue := processStreamData(c, data, &projectId, cookie, responseId, modelName, jsonData, searchModel); !shouldContinue { return false } } if !isRateLimit { return true } // 获取下一个可用的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 false } // requestBody重制chatId currentQueryString := fmt.Sprintf("type=%s", chatType) if chatId, ok := config.GlobalSessionManager.GetChatID(cookie, modelName); ok { currentQueryString = fmt.Sprintf("id=%s&type=%s", chatId, chatType) } requestBody["current_query_string"] = currentQueryString } logger.Errorf(ctx, "All cookies exhausted after %d attempts", maxRetries) c.JSON(http.StatusInternalServerError, gin.H{"error": "All cookies are temporarily unavailable."}) return false }) } func cheat(requestBody map[string]interface{}, c *gin.Context, cookie string) (map[string]interface{}, error) { 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 失败,查看 playwright-proxy log")) return nil, err } } else { logger.Errorf(c.Request.Context(), fmt.Sprintf("请求/genspark失败,查看 playwright-proxy log")) return nil, err } } } // 处理流式数据的辅助函数,返回bool表示是否继续处理 func processStreamData(c *gin.Context, data string, projectId *string, cookie, responseId, model string, jsonData []byte, searchModel bool) bool { data = strings.TrimSpace(data) //if !strings.HasPrefix(data, "data: ") { // return true //} data = strings.TrimPrefix(data, "data: ") if !strings.HasPrefix(data, "{\"id\":") && !strings.HasPrefix(data, "{\"message_id\":") { return true } var event map[string]interface{} if err := json.Unmarshal([]byte(data), &event); err != nil { logger.Errorf(c.Request.Context(), "Failed to unmarshal event: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return false } eventType, ok := event["type"].(string) if !ok { return true } switch eventType { case "project_start": *projectId, _ = event["id"].(string) case "message_field": if err := handleMessageFieldDelta(c, event, responseId, model, jsonData); err != nil { logger.Errorf(c.Request.Context(), "handleMessageFieldDelta err: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return false } case "message_field_delta": if err := handleMessageFieldDelta(c, event, responseId, model, jsonData); err != nil { logger.Errorf(c.Request.Context(), "handleMessageFieldDelta err: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return false } case "message_result": go func() { if config.AutoModelChatMapType == 1 { // 保存映射 config.GlobalSessionManager.AddSession(cookie, model, *projectId) } else { if config.AutoDelChat == 1 { client := cycletls.Init() defer safeClose(client) makeDeleteRequest(client, cookie, *projectId) } } }() return handleMessageResult(c, event, responseId, model, jsonData, searchModel) } return true } func makeStreamRequest(c *gin.Context, client cycletls.CycleTLS, jsonData []byte, cookie string) (<-chan cycletls.SSEResponse, error) { options := cycletls.Options{ Timeout: 10 * 60 * 60, Proxy: config.ProxyUrl, // 在每个请求中设置代理 Body: string(jsonData), Method: "POST", Headers: map[string]string{ "Content-Type": "application/json", "Accept": "text/event-stream", "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", }, } logger.Debug(c.Request.Context(), fmt.Sprintf("cookie: %v", cookie)) sseChan, err := client.DoSSE(apiEndpoint, options, "POST") if err != nil { logger.Errorf(c, "Failed to make stream request: %v", err) return nil, fmt.Errorf("Failed to make stream request: %v", err) } return sseChan, nil } // handleNonStreamRequest 处理非流式请求 // // func handleNonStreamRequest(c *gin.Context, client cycletls.CycleTLS, cookie string, jsonData []byte, modelName string) { // response, err := makeRequest(client, jsonData, cookie, false) // if err != nil { // logger.Errorf(c.Request.Context(), "makeRequest err: %v", err) // c.JSON(500, gin.H{"error": err.Error()}) // return // } // // reader := strings.NewReader(response.Body) // scanner := bufio.NewScanner(reader) // // var content string // var firstline string // for scanner.Scan() { // line := scanner.Text() // firstline = line // logger.Debug(c.Request.Context(), strings.TrimSpace(line)) // // if common.IsCloudflareChallenge(line) { // logger.Errorf(c.Request.Context(), "Detected Cloudflare Challenge Page") // c.JSON(500, gin.H{"error": "Detected Cloudflare Challenge Page"}) // return // } // // if common.IsRateLimit(line) { // logger.Errorf(c.Request.Context(), "Cookie has reached the rate Limit") // c.JSON(500, gin.H{"error": "Cookie has reached the rate Limit"}) // return // } // // if strings.HasPrefix(line, "data: ") { // data := strings.TrimPrefix(line, "data: ") // var parsedResponse struct { // Type string `json:"type"` // FieldName string `json:"field_name"` // Content string `json:"content"` // } // if err := json.Unmarshal([]byte(data), &parsedResponse); err != nil { // logger.Warnf(c.Request.Context(), "Failed to unmarshal response: %v", err) // continue // } // if parsedResponse.Type == "message_result" { // content = parsedResponse.Content // break // } // } // } // // if content == "" { // logger.Errorf(c.Request.Context(), firstline) // c.JSON(500, gin.H{"error": "No valid response content"}) // return // } // // promptTokens := common.CountTokenText(string(jsonData), modelName) // completionTokens := common.CountTokenText(content, modelName) // // finishReason := "stop" // // 创建并返回 OpenAIChatCompletionResponse 结构 // resp := model.OpenAIChatCompletionResponse{ // ID: fmt.Sprintf(responseIDFormat, time.Now().Format("20060102150405")), // Object: "chat.completion", // Created: time.Now().Unix(), // Model: modelName, // Choices: []model.OpenAIChoice{ // { // Message: model.OpenAIMessage{ // Role: "assistant", // Content: content, // }, // FinishReason: &finishReason, // }, // }, // Usage: model.OpenAIUsage{ // PromptTokens: promptTokens, // CompletionTokens: completionTokens, // TotalTokens: promptTokens + completionTokens, // }, // } // // c.JSON(200, resp) // } func handleNonStreamRequest(c *gin.Context, client cycletls.CycleTLS, cookie string, cookieManager *config.CookieManager, requestBody map[string]interface{}, modelName string, searchModel bool) { const ( errCloudflareChallengeMsg = "Detected Cloudflare Challenge Page" errCloudflareBlock = "CloudFlare: Sorry, you have been blocked" errServerErrMsg = "An error occurred with the current request, please try again." errServiceUnavailable = "Genspark Service Unavailable" errNoValidResponseContent = "No valid response content" ) ctx := c.Request.Context() maxRetries := len(cookieManager.Cookies) for attempt := 0; attempt < maxRetries; attempt++ { requestBody, err := cheat(requestBody, c, cookie) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } jsonData, err := json.Marshal(requestBody) if err != nil { c.JSON(500, gin.H{"error": "Failed to marshal request body"}) return } response, err := makeRequest(client, jsonData, cookie, false) if err != nil { logger.Errorf(ctx, "makeRequest err: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } scanner := bufio.NewScanner(strings.NewReader(response.Body)) var content string var answerThink string var firstLine string var projectId string isRateLimit := false for scanner.Scan() { line := scanner.Text() if firstLine == "" { firstLine = line } if line == "" { continue } logger.Debug(ctx, strings.TrimSpace(line)) switch { case common.IsCloudflareChallenge(line): logger.Errorf(ctx, errCloudflareChallengeMsg) c.JSON(http.StatusInternalServerError, gin.H{"error": errCloudflareChallengeMsg}) return case common.IsCloudflareBlock(line): logger.Errorf(ctx, errCloudflareBlock) c.JSON(http.StatusInternalServerError, gin.H{"error": errCloudflareBlock}) return case common.IsRateLimit(line): isRateLimit = true 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)) break case common.IsFreeLimit(line): isRateLimit = true 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 //config.RemoveCookie(cookie) break case common.IsNotLogin(line): isRateLimit = true logger.Warnf(ctx, "Cookie Not Login, switching to next cookie, attempt %d/%d, COOKIE:%s", attempt+1, maxRetries, cookie) // 删除cookie config.RemoveCookie(cookie) break case common.IsServiceUnavailablePage(line): logger.Errorf(ctx, errServiceUnavailable) c.JSON(http.StatusInternalServerError, gin.H{"error": errServiceUnavailable}) return case common.IsServerError(line): logger.Errorf(ctx, errServerErrMsg) c.JSON(http.StatusInternalServerError, gin.H{"error": errServerErrMsg}) return case strings.HasPrefix(line, "data: "): data := strings.TrimPrefix(line, "data: ") var parsedResponse struct { Type string `json:"type"` FieldName string `json:"field_name"` Content string `json:"content"` Id string `json:"id"` Delta string `json:"delta"` } if err := json.Unmarshal([]byte(data), &parsedResponse); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if parsedResponse.Type == "project_start" { projectId = parsedResponse.Id } if parsedResponse.Type == "message_field" { // 提取思考过程 if config.ReasoningHide != 1 { if parsedResponse.FieldName == "session_state.answerthink_is_started" { answerThink = "\n" } if parsedResponse.FieldName == "session_state.answerthink_is_finished" { answerThink = answerThink + "\n" } } } if parsedResponse.Type == "message_field_delta" { // 提取思考过程 if config.ReasoningHide != 1 { if parsedResponse.FieldName == "session_state.answerthink" { answerThink = answerThink + parsedResponse.Delta } } } if parsedResponse.Type == "message_result" { // 删除临时会话 go func() { if config.AutoModelChatMapType == 1 { // 保存映射 config.GlobalSessionManager.AddSession(cookie, modelName, projectId) } else { if config.AutoDelChat == 1 { client := cycletls.Init() defer safeClose(client) makeDeleteRequest(client, cookie, projectId) } } }() if modelName == "o1" && searchModel { // 解析内层的 JSON var content Content if err := json.Unmarshal([]byte(parsedResponse.Content), &content); err != nil { logger.Errorf(ctx, "Failed to unmarshal response content: %v err %s", parsedResponse.Content, err.Error()) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to unmarshal response content"}) return } parsedResponse.Content = content.DetailAnswer } content = strings.TrimSpace(answerThink + parsedResponse.Content) break } } } if !isRateLimit { if content == "" { logger.Warnf(ctx, firstLine) //c.JSON(http.StatusInternalServerError, gin.H{"error": errNoValidResponseContent}) } else { promptTokens := common.CountTokenText(string(jsonData), modelName) completionTokens := common.CountTokenText(content, modelName) finishReason := "stop" c.JSON(http.StatusOK, model.OpenAIChatCompletionResponse{ ID: fmt.Sprintf(responseIDFormat, time.Now().Format("20060102150405")), Object: "chat.completion", Created: time.Now().Unix(), Model: modelName, Choices: []model.OpenAIChoice{{ Message: model.OpenAIMessage{ Role: "assistant", Content: content, }, FinishReason: &finishReason, }}, Usage: model.OpenAIUsage{ PromptTokens: promptTokens, CompletionTokens: completionTokens, TotalTokens: promptTokens + completionTokens, }, }) return } } cookie, err = cookieManager.GetNextCookie() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "No more valid cookies available"}) return } // requestBody重制chatId currentQueryString := fmt.Sprintf("type=%s", chatType) if chatId, ok := config.GlobalSessionManager.GetChatID(cookie, modelName); ok { currentQueryString = fmt.Sprintf("id=%s&type=%s", chatId, chatType) } requestBody["current_query_string"] = currentQueryString } logger.Errorf(ctx, "All cookies exhausted after %d attempts", maxRetries) c.JSON(http.StatusInternalServerError, gin.H{"error": "All cookies are temporarily unavailable."}) } func OpenaiModels(c *gin.Context) { var modelsResp []string modelsResp = common.DefaultOpenaiModelList var openaiModelListResponse model.OpenaiModelListResponse var openaiModelResponse []model.OpenaiModelResponse openaiModelListResponse.Object = "list" for _, modelResp := range modelsResp { openaiModelResponse = append(openaiModelResponse, model.OpenaiModelResponse{ ID: modelResp, Object: "model", }) } openaiModelListResponse.Data = openaiModelResponse c.JSON(http.StatusOK, openaiModelListResponse) return } func ImagesForOpenAI(c *gin.Context) { client := cycletls.Init() defer safeClose(client) var openAIReq model.OpenAIImagesGenerationRequest if err := c.BindJSON(&openAIReq); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } // 初始化cookie //cookieManager := config.NewCookieManager() //cookie, err := cookieManager.GetRandomCookie() // //if err != nil { // logger.Errorf(c.Request.Context(), "Failed to get initial cookie: %v", err) // c.JSON(http.StatusInternalServerError, gin.H{"error": errNoValidCookies}) // return //} resp, err := ImageProcess(c, client, openAIReq) if err != nil { logger.Errorf(c.Request.Context(), fmt.Sprintf("ImageProcess 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 ImageProcess(c *gin.Context, client cycletls.CycleTLS, openAIReq model.OpenAIImagesGenerationRequest) (*model.OpenAIImagesGenerationResponse, error) { const ( errNoValidCookies = "No valid cookies available" errRateLimitMsg = "Rate limit reached, please try again later" errServerErrMsg = "An error occurred with the current request, please try again" errNoValidTaskIDs = "No valid task IDs received" ) var ( sessionImageChatManager *config.SessionMapManager maxRetries int cookie string chatId string ) cookieManager := config.NewCookieManager() sessionImageChatManager = config.NewSessionMapManager() 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) } } else { maxRetries = sessionImageChatManager.GetSize() cookie, chatId, _ = sessionImageChatManager.GetRandomKeyValue() } for attempt := 0; attempt < maxRetries; attempt++ { // Create request body requestBody, err := createImageRequestBody(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 := makeImageRequest(client, jsonData, cookie) if err != nil { logger.Errorf(ctx, "Failed to make image request: %v", err) return nil, err } body := response.Body // Handle different response cases switch { case common.IsRateLimit(body): logger.Warnf(ctx, "Cookie rate limited, switching to next cookie, attempt %d/%d, COOKIE:%s", attempt+1, maxRetries, cookie) //if sessionImageChatManager != nil { // cookie, chatId, err = sessionImageChatManager.GetNextKeyValue() // 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) // } //} else { //cookieManager := config.NewCookieManager() 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) //if sessionImageChatManager != nil { // cookie, chatId, err = sessionImageChatManager.GetNextKeyValue() // 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) // } //} else { //cookieManager := config.NewCookieManager() config.AddRateLimitCookie(cookie, time.Now().Add(24*60*60*time.Second)) // 删除cookie //config.RemoveCookie(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.IsNotLogin(body): logger.Warnf(ctx, "Cookie Not Login, switching to next cookie, attempt %d/%d, COOKIE:%s", attempt+1, maxRetries, cookie) //if sessionImageChatManager != nil { // //sessionImageChatManager.RemoveKey(cookie) // cookie, chatId, err = sessionImageChatManager.GetNextKeyValue() // 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) // } //} else { //cookieManager := config.NewCookieManager() //err := cookieManager.RemoveCookie(cookie) //if err != nil { // logger.Errorf(ctx, "Failed to remove cookie: %v", err) //} 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", "官方服务超载或环境变量 SESSION_IMAGE_CHAT_MAP 未配置")) logger.Errorf(ctx, fmt.Sprintf("Server overloaded, please try again later.%s", "官方服务超载")) return nil, fmt.Errorf("Server overloaded, please try again later.") } // Extract task IDs projectId, taskIDs := extractTaskIDs(response.Body) if len(taskIDs) == 0 { logger.Errorf(ctx, "Response body: %s", response.Body) return nil, fmt.Errorf(errNoValidTaskIDs) } // Poll for image URLs imageURLs := pollTaskStatus(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.OpenAIImagesGenerationResponse{ Created: time.Now().Unix(), Data: make([]*model.OpenAIImagesGenerationDataResponse, 0, len(imageURLs)), } // Process image URLs for _, url := range imageURLs { data := &model.OpenAIImagesGenerationDataResponse{ 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 extractTaskIDs(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 { GeneratedImages []struct { TaskID string `json:"task_id"` } `json:"generated_images"` } if err := json.Unmarshal([]byte(outerJSON.Content), &innerJSON); err != nil { continue } // 提取所有task_id for _, img := range innerJSON.GeneratedImages { if img.TaskID != "" { taskIDs = append(taskIDs, img.TaskID) } } } } return projectId, taskIDs } func pollTaskStatus(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/ig_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["image_urls"].([]interface{}); ok && len(urls) > 0 { if imageURL, ok := urls[0].(string); ok { imageURLs = append(imageURLs, imageURL) } } } } } } } } return imageURLs } func getBase64ByUrl(url string) (string, error) { resp, err := http.Get(url) if err != nil { return "", fmt.Errorf("failed to fetch image: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("received non-200 status code: %d", resp.StatusCode) } imgData, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("failed to read image data: %w", err) } // Encode the image data to Base64 base64Str := base64.StdEncoding.EncodeToString(imgData) return base64Str, nil } func safeClose(client cycletls.CycleTLS) { if client.ReqChan != nil { close(client.ReqChan) } if client.RespChan != nil { close(client.RespChan) } }