diff --git a/conversion/requests/chatgpt/convert.go b/conversion/requests/chatgpt/convert.go index 3b9cb18..8bba6bd 100644 --- a/conversion/requests/chatgpt/convert.go +++ b/conversion/requests/chatgpt/convert.go @@ -25,7 +25,7 @@ func ConvertAPIRequest(api_request official_types.APIRequest, account string, se chatgpt_request.ConversationMode.Kind = "gizmo_interaction" chatgpt_request.ConversationMode.GizmoId = "g-" + matches[1] } - ifMultimodel := strings.HasPrefix(api_request.Model, "gpt-4") + ifMultimodel := secret.Token != "" for _, api_message := range api_request.Messages { if api_message.Role == "system" { api_message.Role = "critic" @@ -34,3 +34,10 @@ func ConvertAPIRequest(api_request official_types.APIRequest, account string, se } return chatgpt_request } + +func ConvertTTSAPIRequest(input string) chatgpt_types.ChatGPTRequest { + chatgpt_request := chatgpt_types.NewChatGPTRequest() + chatgpt_request.HistoryAndTrainingDisabled = false + chatgpt_request.AddAssistantMessage(input) + return chatgpt_request +} diff --git a/handlers.go b/handlers.go index 0660da7..a250d12 100644 --- a/handlers.go +++ b/handlers.go @@ -6,7 +6,6 @@ import ( "freechatgpt/internal/tokens" official_types "freechatgpt/typings/official" "os" - "strings" "github.com/gin-gonic/gin" "github.com/google/uuid" @@ -93,15 +92,7 @@ func nightmare(c *gin.Context) { return } - authHeader := c.GetHeader("Authorization") account, secret := getSecret() - if authHeader != "" { - customAccessToken := strings.Replace(authHeader, "Bearer ", "", 1) - // Check if customAccessToken starts with sk- - if strings.HasPrefix(customAccessToken, "eyJhbGciOiJSUzI1NiI") { - secret.Token = customAccessToken - } - } var proxy_url string if len(proxies) == 0 { proxy_url = "" @@ -195,3 +186,102 @@ func nightmare(c *gin.Context) { } // chatgpt.UnlockSpecConn(secret.Token+secret.TeamUserID, uid) } + +var ttsFmtMap = map[string]string{ + "mp3": "mp3", + "opus": "opus", + "aac": "aac", + "flac": "aac", + "wav": "aac", + "pcm": "aac", +} + +var ttsTypeMap = map[string]string{ + "mp3": "audio/mpeg", + "opus": "audio/ogg", + "aac": "audio/aac", +} + +var ttsVoiceMap = map[string]string{ + "alloy": "cove", + "echo": "ember", + "fable": "breeze", + "onyx": "cove", + "nova": "juniper", + "shimmer": "juniper", +} + +func tts(c *gin.Context) { + var original_request official_types.TTSAPIRequest + err := c.BindJSON(&original_request) + if err != nil { + c.JSON(400, gin.H{"error": gin.H{ + "message": "Request must be proper JSON", + "type": "invalid_request_error", + "param": nil, + "code": err.Error(), + }}) + return + } + + account, secret := getSecret() + if account == "" || secret.PUID == "" { + c.JSON(500, gin.H{"error": "Plus user only"}) + return + } + var proxy_url string + if len(proxies) == 0 { + proxy_url = "" + } else { + proxy_url = proxies[0] + // Push used proxy to the back of the list + proxies = append(proxies[1:], proxies[0]) + } + var deviceId = generateUUID(account) + chatgpt.SetOAICookie(deviceId) + chat_require := chatgpt.CheckRequire(&secret, deviceId, proxy_url) + if chat_require == nil { + c.JSON(500, gin.H{"error": "unable to check chat requirement"}) + return + } + var proofToken string + if chat_require.Proof.Required { + proofToken = chatgpt.CalcProofToken(chat_require, proxy_url) + } + var arkoseToken string + if chat_require.Arkose.Required { + arkoseToken, err = arkose.GetOpenAIToken(4, secret.PUID, chat_require.Arkose.DX, proxy_url) + if err != nil { + println("Error getting Arkose token: ", err) + } + } + // Convert the chat request to a ChatGPT request + translated_request := chatgpt_request_converter.ConvertTTSAPIRequest(original_request.Input) + + response, err := chatgpt.POSTconversation(translated_request, &secret, deviceId, chat_require.Token, arkoseToken, proofToken, proxy_url) + if err != nil { + c.JSON(500, gin.H{ + "error": "error sending request", + }) + return + } + defer response.Body.Close() + if chatgpt.Handle_request_error(c, response) { + return + } + msgId, convId := chatgpt.HandlerTTS(response, original_request.Input) + format := ttsFmtMap[original_request.Format] + if format == "" { + format = "aac" + } + voice := ttsVoiceMap[original_request.Voice] + if voice == "" { + voice = "cove" + } + apiUrl := "https://chatgpt.com/backend-api/synthesize?message_id=" + msgId + "&conversation_id=" + convId + "&voice=" + voice + "&format=" + format + data := chatgpt.GetTTS(&secret, deviceId, apiUrl, proxy_url) + if data != nil { + c.Data(200, ttsTypeMap[format], data) + } + chatgpt.RemoveConversation(&secret, deviceId, convId, proxy_url) +} diff --git a/internal/chatgpt/request.go b/internal/chatgpt/request.go index bdde942..c942fdc 100644 --- a/internal/chatgpt/request.go +++ b/internal/chatgpt/request.go @@ -777,3 +777,91 @@ func Handler(c *gin.Context, response *http.Response, secret *tokens.Secret, pro ParentID: original_response.Message.ID, } } + +func HandlerTTS(response *http.Response, input string) (string, string) { + // Create a bufio.Reader from the response body + reader := bufio.NewReader(response.Body) + + var original_response chatgpt_types.ChatGPTResponse + var convId string + + for { + var line string + var err error + line, err = reader.ReadString('\n') + if err != nil { + if err == io.EOF { + break + } + return "", "" + } + if len(line) < 6 { + continue + } + // Remove "data: " from the beginning of the line + line = line[6:] + // Check if line starts with [DONE] + if !strings.HasPrefix(line, "[DONE]") { + // Parse the line as JSON + original_response.Message.ID = "" + err = json.Unmarshal([]byte(line), &original_response) + if err != nil { + continue + } + if original_response.Error != nil { + return "", "" + } + if original_response.Message.ID == "" { + continue + } + if original_response.ConversationID != convId { + if convId == "" { + convId = original_response.ConversationID + } else { + continue + } + } + if original_response.Message.Author.Role == "assistant" && original_response.Message.Content.Parts[0].(string) == input { + return original_response.Message.ID, convId + } + } + } + return "", "" +} + +func GetTTS(secret *tokens.Secret, deviceId string, url string, proxy string) []byte { + if proxy != "" { + client.SetProxy(proxy) + } + request, err := newRequest(http.MethodGet, url, nil, secret, deviceId) + if err != nil { + return nil + } + response, err := client.Do(request) + if err != nil { + return nil + } + defer response.Body.Close() + blob, err := io.ReadAll(response.Body) + if err != nil { + return nil + } + return blob +} + +func RemoveConversation(secret *tokens.Secret, deviceId string, id string, proxy string) { + if proxy != "" { + client.SetProxy(proxy) + } + url := "https://chatgpt.com/backend-api/conversation/" + id + request, err := newRequest(http.MethodPatch, url, bytes.NewBuffer([]byte(`{"is_visible":false}`)), secret, deviceId) + request.Header.Set("Content-Type", "application/json") + if err != nil { + return + } + response, err := client.Do(request) + if err != nil { + return + } + response.Body.Close() +} diff --git a/main.go b/main.go index 239f983..3648d5b 100644 --- a/main.go +++ b/main.go @@ -82,6 +82,9 @@ func main() { /// Public routes router.OPTIONS("/v1/chat/completions", optionsHandler) router.POST("/v1/chat/completions", Authorization, nightmare) + router.OPTIONS("/v1/audio/speech", optionsHandler) + router.POST("/v1/audio/speech", Authorization, tts) + router.OPTIONS("/v1/models", optionsHandler) router.GET("/v1/models", Authorization, simulateModel) endless.ListenAndServe(HOST+":"+PORT, router) } diff --git a/typings/chatgpt/request.go b/typings/chatgpt/request.go index f57f5ec..b3afbc8 100644 --- a/typings/chatgpt/request.go +++ b/typings/chatgpt/request.go @@ -471,3 +471,13 @@ func (c *ChatGPTRequest) AddMessage(role string, content interface{}, multimodal } c.Messages = append(c.Messages, msg) } + +func (c *ChatGPTRequest) AddAssistantMessage(input string) { + var msg = chatgpt_message{ + ID: uuid.New(), + Author: chatgpt_author{Role: "assistant"}, + Content: chatgpt_content{ContentType: "text", Parts: []interface{}{input}}, + Metadata: nil, + } + c.Messages = append(c.Messages, msg) +} diff --git a/typings/official/request.go b/typings/official/request.go index f17ab20..0658e5f 100644 --- a/typings/official/request.go +++ b/typings/official/request.go @@ -10,3 +10,9 @@ type api_message struct { Role string `json:"role"` Content interface{} `json:"content"` } + +type TTSAPIRequest struct { + Input string `json:"input"` + Voice string `json:"voice"` + Format string `json:"response_format"` +}