2023-07-03 00:50:58 +08:00
/ *
* @ Author : Vincent Young
* @ Date : 2023 - 07 - 01 21 : 45 : 34
* @ LastEditors : Vincent Young
2023-10-29 10:48:58 +08:00
* @ LastEditTime : 2023 - 10 - 28 22 : 42 : 0 8
2023-07-03 00:50:58 +08:00
* @ FilePath : / DeepLX / main . go
* @ Telegram : https : //t.me/missuo
*
* Copyright © 2023 by Vincent , All Rights Reserved .
* /
2022-10-17 13:51:22 +08:00
package main
import (
"bytes"
"encoding/json"
2023-04-23 13:25:47 +08:00
"flag"
2023-02-12 09:23:04 +08:00
"fmt"
2022-10-17 13:51:22 +08:00
"io"
"log"
"math/rand"
"net/http"
2023-07-01 21:45:00 +08:00
"os"
2022-10-17 13:51:22 +08:00
"strings"
"time"
2023-03-02 14:36:41 +08:00
"github.com/abadojack/whatlanggo"
2023-07-01 07:15:22 +08:00
"github.com/andybalholm/brotli"
2023-03-07 00:19:27 +08:00
"github.com/gin-contrib/cors"
2022-10-20 01:06:30 +08:00
"github.com/gin-gonic/gin"
2022-10-17 13:51:22 +08:00
"github.com/tidwall/gjson"
)
2023-04-23 13:25:47 +08:00
var port int
2023-10-29 10:48:58 +08:00
var token string
2023-04-23 13:25:47 +08:00
2023-02-25 22:03:51 +08:00
func init ( ) {
2023-04-23 13:25:47 +08:00
const (
defaultPort = 1188
usage = "set up the port to listen on"
)
flag . IntVar ( & port , "port" , defaultPort , usage )
flag . IntVar ( & port , "p" , defaultPort , usage )
2023-10-29 10:48:58 +08:00
flag . StringVar ( & token , "token" , "" , "set the access token for /translate endpoint" )
2023-04-23 13:25:47 +08:00
2023-02-25 22:03:51 +08:00
log . SetFlags ( log . LstdFlags | log . Lshortfile )
}
2022-10-17 13:51:22 +08:00
type Lang struct {
SourceLangUserSelected string ` json:"source_lang_user_selected" `
TargetLang string ` json:"target_lang" `
}
type CommonJobParams struct {
2023-02-18 20:14:02 +08:00
WasSpoken bool ` json:"wasSpoken" `
TranscribeAS string ` json:"transcribe_as" `
// RegionalVariant string `json:"regionalVariant"`
2022-10-17 13:51:22 +08:00
}
type Params struct {
2023-02-12 09:23:04 +08:00
Texts [ ] Text ` json:"texts" `
Splitting string ` json:"splitting" `
Lang Lang ` json:"lang" `
Timestamp int64 ` json:"timestamp" `
CommonJobParams CommonJobParams ` json:"commonJobParams" `
2022-10-17 13:51:22 +08:00
}
type Text struct {
Text string ` json:"text" `
RequestAlternatives int ` json:"requestAlternatives" `
}
type PostData struct {
Jsonrpc string ` json:"jsonrpc" `
Method string ` json:"method" `
ID int64 ` json:"id" `
Params Params ` json:"params" `
}
2023-02-25 22:03:51 +08:00
func initData ( sourceLang string , targetLang string ) * PostData {
2022-10-17 13:51:22 +08:00
return & PostData {
Jsonrpc : "2.0" ,
Method : "LMT_handle_texts" ,
Params : Params {
Splitting : "newlines" ,
Lang : Lang {
2023-02-25 22:03:51 +08:00
SourceLangUserSelected : sourceLang ,
TargetLang : targetLang ,
2022-10-17 13:51:22 +08:00
} ,
2023-02-12 09:23:04 +08:00
CommonJobParams : CommonJobParams {
2023-02-18 20:14:02 +08:00
WasSpoken : false ,
TranscribeAS : "" ,
// RegionalVariant: "en-US",
2023-02-12 09:23:04 +08:00
} ,
2022-10-17 13:51:22 +08:00
} ,
}
}
2023-02-25 22:03:51 +08:00
func getICount ( translateText string ) int64 {
return int64 ( strings . Count ( translateText , "i" ) )
2022-10-17 13:51:22 +08:00
}
func getRandomNumber ( ) int64 {
rand . Seed ( time . Now ( ) . Unix ( ) )
2023-02-12 09:23:04 +08:00
num := rand . Int63n ( 99999 ) + 8300000
2022-10-17 13:51:22 +08:00
return num * 1000
}
2023-02-25 22:03:51 +08:00
func getTimeStamp ( iCount int64 ) int64 {
2022-10-17 13:51:22 +08:00
ts := time . Now ( ) . UnixMilli ( )
2023-02-25 22:03:51 +08:00
if iCount != 0 {
iCount = iCount + 1
return ts - ts % iCount + iCount
2022-10-17 13:51:22 +08:00
} else {
return ts
}
}
2022-10-20 01:06:30 +08:00
type ResData struct {
2023-02-25 22:03:51 +08:00
TransText string ` json:"text" `
SourceLang string ` json:"source_lang" `
TargetLang string ` json:"target_lang" `
2022-10-20 01:06:30 +08:00
}
2022-10-17 13:51:22 +08:00
2022-10-20 01:06:30 +08:00
func main ( ) {
2023-09-15 01:36:36 +08:00
// Parsing the command-line flags
2023-04-23 13:25:47 +08:00
flag . Parse ( )
2023-09-15 01:36:36 +08:00
// Displaying initialization information
2023-04-23 13:25:47 +08:00
fmt . Printf ( "DeepL X has been successfully launched! Listening on 0.0.0.0:%v\n" , port )
2023-09-15 01:36:36 +08:00
fmt . Println ( "Developed by sjlleo <i@leo.moe> and missuo <me@missuo.me>." )
2023-02-12 09:23:04 +08:00
2023-10-29 10:48:58 +08:00
// Check if the token is set in the environment variable
if token == "" {
envToken , ok := os . LookupEnv ( "TOKEN" )
if ok {
token = envToken
fmt . Println ( "Access token is set from the environment variable." )
}
}
if token == "" {
fmt . Println ( "Access token is not set. You can set it using the -token flag or the TOKEN environment variable." )
} else {
fmt . Println ( "Access token is set. Use the Authorization: Bearer <token> header to access /translate." )
}
2023-09-15 01:36:36 +08:00
// Generating a random ID
2022-11-10 02:23:14 +08:00
id := getRandomNumber ( )
2023-02-12 09:23:04 +08:00
2023-09-15 01:36:36 +08:00
// Setting the application to release mode
2023-02-12 09:23:04 +08:00
gin . SetMode ( gin . ReleaseMode )
2022-10-20 01:06:30 +08:00
r := gin . Default ( )
2023-03-07 00:19:27 +08:00
r . Use ( cors . Default ( ) )
2022-10-20 01:06:30 +08:00
2023-09-15 01:36:36 +08:00
// Defining the root endpoint which returns the project details
2023-02-12 09:23:04 +08:00
r . GET ( "/" , func ( c * gin . Context ) {
2022-10-20 01:06:30 +08:00
c . JSON ( 200 , gin . H {
2023-02-24 21:37:01 +08:00
"code" : 200 ,
2023-09-15 01:36:36 +08:00
"message" : "DeepL Free API, Developed by sjlleo <i@leo.moe> and missuo <me@missuo.me>. Go to /translate with POST. http://github.com/OwO-Network/DeepLX" ,
2022-10-20 01:06:30 +08:00
} )
} )
2022-11-10 02:23:14 +08:00
2023-09-15 01:36:36 +08:00
// Defining the translation endpoint which receives translation requests and returns translations
2022-10-20 01:06:30 +08:00
r . POST ( "/translate" , func ( c * gin . Context ) {
reqj := ResData { }
c . BindJSON ( & reqj )
2023-02-25 22:03:51 +08:00
2023-10-29 10:48:58 +08:00
if token != "" {
providedToken := c . GetHeader ( "Authorization" )
if providedToken != "Bearer " + token {
c . JSON ( http . StatusUnauthorized , gin . H {
"code" : http . StatusUnauthorized ,
"message" : "Invalid access token" ,
} )
return
}
}
2023-09-15 01:36:36 +08:00
// Extracting details from the request JSON
2023-02-25 22:03:51 +08:00
sourceLang := reqj . SourceLang
targetLang := reqj . TargetLang
2023-03-02 14:36:41 +08:00
translateText := reqj . TransText
2023-09-15 01:36:36 +08:00
// If source language is not specified, auto-detect it
2023-02-25 22:03:51 +08:00
if sourceLang == "" {
2023-03-02 14:36:41 +08:00
lang := whatlanggo . DetectLang ( translateText )
deepLLang := strings . ToUpper ( lang . Iso6391 ( ) )
sourceLang = deepLLang
2022-10-20 01:06:30 +08:00
}
2023-09-15 01:36:36 +08:00
// If target language is not specified, set it to English
2023-02-25 22:03:51 +08:00
if targetLang == "" {
targetLang = "EN"
2022-10-20 01:06:30 +08:00
}
2023-09-15 01:36:36 +08:00
// Handling empty translation text
2023-02-25 22:03:51 +08:00
if translateText == "" {
c . JSON ( http . StatusNotFound , gin . H {
"code" : http . StatusNotFound ,
"message" : "No Translate Text Found" ,
} )
2023-09-15 01:36:36 +08:00
return
}
// Preparing the request data for the DeepL API
url := "https://www2.deepl.com/jsonrpc"
id = id + 1
postData := initData ( 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 )
2023-02-25 22:03:51 +08:00
} else {
2023-09-15 01:36:36 +08:00
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
}
// 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
}
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 , err := 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 ( ) )
c . JSON ( http . StatusNotAcceptable , gin . H {
"code" : http . StatusNotAcceptable ,
"message" : "Invalid targetLang" ,
} )
return
}
if resp . StatusCode == http . StatusTooManyRequests {
c . JSON ( http . StatusTooManyRequests , gin . H {
"code" : http . StatusTooManyRequests ,
"message" : "Too Many Requests" ,
} )
} 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
} )
c . JSON ( http . StatusOK , gin . H {
"code" : http . StatusOK ,
"id" : id ,
"data" : res . Get ( "result.texts.0.text" ) . String ( ) ,
"alternatives" : alternatives ,
"source_lang" : sourceLang ,
"target_lang" : targetLang ,
} )
2022-10-20 01:06:30 +08:00
}
} )
2023-07-01 21:45:00 +08:00
2023-09-15 01:36:36 +08:00
// Catch-all route to handle undefined paths
r . NoRoute ( func ( c * gin . Context ) {
c . JSON ( http . StatusNotFound , gin . H {
"code" : http . StatusNotFound ,
"message" : "Path not found" ,
} )
} )
// Determining which port to run the server on, with a fallback to a default port
2023-07-01 21:45:00 +08:00
envPort , ok := os . LookupEnv ( "PORT" )
if ok {
r . Run ( ":" + envPort )
} else {
r . Run ( fmt . Sprintf ( ":%v" , port ) )
}
2022-10-17 13:51:22 +08:00
}