mirror of
https://github.com/OwO-Network/DeepLX.git
synced 2024-11-16 02:32:20 +08:00
feat: support pro account (#105)
This commit is contained in:
parent
90d4d44fc0
commit
401b5cb117
|
@ -3,7 +3,10 @@
|
|||
FROM golang:1.22 AS builder
|
||||
WORKDIR /go/src/github.com/OwO-Network/DeepLX
|
||||
COPY main.go ./
|
||||
COPY dto.go ./
|
||||
COPY types.go ./
|
||||
COPY utils.go ./
|
||||
COPY config.go ./
|
||||
COPY translate.go ./
|
||||
COPY go.mod ./
|
||||
COPY go.sum ./
|
||||
RUN go get -d -v ./
|
||||
|
|
11
README.md
11
README.md
|
@ -1,8 +1,8 @@
|
|||
<!--
|
||||
* @Author: Vincent Young
|
||||
* @Date: 2022-10-18 07:32:29
|
||||
* @LastEditors: Vincent Young
|
||||
* @LastEditTime: 2024-04-16 15:10:38
|
||||
* @LastEditors: Vincent Yang
|
||||
* @LastEditTime: 2024-04-23 00:47:59
|
||||
* @FilePath: /DeepLX/README.md
|
||||
* @Telegram: https://t.me/missuo
|
||||
*
|
||||
|
@ -69,8 +69,10 @@
|
|||
- `-port` or `-p` : Listening port. Default is `1188`.
|
||||
- `-token` : Access token. If you have set it up, each request needs to include `Authorization` in the **Headers** or `token` parameter in the **URL Params**.
|
||||
- `-authkey` : DeepL Official `AuthKey`. If you have set it up, after the 429 response, the official AuthKey will be used for the request. If multiple authKeys are used simultaneously, they need to be separated by commas.
|
||||
- `s`: `dl-session` is the cookie for a **DeepL Pro** account. If you set this, you will be able to use another endpoint `/v1/translate`, which can effectively avoid 429.
|
||||
- `/v2/translate` : This endpoint is fully compatible with the DeepL official API. When using this endpoint, please strictly adhere to the request styles outlined in the official DeepL documentation. Note that in this endpoint, please use `DeepL-Auth-Key $token` in the `Authorization`, which is actually the Access Token, not the official `Auth Key` of DeepL.
|
||||
|
||||
|
||||
#### Example of requesting a token-protected `/v2/translate` endpoint
|
||||
```bash
|
||||
curl -X POST 'http://localhost:1188/v2/translate' \
|
||||
|
@ -113,13 +115,13 @@ curl -X POST http://localhost:1188/translate?token=your_access_token \
|
|||
docker run -itd -p 1188:1188 ghcr.io/owo-network/deeplx:latest
|
||||
|
||||
# custom environment variables
|
||||
docker run -itd -p 1188:1188 -e "TOKEN=helloxxx" -e "AUTHKEY=xxxx:fx" ghcr.io/owo-network/deeplx:latest
|
||||
docker run -itd -p 1188:1188 -e "TOKEN=helloxxx" -e "AUTHKEY=xxxx:fx" -e "DL_SESSION=xxxxx" ghcr.io/owo-network/deeplx:latest
|
||||
|
||||
# dockerhub
|
||||
docker run -itd -p 1188:1188 missuo/deeplx:latest
|
||||
|
||||
# custom environment variables
|
||||
docker run -itd -p 1188:1188 -e "TOKEN=helloxxx" -e "AUTHKEY=xxxx:fx" missuo/deeplx:latest
|
||||
docker run -itd -p 1188:1188 -e "TOKEN=helloxxx" -e "AUTHKEY=xxxx:fx" -e "DL_SESSION=xxxxx" missuo/deeplx:latest
|
||||
```
|
||||
|
||||
### Run with Docker Compose
|
||||
|
@ -131,6 +133,7 @@ wget https://raw.githubusercontent.com/OwO-Network/DeepLX/main/compose.yaml
|
|||
# environment:
|
||||
# - TOKEN=helloxxx
|
||||
# - AUTHKEY=xxxxxxx:fx
|
||||
# - DL_SESSION=xxxxx
|
||||
# docker compose
|
||||
docker compose up -d
|
||||
```
|
||||
|
|
|
@ -8,4 +8,5 @@ services:
|
|||
- "1188:1188"
|
||||
# environment:
|
||||
# - TOKEN=helloworld
|
||||
# - AUTHKEY=xxxxxxx:fx
|
||||
# - AUTHKEY=xxxxxxx:fx
|
||||
# - DL_SESSION=xxxxxx
|
55
config.go
Normal file
55
config.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* @Author: Vincent Yang
|
||||
* @Date: 2024-04-23 00:39:03
|
||||
* @LastEditors: Vincent Yang
|
||||
* @LastEditTime: 2024-04-23 00:43:43
|
||||
* @FilePath: /DeepLX/config.go
|
||||
* @Telegram: https://t.me/missuo
|
||||
* @GitHub: https://github.com/missuo
|
||||
*
|
||||
* Copyright © 2024 by Vincent, All Rights Reserved.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
)
|
||||
|
||||
func initConfig() *Config {
|
||||
cfg := &Config{
|
||||
Port: 1188,
|
||||
}
|
||||
|
||||
// Port flag
|
||||
flag.IntVar(&cfg.Port, "port", cfg.Port, "set up the port to listen on")
|
||||
flag.IntVar(&cfg.Port, "p", cfg.Port, "set up the port to listen on")
|
||||
|
||||
// DL Session flag
|
||||
flag.StringVar(&cfg.DlSession, "s", "", "set the dl-session for /v1/translate endpoint")
|
||||
if cfg.DlSession == "" {
|
||||
if dlSession, ok := os.LookupEnv("DL_SESSION"); ok {
|
||||
cfg.DlSession = dlSession
|
||||
}
|
||||
}
|
||||
|
||||
// Access token flag
|
||||
flag.StringVar(&cfg.Token, "token", "", "set the access token for /translate endpoint")
|
||||
if cfg.Token == "" {
|
||||
if token, ok := os.LookupEnv("TOKEN"); ok {
|
||||
cfg.Token = token
|
||||
}
|
||||
}
|
||||
|
||||
// DeepL Official Authentication key flag
|
||||
flag.StringVar(&cfg.AuthKey, "authkey", "", "The authentication key for DeepL API")
|
||||
if cfg.AuthKey == "" {
|
||||
if authKey, ok := os.LookupEnv("AUTHKEY"); ok {
|
||||
cfg.AuthKey = authKey
|
||||
}
|
||||
}
|
||||
|
||||
flag.Parse()
|
||||
return cfg
|
||||
}
|
371
main.go
371
main.go
|
@ -1,8 +1,8 @@
|
|||
/*
|
||||
* @Author: Vincent Yang
|
||||
* @Date: 2023-07-01 21:45:34
|
||||
* @LastEditors: Vincent Young
|
||||
* @LastEditTime: 2024-04-16 15:07:54
|
||||
* @LastEditors: Vincent Yang
|
||||
* @LastEditTime: 2024-04-23 00:42:28
|
||||
* @FilePath: /DeepLX/main.go
|
||||
* @Telegram: https://t.me/missuo
|
||||
* @GitHub: https://github.com/missuo
|
||||
|
@ -12,332 +12,16 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/abadojack/whatlanggo"
|
||||
"github.com/andybalholm/brotli"
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func initConfig() *Config {
|
||||
cfg := &Config{
|
||||
Port: 1188,
|
||||
}
|
||||
|
||||
flag.IntVar(&cfg.Port, "port", cfg.Port, "set up the port to listen on")
|
||||
flag.IntVar(&cfg.Port, "p", cfg.Port, "set up the port to listen on")
|
||||
|
||||
flag.StringVar(&cfg.Token, "token", "", "set the access token for /translate endpoint")
|
||||
if cfg.Token == "" {
|
||||
if token, ok := os.LookupEnv("TOKEN"); ok {
|
||||
cfg.Token = token
|
||||
}
|
||||
}
|
||||
|
||||
flag.StringVar(&cfg.AuthKey, "authkey", "", "The authentication key for DeepL API")
|
||||
if cfg.AuthKey == "" {
|
||||
if authKey, ok := os.LookupEnv("AUTHKEY"); ok {
|
||||
cfg.AuthKey = authKey
|
||||
}
|
||||
}
|
||||
|
||||
flag.Parse()
|
||||
return cfg
|
||||
}
|
||||
|
||||
func initDeepLXData(sourceLang string, targetLang string) *PostData {
|
||||
return &PostData{
|
||||
Jsonrpc: "2.0",
|
||||
Method: "LMT_handle_texts",
|
||||
Params: Params{
|
||||
Splitting: "newlines",
|
||||
Lang: Lang{
|
||||
SourceLangUserSelected: sourceLang,
|
||||
TargetLang: targetLang,
|
||||
},
|
||||
CommonJobParams: CommonJobParams{
|
||||
WasSpoken: false,
|
||||
TranscribeAS: "",
|
||||
// RegionalVariant: "en-US",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getICount(translateText string) int64 {
|
||||
return int64(strings.Count(translateText, "i"))
|
||||
}
|
||||
|
||||
func getRandomNumber() int64 {
|
||||
src := rand.NewSource(time.Now().UnixNano())
|
||||
rng := rand.New(src)
|
||||
num := rng.Int63n(99999) + 8300000
|
||||
return num * 1000
|
||||
}
|
||||
|
||||
func getTimeStamp(iCount int64) int64 {
|
||||
ts := time.Now().UnixMilli()
|
||||
if iCount != 0 {
|
||||
iCount = iCount + 1
|
||||
return ts - ts%iCount + iCount
|
||||
} else {
|
||||
return ts
|
||||
}
|
||||
}
|
||||
|
||||
func checkUsageAuthKey(authKey string) (bool, error) {
|
||||
url := "https://api-free.deepl.com/v2/usage"
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
req.Header.Add("Authorization", "DeepL-Auth-Key "+authKey)
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var response DeepLUsageResponse
|
||||
err = json.Unmarshal(body, &response)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return response.CharacterCount < 499900, nil
|
||||
}
|
||||
|
||||
func translateByOfficialAPI(text string, sourceLang string, targetLang string, authKey string) (string, error) {
|
||||
url := "https://api-free.deepl.com/v2/translate"
|
||||
textArray := strings.Split(text, "\n")
|
||||
|
||||
payload := PayloadAPI{
|
||||
Text: textArray,
|
||||
TargetLang: targetLang,
|
||||
SourceLang: sourceLang,
|
||||
}
|
||||
|
||||
payloadBytes, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(payloadBytes))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "DeepL-Auth-Key "+authKey)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Parsing the response
|
||||
var translationResponse TranslationResponse
|
||||
err = json.Unmarshal(body, &translationResponse)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Concatenating the translations
|
||||
var sb strings.Builder
|
||||
for _, translation := range translationResponse.Translations {
|
||||
sb.WriteString(translation.Text)
|
||||
}
|
||||
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
func translateByDeepLX(sourceLang string, targetLang string, translateText string, authKey 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
|
||||
url := "https://www2.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))
|
||||
|
||||
// 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", url, reader)
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return DeepLXTranslationResult{
|
||||
Code: http.StatusServiceUnavailable,
|
||||
Message: "Post request failed",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Setting HTTP headers to mimic a request from the DeepL iOS App
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
request.Header.Set("Accept", "*/*")
|
||||
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
|
||||
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)
|
||||
|
||||
// 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 && authKey != "" {
|
||||
authKeyArray := strings.Split(authKey, ",")
|
||||
for _, authKey := range authKeyArray {
|
||||
validity, err := checkUsageAuthKey(authKey)
|
||||
if err != nil {
|
||||
continue
|
||||
} else {
|
||||
if validity {
|
||||
translatedText, err := translateByOfficialAPI(translateText, sourceLang, targetLang, authKey)
|
||||
if err != nil {
|
||||
return DeepLXTranslationResult{
|
||||
Code: http.StatusTooManyRequests,
|
||||
Message: "Too Many Requests",
|
||||
}, nil
|
||||
}
|
||||
return DeepLXTranslationResult{
|
||||
Code: http.StatusOK,
|
||||
Message: "Success",
|
||||
ID: 1000000,
|
||||
Data: translatedText,
|
||||
SourceLang: sourceLang,
|
||||
TargetLang: targetLang,
|
||||
Method: "Official API",
|
||||
}, 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: "Free",
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
return DeepLXTranslationResult{
|
||||
Code: http.StatusServiceUnavailable,
|
||||
Message: "Uknown error",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func authMiddleware(cfg *Config) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if cfg.Token != "" {
|
||||
|
@ -398,7 +82,7 @@ func main() {
|
|||
})
|
||||
})
|
||||
|
||||
// Defining the translation endpoint which receives translation requests and returns translations
|
||||
// Free API endpoint, No Pro Account required
|
||||
r.POST("/translate", authMiddleware(cfg), func(c *gin.Context) {
|
||||
req := PayloadFree{}
|
||||
c.BindJSON(&req)
|
||||
|
@ -432,6 +116,55 @@ func main() {
|
|||
}
|
||||
})
|
||||
|
||||
// Pro API endpoint, Pro Account required
|
||||
r.POST("/v1/translate", authMiddleware(cfg), func(c *gin.Context) {
|
||||
req := PayloadFree{}
|
||||
c.BindJSON(&req)
|
||||
|
||||
sourceLang := req.SourceLang
|
||||
targetLang := req.TargetLang
|
||||
translateText := req.TransText
|
||||
|
||||
dlSession := cfg.DlSession
|
||||
if dlSession == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": http.StatusUnauthorized,
|
||||
"message": "No dl_session Found",
|
||||
})
|
||||
return
|
||||
} else if strings.Contains(dlSession, ".") {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": http.StatusUnauthorized,
|
||||
"message": "Your account is not a Pro account. Please upgrade your account or switch to a different account.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
result, err := translateByDeepLXPro(sourceLang, targetLang, translateText, dlSession)
|
||||
if err != nil {
|
||||
log.Fatalf("Translation failed: %s", err)
|
||||
}
|
||||
|
||||
if result.Code == http.StatusOK {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": http.StatusOK,
|
||||
"id": result.ID,
|
||||
"data": result.Data,
|
||||
"alternatives": result.Alternatives,
|
||||
"source_lang": result.SourceLang,
|
||||
"target_lang": result.TargetLang,
|
||||
"method": result.Method,
|
||||
})
|
||||
} else {
|
||||
c.JSON(result.Code, gin.H{
|
||||
"code": result.Code,
|
||||
"message": result.Message,
|
||||
})
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
// Free API endpoint, Consistent with the official API format
|
||||
r.POST("/v2/translate", authMiddleware(cfg), func(c *gin.Context) {
|
||||
authorizationHeader := c.GetHeader("Authorization")
|
||||
var authKey string
|
||||
|
|
380
translate.go
Normal file
380
translate.go
Normal file
|
@ -0,0 +1,380 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/abadojack/whatlanggo"
|
||||
"github.com/andybalholm/brotli"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func initDeepLXData(sourceLang string, targetLang string) *PostData {
|
||||
return &PostData{
|
||||
Jsonrpc: "2.0",
|
||||
Method: "LMT_handle_texts",
|
||||
Params: Params{
|
||||
Splitting: "newlines",
|
||||
Lang: Lang{
|
||||
SourceLangUserSelected: sourceLang,
|
||||
TargetLang: targetLang,
|
||||
},
|
||||
CommonJobParams: CommonJobParams{
|
||||
WasSpoken: false,
|
||||
TranscribeAS: "",
|
||||
// RegionalVariant: "en-US",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func translateByOfficialAPI(text string, sourceLang string, targetLang string, authKey string) (string, error) {
|
||||
url := "https://api-free.deepl.com/v2/translate"
|
||||
textArray := strings.Split(text, "\n")
|
||||
|
||||
payload := PayloadAPI{
|
||||
Text: textArray,
|
||||
TargetLang: targetLang,
|
||||
SourceLang: sourceLang,
|
||||
}
|
||||
|
||||
payloadBytes, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(payloadBytes))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "DeepL-Auth-Key "+authKey)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Parsing the response
|
||||
var translationResponse TranslationResponse
|
||||
err = json.Unmarshal(body, &translationResponse)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Concatenating the translations
|
||||
var sb strings.Builder
|
||||
for _, translation := range translationResponse.Translations {
|
||||
sb.WriteString(translation.Text)
|
||||
}
|
||||
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
func translateByDeepLX(sourceLang string, targetLang string, translateText string, authKey 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
|
||||
url := "https://www2.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))
|
||||
|
||||
// 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", url, reader)
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return DeepLXTranslationResult{
|
||||
Code: http.StatusServiceUnavailable,
|
||||
Message: "Post request failed",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Setting HTTP headers to mimic a request from the DeepL iOS App
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
request.Header.Set("Accept", "*/*")
|
||||
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
|
||||
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)
|
||||
|
||||
// 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 && authKey != "" {
|
||||
authKeyArray := strings.Split(authKey, ",")
|
||||
for _, authKey := range authKeyArray {
|
||||
validity, err := checkUsageAuthKey(authKey)
|
||||
if err != nil {
|
||||
continue
|
||||
} else {
|
||||
if validity {
|
||||
translatedText, err := translateByOfficialAPI(translateText, sourceLang, targetLang, authKey)
|
||||
if err != nil {
|
||||
return DeepLXTranslationResult{
|
||||
Code: http.StatusTooManyRequests,
|
||||
Message: "Too Many Requests",
|
||||
}, nil
|
||||
}
|
||||
return DeepLXTranslationResult{
|
||||
Code: http.StatusOK,
|
||||
Message: "Success",
|
||||
ID: 1000000,
|
||||
Data: translatedText,
|
||||
SourceLang: sourceLang,
|
||||
TargetLang: targetLang,
|
||||
Method: "Official API",
|
||||
}, 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: "Free",
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
return DeepLXTranslationResult{
|
||||
Code: http.StatusServiceUnavailable,
|
||||
Message: "Uknown error",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func translateByDeepLXPro(sourceLang string, targetLang string, translateText string, dlSession 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
|
||||
url := "https://www2.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))
|
||||
|
||||
// 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", url, 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", "dlsession="+dlSession)
|
||||
|
||||
// Making the HTTP request to the DeepL API
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,21 @@
|
|||
/*
|
||||
* @Author: Vincent Yang
|
||||
* @Date: 2024-03-20 15:43:57
|
||||
* @LastEditors: Vincent Yang
|
||||
* @LastEditTime: 2024-04-23 00:37:39
|
||||
* @FilePath: /DeepLX/types.go
|
||||
* @Telegram: https://t.me/missuo
|
||||
* @GitHub: https://github.com/missuo
|
||||
*
|
||||
* Copyright © 2024 by Vincent, All Rights Reserved.
|
||||
*/
|
||||
package main
|
||||
|
||||
type Config struct {
|
||||
Port int
|
||||
Token string
|
||||
AuthKey string
|
||||
Port int
|
||||
Token string
|
||||
AuthKey string
|
||||
DlSession string
|
||||
}
|
||||
|
||||
type Lang struct {
|
72
utils.go
Normal file
72
utils.go
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* @Author: Vincent Yang
|
||||
* @Date: 2024-04-23 00:17:27
|
||||
* @LastEditors: Vincent Yang
|
||||
* @LastEditTime: 2024-04-23 00:17:29
|
||||
* @FilePath: /DeepLX/utils.go
|
||||
* @Telegram: https://t.me/missuo
|
||||
* @GitHub: https://github.com/missuo
|
||||
*
|
||||
* Copyright © 2024 by Vincent, All Rights Reserved.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func getICount(translateText string) int64 {
|
||||
return int64(strings.Count(translateText, "i"))
|
||||
}
|
||||
|
||||
func getRandomNumber() int64 {
|
||||
src := rand.NewSource(time.Now().UnixNano())
|
||||
rng := rand.New(src)
|
||||
num := rng.Int63n(99999) + 8300000
|
||||
return num * 1000
|
||||
}
|
||||
|
||||
func getTimeStamp(iCount int64) int64 {
|
||||
ts := time.Now().UnixMilli()
|
||||
if iCount != 0 {
|
||||
iCount = iCount + 1
|
||||
return ts - ts%iCount + iCount
|
||||
} else {
|
||||
return ts
|
||||
}
|
||||
}
|
||||
|
||||
func checkUsageAuthKey(authKey string) (bool, error) {
|
||||
url := "https://api-free.deepl.com/v2/usage"
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
req.Header.Add("Authorization", "DeepL-Auth-Key "+authKey)
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var response DeepLUsageResponse
|
||||
err = json.Unmarshal(body, &response)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return response.CharacterCount < 499900, nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user