mirror of
https://github.com/OwO-Network/DeepLX.git
synced 2025-04-18 13:43:24 +00:00
279 lines
8.2 KiB
Go
279 lines
8.2 KiB
Go
/*
|
|
* @Author: Vincent Young
|
|
* @Date: 2024-09-16 11:59:24
|
|
* @LastEditors: Vincent Yang
|
|
* @LastEditTime: 2024-11-01 13:12:25
|
|
* @FilePath: /DeepLX/translate/translate.go
|
|
* @Telegram: https://t.me/missuo
|
|
* @GitHub: https://github.com/missuo
|
|
*
|
|
* Copyright © 2024 by Vincent, All Rights Reserved.
|
|
*/
|
|
|
|
package translate
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/andybalholm/brotli"
|
|
"github.com/tidwall/gjson"
|
|
)
|
|
|
|
const baseURL = "https://www2.deepl.com/jsonrpc"
|
|
|
|
// 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)
|
|
|
|
postStr := formatPostString(postData)
|
|
req, err := http.NewRequest("POST", urlFull, bytes.NewReader([]byte(postStr)))
|
|
if err != nil {
|
|
return gjson.Result{}, err
|
|
}
|
|
|
|
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 {
|
|
req.Header = http.Header{
|
|
"Accept": []string{"*/*"},
|
|
"Accept-Language": []string{"en-US,en;q=0.9,zh-CN;q=0.8,zh-TW;q=0.7,zh-HK;q=0.6,zh;q=0.5"},
|
|
"Authorization": []string{"None"},
|
|
"Cache-Control": []string{"no-cache"},
|
|
"Content-Type": []string{"application/json"},
|
|
"DNT": []string{"1"},
|
|
"Origin": []string{"chrome-extension://cofdbpoegempjloogbagkncekinflcnj"},
|
|
"Pragma": []string{"no-cache"},
|
|
"Priority": []string{"u=1, i"},
|
|
"Referer": []string{"https://www.deepl.com/"},
|
|
"Sec-Fetch-Dest": []string{"empty"},
|
|
"Sec-Fetch-Mode": []string{"cors"},
|
|
"Sec-Fetch-Site": []string{"none"},
|
|
"Sec-GPC": []string{"1"},
|
|
"User-Agent": []string{"DeepLBrowserExtension/1.28.0 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36"},
|
|
}
|
|
}
|
|
|
|
// Setup client with proxy if provided
|
|
var client *http.Client
|
|
if proxyURL != "" {
|
|
proxy, err := url.Parse(proxyURL)
|
|
if err != nil {
|
|
return 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()
|
|
|
|
// Check status code before processing response
|
|
if resp.StatusCode != http.StatusOK {
|
|
switch resp.StatusCode {
|
|
case http.StatusTooManyRequests:
|
|
return gjson.Result{}, fmt.Errorf("too many requests")
|
|
case http.StatusUnauthorized:
|
|
return gjson.Result{}, fmt.Errorf("unauthorized")
|
|
case http.StatusForbidden:
|
|
return gjson.Result{}, fmt.Errorf("forbidden")
|
|
default:
|
|
return gjson.Result{}, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
|
}
|
|
}
|
|
|
|
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, 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)
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// Get detected language if source language is auto
|
|
if sourceLang == "auto" || sourceLang == "" {
|
|
sourceLang = strings.ToLower(splitResult.Get("result.lang.detected").String())
|
|
}
|
|
|
|
// 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()}
|
|
}
|
|
|
|
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,
|
|
}},
|
|
})
|
|
}
|
|
|
|
hasRegionalVariant := false
|
|
targetLangParts := strings.Split(targetLang, "-")
|
|
targetLangCode := targetLangParts[0]
|
|
if len(targetLangParts) > 1 {
|
|
hasRegionalVariant = true
|
|
}
|
|
|
|
// Prepare translation request
|
|
id := getRandomNumber()
|
|
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)),
|
|
},
|
|
}
|
|
|
|
// Make translation request
|
|
result, err := makeRequest(postData, "LMT_handle_jobs", proxyURL, dlSession)
|
|
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: map[bool]string{true: "Pro", false: "Free"}[dlSession != ""],
|
|
}, nil
|
|
}
|