Compare commits

...

15 Commits
v1.0.0 ... main

Author SHA1 Message Date
Vincent Yang
4cee0f3031
chore: downgrade Go version to 1.23.0 in go.mod 2025-04-08 15:28:04 -04:00
Vincent Yang
cbc3c1be51
chore: remove unused isRichText function 2025-04-08 14:27:33 -04:00
Vincent Yang
84792ead81
chore: bump Go version to 1.24.2 in Dockerfile, go.mod, and CI workflows 2025-04-08 14:27:01 -04:00
Jason Lyu
0a9ff6b582
refactor: make service exportable (#183) 2025-04-08 13:53:20 -04:00
dependabot[bot]
d1dbfcc1e5
chore(deps): bump golang.org/x/net from 0.33.0 to 0.36.0 (#181)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.33.0 to 0.36.0.
- [Commits](https://github.com/golang/net/compare/v0.33.0...v0.36.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-21 23:00:40 -04:00
Vincent Yang
2400139a8d
fix: unable to translate (switch to DeepL iOS) 2025-03-01 04:24:53 -05:00
Vincent Yang
26a9003b13
bump to Go 1.24.0 2025-02-25 13:16:58 -05:00
Vincent Yang
85fd738f3e
bump to Go 1.23.6 2025-02-09 20:12:06 -05:00
Vincent Yang
de9888ca5f
Upgrade to Go 1.23.5 2025-01-20 17:18:47 -05:00
Vincent Yang
bbfb808793
fix: handle line breaks #151 2025-01-20 17:11:53 -05:00
dependabot[bot]
4a77cbf30e
chore(deps): bump golang.org/x/net from 0.29.0 to 0.33.0 (#173)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.29.0 to 0.33.0.
- [Commits](https://github.com/golang/net/compare/v0.29.0...v0.33.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-20 16:59:52 -05:00
Vincent Young
2d8b84e2c8
Update compose.yaml 2024-12-22 14:18:30 -05:00
dependabot[bot]
1c8beb8a87
chore(deps): bump golang.org/x/crypto from 0.27.0 to 0.31.0 (#166)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.27.0 to 0.31.0.
- [Commits](https://github.com/golang/crypto/compare/v0.27.0...v0.31.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-12 17:38:43 -05:00
Vincent Young
f2fa90208d
ci: upgraded to Go 1.23.4 2024-12-05 13:13:40 -05:00
dependabot[bot]
b04139e89d
chore(deps): bump github.com/quic-go/quic-go from 0.47.0 to 0.48.2 (#165)
Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.47.0 to 0.48.2.
- [Release notes](https://github.com/quic-go/quic-go/releases)
- [Changelog](https://github.com/quic-go/quic-go/blob/master/Changelog.md)
- [Commits](https://github.com/quic-go/quic-go/compare/v0.47.0...v0.48.2)

---
updated-dependencies:
- dependency-name: github.com/quic-go/quic-go
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-03 11:27:04 -05:00
12 changed files with 501 additions and 469 deletions

View File

@ -15,7 +15,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.23.3'
go-version: '1.24.2'
- name: Install golint
run: go install golang.org/x/lint/golint@latest

View File

@ -15,7 +15,7 @@ jobs:
- uses: actions/setup-go@v4
with:
go-version: "1.23.3"
go-version: "1.24.2"
- run: bash .cross_compile.sh

View File

@ -1,4 +1,4 @@
FROM golang:1.23.3 AS builder
FROM golang:1.24.2 AS builder
WORKDIR /go/src/github.com/OwO-Network/DeepLX
COPY . .
RUN go get -d -v ./

View File

@ -1,5 +1,3 @@
version: '3.8'
services:
deeplx:
image: ghcr.io/owo-network/deeplx:latest

14
go.mod
View File

@ -1,6 +1,6 @@
module github.com/OwO-Network/DeepLX
go 1.23.3
go 1.23.0
require (
github.com/abadojack/whatlanggo v1.0.1
@ -37,7 +37,7 @@ require (
github.com/onsi/ginkgo/v2 v2.20.2 // indirect
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.47.0 // indirect
github.com/quic-go/quic-go v0.48.2 // indirect
github.com/refraction-networking/utls v1.6.7 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
@ -45,13 +45,13 @@ require (
github.com/ugorji/go/codec v1.2.12 // indirect
go.uber.org/mock v0.4.0 // indirect
golang.org/x/arch v0.7.0 // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/crypto v0.35.0 // indirect
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.18.0 // indirect
golang.org/x/net v0.36.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/tools v0.25.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

24
go.sum
View File

@ -84,8 +84,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.47.0 h1:yXs3v7r2bm1wmPTYNLKAAJTHMYkPEsfYJmTazXrCZ7Y=
github.com/quic-go/quic-go v0.47.0/go.mod h1:3bCapYsJvXGZcipOHuu7plYtaV6tnF+z7wIFsU0WK9E=
github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE=
github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM=
github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
@ -116,22 +116,22 @@ go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=

261
main.go
View File

@ -1,8 +1,8 @@
/*
* @Author: Vincent Yang
* @Date: 2023-07-01 21:45:34
* @LastEditors: Vincent Yang
* @LastEditTime: 2024-11-01 13:04:50
* @LastEditors: Jason Lyu
* @LastEditTime: 2025-04-08 13:45:00
* @FilePath: /DeepLX/main.go
* @Telegram: https://t.me/missuo
* @GitHub: https://github.com/missuo
@ -14,268 +14,21 @@ package main
import (
"fmt"
"log"
"net/http"
"net/url"
"os"
"strings"
translate "github.com/OwO-Network/DeepLX/translate"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/OwO-Network/DeepLX/service"
)
func authMiddleware(cfg *Config) gin.HandlerFunc {
return func(c *gin.Context) {
if cfg.Token != "" {
providedTokenInQuery := c.Query("token")
providedTokenInHeader := c.GetHeader("Authorization")
// Compatability with the Bearer token format
if providedTokenInHeader != "" {
parts := strings.Split(providedTokenInHeader, " ")
if len(parts) == 2 {
if parts[0] == "Bearer" || parts[0] == "DeepL-Auth-Key" {
providedTokenInHeader = parts[1]
} else {
providedTokenInHeader = ""
}
} else {
providedTokenInHeader = ""
}
}
if providedTokenInHeader != cfg.Token && providedTokenInQuery != cfg.Token {
c.JSON(http.StatusUnauthorized, gin.H{
"code": http.StatusUnauthorized,
"message": "Invalid access token",
})
c.Abort()
return
}
}
c.Next()
}
}
type PayloadFree struct {
TransText string `json:"text"`
SourceLang string `json:"source_lang"`
TargetLang string `json:"target_lang"`
TagHandling string `json:"tag_handling"`
}
type PayloadAPI struct {
Text []string `json:"text"`
TargetLang string `json:"target_lang"`
SourceLang string `json:"source_lang"`
TagHandling string `json:"tag_handling"`
}
func main() {
cfg := initConfig()
cfg := service.InitConfig()
fmt.Printf("DeepL X has been successfully launched! Listening on %v:%v\n", cfg.IP, cfg.Port)
fmt.Println("Developed by sjlleo <i@leo.moe> and missuo <me@missuo.me>.")
// Set Proxy
proxyURL := os.Getenv("PROXY")
if proxyURL == "" {
proxyURL = cfg.Proxy
}
if proxyURL != "" {
proxy, err := url.Parse(proxyURL)
if err != nil {
log.Fatalf("Failed to parse proxy URL: %v", err)
}
http.DefaultTransport = &http.Transport{
Proxy: http.ProxyURL(proxy),
}
}
if cfg.Token != "" {
fmt.Println("Access token is set.")
}
// Setting the application to release mode
gin.SetMode(gin.ReleaseMode)
r := gin.Default()
r.Use(cors.Default())
// Defining the root endpoint which returns the project details
r.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"code": http.StatusOK,
"message": "DeepL Free API, Developed by sjlleo and missuo. Go to /translate with POST. http://github.com/OwO-Network/DeepLX",
})
})
// Free API endpoint, No Pro Account required
r.POST("/translate", authMiddleware(cfg), func(c *gin.Context) {
req := PayloadFree{}
c.BindJSON(&req)
sourceLang := req.SourceLang
targetLang := req.TargetLang
translateText := req.TransText
tagHandling := req.TagHandling
proxyURL := cfg.Proxy
if tagHandling != "" && tagHandling != "html" && tagHandling != "xml" {
c.JSON(http.StatusBadRequest, gin.H{
"code": http.StatusBadRequest,
"message": "Invalid tag_handling value. Allowed values are 'html' and 'xml'.",
})
return
}
result, err := translate.TranslateByDeepLX(sourceLang, targetLang, translateText, tagHandling, proxyURL, "")
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,
})
}
})
// 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
tagHandling := req.TagHandling
proxyURL := cfg.Proxy
dlSession := cfg.DlSession
if tagHandling != "" && tagHandling != "html" && tagHandling != "xml" {
c.JSON(http.StatusBadRequest, gin.H{
"code": http.StatusBadRequest,
"message": "Invalid tag_handling value. Allowed values are 'html' and 'xml'.",
})
return
}
cookie := c.GetHeader("Cookie")
if cookie != "" {
dlSession = strings.Replace(cookie, "dl_session=", "", -1)
}
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 := translate.TranslateByDeepLX(sourceLang, targetLang, translateText, tagHandling, proxyURL, 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) {
proxyURL := cfg.Proxy
var translateText string
var targetLang string
translateText = c.PostForm("text")
targetLang = c.PostForm("target_lang")
if translateText == "" || targetLang == "" {
var jsonData struct {
Text []string `json:"text"`
TargetLang string `json:"target_lang"`
}
if err := c.BindJSON(&jsonData); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": http.StatusBadRequest,
"message": "Invalid request payload",
})
return
}
translateText = strings.Join(jsonData.Text, "\n")
targetLang = jsonData.TargetLang
}
result, err := translate.TranslateByDeepLX("", targetLang, translateText, "", proxyURL, "")
if err != nil {
log.Fatalf("Translation failed: %s", err)
}
if result.Code == http.StatusOK {
c.JSON(http.StatusOK, gin.H{
"translations": []map[string]interface{}{
{
"detected_source_language": result.SourceLang,
"text": result.Data,
},
},
})
} else {
c.JSON(result.Code, gin.H{
"code": result.Code,
"message": result.Message,
})
}
})
// 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",
})
})
r.Run(fmt.Sprintf("%v:%v", cfg.IP, cfg.Port))
app := service.Router(cfg)
app.Run(fmt.Sprintf("%v:%v", cfg.IP, cfg.Port))
}

View File

@ -1,8 +1,8 @@
/*
* @Author: Vincent Yang
* @Date: 2024-04-23 00:39:03
* @LastEditors: Vincent Yang
* @LastEditTime: 2024-09-17 19:34:32
* @LastEditors: Jason Lyu
* @LastEditTime: 2025-04-08 13:45:00
* @FilePath: /DeepLX/config.go
* @Telegram: https://t.me/missuo
* @GitHub: https://github.com/missuo
@ -10,7 +10,7 @@
* Copyright © 2024 by Vincent, All Rights Reserved.
*/
package main
package service
import (
"flag"
@ -26,7 +26,7 @@ type Config struct {
Proxy string
}
func initConfig() *Config {
func InitConfig() *Config {
cfg := &Config{
IP: "0.0.0.0",
Port: 1188,

275
service/service.go Normal file
View File

@ -0,0 +1,275 @@
/*
* @Author: Vincent Yang
* @Date: 2023-07-01 21:45:34
* @LastEditors: Jason Lyu
* @LastEditTime: 2025-04-08 13:45:00
* @FilePath: /DeepLX/main.go
* @Telegram: https://t.me/missuo
* @GitHub: https://github.com/missuo
*
* Copyright © 2024 by Vincent, All Rights Reserved.
*/
package service
import (
"fmt"
"log"
"net/http"
"net/url"
"os"
"strings"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/OwO-Network/DeepLX/translate"
)
func authMiddleware(cfg *Config) gin.HandlerFunc {
return func(c *gin.Context) {
if cfg.Token != "" {
providedTokenInQuery := c.Query("token")
providedTokenInHeader := c.GetHeader("Authorization")
// Compatability with the Bearer token format
if providedTokenInHeader != "" {
parts := strings.Split(providedTokenInHeader, " ")
if len(parts) == 2 {
if parts[0] == "Bearer" || parts[0] == "DeepL-Auth-Key" {
providedTokenInHeader = parts[1]
} else {
providedTokenInHeader = ""
}
} else {
providedTokenInHeader = ""
}
}
if providedTokenInHeader != cfg.Token && providedTokenInQuery != cfg.Token {
c.JSON(http.StatusUnauthorized, gin.H{
"code": http.StatusUnauthorized,
"message": "Invalid access token",
})
c.Abort()
return
}
}
c.Next()
}
}
type PayloadFree struct {
TransText string `json:"text"`
SourceLang string `json:"source_lang"`
TargetLang string `json:"target_lang"`
TagHandling string `json:"tag_handling"`
}
type PayloadAPI struct {
Text []string `json:"text"`
TargetLang string `json:"target_lang"`
SourceLang string `json:"source_lang"`
TagHandling string `json:"tag_handling"`
}
func Router(cfg *Config) *gin.Engine {
// Set Proxy
proxyURL := os.Getenv("PROXY")
if proxyURL == "" {
proxyURL = cfg.Proxy
}
if proxyURL != "" {
proxy, err := url.Parse(proxyURL)
if err != nil {
log.Fatalf("Failed to parse proxy URL: %v", err)
}
http.DefaultTransport = &http.Transport{
Proxy: http.ProxyURL(proxy),
}
}
if cfg.Token != "" {
fmt.Println("Access token is set.")
}
r := gin.Default()
r.Use(cors.Default())
// Defining the root endpoint which returns the project details
r.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"code": http.StatusOK,
"message": "DeepL Free API, Developed by sjlleo and missuo. Go to /translate with POST. http://github.com/OwO-Network/DeepLX",
})
})
// Free API endpoint, No Pro Account required
r.POST("/translate", authMiddleware(cfg), func(c *gin.Context) {
req := PayloadFree{}
c.BindJSON(&req)
sourceLang := req.SourceLang
targetLang := req.TargetLang
translateText := req.TransText
tagHandling := req.TagHandling
proxyURL := cfg.Proxy
if tagHandling != "" && tagHandling != "html" && tagHandling != "xml" {
c.JSON(http.StatusBadRequest, gin.H{
"code": http.StatusBadRequest,
"message": "Invalid tag_handling value. Allowed values are 'html' and 'xml'.",
})
return
}
result, err := translate.TranslateByDeepLX(sourceLang, targetLang, translateText, tagHandling, proxyURL, "")
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,
})
}
})
// 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
tagHandling := req.TagHandling
proxyURL := cfg.Proxy
dlSession := cfg.DlSession
if tagHandling != "" && tagHandling != "html" && tagHandling != "xml" {
c.JSON(http.StatusBadRequest, gin.H{
"code": http.StatusBadRequest,
"message": "Invalid tag_handling value. Allowed values are 'html' and 'xml'.",
})
return
}
cookie := c.GetHeader("Cookie")
if cookie != "" {
dlSession = strings.Replace(cookie, "dl_session=", "", -1)
}
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 := translate.TranslateByDeepLX(sourceLang, targetLang, translateText, tagHandling, proxyURL, 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) {
proxyURL := cfg.Proxy
var translateText string
var targetLang string
translateText = c.PostForm("text")
targetLang = c.PostForm("target_lang")
if translateText == "" || targetLang == "" {
var jsonData struct {
Text []string `json:"text"`
TargetLang string `json:"target_lang"`
}
if err := c.BindJSON(&jsonData); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": http.StatusBadRequest,
"message": "Invalid request payload",
})
return
}
translateText = strings.Join(jsonData.Text, "\n")
targetLang = jsonData.TargetLang
}
result, err := translate.TranslateByDeepLX("", targetLang, translateText, "", proxyURL, "")
if err != nil {
log.Fatalf("Translation failed: %s", err)
}
if result.Code == http.StatusOK {
c.JSON(http.StatusOK, gin.H{
"translations": []map[string]interface{}{
{
"detected_source_language": result.SourceLang,
"text": result.Data,
},
},
})
} else {
c.JSON(result.Code, gin.H{
"code": result.Code,
"message": result.Message,
})
}
})
// 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",
})
})
return r
}

View File

@ -2,7 +2,7 @@
* @Author: Vincent Young
* @Date: 2024-09-16 11:59:24
* @LastEditors: Vincent Yang
* @LastEditTime: 2024-12-03 11:23:23
* @LastEditTime: 2025-04-08 14:26:33
* @FilePath: /DeepLX/translate/translate.go
* @Telegram: https://t.me/missuo
* @GitHub: https://github.com/missuo
@ -14,6 +14,8 @@ package translate
import (
"bytes"
"compress/flate"
"compress/gzip"
"fmt"
"io"
"net/http"
@ -27,13 +29,9 @@ import (
"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)
func makeRequest(postData *PostData, proxyURL string, dlSession string) (gjson.Result, error) {
urlFull := "https://www2.deepl.com/jsonrpc"
postStr := formatPostString(postData)
// Create a new req client
@ -41,21 +39,18 @@ func makeRequest(postData *PostData, urlMethod string, proxyURL string, dlSessio
// Set headers
headers := 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"},
"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"},
}
if dlSession != "" {
@ -83,40 +78,28 @@ func makeRequest(postData *PostData, urlMethod string, proxyURL string, dlSessio
}
var bodyReader io.Reader
if resp.Header.Get("Content-Encoding") == "br" {
contentEncoding := resp.Header.Get("Content-Encoding")
switch contentEncoding {
case "br":
bodyReader = brotli.NewReader(resp.Body)
} else {
case "gzip":
bodyReader, err = gzip.NewReader(resp.Body) // Use gzip.NewReader
if err != nil {
return gjson.Result{}, fmt.Errorf("failed to create gzip reader: %w", err)
}
case "deflate": // Less common, but good to handle
bodyReader = flate.NewReader(resp.Body)
default:
bodyReader = resp.Body
}
body, err := io.ReadAll(bodyReader)
if err != nil {
return gjson.Result{}, err
return gjson.Result{}, fmt.Errorf("failed to read response body: %w", 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 == "" {
@ -126,148 +109,182 @@ func TranslateByDeepLX(sourceLang, targetLang, text string, tagHandling string,
}, 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
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 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")
for _, part := range textParts {
if strings.TrimSpace(part) == "" {
translatedParts = append(translatedParts, "")
allAlternatives = append(allAlternatives, []string{""})
continue
}
// 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()}
// 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: contextBefore,
RawEnContextAfter: contextAfter,
RawEnContextBefore: []string{},
RawEnContextAfter: []string{},
Sentences: []Sentence{{
Prefix: sentence.Get("prefix").String(),
Text: sentence.Get("text").String(),
ID: idx + 1,
Prefix: "",
Text: text,
ID: 0,
}},
})
}
hasRegionalVariant := false
targetLangCode := targetLang
targetLangParts := strings.Split(targetLang, "-")
if len(targetLangParts) > 1 {
targetLangCode = targetLangParts[0]
hasRegionalVariant = true
}
hasRegionalVariant := false
targetLangCode := targetLang
targetLangParts := strings.Split(targetLang, "-")
if len(targetLangParts) > 1 {
targetLangCode = targetLangParts[0]
hasRegionalVariant = true
}
// Prepare translation request
id := getRandomNumber()
// Prepare translation request
id := getRandomNumber()
postData := &PostData{
Jsonrpc: "2.0",
Method: "LMT_handle_jobs",
ID: id,
Params: Params{
CommonJobParams: CommonJobParams{
Mode: "translate",
},
Lang: Lang{
SourceLangComputed: strings.ToUpper(sourceLang),
TargetLang: strings.ToUpper(targetLangCode),
},
Jobs: jobs,
Priority: 1,
Timestamp: getTimeStamp(getICount(text)),
},
}
if hasRegionalVariant {
postData = &PostData{
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],
Mode: "translate",
Formality: "undefined",
TranscribeAs: "romanize",
AdvancedMode: false,
TextType: tagHandling,
WasSpoken: false,
},
Lang: Lang{
SourceLangComputed: strings.ToUpper(sourceLang),
TargetLang: strings.ToUpper(targetLangCode),
SourceLangUserSelected: "auto",
TargetLang: strings.ToUpper(targetLangCode),
SourceLangComputed: strings.ToUpper(sourceLang),
},
Jobs: jobs,
Priority: 1,
Timestamp: getTimeStamp(getICount(text)),
Timestamp: getTimeStamp(getICount(part)),
},
}
}
// Make translation request
result, err := makeRequest(postData, "LMT_handle_jobs", proxyURL, dlSession)
if err != nil {
return DeepLXTranslationResult{
Code: http.StatusServiceUnavailable,
Message: err.Error(),
}, nil
}
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)),
},
}
}
// Process translation results
var alternatives []string
var translatedText string
// Make translation request
result, err := makeRequest(postData, proxyURL, dlSession)
if err != nil {
return DeepLXTranslationResult{
Code: http.StatusServiceUnavailable,
Message: err.Error(),
}, nil
}
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
// 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 {
beams := translation.Get("beams").Array()
if i < len(beams) {
altText += beams[i].Get("sentences.0.text").String()
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 altText != "" {
alternatives = append(alternatives, altText)
}
}
// Get main translation
for _, translation := range translations {
translatedText += translation.Get("beams.0.sentences.0.text").String() + " "
if partTranslation == "" {
return DeepLXTranslationResult{
Code: http.StatusServiceUnavailable,
Message: "Translation failed",
}, nil
}
translatedText = strings.TrimSpace(translatedText)
translatedParts = append(translatedParts, partTranslation)
allAlternatives = append(allAlternatives, partAlternatives)
}
if translatedText == "" {
return DeepLXTranslationResult{
Code: http.StatusServiceUnavailable,
Message: "Translation failed",
}, nil
// Join all translated parts with newlines
translatedText := strings.Join(translatedParts, "\n")
// Combine alternatives with proper newline handling
var combinedAlternatives []string
maxAlts := 0
for _, alts := range allAlternatives {
if len(alts) > maxAlts {
maxAlts = len(alts)
}
}
// 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"))
}
return DeepLXTranslationResult{
Code: http.StatusOK,
ID: id,
ID: getRandomNumber(), // Using new ID for the complete translation
Data: translatedText,
Alternatives: alternatives,
Alternatives: combinedAlternatives,
SourceLang: sourceLang,
TargetLang: targetLang,
Method: map[bool]string{true: "Pro", false: "Free"}[dlSession != ""],

View File

@ -2,7 +2,7 @@
* @Author: Vincent Young
* @Date: 2024-09-16 11:59:24
* @LastEditors: Vincent Yang
* @LastEditTime: 2024-11-01 23:18:56
* @LastEditTime: 2025-03-01 04:16:07
* @FilePath: /DeepLX/translate/types.go
* @Telegram: https://t.me/missuo
* @GitHub: https://github.com/missuo
@ -14,14 +14,19 @@ package translate
// Lang represents the language settings for translation
type Lang struct {
SourceLangComputed string `json:"source_lang_computed,omitempty"`
TargetLang string `json:"target_lang"`
LangUserSelected string `json:"lang_user_selected,omitempty"`
SourceLangUserSelected string `json:"source_lang_user_selected"` // Can be "auto"
TargetLang string `json:"target_lang"`
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"`
}
@ -45,10 +50,7 @@ type Job struct {
type Params struct {
CommonJobParams CommonJobParams `json:"commonJobParams"`
Lang Lang `json:"lang"`
Texts []string `json:"texts,omitempty"`
TextType string `json:"textType,omitempty"`
Jobs []Job `json:"jobs,omitempty"`
Priority int `json:"priority,omitempty"`
Jobs []Job `json:"jobs"`
Timestamp int64 `json:"timestamp"`
}
@ -60,25 +62,6 @@ type PostData struct {
Params Params `json:"params"`
}
// SplitTextResponse represents the response from text splitting
type SplitTextResponse struct {
Jsonrpc string `json:"jsonrpc"`
ID int64 `json:"id"`
Result struct {
Lang struct {
Detected string `json:"detected"`
} `json:"lang"`
Texts []struct {
Chunks []struct {
Sentences []struct {
Prefix string `json:"prefix"`
Text string `json:"text"`
} `json:"sentences"`
} `json:"chunks"`
} `json:"texts"`
} `json:"result"`
}
// TranslationResponse represents the response from translation
type TranslationResponse struct {
Jsonrpc string `json:"jsonrpc"`
@ -86,23 +69,34 @@ type TranslationResponse struct {
Result struct {
Translations []struct {
Beams []struct {
Sentences []struct {
Text string `json:"text"`
} `json:"sentences"`
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"`
SourceLang string `json:"source_lang"`
TargetLang string `json:"target_lang"`
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 {
Code int `json:"code"`
ID int64 `json:"id"`
Message string `json:"message,omitempty"`
Data string `json:"data"`
Alternatives []string `json:"alternatives"`
Data string `json:"data"` // The primary translated text
Alternatives []string `json:"alternatives"` // Other possible translations
SourceLang string `json:"source_lang"`
TargetLang string `json:"target_lang"`
Method string `json:"method"`

View File

@ -2,7 +2,7 @@
* @Author: Vincent Young
* @Date: 2024-09-16 11:59:24
* @LastEditors: Vincent Yang
* @LastEditTime: 2024-11-01 00:39:32
* @LastEditTime: 2025-04-08 14:27:21
* @FilePath: /DeepLX/translate/utils.go
* @Telegram: https://t.me/missuo
* @GitHub: https://github.com/missuo
@ -55,8 +55,3 @@ func formatPostString(postData *PostData) string {
return postStr
}
// isRichText checks if text contains HTML-like tags
func isRichText(text string) bool {
return strings.Contains(text, "<") && strings.Contains(text, ">")
}