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,277 @@
// Package translator 提供文本翻译功能
package translator
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"regexp"
"strings"
"time"
"golang.org/x/text/language"
)
// BingTranslator Bing翻译器结构体
type BingTranslator struct {
BingHost string // Bing服务主机
httpClient *http.Client // HTTP客户端
Timeout time.Duration // 请求超时时间
}
// 常量定义
const (
bingDefaultTimeout = 30 * time.Second
defaultBingHost = "cn.bing.com" // 使用cn.bing.com作为默认域名
)
// 错误定义
var (
ErrBingNetworkError = errors.New("bing translator network error")
ErrBingParseError = errors.New("bing translator parse error")
ErrBingTokenError = errors.New("failed to get bing translator token")
)
// BingTranslationParams Bing翻译所需的参数
type BingTranslationParams struct {
Token string // token参数
Key string // key参数
IG string // IG参数
}
// NewBingTranslator 创建一个新的Bing翻译器实例
func NewBingTranslator() *BingTranslator {
translator := &BingTranslator{
BingHost: defaultBingHost,
Timeout: bingDefaultTimeout,
httpClient: &http.Client{Timeout: bingDefaultTimeout},
}
return translator
}
// SetTimeout 设置请求超时时间
func (t *BingTranslator) SetTimeout(timeout time.Duration) {
t.Timeout = timeout
t.httpClient.Timeout = timeout
}
// SetBingHost 设置Bing主机
func (t *BingTranslator) SetBingHost(host string) {
t.BingHost = host
}
// Translate 使用标准语言标签进行文本翻译
func (t *BingTranslator) Translate(text string, from language.Tag, to language.Tag) (string, error) {
return t.translate(text, from.String(), to.String())
}
// TranslateWithParams 使用简单字符串参数进行文本翻译
func (t *BingTranslator) TranslateWithParams(text string, params TranslationParams) (string, error) {
return t.translate(text, params.From, params.To)
}
// translate 执行实际翻译操作
func (t *BingTranslator) translate(text, from, to string) (string, error) {
// 获取翻译所需的参数
params, err := t.ExtractBingTranslationParams()
if err != nil {
return "", fmt.Errorf("failed to extract bing translation params: %w", err)
}
// 执行翻译
return t.GetBingTranslation(params.Token, params.Key, params.IG, text, from, to)
}
// ExtractBingTranslationParams 提取Bing翻译所需的参数
func (t *BingTranslator) ExtractBingTranslationParams() (*BingTranslationParams, error) {
// 发送GET请求获取网页内容
url := fmt.Sprintf("https://%s/translator?mkt=zh-CN", t.BingHost)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36")
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
resp, err := t.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrBingNetworkError, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to access Bing translator page: status code %d", resp.StatusCode)
}
// 读取响应内容
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
pageContent := string(body)
// 模式1: 标准的params_AbusePreventionHelper数组
keyPattern := regexp.MustCompile(`params_AbusePreventionHelper\s*=\s*\[([^\]]+)\]`)
keyMatch := keyPattern.FindStringSubmatch(pageContent)
var key, token string
if len(keyMatch) >= 2 {
// 提取并解析数组
paramsStr := keyMatch[1]
paramsList := strings.Split(paramsStr, ",")
if len(paramsList) >= 2 {
// 清理引号
key = strings.Trim(paramsList[0], `"' `)
token = strings.Trim(paramsList[1], `"' `)
}
}
// 如果标准模式失败,尝试备用模式
if key == "" || token == "" {
// 模式2: 查找_G.Token和_G.Key
tokenPattern := regexp.MustCompile(`_G\.Token\s*=\s*["']([^"']+)["']`)
tokenMatch := tokenPattern.FindStringSubmatch(pageContent)
keyPattern := regexp.MustCompile(`_G\.Key\s*=\s*["']?([^"',]+)["']?`)
keyMatch := keyPattern.FindStringSubmatch(pageContent)
if len(tokenMatch) >= 2 && len(keyMatch) >= 2 {
token = tokenMatch[1]
key = keyMatch[1]
}
}
// 如果仍然失败尝试JSON格式
if key == "" || token == "" {
jsonPattern := regexp.MustCompile(`"token"\s*:\s*"([^"]+)"\s*,\s*"key"\s*:\s*"?([^",]+)"?`)
jsonMatch := jsonPattern.FindStringSubmatch(pageContent)
if len(jsonMatch) >= 3 {
token = jsonMatch[1]
key = jsonMatch[2]
}
}
// 如果所有模式都失败
if key == "" || token == "" {
return nil, fmt.Errorf("%w: unable to extract token and key", ErrBingTokenError)
}
// 查找并提取 IG 参数,尝试多种格式
var ig string
// 模式1: 标准IG格式
igPattern := regexp.MustCompile(`IG["']?\s*:\s*["']([^"']+)["']`)
igMatch := igPattern.FindStringSubmatch(pageContent)
if len(igMatch) >= 2 {
ig = igMatch[1]
} else {
// 模式2: 备用IG格式
igPattern = regexp.MustCompile(`"IG"\s*:\s*"([^"]+)"`)
igMatch = igPattern.FindStringSubmatch(pageContent)
if len(igMatch) >= 2 {
ig = igMatch[1]
} else {
// 模式3: _G.IG格式
igPattern = regexp.MustCompile(`_G\.IG\s*=\s*["']([^"']+)["']`)
igMatch = igPattern.FindStringSubmatch(pageContent)
if len(igMatch) >= 2 {
ig = igMatch[1]
}
}
}
// 如果所有IG提取模式都失败
if ig == "" {
return nil, fmt.Errorf("%w: unable to extract IG parameter", ErrBingTokenError)
}
return &BingTranslationParams{
Token: token,
Key: key,
IG: ig,
}, nil
}
// GetBingTranslation 获取Bing翻译结果
func (t *BingTranslator) GetBingTranslation(token, key, ig, text, fromLang, toLang string) (string, error) {
// URL编码文本
encodedText := url.QueryEscape(text)
// 构建POST请求的payload
payload := fmt.Sprintf("fromLang=%s&to=%s&text=%s&token=%s&key=%s",
fromLang, toLang, encodedText, token, key)
// 构建URL
urlStr := fmt.Sprintf("https://%s/ttranslatev3?isVertical=1&IG=%s&IID=translator.5028", t.BingHost, ig)
// 创建请求
req, err := http.NewRequest("POST", urlStr, strings.NewReader(payload))
if err != nil {
return "", fmt.Errorf("failed to create request: %w", err)
}
// 设置请求头
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36")
req.Header.Set("Accept", "*/*")
req.Header.Set("Host", t.BingHost)
req.Header.Set("Connection", "keep-alive")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Origin", fmt.Sprintf("https://%s", t.BingHost))
req.Header.Set("Referer", fmt.Sprintf("https://%s/translator", t.BingHost))
// 发送请求
resp, err := t.httpClient.Do(req)
if err != nil {
return "", fmt.Errorf("%w: %v", ErrBingNetworkError, err)
}
defer resp.Body.Close()
// 判断请求是否成功
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("API error: status code %d", resp.StatusCode)
}
// 读取响应体
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("failed to read response: %w", err)
}
// 检查响应内容
if len(body) == 0 {
return "", fmt.Errorf("translation API returned empty response")
}
// 使用最简单的结构体解析JSON
var response []struct {
Translations []struct {
Text string `json:"text"`
} `json:"translations"`
}
if err := json.Unmarshal(body, &response); err != nil {
return "", fmt.Errorf("%w: JSON parsing error: %v", ErrBingParseError, err)
}
// 检查解析结果
if len(response) == 0 || len(response[0].Translations) == 0 {
return "", fmt.Errorf("%w: invalid response format", ErrBingParseError)
}
// 返回翻译结果
return response[0].Translations[0].Text, nil
}