mirror of
https://github.com/OwO-Network/DeepLX.git
synced 2025-07-25 20:03:24 +00:00
refactor: update translation request handling to use new LMT_handle_texts method and improve response processing
- Renamed makeRequest to makeRequestWithBody for clarity. - Introduced new TextItem and TextResponse types for better structure in translation requests and responses. - Updated translation logic to handle multiple texts and alternatives. - Enhanced error handling for blocked requests and translation failures. - Adjusted timestamp and random number generation for improved request uniqueness.
This commit is contained in:
parent
56637adc3c
commit
8a605887ff
@ -2,7 +2,7 @@
|
|||||||
* @Author: Vincent Young
|
* @Author: Vincent Young
|
||||||
* @Date: 2024-09-16 11:59:24
|
* @Date: 2024-09-16 11:59:24
|
||||||
* @LastEditors: Vincent Yang
|
* @LastEditors: Vincent Yang
|
||||||
* @LastEditTime: 2025-04-08 14:26:33
|
* @LastEditTime: 2025-07-13 23:06:01
|
||||||
* @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
|
||||||
@ -29,28 +29,16 @@ import (
|
|||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
// makeRequest makes an HTTP request to DeepL API
|
// makeRequestWithBody makes an HTTP request with pre-formatted body using minimal headers
|
||||||
func makeRequest(postData *PostData, proxyURL string, dlSession string) (gjson.Result, error) {
|
func makeRequestWithBody(postStr string, proxyURL string, dlSession string) (gjson.Result, error) {
|
||||||
urlFull := "https://www2.deepl.com/jsonrpc"
|
urlFull := "https://www2.deepl.com/jsonrpc"
|
||||||
postStr := formatPostString(postData)
|
|
||||||
|
|
||||||
// Create a new req client
|
// Create a new req client
|
||||||
client := req.C().SetTLSFingerprintRandomized()
|
client := req.C().SetTLSFingerprintRandomized()
|
||||||
|
|
||||||
// Set headers
|
// Set minimal headers like TypeScript version
|
||||||
headers := http.Header{
|
headers := http.Header{
|
||||||
"Content-Type": []string{"application/json"},
|
"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 != "" {
|
if dlSession != "" {
|
||||||
@ -77,17 +65,22 @@ func makeRequest(postData *PostData, proxyURL string, dlSession string) (gjson.R
|
|||||||
return gjson.Result{}, err
|
return gjson.Result{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for blocked status like TypeScript version
|
||||||
|
if resp.StatusCode == 429 {
|
||||||
|
return gjson.Result{}, fmt.Errorf("too many requests, your IP has been blocked by DeepL temporarily, please don't request it frequently in a short time")
|
||||||
|
}
|
||||||
|
|
||||||
var bodyReader io.Reader
|
var bodyReader io.Reader
|
||||||
contentEncoding := resp.Header.Get("Content-Encoding")
|
contentEncoding := resp.Header.Get("Content-Encoding")
|
||||||
switch contentEncoding {
|
switch contentEncoding {
|
||||||
case "br":
|
case "br":
|
||||||
bodyReader = brotli.NewReader(resp.Body)
|
bodyReader = brotli.NewReader(resp.Body)
|
||||||
case "gzip":
|
case "gzip":
|
||||||
bodyReader, err = gzip.NewReader(resp.Body) // Use gzip.NewReader
|
bodyReader, err = gzip.NewReader(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gjson.Result{}, fmt.Errorf("failed to create gzip reader: %w", err)
|
return gjson.Result{}, fmt.Errorf("failed to create gzip reader: %w", err)
|
||||||
}
|
}
|
||||||
case "deflate": // Less common, but good to handle
|
case "deflate":
|
||||||
bodyReader = flate.NewReader(resp.Body)
|
bodyReader = flate.NewReader(resp.Body)
|
||||||
default:
|
default:
|
||||||
bodyReader = resp.Body
|
bodyReader = resp.Body
|
||||||
@ -109,182 +102,86 @@ func TranslateByDeepLX(sourceLang, targetLang, text string, tagHandling string,
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if tagHandling == "" {
|
// Get detected language if source language is auto
|
||||||
tagHandling = "plaintext"
|
if sourceLang == "auto" || sourceLang == "" {
|
||||||
|
sourceLang = strings.ToUpper(whatlanggo.DetectLang(text).Iso6391())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split text by newlines and store them for later reconstruction
|
// Prepare translation request using new LMT_handle_texts method
|
||||||
textParts := strings.Split(text, "\n")
|
id := getRandomNumber()
|
||||||
var translatedParts []string
|
iCount := getICount(text)
|
||||||
var allAlternatives [][]string // Store alternatives for each part
|
timestamp := getTimeStamp(iCount)
|
||||||
|
|
||||||
for _, part := range textParts {
|
postData := &PostData{
|
||||||
if strings.TrimSpace(part) == "" {
|
Jsonrpc: "2.0",
|
||||||
translatedParts = append(translatedParts, "")
|
Method: "LMT_handle_texts",
|
||||||
allAlternatives = append(allAlternatives, []string{""})
|
ID: id,
|
||||||
continue
|
Params: Params{
|
||||||
}
|
Splitting: "newlines",
|
||||||
|
Lang: Lang{
|
||||||
// Get detected language if source language is auto
|
SourceLangUserSelected: sourceLang,
|
||||||
if sourceLang == "auto" || sourceLang == "" {
|
TargetLang: targetLang,
|
||||||
sourceLang = strings.ToUpper(whatlanggo.DetectLang(part).Iso6391())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare jobs from split result
|
|
||||||
var jobs []Job
|
|
||||||
|
|
||||||
jobs = append(jobs, Job{
|
|
||||||
Kind: "default",
|
|
||||||
PreferredNumBeams: 4,
|
|
||||||
RawEnContextBefore: []string{},
|
|
||||||
RawEnContextAfter: []string{},
|
|
||||||
Sentences: []Sentence{{
|
|
||||||
Prefix: "",
|
|
||||||
Text: text,
|
|
||||||
ID: 0,
|
|
||||||
}},
|
|
||||||
})
|
|
||||||
|
|
||||||
hasRegionalVariant := false
|
|
||||||
targetLangCode := targetLang
|
|
||||||
targetLangParts := strings.Split(targetLang, "-")
|
|
||||||
if len(targetLangParts) > 1 {
|
|
||||||
targetLangCode = targetLangParts[0]
|
|
||||||
hasRegionalVariant = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare translation request
|
|
||||||
id := getRandomNumber()
|
|
||||||
|
|
||||||
postData := &PostData{
|
|
||||||
Jsonrpc: "2.0",
|
|
||||||
Method: "LMT_handle_jobs",
|
|
||||||
ID: id,
|
|
||||||
Params: Params{
|
|
||||||
CommonJobParams: CommonJobParams{
|
|
||||||
Mode: "translate",
|
|
||||||
Formality: "undefined",
|
|
||||||
TranscribeAs: "romanize",
|
|
||||||
AdvancedMode: false,
|
|
||||||
TextType: tagHandling,
|
|
||||||
WasSpoken: false,
|
|
||||||
},
|
|
||||||
Lang: Lang{
|
|
||||||
SourceLangUserSelected: "auto",
|
|
||||||
TargetLang: strings.ToUpper(targetLangCode),
|
|
||||||
SourceLangComputed: strings.ToUpper(sourceLang),
|
|
||||||
},
|
|
||||||
Jobs: jobs,
|
|
||||||
Timestamp: getTimeStamp(getICount(part)),
|
|
||||||
},
|
},
|
||||||
}
|
Texts: []TextItem{{
|
||||||
|
Text: text,
|
||||||
if hasRegionalVariant {
|
RequestAlternatives: 3,
|
||||||
postData = &PostData{
|
}},
|
||||||
Jsonrpc: "2.0",
|
Timestamp: timestamp,
|
||||||
Method: "LMT_handle_jobs",
|
},
|
||||||
ID: id,
|
|
||||||
Params: Params{
|
|
||||||
CommonJobParams: CommonJobParams{
|
|
||||||
Mode: "translate",
|
|
||||||
Formality: "undefined",
|
|
||||||
TranscribeAs: "romanize",
|
|
||||||
AdvancedMode: false,
|
|
||||||
TextType: tagHandling,
|
|
||||||
WasSpoken: false,
|
|
||||||
RegionalVariant: targetLang,
|
|
||||||
},
|
|
||||||
Lang: Lang{
|
|
||||||
SourceLangUserSelected: "auto",
|
|
||||||
TargetLang: strings.ToUpper(targetLangCode),
|
|
||||||
SourceLangComputed: strings.ToUpper(sourceLang),
|
|
||||||
},
|
|
||||||
Jobs: jobs,
|
|
||||||
Timestamp: getTimeStamp(getICount(part)),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make translation request
|
|
||||||
result, err := makeRequest(postData, proxyURL, dlSession)
|
|
||||||
if err != nil {
|
|
||||||
return DeepLXTranslationResult{
|
|
||||||
Code: http.StatusServiceUnavailable,
|
|
||||||
Message: err.Error(),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process translation results
|
|
||||||
var partTranslation string
|
|
||||||
var partAlternatives []string
|
|
||||||
|
|
||||||
translations := result.Get("result.translations").Array()
|
|
||||||
if len(translations) > 0 {
|
|
||||||
// Process main translation
|
|
||||||
for _, translation := range translations {
|
|
||||||
partTranslation += translation.Get("beams.0.sentences.0.text").String() + " "
|
|
||||||
}
|
|
||||||
partTranslation = strings.TrimSpace(partTranslation)
|
|
||||||
|
|
||||||
// Process alternatives
|
|
||||||
numBeams := len(translations[0].Get("beams").Array())
|
|
||||||
for i := 1; i < numBeams; i++ { // Start from 1 since 0 is the main translation
|
|
||||||
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 != "" {
|
|
||||||
partAlternatives = append(partAlternatives, strings.TrimSpace(altText))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if partTranslation == "" {
|
|
||||||
return DeepLXTranslationResult{
|
|
||||||
Code: http.StatusServiceUnavailable,
|
|
||||||
Message: "Translation failed",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
translatedParts = append(translatedParts, partTranslation)
|
|
||||||
allAlternatives = append(allAlternatives, partAlternatives)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Join all translated parts with newlines
|
// Format and apply body manipulation method like TypeScript
|
||||||
translatedText := strings.Join(translatedParts, "\n")
|
postStr := formatPostString(postData)
|
||||||
|
postStr = handlerBodyMethod(id, postStr)
|
||||||
|
|
||||||
// Combine alternatives with proper newline handling
|
// Make translation request
|
||||||
var combinedAlternatives []string
|
result, err := makeRequestWithBody(postStr, proxyURL, dlSession)
|
||||||
maxAlts := 0
|
if err != nil {
|
||||||
for _, alts := range allAlternatives {
|
return DeepLXTranslationResult{
|
||||||
if len(alts) > maxAlts {
|
Code: http.StatusServiceUnavailable,
|
||||||
maxAlts = len(alts)
|
Message: err.Error(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process translation results using new format
|
||||||
|
textsArray := result.Get("result.texts").Array()
|
||||||
|
if len(textsArray) == 0 {
|
||||||
|
return DeepLXTranslationResult{
|
||||||
|
Code: http.StatusServiceUnavailable,
|
||||||
|
Message: "Translation failed",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get main translation
|
||||||
|
mainText := textsArray[0].Get("text").String()
|
||||||
|
if mainText == "" {
|
||||||
|
return DeepLXTranslationResult{
|
||||||
|
Code: http.StatusServiceUnavailable,
|
||||||
|
Message: "Translation failed",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get alternatives
|
||||||
|
var alternatives []string
|
||||||
|
alternativesArray := textsArray[0].Get("alternatives").Array()
|
||||||
|
for _, alt := range alternativesArray {
|
||||||
|
altText := alt.Get("text").String()
|
||||||
|
if altText != "" {
|
||||||
|
alternatives = append(alternatives, altText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create combined alternatives preserving line structure
|
// Get detected source language from response
|
||||||
for i := 0; i < maxAlts; i++ {
|
detectedLang := result.Get("result.lang").String()
|
||||||
var altParts []string
|
if detectedLang != "" {
|
||||||
for j, alts := range allAlternatives {
|
sourceLang = detectedLang
|
||||||
if i < len(alts) {
|
|
||||||
altParts = append(altParts, alts[i])
|
|
||||||
} else if len(translatedParts[j]) == 0 {
|
|
||||||
altParts = append(altParts, "") // Keep empty lines
|
|
||||||
} else {
|
|
||||||
altParts = append(altParts, translatedParts[j]) // Use main translation if no alternative
|
|
||||||
}
|
|
||||||
}
|
|
||||||
combinedAlternatives = append(combinedAlternatives, strings.Join(altParts, "\n"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return DeepLXTranslationResult{
|
return DeepLXTranslationResult{
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
ID: getRandomNumber(), // Using new ID for the complete translation
|
ID: id,
|
||||||
Data: translatedText,
|
Data: mainText,
|
||||||
Alternatives: combinedAlternatives,
|
Alternatives: alternatives,
|
||||||
SourceLang: sourceLang,
|
SourceLang: sourceLang,
|
||||||
TargetLang: targetLang,
|
TargetLang: targetLang,
|
||||||
Method: map[bool]string{true: "Pro", false: "Free"}[dlSession != ""],
|
Method: map[bool]string{true: "Pro", false: "Free"}[dlSession != ""],
|
||||||
|
@ -46,8 +46,22 @@ type Job struct {
|
|||||||
Sentences []Sentence `json:"sentences"`
|
Sentences []Sentence `json:"sentences"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TextItem represents a text item for translation
|
||||||
|
type TextItem struct {
|
||||||
|
Text string `json:"text"`
|
||||||
|
RequestAlternatives int `json:"requestAlternatives"`
|
||||||
|
}
|
||||||
|
|
||||||
// Params represents parameters for translation requests
|
// Params represents parameters for translation requests
|
||||||
type Params struct {
|
type Params struct {
|
||||||
|
Splitting string `json:"splitting"`
|
||||||
|
Lang Lang `json:"lang"`
|
||||||
|
Texts []TextItem `json:"texts"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LegacyParams represents the old parameters structure for jobs (kept for compatibility)
|
||||||
|
type LegacyParams struct {
|
||||||
CommonJobParams CommonJobParams `json:"commonJobParams"`
|
CommonJobParams CommonJobParams `json:"commonJobParams"`
|
||||||
Lang Lang `json:"lang"`
|
Lang Lang `json:"lang"`
|
||||||
Jobs []Job `json:"jobs"`
|
Jobs []Job `json:"jobs"`
|
||||||
@ -62,8 +76,26 @@ type PostData struct {
|
|||||||
Params Params `json:"params"`
|
Params Params `json:"params"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TranslationResponse represents the response from translation
|
// TextResponse represents a single text response
|
||||||
|
type TextResponse struct {
|
||||||
|
Text string `json:"text"`
|
||||||
|
Alternatives []struct {
|
||||||
|
Text string `json:"text"`
|
||||||
|
} `json:"alternatives"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TranslationResponse represents the response from LMT_handle_texts
|
||||||
type TranslationResponse struct {
|
type TranslationResponse struct {
|
||||||
|
Jsonrpc string `json:"jsonrpc"`
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Result struct {
|
||||||
|
Lang string `json:"lang"`
|
||||||
|
Texts []TextResponse `json:"texts"`
|
||||||
|
} `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LegacyTranslationResponse represents the old response format (kept for compatibility)
|
||||||
|
type LegacyTranslationResponse struct {
|
||||||
Jsonrpc string `json:"jsonrpc"`
|
Jsonrpc string `json:"jsonrpc"`
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Result struct {
|
Result struct {
|
||||||
|
@ -28,7 +28,7 @@ func getICount(translateText string) int64 {
|
|||||||
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)
|
||||||
num := rng.Int63n(99999) + 8300000
|
num := rng.Int63n(99999) + 100000
|
||||||
return num * 1000
|
return num * 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ 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
|
||||||
}
|
}
|
||||||
return ts
|
return ts
|
||||||
}
|
}
|
||||||
@ -46,12 +46,14 @@ func getTimeStamp(iCount int64) int64 {
|
|||||||
func formatPostString(postData *PostData) string {
|
func formatPostString(postData *PostData) string {
|
||||||
postBytes, _ := json.Marshal(postData)
|
postBytes, _ := json.Marshal(postData)
|
||||||
postStr := string(postBytes)
|
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
|
return postStr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handlerBodyMethod manipulates the request body based on random number calculation
|
||||||
|
func handlerBodyMethod(random int64, body string) string {
|
||||||
|
calc := (random+5)%29 == 0 || (random+3)%13 == 0
|
||||||
|
if calc {
|
||||||
|
return strings.Replace(body, `"method":"`, `"method" : "`, 1)
|
||||||
|
}
|
||||||
|
return strings.Replace(body, `"method":"`, `"method": "`, 1)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user