mirror of
https://github.com/OwO-Network/DeepLX.git
synced 2026-06-11 15:28:50 +00:00
feat(translate): migrate to oneshot endpoint to bypass www2 anti-bot
The www2.deepl.com/jsonrpc backends behind LMT_handle_texts / LMT_handle_jobs now sit behind aggressive WAF + per-IP throttling that returns HTTP 429 (code 1042911 "Too many requests") within a handful of calls from any single host — making the free path effectively unusable. The official DeepL browser extension and iOS app skip that backend entirely for stateless single-shot translation and POST to a separate "oneshot" endpoint on a different host pool with its own (much looser) rate limit. It accepts anonymous traffic with a literal `Authorization: None` header, returns plain JSON, and supports the same language pairs. Switch the free path to: POST https://oneshot-free.www.deepl.com/v1/translate Authorization: None {"text": ["..."], "target_lang": "de", "source_lang": "en"} Pro users continue to hit oneshot-pro.www.deepl.com with their bearer token (the `-s` flag now carries an OAuth access token rather than the legacy dl_session cookie). This removes: - the JSON-RPC envelope (jsonrpc/method/id/params/timestamp wrapper) - the `i`-count timestamp trick (getICount + getTimeStamp) - the random-id body-spacing trick (handlerBodyMethod) - the whatlanggo client-side detection (oneshot detects server-side) The DeepLXTranslationResult contract is unchanged for service handlers; Alternatives is now always nil because the oneshot endpoint does not return alternative translations. Verified against /translate, /v1/translate and /v2/translate routes end-to-end (EN/DE/ZH/JA/FR pairs, multi-sentence input, autodetect, 10x burst) — all 200 OK on an IP that was concurrently being 429'd by www2.
This commit is contained in:
parent
8d145722eb
commit
1fa6d7a2e3
3
go.mod
3
go.mod
@ -3,8 +3,6 @@ module github.com/OwO-Network/DeepLX
|
|||||||
go 1.25.0
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/abadojack/whatlanggo v1.0.1
|
|
||||||
github.com/andybalholm/brotli v1.2.0
|
|
||||||
github.com/gin-contrib/cors v1.7.6
|
github.com/gin-contrib/cors v1.7.6
|
||||||
github.com/gin-gonic/gin v1.11.0
|
github.com/gin-gonic/gin v1.11.0
|
||||||
github.com/imroc/req/v3 v3.57.0
|
github.com/imroc/req/v3 v3.57.0
|
||||||
@ -12,6 +10,7 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||||
github.com/bytedance/sonic v1.15.0 // indirect
|
github.com/bytedance/sonic v1.15.0 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@ -1,5 +1,3 @@
|
|||||||
github.com/abadojack/whatlanggo v1.0.1 h1:19N6YogDnf71CTHm3Mp2qhYfkRdyvbgwWdd2EPxJRG4=
|
|
||||||
github.com/abadojack/whatlanggo v1.0.1/go.mod h1:66WiQbSbJBIlOZMsvbKe5m6pzQovxCH9B/K8tQB2uoc=
|
|
||||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||||
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||||
|
|||||||
@ -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-07-13 23:09:49
|
* @LastEditTime: 2026-05-22 00:00:00
|
||||||
* @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,100 +14,104 @@ package translate
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"compress/flate"
|
"encoding/json"
|
||||||
"compress/gzip"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/abadojack/whatlanggo"
|
|
||||||
"github.com/imroc/req/v3"
|
"github.com/imroc/req/v3"
|
||||||
|
|
||||||
"github.com/andybalholm/brotli"
|
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
// makeRequestWithBody makes an HTTP request with pre-formatted body using minimal headers
|
// DeepL's web frontend retired LMT_handle_jobs/LMT_handle_texts on www2.deepl.com
|
||||||
func makeRequestWithBody(postStr string, proxyURL string, dlSession string) (gjson.Result, error) {
|
// for the interactive translator (now a SignalR/WebSocket channel). The browser
|
||||||
urlFull := "https://www2.deepl.com/jsonrpc"
|
// extension and iOS app still use a stateless REST endpoint called "oneshot",
|
||||||
|
// which is what we target here. It accepts anonymous traffic with a literal
|
||||||
|
// `Authorization: None` header and lives on a separate rate-limit pool from
|
||||||
|
// the JSON-RPC backends, so it is far less prone to "Too many requests" 429s.
|
||||||
|
const (
|
||||||
|
oneshotFreeEndpoint = "https://oneshot-free.www.deepl.com/v1/translate"
|
||||||
|
oneshotProEndpoint = "https://oneshot-pro.www.deepl.com/v1/translate"
|
||||||
|
)
|
||||||
|
|
||||||
// Create a new req client
|
// oneshot uses lowercase, BCP-47-ish language codes (de, en-US, zh-Hans).
|
||||||
client := req.C().SetTLSFingerprintRandomized()
|
// Callers historically pass DeepL's uppercase codes (DE, EN, ZH) — translate
|
||||||
|
// them here. Unknown codes fall through lowercased so future additions still
|
||||||
|
// work without a code change.
|
||||||
|
var langCodeToOneshot = map[string]string{
|
||||||
|
"AR": "ar", "BG": "bg", "CS": "cs", "DA": "da", "DE": "de", "EL": "el",
|
||||||
|
"EN": "en-US", "EN-GB": "en-GB", "EN-US": "en-US",
|
||||||
|
"ES": "es", "ET": "et", "FI": "fi", "FR": "fr", "HU": "hu",
|
||||||
|
"ID": "id", "IT": "it", "JA": "ja", "KO": "ko", "LT": "lt", "LV": "lv",
|
||||||
|
"NB": "nb", "NL": "nl", "PL": "pl",
|
||||||
|
"PT": "pt-BR", "PT-BR": "pt-BR", "PT-PT": "pt-PT",
|
||||||
|
"RO": "ro", "RU": "ru", "SK": "sk", "SL": "sl", "SV": "sv",
|
||||||
|
"TR": "tr", "UK": "uk",
|
||||||
|
"ZH": "zh-Hans", "ZH-HANS": "zh-Hans", "ZH-HANT": "zh-Hant",
|
||||||
|
}
|
||||||
|
|
||||||
// Set headers to simulate browser request
|
func toOneshotLang(code string) string {
|
||||||
headers := http.Header{
|
if v, ok := langCodeToOneshot[strings.ToUpper(code)]; ok {
|
||||||
"Content-Type": []string{"application/json"},
|
return v
|
||||||
"Accept": []string{"*/*"},
|
|
||||||
"Accept-Language": []string{"en-US,en;q=0.9"},
|
|
||||||
"Accept-Encoding": []string{"gzip, deflate, br, zstd"},
|
|
||||||
"Origin": []string{"https://www.deepl.com"},
|
|
||||||
"Referer": []string{"https://www.deepl.com/"},
|
|
||||||
"Sec-Fetch-Dest": []string{"empty"},
|
|
||||||
"Sec-Fetch-Mode": []string{"cors"},
|
|
||||||
"Sec-Fetch-Site": []string{"same-site"},
|
|
||||||
"User-Agent": []string{"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0"},
|
|
||||||
}
|
}
|
||||||
|
return strings.ToLower(code)
|
||||||
|
}
|
||||||
|
|
||||||
if dlSession != "" {
|
// oneshotResponse is the JSON shape returned by /v1/translate.
|
||||||
headers.Set("Cookie", "dl_session="+dlSession)
|
type oneshotResponse struct {
|
||||||
}
|
Translations []struct {
|
||||||
|
DetectedSourceLanguage string `json:"detected_source_language"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
} `json:"translations"`
|
||||||
|
}
|
||||||
|
|
||||||
// Set proxy if provided
|
// callOneshot POSTs the prepared body and returns the parsed JSON.
|
||||||
|
// `bearerToken` is empty for anonymous (free) requests, in which case the
|
||||||
|
// extension sends the literal string "None" — replicate that exactly, because
|
||||||
|
// omitting the header changes the server's auth-handling branch.
|
||||||
|
func callOneshot(endpoint string, body []byte, bearerToken, proxyURL string) (gjson.Result, int, error) {
|
||||||
|
client := req.C().ImpersonateChrome()
|
||||||
if proxyURL != "" {
|
if proxyURL != "" {
|
||||||
proxy, err := url.Parse(proxyURL)
|
proxy, err := url.Parse(proxyURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gjson.Result{}, err
|
return gjson.Result{}, 0, err
|
||||||
}
|
}
|
||||||
client.SetProxyURL(proxy.String())
|
client.SetProxyURL(proxy.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make the request
|
authValue := "None"
|
||||||
r := client.R()
|
if bearerToken != "" {
|
||||||
r.Headers = headers
|
authValue = "Bearer " + bearerToken
|
||||||
resp, err := r.
|
}
|
||||||
SetBody(bytes.NewReader([]byte(postStr))).
|
|
||||||
Post(urlFull)
|
|
||||||
|
|
||||||
|
resp, err := client.R().
|
||||||
|
SetHeader("Content-Type", "application/json").
|
||||||
|
SetHeader("Accept", "*/*").
|
||||||
|
SetHeader("Authorization", authValue).
|
||||||
|
SetHeader("Origin", "https://www.deepl.com").
|
||||||
|
SetHeader("Referer", "https://www.deepl.com/").
|
||||||
|
SetHeader("Sec-Fetch-Site", "same-site").
|
||||||
|
SetHeader("Sec-Fetch-Mode", "cors").
|
||||||
|
SetHeader("Sec-Fetch-Dest", "empty").
|
||||||
|
SetBody(bytes.NewReader(body)).
|
||||||
|
Post(endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gjson.Result{}, err
|
return gjson.Result{}, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for blocked status like TypeScript version
|
raw, err := resp.ToBytes()
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for other error status codes
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
return gjson.Result{}, fmt.Errorf("request failed with status code: %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
if err != nil {
|
|
||||||
return gjson.Result{}, fmt.Errorf("failed to create gzip reader: %w", err)
|
|
||||||
}
|
|
||||||
case "deflate":
|
|
||||||
bodyReader = flate.NewReader(resp.Body)
|
|
||||||
default:
|
|
||||||
bodyReader = resp.Body
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := io.ReadAll(bodyReader)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gjson.Result{}, fmt.Errorf("failed to read response body: %w", err)
|
return gjson.Result{}, resp.StatusCode, fmt.Errorf("failed to read response body: %w", err)
|
||||||
}
|
}
|
||||||
return gjson.ParseBytes(body), nil
|
return gjson.ParseBytes(raw), resp.StatusCode, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TranslateByDeepLX performs translation using DeepL API
|
// TranslateByDeepLX performs translation via DeepL's oneshot endpoint.
|
||||||
|
// Passing dlSession switches to the Pro endpoint; the value is sent verbatim
|
||||||
|
// as the Bearer token, so callers must supply an OAuth access token (not the
|
||||||
|
// legacy `dl_session` cookie) when using Pro.
|
||||||
func TranslateByDeepLX(sourceLang, targetLang, text string, tagHandling string, proxyURL string, dlSession string) (DeepLXTranslationResult, error) {
|
func TranslateByDeepLX(sourceLang, targetLang, text string, tagHandling string, proxyURL string, dlSession string) (DeepLXTranslationResult, error) {
|
||||||
if text == "" {
|
if text == "" {
|
||||||
return DeepLXTranslationResult{
|
return DeepLXTranslationResult{
|
||||||
@ -116,86 +120,76 @@ func TranslateByDeepLX(sourceLang, targetLang, text string, tagHandling string,
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get detected language if source language is auto
|
reqBody := map[string]any{
|
||||||
if sourceLang == "auto" || sourceLang == "" {
|
"text": []string{text},
|
||||||
sourceLang = strings.ToUpper(whatlanggo.DetectLang(text).Iso6391())
|
"target_lang": toOneshotLang(targetLang),
|
||||||
|
}
|
||||||
|
if sourceLang != "" && !strings.EqualFold(sourceLang, "auto") {
|
||||||
|
reqBody["source_lang"] = toOneshotLang(sourceLang)
|
||||||
|
}
|
||||||
|
bodyBytes, _ := json.Marshal(reqBody)
|
||||||
|
|
||||||
|
endpoint := oneshotFreeEndpoint
|
||||||
|
if dlSession != "" {
|
||||||
|
endpoint = oneshotProEndpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare translation request using new LMT_handle_texts method
|
id := time.Now().UnixMilli()
|
||||||
id := getRandomNumber()
|
result, status, err := callOneshot(endpoint, bodyBytes, dlSession, proxyURL)
|
||||||
iCount := getICount(text)
|
|
||||||
timestamp := getTimeStamp(iCount)
|
|
||||||
|
|
||||||
postData := &PostData{
|
|
||||||
Jsonrpc: "2.0",
|
|
||||||
Method: "LMT_handle_texts",
|
|
||||||
ID: id,
|
|
||||||
Params: Params{
|
|
||||||
Splitting: "newlines",
|
|
||||||
Lang: Lang{
|
|
||||||
SourceLangUserSelected: sourceLang,
|
|
||||||
TargetLang: targetLang,
|
|
||||||
},
|
|
||||||
Texts: []TextItem{{
|
|
||||||
Text: text,
|
|
||||||
RequestAlternatives: 3,
|
|
||||||
}},
|
|
||||||
Timestamp: timestamp,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format and apply body manipulation method like TypeScript
|
|
||||||
postStr := formatPostString(postData)
|
|
||||||
postStr = handlerBodyMethod(id, postStr)
|
|
||||||
|
|
||||||
// Make translation request
|
|
||||||
result, err := makeRequestWithBody(postStr, proxyURL, dlSession)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DeepLXTranslationResult{
|
return DeepLXTranslationResult{
|
||||||
|
ID: id,
|
||||||
Code: http.StatusServiceUnavailable,
|
Code: http.StatusServiceUnavailable,
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process translation results using new format
|
switch status {
|
||||||
textsArray := result.Get("result.texts").Array()
|
case http.StatusOK:
|
||||||
if len(textsArray) == 0 {
|
// fall through to body parsing
|
||||||
|
case http.StatusTooManyRequests:
|
||||||
return DeepLXTranslationResult{
|
return DeepLXTranslationResult{
|
||||||
|
ID: id,
|
||||||
|
Code: http.StatusTooManyRequests,
|
||||||
|
Message: "too many requests, your IP has been blocked by DeepL temporarily, please don't request it frequently in a short time",
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
return DeepLXTranslationResult{
|
||||||
|
ID: id,
|
||||||
|
Code: http.StatusServiceUnavailable,
|
||||||
|
Message: fmt.Sprintf("request failed with status code: %d", status),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
translations := result.Get("translations").Array()
|
||||||
|
if len(translations) == 0 {
|
||||||
|
return DeepLXTranslationResult{
|
||||||
|
ID: id,
|
||||||
Code: http.StatusServiceUnavailable,
|
Code: http.StatusServiceUnavailable,
|
||||||
Message: "Translation failed",
|
Message: "Translation failed",
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get main translation
|
mainText := translations[0].Get("text").String()
|
||||||
mainText := textsArray[0].Get("text").String()
|
|
||||||
if mainText == "" {
|
if mainText == "" {
|
||||||
return DeepLXTranslationResult{
|
return DeepLXTranslationResult{
|
||||||
|
ID: id,
|
||||||
Code: http.StatusServiceUnavailable,
|
Code: http.StatusServiceUnavailable,
|
||||||
Message: "Translation failed",
|
Message: "Translation failed",
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get alternatives
|
detected := translations[0].Get("detected_source_language").String()
|
||||||
var alternatives []string
|
if detected != "" {
|
||||||
alternativesArray := textsArray[0].Get("alternatives").Array()
|
// Normalize back to DeepL-style uppercase for response continuity.
|
||||||
for _, alt := range alternativesArray {
|
sourceLang = strings.ToUpper(detected)
|
||||||
altText := alt.Get("text").String()
|
|
||||||
if altText != "" {
|
|
||||||
alternatives = append(alternatives, altText)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get detected source language from response
|
|
||||||
detectedLang := result.Get("result.lang").String()
|
|
||||||
if detectedLang != "" {
|
|
||||||
sourceLang = detectedLang
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return DeepLXTranslationResult{
|
return DeepLXTranslationResult{
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
ID: id,
|
ID: id,
|
||||||
Data: mainText,
|
Data: mainText,
|
||||||
Alternatives: alternatives,
|
Alternatives: nil, // oneshot does not return 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 != ""],
|
||||||
|
|||||||
@ -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-03-01 04:16:07
|
* @LastEditTime: 2026-05-22 00:00:00
|
||||||
* @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,123 +12,16 @@
|
|||||||
|
|
||||||
package translate
|
package translate
|
||||||
|
|
||||||
// Lang represents the language settings for translation
|
// DeepLXTranslationResult is the public response shape consumed by the HTTP
|
||||||
type Lang struct {
|
// handlers in the service package. The structure predates the migration to
|
||||||
SourceLangUserSelected string `json:"source_lang_user_selected"` // Can be "auto"
|
// the oneshot endpoint; Alternatives is now always empty because oneshot does
|
||||||
TargetLang string `json:"target_lang"`
|
// not return alternative translations, and ID is synthesized from time.
|
||||||
SourceLangComputed string `json:"source_lang_computed,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CommonJobParams represents common parameters for translation jobs
|
|
||||||
type CommonJobParams struct {
|
|
||||||
Formality string `json:"formality"` // Can be "undefined"
|
|
||||||
TranscribeAs string `json:"transcribe_as"`
|
|
||||||
Mode string `json:"mode"`
|
|
||||||
WasSpoken bool `json:"wasSpoken"`
|
|
||||||
AdvancedMode bool `json:"advancedMode"`
|
|
||||||
TextType string `json:"textType"`
|
|
||||||
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"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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"`
|
|
||||||
Timestamp int64 `json:"timestamp"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PostData represents the complete translation request
|
|
||||||
type PostData struct {
|
|
||||||
Jsonrpc string `json:"jsonrpc"`
|
|
||||||
Method string `json:"method"`
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
Params Params `json:"params"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
Translations []struct {
|
|
||||||
Beams []struct {
|
|
||||||
Sentences []SentenceResponse `json:"sentences"`
|
|
||||||
NumSymbols int `json:"num_symbols"`
|
|
||||||
RephraseVariant struct { // Added rephrase_variant
|
|
||||||
Name string `json:"name"`
|
|
||||||
} `json:"rephrase_variant"`
|
|
||||||
} `json:"beams"`
|
|
||||||
Quality string `json:"quality"` // Added quality
|
|
||||||
} `json:"translations"`
|
|
||||||
TargetLang string `json:"target_lang"`
|
|
||||||
SourceLang string `json:"source_lang"`
|
|
||||||
SourceLangIsConfident bool `json:"source_lang_is_confident"`
|
|
||||||
DetectedLanguages map[string]interface{} `json:"detectedLanguages"` // Use interface{} for now
|
|
||||||
} `json:"result"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SentenceResponse is a helper struct for the response sentences
|
|
||||||
type SentenceResponse struct {
|
|
||||||
Text string `json:"text"`
|
|
||||||
IDS []int `json:"ids"` // Added IDS
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepLXTranslationResult represents the final translation result
|
|
||||||
type DeepLXTranslationResult struct {
|
type DeepLXTranslationResult struct {
|
||||||
Code int `json:"code"`
|
Code int `json:"code"`
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Message string `json:"message,omitempty"`
|
Message string `json:"message,omitempty"`
|
||||||
Data string `json:"data"` // The primary translated text
|
Data string `json:"data"`
|
||||||
Alternatives []string `json:"alternatives"` // Other possible translations
|
Alternatives []string `json:"alternatives"`
|
||||||
SourceLang string `json:"source_lang"`
|
SourceLang string `json:"source_lang"`
|
||||||
TargetLang string `json:"target_lang"`
|
TargetLang string `json:"target_lang"`
|
||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
|
|||||||
@ -1,59 +0,0 @@
|
|||||||
/*
|
|
||||||
* @Author: Vincent Young
|
|
||||||
* @Date: 2024-09-16 11:59:24
|
|
||||||
* @LastEditors: Vincent Yang
|
|
||||||
* @LastEditTime: 2025-04-08 14:27:21
|
|
||||||
* @FilePath: /DeepLX/translate/utils.go
|
|
||||||
* @Telegram: https://t.me/missuo
|
|
||||||
* @GitHub: https://github.com/missuo
|
|
||||||
*
|
|
||||||
* Copyright © 2024 by Vincent, All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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)
|
|
||||||
num := rng.Int63n(99999) + 100000
|
|
||||||
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)
|
|
||||||
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