package chat import ( "encoding/json" "errors" "fmt" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/launchdarkly/eventsource" "github.com/zbronya/free-chat-to-api/config" "github.com/zbronya/free-chat-to-api/httpclient" "github.com/zbronya/free-chat-to-api/logger" "github.com/zbronya/free-chat-to-api/model" "github.com/zbronya/free-chat-to-api/model/request" "github.com/zbronya/free-chat-to-api/model/response" "github.com/zbronya/free-chat-to-api/proofofwork" "github.com/zbronya/free-chat-to-api/utils" "io" "net/http" "strings" "time" ) func Completions(c *gin.Context) { req := &request.ChatRequest{} err := c.BindJSON(req) if err != nil { utils.ErrorResp(c, http.StatusBadRequest, "Invalid parameter", nil) return } ua := "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" client := httpclient.NewReqClient() pConfig := proofofwork.GetConfig(ua) chatRequirementReq := proofofwork.GetChatRequirementReq(pConfig) deviceId := uuid.NewString() requirement, err := getChatRequirement(c, client, chatRequirementReq, ua, deviceId) if err != nil { return } if requirement.ForceLogin { utils.ErrorResp(c, http.StatusForbidden, "force login", nil) return } token := "gAAAAAB" + proofofwork.CalcProofToken(pConfig, requirement.Proof.Seed, requirement.Proof.Difficulty) doConversation(c, client, req, requirement, ua, deviceId, token) } func doConversation(c *gin.Context, client *httpclient.ReqClient, req *request.ChatRequest, requirement *model.ChatRequirementRes, ua string, deviceId string, token string) { completionReq := model.ApiReqToChatReq(req) url := config.GatewayUrl + "/backend-anon/conversation" header := map[string]string{ "Accept": "text/event-stream", "Accept-Encoding": "gzip, deflate, br", "Accept-Language": "en-US,en;q=0.9", "Content-Type": "application/json", "Oai-Device-Id": deviceId, "Oai-Language": "en-US", "Openai-Sentinel-Chat-Requirements-Token": requirement.Token, "Openai-Sentinel-Proof-Token": token, "Origin": "https://chatgpt.com", "Priority": "u=1, i", "Referer": "https://chatgpt.com/?oai-dm=1", "User-Agent": ua, "Cookie": "__Secure-next-auth.callback-url=https%3A%2F%2Fchatgpt.com;", } j, _ := json.Marshal(completionReq) resp, err := client.Post(url, header, j) if err != nil { utils.ErrorResp(c, http.StatusInternalServerError, "fail to completions", nil) return } if resp.StatusCode != 200 { body, _ := io.ReadAll(resp.Body) utils.ErrorResp(c, resp.StatusCode, string(body), nil) return } if req.Stream { conversationStream(c, req, resp) } else { conversation(c, req, resp) } } func getChatRequirement(c *gin.Context, client httpclient.HttpClient, req model.ChatRequirementReq, ua string, deviceId string) (*model.ChatRequirementRes, error) { url := config.GatewayUrl + "/backend-anon/sentinel/chat-requirements" header := map[string]string{ "Accept": "*/*", "Accept-Language": "en-US,en;q=0.9", "Content-Type": "application/json", "Oai-Device-Id": deviceId, "Oai-Language": "en-US", "Origin": "https://chatgpt.com", "Referer": "https://chatgpt.com/?oai-dm=1", "User-Agent": ua, } j, _ := json.Marshal(req) resp, err := client.Post(url, header, j) if err != nil { utils.ErrorResp(c, http.StatusInternalServerError, "fail to get chat requirements", nil) return nil, err } if resp.StatusCode != 200 { body, _ := io.ReadAll(resp.Body) utils.ErrorResp(c, resp.StatusCode, string(body), nil) return nil, errors.New("fail to get chat requirements") } defer resp.Body.Close() var chatRequirementRes model.ChatRequirementRes err = json.NewDecoder(resp.Body).Decode(&chatRequirementRes) return &chatRequirementRes, err } func conversationStream(c *gin.Context, req *request.ChatRequest, resp *http.Response) { messageTemp := "" decoder := eventsource.NewDecoder(resp.Body) defer func(decoder *eventsource.Decoder) { _, _ = decoder.Decode() }(decoder) id := utils.GenerateID(29) handlingSigns := false for { event, err := decoder.Decode() if err != nil { logger.GetLogger().Error(err.Error()) utils.ErrorResp(c, http.StatusInternalServerError, "", nil) break } name := event.Event() data := event.Data() if data == "" { continue } if data == "[DONE]" { result := &response.Stream{} result.ID = id result.Created = time.Now().Unix() result.Object = "chat.completion.chunk" delta := response.StreamDelta{ Content: "", } choices := response.StreamChoice{ Delta: delta, FinishReason: "stop", } result.Choices = append(result.Choices, choices) result.Model = req.Model bytes, err := json.Marshal(result) if err != nil { logger.GetLogger().Error(err.Error()) continue } c.SSEvent(name, fmt.Sprint(" ", string(bytes))) c.SSEvent(name, " [DONE]") break } chatResp := &model.ChatCompletionResp{} err = json.Unmarshal([]byte(data), chatResp) if chatResp.Error != nil && !handlingSigns { logger.GetLogger().Error(fmt.Sprint(chatResp.Error)) utils.ErrorResp(c, http.StatusInternalServerError, "", nil) return } if err != nil { continue } if chatResp.Message.Author.Role == "assistant" && (chatResp.Message.Status == "in_progress" || handlingSigns) { handlingSigns = true parts := chatResp.Message.Content.Parts[0] content := strings.Replace(parts, messageTemp, "", 1) messageTemp = parts if content == "" { continue } apiResp := &response.Stream{} apiResp.ID = id apiResp.Created = time.Now().Unix() apiResp.Object = "chat.completion.chunk" delta := response.StreamDelta{ Content: content, } choices := response.StreamChoice{ Delta: delta, } apiResp.Choices = append(apiResp.Choices, choices) apiResp.Model = req.Model bytes, err := json.Marshal(apiResp) if err != nil { logger.GetLogger().Error(err.Error()) continue } c.SSEvent(name, fmt.Sprint(" ", string(bytes))) continue } } } func conversation(c *gin.Context, req *request.ChatRequest, resp *http.Response) { content := "" decoder := eventsource.NewDecoder(resp.Body) defer func(decoder *eventsource.Decoder) { _, _ = decoder.Decode() }(decoder) handlingSigns := false for { event, err := decoder.Decode() if err != nil { logger.GetLogger().Error(err.Error()) utils.ErrorResp(c, http.StatusInternalServerError, "", nil) break } data := event.Data() if data == "" { continue } if data == "[DONE]" { result := &response.ChatResponse{} result.ID = utils.GenerateID(29) result.Created = time.Now().Unix() result.Object = "chat.completion" result.Model = req.Model usage := response.Usage{ PromptTokens: 0, CompletionTokens: 0, TotalTokens: 0, } result.Usage = usage message := response.Message{ Role: "assistant", Content: content, } choice := response.Choice{ Message: message, FinishReason: "stop", Index: 0, } result.Choices = append(result.Choices, choice) c.JSON(http.StatusOK, result) break } chatResp := &model.ChatCompletionResp{} err = json.Unmarshal([]byte(data), chatResp) if chatResp.Error != nil && !handlingSigns { logger.GetLogger().Error(fmt.Sprint(chatResp.Error)) utils.ErrorResp(c, http.StatusInternalServerError, "", nil) return } if err != nil { continue } if chatResp.Message.Author.Role == "assistant" && (chatResp.Message.Status == "in_progress" || handlingSigns) { handlingSigns = true if !strings.Contains(chatResp.Message.Content.Parts[0], content) { continue } content = chatResp.Message.Content.Parts[0] if content == "" { continue } continue } } }