mirror of
https://github.com/OwO-Network/DeepLX.git
synced 2026-06-11 15:28:50 +00:00
Compare commits
No commits in common. "432c0a223cc1b585ed7c3ade971ae9eb2c8dd58d" and "8d145722eba578fac10d4ef22959cb92044b1673" have entirely different histories.
432c0a223c
...
8d145722eb
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,4 +1 @@
|
|||||||
DeepLX
|
DeepLX
|
||||||
|
|
||||||
# macOS Finder metadata
|
|
||||||
.DS_Store
|
|
||||||
35
go.mod
35
go.mod
@ -3,35 +3,36 @@ module github.com/OwO-Network/DeepLX
|
|||||||
go 1.25.0
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.2.1
|
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
|
||||||
github.com/tidwall/gjson v1.19.0
|
github.com/tidwall/gjson v1.18.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bytedance/gopkg v0.1.4 // indirect
|
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||||
github.com/bytedance/sonic v1.15.1 // indirect
|
github.com/bytedance/sonic v1.15.0 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.5.1 // indirect
|
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.7 // indirect
|
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
||||||
github.com/gin-contrib/sse v1.1.1 // indirect
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.30.2 // indirect
|
github.com/go-playground/validator/v10 v10.30.1 // indirect
|
||||||
github.com/goccy/go-json v0.10.6 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||||
github.com/google/go-querystring v1.2.0 // indirect
|
github.com/google/go-querystring v1.2.0 // indirect
|
||||||
github.com/icholy/digest v1.1.0 // indirect
|
github.com/icholy/digest v1.1.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.18.6 // indirect
|
github.com/klauspost/compress v1.18.4 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.22 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.3.1 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
github.com/quic-go/qpack v0.6.0 // indirect
|
github.com/quic-go/qpack v0.6.0 // indirect
|
||||||
github.com/quic-go/quic-go v0.57.1 // indirect
|
github.com/quic-go/quic-go v0.57.1 // indirect
|
||||||
github.com/refraction-networking/utls v1.8.2 // indirect
|
github.com/refraction-networking/utls v1.8.2 // indirect
|
||||||
@ -39,10 +40,10 @@ require (
|
|||||||
github.com/tidwall/pretty v1.2.1 // indirect
|
github.com/tidwall/pretty v1.2.1 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||||
golang.org/x/arch v0.27.0 // indirect
|
golang.org/x/arch v0.24.0 // indirect
|
||||||
golang.org/x/crypto v0.52.0 // indirect
|
golang.org/x/crypto v0.48.0 // indirect
|
||||||
golang.org/x/net v0.55.0 // indirect
|
golang.org/x/net v0.51.0 // indirect
|
||||||
golang.org/x/sys v0.45.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
golang.org/x/text v0.37.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.11 // indirect
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
73
go.sum
73
go.sum
@ -1,13 +1,15 @@
|
|||||||
github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=
|
github.com/abadojack/whatlanggo v1.0.1 h1:19N6YogDnf71CTHm3Mp2qhYfkRdyvbgwWdd2EPxJRG4=
|
||||||
github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
github.com/abadojack/whatlanggo v1.0.1/go.mod h1:66WiQbSbJBIlOZMsvbKe5m6pzQovxCH9B/K8tQB2uoc=
|
||||||
github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM=
|
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||||
github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4=
|
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||||
github.com/bytedance/sonic v1.15.1 h1:nJD5PmM0vY7J8CT6MxoqbVAAMhkSmV2HgRAUrrpLoOw=
|
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||||
github.com/bytedance/sonic v1.15.1/go.mod h1:mT2NbXunuaEbnZ+mRIX/vYqKISmgEuHFDI4UzmKx2SA=
|
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||||
github.com/bytedance/sonic/loader v0.5.1 h1:Ygpfa9zwRCCKSlrp5bBP/b/Xzc3VxsAW+5NIYXrOOpI=
|
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
|
||||||
github.com/bytedance/sonic/loader v0.5.1/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
|
||||||
github.com/cloudwego/base64x v0.1.7 h1:NppS+Fgzg5ovhn4NkUXaDT3x9jldgH5ToMCqzBSi2zI=
|
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
|
||||||
github.com/cloudwego/base64x v0.1.7/go.mod h1:Cu1PV9zfrSf7ET2tIbWbbEy7jO7HHJ13q4X2SQ8aWYg=
|
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||||
|
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||||
|
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@ -15,8 +17,8 @@ github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9
|
|||||||
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||||
github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY=
|
github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY=
|
||||||
github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk=
|
github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk=
|
||||||
github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko=
|
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s=
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||||
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
@ -25,10 +27,10 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
|||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.30.2 h1:JiFIMtSSHb2/XBUbWM4i/MpeQm9ZK2xqPNk8vgvu5JQ=
|
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
|
||||||
github.com/go-playground/validator/v10 v10.30.2/go.mod h1:mAf2pIOVXjTEBrwUMGKkCWKKPs9NheYGabeB04txQSc=
|
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
|
||||||
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||||
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
@ -43,21 +45,21 @@ github.com/imroc/req/v3 v3.57.0 h1:LMTUjNRUybUkTPn8oJDq8Kg3JRBOBTcnDhKu7mzupKI=
|
|||||||
github.com/imroc/req/v3 v3.57.0/go.mod h1:JL62ey1nvSLq81HORNcosvlf7SxZStONNqOprg0Pz00=
|
github.com/imroc/req/v3 v3.57.0/go.mod h1:JL62ey1nvSLq81HORNcosvlf7SxZStONNqOprg0Pz00=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
|
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
|
||||||
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7olPtrEc=
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||||
@ -77,10 +79,12 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
|
|||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU=
|
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||||
github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc=
|
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
|
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
|
||||||
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
@ -91,16 +95,17 @@ github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZ
|
|||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||||
golang.org/x/arch v0.27.0 h1:0WNVcR8u9yFz8j5FvdHpgwNp3FS5U4guYdzHwEiGjoU=
|
golang.org/x/arch v0.24.0 h1:qlJ3M9upxvFfwRM51tTg3Yl+8CP9vCC1E7vlFpgv99Y=
|
||||||
golang.org/x/arch v0.27.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
|
golang.org/x/arch v0.24.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||||
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
||||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
|||||||
@ -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: 2026-05-22 00:00:00
|
* @LastEditTime: 2025-07-13 23:09:49
|
||||||
* @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
|
||||||
@ -13,379 +13,101 @@
|
|||||||
package translate
|
package translate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"compress/flate"
|
"compress/flate"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"context"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/cookiejar"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"github.com/abadojack/whatlanggo"
|
||||||
"unicode/utf8"
|
"github.com/imroc/req/v3"
|
||||||
|
|
||||||
"github.com/andybalholm/brotli"
|
"github.com/andybalholm/brotli"
|
||||||
"github.com/imroc/req/v3"
|
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DeepL's interactive web translator migrated to a SignalR/WebSocket
|
// makeRequestWithBody makes an HTTP request with pre-formatted body using minimal headers
|
||||||
// channel and the legacy LMT_handle_texts backend on www2.deepl.com now
|
func makeRequestWithBody(postStr string, proxyURL string, dlSession string) (gjson.Result, error) {
|
||||||
// 429s anonymous traffic within a handful of calls. The official Chrome
|
urlFull := "https://www2.deepl.com/jsonrpc"
|
||||||
// extension instead POSTs to a stateless "oneshot" endpoint that lives
|
|
||||||
// on a separate rate-limit pool and accepts the literal header
|
|
||||||
// `Authorization: None` for anonymous requests — that is what we target.
|
|
||||||
//
|
|
||||||
// The request we send is reverse-engineered from the extension's
|
|
||||||
// background.js (Chrome Web Store ID cofdbpoegempjloogbagkncekinflcnj):
|
|
||||||
// - URL builder → mN() at ~offset 529948
|
|
||||||
// - body builder → IN() at ~offset 531200
|
|
||||||
// - fetch wrapper → JO() at ~offset 508659
|
|
||||||
// - app metadata → Wo() at ~offset 16500
|
|
||||||
const (
|
|
||||||
oneshotFreeEndpoint = "https://oneshot-free.www.deepl.com/v1/translate"
|
|
||||||
oneshotProEndpoint = "https://oneshot-pro.www.deepl.com/v1/translate"
|
|
||||||
|
|
||||||
// Pinned to the Chrome version utls bundles into req v3 (HelloChrome_120).
|
// Create a new req client
|
||||||
// Keep this in lockstep with the user-agent and app_information.os_version
|
client := req.C().SetTLSFingerprintRandomized()
|
||||||
// so the TLS handshake, UA, and self-reported browser version all agree —
|
|
||||||
// a mismatch on any one of those is a cheap signal for the WAF.
|
|
||||||
impersonatedChromeMajor = "120"
|
|
||||||
chromeExtensionVersion = "1.86.0"
|
|
||||||
chromeExtensionID = "cofdbpoegempjloogbagkncekinflcnj"
|
|
||||||
|
|
||||||
// oneshot enforces a 1500-character hard cap on the total length of
|
// Set headers to simulate browser request
|
||||||
// the `text` array (sum across all items). Source: the extension's
|
headers := http.Header{
|
||||||
// own `G.notLoggedIn = 1500` constant in background.js. The server
|
"Content-Type": []string{"application/json"},
|
||||||
// returns 400 `{"errors":{"text":["text exceeds maximum length"]}}`
|
"Accept": []string{"*/*"},
|
||||||
// past this; bail early to spare the upstream and give the caller a
|
"Accept-Language": []string{"en-US,en;q=0.9"},
|
||||||
// faster, less ambiguous error.
|
"Accept-Encoding": []string{"gzip, deflate, br, zstd"},
|
||||||
maxFreeTextLength = 1500
|
"Origin": []string{"https://www.deepl.com"},
|
||||||
|
"Referer": []string{"https://www.deepl.com/"},
|
||||||
// oneshotTimeout caps how long we wait on a single translate request.
|
"Sec-Fetch-Dest": []string{"empty"},
|
||||||
// Without an explicit timeout, a hung upstream connection would
|
"Sec-Fetch-Mode": []string{"cors"},
|
||||||
// dangle indefinitely and the caller (e.g. browser extension) would
|
"Sec-Fetch-Site": []string{"same-site"},
|
||||||
// sit on a spinner forever — observed in the field.
|
"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"},
|
||||||
oneshotTimeout = 20 * time.Second
|
|
||||||
|
|
||||||
// warmupTimeout caps the initial GET to www.deepl.com that seeds the
|
|
||||||
// cookie jar. Shorter than oneshotTimeout because warmup typically
|
|
||||||
// completes in well under a second; we'd rather skip a slow warmup
|
|
||||||
// (cookies are best-effort anyway) than block the first translation.
|
|
||||||
warmupTimeout = 5 * time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
// instanceID mirrors the UUID the extension persists in chrome.storage on
|
|
||||||
// install: stable for the life of the process, reused on every request.
|
|
||||||
// Rotating it per-request would be a far stronger signal than reusing one.
|
|
||||||
var instanceID = newInstanceID()
|
|
||||||
|
|
||||||
// A real extension fetch() inherits whatever cookies the browser has
|
|
||||||
// accumulated on .deepl.com. A cold visit to www.deepl.com sets
|
|
||||||
// userCountry=<iso2> and verifiedBot=false; users who have ever opened
|
|
||||||
// the site additionally have _ga / _ga_<id> from analytics JS. We share
|
|
||||||
// a process-wide cookie jar so every oneshot POST automatically carries
|
|
||||||
// whatever the warmup GET picked up.
|
|
||||||
var (
|
|
||||||
cookieJar http.CookieJar
|
|
||||||
cookieJarOnce sync.Once
|
|
||||||
cookieWarmer sync.Once
|
|
||||||
)
|
|
||||||
|
|
||||||
// oneshotClients caches one req.Client per proxy URL so all translate
|
|
||||||
// calls share the underlying TCP / TLS / HTTP/2 connection pool.
|
|
||||||
// Creating a fresh req.Client per request meant a brand-new TLS
|
|
||||||
// handshake every time (~200-400ms of overhead on top of DeepL's own
|
|
||||||
// ~1.5s processing latency). Reusing the client lets keep-alive +
|
|
||||||
// session tickets cut that to near zero on the warm path.
|
|
||||||
var oneshotClients sync.Map // map[string]*req.Client
|
|
||||||
|
|
||||||
func sharedCookieJar() http.CookieJar {
|
|
||||||
cookieJarOnce.Do(func() {
|
|
||||||
j, _ := cookiejar.New(nil)
|
|
||||||
cookieJar = j
|
|
||||||
})
|
|
||||||
return cookieJar
|
|
||||||
}
|
|
||||||
|
|
||||||
// warmCookies primes the shared jar by GETting www.deepl.com once.
|
|
||||||
// The Set-Cookie response (userCountry / verifiedBot) lands on .deepl.com,
|
|
||||||
// which is the eTLD+1 of oneshot-free.www.deepl.com, so subsequent POSTs
|
|
||||||
// to the oneshot endpoint will carry those cookies automatically. The
|
|
||||||
// same request doubles as a TLS-handshake warmup: it leaves a live
|
|
||||||
// HTTP/2 connection to www.deepl.com in the client pool, which the
|
|
||||||
// first oneshot POST then resumes via TLS session tickets.
|
|
||||||
func warmCookies(client *req.Client) {
|
|
||||||
cookieWarmer.Do(func() {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), warmupTimeout)
|
|
||||||
defer cancel()
|
|
||||||
_, _ = client.R().SetContext(ctx).Get("https://www.deepl.com/translator")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func newInstanceID() string {
|
|
||||||
b := make([]byte, 16)
|
|
||||||
if _, err := rand.Read(b); err != nil {
|
|
||||||
return "00000000-0000-4000-8000-000000000000"
|
|
||||||
}
|
}
|
||||||
b[6] = (b[6] & 0x0f) | 0x40 // RFC 4122 v4
|
|
||||||
b[8] = (b[8] & 0x3f) | 0x80
|
|
||||||
s := hex.EncodeToString(b)
|
|
||||||
return fmt.Sprintf("%s-%s-%s-%s-%s", s[0:8], s[8:12], s[12:16], s[16:20], s[20:32])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Language code tables mirror the bundled list in the extension's
|
if dlSession != "" {
|
||||||
// background.js (arrays `y` ~offset 6000 for the full target-capable
|
headers.Set("Cookie", "dl_session="+dlSession)
|
||||||
// set, `A` for source-only aliases). Keys are the uppercase forms
|
|
||||||
// callers pass; values are the lowercase BCP-47-ish forms the oneshot
|
|
||||||
// endpoint expects ("de", "en-US", "zh-Hans", ...).
|
|
||||||
//
|
|
||||||
// targetLangMap is what the API accepts as `target_lang`. EN and PT
|
|
||||||
// are intentionally absent — DeepL deprecated them as target codes in
|
|
||||||
// favour of EN-US/EN-GB and PT-BR/PT-PT, and the extension's y array
|
|
||||||
// reflects that. We accept EN/PT as a backward-compat convenience and
|
|
||||||
// resolve them to the regional default (en-US, pt-BR).
|
|
||||||
var targetLangMap = map[string]string{
|
|
||||||
"AR": "ar", "BG": "bg", "CS": "cs", "DA": "da", "DE": "de", "EL": "el",
|
|
||||||
"EN-GB": "en-GB", "EN-US": "en-US",
|
|
||||||
"ES": "es", "ES-419": "es-419", "ET": "et", "FI": "fi", "FR": "fr",
|
|
||||||
"HE": "he", "HU": "hu", "ID": "id", "IT": "it", "JA": "ja", "KO": "ko",
|
|
||||||
"LT": "lt", "LV": "lv", "NB": "nb", "NL": "nl", "PL": "pl",
|
|
||||||
"PT-BR": "pt-BR", "PT-PT": "pt-PT",
|
|
||||||
"RO": "ro", "RU": "ru", "SK": "sk", "SL": "sl", "SV": "sv",
|
|
||||||
"TR": "tr", "UK": "uk", "VI": "vi",
|
|
||||||
"ZH": "zh-Hans", "ZH-HANS": "zh-Hans", "ZH-HANT": "zh-Hant",
|
|
||||||
// Convenience aliases for legacy callers.
|
|
||||||
"EN": "en-US",
|
|
||||||
"PT": "pt-BR",
|
|
||||||
}
|
|
||||||
|
|
||||||
// sourceLangMap is what the API accepts as `source_lang`. It is a
|
|
||||||
// superset of targetLangMap: EN and PT are first-class source codes
|
|
||||||
// (extension array `A`) mapping to the generic "en"/"pt" — used when
|
|
||||||
// the caller knows the input is English/Portuguese but does not want
|
|
||||||
// to commit to a regional variant.
|
|
||||||
var sourceLangMap = func() map[string]string {
|
|
||||||
m := make(map[string]string, len(targetLangMap)+2)
|
|
||||||
for k, v := range targetLangMap {
|
|
||||||
m[k] = v
|
|
||||||
}
|
}
|
||||||
m["EN"] = "en"
|
|
||||||
m["PT"] = "pt"
|
|
||||||
return m
|
|
||||||
}()
|
|
||||||
|
|
||||||
// resolveTargetLang validates and normalizes a user-supplied target
|
|
||||||
// language code. Returns "" and a non-nil error if the code is empty,
|
|
||||||
// "auto", or otherwise not in the supported set.
|
|
||||||
func resolveTargetLang(code string) (string, error) {
|
|
||||||
if code == "" {
|
|
||||||
return "", fmt.Errorf("target_lang is required")
|
|
||||||
}
|
|
||||||
if strings.EqualFold(code, "auto") {
|
|
||||||
return "", fmt.Errorf("target_lang cannot be \"auto\"; pick one of: %s", supportedTargetLangsList())
|
|
||||||
}
|
|
||||||
if v, ok := targetLangMap[strings.ToUpper(code)]; ok {
|
|
||||||
return v, nil
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("unsupported target_lang %q; valid codes: %s", code, supportedTargetLangsList())
|
|
||||||
}
|
|
||||||
|
|
||||||
// resolveSourceLang validates and normalizes a user-supplied source
|
|
||||||
// language code. An empty string or "auto" is allowed and returns
|
|
||||||
// ("", nil) so the caller omits source_lang and lets the server
|
|
||||||
// autodetect.
|
|
||||||
func resolveSourceLang(code string) (string, error) {
|
|
||||||
if code == "" || strings.EqualFold(code, "auto") {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
if v, ok := sourceLangMap[strings.ToUpper(code)]; ok {
|
|
||||||
return v, nil
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("unsupported source_lang %q; valid codes: %s (or \"auto\")", code, supportedSourceLangsList())
|
|
||||||
}
|
|
||||||
|
|
||||||
// supportedTargetLangsList / supportedSourceLangsList return a sorted,
|
|
||||||
// comma-separated rendering of the supported codes for use in error
|
|
||||||
// messages. Cached at first call.
|
|
||||||
var (
|
|
||||||
targetLangsListOnce sync.Once
|
|
||||||
targetLangsList string
|
|
||||||
sourceLangsListOnce sync.Once
|
|
||||||
sourceLangsList string
|
|
||||||
)
|
|
||||||
|
|
||||||
func supportedTargetLangsList() string {
|
|
||||||
targetLangsListOnce.Do(func() {
|
|
||||||
targetLangsList = sortedKeys(targetLangMap)
|
|
||||||
})
|
|
||||||
return targetLangsList
|
|
||||||
}
|
|
||||||
|
|
||||||
func supportedSourceLangsList() string {
|
|
||||||
sourceLangsListOnce.Do(func() {
|
|
||||||
sourceLangsList = sortedKeys(sourceLangMap)
|
|
||||||
})
|
|
||||||
return sourceLangsList
|
|
||||||
}
|
|
||||||
|
|
||||||
func sortedKeys(m map[string]string) string {
|
|
||||||
keys := make([]string, 0, len(m))
|
|
||||||
for k := range m {
|
|
||||||
keys = append(keys, k)
|
|
||||||
}
|
|
||||||
sort.Strings(keys)
|
|
||||||
return strings.Join(keys, ", ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// appInformation matches the snake_case shape produced by background.js
|
|
||||||
// Wo({isSnakeCase: true}). Values are pinned to the same Chrome version
|
|
||||||
// as the TLS handshake so the request tells one consistent story.
|
|
||||||
type appInformation struct {
|
|
||||||
OS string `json:"os"`
|
|
||||||
OSVersion string `json:"os_version"`
|
|
||||||
AppVersion string `json:"app_version"`
|
|
||||||
AppBuild string `json:"app_build"`
|
|
||||||
InstanceID string `json:"instance_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// oneshotRequest mirrors the body assembled in background.js IN(...).
|
|
||||||
// Field order matches the extension's object literal so the serialized
|
|
||||||
// JSON is byte-identical (encoding/json honours struct field order).
|
|
||||||
type oneshotRequest struct {
|
|
||||||
Text []string `json:"text"`
|
|
||||||
TargetLang string `json:"target_lang"`
|
|
||||||
SourceLang string `json:"source_lang,omitempty"`
|
|
||||||
UsageType string `json:"usage_type"`
|
|
||||||
AppInformation appInformation `json:"app_information"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// newOneshotClient configures a req.Client whose outbound profile matches
|
|
||||||
// a chrome-extension service-worker fetch() byte-for-byte where it can.
|
|
||||||
// ImpersonateChrome gives us the Chrome 120 TLS ClientHello, HTTP/2
|
|
||||||
// SETTINGS, pseudo/header order, and a sec-ch-ua/user-agent set tied to
|
|
||||||
// the same version. It also installs a navigation-flavoured set of common
|
|
||||||
// headers (pragma, cache-control, upgrade-insecure-requests, sec-fetch-user)
|
|
||||||
// that a fetch() never emits — wipe those so the WAF cannot tell us apart
|
|
||||||
// on that axis.
|
|
||||||
// getOneshotClient returns a process-wide cached client for the given
|
|
||||||
// proxy URL, creating it on first use. Sharing the client across
|
|
||||||
// requests is the single biggest latency win we have on the warm path:
|
|
||||||
// it keeps the TLS / HTTP/2 connection in the pool so subsequent
|
|
||||||
// requests skip the handshake entirely. Kicks off cookie-jar warmup
|
|
||||||
// in the background on first creation so that the first real translate
|
|
||||||
// call lands on an already-established connection.
|
|
||||||
func getOneshotClient(proxyURL string) (*req.Client, error) {
|
|
||||||
if c, ok := oneshotClients.Load(proxyURL); ok {
|
|
||||||
return c.(*req.Client), nil
|
|
||||||
}
|
|
||||||
c, err := newOneshotClient(proxyURL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if actual, loaded := oneshotClients.LoadOrStore(proxyURL, c); loaded {
|
|
||||||
return actual.(*req.Client), nil
|
|
||||||
}
|
|
||||||
// First time we've seen this proxy. Kick warmup off in the
|
|
||||||
// background so the very first translate call can run in parallel
|
|
||||||
// with the TLS handshake to www.deepl.com.
|
|
||||||
go warmCookies(c)
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newOneshotClient(proxyURL string) (*req.Client, error) {
|
|
||||||
client := req.C().ImpersonateChrome().SetCookieJar(sharedCookieJar()).SetTimeout(oneshotTimeout)
|
|
||||||
for _, h := range []string{
|
|
||||||
"Pragma",
|
|
||||||
"Cache-Control",
|
|
||||||
"Upgrade-Insecure-Requests",
|
|
||||||
"Sec-Fetch-User",
|
|
||||||
} {
|
|
||||||
client.Headers.Del(h)
|
|
||||||
}
|
|
||||||
// Chrome 120 fetch() advertises gzip/deflate/br (zstd only appeared
|
|
||||||
// as a default in Chrome 123+). req's default of just "gzip" is a
|
|
||||||
// distinguishable signal — match Chrome explicitly.
|
|
||||||
client.SetCommonHeader("Accept-Encoding", "gzip, deflate, br")
|
|
||||||
|
|
||||||
|
// Set proxy if provided
|
||||||
if proxyURL != "" {
|
if proxyURL != "" {
|
||||||
u, err := url.Parse(proxyURL)
|
proxy, err := url.Parse(proxyURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return gjson.Result{}, err
|
||||||
}
|
}
|
||||||
client.SetProxyURL(u.String())
|
client.SetProxyURL(proxy.String())
|
||||||
}
|
}
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// callOneshot POSTs to the oneshot endpoint and returns the parsed JSON.
|
// Make the request
|
||||||
// For anonymous traffic bearerToken is empty and we send the literal
|
r := client.R()
|
||||||
// header `Authorization: None` — replicating the extension's JO() wrapper
|
r.Headers = headers
|
||||||
// exactly. Omitting that header instead would put the request on a
|
resp, err := r.
|
||||||
// different server-side auth branch.
|
SetBody(bytes.NewReader([]byte(postStr))).
|
||||||
func callOneshot(endpoint string, body []byte, bearerToken, proxyURL string) (gjson.Result, int, error) {
|
Post(urlFull)
|
||||||
client, err := getOneshotClient(proxyURL)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gjson.Result{}, 0, err
|
return gjson.Result{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
authValue := "None"
|
// Check for blocked status like TypeScript version
|
||||||
if bearerToken != "" {
|
if resp.StatusCode == 429 {
|
||||||
authValue = "Bearer " + bearerToken
|
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")
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := client.R().
|
// Check for other error status codes
|
||||||
DisableAutoReadResponse().
|
if resp.StatusCode != 200 {
|
||||||
SetHeader("Content-Type", "application/json").
|
return gjson.Result{}, fmt.Errorf("request failed with status code: %d", resp.StatusCode)
|
||||||
SetHeader("Accept", "*/*").
|
|
||||||
SetHeader("Authorization", authValue).
|
|
||||||
SetHeader("Origin", "chrome-extension://"+chromeExtensionID).
|
|
||||||
SetHeader("Sec-Fetch-Site", "cross-site").
|
|
||||||
SetHeader("Sec-Fetch-Mode", "cors").
|
|
||||||
SetHeader("Sec-Fetch-Dest", "empty").
|
|
||||||
SetBodyBytes(body). // SetBodyBytes pins Content-Length; using an
|
|
||||||
// io.Reader instead forces Transfer-Encoding: chunked, which a
|
|
||||||
// real fetch() with JSON.stringify body never emits.
|
|
||||||
Post(endpoint)
|
|
||||||
if err != nil {
|
|
||||||
return gjson.Result{}, 0, err
|
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
// Once we set Accept-Encoding ourselves, Go's HTTP stack stops
|
var bodyReader io.Reader
|
||||||
// transparently decompressing, so handle gzip/deflate/br by hand.
|
contentEncoding := resp.Header.Get("Content-Encoding")
|
||||||
var reader io.Reader = resp.Body
|
switch contentEncoding {
|
||||||
switch strings.ToLower(resp.Header.Get("Content-Encoding")) {
|
|
||||||
case "gzip":
|
|
||||||
gr, err := gzip.NewReader(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return gjson.Result{}, resp.StatusCode, fmt.Errorf("gzip reader: %w", err)
|
|
||||||
}
|
|
||||||
defer gr.Close()
|
|
||||||
reader = gr
|
|
||||||
case "deflate":
|
|
||||||
reader = flate.NewReader(resp.Body)
|
|
||||||
case "br":
|
case "br":
|
||||||
reader = brotli.NewReader(resp.Body)
|
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
|
||||||
}
|
}
|
||||||
raw, err := io.ReadAll(reader)
|
|
||||||
|
body, err := io.ReadAll(bodyReader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gjson.Result{}, resp.StatusCode, fmt.Errorf("read response body: %w", err)
|
return gjson.Result{}, fmt.Errorf("failed to read response body: %w", err)
|
||||||
}
|
}
|
||||||
return gjson.ParseBytes(raw), resp.StatusCode, nil
|
return gjson.ParseBytes(body), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TranslateByDeepLX performs translation via the DeepL oneshot endpoint.
|
// TranslateByDeepLX performs translation using DeepL API
|
||||||
// Passing dlSession switches to the Pro endpoint; the value is sent
|
|
||||||
// verbatim as the Bearer token (i.e. it must be an OAuth access token,
|
|
||||||
// not the legacy dl_session cookie).
|
|
||||||
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{
|
||||||
@ -394,112 +116,86 @@ func TranslateByDeepLX(sourceLang, targetLang, text string, tagHandling string,
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
resolvedTarget, err := resolveTargetLang(targetLang)
|
// Get detected language if source language is auto
|
||||||
if err != nil {
|
if sourceLang == "auto" || sourceLang == "" {
|
||||||
return DeepLXTranslationResult{
|
sourceLang = strings.ToUpper(whatlanggo.DetectLang(text).Iso6391())
|
||||||
Code: http.StatusBadRequest,
|
|
||||||
Message: err.Error(),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
resolvedSource, err := resolveSourceLang(sourceLang)
|
|
||||||
if err != nil {
|
|
||||||
return DeepLXTranslationResult{
|
|
||||||
Code: http.StatusBadRequest,
|
|
||||||
Message: err.Error(),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if n := utf8.RuneCountInString(text); n > maxFreeTextLength {
|
// Prepare translation request using new LMT_handle_texts method
|
||||||
return DeepLXTranslationResult{
|
id := getRandomNumber()
|
||||||
Code: http.StatusRequestEntityTooLarge,
|
iCount := getICount(text)
|
||||||
Message: fmt.Sprintf("text exceeds maximum length: %d characters (anonymous oneshot limit is %d)", n, maxFreeTextLength),
|
timestamp := getTimeStamp(iCount)
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
reqStruct := oneshotRequest{
|
postData := &PostData{
|
||||||
Text: []string{text},
|
Jsonrpc: "2.0",
|
||||||
TargetLang: resolvedTarget,
|
Method: "LMT_handle_texts",
|
||||||
SourceLang: resolvedSource, // empty = autodetect; omitempty drops the field
|
ID: id,
|
||||||
UsageType: "Translate",
|
Params: Params{
|
||||||
AppInformation: appInformation{
|
Splitting: "newlines",
|
||||||
OS: "brex_macOS",
|
Lang: Lang{
|
||||||
OSVersion: "brex_chrome_" + impersonatedChromeMajor + ".0.0.0",
|
SourceLangUserSelected: sourceLang,
|
||||||
AppVersion: chromeExtensionVersion,
|
TargetLang: targetLang,
|
||||||
AppBuild: "chrome_web_store",
|
},
|
||||||
InstanceID: instanceID,
|
Texts: []TextItem{{
|
||||||
|
Text: text,
|
||||||
|
RequestAlternatives: 3,
|
||||||
|
}},
|
||||||
|
Timestamp: timestamp,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
bodyBytes, _ := json.Marshal(reqStruct)
|
|
||||||
|
|
||||||
endpoint := oneshotFreeEndpoint
|
// Format and apply body manipulation method like TypeScript
|
||||||
if dlSession != "" {
|
postStr := formatPostString(postData)
|
||||||
endpoint = oneshotProEndpoint
|
postStr = handlerBodyMethod(id, postStr)
|
||||||
}
|
|
||||||
|
|
||||||
id := time.Now().UnixMilli()
|
// Make translation request
|
||||||
result, status, err := callOneshot(endpoint, bodyBytes, dlSession, proxyURL)
|
result, err := makeRequestWithBody(postStr, proxyURL, dlSession)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Map upstream timeouts to 504 so callers can distinguish "DeepL
|
|
||||||
// took too long" from other 503 failure modes (DNS, TLS, etc.).
|
|
||||||
var ue *url.Error
|
|
||||||
if errors.Is(err, context.DeadlineExceeded) || (errors.As(err, &ue) && ue.Timeout()) {
|
|
||||||
return DeepLXTranslationResult{
|
|
||||||
ID: id,
|
|
||||||
Code: http.StatusGatewayTimeout,
|
|
||||||
Message: fmt.Sprintf("upstream DeepL request timed out after %s", oneshotTimeout),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
return DeepLXTranslationResult{
|
return DeepLXTranslationResult{
|
||||||
ID: id,
|
|
||||||
Code: http.StatusServiceUnavailable,
|
Code: http.StatusServiceUnavailable,
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
switch status {
|
// Process translation results using new format
|
||||||
case http.StatusOK:
|
textsArray := result.Get("result.texts").Array()
|
||||||
// fall through to body parsing
|
if len(textsArray) == 0 {
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
mainText := translations[0].Get("text").String()
|
// Get main translation
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
if detected := translations[0].Get("detected_source_language").String(); detected != "" {
|
// Get alternatives
|
||||||
sourceLang = strings.ToUpper(detected)
|
var alternatives []string
|
||||||
|
alternativesArray := textsArray[0].Get("alternatives").Array()
|
||||||
|
for _, alt := range alternativesArray {
|
||||||
|
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: nil, // oneshot does not return alternatives
|
Alternatives: 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: 2026-05-22 00:00:00
|
* @LastEditTime: 2025-03-01 04:16:07
|
||||||
* @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,16 +12,123 @@
|
|||||||
|
|
||||||
package translate
|
package translate
|
||||||
|
|
||||||
// DeepLXTranslationResult is the public response shape consumed by the HTTP
|
// Lang represents the language settings for translation
|
||||||
// handlers in the service package. The structure predates the migration to
|
type Lang struct {
|
||||||
// the oneshot endpoint; Alternatives is now always empty because oneshot does
|
SourceLangUserSelected string `json:"source_lang_user_selected"` // Can be "auto"
|
||||||
// not return alternative translations, and ID is synthesized from time.
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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"`
|
Data string `json:"data"` // The primary translated text
|
||||||
Alternatives []string `json:"alternatives"`
|
Alternatives []string `json:"alternatives"` // Other possible translations
|
||||||
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"`
|
||||||
|
|||||||
59
translate/utils.go
Normal file
59
translate/utils.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* @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