GA / chat /api.go
Niansuh's picture
Upload 19 files
7d06702 verified
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
}
}
}