mirror of
https://github.com/OwO-Network/DeepLX.git
synced 2025-07-24 19:33:24 +00:00
Compare commits
3 Commits
56637adc3c
...
72beefbebe
Author | SHA1 | Date | |
---|---|---|---|
|
72beefbebe | ||
|
2b0e1e3cbb | ||
|
8a605887ff |
2
go.mod
2
go.mod
@ -2,7 +2,7 @@ module github.com/OwO-Network/DeepLX
|
||||
|
||||
go 1.24.0
|
||||
|
||||
toolchain go1.24.2
|
||||
toolchain go1.24.5
|
||||
|
||||
require (
|
||||
github.com/abadojack/whatlanggo v1.0.1
|
||||
|
@ -2,7 +2,7 @@
|
||||
* @Author: Vincent Young
|
||||
* @Date: 2024-09-16 11:59:24
|
||||
* @LastEditors: Vincent Yang
|
||||
* @LastEditTime: 2025-04-08 14:26:33
|
||||
* @LastEditTime: 2025-07-13 23:09:49
|
||||
* @FilePath: /DeepLX/translate/translate.go
|
||||
* @Telegram: https://t.me/missuo
|
||||
* @GitHub: https://github.com/missuo
|
||||
@ -29,28 +29,16 @@ import (
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
// makeRequest makes an HTTP request to DeepL API
|
||||
func makeRequest(postData *PostData, proxyURL string, dlSession string) (gjson.Result, error) {
|
||||
// makeRequestWithBody makes an HTTP request with pre-formatted body using minimal headers
|
||||
func makeRequestWithBody(postStr string, proxyURL string, dlSession string) (gjson.Result, error) {
|
||||
urlFull := "https://www2.deepl.com/jsonrpc"
|
||||
postStr := formatPostString(postData)
|
||||
|
||||
// Create a new req client
|
||||
client := req.C().SetTLSFingerprintRandomized()
|
||||
|
||||
// Set headers
|
||||
// Set minimal headers like TypeScript version
|
||||
headers := http.Header{
|
||||
"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"},
|
||||
"Content-Type": []string{"application/json"},
|
||||
}
|
||||
|
||||
if dlSession != "" {
|
||||
@ -77,17 +65,22 @@ func makeRequest(postData *PostData, proxyURL string, dlSession string) (gjson.R
|
||||
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
|
||||
contentEncoding := resp.Header.Get("Content-Encoding")
|
||||
switch contentEncoding {
|
||||
case "br":
|
||||
bodyReader = brotli.NewReader(resp.Body)
|
||||
case "gzip":
|
||||
bodyReader, err = gzip.NewReader(resp.Body) // Use gzip.NewReader
|
||||
bodyReader, err = gzip.NewReader(resp.Body)
|
||||
if err != nil {
|
||||
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)
|
||||
default:
|
||||
bodyReader = resp.Body
|
||||
@ -109,182 +102,86 @@ func TranslateByDeepLX(sourceLang, targetLang, text string, tagHandling string,
|
||||
}, nil
|
||||
}
|
||||
|
||||
if tagHandling == "" {
|
||||
tagHandling = "plaintext"
|
||||
// Get detected language if source language is auto
|
||||
if sourceLang == "auto" || sourceLang == "" {
|
||||
sourceLang = strings.ToUpper(whatlanggo.DetectLang(text).Iso6391())
|
||||
}
|
||||
|
||||
// Split text by newlines and store them for later reconstruction
|
||||
textParts := strings.Split(text, "\n")
|
||||
var translatedParts []string
|
||||
var allAlternatives [][]string // Store alternatives for each part
|
||||
// Prepare translation request using new LMT_handle_texts method
|
||||
id := getRandomNumber()
|
||||
iCount := getICount(text)
|
||||
timestamp := getTimeStamp(iCount)
|
||||
|
||||
for _, part := range textParts {
|
||||
if strings.TrimSpace(part) == "" {
|
||||
translatedParts = append(translatedParts, "")
|
||||
allAlternatives = append(allAlternatives, []string{""})
|
||||
continue
|
||||
}
|
||||
|
||||
// Get detected language if source language is auto
|
||||
if sourceLang == "auto" || sourceLang == "" {
|
||||
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)),
|
||||
postData := &PostData{
|
||||
Jsonrpc: "2.0",
|
||||
Method: "LMT_handle_texts",
|
||||
ID: id,
|
||||
Params: Params{
|
||||
Splitting: "newlines",
|
||||
Lang: Lang{
|
||||
SourceLangUserSelected: sourceLang,
|
||||
TargetLang: targetLang,
|
||||
},
|
||||
}
|
||||
|
||||
if hasRegionalVariant {
|
||||
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,
|
||||
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)
|
||||
Texts: []TextItem{{
|
||||
Text: text,
|
||||
RequestAlternatives: 3,
|
||||
}},
|
||||
Timestamp: timestamp,
|
||||
},
|
||||
}
|
||||
|
||||
// Join all translated parts with newlines
|
||||
translatedText := strings.Join(translatedParts, "\n")
|
||||
// Format and apply body manipulation method like TypeScript
|
||||
postStr := formatPostString(postData)
|
||||
postStr = handlerBodyMethod(id, postStr)
|
||||
|
||||
// Combine alternatives with proper newline handling
|
||||
var combinedAlternatives []string
|
||||
maxAlts := 0
|
||||
for _, alts := range allAlternatives {
|
||||
if len(alts) > maxAlts {
|
||||
maxAlts = len(alts)
|
||||
// Make translation request
|
||||
result, err := makeRequestWithBody(postStr, proxyURL, dlSession)
|
||||
if err != nil {
|
||||
return DeepLXTranslationResult{
|
||||
Code: http.StatusServiceUnavailable,
|
||||
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
|
||||
for i := 0; i < maxAlts; i++ {
|
||||
var altParts []string
|
||||
for j, alts := range allAlternatives {
|
||||
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"))
|
||||
// Get detected source language from response
|
||||
detectedLang := result.Get("result.lang").String()
|
||||
if detectedLang != "" {
|
||||
sourceLang = detectedLang
|
||||
}
|
||||
|
||||
return DeepLXTranslationResult{
|
||||
Code: http.StatusOK,
|
||||
ID: getRandomNumber(), // Using new ID for the complete translation
|
||||
Data: translatedText,
|
||||
Alternatives: combinedAlternatives,
|
||||
ID: id,
|
||||
Data: mainText,
|
||||
Alternatives: alternatives,
|
||||
SourceLang: sourceLang,
|
||||
TargetLang: targetLang,
|
||||
Method: map[bool]string{true: "Pro", false: "Free"}[dlSession != ""],
|
||||
|
@ -46,8 +46,22 @@ type Job struct {
|
||||
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
|
||||
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"`
|
||||
Lang Lang `json:"lang"`
|
||||
Jobs []Job `json:"jobs"`
|
||||
@ -62,8 +76,26 @@ type PostData struct {
|
||||
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 {
|
||||
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"`
|
||||
ID int64 `json:"id"`
|
||||
Result struct {
|
||||
|
@ -28,7 +28,7 @@ func getICount(translateText string) int64 {
|
||||
func getRandomNumber() int64 {
|
||||
src := rand.NewSource(time.Now().UnixNano())
|
||||
rng := rand.New(src)
|
||||
num := rng.Int63n(99999) + 8300000
|
||||
num := rng.Int63n(99999) + 100000
|
||||
return num * 1000
|
||||
}
|
||||
|
||||
@ -37,7 +37,7 @@ func getTimeStamp(iCount int64) int64 {
|
||||
ts := time.Now().UnixMilli()
|
||||
if iCount != 0 {
|
||||
iCount = iCount + 1
|
||||
return ts - ts%iCount + iCount
|
||||
return ts - (ts % iCount) + iCount
|
||||
}
|
||||
return ts
|
||||
}
|
||||
@ -46,12 +46,14 @@ func getTimeStamp(iCount int64) int64 {
|
||||
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
|
||||
}
|
||||
|
||||
// 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