diff --git a/translate/translate.go b/translate/translate.go index 311a358..513e713 100644 --- a/translate/translate.go +++ b/translate/translate.go @@ -2,7 +2,7 @@ * @Author: Vincent Young * @Date: 2024-09-16 11:59:24 * @LastEditors: Vincent Yang - * @LastEditTime: 2025-01-20 17:09:59 + * @LastEditTime: 2025-03-01 04:23:49 * @FilePath: /DeepLX/translate/translate.go * @Telegram: https://t.me/missuo * @GitHub: https://github.com/missuo @@ -14,6 +14,8 @@ package translate import ( "bytes" + "compress/flate" + "compress/gzip" "fmt" "io" "net/http" @@ -27,13 +29,9 @@ import ( "github.com/tidwall/gjson" ) -const baseURL = "https://www2.deepl.com/jsonrpc" - // makeRequest makes an HTTP request to DeepL API - -func makeRequest(postData *PostData, urlMethod string, proxyURL string, dlSession string) (gjson.Result, error) { - urlFull := fmt.Sprintf("%s?client=chrome-extension,1.28.0&method=%s", baseURL, urlMethod) - +func makeRequest(postData *PostData, proxyURL string, dlSession string) (gjson.Result, error) { + urlFull := "https://www2.deepl.com/jsonrpc" postStr := formatPostString(postData) // Create a new req client @@ -41,21 +39,18 @@ func makeRequest(postData *PostData, urlMethod string, proxyURL string, dlSessio // Set headers headers := http.Header{ - "Accept": []string{"*/*"}, - "Accept-Language": []string{"en-US,en;q=0.9,zh-CN;q=0.8,zh-TW;q=0.7,zh-HK;q=0.6,zh;q=0.5"}, - "Authorization": []string{"None"}, - "Cache-Control": []string{"no-cache"}, - "Content-Type": []string{"application/json"}, - "DNT": []string{"1"}, - "Origin": []string{"chrome-extension://cofdbpoegempjloogbagkncekinflcnj"}, - "Pragma": []string{"no-cache"}, - "Priority": []string{"u=1, i"}, - "Referer": []string{"https://www.deepl.com/"}, - "Sec-Fetch-Dest": []string{"empty"}, - "Sec-Fetch-Mode": []string{"cors"}, - "Sec-Fetch-Site": []string{"none"}, - "Sec-GPC": []string{"1"}, - "User-Agent": []string{"DeepLBrowserExtension/1.28.0 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36"}, + "Content-Type": []string{"application/json"}, + "User-Agent": []string{"DeepL/1627620 CFNetwork/3826.500.62.2.1 Darwin/24.4.0"}, + "Accept": []string{"*/*"}, + "X-App-Os-Name": []string{"iOS"}, + "X-App-Os-Version": []string{"18.4.0"}, + "Accept-Language": []string{"en-US,en;q=0.9"}, + "Accept-Encoding": []string{"gzip, deflate, br"}, // Keep this! + "X-App-Device": []string{"iPhone16,2"}, + "Referer": []string{"https://www.deepl.com/"}, + "X-Product": []string{"translator"}, + "X-App-Build": []string{"1627620"}, + "X-App-Version": []string{"25.1"}, } if dlSession != "" { @@ -83,40 +78,28 @@ func makeRequest(postData *PostData, urlMethod string, proxyURL string, dlSessio } var bodyReader io.Reader - if resp.Header.Get("Content-Encoding") == "br" { + contentEncoding := resp.Header.Get("Content-Encoding") + switch contentEncoding { + case "br": bodyReader = brotli.NewReader(resp.Body) - } else { + case "gzip": + bodyReader, err = gzip.NewReader(resp.Body) // Use gzip.NewReader + if err != nil { + return gjson.Result{}, fmt.Errorf("failed to create gzip reader: %w", err) + } + case "deflate": // Less common, but good to handle + bodyReader = flate.NewReader(resp.Body) + default: bodyReader = resp.Body } body, err := io.ReadAll(bodyReader) if err != nil { - return gjson.Result{}, err + return gjson.Result{}, fmt.Errorf("failed to read response body: %w", err) } return gjson.ParseBytes(body), nil } -// splitText splits the input text for translation -func splitText(text string, tagHandling bool, proxyURL string, dlSession string) (gjson.Result, error) { - postData := &PostData{ - Jsonrpc: "2.0", - Method: "LMT_split_text", - ID: getRandomNumber(), - Params: Params{ - CommonJobParams: CommonJobParams{ - Mode: "translate", - }, - Lang: Lang{ - LangUserSelected: "auto", - }, - Texts: []string{text}, - TextType: map[bool]string{true: "richtext", false: "plaintext"}[tagHandling || isRichText(text)], - }, - } - - return makeRequest(postData, "LMT_split_text", proxyURL, dlSession) -} - // TranslateByDeepLX performs translation using DeepL API func TranslateByDeepLX(sourceLang, targetLang, text string, tagHandling string, proxyURL string, dlSession string) (DeepLXTranslationResult, error) { if text == "" { @@ -126,6 +109,10 @@ func TranslateByDeepLX(sourceLang, targetLang, text string, tagHandling string, }, nil } + if tagHandling == "" { + tagHandling = "plaintext" + } + // Split text by newlines and store them for later reconstruction textParts := strings.Split(text, "\n") var translatedParts []string @@ -138,15 +125,6 @@ func TranslateByDeepLX(sourceLang, targetLang, text string, tagHandling string, continue } - // Split text first - splitResult, err := splitText(part, tagHandling == "html" || tagHandling == "xml", proxyURL, dlSession) - if err != nil { - return DeepLXTranslationResult{ - Code: http.StatusServiceUnavailable, - Message: err.Error(), - }, nil - } - // Get detected language if source language is auto if sourceLang == "auto" || sourceLang == "" { sourceLang = strings.ToUpper(whatlanggo.DetectLang(part).Iso6391()) @@ -154,32 +132,18 @@ func TranslateByDeepLX(sourceLang, targetLang, text string, tagHandling string, // Prepare jobs from split result var jobs []Job - chunks := splitResult.Get("result.texts.0.chunks").Array() - for idx, chunk := range chunks { - sentence := chunk.Get("sentences.0") - // Handle context - contextBefore := []string{} - contextAfter := []string{} - if idx > 0 { - contextBefore = []string{chunks[idx-1].Get("sentences.0.text").String()} - } - if idx < len(chunks)-1 { - contextAfter = []string{chunks[idx+1].Get("sentences.0.text").String()} - } - - jobs = append(jobs, Job{ - Kind: "default", - PreferredNumBeams: 4, - RawEnContextBefore: contextBefore, - RawEnContextAfter: contextAfter, - Sentences: []Sentence{{ - Prefix: sentence.Get("prefix").String(), - Text: sentence.Get("text").String(), - ID: idx + 1, - }}, - }) - } + jobs = append(jobs, Job{ + Kind: "default", + PreferredNumBeams: 4, + RawEnContextBefore: []string{}, + RawEnContextAfter: []string{}, + Sentences: []Sentence{{ + Prefix: "", + Text: text, + ID: 0, + }}, + }) hasRegionalVariant := false targetLangCode := targetLang @@ -198,14 +162,19 @@ func TranslateByDeepLX(sourceLang, targetLang, text string, tagHandling string, ID: id, Params: Params{ CommonJobParams: CommonJobParams{ - Mode: "translate", + Mode: "translate", + Formality: "undefined", + TranscribeAs: "romanize", + AdvancedMode: false, + TextType: tagHandling, + WasSpoken: false, }, Lang: Lang{ - SourceLangComputed: strings.ToUpper(sourceLang), - TargetLang: strings.ToUpper(targetLangCode), + SourceLangUserSelected: "auto", + TargetLang: strings.ToUpper(targetLangCode), + SourceLangComputed: strings.ToUpper(sourceLang), }, Jobs: jobs, - Priority: 1, Timestamp: getTimeStamp(getICount(part)), }, } @@ -218,21 +187,26 @@ func TranslateByDeepLX(sourceLang, targetLang, text string, tagHandling string, Params: Params{ CommonJobParams: CommonJobParams{ Mode: "translate", - RegionalVariant: map[bool]string{true: targetLang, false: ""}[hasRegionalVariant], + Formality: "undefined", + TranscribeAs: "romanize", + AdvancedMode: false, + TextType: tagHandling, + WasSpoken: false, + RegionalVariant: targetLang, }, Lang: Lang{ - SourceLangComputed: strings.ToUpper(sourceLang), - TargetLang: strings.ToUpper(targetLangCode), + SourceLangUserSelected: "auto", + TargetLang: strings.ToUpper(targetLangCode), + SourceLangComputed: strings.ToUpper(sourceLang), }, Jobs: jobs, - Priority: 1, Timestamp: getTimeStamp(getICount(part)), }, } } // Make translation request - result, err := makeRequest(postData, "LMT_handle_jobs", proxyURL, dlSession) + result, err := makeRequest(postData, proxyURL, dlSession) if err != nil { return DeepLXTranslationResult{ Code: http.StatusServiceUnavailable, diff --git a/translate/types.go b/translate/types.go index c725f8c..f370d2a 100644 --- a/translate/types.go +++ b/translate/types.go @@ -2,7 +2,7 @@ * @Author: Vincent Young * @Date: 2024-09-16 11:59:24 * @LastEditors: Vincent Yang - * @LastEditTime: 2024-11-01 23:18:56 + * @LastEditTime: 2025-03-01 04:16:07 * @FilePath: /DeepLX/translate/types.go * @Telegram: https://t.me/missuo * @GitHub: https://github.com/missuo @@ -14,14 +14,19 @@ package translate // Lang represents the language settings for translation type Lang struct { - SourceLangComputed string `json:"source_lang_computed,omitempty"` - TargetLang string `json:"target_lang"` - LangUserSelected string `json:"lang_user_selected,omitempty"` + SourceLangUserSelected string `json:"source_lang_user_selected"` // Can be "auto" + TargetLang string `json:"target_lang"` + SourceLangComputed string `json:"source_lang_computed,omitempty"` } // CommonJobParams represents common parameters for translation jobs type CommonJobParams struct { + Formality string `json:"formality"` // Can be "undefined" + TranscribeAs string `json:"transcribe_as"` Mode string `json:"mode"` + WasSpoken bool `json:"wasSpoken"` + AdvancedMode bool `json:"advancedMode"` + TextType string `json:"textType"` RegionalVariant string `json:"regionalVariant,omitempty"` } @@ -45,10 +50,7 @@ type Job struct { type Params struct { CommonJobParams CommonJobParams `json:"commonJobParams"` Lang Lang `json:"lang"` - Texts []string `json:"texts,omitempty"` - TextType string `json:"textType,omitempty"` - Jobs []Job `json:"jobs,omitempty"` - Priority int `json:"priority,omitempty"` + Jobs []Job `json:"jobs"` Timestamp int64 `json:"timestamp"` } @@ -60,25 +62,6 @@ type PostData struct { Params Params `json:"params"` } -// SplitTextResponse represents the response from text splitting -type SplitTextResponse struct { - Jsonrpc string `json:"jsonrpc"` - ID int64 `json:"id"` - Result struct { - Lang struct { - Detected string `json:"detected"` - } `json:"lang"` - Texts []struct { - Chunks []struct { - Sentences []struct { - Prefix string `json:"prefix"` - Text string `json:"text"` - } `json:"sentences"` - } `json:"chunks"` - } `json:"texts"` - } `json:"result"` -} - // TranslationResponse represents the response from translation type TranslationResponse struct { Jsonrpc string `json:"jsonrpc"` @@ -86,23 +69,34 @@ type TranslationResponse struct { Result struct { Translations []struct { Beams []struct { - Sentences []struct { - Text string `json:"text"` - } `json:"sentences"` + Sentences []SentenceResponse `json:"sentences"` + NumSymbols int `json:"num_symbols"` + RephraseVariant struct { // Added rephrase_variant + Name string `json:"name"` + } `json:"rephrase_variant"` } `json:"beams"` + Quality string `json:"quality"` // Added quality } `json:"translations"` - SourceLang string `json:"source_lang"` - TargetLang string `json:"target_lang"` + TargetLang string `json:"target_lang"` + SourceLang string `json:"source_lang"` + SourceLangIsConfident bool `json:"source_lang_is_confident"` + DetectedLanguages map[string]interface{} `json:"detectedLanguages"` // Use interface{} for now } `json:"result"` } +// SentenceResponse is a helper struct for the response sentences +type SentenceResponse struct { + Text string `json:"text"` + IDS []int `json:"ids"` // Added IDS +} + // DeepLXTranslationResult represents the final translation result type DeepLXTranslationResult struct { Code int `json:"code"` ID int64 `json:"id"` Message string `json:"message,omitempty"` - Data string `json:"data"` - Alternatives []string `json:"alternatives"` + Data string `json:"data"` // The primary translated text + Alternatives []string `json:"alternatives"` // Other possible translations SourceLang string `json:"source_lang"` TargetLang string `json:"target_lang"` Method string `json:"method"`