mirror of
https://github.com/OwO-Network/DeepLX.git
synced 2024-11-16 13:32:39 +08:00
fix: unable to translate
This commit is contained in:
parent
c678e87631
commit
62a993bb13
112
main.go
112
main.go
|
@ -1,8 +1,8 @@
|
||||||
/*
|
/*
|
||||||
* @Author: Vincent Yang
|
* @Author: Vincent Yang
|
||||||
* @Date: 2023-07-01 21:45:34
|
* @Date: 2023-07-01 21:45:34
|
||||||
* @LastEditors: Vincent Young
|
* @LastEditors: Vincent Yang
|
||||||
* @LastEditTime: 2024-09-16 12:12:35
|
* @LastEditTime: 2024-11-01 00:42:58
|
||||||
* @FilePath: /DeepLX/main.go
|
* @FilePath: /DeepLX/main.go
|
||||||
* @Telegram: https://t.me/missuo
|
* @Telegram: https://t.me/missuo
|
||||||
* @GitHub: https://github.com/missuo
|
* @GitHub: https://github.com/missuo
|
||||||
|
@ -156,68 +156,68 @@ func main() {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Pro API endpoint, Pro Account required
|
// Pro API endpoint, Pro Account required
|
||||||
r.POST("/v1/translate", authMiddleware(cfg), func(c *gin.Context) {
|
// r.POST("/v1/translate", authMiddleware(cfg), func(c *gin.Context) {
|
||||||
req := PayloadFree{}
|
// req := PayloadFree{}
|
||||||
c.BindJSON(&req)
|
// c.BindJSON(&req)
|
||||||
|
|
||||||
sourceLang := req.SourceLang
|
// sourceLang := req.SourceLang
|
||||||
targetLang := req.TargetLang
|
// targetLang := req.TargetLang
|
||||||
translateText := req.TransText
|
// translateText := req.TransText
|
||||||
tagHandling := req.TagHandling
|
// tagHandling := req.TagHandling
|
||||||
proxyURL := cfg.Proxy
|
// proxyURL := cfg.Proxy
|
||||||
|
|
||||||
dlSession := cfg.DlSession
|
// dlSession := cfg.DlSession
|
||||||
|
|
||||||
if tagHandling != "" && tagHandling != "html" && tagHandling != "xml" {
|
// if tagHandling != "" && tagHandling != "html" && tagHandling != "xml" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
// c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"code": http.StatusBadRequest,
|
// "code": http.StatusBadRequest,
|
||||||
"message": "Invalid tag_handling value. Allowed values are 'html' and 'xml'.",
|
// "message": "Invalid tag_handling value. Allowed values are 'html' and 'xml'.",
|
||||||
})
|
// })
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
cookie := c.GetHeader("Cookie")
|
// cookie := c.GetHeader("Cookie")
|
||||||
if cookie != "" {
|
// if cookie != "" {
|
||||||
dlSession = strings.Replace(cookie, "dl_session=", "", -1)
|
// dlSession = strings.Replace(cookie, "dl_session=", "", -1)
|
||||||
}
|
// }
|
||||||
|
|
||||||
if dlSession == "" {
|
// if dlSession == "" {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{
|
// c.JSON(http.StatusUnauthorized, gin.H{
|
||||||
"code": http.StatusUnauthorized,
|
// "code": http.StatusUnauthorized,
|
||||||
"message": "No dl_session Found",
|
// "message": "No dl_session Found",
|
||||||
})
|
// })
|
||||||
return
|
// return
|
||||||
} else if strings.Contains(dlSession, ".") {
|
// } else if strings.Contains(dlSession, ".") {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{
|
// c.JSON(http.StatusUnauthorized, gin.H{
|
||||||
"code": http.StatusUnauthorized,
|
// "code": http.StatusUnauthorized,
|
||||||
"message": "Your account is not a Pro account. Please upgrade your account or switch to a different account.",
|
// "message": "Your account is not a Pro account. Please upgrade your account or switch to a different account.",
|
||||||
})
|
// })
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
result, err := translate.TranslateByDeepLXPro(sourceLang, targetLang, translateText, tagHandling, dlSession, proxyURL)
|
// result, err := translate.TranslateByDeepLXPro(sourceLang, targetLang, translateText, tagHandling, dlSession, proxyURL)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
log.Fatalf("Translation failed: %s", err)
|
// log.Fatalf("Translation failed: %s", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
if result.Code == http.StatusOK {
|
// if result.Code == http.StatusOK {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
// c.JSON(http.StatusOK, gin.H{
|
||||||
"code": http.StatusOK,
|
// "code": http.StatusOK,
|
||||||
"id": result.ID,
|
// "id": result.ID,
|
||||||
"data": result.Data,
|
// "data": result.Data,
|
||||||
"alternatives": result.Alternatives,
|
// "alternatives": result.Alternatives,
|
||||||
"source_lang": result.SourceLang,
|
// "source_lang": result.SourceLang,
|
||||||
"target_lang": result.TargetLang,
|
// "target_lang": result.TargetLang,
|
||||||
"method": result.Method,
|
// "method": result.Method,
|
||||||
})
|
// })
|
||||||
} else {
|
// } else {
|
||||||
c.JSON(result.Code, gin.H{
|
// c.JSON(result.Code, gin.H{
|
||||||
"code": result.Code,
|
// "code": result.Code,
|
||||||
"message": result.Message,
|
// "message": result.Message,
|
||||||
})
|
// })
|
||||||
|
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
|
|
||||||
// Free API endpoint, Consistent with the official API format
|
// Free API endpoint, Consistent with the official API format
|
||||||
r.POST("/v2/translate", authMiddleware(cfg), func(c *gin.Context) {
|
r.POST("/v2/translate", authMiddleware(cfg), func(c *gin.Context) {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
/*
|
/*
|
||||||
* @Author: Vincent Young
|
* @Author: Vincent Young
|
||||||
* @Date: 2024-09-16 11:59:24
|
* @Date: 2024-09-16 11:59:24
|
||||||
* @LastEditors: Vincent Young
|
* @LastEditors: Vincent Yang
|
||||||
* @LastEditTime: 2024-09-16 12:09:37
|
* @LastEditTime: 2024-11-01 00:42:43
|
||||||
* @FilePath: /DeepLX/translate/translate.go
|
* @FilePath: /DeepLX/translate/translate.go
|
||||||
* @Telegram: https://t.me/missuo
|
* @Telegram: https://t.me/missuo
|
||||||
* @GitHub: https://github.com/missuo
|
* @GitHub: https://github.com/missuo
|
||||||
|
@ -14,354 +14,224 @@ package translate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/abadojack/whatlanggo"
|
|
||||||
"github.com/andybalholm/brotli"
|
"github.com/andybalholm/brotli"
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
func initDeepLXData(sourceLang string, targetLang string) *PostData {
|
const baseURL = "https://www2.deepl.com/jsonrpc"
|
||||||
hasRegionalVariant := false
|
|
||||||
targetLangParts := strings.Split(targetLang, "-")
|
|
||||||
|
|
||||||
// targetLang can be "en", "pt", "pt-PT", "pt-BR"
|
// makeRequest makes an HTTP request to DeepL API
|
||||||
// targetLangCode is the first part of the targetLang, e.g. "pt" in "pt-PT"
|
func makeRequest(postData *PostData, urlMethod string, proxyURL string) (gjson.Result, error) {
|
||||||
targetLangCode := targetLangParts[0]
|
urlFull := fmt.Sprintf("%s?client=chrome-extension,1.28.0&method=%s", baseURL, urlMethod)
|
||||||
if len(targetLangParts) > 1 {
|
|
||||||
hasRegionalVariant = true
|
postStr := formatPostString(postData)
|
||||||
|
req, err := http.NewRequest("POST", urlFull, bytes.NewReader([]byte(postStr)))
|
||||||
|
if err != nil {
|
||||||
|
return gjson.Result{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
commonJobParams := CommonJobParams{
|
// Set headers
|
||||||
WasSpoken: false,
|
req.Header = http.Header{
|
||||||
TranscribeAS: "",
|
"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"},
|
||||||
if hasRegionalVariant {
|
"Authorization": []string{"None"},
|
||||||
commonJobParams.RegionalVariant = targetLang
|
"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"},
|
||||||
}
|
}
|
||||||
|
|
||||||
return &PostData{
|
// Setup client with proxy if provided
|
||||||
|
var client *http.Client
|
||||||
|
if proxyURL != "" {
|
||||||
|
proxy, err := url.Parse(proxyURL)
|
||||||
|
if err != nil {
|
||||||
|
return gjson.Result{}, err
|
||||||
|
}
|
||||||
|
client = &http.Client{Transport: &http.Transport{Proxy: http.ProxyURL(proxy)}}
|
||||||
|
} else {
|
||||||
|
client = &http.Client{}
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return gjson.Result{}, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var bodyReader io.Reader
|
||||||
|
if resp.Header.Get("Content-Encoding") == "br" {
|
||||||
|
bodyReader = brotli.NewReader(resp.Body)
|
||||||
|
} else {
|
||||||
|
bodyReader = resp.Body
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(bodyReader)
|
||||||
|
if err != nil {
|
||||||
|
return gjson.Result{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return gjson.ParseBytes(body), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitText splits the input text for translation
|
||||||
|
func splitText(text string, tagHandling bool, proxyURL string) (gjson.Result, error) {
|
||||||
|
postData := &PostData{
|
||||||
Jsonrpc: "2.0",
|
Jsonrpc: "2.0",
|
||||||
Method: "LMT_handle_texts",
|
Method: "LMT_split_text",
|
||||||
|
ID: getRandomNumber(),
|
||||||
Params: Params{
|
Params: Params{
|
||||||
Splitting: "newlines",
|
CommonJobParams: CommonJobParams{
|
||||||
Lang: Lang{
|
Mode: "translate",
|
||||||
SourceLangUserSelected: sourceLang,
|
|
||||||
TargetLang: targetLangCode,
|
|
||||||
},
|
},
|
||||||
CommonJobParams: commonJobParams,
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TranslateByDeepLX(sourceLang string, targetLang string, translateText string, tagHandling string, proxyURL string) (DeepLXTranslationResult, error) {
|
// TranslateByDeepLX performs translation using DeepL API
|
||||||
id := getRandomNumber()
|
func TranslateByDeepLX(sourceLang, targetLang, text string, tagHandling string, proxyURL string) (DeepLXTranslationResult, error) {
|
||||||
if sourceLang == "" {
|
if text == "" {
|
||||||
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{
|
return DeepLXTranslationResult{
|
||||||
Code: http.StatusNotFound,
|
Code: http.StatusNotFound,
|
||||||
Message: "No text to translate",
|
Message: "No text to translate",
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preparing the request data for the DeepL API
|
// Split text first
|
||||||
www2URL := "https://www2.deepl.com/jsonrpc"
|
splitResult, err := splitText(text, tagHandling == "html" || tagHandling == "xml", proxyURL)
|
||||||
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)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
|
||||||
return DeepLXTranslationResult{
|
return DeepLXTranslationResult{
|
||||||
Code: http.StatusServiceUnavailable,
|
Code: http.StatusServiceUnavailable,
|
||||||
Message: "Post request failed",
|
Message: err.Error(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setting HTTP headers to mimic a request from the DeepL iOS App
|
// Get detected language if source language is auto
|
||||||
request.Header.Set("Content-Type", "application/json")
|
if sourceLang == "auto" || sourceLang == "" {
|
||||||
request.Header.Set("Accept", "*/*")
|
sourceLang = strings.ToLower(splitResult.Get("result.lang.detected").String())
|
||||||
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
|
// Prepare jobs from split result
|
||||||
var client *http.Client
|
var jobs []Job
|
||||||
if proxyURL != "" {
|
chunks := splitResult.Get("result.texts.0.chunks").Array()
|
||||||
proxy, err := url.Parse(proxyURL)
|
for idx, chunk := range chunks {
|
||||||
if err != nil {
|
sentence := chunk.Get("sentences.0")
|
||||||
return DeepLXTranslationResult{
|
|
||||||
Code: http.StatusServiceUnavailable,
|
// Handle context
|
||||||
Message: "Unknown error",
|
contextBefore := []string{}
|
||||||
}, nil
|
contextAfter := []string{}
|
||||||
|
if idx > 0 {
|
||||||
|
contextBefore = []string{chunks[idx-1].Get("sentences.0.text").String()}
|
||||||
}
|
}
|
||||||
transport := &http.Transport{
|
if idx < len(chunks)-1 {
|
||||||
Proxy: http.ProxyURL(proxy),
|
contextAfter = []string{chunks[idx+1].Get("sentences.0.text").String()}
|
||||||
}
|
}
|
||||||
client = &http.Client{Transport: transport}
|
|
||||||
} else {
|
|
||||||
client = &http.Client{}
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := client.Do(request)
|
jobs = append(jobs, Job{
|
||||||
if err != nil {
|
Kind: "default",
|
||||||
log.Println(err)
|
PreferredNumBeams: 4,
|
||||||
return DeepLXTranslationResult{
|
RawEnContextBefore: contextBefore,
|
||||||
Code: http.StatusServiceUnavailable,
|
RawEnContextAfter: contextAfter,
|
||||||
Message: "DeepL API request failed",
|
Sentences: []Sentence{{
|
||||||
}, nil
|
Prefix: sentence.Get("prefix").String(),
|
||||||
}
|
Text: sentence.Get("text").String(),
|
||||||
defer resp.Body.Close()
|
ID: idx + 1,
|
||||||
|
}},
|
||||||
// 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)
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 == "" {
|
|
||||||
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)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return DeepLXTranslationResult{
|
|
||||||
Code: http.StatusServiceUnavailable,
|
|
||||||
Message: "Post request failed",
|
|
||||||
}, 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)
|
|
||||||
|
|
||||||
// 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),
|
|
||||||
}
|
|
||||||
client = &http.Client{Transport: transport}
|
|
||||||
} else {
|
|
||||||
client = &http.Client{}
|
|
||||||
}
|
|
||||||
resp, err := client.Do(request)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return DeepLXTranslationResult{
|
|
||||||
Code: http.StatusServiceUnavailable,
|
|
||||||
Message: "DeepL API request failed",
|
|
||||||
}, 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)
|
|
||||||
|
|
||||||
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
|
|
||||||
} else if resp.StatusCode == http.StatusUnauthorized {
|
|
||||||
return DeepLXTranslationResult{
|
|
||||||
Code: http.StatusUnauthorized,
|
|
||||||
Message: "dlsession is invalid",
|
|
||||||
}, 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
return DeepLXTranslationResult{
|
||||||
|
Code: http.StatusServiceUnavailable,
|
||||||
|
Message: err.Error(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get main translation
|
||||||
|
for _, translation := range translations {
|
||||||
|
translatedText += translation.Get("beams.0.sentences.0.text").String() + " "
|
||||||
|
}
|
||||||
|
translatedText = strings.TrimSpace(translatedText)
|
||||||
|
}
|
||||||
|
|
||||||
|
if translatedText == "" {
|
||||||
|
return DeepLXTranslationResult{
|
||||||
|
Code: http.StatusServiceUnavailable,
|
||||||
|
Message: "Translation failed",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return DeepLXTranslationResult{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
ID: id,
|
||||||
|
Data: translatedText,
|
||||||
|
Alternatives: alternatives,
|
||||||
|
SourceLang: sourceLang,
|
||||||
|
TargetLang: targetLang,
|
||||||
|
Method: "Free",
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
/*
|
/*
|
||||||
* @Author: Vincent Young
|
* @Author: Vincent Young
|
||||||
* @Date: 2024-09-16 11:59:24
|
* @Date: 2024-09-16 11:59:24
|
||||||
* @LastEditors: Vincent Young
|
* @LastEditors: Vincent Yang
|
||||||
* @LastEditTime: 2024-09-16 12:06:36
|
* @LastEditTime: 2024-11-01 00:39:49
|
||||||
* @FilePath: /DeepLX/translate/types.go
|
* @FilePath: /DeepLX/translate/types.go
|
||||||
* @Telegram: https://t.me/missuo
|
* @Telegram: https://t.me/missuo
|
||||||
* @GitHub: https://github.com/missuo
|
* @GitHub: https://github.com/missuo
|
||||||
|
@ -12,31 +12,46 @@
|
||||||
|
|
||||||
package translate
|
package translate
|
||||||
|
|
||||||
|
// Lang represents the language settings for translation
|
||||||
type Lang struct {
|
type Lang struct {
|
||||||
SourceLangUserSelected string `json:"source_lang_user_selected"`
|
SourceLangComputed string `json:"source_lang_computed,omitempty"`
|
||||||
TargetLang string `json:"target_lang"`
|
TargetLang string `json:"target_lang"`
|
||||||
|
LangUserSelected string `json:"lang_user_selected,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CommonJobParams represents common parameters for translation jobs
|
||||||
type CommonJobParams struct {
|
type CommonJobParams struct {
|
||||||
WasSpoken bool `json:"wasSpoken"`
|
Mode string `json:"mode"`
|
||||||
TranscribeAS string `json:"transcribe_as"`
|
|
||||||
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 {
|
type Params struct {
|
||||||
Texts []Text `json:"texts"`
|
|
||||||
Splitting string `json:"splitting"`
|
|
||||||
Lang Lang `json:"lang"`
|
|
||||||
Timestamp int64 `json:"timestamp"`
|
|
||||||
CommonJobParams CommonJobParams `json:"commonJobParams"`
|
CommonJobParams CommonJobParams `json:"commonJobParams"`
|
||||||
TagHandling string `json:"tag_handling"`
|
Lang Lang `json:"lang"`
|
||||||
}
|
Texts []string `json:"texts,omitempty"`
|
||||||
|
TextType string `json:"textType,omitempty"`
|
||||||
type Text struct {
|
Jobs []Job `json:"jobs,omitempty"`
|
||||||
Text string `json:"text"`
|
Priority int `json:"priority,omitempty"`
|
||||||
RequestAlternatives int `json:"requestAlternatives"`
|
Timestamp int64 `json:"timestamp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PostData represents the complete translation request
|
||||||
type PostData struct {
|
type PostData struct {
|
||||||
Jsonrpc string `json:"jsonrpc"`
|
Jsonrpc string `json:"jsonrpc"`
|
||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
|
@ -44,26 +59,50 @@ type PostData struct {
|
||||||
Params Params `json:"params"`
|
Params Params `json:"params"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Translation struct {
|
// SplitTextResponse represents the response from text splitting
|
||||||
Text string `json:"text"`
|
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 {
|
type TranslationResponse struct {
|
||||||
Translations []Translation `json:"translations"`
|
Jsonrpc string `json:"jsonrpc"`
|
||||||
}
|
ID int64 `json:"id"`
|
||||||
|
Result struct {
|
||||||
type DeepLUsageResponse struct {
|
Translations []struct {
|
||||||
CharacterCount int `json:"character_count"`
|
Beams []struct {
|
||||||
CharacterLimit int `json:"character_limit"`
|
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 {
|
type DeepLXTranslationResult struct {
|
||||||
Code int
|
Code int `json:"code"`
|
||||||
ID int64
|
ID int64 `json:"id"`
|
||||||
Message string
|
Message string `json:"message,omitempty"`
|
||||||
Data string
|
Data string `json:"data"`
|
||||||
Alternatives []string
|
Alternatives []string `json:"alternatives"`
|
||||||
SourceLang string
|
SourceLang string `json:"source_lang"`
|
||||||
TargetLang string
|
TargetLang string `json:"target_lang"`
|
||||||
Method string
|
Method string `json:"method"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
/*
|
/*
|
||||||
* @Author: Vincent Young
|
* @Author: Vincent Young
|
||||||
* @Date: 2024-09-16 11:59:24
|
* @Date: 2024-09-16 11:59:24
|
||||||
* @LastEditors: Vincent Young
|
* @LastEditors: Vincent Yang
|
||||||
* @LastEditTime: 2024-09-16 12:06:44
|
* @LastEditTime: 2024-11-01 00:39:32
|
||||||
* @FilePath: /DeepLX/translate/utils.go
|
* @FilePath: /DeepLX/translate/utils.go
|
||||||
* @Telegram: https://t.me/missuo
|
* @Telegram: https://t.me/missuo
|
||||||
* @GitHub: https://github.com/missuo
|
* @GitHub: https://github.com/missuo
|
||||||
|
@ -13,15 +13,18 @@
|
||||||
package translate
|
package translate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// getICount returns the number of 'i' characters in the text
|
||||||
func getICount(translateText string) int64 {
|
func getICount(translateText string) int64 {
|
||||||
return int64(strings.Count(translateText, "i"))
|
return int64(strings.Count(translateText, "i"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getRandomNumber generates a random number for request ID
|
||||||
func getRandomNumber() int64 {
|
func getRandomNumber() int64 {
|
||||||
src := rand.NewSource(time.Now().UnixNano())
|
src := rand.NewSource(time.Now().UnixNano())
|
||||||
rng := rand.New(src)
|
rng := rand.New(src)
|
||||||
|
@ -29,12 +32,31 @@ func getRandomNumber() int64 {
|
||||||
return num * 1000
|
return num * 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getTimeStamp generates timestamp for request based on i count
|
||||||
func getTimeStamp(iCount int64) int64 {
|
func getTimeStamp(iCount int64) int64 {
|
||||||
ts := time.Now().UnixMilli()
|
ts := time.Now().UnixMilli()
|
||||||
if iCount != 0 {
|
if iCount != 0 {
|
||||||
iCount = iCount + 1
|
iCount = iCount + 1
|
||||||
return ts - ts%iCount + iCount
|
return ts - ts%iCount + iCount
|
||||||
} else {
|
|
||||||
return ts
|
|
||||||
}
|
}
|
||||||
|
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 {
|
||||||
|
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, ">")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user