From 62a993bb13229ced1521d69aad31bba47e957a08 Mon Sep 17 00:00:00 2001 From: Vincent Yang Date: Fri, 1 Nov 2024 00:43:57 -0400 Subject: [PATCH] fix: unable to translate --- main.go | 128 ++++++------ translate/translate.go | 448 +++++++++++++++-------------------------- translate/types.go | 103 +++++++--- translate/utils.go | 28 ++- 4 files changed, 319 insertions(+), 388 deletions(-) diff --git a/main.go b/main.go index 5b1b28d0..00d54b1e 100644 --- a/main.go +++ b/main.go @@ -1,8 +1,8 @@ /* * @Author: Vincent Yang * @Date: 2023-07-01 21:45:34 - * @LastEditors: Vincent Young - * @LastEditTime: 2024-09-16 12:12:35 + * @LastEditors: Vincent Yang + * @LastEditTime: 2024-11-01 00:42:58 * @FilePath: /DeepLX/main.go * @Telegram: https://t.me/missuo * @GitHub: https://github.com/missuo @@ -156,68 +156,68 @@ func main() { }) // Pro API endpoint, Pro Account required - r.POST("/v1/translate", authMiddleware(cfg), func(c *gin.Context) { - req := PayloadFree{} - c.BindJSON(&req) - - sourceLang := req.SourceLang - targetLang := req.TargetLang - translateText := req.TransText - tagHandling := req.TagHandling - proxyURL := cfg.Proxy - - dlSession := cfg.DlSession - - if tagHandling != "" && tagHandling != "html" && tagHandling != "xml" { - c.JSON(http.StatusBadRequest, gin.H{ - "code": http.StatusBadRequest, - "message": "Invalid tag_handling value. Allowed values are 'html' and 'xml'.", - }) - return - } - - cookie := c.GetHeader("Cookie") - if cookie != "" { - dlSession = strings.Replace(cookie, "dl_session=", "", -1) - } - - if dlSession == "" { - c.JSON(http.StatusUnauthorized, gin.H{ - "code": http.StatusUnauthorized, - "message": "No dl_session Found", - }) - return - } else if strings.Contains(dlSession, ".") { - c.JSON(http.StatusUnauthorized, gin.H{ - "code": http.StatusUnauthorized, - "message": "Your account is not a Pro account. Please upgrade your account or switch to a different account.", - }) - return - } - - result, err := translate.TranslateByDeepLXPro(sourceLang, targetLang, translateText, tagHandling, dlSession, proxyURL) - if err != nil { - log.Fatalf("Translation failed: %s", err) - } - - if result.Code == http.StatusOK { - c.JSON(http.StatusOK, gin.H{ - "code": http.StatusOK, - "id": result.ID, - "data": result.Data, - "alternatives": result.Alternatives, - "source_lang": result.SourceLang, - "target_lang": result.TargetLang, - "method": result.Method, - }) - } else { - c.JSON(result.Code, gin.H{ - "code": result.Code, - "message": result.Message, - }) - - } - }) + // r.POST("/v1/translate", authMiddleware(cfg), func(c *gin.Context) { + // req := PayloadFree{} + // c.BindJSON(&req) + + // sourceLang := req.SourceLang + // targetLang := req.TargetLang + // translateText := req.TransText + // tagHandling := req.TagHandling + // proxyURL := cfg.Proxy + + // dlSession := cfg.DlSession + + // if tagHandling != "" && tagHandling != "html" && tagHandling != "xml" { + // c.JSON(http.StatusBadRequest, gin.H{ + // "code": http.StatusBadRequest, + // "message": "Invalid tag_handling value. Allowed values are 'html' and 'xml'.", + // }) + // return + // } + + // cookie := c.GetHeader("Cookie") + // if cookie != "" { + // dlSession = strings.Replace(cookie, "dl_session=", "", -1) + // } + + // if dlSession == "" { + // c.JSON(http.StatusUnauthorized, gin.H{ + // "code": http.StatusUnauthorized, + // "message": "No dl_session Found", + // }) + // return + // } else if strings.Contains(dlSession, ".") { + // c.JSON(http.StatusUnauthorized, gin.H{ + // "code": http.StatusUnauthorized, + // "message": "Your account is not a Pro account. Please upgrade your account or switch to a different account.", + // }) + // return + // } + + // result, err := translate.TranslateByDeepLXPro(sourceLang, targetLang, translateText, tagHandling, dlSession, proxyURL) + // if err != nil { + // log.Fatalf("Translation failed: %s", err) + // } + + // if result.Code == http.StatusOK { + // c.JSON(http.StatusOK, gin.H{ + // "code": http.StatusOK, + // "id": result.ID, + // "data": result.Data, + // "alternatives": result.Alternatives, + // "source_lang": result.SourceLang, + // "target_lang": result.TargetLang, + // "method": result.Method, + // }) + // } else { + // c.JSON(result.Code, gin.H{ + // "code": result.Code, + // "message": result.Message, + // }) + + // } + // }) // Free API endpoint, Consistent with the official API format r.POST("/v2/translate", authMiddleware(cfg), func(c *gin.Context) { diff --git a/translate/translate.go b/translate/translate.go index e07b1624..ff3efaa7 100644 --- a/translate/translate.go +++ b/translate/translate.go @@ -1,8 +1,8 @@ /* * @Author: Vincent Young * @Date: 2024-09-16 11:59:24 - * @LastEditors: Vincent Young - * @LastEditTime: 2024-09-16 12:09:37 + * @LastEditors: Vincent Yang + * @LastEditTime: 2024-11-01 00:42:43 * @FilePath: /DeepLX/translate/translate.go * @Telegram: https://t.me/missuo * @GitHub: https://github.com/missuo @@ -14,354 +14,224 @@ package translate import ( "bytes" - "encoding/json" + "fmt" "io" - "log" "net/http" "net/url" "strings" - "github.com/abadojack/whatlanggo" "github.com/andybalholm/brotli" "github.com/tidwall/gjson" ) -func initDeepLXData(sourceLang string, targetLang string) *PostData { - hasRegionalVariant := false - targetLangParts := strings.Split(targetLang, "-") +const baseURL = "https://www2.deepl.com/jsonrpc" - // targetLang can be "en", "pt", "pt-PT", "pt-BR" - // targetLangCode is the first part of the targetLang, e.g. "pt" in "pt-PT" - targetLangCode := targetLangParts[0] - if len(targetLangParts) > 1 { - hasRegionalVariant = true - } - - commonJobParams := CommonJobParams{ - WasSpoken: false, - TranscribeAS: "", - } - if hasRegionalVariant { - commonJobParams.RegionalVariant = targetLang - } - - return &PostData{ - Jsonrpc: "2.0", - Method: "LMT_handle_texts", - Params: Params{ - Splitting: "newlines", - Lang: Lang{ - SourceLangUserSelected: sourceLang, - TargetLang: targetLangCode, - }, - CommonJobParams: commonJobParams, - }, - } -} - -func TranslateByDeepLX(sourceLang string, targetLang string, translateText string, tagHandling string, proxyURL string) (DeepLXTranslationResult, error) { - id := getRandomNumber() - if sourceLang == "" { - lang := whatlanggo.DetectLang(translateText) - deepLLang := strings.ToUpper(lang.Iso6391()) - sourceLang = deepLLang - } - // If target language is not specified, set it to English - if targetLang == "" { - targetLang = "EN" - } - // Handling empty translation text - if translateText == "" { - return DeepLXTranslationResult{ - Code: http.StatusNotFound, - Message: "No text to translate", - }, nil - } - - // Preparing the request data for the DeepL API - www2URL := "https://www2.deepl.com/jsonrpc" - id = id + 1 - postData := initDeepLXData(sourceLang, targetLang) - text := Text{ - Text: translateText, - RequestAlternatives: 3, - } - postData.ID = id - postData.Params.Texts = append(postData.Params.Texts, text) - postData.Params.Timestamp = getTimeStamp(getICount(translateText)) - - if tagHandling == "html" || tagHandling == "xml" { - postData.Params.TagHandling = tagHandling - } - - // Marshalling the request data to JSON and making necessary string replacements - post_byte, _ := json.Marshal(postData) - postStr := string(post_byte) - - // Adding spaces to the JSON string based on the ID to adhere to DeepL's request formatting rules - if (id+5)%29 == 0 || (id+3)%13 == 0 { - postStr = strings.Replace(postStr, "\"method\":\"", "\"method\" : \"", -1) - } else { - postStr = strings.Replace(postStr, "\"method\":\"", "\"method\": \"", -1) - } - - // Creating a new HTTP POST request with the JSON data as the body - post_byte = []byte(postStr) - reader := bytes.NewReader(post_byte) - request, err := http.NewRequest("POST", www2URL, reader) +// makeRequest makes an HTTP request to DeepL API +func makeRequest(postData *PostData, urlMethod string, proxyURL string) (gjson.Result, error) { + urlFull := fmt.Sprintf("%s?client=chrome-extension,1.28.0&method=%s", baseURL, urlMethod) + postStr := formatPostString(postData) + req, err := http.NewRequest("POST", urlFull, bytes.NewReader([]byte(postStr))) if err != nil { - log.Println(err) - return DeepLXTranslationResult{ - Code: http.StatusServiceUnavailable, - Message: "Post request failed", - }, nil - } - - // Setting HTTP headers to mimic a request from the DeepL iOS App - request.Header.Set("Content-Type", "application/json") - request.Header.Set("Accept", "*/*") - request.Header.Set("x-app-os-name", "iOS") - request.Header.Set("x-app-os-version", "16.3.0") - request.Header.Set("Accept-Language", "en-US,en;q=0.9") - request.Header.Set("Accept-Encoding", "gzip, deflate, br") - request.Header.Set("x-app-device", "iPhone13,2") - request.Header.Set("User-Agent", "DeepL-iOS/2.9.1 iOS 16.3.0 (iPhone13,2)") - request.Header.Set("x-app-build", "510265") - request.Header.Set("x-app-version", "2.9.1") - request.Header.Set("Connection", "keep-alive") - - // Making the HTTP request to the DeepL API + return gjson.Result{}, err + } + + // Set headers + req.Header = 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"}, + } + + // Setup client with proxy if provided var client *http.Client if proxyURL != "" { proxy, err := url.Parse(proxyURL) if err != nil { - return DeepLXTranslationResult{ - Code: http.StatusServiceUnavailable, - Message: "Unknown error", - }, nil + return gjson.Result{}, err } - transport := &http.Transport{ - Proxy: http.ProxyURL(proxy), - } - client = &http.Client{Transport: transport} + client = &http.Client{Transport: &http.Transport{Proxy: http.ProxyURL(proxy)}} } else { client = &http.Client{} } - resp, err := client.Do(request) + resp, err := client.Do(req) if err != nil { - log.Println(err) - return DeepLXTranslationResult{ - Code: http.StatusServiceUnavailable, - Message: "DeepL API request failed", - }, nil + return gjson.Result{}, err } defer resp.Body.Close() - // Handling potential Brotli compressed response body var bodyReader io.Reader - switch resp.Header.Get("Content-Encoding") { - case "br": + if resp.Header.Get("Content-Encoding") == "br" { bodyReader = brotli.NewReader(resp.Body) - default: + } else { bodyReader = resp.Body } - // Reading the response body and parsing it with gjson - body, _ := io.ReadAll(bodyReader) - // body, _ := io.ReadAll(resp.Body) - res := gjson.ParseBytes(body) - - // Handling various response statuses and potential errors - if res.Get("error.code").String() == "-32600" { - log.Println(res.Get("error").String()) - return DeepLXTranslationResult{ - Code: http.StatusNotAcceptable, - Message: "Invalid target language", - }, nil - } - if resp.StatusCode == http.StatusTooManyRequests { - return DeepLXTranslationResult{ - Code: http.StatusTooManyRequests, - Message: "Too Many Requests", - }, nil + body, err := io.ReadAll(bodyReader) + if err != nil { + return gjson.Result{}, err } - var alternatives []string - res.Get("result.texts.0.alternatives").ForEach(func(key, value gjson.Result) bool { - alternatives = append(alternatives, value.Get("text").String()) - return true - }) - if res.Get("result.texts.0.text").String() == "" { - return DeepLXTranslationResult{ - Code: http.StatusServiceUnavailable, - Message: "Translation failed, API returns an empty result.", - }, nil - } else { - return DeepLXTranslationResult{ - Code: http.StatusOK, - ID: id, - Message: "Success", - Data: res.Get("result.texts.0.text").String(), - Alternatives: alternatives, - SourceLang: sourceLang, - TargetLang: targetLang, - Method: "Free", - }, nil - } + return gjson.ParseBytes(body), nil } -func TranslateByDeepLXPro(sourceLang string, targetLang string, translateText string, tagHandling string, dlSession string, proxyURL string) (DeepLXTranslationResult, error) { - id := getRandomNumber() - if sourceLang == "" { - lang := whatlanggo.DetectLang(translateText) - deepLLang := strings.ToUpper(lang.Iso6391()) - sourceLang = deepLLang - } - // If target language is not specified, set it to English - if targetLang == "" { - targetLang = "EN" +// splitText splits the input text for translation +func splitText(text string, tagHandling bool, proxyURL 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)], + }, } - // Handling empty translation text - if translateText == "" { + + return makeRequest(postData, "LMT_split_text", proxyURL) +} + +// TranslateByDeepLX performs translation using DeepL API +func TranslateByDeepLX(sourceLang, targetLang, text string, tagHandling string, proxyURL string) (DeepLXTranslationResult, error) { + if text == "" { return DeepLXTranslationResult{ Code: http.StatusNotFound, Message: "No text to translate", }, nil } - // Preparing the request data for the DeepL API - proURL := "https://api.deepl.com/jsonrpc" - id = id + 1 - postData := initDeepLXData(sourceLang, targetLang) - text := Text{ - Text: translateText, - RequestAlternatives: 3, - } - postData.ID = id - postData.Params.Texts = append(postData.Params.Texts, text) - postData.Params.Timestamp = getTimeStamp(getICount(translateText)) - - if tagHandling == "html" || tagHandling == "xml" { - postData.Params.TagHandling = tagHandling - } - - // Marshalling the request data to JSON and making necessary string replacements - post_byte, _ := json.Marshal(postData) - postStr := string(post_byte) - - // Adding spaces to the JSON string based on the ID to adhere to DeepL's request formatting rules - if (id+5)%29 == 0 || (id+3)%13 == 0 { - postStr = strings.Replace(postStr, "\"method\":\"", "\"method\" : \"", -1) - } else { - postStr = strings.Replace(postStr, "\"method\":\"", "\"method\": \"", -1) - } - - // Creating a new HTTP POST request with the JSON data as the body - post_byte = []byte(postStr) - reader := bytes.NewReader(post_byte) - request, err := http.NewRequest("POST", proURL, reader) - + // Split text first + splitResult, err := splitText(text, tagHandling == "html" || tagHandling == "xml", proxyURL) if err != nil { - log.Println(err) return DeepLXTranslationResult{ Code: http.StatusServiceUnavailable, - Message: "Post request failed", + Message: err.Error(), }, nil } - request.Header.Set("Content-Type", "application/json") - request.Header.Set("Accept", "*/*") - request.Header.Set("Accept-Language", "en-US,en;q=0.9") - request.Header.Set("Accept-Encoding", "gzip, deflate, br") - request.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36") - request.Header.Set("Origin", "https://www.deepl.com") - request.Header.Set("Referer", "https://www.deepl.com") - request.Header.Set("Connection", "keep-alive") - request.Header.Set("Cookie", "dl_session="+dlSession) + // Get detected language if source language is auto + if sourceLang == "auto" || sourceLang == "" { + sourceLang = strings.ToLower(splitResult.Get("result.lang.detected").String()) + } - // Making the HTTP request to the DeepL API - var client *http.Client - if proxyURL != "" { - proxy, err := url.Parse(proxyURL) - if err != nil { - return DeepLXTranslationResult{ - Code: http.StatusServiceUnavailable, - Message: "DeepL API request failed", - }, nil + // 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()} } - transport := &http.Transport{ - Proxy: http.ProxyURL(proxy), + if idx < len(chunks)-1 { + contextAfter = []string{chunks[idx+1].Get("sentences.0.text").String()} } - client = &http.Client{Transport: transport} - } else { - client = &http.Client{} + + 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, + }}, + }) } - resp, err := client.Do(request) + + // Prepare translation request + id := getRandomNumber() + postData := &PostData{ + Jsonrpc: "2.0", + Method: "LMT_handle_jobs", + ID: id, + Params: Params{ + CommonJobParams: CommonJobParams{ + Mode: "translate", + }, + Lang: Lang{ + SourceLangComputed: strings.ToUpper(sourceLang), + TargetLang: strings.ToUpper(targetLang), + }, + Jobs: jobs, + Priority: 1, + Timestamp: getTimeStamp(getICount(text)), + }, + } + + // Make translation request + result, err := makeRequest(postData, "LMT_handle_jobs", proxyURL) if err != nil { - log.Println(err) return DeepLXTranslationResult{ Code: http.StatusServiceUnavailable, - Message: "DeepL API request failed", + Message: err.Error(), }, nil } - defer resp.Body.Close() - - // Handling potential Brotli compressed response body - var bodyReader io.Reader - switch resp.Header.Get("Content-Encoding") { - case "br": - bodyReader = brotli.NewReader(resp.Body) - default: - bodyReader = resp.Body - } - // Reading the response body and parsing it with gjson - body, _ := io.ReadAll(bodyReader) - // body, _ := io.ReadAll(resp.Body) - res := gjson.ParseBytes(body) + // Process translation results + var alternatives []string + var translatedText string + + translations := result.Get("result.translations").Array() + if len(translations) > 0 { + // Get alternatives + numBeams := len(translations[0].Get("beams").Array()) + for i := 0; i < numBeams; i++ { + var altText string + for _, translation := range translations { + beams := translation.Get("beams").Array() + if i < len(beams) { + altText += beams[i].Get("sentences.0.text").String() + } + } + if altText != "" { + alternatives = append(alternatives, altText) + } + } - if res.Get("error.code").String() == "-32600" { - log.Println(res.Get("error").String()) - return DeepLXTranslationResult{ - Code: http.StatusNotAcceptable, - Message: "Invalid target language", - }, nil + // Get main translation + for _, translation := range translations { + translatedText += translation.Get("beams.0.sentences.0.text").String() + " " + } + translatedText = strings.TrimSpace(translatedText) } - if resp.StatusCode == http.StatusTooManyRequests { + if translatedText == "" { return DeepLXTranslationResult{ - Code: http.StatusTooManyRequests, - Message: "Too Many Requests", - }, nil - } else if resp.StatusCode == http.StatusUnauthorized { - return DeepLXTranslationResult{ - Code: http.StatusUnauthorized, - Message: "dlsession is invalid", + Code: http.StatusServiceUnavailable, + Message: "Translation failed", }, nil - } else { - var alternatives []string - res.Get("result.texts.0.alternatives").ForEach(func(key, value gjson.Result) bool { - alternatives = append(alternatives, value.Get("text").String()) - return true - }) - if res.Get("result.texts.0.text").String() == "" { - return DeepLXTranslationResult{ - Code: http.StatusServiceUnavailable, - Message: "Translation failed, API returns an empty result.", - }, nil - } else { - return DeepLXTranslationResult{ - Code: http.StatusOK, - ID: id, - Message: "Success", - Data: res.Get("result.texts.0.text").String(), - Alternatives: alternatives, - SourceLang: sourceLang, - TargetLang: targetLang, - Method: "Pro", - }, nil - } } + + return DeepLXTranslationResult{ + Code: http.StatusOK, + ID: id, + Data: translatedText, + Alternatives: alternatives, + SourceLang: sourceLang, + TargetLang: targetLang, + Method: "Free", + }, nil } diff --git a/translate/types.go b/translate/types.go index 105a7603..ef76bf53 100644 --- a/translate/types.go +++ b/translate/types.go @@ -1,8 +1,8 @@ /* * @Author: Vincent Young * @Date: 2024-09-16 11:59:24 - * @LastEditors: Vincent Young - * @LastEditTime: 2024-09-16 12:06:36 + * @LastEditors: Vincent Yang + * @LastEditTime: 2024-11-01 00:39:49 * @FilePath: /DeepLX/translate/types.go * @Telegram: https://t.me/missuo * @GitHub: https://github.com/missuo @@ -12,31 +12,46 @@ package translate +// Lang represents the language settings for translation type Lang struct { - SourceLangUserSelected string `json:"source_lang_user_selected"` - TargetLang string `json:"target_lang"` + SourceLangComputed string `json:"source_lang_computed,omitempty"` + TargetLang string `json:"target_lang"` + LangUserSelected string `json:"lang_user_selected,omitempty"` } +// CommonJobParams represents common parameters for translation jobs type CommonJobParams struct { - WasSpoken bool `json:"wasSpoken"` - TranscribeAS string `json:"transcribe_as"` - RegionalVariant string `json:"regionalVariant,omitempty"` + Mode string `json:"mode"` } +// Sentence represents a sentence in the translation request +type Sentence struct { + Prefix string `json:"prefix"` + Text string `json:"text"` + ID int `json:"id"` +} + +// Job represents a translation job +type Job struct { + Kind string `json:"kind"` + PreferredNumBeams int `json:"preferred_num_beams"` + RawEnContextBefore []string `json:"raw_en_context_before"` + RawEnContextAfter []string `json:"raw_en_context_after"` + Sentences []Sentence `json:"sentences"` +} + +// Params represents parameters for translation requests type Params struct { - Texts []Text `json:"texts"` - Splitting string `json:"splitting"` + 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"` Timestamp int64 `json:"timestamp"` - CommonJobParams CommonJobParams `json:"commonJobParams"` - TagHandling string `json:"tag_handling"` -} - -type Text struct { - Text string `json:"text"` - RequestAlternatives int `json:"requestAlternatives"` } +// PostData represents the complete translation request type PostData struct { Jsonrpc string `json:"jsonrpc"` Method string `json:"method"` @@ -44,26 +59,50 @@ type PostData struct { Params Params `json:"params"` } -type Translation struct { - Text string `json:"text"` +// 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 { - Translations []Translation `json:"translations"` -} - -type DeepLUsageResponse struct { - CharacterCount int `json:"character_count"` - CharacterLimit int `json:"character_limit"` + Jsonrpc string `json:"jsonrpc"` + ID int64 `json:"id"` + Result struct { + Translations []struct { + Beams []struct { + Sentences []struct { + Text string `json:"text"` + } `json:"sentences"` + } `json:"beams"` + } `json:"translations"` + SourceLang string `json:"source_lang"` + TargetLang string `json:"target_lang"` + } `json:"result"` } +// DeepLXTranslationResult represents the final translation result type DeepLXTranslationResult struct { - Code int - ID int64 - Message string - Data string - Alternatives []string - SourceLang string - TargetLang string - Method string + Code int `json:"code"` + ID int64 `json:"id"` + Message string `json:"message,omitempty"` + Data string `json:"data"` + Alternatives []string `json:"alternatives"` + SourceLang string `json:"source_lang"` + TargetLang string `json:"target_lang"` + Method string `json:"method"` } diff --git a/translate/utils.go b/translate/utils.go index 2f9a8ec9..12682624 100644 --- a/translate/utils.go +++ b/translate/utils.go @@ -1,8 +1,8 @@ /* * @Author: Vincent Young * @Date: 2024-09-16 11:59:24 - * @LastEditors: Vincent Young - * @LastEditTime: 2024-09-16 12:06:44 + * @LastEditors: Vincent Yang + * @LastEditTime: 2024-11-01 00:39:32 * @FilePath: /DeepLX/translate/utils.go * @Telegram: https://t.me/missuo * @GitHub: https://github.com/missuo @@ -13,15 +13,18 @@ package translate import ( + "encoding/json" "math/rand" "strings" "time" ) +// getICount returns the number of 'i' characters in the text func getICount(translateText string) int64 { return int64(strings.Count(translateText, "i")) } +// getRandomNumber generates a random number for request ID func getRandomNumber() int64 { src := rand.NewSource(time.Now().UnixNano()) rng := rand.New(src) @@ -29,12 +32,31 @@ func getRandomNumber() int64 { return num * 1000 } +// getTimeStamp generates timestamp for request based on i count func getTimeStamp(iCount int64) int64 { ts := time.Now().UnixMilli() if iCount != 0 { iCount = iCount + 1 return ts - ts%iCount + iCount + } + return ts +} + +// formatPostString formats the request JSON string with specific spacing rules +func formatPostString(postData *PostData) string { + postBytes, _ := json.Marshal(postData) + postStr := string(postBytes) + + if (postData.ID+5)%29 == 0 || (postData.ID+3)%13 == 0 { + postStr = strings.Replace(postStr, `"method":"`, `"method" : "`, 1) } else { - return ts + postStr = strings.Replace(postStr, `"method":"`, `"method": "`, 1) } + + return postStr +} + +// isRichText checks if text contains HTML-like tags +func isRichText(text string) bool { + return strings.Contains(text, "<") && strings.Contains(text, ">") }