diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index 3a4587fd..b311e4b0 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -3,7 +3,7 @@ name: Docker Image CI on: push: branches: - - dev + - develop/* tags: - 'v*' @@ -21,23 +21,23 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 with: platforms: all - name: Set up docker buildx id: buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 with: version: latest - name: Login to DockerHub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: docker.io username: ${{ env.DOCKER_USERNAME }} @@ -52,7 +52,7 @@ jobs: - name: Docker meta id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: # list of Docker images to use as base name for tags images: | @@ -65,7 +65,7 @@ jobs: type=raw,value=dev,enable=${{ github.ref == 'refs/heads/dev' }} - name: Build and push - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v6 with: context: . platforms: linux/amd64,linux/arm64 diff --git a/main.go b/main.go index 43aefc5b..0a156446 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 13:04:50 * @FilePath: /DeepLX/main.go * @Telegram: https://t.me/missuo * @GitHub: https://github.com/missuo @@ -131,7 +131,7 @@ func main() { return } - result, err := translate.TranslateByDeepLX(sourceLang, targetLang, translateText, tagHandling, proxyURL) + result, err := translate.TranslateByDeepLX(sourceLang, targetLang, translateText, tagHandling, proxyURL, "") if err != nil { log.Fatalf("Translation failed: %s", err) } @@ -168,8 +168,6 @@ func main() { dlSession := cfg.DlSession - // fmt.Printf("ENV DL_SESSION: %s\n", cfg.DlSession) - if tagHandling != "" && tagHandling != "html" && tagHandling != "xml" { c.JSON(http.StatusBadRequest, gin.H{ "code": http.StatusBadRequest, @@ -187,12 +185,10 @@ func main() { } } - // log.Printf("dlSession: %s\n", dlSession) - if dlSession == "" { c.JSON(http.StatusUnauthorized, gin.H{ "code": http.StatusUnauthorized, - "message": "No dl_session Found.", + "message": "No dl_session Found", }) return } else if strings.Contains(dlSession, ".") { @@ -203,7 +199,7 @@ func main() { return } - result, err := translate.TranslateByDeepLXPro(sourceLang, targetLang, translateText, tagHandling, dlSession, proxyURL) + result, err := translate.TranslateByDeepLX(sourceLang, targetLang, translateText, tagHandling, proxyURL, dlSession) if err != nil { log.Fatalf("Translation failed: %s", err) } @@ -262,7 +258,7 @@ func main() { return } - result, err := translate.TranslateByDeepLXPro(sourceLang, targetLang, translateText, tagHandling, dlSession, proxyURL) + result, err := translate.TranslateByDeepLX(sourceLang, targetLang, translateText, tagHandling, proxyURL, dlSession) if err != nil { log.Fatalf("Translation failed: %s", err) } @@ -314,7 +310,7 @@ func main() { targetLang = jsonData.TargetLang } - result, err := translate.TranslateByDeepLX("", targetLang, translateText, "", proxyURL) + result, err := translate.TranslateByDeepLX("", targetLang, translateText, "", proxyURL, "") if err != nil { log.Fatalf("Translation failed: %s", err) } diff --git a/translate/translate.go b/translate/translate.go index b13761ed..7dd15439 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 23:19:11 * @FilePath: /DeepLX/translate/translate.go * @Telegram: https://t.me/missuo * @GitHub: https://github.com/missuo @@ -14,354 +14,273 @@ 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, "-") - - // 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 - } +const baseURL = "https://www2.deepl.com/jsonrpc" - commonJobParams := CommonJobParams{ - WasSpoken: false, - TranscribeAS: "", - } - if hasRegionalVariant { - commonJobParams.RegionalVariant = targetLang - } +// 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) - 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 + postStr := formatPostString(postData) + req, err := http.NewRequest("POST", urlFull, bytes.NewReader([]byte(postStr))) + if err != nil { + return gjson.Result{}, err } - // 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) + if dlSession != "" { + 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"}, + "Cookie": []string{"dl_session=" + dlSession}, + } } 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) - - if err != nil { - log.Println(err) - return DeepLXTranslationResult{ - Code: http.StatusServiceUnavailable, - Message: "Post request failed", - }, nil + 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"}, + } } - // 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", "18.0.1") - 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 18.0.1 (iPhone16,1)") - 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 + // 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 - } - transport := &http.Transport{ - Proxy: http.ProxyURL(proxy), + return gjson.Result{}, err } - 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 } + return gjson.ParseBytes(body), nil +} - 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 +// 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) } -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" - } - // Handling empty translation text - if translateText == "" { +// TranslateByDeepLX performs translation using DeepL API +func TranslateByDeepLX(sourceLang, targetLang, text string, tagHandling string, proxyURL string, dlSession 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, + // Split text first + splitResult, err := splitText(text, tagHandling == "html" || tagHandling == "xml", proxyURL, dlSession) + if err != nil { + return DeepLXTranslationResult{ + Code: http.StatusServiceUnavailable, + Message: err.Error(), + }, nil } - 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 + // Get detected language if source language is auto + if sourceLang == "auto" || sourceLang == "" { + sourceLang = strings.ToLower(splitResult.Get("result.lang.detected").String()) } - // Marshalling the request data to JSON and making necessary string replacements - post_byte, _ := json.Marshal(postData) - postStr := string(post_byte) + // 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()} + } - // 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) + 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, + }}, + }) } - // 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) - - if err != nil { - log.Println(err) - return DeepLXTranslationResult{ - Code: http.StatusServiceUnavailable, - Message: "Post request failed", - }, nil + hasRegionalVariant := false + targetLangCode := targetLang + targetLangParts := strings.Split(targetLang, "-") + if len(targetLangParts) > 1 { + targetLangCode = targetLangParts[0] + hasRegionalVariant = true } - 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) + // Prepare translation request + id := getRandomNumber() - // 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 - } - transport := &http.Transport{ - Proxy: http.ProxyURL(proxy), + 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(targetLangCode), + }, + Jobs: jobs, + Priority: 1, + Timestamp: getTimeStamp(getICount(text)), + }, + } + + if hasRegionalVariant { + postData = &PostData{ + Jsonrpc: "2.0", + Method: "LMT_handle_jobs", + ID: id, + Params: Params{ + CommonJobParams: CommonJobParams{ + Mode: "translate", + RegionalVariant: map[bool]string{true: targetLang, false: ""}[hasRegionalVariant], + }, + Lang: Lang{ + SourceLangComputed: strings.ToUpper(sourceLang), + TargetLang: strings.ToUpper(targetLangCode), + }, + Jobs: jobs, + Priority: 1, + Timestamp: getTimeStamp(getICount(text)), + }, } - client = &http.Client{Transport: transport} - } else { - client = &http.Client{} } - resp, err := client.Do(request) + + // Make translation request + result, err := makeRequest(postData, "LMT_handle_jobs", proxyURL, dlSession) 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: map[bool]string{true: "Pro", false: "Free"}[dlSession != ""], + }, nil } diff --git a/translate/types.go b/translate/types.go index 105a7603..c725f8c8 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 23:18:56 * @FilePath: /DeepLX/translate/types.go * @Telegram: https://t.me/missuo * @GitHub: https://github.com/missuo @@ -12,31 +12,47 @@ 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"` + Mode string `json:"mode"` RegionalVariant string `json:"regionalVariant,omitempty"` } +// 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 +60,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, ">") }