Added context menu

This commit is contained in:
2025-07-04 14:37:03 +08:00
parent ebee33ea7c
commit a2a332e735
21 changed files with 2696 additions and 300 deletions

View File

@@ -0,0 +1,311 @@
// Package translator 提供文本翻译功能
package translator
import (
"encoding/json"
"errors"
"fmt"
"io"
"log"
"math"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"time"
"github.com/robertkrimen/otto"
"golang.org/x/text/language"
)
// 错误定义
var (
ErrBadNetwork = errors.New("bad network, please check your internet connection")
)
// GoogleTranslator Google翻译器结构体统一管理翻译功能
type GoogleTranslator struct {
GoogleHost string // Google服务主机
vm *otto.Otto // JavaScript虚拟机
ttk otto.Value // 翻译token缓存
httpClient *http.Client // HTTP客户端
Timeout time.Duration // 请求超时时间
}
// NewGoogleTranslator 创建一个新的Google翻译器实例
func NewGoogleTranslator() *GoogleTranslator {
translator := &GoogleTranslator{
GoogleHost: "google.com",
vm: otto.New(),
Timeout: defaultTimeout,
httpClient: &http.Client{Timeout: defaultTimeout},
}
// 初始化ttk
translator.ttk, _ = otto.ToValue("0")
return translator
}
// SetTimeout 设置请求超时时间
func (t *GoogleTranslator) SetTimeout(timeout time.Duration) {
t.Timeout = timeout
t.httpClient.Timeout = timeout
}
// SetGoogleHost 设置Google主机
func (t *GoogleTranslator) SetGoogleHost(host string) {
t.GoogleHost = host
}
// Translate 使用Go语言提供的标准语言标签进行文本翻译
func (t *GoogleTranslator) Translate(text string, from language.Tag, to language.Tag) (string, error) {
return t.translate(text, from.String(), to.String(), false, defaultNumberOfRetries, 0)
}
// TranslateWithParams 使用简单字符串参数进行文本翻译
func (t *GoogleTranslator) TranslateWithParams(text string, params TranslationParams) (string, error) {
tries := params.Tries
if tries == 0 {
tries = defaultNumberOfRetries
}
return t.translate(text, params.From, params.To, true, tries, params.Delay)
}
// translate 执行实际翻译操作
func (t *GoogleTranslator) translate(text, from, to string, withVerification bool, tries int, delay time.Duration) (string, error) {
if tries == 0 {
tries = defaultNumberOfRetries
}
if withVerification {
if _, err := language.Parse(from); err != nil && from != "auto" {
log.Println("[WARNING], '" + from + "' is a invalid language, switching to 'auto'")
from = "auto"
}
if _, err := language.Parse(to); err != nil {
log.Println("[WARNING], '" + to + "' is a invalid language, switching to 'en'")
to = "en"
}
}
textValue, _ := otto.ToValue(text)
urlStr := fmt.Sprintf("https://translate.%s/translate_a/single", t.GoogleHost)
token := t.getToken(textValue)
data := map[string]string{
"client": "gtx",
"sl": from,
"tl": to,
"hl": to,
"ie": "UTF-8",
"oe": "UTF-8",
"otf": "1",
"ssel": "0",
"tsel": "0",
"kc": "7",
"q": text,
}
u, err := url.Parse(urlStr)
if err != nil {
return "", err
}
parameters := url.Values{}
for k, v := range data {
parameters.Add(k, v)
}
for _, v := range []string{"at", "bd", "ex", "ld", "md", "qca", "rw", "rm", "ss", "t"} {
parameters.Add("dt", v)
}
parameters.Add("tk", token)
u.RawQuery = parameters.Encode()
var r *http.Response
for tries > 0 {
req, err := http.NewRequest("GET", u.String(), nil)
if err != nil {
return "", err
}
r, err = t.httpClient.Do(req)
if err != nil {
if errors.Is(err, http.ErrHandlerTimeout) {
return "", ErrBadNetwork
}
return "", err
}
if r.StatusCode == http.StatusOK {
break
}
if r.StatusCode == http.StatusForbidden {
tries--
time.Sleep(delay)
}
}
raw, err := io.ReadAll(r.Body)
if err != nil {
return "", err
}
defer r.Body.Close()
var resp []interface{}
err = json.Unmarshal(raw, &resp)
if err != nil {
return "", err
}
responseText := ""
for _, obj := range resp[0].([]interface{}) {
if len(obj.([]interface{})) == 0 {
break
}
t, ok := obj.([]interface{})[0].(string)
if ok {
responseText += t
}
}
return responseText, nil
}
// getToken 获取翻译API所需的token
func (t *GoogleTranslator) getToken(text otto.Value) string {
ttk, err := t.updateTTK()
if err != nil {
return ""
}
tk, err := t.generateToken(text, ttk)
if err != nil {
return ""
}
return strings.Replace(tk.String(), "&tk=", "", -1)
}
// updateTTK 更新TTK值
func (t *GoogleTranslator) updateTTK() (otto.Value, error) {
timestamp := time.Now().UnixNano() / 3600000
now := math.Floor(float64(timestamp))
ttk, err := strconv.ParseFloat(t.ttk.String(), 64)
if err != nil {
return otto.UndefinedValue(), err
}
if ttk == now {
return t.ttk, nil
}
req, err := http.NewRequest("GET", fmt.Sprintf("https://translate.%s", t.GoogleHost), nil)
if err != nil {
return otto.UndefinedValue(), err
}
resp, err := t.httpClient.Do(req)
if err != nil {
return otto.UndefinedValue(), err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return otto.UndefinedValue(), err
}
matches := regexp.MustCompile(`tkk:\s?'(.+?)'`).FindStringSubmatch(string(body))
if len(matches) > 0 {
v, err := otto.ToValue(matches[0])
if err != nil {
return otto.UndefinedValue(), err
}
t.ttk = v
return v, nil
}
return t.ttk, nil
}
// generateToken 生成翻译API所需的token
func (t *GoogleTranslator) generateToken(a otto.Value, TTK otto.Value) (otto.Value, error) {
err := t.vm.Set("x", a)
if err != nil {
return otto.UndefinedValue(), err
}
_ = t.vm.Set("internalTTK", TTK)
result, err := t.vm.Run(`
function sM(a) {
var b;
if (null !== yr)
b = yr;
else {
b = wr(String.fromCharCode(84));
var c = wr(String.fromCharCode(75));
b = [b(), b()];
b[1] = c();
b = (yr = window[b.join(c())] || "") || ""
}
var d = wr(String.fromCharCode(116))
, c = wr(String.fromCharCode(107))
, d = [d(), d()];
d[1] = c();
c = "&" + d.join("") + "=";
d = b.split(".");
b = Number(d[0]) || 0;
for (var e = [], f = 0, g = 0; g < a.length; g++) {
var l = a.charCodeAt(g);
128 > l ? e[f++] = l : (2048 > l ? e[f++] = l >> 6 | 192 : (55296 == (l & 64512) && g + 1 < a.length && 56320 == (a.charCodeAt(g + 1) & 64512) ? (l = 65536 + ((l & 1023) << 10) + (a.charCodeAt(++g) & 1023),
e[f++] = l >> 18 | 240,
e[f++] = l >> 12 & 63 | 128) : e[f++] = l >> 12 | 224,
e[f++] = l >> 6 & 63 | 128),
e[f++] = l & 63 | 128)
}
a = b;
for (f = 0; f < e.length; f++)
a += e[f],
a = xr(a, "+-a^+6");
a = xr(a, "+-3^+b+-f");
a ^= Number(d[1]) || 0;
0 > a && (a = (a & 2147483647) + 2147483648);
a %= 1E6;
return c + (a.toString() + "." + (a ^ b))
}
var yr = null;
var wr = function(a) {
return function() {
return a
}
}
, xr = function(a, b) {
for (var c = 0; c < b.length - 2; c += 3) {
var d = b.charAt(c + 2)
, d = "a" <= d ? d.charCodeAt(0) - 87 : Number(d)
, d = "+" == b.charAt(c + 1) ? a >>> d : a << d;
a = "+" == b.charAt(c) ? a + d & 4294967295 : a ^ d
}
return a
};
var window = {
TKK: internalTTK
};
sM(x)
`)
if err != nil {
return otto.UndefinedValue(), err
}
return result, nil
}