├── .gitignore ├── .run └── anto.run.xml ├── LICENSE ├── Makefile ├── README.md ├── assets └── images │ ├── settings.jpg │ └── startup.jpg ├── bootstrap ├── boot.go └── resource_builder.go ├── cfg.yml ├── cfg ├── app.go ├── cfg.go └── opts.go ├── cmd ├── anto │ ├── anto.manifest │ ├── app.go │ └── rsrc.syso └── sandbox │ └── main.go ├── common ├── combo_box.go ├── const.go └── type.go ├── cron ├── detector │ ├── data.go │ └── srt_detector.go ├── func.go ├── reader │ ├── data.go │ └── srt_reader.go ├── translate │ ├── data.go │ └── srt_translator.go └── writer │ ├── data.go │ └── srt_writer.go ├── domain ├── repository │ └── translator.go └── service │ └── translator │ ├── ai_baidu │ ├── config.go │ ├── lang.go │ └── translator.go │ ├── ali_cloud_mt │ ├── config.go │ ├── lang.go │ └── translator.go │ ├── baidu │ ├── config.go │ ├── lang.go │ └── translator.go │ ├── caiyunai │ ├── config.go │ ├── lang.go │ └── translator.go │ ├── config.go │ ├── deepl │ ├── config.go │ ├── lang.go │ └── translator.go │ ├── deepl_pro │ ├── config.go │ ├── lang.go │ └── translator.go │ ├── errors.go │ ├── g_deepl_x │ ├── config.go │ ├── lang.go │ └── translator.go │ ├── google_cloud │ ├── config.go │ ├── lang.go │ └── translator.go │ ├── huawei_cloud_nlp │ ├── config.go │ ├── lang.go │ └── translator.go │ ├── ling_va │ ├── config.go │ ├── lang.go │ └── translator.go │ ├── microsoft_edge │ ├── config.go │ ├── lang.go │ └── translator.go │ ├── niutrans │ ├── config.go │ ├── lang.go │ └── translator.go │ ├── openai │ ├── config.go │ ├── lang.go │ └── translator.go │ ├── openai_sweet │ ├── config.go │ ├── lang.go │ └── translator.go │ ├── openapi_youdao │ ├── config.go │ ├── error.go │ ├── lang.go │ └── translator.go │ ├── request.go │ ├── tencent_cloud_mt │ ├── config.go │ ├── lang.go │ └── translator.go │ ├── translator.go │ ├── volcengine │ ├── config.go │ ├── lang.go │ └── translator.go │ ├── xfyun │ ├── config.go │ ├── lang.go │ └── translator.go │ └── youdao │ ├── config.go │ ├── lang.go │ └── translator.go ├── go.mod ├── go.sum ├── lib ├── log │ └── log.go ├── nohup │ └── resident.go ├── restrictor │ └── restrictor.go ├── srt │ ├── block.go │ ├── decode.go │ ├── encode.go │ └── srt.go └── util │ ├── rand.go │ ├── time.go │ └── util.go ├── logo.png ├── platform └── win │ ├── menu.go │ ├── page │ ├── page.go │ ├── settings.go │ └── subtitle_translate.go │ ├── ui │ ├── cfg.go │ ├── event.go │ ├── handle │ │ └── file_dialog.go │ ├── msg │ │ └── msg.go │ ├── notification.go │ ├── pack │ │ ├── ui_checkbox.go │ │ ├── ui_combo_box.go │ │ ├── ui_composite.go │ │ ├── ui_group_box.go │ │ ├── ui_image_view.go │ │ ├── ui_label.go │ │ ├── ui_line_edit.go │ │ ├── ui_push_btn.go │ │ ├── ui_scroll_view.go │ │ ├── ui_text_edit.go │ │ ├── ui_text_label.go │ │ └── widget_group.go │ ├── page.go │ ├── sets.go │ └── ui.go │ └── win.go └── resource ├── favicon.ico └── resource.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | bin 4 | logs -------------------------------------------------------------------------------- /.run/anto.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 speauty 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: tidy deploy_gui_win rs build compress 2 | 3 | tidy: 4 | go mod tidy 5 | 6 | BinName=anto-v3.7.0-windows.exe 7 | 8 | deploy: rs build 9 | 10 | rs: 11 | rsrc -manifest ./cmd/anto/anto.manifest -ico ./resource/favicon.ico -o ./cmd/anto/rsrc.syso 12 | 13 | build: 14 | go build -gcflags='-l -N' -ldflags='-w -s -H=windowsgui' -o "./bin/${BinName}" anto/cmd/anto 15 | 16 | compress: 17 | upx -9 "./bin/${BinName}" 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ANTO-不专业字幕翻译的Windows桌面应用 2 | 3 | > 作为一名临时搬运工,搞个字幕翻译工具,一点也不过分~是吧 4 | > 5 | 6 | 7 | 8 | ### 写在前面 9 | 10 | 我是21年底才接触到油管搬运。不知出于什么原因,看了UP主[@神经元猫](https://space.bilibili.com/364152971/?spm_id_from=333.999.0.0)搬运翻译的[Cherno C++ 中文](https://space.bilibili.com/364152971/channel/collectiondetail?sid=13909),感觉[TheCherno](https://github.com/TheCherno)挺有趣的,就萌生了搬运他的OpenGL教程,然后就是游戏引擎等。刚开始,特别费人,使用剪映的智能字幕进行音频识别并导出SRT字幕。 11 | 12 | 由于分段很乱,需要人工调整,然后挨个翻译。遇到陌生单词,还要去查一下。但是吧,后来觉得效率低得令人发指。既然有智能字幕,那么就有智能翻译,然后,就了解到了各种机器翻译。看着每月的免费额度,完全可以操作一下。那么,就开始着手这款应用的研发。并不复杂,初版几天就搞定了,主要还是对框架不太熟悉,而且有些场景不太了解,大多都是后面慢慢完善。 13 | 14 | 用,是能用了,但是,应该还有很大提升的空间。比如,有朋友提到的AI翻译,以及通知和托盘等。 15 | 16 | 加油~ 17 | 18 | ### 任务列表 19 | 20 | - [x] 集成lxn/walk桌面GUI库(才把fyne清理了,那个编译运行太慢了,而且Win下,依赖环境有点。。。),这个要抽一波,不然用起来,小难受; 21 | - [x] 实现静态界面(还是不大会多窗口模式,估计会照旧采用Widget的显示来模拟多页面,也可能还是单页); 22 | - [x] 常用配置本地缓存(文件),就我最近的使用体验来看,不想再输密钥了; 23 | - [x] 增加全量和增量翻译模式区分,防止重复翻译,浪费资源; 24 | - [x] 增加批量翻译模式(挂机,一直跑),那个列表有点小烦,后面没有单独放置操作的列; 25 | - [x] 实现srt文件解析和压缩(这块好搞,我感觉都写了好几次了); 26 | - [x] 接入上游翻译服务(如果有什么好的免费的服务,也可以给我说); 27 | 28 | ### 应用预览 29 | 30 | ![应用启动](./assets/images/startup.jpg) 31 | 32 | ![设置界面](./assets/images/settings.jpg) 33 | 34 | -------------------------------------------------------------------------------- /assets/images/settings.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/speauty/anto/d537a1ad2b935ec8c6973912e1a5ed919d932ea7/assets/images/settings.jpg -------------------------------------------------------------------------------- /assets/images/startup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/speauty/anto/d537a1ad2b935ec8c6973912e1a5ed919d932ea7/assets/images/startup.jpg -------------------------------------------------------------------------------- /bootstrap/boot.go: -------------------------------------------------------------------------------- 1 | package bootstrap 2 | 3 | import ( 4 | "anto/cfg" 5 | "anto/common" 6 | "anto/domain/repository" 7 | "anto/domain/service/translator/ai_baidu" 8 | "anto/domain/service/translator/ali_cloud_mt" 9 | "anto/domain/service/translator/baidu" 10 | "anto/domain/service/translator/caiyunai" 11 | "anto/domain/service/translator/deepl" 12 | "anto/domain/service/translator/deepl_pro" 13 | "anto/domain/service/translator/g_deepl_x" 14 | "anto/domain/service/translator/google_cloud" 15 | "anto/domain/service/translator/huawei_cloud_nlp" 16 | "anto/domain/service/translator/microsoft_edge" 17 | "anto/domain/service/translator/niutrans" 18 | "anto/domain/service/translator/openai" 19 | "anto/domain/service/translator/openai_sweet" 20 | "anto/domain/service/translator/openapi_youdao" 21 | "anto/domain/service/translator/tencent_cloud_mt" 22 | "anto/domain/service/translator/volcengine" 23 | "anto/domain/service/translator/xfyun" 24 | "anto/lib/log" 25 | "context" 26 | ) 27 | 28 | func Boot(_ context.Context) { 29 | new(ResourceBuilder).Install() 30 | 31 | if err := cfg.Singleton().Load(); err != nil { 32 | panic(err) 33 | } 34 | 35 | cfg.Singleton().App.Author = common.Author 36 | cfg.Singleton().App.Version = common.Version 37 | log.Singleton() 38 | 39 | cfg.Singleton().UI.Title = cfg.Singleton().NewUITitle() 40 | 41 | huawei_cloud_nlp.API().Init(cfg.Singleton().HuaweiCloudNlp) 42 | baidu.API().Init(cfg.Singleton().Baidu) 43 | tencent_cloud_mt.API().Init(cfg.Singleton().TencentCloudMT) 44 | openapi_youdao.API().Init(cfg.Singleton().OpenAPIYouDao) 45 | ali_cloud_mt.API().Init(cfg.Singleton().AliCloudMT) 46 | caiyunai.API().Init(cfg.Singleton().CaiYunAI) 47 | niutrans.API().Init(cfg.Singleton().Niutrans) 48 | volcengine.API().Init(cfg.Singleton().VolcEngine) 49 | g_deepl_x.API().Init(cfg.Singleton().YouDao) 50 | google_cloud.API().Init(cfg.Singleton().GoogleCloud) 51 | openai.API().Init(cfg.Singleton().OpenAI) 52 | openai_sweet.API().Init(cfg.Singleton().OpenAISweet) 53 | deepl.API().Init(cfg.Singleton().DeepL) 54 | deepl_pro.API().Init(cfg.Singleton().DeepLPro) 55 | ai_baidu.API().Init(cfg.Singleton().AiBaidu) 56 | microsoft_edge.API().Init(cfg.Singleton().MicrosoftEdge) 57 | xfyun.API().Init(cfg.Singleton().XFYun) 58 | 59 | repository.GetTranslators().Register( 60 | huawei_cloud_nlp.API(), baidu.API(), 61 | tencent_cloud_mt.API(), openapi_youdao.API(), 62 | ali_cloud_mt.API(), caiyunai.API(), niutrans.API(), 63 | volcengine.API(), g_deepl_x.API(), 64 | google_cloud.API(), openai.API(), deepl.API(), deepl_pro.API(), 65 | openai_sweet.API(), ai_baidu.API(), microsoft_edge.API(), 66 | xfyun.API(), 67 | ) 68 | } 69 | -------------------------------------------------------------------------------- /bootstrap/resource_builder.go: -------------------------------------------------------------------------------- 1 | package bootstrap 2 | 3 | import ( 4 | "anto/cfg" 5 | "anto/resource" 6 | "fmt" 7 | "os" 8 | ) 9 | 10 | type ResourceBuilder struct { 11 | } 12 | 13 | func (customRB *ResourceBuilder) Install() { 14 | customRB.installICO() 15 | customRB.installDirLogs() 16 | customRB.installCfgYml() 17 | } 18 | 19 | func (customRB *ResourceBuilder) installDirLogs() { 20 | dirname := "logs" 21 | fd, err := os.Stat(dirname) 22 | if err == nil && (fd != nil && fd.IsDir()) { 23 | return 24 | } 25 | if err := os.Mkdir(dirname, os.ModePerm); err != nil { 26 | panic(fmt.Errorf("创建日志目录失败(如果存在相应logs文件, 请手动处理), 错误: %s", err)) 27 | } 28 | } 29 | 30 | func (customRB *ResourceBuilder) installCfgYml() { 31 | filename := "cfg.yml" 32 | _, err := os.Stat(filename) 33 | if err == nil { 34 | return 35 | } 36 | fd, err := os.Create(filename) 37 | if err != nil { 38 | panic(fmt.Errorf("创建配置文件失败, 错误: %s", err)) 39 | } 40 | fd.Close() 41 | 42 | if err := cfg.Singleton().InitConfig(); err != nil { 43 | panic(fmt.Errorf("写入默认配置失败, 错误: %s", err)) 44 | } 45 | } 46 | 47 | func (customRB *ResourceBuilder) installICO() { 48 | filename := "favicon.ico" 49 | _, err := os.Stat(filename) 50 | if err == nil { 51 | return 52 | } 53 | fd, err := os.Create(filename) 54 | if err != nil { 55 | panic(fmt.Errorf("创建图标文件失败, 错误: %s", err)) 56 | } 57 | 58 | if _, err = fd.Write(resource.Favicon); err != nil { 59 | panic(fmt.Errorf("写入图标文件失败, 错误: %s", err)) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /cfg.yml: -------------------------------------------------------------------------------- 1 | huawei_cloud_nlp: # 华为云机器翻译 2 | ak_id: "" # Access Key 3 | sk_key: "" # Secret Access Key 4 | region: "cn-north-4" # 当前接口开发的区域, 目前仅支持华北-北京四终端节点 5 | project_id: "" # 项目ID 6 | max_single_text_length: 2000 # 单次翻译最大长度 7 | 8 | ling_va: 9 | data_id: "3qnDcUVykFKnSC3cdRX2t" # 数据ID 10 | max_single_text_length: 1000 # 单次翻译最大长度 11 | 12 | baidu: 13 | app_id: "" # 应用ID 14 | app_key: "" # 应用密钥 15 | max_single_text_length: 1000 # 单次翻译最大长度 16 | 17 | tencent_cloud_mt: # 腾讯云机器翻译 18 | secret_id: "" # 密钥ID 用于标识接口调用者身份 19 | secret_key: "" # 密钥关键字 用于验证接口调用者的身份 20 | max_single_text_length: 2000 # 单次翻译最大长度 21 | 22 | openapi_youdao: # 有道智云翻译 23 | app_key: "" # 应用ID 24 | app_secret: "" # 应用密钥 25 | max_single_text_length: 5000 # 单次翻译最大长度 26 | 27 | ali_cloud_mt: # 阿里云翻译 28 | ak_id: "" # 应用ID 29 | ak_secret: "" # 应用密钥 30 | region: "" # 区域 31 | max_single_text_length: 3000 # 单次翻译最大长度 32 | 33 | caiyun_ai: # 彩云小翻译 34 | token: "" # 密钥 35 | max_single_text_length: 5000 # 单次翻译最大长度 36 | 37 | niutrans: 38 | app_key: 39 | 40 | volc_engine: 41 | access_key: 42 | secret_key: -------------------------------------------------------------------------------- /cfg/app.go: -------------------------------------------------------------------------------- 1 | package cfg 2 | 3 | import ( 4 | _const "anto/common" 5 | ) 6 | 7 | type App struct { 8 | Author string `mapstructure:"author"` 9 | Version string `mapstructure:"version"` 10 | } 11 | 12 | func (customA App) Default() *App { 13 | return &App{ 14 | Author: _const.Author, 15 | Version: _const.Version, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /cfg/cfg.go: -------------------------------------------------------------------------------- 1 | package cfg 2 | 3 | import ( 4 | "anto/domain/service/translator/ai_baidu" 5 | "anto/domain/service/translator/ali_cloud_mt" 6 | "anto/domain/service/translator/baidu" 7 | "anto/domain/service/translator/caiyunai" 8 | "anto/domain/service/translator/deepl" 9 | "anto/domain/service/translator/deepl_pro" 10 | "anto/domain/service/translator/g_deepl_x" 11 | "anto/domain/service/translator/google_cloud" 12 | "anto/domain/service/translator/huawei_cloud_nlp" 13 | "anto/domain/service/translator/ling_va" 14 | "anto/domain/service/translator/microsoft_edge" 15 | "anto/domain/service/translator/niutrans" 16 | "anto/domain/service/translator/openai" 17 | "anto/domain/service/translator/openai_sweet" 18 | "anto/domain/service/translator/openapi_youdao" 19 | "anto/domain/service/translator/tencent_cloud_mt" 20 | "anto/domain/service/translator/volcengine" 21 | "anto/domain/service/translator/xfyun" 22 | "anto/domain/service/translator/youdao" 23 | "anto/platform/win/ui" 24 | "fmt" 25 | "github.com/spf13/viper" 26 | "sync" 27 | ) 28 | 29 | var ( 30 | apiSingleton *Cfg 31 | onceSingleton sync.Once 32 | ) 33 | 34 | func Singleton() *Cfg { 35 | onceSingleton.Do(func() { 36 | apiSingleton = new(Cfg) 37 | apiSingleton.App = App{}.Default() 38 | apiSingleton.UI = ui.Cfg{}.Default() 39 | apiSingleton.HuaweiCloudNlp = new(huawei_cloud_nlp.Config).Default().(*huawei_cloud_nlp.Config) 40 | apiSingleton.LingVA = new(ling_va.Config).Default().(*ling_va.Config) 41 | apiSingleton.Baidu = new(baidu.Config).Default().(*baidu.Config) 42 | apiSingleton.TencentCloudMT = new(tencent_cloud_mt.Config).Default().(*tencent_cloud_mt.Config) 43 | apiSingleton.OpenAPIYouDao = new(openapi_youdao.Config).Default().(*openapi_youdao.Config) 44 | apiSingleton.AliCloudMT = new(ali_cloud_mt.Config).Default().(*ali_cloud_mt.Config) 45 | apiSingleton.CaiYunAI = new(caiyunai.Config).Default().(*caiyunai.Config) 46 | apiSingleton.Niutrans = new(niutrans.Config).Default().(*niutrans.Config) 47 | apiSingleton.VolcEngine = new(volcengine.Config).Default().(*volcengine.Config) 48 | apiSingleton.YouDao = new(youdao.Config).Default().(*youdao.Config) 49 | apiSingleton.GoogleCloud = new(google_cloud.Config).Default().(*google_cloud.Config) 50 | apiSingleton.OpenAI = new(openai.Config).Default().(*openai.Config) 51 | apiSingleton.OpenAISweet = new(openai_sweet.Config).Default().(*openai_sweet.Config) 52 | apiSingleton.DeepL = new(deepl.Config).Default().(*deepl.Config) 53 | apiSingleton.DeepLPro = new(deepl_pro.Config).Default().(*deepl_pro.Config) 54 | apiSingleton.AiBaidu = new(ai_baidu.Config).Default().(*ai_baidu.Config) 55 | apiSingleton.MicrosoftEdge = new(microsoft_edge.Config).Default().(*microsoft_edge.Config) 56 | apiSingleton.XFYun = new(xfyun.Config).Default().(*xfyun.Config) 57 | }) 58 | return apiSingleton 59 | } 60 | 61 | type Cfg struct { 62 | App *App `mapstructure:"-"` 63 | UI *ui.Cfg `mapstructure:"-"` 64 | HuaweiCloudNlp *huawei_cloud_nlp.Config `mapstructure:"huawei_cloud_nlp"` 65 | LingVA *ling_va.Config `mapstructure:"ling_va"` 66 | Baidu *baidu.Config `mapstructure:"baidu"` 67 | TencentCloudMT *tencent_cloud_mt.Config `mapstructure:"tencent_cloud_mt"` 68 | OpenAPIYouDao *openapi_youdao.Config `mapstructure:"openapi_youdao"` 69 | AliCloudMT *ali_cloud_mt.Config `mapstructure:"ali_cloud_mt"` 70 | CaiYunAI *caiyunai.Config `mapstructure:"caiyun_ai"` 71 | Niutrans *niutrans.Config `mapstructure:"niutrans"` 72 | VolcEngine *volcengine.Config `mapstructure:"volc_engine"` 73 | YouDao *youdao.Config `mapstructure:"youdao"` 74 | GDeeplX *g_deepl_x.Config `mapstructure:"g_deepl_x"` 75 | GoogleCloud *google_cloud.Config `mapstructure:"google_cloud"` 76 | OpenAI *openai.Config `mapstructure:"openai"` 77 | OpenAISweet *openai_sweet.Config `mapstructure:"openai_sweet"` 78 | DeepL *deepl.Config `mapstructure:"deepl"` 79 | DeepLPro *deepl_pro.Config `mapstructure:"deepl_pro"` 80 | AiBaidu *ai_baidu.Config `mapstructure:"ai_baidu"` 81 | MicrosoftEdge *microsoft_edge.Config `mapstructure:"microsoft_edge"` 82 | XFYun *xfyun.Config `mapstructure:"xfyun"` 83 | 84 | currentViper *viper.Viper `mapstructure:"-"` 85 | } 86 | 87 | func (customC *Cfg) NewUITitle() string { 88 | return fmt.Sprintf( 89 | "%s-%s speauty@163.com", 90 | customC.UI.Title, customC.App.Version, 91 | ) 92 | } 93 | -------------------------------------------------------------------------------- /cfg/opts.go: -------------------------------------------------------------------------------- 1 | package cfg 2 | 3 | import ( 4 | "fmt" 5 | "github.com/spf13/viper" 6 | ) 7 | 8 | func (customC *Cfg) GetViper() *viper.Viper { 9 | if customC.currentViper == nil { 10 | customC.currentViper = viper.New() 11 | customC.currentViper.AddConfigPath("./") 12 | customC.currentViper.SetConfigType("yml") 13 | customC.currentViper.SetConfigName("cfg") 14 | } 15 | return customC.currentViper 16 | } 17 | 18 | func (customC *Cfg) Load() error { 19 | currentViper := customC.GetViper() 20 | currentViper.AutomaticEnv() 21 | 22 | if err := currentViper.ReadInConfig(); err != nil { 23 | return fmt.Errorf("当前配置加载失败, 错误: %s", err) 24 | } 25 | 26 | if err := currentViper.Unmarshal(customC); err != nil { 27 | return fmt.Errorf("当前配置解析失败, 错误: %s", err) 28 | } 29 | return nil 30 | } 31 | 32 | func (customC *Cfg) InitConfig() error { 33 | currentViper := customC.GetViper() 34 | 35 | _ = customC.HuaweiCloudNlp.SyncDisk(currentViper) 36 | _ = customC.LingVA.SyncDisk(currentViper) 37 | _ = customC.Baidu.SyncDisk(currentViper) 38 | _ = customC.TencentCloudMT.SyncDisk(currentViper) 39 | _ = customC.OpenAPIYouDao.SyncDisk(currentViper) 40 | _ = customC.AliCloudMT.SyncDisk(currentViper) 41 | _ = customC.CaiYunAI.SyncDisk(currentViper) 42 | _ = customC.Niutrans.SyncDisk(currentViper) 43 | _ = customC.VolcEngine.SyncDisk(currentViper) 44 | _ = customC.YouDao.SyncDisk(currentViper) 45 | _ = customC.AiBaidu.SyncDisk(currentViper) 46 | _ = customC.MicrosoftEdge.SyncDisk(currentViper) 47 | _ = customC.XFYun.SyncDisk(currentViper) 48 | 49 | return customC.Sync() 50 | } 51 | 52 | func (customC *Cfg) Sync() error { 53 | return customC.GetViper().WriteConfig() 54 | } 55 | -------------------------------------------------------------------------------- /cmd/anto/anto.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | PerMonitorV2, PerMonitor 12 | True 13 | 14 | 15 | -------------------------------------------------------------------------------- /cmd/anto/app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "anto/bootstrap" 5 | "anto/platform/win" 6 | "context" 7 | ) 8 | 9 | func main() { 10 | ctx := context.Background() 11 | bootstrap.Boot(ctx) 12 | win.Run(ctx) 13 | } 14 | -------------------------------------------------------------------------------- /cmd/anto/rsrc.syso: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/speauty/anto/d537a1ad2b935ec8c6973912e1a5ed919d932ea7/cmd/anto/rsrc.syso -------------------------------------------------------------------------------- /cmd/sandbox/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/imroc/req/v3" 5 | ) 6 | 7 | func main() { 8 | testClient := req.C() 9 | testClient.SetProxyURL("sock5://hello:1415456@165.154.3.235:4532") 10 | 11 | res, err := testClient.R().Get("https://www.google.com") 12 | if err != nil { 13 | panic(err) 14 | } 15 | print(res.Status) 16 | } 17 | -------------------------------------------------------------------------------- /common/combo_box.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | type StdComboBoxModel struct { 4 | Key string 5 | Name string 6 | } 7 | 8 | func (customSTD *StdComboBoxModel) BindKey() string { 9 | return "Key" 10 | } 11 | 12 | func (customSTD *StdComboBoxModel) DisplayKey() string { 13 | return "Name" 14 | } 15 | -------------------------------------------------------------------------------- /common/const.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | const ( 4 | AppName = "anto" 5 | Author = "speauty" 6 | Version = "v3.7.0" 7 | DownloadLatestVersionUrl = "http://kodo.app.speauty.cn/anto-latest-windows.exe" 8 | 9 | GoUidLen = 8 10 | 11 | UITitle = "anto" 12 | UIIcon = "favicon.ico" 13 | UIResourceDir = "./" 14 | ) 15 | -------------------------------------------------------------------------------- /common/type.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | const ( 4 | ModeFull TranslateMode = "全量翻译" 5 | ModeDelta TranslateMode = "增量翻译" 6 | ) 7 | 8 | var modes = []TranslateMode{ModeFull, ModeDelta} 9 | 10 | type TranslateMode string 11 | 12 | func (customType TranslateMode) String() string { 13 | return string(customType) 14 | } 15 | 16 | func (customType TranslateMode) GetModes() []string { // ComboBox不支持typedef么? 17 | var strModes []string 18 | for _, mode := range modes { 19 | strModes = append(strModes, mode.String()) 20 | } 21 | return strModes 22 | } 23 | 24 | func (customType TranslateMode) GetIdx() int { 25 | for idx, mode := range modes { 26 | if customType == mode { 27 | return idx 28 | } 29 | } 30 | return 0 31 | } 32 | 33 | const ( 34 | LangDirectionFrom LangDirection = "来源语种" 35 | LangDirectionTo LangDirection = "目标语种" 36 | ) 37 | 38 | var langDirectionTypes = []LangDirection{LangDirectionFrom, LangDirectionTo} 39 | 40 | type LangDirection string 41 | 42 | func (customType LangDirection) String() string { 43 | return string(customType) 44 | } 45 | 46 | func (customType LangDirection) GetDirections() []string { // ComboBox不支持typedef么? 47 | var strLangDirections []string 48 | for _, langDirection := range langDirectionTypes { 49 | strLangDirections = append(strLangDirections, langDirection.String()) 50 | } 51 | return strLangDirections 52 | } 53 | 54 | func (customType LangDirection) GetIdx() int { 55 | for idx, langDirection := range langDirectionTypes { 56 | if customType == langDirection { 57 | return idx 58 | } 59 | } 60 | return 0 61 | } 62 | -------------------------------------------------------------------------------- /cron/detector/data.go: -------------------------------------------------------------------------------- 1 | package detector 2 | 3 | import ( 4 | "anto/common" 5 | "anto/cron/reader" 6 | "anto/cron/translate" 7 | "anto/domain/service/translator" 8 | ) 9 | 10 | type StrDetectorData struct { 11 | Translator translator.ImplTranslator 12 | FromLang string 13 | ToLang string 14 | TranslateMode common.TranslateMode 15 | MainTrackReport common.LangDirection 16 | SrtFile string 17 | SrtDir string 18 | FlagTrackExport int 19 | } 20 | 21 | func (customData StrDetectorData) toReaderData(filePath string) *reader.SrtReaderData { 22 | return &reader.SrtReaderData{ 23 | FilePath: filePath, 24 | PtrTranslatorOpts: &translate.SrtTranslateOpts{ 25 | Translator: customData.Translator, 26 | FromLang: customData.FromLang, ToLang: customData.ToLang, 27 | TranslateMode: customData.TranslateMode, MainTrackReport: customData.MainTrackReport, 28 | FlagTrackExport: customData.FlagTrackExport, 29 | }, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /cron/detector/srt_detector.go: -------------------------------------------------------------------------------- 1 | // Package detector 检测器 2 | package detector 3 | 4 | import ( 5 | _type "anto/common" 6 | "anto/cron" 7 | "anto/cron/reader" 8 | "anto/lib/log" 9 | "anto/lib/util" 10 | "context" 11 | "fmt" 12 | "io/fs" 13 | "path/filepath" 14 | "runtime" 15 | "strings" 16 | "sync" 17 | 18 | "github.com/golang-module/carbon" 19 | ) 20 | 21 | const ( 22 | cronName = "SRT检测程序" 23 | numChanData = 10 24 | numChanMsg = 20 25 | numCoroutine = 5 26 | ) 27 | 28 | var ( 29 | apiSingleton *SrtDetector 30 | onceSingleton sync.Once 31 | ) 32 | 33 | func Singleton() *SrtDetector { 34 | onceSingleton.Do(func() { 35 | apiSingleton = new(SrtDetector) 36 | apiSingleton.init() 37 | }) 38 | return apiSingleton 39 | } 40 | 41 | type SrtDetector struct { 42 | ctx context.Context 43 | ctxFnCancel context.CancelFunc 44 | chanData chan *StrDetectorData 45 | chanMsg chan string 46 | chanMsgRedirect chan string 47 | numCoroutine int 48 | } 49 | 50 | func (customCron *SrtDetector) Push(data *StrDetectorData) { 51 | customCron.chanData <- data 52 | } 53 | 54 | func (customCron *SrtDetector) Run(ctx context.Context, fnCancel context.CancelFunc) { 55 | customCron.ctx = ctx 56 | customCron.ctxFnCancel = fnCancel 57 | 58 | customCron.jobDetector() 59 | customCron.jobMsg() 60 | } 61 | 62 | func (customCron *SrtDetector) Close() {} 63 | 64 | func (customCron *SrtDetector) SetMsgRedirect(chanMsg chan string) { 65 | customCron.chanMsgRedirect = chanMsg 66 | } 67 | 68 | func (customCron *SrtDetector) jobDetector() { 69 | if customCron.numCoroutine <= 0 { 70 | customCron.log().WarnF("%s-%s通道的最大数量(%d)无效, 重置为5", cronName, "chanData", customCron.numCoroutine) 71 | customCron.numCoroutine = 5 72 | } 73 | 74 | for idx := 0; idx < customCron.numCoroutine; idx++ { 75 | go func(ctx context.Context, chanDetector chan *StrDetectorData, chanMsg chan string, idx int) { 76 | coroutineName := fmt.Sprintf("检测协程(%d)", idx) 77 | for { 78 | select { 79 | case <-ctx.Done(): 80 | customCron.log().WarnF("%s关闭(ctx.done), %s被迫退出", cronName, coroutineName) 81 | runtime.Goexit() 82 | case currentData, isOpen := <-chanDetector: 83 | timeStart := carbon.Now() 84 | if !isOpen { 85 | customCron.log().WarnF("%s-通道关闭, %s被迫退出", cronName, coroutineName) 86 | runtime.Goexit() 87 | } 88 | if currentData.SrtFile != "" { 89 | if !strings.Contains(currentData.SrtFile, _type.AppName) && len(currentData.SrtFile) > 4 && currentData.SrtFile[len(currentData.SrtFile)-4:] == ".srt" { 90 | reader.Singleton().Push(currentData.toReaderData(currentData.SrtFile)) 91 | chanMsg <- fmt.Sprintf("检测到文件(%s), 耗时(s): %d", currentData.SrtFile, util.GetSecondsFromTime(timeStart)) 92 | } 93 | } 94 | if currentData.SrtDir == "" { 95 | continue 96 | } 97 | _ = filepath.Walk(currentData.SrtDir, func(path string, info fs.FileInfo, err error) error { 98 | if info.IsDir() { 99 | if path != currentData.SrtDir { 100 | return filepath.SkipDir 101 | } 102 | return nil 103 | } 104 | if !util.IsSrtFile(path) || strings.Contains(path, _type.AppName) || info.Size() == 0 { 105 | return nil 106 | } 107 | reader.Singleton().Push(currentData.toReaderData(path)) 108 | chanMsg <- fmt.Sprintf("检测到文件(%s), 耗时(s): %d", path, util.GetSecondsFromTime(timeStart)) 109 | return nil 110 | }) 111 | } 112 | } 113 | }(customCron.ctx, customCron.chanData, customCron.chanMsg, idx) 114 | } 115 | } 116 | 117 | func (customCron *SrtDetector) jobMsg() { 118 | cron.FuncSrtCronMsgRedirect(customCron.ctx, cronName, customCron.log(), customCron.chanMsg, customCron.chanMsgRedirect) 119 | } 120 | 121 | func (customCron *SrtDetector) init() { 122 | customCron.chanData = make(chan *StrDetectorData, numChanData) 123 | customCron.chanMsg = make(chan string, numChanMsg) 124 | customCron.numCoroutine = numCoroutine 125 | } 126 | 127 | func (customCron *SrtDetector) log() *log.Log { 128 | return log.Singleton() 129 | } 130 | -------------------------------------------------------------------------------- /cron/func.go: -------------------------------------------------------------------------------- 1 | package cron 2 | 3 | import ( 4 | "anto/lib/log" 5 | "anto/lib/util" 6 | "context" 7 | "fmt" 8 | "runtime" 9 | ) 10 | 11 | func FuncSrtCronMsgRedirect(ctx context.Context, cronName string, ptrLog *log.Log, chanMsg, chanMsgRedirect chan string) { 12 | go func(ctx context.Context, localChanMsg, localChanMsgRedirect chan string) { 13 | coroutineName := "消息协程" 14 | chanName := "chanMsg" 15 | for true { 16 | select { 17 | case <-ctx.Done(): 18 | ptrLog.WarnF("%s关闭(ctx.done), %s被迫退出", cronName, coroutineName) 19 | runtime.Goexit() 20 | case currentMsg, isOpen := <-localChanMsg: 21 | if isOpen == false && currentMsg == "" { 22 | ptrLog.WarnF("%s-%s通道关闭, %s被迫退出", cronName, chanName, coroutineName) 23 | runtime.Goexit() 24 | } 25 | if localChanMsgRedirect != nil { 26 | localChanMsgRedirect <- fmt.Sprintf("时间: %s, 来源: %s, 信息: %s", util.GetDateTime(), cronName, currentMsg) 27 | } 28 | } 29 | } 30 | }(ctx, chanMsg, chanMsgRedirect) 31 | } 32 | -------------------------------------------------------------------------------- /cron/reader/data.go: -------------------------------------------------------------------------------- 1 | package reader 2 | 3 | import ( 4 | "anto/cron/translate" 5 | "anto/lib/srt" 6 | ) 7 | 8 | type SrtReaderData struct { 9 | FilePath string 10 | PrtSrt *srt.Srt 11 | PtrTranslatorOpts *translate.SrtTranslateOpts 12 | } 13 | 14 | func (customData *SrtReaderData) toTranslateData() *translate.SrtTranslateData { 15 | return &translate.SrtTranslateData{ 16 | PrtSrt: customData.PrtSrt, 17 | PtrOpts: customData.PtrTranslatorOpts, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /cron/reader/srt_reader.go: -------------------------------------------------------------------------------- 1 | package reader 2 | 3 | import ( 4 | "anto/cron" 5 | "anto/cron/translate" 6 | "anto/lib/log" 7 | "anto/lib/srt" 8 | "anto/lib/util" 9 | "bytes" 10 | "context" 11 | "fmt" 12 | "github.com/golang-module/carbon" 13 | "io" 14 | "os" 15 | "runtime" 16 | "sync" 17 | ) 18 | 19 | const ( 20 | cronName = "SRT读取程序" 21 | numChanData = 10 22 | numChanMsg = 20 23 | numCoroutine = 10 24 | ) 25 | 26 | var ( 27 | apiSingleton *SrtReader 28 | onceSingleton sync.Once 29 | ) 30 | 31 | func Singleton() *SrtReader { 32 | onceSingleton.Do(func() { 33 | apiSingleton = new(SrtReader) 34 | apiSingleton.init() 35 | }) 36 | return apiSingleton 37 | } 38 | 39 | type SrtReader struct { 40 | ctx context.Context 41 | ctxFnCancel context.CancelFunc 42 | chanData chan *SrtReaderData 43 | chanMsg chan string 44 | chanMsgRedirect chan string 45 | numCoroutine int 46 | } 47 | 48 | func (customCron *SrtReader) Push(data *SrtReaderData) { 49 | customCron.chanData <- data 50 | } 51 | 52 | func (customCron *SrtReader) Run(ctx context.Context, fnCancel context.CancelFunc) { 53 | customCron.ctx = ctx 54 | customCron.ctxFnCancel = fnCancel 55 | 56 | customCron.jobReader() 57 | customCron.jobMsg() 58 | } 59 | 60 | func (customCron *SrtReader) Close() {} 61 | 62 | func (customCron *SrtReader) SetMsgRedirect(chanMsg chan string) { 63 | customCron.chanMsgRedirect = chanMsg 64 | } 65 | 66 | func (customCron *SrtReader) jobReader() { 67 | if customCron.numCoroutine <= 0 { 68 | customCron.log().WarnF("%s-%s通道的最大数量(%d)无效, 重置为5", cronName, "chanData", customCron.numCoroutine) 69 | customCron.numCoroutine = 5 70 | } 71 | 72 | for idx := 0; idx < customCron.numCoroutine; idx++ { 73 | go func(ctx context.Context, chanReader chan *SrtReaderData, chanMsg chan string, idx int) { 74 | coroutineName := fmt.Sprintf("读取协程(%d)", idx) 75 | for true { 76 | select { 77 | case <-ctx.Done(): 78 | customCron.log().WarnF("%s关闭(ctx.done), %s被迫退出", cronName, coroutineName) 79 | runtime.Goexit() 80 | case currentData, isOpen := <-chanReader: 81 | timeStart := carbon.Now() 82 | if isOpen == false && currentData == nil { 83 | customCron.log().WarnF("%s-通道关闭, %s被迫退出", cronName, coroutineName) 84 | runtime.Goexit() 85 | } 86 | 87 | if currentData.FilePath == "" || currentData.PtrTranslatorOpts == nil { 88 | chanMsg <- "当前数据包无效, 即将丢弃" 89 | continue 90 | } 91 | fileFd, err := os.Open(currentData.FilePath) 92 | if err != nil { 93 | msg := fmt.Sprintf("打开文件(%s)异常, 错误: %s, 即将丢弃", currentData.FilePath, err) 94 | customCron.log().Error(msg) 95 | chanMsg <- msg 96 | continue 97 | } 98 | bytesRead, err := io.ReadAll(fileFd) 99 | if err != nil { 100 | _ = fileFd.Close() 101 | msg := fmt.Sprintf("读取文件(%s)异常, 错误: %s, 即将丢弃", currentData.FilePath, err) 102 | customCron.log().Error(msg) 103 | chanMsg <- msg 104 | continue 105 | } 106 | _ = fileFd.Close() 107 | 108 | currentData.PrtSrt = new(srt.Srt) 109 | currentData.PrtSrt.FilePath = currentData.FilePath 110 | currentData.PrtSrt.FileSize = len(bytesRead) 111 | currentData.PrtSrt.FileNameSync() 112 | 113 | if err = currentData.PrtSrt.Decode(bytes.NewReader(bytesRead)); err != nil { 114 | msg := fmt.Sprintf("解析文件(%s)异常, 错误: %s, 即将丢弃", currentData.FilePath, err) 115 | customCron.log().Error(msg) 116 | chanMsg <- msg 117 | continue 118 | } 119 | 120 | currentData.PrtSrt.CntBlock = len(currentData.PrtSrt.Blocks) 121 | translate.Singleton().Push(currentData.toTranslateData()) 122 | 123 | chanMsg <- fmt.Sprintf( 124 | "读取文件(%s)成功, 文件名: %s, 字幕块: %d, 文件大小: %d, 耗时: %d", 125 | currentData.FilePath, currentData.PrtSrt.FileName, currentData.PrtSrt.CntBlock, 126 | currentData.PrtSrt.FileSize, util.GetSecondsFromTime(timeStart), 127 | ) 128 | } 129 | } 130 | }(customCron.ctx, customCron.chanData, customCron.chanMsg, idx) 131 | } 132 | } 133 | 134 | func (customCron *SrtReader) jobMsg() { 135 | cron.FuncSrtCronMsgRedirect(customCron.ctx, cronName, customCron.log(), customCron.chanMsg, customCron.chanMsgRedirect) 136 | } 137 | 138 | func (customCron *SrtReader) init() { 139 | customCron.chanData = make(chan *SrtReaderData, numChanData) 140 | customCron.chanMsg = make(chan string, numChanMsg) 141 | customCron.numCoroutine = numCoroutine 142 | } 143 | 144 | func (customCron *SrtReader) log() *log.Log { 145 | return log.Singleton() 146 | } 147 | -------------------------------------------------------------------------------- /cron/translate/data.go: -------------------------------------------------------------------------------- 1 | package translate 2 | 3 | import ( 4 | _const "anto/common" 5 | "anto/cron/writer" 6 | "anto/domain/service/translator" 7 | "anto/lib/srt" 8 | "fmt" 9 | "path/filepath" 10 | ) 11 | 12 | type SrtTranslateData struct { 13 | PrtSrt *srt.Srt 14 | PtrOpts *SrtTranslateOpts 15 | } 16 | 17 | func (customData *SrtTranslateData) toSrtWriterData() *writer.SrtWriterData { 18 | tmpData := &writer.SrtWriterData{ 19 | FileNameSaved: customData.fileNameSavedBuilder(), 20 | PrtSrt: customData.PrtSrt, 21 | PtrOpts: &srt.EncodeOpt{ 22 | FlagIsInverse: customData.PtrOpts.MainTrackReport == _const.LangDirectionTo, 23 | FlagTrackExport: customData.PtrOpts.FlagTrackExport, 24 | }, 25 | } 26 | return tmpData 27 | } 28 | 29 | func (customData *SrtTranslateData) fileNameSavedBuilder() string { 30 | newFileName := customData.PrtSrt.FilePath[0 : len(customData.PrtSrt.FilePath)-4] 31 | newFileName = fmt.Sprintf( 32 | "%s/%s/%s.srt", filepath.Dir(newFileName), _const.AppName, filepath.Base(newFileName), 33 | ) 34 | return newFileName 35 | } 36 | 37 | type SrtTranslateOpts struct { 38 | Translator translator.ImplTranslator 39 | FromLang string 40 | ToLang string 41 | TranslateMode _const.TranslateMode 42 | MainTrackReport _const.LangDirection 43 | FlagTrackExport int 44 | } 45 | -------------------------------------------------------------------------------- /cron/writer/data.go: -------------------------------------------------------------------------------- 1 | package writer 2 | 3 | import "anto/lib/srt" 4 | 5 | type SrtWriterData struct { 6 | FileNameSaved string 7 | PrtSrt *srt.Srt 8 | PtrOpts *srt.EncodeOpt 9 | } 10 | -------------------------------------------------------------------------------- /cron/writer/srt_writer.go: -------------------------------------------------------------------------------- 1 | package writer 2 | 3 | import ( 4 | "anto/cron" 5 | "anto/lib/log" 6 | "anto/lib/util" 7 | "context" 8 | "fmt" 9 | "github.com/golang-module/carbon" 10 | "os" 11 | "path/filepath" 12 | "runtime" 13 | "sync" 14 | ) 15 | 16 | const ( 17 | cronName = "SRT写入程序" 18 | numChanData = 10 19 | numChanMsg = 20 20 | numCoroutine = 10 21 | ) 22 | 23 | var ( 24 | apiSingleton *SrtWriter 25 | onceSingleton sync.Once 26 | ) 27 | 28 | func Singleton() *SrtWriter { 29 | onceSingleton.Do(func() { 30 | apiSingleton = new(SrtWriter) 31 | apiSingleton.init() 32 | }) 33 | return apiSingleton 34 | } 35 | 36 | type SrtWriter struct { 37 | ctx context.Context 38 | ctxFnCancel context.CancelFunc 39 | chanData chan *SrtWriterData 40 | chanMsg chan string 41 | chanMsgRedirect chan string 42 | numCoroutine int 43 | } 44 | 45 | func (customCron *SrtWriter) Push(data *SrtWriterData) { 46 | customCron.chanData <- data 47 | } 48 | 49 | func (customCron *SrtWriter) Run(ctx context.Context, fnCancel context.CancelFunc) { 50 | customCron.ctx = ctx 51 | customCron.ctxFnCancel = fnCancel 52 | 53 | customCron.jobWriter() 54 | customCron.jobMsg() 55 | } 56 | 57 | func (customCron *SrtWriter) Close() {} 58 | 59 | func (customCron *SrtWriter) SetMsgRedirect(chanMsg chan string) { 60 | customCron.chanMsgRedirect = chanMsg 61 | } 62 | 63 | func (customCron *SrtWriter) jobWriter() { 64 | if customCron.numCoroutine <= 0 { 65 | customCron.log().WarnF("%s-%s通道的最大数量(%d)无效, 重置为5", cronName, "chanData", customCron.numCoroutine) 66 | customCron.numCoroutine = 5 67 | } 68 | 69 | for idx := 0; idx < customCron.numCoroutine; idx++ { 70 | go func(ctx context.Context, chanWriter chan *SrtWriterData, chanMsg chan string, idx int) { 71 | coroutineName := fmt.Sprintf("写入协程(%d)", idx) 72 | for true { 73 | select { 74 | case <-ctx.Done(): 75 | customCron.log().WarnF("%s关闭(ctx.done), %s被迫退出", cronName, coroutineName) 76 | runtime.Goexit() 77 | case currentData, isOpen := <-chanWriter: 78 | timeStart := carbon.Now() 79 | if isOpen == false && currentData == nil { 80 | customCron.log().WarnF("%s-通道关闭, %s被迫退出", cronName, coroutineName) 81 | runtime.Goexit() 82 | } 83 | if currentData.FileNameSaved == "" { 84 | chanMsg <- fmt.Sprintf("%s未检测到保存文件名称", currentData.PrtSrt.FileName) 85 | continue 86 | } 87 | if currentData.PrtSrt.FlagTranslated == false { 88 | chanMsg <- fmt.Sprintf("%s未进行翻译, 可能选择了增量翻译模式, 源文已全部翻译", currentData.PrtSrt.FileName) 89 | continue 90 | } 91 | bytesEncoded, err := currentData.PrtSrt.Encode(currentData.PtrOpts) 92 | if err != nil { 93 | customCron.log().ErrorF("%s编码失败, 错误: %s", currentData.PrtSrt.FileName, err) 94 | chanMsg <- fmt.Sprintf("%s编码失败", currentData.PrtSrt.FileName) 95 | continue 96 | } 97 | 98 | dirSrt := filepath.Dir(currentData.FileNameSaved) 99 | if err = os.MkdirAll(dirSrt, os.ModePerm); err != nil { 100 | customCron.log().ErrorF("创建目录[%s]失败, 错误: %s", dirSrt, err) 101 | chanMsg <- fmt.Sprintf("%s创建失败", dirSrt) 102 | continue 103 | } 104 | 105 | fd, err := os.OpenFile(currentData.FileNameSaved, os.O_CREATE|os.O_WRONLY, os.ModePerm) 106 | if err != nil { 107 | customCron.log().ErrorF("创建文件[%s]失败, 错误: %s", currentData.FileNameSaved, err) 108 | chanMsg <- fmt.Sprintf("%s创建失败", currentData.FileNameSaved) 109 | _ = fd.Close() 110 | continue 111 | } 112 | if _, err = fd.Write(bytesEncoded); err != nil { 113 | customCron.log().ErrorF("写入文件失败(%s => %s), 错误: %s", currentData.PrtSrt.FileName, currentData.FileNameSaved, err) 114 | chanMsg <- fmt.Sprintf("写入文件失败(%s => %s)", currentData.PrtSrt.FileName, currentData.FileNameSaved) 115 | _ = fd.Close() 116 | continue 117 | } 118 | _ = fd.Close() 119 | chanMsg <- fmt.Sprintf( 120 | "写入文件成功, 源件: %s, 目标文件: %s, 写入字节数: %d, 耗时(s): %d", 121 | currentData.PrtSrt.FileName, currentData.FileNameSaved, len(bytesEncoded), 122 | util.GetSecondsFromTime(timeStart), 123 | ) 124 | } 125 | } 126 | }(customCron.ctx, customCron.chanData, customCron.chanMsg, idx) 127 | } 128 | } 129 | 130 | func (customCron *SrtWriter) jobMsg() { 131 | cron.FuncSrtCronMsgRedirect(customCron.ctx, cronName, customCron.log(), customCron.chanMsg, customCron.chanMsgRedirect) 132 | } 133 | 134 | func (customCron *SrtWriter) init() { 135 | customCron.chanData = make(chan *SrtWriterData, numChanData) 136 | customCron.chanMsg = make(chan string, numChanMsg) 137 | customCron.numCoroutine = numCoroutine 138 | } 139 | 140 | func (customCron *SrtWriter) log() *log.Log { 141 | return log.Singleton() 142 | } 143 | -------------------------------------------------------------------------------- /domain/repository/translator.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | _type "anto/common" 5 | serviceTranslator "anto/domain/service/translator" 6 | "anto/lib/restrictor" 7 | "sort" 8 | "sync" 9 | ) 10 | 11 | var ( 12 | apiTranslators *Translators 13 | onceTranslators sync.Once 14 | ) 15 | 16 | func GetTranslators() *Translators { 17 | onceTranslators.Do(func() { 18 | apiTranslators = new(Translators) 19 | }) 20 | return apiTranslators 21 | } 22 | 23 | type Translators struct { 24 | list sync.Map 25 | names []*_type.StdComboBoxModel 26 | namesAll []*_type.StdComboBoxModel 27 | } 28 | 29 | func (customT *Translators) Register(translators ...serviceTranslator.ImplTranslator) { 30 | tmpRestrictor := restrictor.Singleton() 31 | for _, translator := range translators { 32 | if _, isExisted := customT.list.Load(translator.GetId()); isExisted { 33 | continue 34 | } 35 | customT.list.Store(translator.GetId(), translator) 36 | tmpLimiter := tmpRestrictor.Get(translator.GetId()) 37 | limited := translator.GetCfg().GetQPS() / 4 * 3 // 缓冲 38 | if limited < 1 { 39 | limited = 1 40 | } 41 | tmpLimiter.SetLimit(1) 42 | tmpLimiter.SetBurst(limited) 43 | 44 | tmpRestrictor.Set(translator.GetId(), tmpLimiter) 45 | } 46 | customT.genNames2ComboBox() 47 | } 48 | 49 | func (customT *Translators) GetById(id string) serviceTranslator.ImplTranslator { 50 | obj, isExisted := customT.list.Load(id) 51 | if !isExisted { 52 | return nil 53 | } 54 | return obj.(serviceTranslator.ImplTranslator) 55 | } 56 | 57 | func (customT *Translators) GetNames() []*_type.StdComboBoxModel { 58 | return customT.names 59 | } 60 | 61 | func (customT *Translators) GetNamesAll() []*_type.StdComboBoxModel { 62 | return customT.namesAll 63 | } 64 | 65 | func (customT *Translators) genNames2ComboBox() { 66 | customT.names = []*_type.StdComboBoxModel{} 67 | customT.namesAll = []*_type.StdComboBoxModel{} 68 | customT.list.Range(func(idx, translator any) bool { 69 | customT.namesAll = append(customT.namesAll, &_type.StdComboBoxModel{ 70 | Key: translator.(serviceTranslator.ImplTranslator).GetId(), 71 | Name: translator.(serviceTranslator.ImplTranslator).GetName(), 72 | }) 73 | if translator.(serviceTranslator.ImplTranslator).IsValid() { 74 | customT.names = append(customT.names, &_type.StdComboBoxModel{ 75 | Key: translator.(serviceTranslator.ImplTranslator).GetId(), 76 | Name: translator.(serviceTranslator.ImplTranslator).GetName(), 77 | }) 78 | } 79 | return true 80 | }) 81 | sort.Slice(customT.namesAll, func(i, j int) bool { 82 | return customT.namesAll[i].Key < customT.namesAll[j].Key 83 | }) 84 | if len(customT.names) > 1 { 85 | sort.Slice(customT.names, func(i, j int) bool { 86 | return customT.names[i].Key < customT.names[j].Key 87 | }) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /domain/service/translator/ai_baidu/config.go: -------------------------------------------------------------------------------- 1 | package ai_baidu 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/imroc/req/v3" 8 | "github.com/spf13/viper" 9 | "io" 10 | "time" 11 | ) 12 | 13 | type Config struct { 14 | *translator.DefaultConfig 15 | ApiKey string `mapstructure:"api_key"` // 应用关键字 16 | SecretKey string `mapstructure:"secret_key"` // 应用密钥 17 | QPS int `mapstructure:"qps"` 18 | MaxCharNum int `mapstructure:"max_single_text_length"` 19 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"` 20 | AccessToken string `mapstructure:"access_token"` 21 | ExpiredAt int64 `mapstructure:"expired_at"` 22 | } 23 | 24 | func (config *Config) Default() translator.ImplConfig { 25 | return &Config{ 26 | ApiKey: "", SecretKey: "", 27 | MaxCharNum: 6000, QPS: 10, MaxCoroutineNum: 5, 28 | } 29 | } 30 | 31 | func (config *Config) SyncDisk(currentViper *viper.Viper) error { 32 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure") 33 | 34 | for tag, val := range tagAndVal { 35 | currentViper.Set(tag, val) 36 | } 37 | return nil 38 | } 39 | 40 | func (config *Config) GetAK() string { return config.ApiKey } 41 | func (config *Config) GetSK() string { return config.SecretKey } 42 | 43 | func (config *Config) GetQPS() int { return config.QPS } 44 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum } 45 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum } 46 | 47 | func (config *Config) SetAK(str string) error { 48 | if err := config.ValidatorStr(str); err != nil { 49 | return err 50 | } 51 | config.ApiKey = str 52 | return nil 53 | } 54 | func (config *Config) SetSK(str string) error { 55 | if err := config.ValidatorStr(str); err != nil { 56 | return err 57 | } 58 | config.SecretKey = str 59 | return nil 60 | } 61 | 62 | func (config *Config) SetQPS(num int) error { 63 | if err := config.ValidatorNum(num); err != nil { 64 | return err 65 | } 66 | config.QPS = num 67 | return nil 68 | } 69 | 70 | func (config *Config) SetMaxCharNum(num int) error { 71 | if err := config.ValidatorNum(num); err != nil { 72 | return err 73 | } 74 | config.MaxCharNum = num 75 | return nil 76 | } 77 | 78 | func (config *Config) SetMaxCoroutineNum(num int) error { 79 | if err := config.ValidatorNum(num); err != nil { 80 | return err 81 | } 82 | config.MaxCoroutineNum = num 83 | return nil 84 | } 85 | 86 | func (config *Config) GetAccessToken() (string, error) { 87 | if config.AccessToken == "" || config.ExpiredAt < (time.Now().Unix()-3600) { 88 | tmpUrl := fmt.Sprintf( 89 | "https://aip.baidubce.com/oauth/2.0/token?client_id=%s&client_secret=%s&grant_type=client_credentials", 90 | config.GetAK(), config.GetSK(), 91 | ) 92 | resp, err := req.R().Post(tmpUrl) 93 | if err != nil { 94 | return "", err 95 | } 96 | respBytes, err := io.ReadAll(resp.Body) 97 | if err != nil { 98 | return "", fmt.Errorf("读取报文异常, 错误: %s", err.Error()) 99 | } 100 | auth := &authResp{} 101 | if err = json.Unmarshal(respBytes, auth); err != nil { 102 | return "", fmt.Errorf("解析报文异常, 错误: %s", err.Error()) 103 | } 104 | config.AccessToken = auth.AccessToken 105 | config.ExpiredAt = time.Now().Unix() + auth.ExpiresIn 106 | } 107 | 108 | return config.AccessToken, nil 109 | } 110 | 111 | type authResp struct { 112 | RefreshToken string `json:"refresh_token"` 113 | ExpiresIn int64 `json:"expires_in"` 114 | SessionKey string `json:"session_key"` 115 | AccessToken string `json:"access_token"` 116 | Scope string `json:"scope"` 117 | SessionSecret string `json:"session_secret"` 118 | } 119 | -------------------------------------------------------------------------------- /domain/service/translator/ai_baidu/lang.go: -------------------------------------------------------------------------------- 1 | package ai_baidu 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | ) 6 | 7 | var langSupported = []translator.LangPair{ 8 | {"zh", "中文"}, 9 | {"en", "英语"}, 10 | {"jp", "日语"}, 11 | {"kor", "韩语"}, 12 | {"fra", "法语"}, 13 | {"spa", "西班牙语"}, 14 | {"th", "泰语"}, 15 | {"ara", "阿拉伯语"}, 16 | {"ru", "俄语"}, 17 | {"pt", "葡萄牙语"}, 18 | {"de", "德语"}, 19 | {"it", "意大利语"}, 20 | {"el", "希腊语"}, 21 | {"nl", "荷兰语"}, 22 | {"pl", "波兰语"}, 23 | {"bul", "保加利亚语"}, 24 | {"est", "爱沙尼亚语"}, 25 | {"dan", "丹麦语"}, 26 | {"fin", "芬兰语"}, 27 | {"cs", "捷克语"}, 28 | {"rom", "罗马尼亚语"}, 29 | {"slo", "斯洛文尼亚语"}, 30 | {"swe", "瑞典语"}, 31 | {"hu", "匈牙利语"}, 32 | {"vie", "越南语"}, 33 | } 34 | -------------------------------------------------------------------------------- /domain/service/translator/ai_baidu/translator.go: -------------------------------------------------------------------------------- 1 | package ai_baidu 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "anto/lib/log" 6 | "context" 7 | "encoding/json" 8 | "fmt" 9 | "github.com/golang-module/carbon" 10 | "sync" 11 | ) 12 | 13 | var api = "https://aip.baidubce.com/rpc/2.0/mt/texttrans/v1" 14 | 15 | var ( 16 | apiTranslator *Translator 17 | onceTranslator sync.Once 18 | ) 19 | 20 | func API() *Translator { 21 | onceTranslator.Do(func() { 22 | apiTranslator = New() 23 | }) 24 | return apiTranslator 25 | } 26 | 27 | func New() *Translator { 28 | return &Translator{ 29 | id: "ai_baidu", 30 | name: "百度智能云", 31 | sep: "\n", 32 | langSupported: langSupported, 33 | } 34 | } 35 | 36 | type Translator struct { 37 | id string 38 | name string 39 | cfg translator.ImplConfig 40 | qps int 41 | procMax int 42 | textMaxLen int 43 | langSupported []translator.LangPair 44 | sep string 45 | } 46 | 47 | func (customT *Translator) Init(cfg translator.ImplConfig) { customT.cfg = cfg } 48 | 49 | func (customT *Translator) GetId() string { return customT.id } 50 | func (customT *Translator) GetShortId() string { return "ai_bd" } 51 | func (customT *Translator) GetName() string { return customT.name } 52 | func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg } 53 | func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported } 54 | func (customT *Translator) GetSep() string { return customT.sep } 55 | func (customT *Translator) IsValid() bool { 56 | return customT.cfg != nil && customT.cfg.GetAK() != "" && customT.cfg.GetSK() != "" 57 | } 58 | 59 | func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) { 60 | timeStart := carbon.Now() 61 | accessToken, err := customT.GetCfg().(*Config).GetAccessToken() 62 | if err != nil { 63 | return nil, err 64 | } 65 | urlQueried := fmt.Sprintf("%s?access_token=%s", api, accessToken) 66 | bodyContent := translateReq{Q: args.TextContent, From: args.FromLang, To: args.ToLang} 67 | respBytes, err := translator.RequestSimpleHttp(ctx, customT, urlQueried, true, bodyContent, nil) 68 | if err != nil { 69 | return nil, err 70 | } 71 | respObj := new(translateResp) 72 | if err = json.Unmarshal(respBytes, respObj); err != nil { 73 | log.Singleton().ErrorF("解析报文异常, 引擎: %s, 错误: %s", customT.GetName(), err) 74 | return nil, fmt.Errorf("解析报文出现异常, 错误: %s", err.Error()) 75 | } 76 | 77 | ret := new(translator.TranslateRes) 78 | for _, transBlockArray := range respObj.Result.TransResult { 79 | ret.Results = append(ret.Results, &translator.TranslateResBlock{ 80 | Id: transBlockArray.Src, 81 | TextTranslated: transBlockArray.Dst, 82 | }) 83 | } 84 | 85 | ret.TimeUsed = int(carbon.Now().DiffInSeconds(timeStart)) 86 | return ret, nil 87 | } 88 | 89 | type translateReq struct { 90 | Q string `json:"q"` 91 | From string `json:"from"` 92 | To string `json:"to"` 93 | } 94 | 95 | type translateResp struct { 96 | Result struct { 97 | From string `json:"from"` 98 | TransResult []struct { 99 | Dst string `json:"dst"` 100 | Src string `json:"src"` 101 | } `json:"trans_result"` 102 | To string `json:"to"` 103 | } `json:"result"` 104 | LogId int64 `json:"log_id"` 105 | } 106 | -------------------------------------------------------------------------------- /domain/service/translator/ali_cloud_mt/config.go: -------------------------------------------------------------------------------- 1 | package ali_cloud_mt 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "github.com/spf13/viper" 6 | ) 7 | 8 | type Config struct { 9 | *translator.DefaultConfig 10 | AKId string `mapstructure:"ak_id"` 11 | AKSecret string `mapstructure:"ak_secret"` 12 | Region string `mapstructure:"region"` 13 | QPS int `mapstructure:"qps"` 14 | MaxCharNum int `mapstructure:"max_single_text_length"` 15 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"` 16 | } 17 | 18 | func (config *Config) Default() translator.ImplConfig { 19 | return &Config{ 20 | AKId: "", AKSecret: "", Region: "cn-hangzhou", 21 | MaxCharNum: 3000, QPS: 50, MaxCoroutineNum: 25, 22 | } 23 | } 24 | 25 | func (config *Config) SyncDisk(currentViper *viper.Viper) error { 26 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure") 27 | 28 | for tag, val := range tagAndVal { 29 | currentViper.Set(tag, val) 30 | } 31 | return nil 32 | } 33 | 34 | func (config *Config) GetAK() string { return config.AKId } 35 | func (config *Config) GetSK() string { return config.AKSecret } 36 | func (config *Config) GetRegion() string { 37 | if config.Region != "" { 38 | return config.Region 39 | } 40 | return "cn-hangzhou" 41 | } 42 | 43 | func (config *Config) GetQPS() int { return config.QPS } 44 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum } 45 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum } 46 | 47 | func (config *Config) SetAK(str string) error { 48 | if err := config.ValidatorStr(str); err != nil { 49 | return err 50 | } 51 | config.AKId = str 52 | return nil 53 | } 54 | func (config *Config) SetSK(str string) error { 55 | if err := config.ValidatorStr(str); err != nil { 56 | return err 57 | } 58 | config.AKSecret = str 59 | return nil 60 | } 61 | func (config *Config) SetRegion(str string) error { 62 | if err := config.ValidatorStr(str); err != nil { 63 | return err 64 | } 65 | config.Region = str 66 | return nil 67 | } 68 | 69 | func (config *Config) SetQPS(num int) error { 70 | if err := config.ValidatorNum(num); err != nil { 71 | return err 72 | } 73 | config.QPS = num 74 | return nil 75 | } 76 | 77 | func (config *Config) SetMaxCharNum(num int) error { 78 | if err := config.ValidatorNum(num); err != nil { 79 | return err 80 | } 81 | config.MaxCharNum = num 82 | return nil 83 | } 84 | 85 | func (config *Config) SetMaxCoroutineNum(num int) error { 86 | if err := config.ValidatorNum(num); err != nil { 87 | return err 88 | } 89 | config.MaxCoroutineNum = num 90 | return nil 91 | } 92 | -------------------------------------------------------------------------------- /domain/service/translator/ali_cloud_mt/lang.go: -------------------------------------------------------------------------------- 1 | package ali_cloud_mt 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | ) 6 | 7 | var langSupported = []translator.LangPair{ 8 | {"zh", "中文"}, 9 | {"en", "英语"}, 10 | {"ja", "日语"}, 11 | {"ko", "韩语"}, 12 | {"ru", "俄语"}, 13 | {"fr", "法语"}, 14 | {"de", "德语"}, 15 | {"ar", "阿拉伯语"}, 16 | } 17 | -------------------------------------------------------------------------------- /domain/service/translator/ali_cloud_mt/translator.go: -------------------------------------------------------------------------------- 1 | package ali_cloud_mt 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "anto/lib/log" 6 | "anto/lib/restrictor" 7 | "context" 8 | "encoding/json" 9 | "fmt" 10 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk" 11 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials" 12 | alimt "github.com/aliyun/alibaba-cloud-sdk-go/services/alimt" 13 | "github.com/golang-module/carbon" 14 | "github.com/spf13/cast" 15 | "strings" 16 | "sync" 17 | ) 18 | 19 | var ( 20 | apiTranslator *Translator 21 | onceTranslator sync.Once 22 | ) 23 | 24 | func API() *Translator { 25 | onceTranslator.Do(func() { 26 | apiTranslator = New() 27 | }) 28 | return apiTranslator 29 | } 30 | 31 | func New() *Translator { 32 | return &Translator{ 33 | id: "ali_cloud_mt", 34 | name: "阿里云", 35 | sep: "\n", 36 | langSupported: langSupported, 37 | isClientOk: false, 38 | } 39 | } 40 | 41 | type Translator struct { 42 | id string 43 | name string 44 | cfg translator.ImplConfig 45 | langSupported []translator.LangPair 46 | sep string 47 | mtClient *alimt.Client 48 | isClientOk bool 49 | } 50 | 51 | func (customT *Translator) Init(cfg translator.ImplConfig) { 52 | customT.cfg = cfg 53 | customT.clientBuilder() 54 | } 55 | 56 | func (customT *Translator) GetId() string { return customT.id } 57 | func (customT *Translator) GetShortId() string { return "ac" } 58 | func (customT *Translator) GetName() string { return customT.name } 59 | func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg } 60 | func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported } 61 | func (customT *Translator) GetSep() string { return customT.sep } 62 | func (customT *Translator) IsValid() bool { 63 | return customT.cfg != nil && customT.cfg.GetAK() != "" && customT.cfg.GetSK() != "" && customT.isClientOk == true 64 | } 65 | 66 | func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) { 67 | timeStart := carbon.Now() 68 | ret := new(translator.TranslateRes) 69 | 70 | texts := strings.Split(args.TextContent, customT.GetSep()) 71 | var txtMap []map[int]string 72 | for idx, text := range texts { 73 | if idx%50 == 0 { 74 | txtMap = append(txtMap, map[int]string{}) 75 | } 76 | txtMap[idx/50][idx] = text 77 | } 78 | for _, currentTxtBlock := range txtMap { 79 | bytes, _ := json.Marshal(currentTxtBlock) 80 | req := alimt.CreateGetBatchTranslateRequest() 81 | req.Scheme = "https" 82 | req.TargetLanguage = args.ToLang 83 | req.SourceLanguage = args.FromLang 84 | req.ApiType = "translate_standard" 85 | req.FormatType = "text" 86 | req.Scene = "general" 87 | req.SourceText = string(bytes) 88 | if err := restrictor.Singleton().Wait(customT.GetId(), ctx); err != nil { 89 | return nil, fmt.Errorf("限流异常, 错误: %s", err.Error()) 90 | } 91 | resp, err := customT.mtClient.GetBatchTranslate(req) 92 | if err != nil { 93 | log.Singleton().ErrorF("引擎: %s, 错误: %s", customT.GetName(), err) 94 | return nil, fmt.Errorf("引擎: %s, 错误: %s", customT.GetName(), err) 95 | } 96 | for _, translated := range resp.TranslatedList { 97 | if translated["code"] != "200" { 98 | log.Singleton().ErrorF("引擎: %s, 错误: %s", customT.GetName(), translated["errorMsg"].(string)) 99 | return nil, fmt.Errorf("引擎: %s, 错误: %s", customT.GetName(), translated["errorMsg"].(string)) 100 | } 101 | idx := cast.ToInt(translated["index"].(string)) 102 | ret.Results = append(ret.Results, &translator.TranslateResBlock{ 103 | Id: texts[idx], TextTranslated: translated["translated"].(string), 104 | }) 105 | } 106 | } 107 | 108 | ret.TimeUsed = int(carbon.Now().DiffInSeconds(timeStart)) 109 | return ret, nil 110 | } 111 | 112 | func (customT *Translator) clientBuilder() { 113 | if customT.cfg == nil || customT.cfg.GetAK() == "" || customT.cfg.GetSK() == "" { 114 | return 115 | } 116 | config := sdk.NewConfig() 117 | 118 | credential := credentials.NewAccessKeyCredential(customT.cfg.GetAK(), customT.cfg.GetSK()) 119 | client, err := alimt.NewClientWithOptions(customT.cfg.GetRegion(), config, credential) 120 | if err != nil { 121 | log.Singleton().ErrorF("引擎: %s, 错误: 生成客户端失败(%s)", customT.GetName(), err) 122 | return 123 | } 124 | customT.mtClient = client 125 | customT.isClientOk = true 126 | } 127 | -------------------------------------------------------------------------------- /domain/service/translator/baidu/config.go: -------------------------------------------------------------------------------- 1 | package baidu 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "github.com/spf13/viper" 6 | ) 7 | 8 | type Config struct { 9 | *translator.DefaultConfig 10 | AppId string `mapstructure:"app_id"` // 应用ID 11 | AppKey string `mapstructure:"app_key"` // 应用密钥 12 | QPS int `mapstructure:"qps"` 13 | MaxCharNum int `mapstructure:"max_single_text_length"` 14 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"` 15 | } 16 | 17 | func (config *Config) Default() translator.ImplConfig { 18 | return &Config{ 19 | AppId: "", AppKey: "", 20 | MaxCharNum: 1000, QPS: 1, MaxCoroutineNum: 1, 21 | } 22 | } 23 | 24 | func (config *Config) SyncDisk(currentViper *viper.Viper) error { 25 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure") 26 | 27 | for tag, val := range tagAndVal { 28 | currentViper.Set(tag, val) 29 | } 30 | return nil 31 | } 32 | 33 | func (config *Config) GetAK() string { return config.AppId } 34 | func (config *Config) GetSK() string { return config.AppKey } 35 | 36 | func (config *Config) GetQPS() int { return config.QPS } 37 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum } 38 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum } 39 | 40 | func (config *Config) SetAK(str string) error { 41 | if err := config.ValidatorStr(str); err != nil { 42 | return err 43 | } 44 | config.AppId = str 45 | return nil 46 | } 47 | func (config *Config) SetSK(str string) error { 48 | if err := config.ValidatorStr(str); err != nil { 49 | return err 50 | } 51 | config.AppKey = str 52 | return nil 53 | } 54 | 55 | func (config *Config) SetQPS(num int) error { 56 | if err := config.ValidatorNum(num); err != nil { 57 | return err 58 | } 59 | config.QPS = num 60 | return nil 61 | } 62 | 63 | func (config *Config) SetMaxCharNum(num int) error { 64 | if err := config.ValidatorNum(num); err != nil { 65 | return err 66 | } 67 | config.MaxCharNum = num 68 | return nil 69 | } 70 | 71 | func (config *Config) SetMaxCoroutineNum(num int) error { 72 | if err := config.ValidatorNum(num); err != nil { 73 | return err 74 | } 75 | config.MaxCoroutineNum = num 76 | return nil 77 | } 78 | -------------------------------------------------------------------------------- /domain/service/translator/baidu/lang.go: -------------------------------------------------------------------------------- 1 | package baidu 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | ) 6 | 7 | var langSupported = []translator.LangPair{ 8 | {"zh", "中文"}, 9 | {"en", "英语"}, 10 | {"jp", "日语"}, 11 | {"kor", "韩语"}, 12 | {"fra", "法语"}, 13 | {"spa", "西班牙语"}, 14 | {"th", "泰语"}, 15 | {"ara", "阿拉伯语"}, 16 | {"ru", "俄语"}, 17 | {"pt", "葡萄牙语"}, 18 | {"de", "德语"}, 19 | {"it", "意大利语"}, 20 | {"el", "希腊语"}, 21 | {"nl", "荷兰语"}, 22 | {"pl", "波兰语"}, 23 | {"bul", "保加利亚语"}, 24 | {"est", "爱沙尼亚语"}, 25 | {"dan", "丹麦语"}, 26 | {"fin", "芬兰语"}, 27 | {"cs", "捷克语"}, 28 | {"rom", "罗马尼亚语"}, 29 | {"slo", "斯洛文尼亚语"}, 30 | {"swe", "瑞典语"}, 31 | {"hu", "匈牙利语"}, 32 | {"vie", "越南语"}, 33 | } 34 | -------------------------------------------------------------------------------- /domain/service/translator/baidu/translator.go: -------------------------------------------------------------------------------- 1 | package baidu 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "anto/lib/log" 6 | "anto/lib/util" 7 | "context" 8 | "crypto/md5" 9 | "encoding/json" 10 | "fmt" 11 | "github.com/golang-module/carbon" 12 | "net/url" 13 | "sync" 14 | ) 15 | 16 | var api = "https://fanyi-api.baidu.com/api/trans/vip/translate" 17 | 18 | var ( 19 | apiTranslator *Translator 20 | onceTranslator sync.Once 21 | ) 22 | 23 | func API() *Translator { 24 | onceTranslator.Do(func() { 25 | apiTranslator = New() 26 | }) 27 | return apiTranslator 28 | } 29 | 30 | func New() *Translator { 31 | return &Translator{ 32 | id: "baidu", 33 | name: "百度翻译", 34 | sep: "\n", 35 | langSupported: langSupported, 36 | } 37 | } 38 | 39 | type Translator struct { 40 | id string 41 | name string 42 | cfg translator.ImplConfig 43 | qps int 44 | procMax int 45 | textMaxLen int 46 | langSupported []translator.LangPair 47 | sep string 48 | } 49 | 50 | func (customT *Translator) Init(cfg translator.ImplConfig) { customT.cfg = cfg } 51 | 52 | func (customT *Translator) GetId() string { return customT.id } 53 | func (customT *Translator) GetShortId() string { return "bd" } 54 | func (customT *Translator) GetName() string { return customT.name } 55 | func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg } 56 | func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported } 57 | func (customT *Translator) GetSep() string { return customT.sep } 58 | func (customT *Translator) IsValid() bool { 59 | return customT.cfg != nil && customT.cfg.GetAK() != "" && customT.cfg.GetSK() != "" 60 | } 61 | 62 | func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) { 63 | timeStart := carbon.Now() 64 | salt := util.Uid() 65 | sign := customT.signBuilder(args.TextContent, salt) 66 | urlQueried := fmt.Sprintf( 67 | "%s?q=%s&from=%s&to=%s&appid=%s&salt=%s&sign=%s", api, 68 | url.QueryEscape(args.TextContent), args.FromLang, args.ToLang, 69 | customT.cfg.GetAK(), salt, sign, 70 | ) 71 | respBytes, err := translator.RequestSimpleHttp(ctx, customT, urlQueried, false, nil, nil) 72 | if err != nil { 73 | return nil, err 74 | } 75 | respObj := new(remoteResp) 76 | if err = json.Unmarshal(respBytes, respObj); err != nil { 77 | log.Singleton().ErrorF("解析报文异常, 引擎: %s, 错误: %s", customT.GetName(), err) 78 | return nil, fmt.Errorf("解析报文出现异常, 错误: %s", err.Error()) 79 | } 80 | if respObj.ErrorCode != "" && respObj.ErrorCode != "52000" { 81 | log.Singleton().ErrorF("接口响应异常, 引擎: %s, 错误: %s(%s)", customT.GetName(), respObj.ErrorCode, respObj.ErrorMsg) 82 | return nil, fmt.Errorf("翻译异常, 代码: %s, 错误: %s", respObj.ErrorCode, respObj.ErrorMsg) 83 | } 84 | 85 | ret := new(translator.TranslateRes) 86 | for _, transBlockArray := range respObj.Results { 87 | ret.Results = append(ret.Results, &translator.TranslateResBlock{ 88 | Id: transBlockArray.Src, 89 | TextTranslated: transBlockArray.Dst, 90 | }) 91 | } 92 | 93 | ret.TimeUsed = int(carbon.Now().DiffInSeconds(timeStart)) 94 | return ret, nil 95 | } 96 | 97 | func (customT *Translator) signBuilder(strQuery string, salt string) string { 98 | tmpStr := fmt.Sprintf("%s%s%s%s", customT.cfg.GetAK(), strQuery, salt, customT.cfg.GetSK()) 99 | tmpMD5 := md5.New() 100 | tmpMD5.Write([]byte(tmpStr)) 101 | return fmt.Sprintf("%x", tmpMD5.Sum(nil)) 102 | } 103 | 104 | type remoteResp struct { 105 | ErrorCode string `json:"error_code,omitempty"` 106 | ErrorMsg string `json:"error_msg,omitempty"` 107 | From string `json:"from"` 108 | To string `json:"to"` 109 | Results []struct { 110 | Src string `json:"src"` 111 | Dst string `json:"dst"` 112 | } `json:"trans_result"` 113 | } 114 | -------------------------------------------------------------------------------- /domain/service/translator/caiyunai/config.go: -------------------------------------------------------------------------------- 1 | package caiyunai 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "github.com/spf13/viper" 6 | ) 7 | 8 | type Config struct { 9 | *translator.DefaultConfig 10 | Token string `mapstructure:"token"` 11 | QPS int `mapstructure:"qps"` 12 | MaxCharNum int `mapstructure:"max_single_text_length"` 13 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"` 14 | } 15 | 16 | func (config *Config) Default() translator.ImplConfig { 17 | // 这是官网给出的测试Token, 随便造 18 | return &Config{ 19 | Token: "3975l6lr5pcbvidl6jl2", 20 | MaxCharNum: 5000, QPS: 10, MaxCoroutineNum: 5, 21 | } 22 | } 23 | 24 | func (config *Config) SyncDisk(currentViper *viper.Viper) error { 25 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure") 26 | 27 | for tag, val := range tagAndVal { 28 | currentViper.Set(tag, val) 29 | } 30 | return nil 31 | } 32 | 33 | func (config *Config) GetAK() string { return config.Token } 34 | 35 | func (config *Config) GetQPS() int { return config.QPS } 36 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum } 37 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum } 38 | 39 | func (config *Config) SetAK(str string) error { 40 | if err := config.ValidatorStr(str); err != nil { 41 | return err 42 | } 43 | config.Token = str 44 | return nil 45 | } 46 | 47 | func (config *Config) SetQPS(num int) error { 48 | if err := config.ValidatorNum(num); err != nil { 49 | return err 50 | } 51 | config.QPS = num 52 | return nil 53 | } 54 | 55 | func (config *Config) SetMaxCharNum(num int) error { 56 | if err := config.ValidatorNum(num); err != nil { 57 | return err 58 | } 59 | config.MaxCharNum = num 60 | return nil 61 | } 62 | 63 | func (config *Config) SetMaxCoroutineNum(num int) error { 64 | if err := config.ValidatorNum(num); err != nil { 65 | return err 66 | } 67 | config.MaxCoroutineNum = num 68 | return nil 69 | } 70 | -------------------------------------------------------------------------------- /domain/service/translator/caiyunai/lang.go: -------------------------------------------------------------------------------- 1 | package caiyunai 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | ) 6 | 7 | var langSupported = []translator.LangPair{ 8 | {"zh", "中文"}, 9 | {"en", "英语"}, 10 | {"ja", "日语"}, 11 | } 12 | -------------------------------------------------------------------------------- /domain/service/translator/caiyunai/translator.go: -------------------------------------------------------------------------------- 1 | package caiyunai 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "anto/lib/log" 6 | "anto/lib/util" 7 | "context" 8 | "encoding/json" 9 | "fmt" 10 | "github.com/golang-module/carbon" 11 | "strings" 12 | "sync" 13 | ) 14 | 15 | var ( 16 | apiTranslator *Translator 17 | onceTranslator sync.Once 18 | api = "http://api.interpreter.caiyunai.com/v1/translator" 19 | ) 20 | 21 | func API() *Translator { 22 | onceTranslator.Do(func() { 23 | apiTranslator = New() 24 | }) 25 | return apiTranslator 26 | } 27 | 28 | func New() *Translator { 29 | return &Translator{ 30 | id: "caiyun_ai", 31 | name: "彩云小译", 32 | sep: "\n", 33 | langSupported: langSupported, 34 | } 35 | } 36 | 37 | type Translator struct { 38 | id string 39 | name string 40 | cfg translator.ImplConfig 41 | langSupported []translator.LangPair 42 | sep string 43 | } 44 | 45 | func (customT *Translator) Init(cfg translator.ImplConfig) { customT.cfg = cfg } 46 | 47 | func (customT *Translator) GetId() string { return customT.id } 48 | func (customT *Translator) GetShortId() string { return "cy" } 49 | func (customT *Translator) GetName() string { return customT.name } 50 | func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg } 51 | func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported } 52 | func (customT *Translator) GetSep() string { return customT.sep } 53 | func (customT *Translator) IsValid() bool { return customT.cfg != nil && customT.cfg.GetAK() != "" } 54 | 55 | func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) { 56 | timeStart := carbon.Now() 57 | texts := strings.Split(args.TextContent, customT.GetSep()) 58 | req := new(translateReq) 59 | req.Source = texts 60 | req.RequestId = util.Uid() 61 | req.TransType = fmt.Sprintf("%s2%s", args.FromLang, args.ToLang) 62 | 63 | reqBytes, _ := json.Marshal(req) 64 | respBytes, err := translator.RequestSimpleHttp(ctx, customT, api, true, reqBytes, map[string]string{ 65 | "x-authorization": fmt.Sprintf("token %s", customT.cfg.GetAK()), 66 | }) 67 | if err != nil { 68 | return nil, err 69 | } 70 | 71 | resp := new(translateResp) 72 | if err = json.Unmarshal(respBytes, resp); err != nil { 73 | log.Singleton().ErrorF("解析报文异常, 引擎: %s, 错误: %s", customT.GetName(), err) 74 | return nil, fmt.Errorf("解析报文出现异常, 错误: %s", err.Error()) 75 | } 76 | 77 | if resp.Msg != "" { 78 | log.Singleton().ErrorF("接口响应异常, 引擎: %s, 错误: %s", customT.GetName(), resp.Msg) 79 | return nil, fmt.Errorf("接口响应异常, 引擎: %s, 错误: %s", customT.GetName(), resp.Msg) 80 | } 81 | 82 | if len(texts) != len(resp.Target) { 83 | return nil, translator.ErrSrcAndTgtNotMatched 84 | } 85 | 86 | ret := new(translator.TranslateRes) 87 | 88 | for textIdx, textTarget := range resp.Target { 89 | ret.Results = append(ret.Results, &translator.TranslateResBlock{ 90 | Id: texts[textIdx], 91 | TextTranslated: textTarget, 92 | }) 93 | } 94 | 95 | ret.TimeUsed = int(carbon.Now().DiffAbsInSeconds(timeStart)) 96 | return ret, nil 97 | 98 | } 99 | 100 | type translateReq struct { 101 | Source []string `json:"source"` 102 | TransType string `json:"trans_type"` 103 | RequestId string `json:"request_id"` 104 | } 105 | 106 | type translateResp struct { 107 | Msg string `json:"message,omitempty"` 108 | SrcTgt []string `json:"src_tgt,omitempty"` 109 | Target []string `json:"target,omitempty"` 110 | Confidence float64 `json:"confidence,omitempty"` // 可信度? 111 | Rc int `json:"rc,omitempty"` 112 | } 113 | -------------------------------------------------------------------------------- /domain/service/translator/config.go: -------------------------------------------------------------------------------- 1 | package translator 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/spf13/viper" 7 | "reflect" 8 | ) 9 | 10 | const ( 11 | ConfigInvalidStr string = "nil" 12 | ConfigInvalidInt int = -1 13 | ) 14 | 15 | // ImplConfig 引擎配置接口 16 | type ImplConfig interface { 17 | GetAK() string // access-key or app-key or data-id and so on 18 | GetSK() string 19 | GetProjectKey() string 20 | GetRegion() string 21 | GetQPS() int 22 | GetMaxCharNum() int 23 | GetMaxCoroutineNum() int 24 | 25 | SetAK(ak string) error 26 | SetSK(sk string) error 27 | SetProjectKey(projectKey string) error 28 | SetRegion(region string) error 29 | SetQPS(num int) error 30 | SetMaxCharNum(num int) error 31 | SetMaxCoroutineNum(num int) error 32 | 33 | Default() ImplConfig // 输出默认配置 34 | SyncDisk(viper *viper.Viper) error // 同步到磁盘 35 | } 36 | 37 | // DefaultConfig 默认配置结构体, 供具体引擎配置嵌入使用 38 | type DefaultConfig struct{} 39 | 40 | func (defaultConfig *DefaultConfig) GetAK() string { return ConfigInvalidStr } 41 | 42 | func (defaultConfig *DefaultConfig) GetSK() string { return ConfigInvalidStr } 43 | 44 | func (defaultConfig *DefaultConfig) GetProjectKey() string { return ConfigInvalidStr } 45 | 46 | func (defaultConfig *DefaultConfig) GetRegion() string { return ConfigInvalidStr } 47 | 48 | func (defaultConfig *DefaultConfig) GetQPS() int { return ConfigInvalidInt } 49 | 50 | func (defaultConfig *DefaultConfig) GetMaxCharNum() int { return ConfigInvalidInt } 51 | 52 | func (defaultConfig *DefaultConfig) GetMaxCoroutineNum() int { return ConfigInvalidInt } 53 | 54 | func (defaultConfig *DefaultConfig) SetAK(_ string) error { return nil } 55 | 56 | func (defaultConfig *DefaultConfig) SetSK(_ string) error { return nil } 57 | 58 | func (defaultConfig *DefaultConfig) SetProjectKey(_ string) error { return nil } 59 | 60 | func (defaultConfig *DefaultConfig) SetRegion(_ string) error { return nil } 61 | 62 | func (defaultConfig *DefaultConfig) SetQPS(_ int) error { return nil } 63 | 64 | func (defaultConfig *DefaultConfig) SetMaxCharNum(_ int) error { return nil } 65 | 66 | func (defaultConfig *DefaultConfig) SetMaxCoroutineNum(_ int) error { return nil } 67 | 68 | func (defaultConfig *DefaultConfig) Default() ImplConfig { return nil } 69 | 70 | func (defaultConfig *DefaultConfig) SyncDisk(_ *viper.Viper) error { 71 | return errors.New("当前配置暂未实现磁盘同步方法") 72 | } 73 | 74 | func (defaultConfig *DefaultConfig) JoinAllTagAndValue(engine ImplTranslator, config ImplConfig, tagName string) map[string]interface{} { 75 | engineId := engine.GetId() 76 | configType := reflect.TypeOf(config) 77 | configVal := reflect.ValueOf(config) 78 | if configType.Kind() == reflect.Ptr { // 指针不支持 79 | configType = configType.Elem() 80 | } 81 | if configVal.Kind() == reflect.Ptr { // 指针不支持 82 | configVal = configVal.Elem() 83 | } 84 | 85 | result := make(map[string]interface{}) 86 | 87 | for i := 0; i < configType.NumField(); i++ { 88 | currentField := configType.Field(i) 89 | // 当前仅支持整型和字符串 90 | if currentField.Type.Kind() != reflect.Int && 91 | currentField.Type.Kind() != reflect.String { 92 | continue 93 | } 94 | tagVal := currentField.Tag.Get(tagName) 95 | if tagVal == "" || tagVal == "-" { 96 | continue 97 | } 98 | // @todo 可以直接在这里IO的, 但是想了一下还是交给具体配置处理, 毕竟功能还是要分开, 该方法只负责联合标签和具体值 99 | result[fmt.Sprintf("%s.%s", engineId, tagVal)] = configVal.Field(i).Interface() 100 | } 101 | return result 102 | } 103 | 104 | func (defaultConfig *DefaultConfig) ValidatorNum(num int) error { 105 | if num <= 0 { 106 | return errors.New("当前数值无效, 必须大于0") 107 | } 108 | return nil 109 | } 110 | 111 | func (defaultConfig *DefaultConfig) ValidatorStr(str string) error { 112 | if str == ConfigInvalidStr { 113 | return errors.New("当前字符串无效") 114 | } 115 | return nil 116 | } 117 | -------------------------------------------------------------------------------- /domain/service/translator/deepl/config.go: -------------------------------------------------------------------------------- 1 | package deepl 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "github.com/spf13/viper" 6 | ) 7 | 8 | type Config struct { 9 | *translator.DefaultConfig 10 | ApiKey string `mapstructure:"api_key"` 11 | QPS int `mapstructure:"qps"` 12 | MaxCharNum int `mapstructure:"max_single_text_length"` 13 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"` 14 | } 15 | 16 | func (config *Config) Default() translator.ImplConfig { 17 | return &Config{ 18 | ApiKey: "", 19 | MaxCharNum: 1000, QPS: 10, MaxCoroutineNum: 5, 20 | } 21 | } 22 | 23 | func (config *Config) SyncDisk(currentViper *viper.Viper) error { 24 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure") 25 | 26 | for tag, val := range tagAndVal { 27 | currentViper.Set(tag, val) 28 | } 29 | return nil 30 | } 31 | 32 | func (config *Config) GetAK() string { return config.ApiKey } 33 | func (config *Config) GetQPS() int { return config.QPS } 34 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum } 35 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum } 36 | 37 | func (config *Config) SetAK(str string) error { 38 | if err := config.ValidatorStr(str); err != nil { 39 | return err 40 | } 41 | config.ApiKey = str 42 | return nil 43 | } 44 | 45 | func (config *Config) SetQPS(num int) error { 46 | if err := config.ValidatorNum(num); err != nil { 47 | return err 48 | } 49 | config.QPS = num 50 | return nil 51 | } 52 | 53 | func (config *Config) SetMaxCharNum(num int) error { 54 | if err := config.ValidatorNum(num); err != nil { 55 | return err 56 | } 57 | config.MaxCharNum = num 58 | return nil 59 | } 60 | 61 | func (config *Config) SetMaxCoroutineNum(num int) error { 62 | if err := config.ValidatorNum(num); err != nil { 63 | return err 64 | } 65 | config.MaxCoroutineNum = num 66 | return nil 67 | } 68 | -------------------------------------------------------------------------------- /domain/service/translator/deepl/lang.go: -------------------------------------------------------------------------------- 1 | package deepl 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | ) 6 | 7 | var langSupported = []translator.LangPair{ 8 | {"ZH", "中文"}, 9 | {"EN", "英语"}, 10 | {"DE", "德语"}, 11 | {"ES", "西班牙语"}, 12 | {"FR", "法语"}, 13 | {"IT", "意大利语"}, 14 | {"PL", "波兰语"}, 15 | {"PT", "葡萄牙语"}, 16 | {"RU", "俄语"}, 17 | {"BG", "保加利亚语"}, 18 | {"CS", "捷克语"}, 19 | {"DA", "丹麦语"}, 20 | {"EL", "希腊语"}, 21 | {"EL", "丹麦语"}, 22 | {"ET", "爱沙尼亚语"}, 23 | {"FI", "芬兰语"}, 24 | {"HU", "匈牙利语"}, 25 | {"LT", "立陶宛语"}, 26 | {"LV", "拉脱维亚语"}, 27 | {"RO", "罗马尼亚语"}, 28 | {"SK", "斯洛伐克语"}, 29 | {"SL", "斯洛文尼亚语"}, 30 | {"SV", "瑞典语"}, 31 | } 32 | -------------------------------------------------------------------------------- /domain/service/translator/deepl/translator.go: -------------------------------------------------------------------------------- 1 | package deepl 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "anto/lib/log" 6 | "context" 7 | "encoding/json" 8 | "fmt" 9 | "github.com/golang-module/carbon" 10 | "strings" 11 | "sync" 12 | ) 13 | 14 | var ( 15 | apiTranslator *Translator 16 | onceTranslator sync.Once 17 | ) 18 | 19 | const ( 20 | TRANSLATE_API_FREE string = "https://api-free.deepl.com/v2/translate" 21 | TRANSLATE_API_PRO string = "https://api.deepl.com/v2/translate" 22 | ) 23 | 24 | func API() *Translator { 25 | onceTranslator.Do(func() { 26 | apiTranslator = New() 27 | }) 28 | return apiTranslator 29 | } 30 | 31 | func New() *Translator { 32 | return &Translator{ 33 | id: "deepl", 34 | name: "DeepL", 35 | sep: "\n", 36 | langSupported: langSupported, 37 | } 38 | } 39 | 40 | type Translator struct { 41 | id string 42 | name string 43 | cfg translator.ImplConfig 44 | langSupported []translator.LangPair 45 | sep string 46 | } 47 | 48 | func (customT *Translator) Init(cfg translator.ImplConfig) { customT.cfg = cfg } 49 | 50 | func (customT *Translator) GetId() string { return customT.id } 51 | func (customT *Translator) GetShortId() string { return "dl" } 52 | func (customT *Translator) GetName() string { return customT.name } 53 | func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg } 54 | func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported } 55 | func (customT *Translator) GetSep() string { return customT.sep } 56 | func (customT *Translator) IsValid() bool { return true } 57 | 58 | func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) { 59 | timeStart := carbon.Now() 60 | texts := strings.Split(args.TextContent, customT.GetSep()) 61 | req := &translateReq{ 62 | Text: texts, SourceLang: args.FromLang, TargetLang: args.ToLang, SplitSentences: "0", 63 | } 64 | 65 | reqBytes, _ := json.Marshal(req) 66 | respBytes, err := translator.RequestSimpleHttp(ctx, customT, TRANSLATE_API_FREE, true, reqBytes, map[string]string{ 67 | "Authorization": fmt.Sprintf("DeepL-Auth-Key %s", customT.cfg.GetAK()), 68 | }) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | resp := new(translateResp) 74 | if err = json.Unmarshal(respBytes, resp); err != nil { 75 | log.Singleton().ErrorF("解析报文异常, 引擎: %s, 错误: %s", customT.GetName(), err) 76 | return nil, fmt.Errorf("解析报文出现异常, 错误: %s", err.Error()) 77 | } 78 | 79 | ret := new(translator.TranslateRes) 80 | 81 | for textIdx, textTarget := range resp.Translations { 82 | ret.Results = append(ret.Results, &translator.TranslateResBlock{ 83 | Id: texts[textIdx], 84 | TextTranslated: textTarget.Text, 85 | }) 86 | } 87 | 88 | ret.TimeUsed = int(carbon.Now().DiffAbsInSeconds(timeStart)) 89 | return ret, nil 90 | } 91 | 92 | type translateReq struct { 93 | Text []string `json:"text"` 94 | SourceLang string `json:"source_lang"` 95 | TargetLang string `json:"target_lang"` 96 | SplitSentences string `json:"split_sentences"` 97 | } 98 | 99 | type translateResp struct { 100 | Translations []struct { 101 | DetectedSourceLanguage string `json:"detected_source_language"` 102 | Text string `json:"text"` 103 | } `json:"translations"` 104 | } 105 | -------------------------------------------------------------------------------- /domain/service/translator/deepl_pro/config.go: -------------------------------------------------------------------------------- 1 | package deepl_pro 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "github.com/spf13/viper" 6 | ) 7 | 8 | type Config struct { 9 | *translator.DefaultConfig 10 | ApiKey string `mapstructure:"api_key"` 11 | QPS int `mapstructure:"qps"` 12 | MaxCharNum int `mapstructure:"max_single_text_length"` 13 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"` 14 | } 15 | 16 | func (config *Config) Default() translator.ImplConfig { 17 | return &Config{ 18 | ApiKey: "", 19 | MaxCharNum: 1000, QPS: 10, MaxCoroutineNum: 5, 20 | } 21 | } 22 | 23 | func (config *Config) SyncDisk(currentViper *viper.Viper) error { 24 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure") 25 | 26 | for tag, val := range tagAndVal { 27 | currentViper.Set(tag, val) 28 | } 29 | return nil 30 | } 31 | 32 | func (config *Config) GetAK() string { return config.ApiKey } 33 | func (config *Config) GetQPS() int { return config.QPS } 34 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum } 35 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum } 36 | 37 | func (config *Config) SetAK(str string) error { 38 | if err := config.ValidatorStr(str); err != nil { 39 | return err 40 | } 41 | config.ApiKey = str 42 | return nil 43 | } 44 | 45 | func (config *Config) SetQPS(num int) error { 46 | if err := config.ValidatorNum(num); err != nil { 47 | return err 48 | } 49 | config.QPS = num 50 | return nil 51 | } 52 | 53 | func (config *Config) SetMaxCharNum(num int) error { 54 | if err := config.ValidatorNum(num); err != nil { 55 | return err 56 | } 57 | config.MaxCharNum = num 58 | return nil 59 | } 60 | 61 | func (config *Config) SetMaxCoroutineNum(num int) error { 62 | if err := config.ValidatorNum(num); err != nil { 63 | return err 64 | } 65 | config.MaxCoroutineNum = num 66 | return nil 67 | } 68 | -------------------------------------------------------------------------------- /domain/service/translator/deepl_pro/lang.go: -------------------------------------------------------------------------------- 1 | package deepl_pro 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | ) 6 | 7 | var langSupported = []translator.LangPair{ 8 | {"ZH", "中文"}, 9 | {"EN", "英语"}, 10 | {"DE", "德语"}, 11 | {"ES", "西班牙语"}, 12 | {"FR", "法语"}, 13 | {"IT", "意大利语"}, 14 | {"PL", "波兰语"}, 15 | {"PT", "葡萄牙语"}, 16 | {"RU", "俄语"}, 17 | {"BG", "保加利亚语"}, 18 | {"CS", "捷克语"}, 19 | {"DA", "丹麦语"}, 20 | {"EL", "希腊语"}, 21 | {"EL", "丹麦语"}, 22 | {"ET", "爱沙尼亚语"}, 23 | {"FI", "芬兰语"}, 24 | {"HU", "匈牙利语"}, 25 | {"LT", "立陶宛语"}, 26 | {"LV", "拉脱维亚语"}, 27 | {"RO", "罗马尼亚语"}, 28 | {"SK", "斯洛伐克语"}, 29 | {"SL", "斯洛文尼亚语"}, 30 | {"SV", "瑞典语"}, 31 | } 32 | -------------------------------------------------------------------------------- /domain/service/translator/deepl_pro/translator.go: -------------------------------------------------------------------------------- 1 | package deepl_pro 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "anto/lib/log" 6 | "context" 7 | "encoding/json" 8 | "fmt" 9 | "github.com/golang-module/carbon" 10 | "strings" 11 | "sync" 12 | ) 13 | 14 | var ( 15 | apiTranslator *Translator 16 | onceTranslator sync.Once 17 | ) 18 | 19 | const ( 20 | TRANSLATE_API_FREE string = "https://api-free.deepl.com/v2/translate" 21 | TRANSLATE_API_PRO string = "https://api.deepl.com/v2/translate" 22 | ) 23 | 24 | func API() *Translator { 25 | onceTranslator.Do(func() { 26 | apiTranslator = New() 27 | }) 28 | return apiTranslator 29 | } 30 | 31 | func New() *Translator { 32 | return &Translator{ 33 | id: "deepl_pro", 34 | name: "DeepL_PRO", 35 | sep: "\n", 36 | langSupported: langSupported, 37 | } 38 | } 39 | 40 | type Translator struct { 41 | id string 42 | name string 43 | cfg translator.ImplConfig 44 | langSupported []translator.LangPair 45 | sep string 46 | } 47 | 48 | func (customT *Translator) Init(cfg translator.ImplConfig) { customT.cfg = cfg } 49 | 50 | func (customT *Translator) GetId() string { return customT.id } 51 | func (customT *Translator) GetShortId() string { return "dlp" } 52 | func (customT *Translator) GetName() string { return customT.name } 53 | func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg } 54 | func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported } 55 | func (customT *Translator) GetSep() string { return customT.sep } 56 | func (customT *Translator) IsValid() bool { return true } 57 | 58 | func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) { 59 | timeStart := carbon.Now() 60 | texts := strings.Split(args.TextContent, customT.GetSep()) 61 | req := &translateReq{ 62 | Text: texts, SourceLang: args.FromLang, TargetLang: args.ToLang, SplitSentences: "0", 63 | } 64 | 65 | reqBytes, _ := json.Marshal(req) 66 | respBytes, err := translator.RequestSimpleHttp(ctx, customT, TRANSLATE_API_PRO, true, reqBytes, map[string]string{ 67 | "Authorization": fmt.Sprintf("DeepL-Auth-Key %s", customT.cfg.GetAK()), 68 | }) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | resp := new(translateResp) 74 | if err = json.Unmarshal(respBytes, resp); err != nil { 75 | log.Singleton().ErrorF("解析报文异常, 引擎: %s, 错误: %s", customT.GetName(), err) 76 | return nil, fmt.Errorf("解析报文出现异常, 错误: %s", err.Error()) 77 | } 78 | 79 | ret := new(translator.TranslateRes) 80 | 81 | for textIdx, textTarget := range resp.Translations { 82 | ret.Results = append(ret.Results, &translator.TranslateResBlock{ 83 | Id: texts[textIdx], 84 | TextTranslated: textTarget.Text, 85 | }) 86 | } 87 | 88 | ret.TimeUsed = int(carbon.Now().DiffAbsInSeconds(timeStart)) 89 | return ret, nil 90 | } 91 | 92 | type translateReq struct { 93 | Text []string `json:"text"` 94 | SourceLang string `json:"source_lang"` 95 | TargetLang string `json:"target_lang"` 96 | SplitSentences string `json:"split_sentences"` 97 | } 98 | 99 | type translateResp struct { 100 | Translations []struct { 101 | DetectedSourceLanguage string `json:"detected_source_language"` 102 | Text string `json:"text"` 103 | } `json:"translations"` 104 | } 105 | -------------------------------------------------------------------------------- /domain/service/translator/errors.go: -------------------------------------------------------------------------------- 1 | package translator 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrSrcAndTgtNotMatched = errors.New("翻译异常, 错误: 原文和译文数量不对等") 7 | ) 8 | -------------------------------------------------------------------------------- /domain/service/translator/g_deepl_x/config.go: -------------------------------------------------------------------------------- 1 | package g_deepl_x 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "github.com/spf13/viper" 6 | ) 7 | 8 | type Config struct { 9 | *translator.DefaultConfig 10 | QPS int `mapstructure:"qps"` 11 | MaxCharNum int `mapstructure:"max_single_text_length"` 12 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"` 13 | } 14 | 15 | func (config *Config) Default() translator.ImplConfig { 16 | return &Config{ 17 | MaxCharNum: 1000, QPS: 10, MaxCoroutineNum: 5, 18 | } 19 | } 20 | 21 | func (config *Config) SyncDisk(currentViper *viper.Viper) error { 22 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure") 23 | 24 | for tag, val := range tagAndVal { 25 | currentViper.Set(tag, val) 26 | } 27 | return nil 28 | } 29 | 30 | func (config *Config) GetQPS() int { return config.QPS } 31 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum } 32 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum } 33 | 34 | func (config *Config) SetQPS(num int) error { 35 | if err := config.ValidatorNum(num); err != nil { 36 | return err 37 | } 38 | config.QPS = num 39 | return nil 40 | } 41 | 42 | func (config *Config) SetMaxCharNum(num int) error { 43 | if err := config.ValidatorNum(num); err != nil { 44 | return err 45 | } 46 | config.MaxCharNum = num 47 | return nil 48 | } 49 | 50 | func (config *Config) SetMaxCoroutineNum(num int) error { 51 | if err := config.ValidatorNum(num); err != nil { 52 | return err 53 | } 54 | config.MaxCoroutineNum = num 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /domain/service/translator/g_deepl_x/lang.go: -------------------------------------------------------------------------------- 1 | package g_deepl_x 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | ) 6 | 7 | var langSupported = []translator.LangPair{ 8 | {"ZH", "中文"}, 9 | {"EN", "英语"}, 10 | {"DE", "德语"}, 11 | {"ES", "西班牙语"}, 12 | {"FR", "法语"}, 13 | {"IT", "意大利语"}, 14 | {"PL", "波兰语"}, 15 | {"PT", "葡萄牙语"}, 16 | {"RU", "俄语"}, 17 | {"BG", "保加利亚语"}, 18 | {"CS", "捷克语"}, 19 | {"DA", "丹麦语"}, 20 | {"EL", "希腊语"}, 21 | {"EL", "丹麦语"}, 22 | {"ET", "爱沙尼亚语"}, 23 | {"FI", "芬兰语"}, 24 | {"HU", "匈牙利语"}, 25 | {"LT", "立陶宛语"}, 26 | {"LV", "拉脱维亚语"}, 27 | {"RO", "罗马尼亚语"}, 28 | {"SK", "斯洛伐克语"}, 29 | {"SL", "斯洛文尼亚语"}, 30 | {"SV", "瑞典语"}, 31 | } 32 | -------------------------------------------------------------------------------- /domain/service/translator/g_deepl_x/translator.go: -------------------------------------------------------------------------------- 1 | package g_deepl_x 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "context" 6 | "github.com/OwO-Network/gdeeplx" 7 | "github.com/golang-module/carbon" 8 | "strings" 9 | "sync" 10 | ) 11 | 12 | var ( 13 | apiTranslator *Translator 14 | onceTranslator sync.Once 15 | ) 16 | 17 | func API() *Translator { 18 | onceTranslator.Do(func() { 19 | apiTranslator = New() 20 | }) 21 | return apiTranslator 22 | } 23 | 24 | func New() *Translator { 25 | return &Translator{ 26 | id: "g_deepl_x", 27 | name: "GDeeplX", 28 | sep: "\n", 29 | langSupported: langSupported, 30 | } 31 | } 32 | 33 | type Translator struct { 34 | id string 35 | name string 36 | cfg translator.ImplConfig 37 | langSupported []translator.LangPair 38 | sep string 39 | } 40 | 41 | func (customT *Translator) Init(cfg translator.ImplConfig) { customT.cfg = cfg } 42 | 43 | func (customT *Translator) GetId() string { return customT.id } 44 | func (customT *Translator) GetShortId() string { return "gdx" } 45 | func (customT *Translator) GetName() string { return customT.name } 46 | func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg } 47 | func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported } 48 | func (customT *Translator) GetSep() string { return customT.sep } 49 | func (customT *Translator) IsValid() bool { return true } 50 | 51 | func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) { 52 | timeStart := carbon.Now() 53 | result, err := gdeeplx.Translate(args.TextContent, args.FromLang, args.ToLang, 0) 54 | if err != nil { 55 | return nil, err 56 | } 57 | resultParsed := result.(map[string]interface{}) 58 | textTranslatedList := strings.Split(resultParsed["data"].(string), customT.sep) 59 | textSourceList := strings.Split(args.TextContent, customT.sep) 60 | if len(textSourceList) != len(textTranslatedList) { 61 | return nil, translator.ErrSrcAndTgtNotMatched 62 | } 63 | 64 | ret := new(translator.TranslateRes) 65 | for textIdx, textSource := range textSourceList { 66 | ret.Results = append(ret.Results, &translator.TranslateResBlock{ 67 | Id: textSource, 68 | TextTranslated: textTranslatedList[textIdx], 69 | }) 70 | } 71 | 72 | ret.TimeUsed = int(carbon.Now().DiffAbsInSeconds(timeStart)) 73 | return ret, nil 74 | 75 | } 76 | -------------------------------------------------------------------------------- /domain/service/translator/google_cloud/config.go: -------------------------------------------------------------------------------- 1 | package google_cloud 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "github.com/spf13/viper" 6 | ) 7 | 8 | type Config struct { 9 | *translator.DefaultConfig 10 | ApiKey string `mapstructure:"api_key"` 11 | QPS int `mapstructure:"qps"` 12 | MaxCharNum int `mapstructure:"max_single_text_length"` 13 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"` 14 | } 15 | 16 | func (config *Config) Default() translator.ImplConfig { 17 | return &Config{ 18 | ApiKey: "", 19 | MaxCharNum: 1000, QPS: 10, MaxCoroutineNum: 5, 20 | } 21 | } 22 | 23 | func (config *Config) SyncDisk(currentViper *viper.Viper) error { 24 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure") 25 | 26 | for tag, val := range tagAndVal { 27 | currentViper.Set(tag, val) 28 | } 29 | return nil 30 | } 31 | 32 | func (config *Config) GetAK() string { return config.ApiKey } 33 | 34 | func (config *Config) GetQPS() int { return config.QPS } 35 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum } 36 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum } 37 | 38 | func (config *Config) SetAK(str string) error { 39 | if err := config.ValidatorStr(str); err != nil { 40 | return err 41 | } 42 | config.ApiKey = str 43 | return nil 44 | } 45 | 46 | func (config *Config) SetQPS(num int) error { 47 | if err := config.ValidatorNum(num); err != nil { 48 | return err 49 | } 50 | config.QPS = num 51 | return nil 52 | } 53 | 54 | func (config *Config) SetMaxCharNum(num int) error { 55 | if err := config.ValidatorNum(num); err != nil { 56 | return err 57 | } 58 | config.MaxCharNum = num 59 | return nil 60 | } 61 | 62 | func (config *Config) SetMaxCoroutineNum(num int) error { 63 | if err := config.ValidatorNum(num); err != nil { 64 | return err 65 | } 66 | config.MaxCoroutineNum = num 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /domain/service/translator/google_cloud/translator.go: -------------------------------------------------------------------------------- 1 | package google_cloud 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "cloud.google.com/go/translate" 6 | "context" 7 | "errors" 8 | "fmt" 9 | "github.com/golang-module/carbon" 10 | "google.golang.org/api/option" 11 | "strings" 12 | "sync" 13 | ) 14 | 15 | var ( 16 | apiTranslator *Translator 17 | onceTranslator sync.Once 18 | ) 19 | 20 | func API() *Translator { 21 | onceTranslator.Do(func() { 22 | apiTranslator = New() 23 | }) 24 | return apiTranslator 25 | } 26 | 27 | func New() *Translator { 28 | return &Translator{ 29 | id: "google_cloud", 30 | name: "谷歌云", 31 | sep: "\n", 32 | langSupported: langSupported, 33 | } 34 | } 35 | 36 | type Translator struct { 37 | id string 38 | name string 39 | cfg translator.ImplConfig 40 | langSupported []translator.LangPair 41 | sep string 42 | } 43 | 44 | func (customT *Translator) Init(cfg translator.ImplConfig) { customT.cfg = cfg } 45 | 46 | func (customT *Translator) GetId() string { return customT.id } 47 | func (customT *Translator) GetShortId() string { return "gc" } 48 | func (customT *Translator) GetName() string { return customT.name } 49 | func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg } 50 | func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported } 51 | func (customT *Translator) GetSep() string { return customT.sep } 52 | func (customT *Translator) IsValid() bool { return customT.cfg.GetAK() != "" } 53 | 54 | func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) { 55 | timeStart := carbon.Now() 56 | client, err := translate.NewClient(ctx, option.WithAPIKey(customT.cfg.GetAK())) 57 | if err != nil { 58 | return nil, err 59 | } 60 | fromLangTag := convertLangToTag(args.FromLang) 61 | toLangTag := convertLangToTag(args.ToLang) 62 | if fromLangTag == nil || toLangTag == nil { 63 | return nil, errors.New(fmt.Sprintf("来源语种或目标语种暂不支持")) 64 | } 65 | textRaw := strings.Split(args.TextContent, customT.GetSep()) 66 | results, err := client.Translate(ctx, textRaw, *fromLangTag, &translate.Options{ 67 | Source: *toLangTag, Format: translate.Text, 68 | }) 69 | if err != nil { 70 | return nil, err 71 | } 72 | if len(results) != len(textRaw) { 73 | return nil, translator.ErrSrcAndTgtNotMatched 74 | } 75 | 76 | ret := new(translator.TranslateRes) 77 | for textIdx, textSource := range textRaw { 78 | ret.Results = append(ret.Results, &translator.TranslateResBlock{Id: textSource, TextTranslated: results[textIdx].Text}) 79 | } 80 | 81 | ret.TimeUsed = int(carbon.Now().DiffAbsInSeconds(timeStart)) 82 | return ret, nil 83 | 84 | } 85 | 86 | type lingVaMTResp struct { 87 | State bool `json:"__N_SSG"` 88 | Props struct { 89 | Type int `json:"type"` 90 | TextTranslated string `json:"translation"` 91 | Params struct { 92 | FromLanguage string `json:"source"` 93 | ToLanguage string `json:"target"` 94 | TextSource string `json:"query"` 95 | } `json:"initial"` 96 | } `json:"pageProps"` 97 | } 98 | -------------------------------------------------------------------------------- /domain/service/translator/huawei_cloud_nlp/config.go: -------------------------------------------------------------------------------- 1 | package huawei_cloud_nlp 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "github.com/spf13/viper" 6 | ) 7 | 8 | type Config struct { 9 | *translator.DefaultConfig 10 | AKId string `mapstructure:"ak_id"` // Access Key 11 | SkKey string `mapstructure:"sk_key"` // Secret Access Key 12 | Region string `mapstructure:"region"` // 当前接口开发的区域, 目前仅支持华北-北京四终端节点 13 | ProjectId string `mapstructure:"project_id"` // 项目ID 14 | QPS int `mapstructure:"qps"` 15 | MaxCharNum int `mapstructure:"max_single_text_length"` 16 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"` 17 | } 18 | 19 | func (config *Config) Default() translator.ImplConfig { 20 | return &Config{ 21 | AKId: "", SkKey: "", Region: "cn-north-4", ProjectId: "", 22 | MaxCharNum: 2000, QPS: 20, MaxCoroutineNum: 10, 23 | } 24 | } 25 | 26 | func (config *Config) SyncDisk(currentViper *viper.Viper) error { 27 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure") 28 | 29 | for tag, val := range tagAndVal { 30 | currentViper.Set(tag, val) 31 | } 32 | return nil 33 | } 34 | 35 | func (config *Config) GetAK() string { return config.AKId } 36 | func (config *Config) GetSK() string { return config.SkKey } 37 | func (config *Config) GetRegion() string { 38 | if config.Region != "" { 39 | return config.Region 40 | } 41 | return "cn-north-4" 42 | } 43 | func (config *Config) GetProjectKey() string { return config.ProjectId } 44 | 45 | func (config *Config) GetQPS() int { return config.QPS } 46 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum } 47 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum } 48 | 49 | func (config *Config) SetAK(str string) error { 50 | if err := config.ValidatorStr(str); err != nil { 51 | return err 52 | } 53 | config.AKId = str 54 | return nil 55 | } 56 | 57 | func (config *Config) SetSK(str string) error { 58 | if err := config.ValidatorStr(str); err != nil { 59 | return err 60 | } 61 | config.SkKey = str 62 | return nil 63 | } 64 | 65 | func (config *Config) SetRegion(str string) error { 66 | if err := config.ValidatorStr(str); err != nil { 67 | return err 68 | } 69 | config.Region = str 70 | return nil 71 | } 72 | 73 | func (config *Config) SetProjectKey(str string) error { 74 | if str != "" { 75 | if err := config.ValidatorStr(str); err != nil { 76 | return err 77 | } 78 | } 79 | 80 | config.ProjectId = str 81 | return nil 82 | } 83 | 84 | func (config *Config) SetQPS(num int) error { 85 | if err := config.ValidatorNum(num); err != nil { 86 | return err 87 | } 88 | config.QPS = num 89 | return nil 90 | } 91 | 92 | func (config *Config) SetMaxCharNum(num int) error { 93 | if err := config.ValidatorNum(num); err != nil { 94 | return err 95 | } 96 | config.MaxCharNum = num 97 | return nil 98 | } 99 | 100 | func (config *Config) SetMaxCoroutineNum(num int) error { 101 | if err := config.ValidatorNum(num); err != nil { 102 | return err 103 | } 104 | config.MaxCoroutineNum = num 105 | return nil 106 | } 107 | -------------------------------------------------------------------------------- /domain/service/translator/huawei_cloud_nlp/lang.go: -------------------------------------------------------------------------------- 1 | package huawei_cloud_nlp 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/nlp/v2/model" 6 | ) 7 | 8 | var langSupported = []translator.LangPair{ 9 | {"zh", "中文(简体)"}, 10 | {"en", "英语"}, 11 | {"ru", "俄语"}, 12 | {"fr", "法语"}, 13 | {"de", "德语"}, 14 | {"ko", "韩语"}, 15 | {"ja", "日语"}, 16 | {"th", "泰语"}, 17 | {"ar", "阿拉伯语"}, 18 | {"pt", "葡萄牙语"}, 19 | {"tr", "土耳其语"}, 20 | {"es", "西班牙语"}, 21 | {"vi", "越南语"}, 22 | } 23 | 24 | var langTo = map[string]model.TextTranslationReqTo{ 25 | "zh": model.GetTextTranslationReqToEnum().ZH, 26 | "en": model.GetTextTranslationReqToEnum().EN, 27 | "ru": model.GetTextTranslationReqToEnum().RU, 28 | "fr": model.GetTextTranslationReqToEnum().FR, 29 | "de": model.GetTextTranslationReqToEnum().DE, 30 | "ko": model.GetTextTranslationReqToEnum().KO, 31 | "ja": model.GetTextTranslationReqToEnum().JA, 32 | "th": model.GetTextTranslationReqToEnum().TH, 33 | "ar": model.GetTextTranslationReqToEnum().AR, 34 | "pt": model.GetTextTranslationReqToEnum().PT, 35 | "tr": model.GetTextTranslationReqToEnum().TR, 36 | "es": model.GetTextTranslationReqToEnum().ES, 37 | "vi": model.GetTextTranslationReqToEnum().VI, 38 | } 39 | 40 | var langFrom = map[string]model.TextTranslationReqFrom{ 41 | "zh": model.GetTextTranslationReqFromEnum().ZH, 42 | "en": model.GetTextTranslationReqFromEnum().EN, 43 | "ru": model.GetTextTranslationReqFromEnum().RU, 44 | "fr": model.GetTextTranslationReqFromEnum().FR, 45 | "de": model.GetTextTranslationReqFromEnum().DE, 46 | "ko": model.GetTextTranslationReqFromEnum().KO, 47 | "ja": model.GetTextTranslationReqFromEnum().JA, 48 | "th": model.GetTextTranslationReqFromEnum().TH, 49 | "ar": model.GetTextTranslationReqFromEnum().AR, 50 | "pt": model.GetTextTranslationReqFromEnum().PT, 51 | "tr": model.GetTextTranslationReqFromEnum().TR, 52 | "es": model.GetTextTranslationReqFromEnum().ES, 53 | "vi": model.GetTextTranslationReqFromEnum().VI, 54 | } 55 | -------------------------------------------------------------------------------- /domain/service/translator/huawei_cloud_nlp/translator.go: -------------------------------------------------------------------------------- 1 | package huawei_cloud_nlp 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "anto/lib/log" 6 | "anto/lib/restrictor" 7 | "context" 8 | "fmt" 9 | "github.com/golang-module/carbon" 10 | "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic" 11 | nlp "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/nlp/v2" 12 | "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/nlp/v2/model" 13 | region "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/nlp/v2/region" 14 | "strings" 15 | "sync" 16 | ) 17 | 18 | var ( 19 | apiTranslator *Translator 20 | onceTranslator sync.Once 21 | ) 22 | 23 | func API() *Translator { 24 | onceTranslator.Do(func() { 25 | apiTranslator = New() 26 | }) 27 | return apiTranslator 28 | } 29 | 30 | func New() *Translator { 31 | return &Translator{ 32 | id: "huawei_cloud_nlp", 33 | name: "华为云", 34 | sep: "\n", 35 | langSupported: langSupported, 36 | } 37 | } 38 | 39 | type Translator struct { 40 | id string 41 | name string 42 | cfg translator.ImplConfig 43 | langSupported []translator.LangPair 44 | sep string 45 | } 46 | 47 | func (customT *Translator) Init(cfg translator.ImplConfig) { 48 | customT.cfg = cfg 49 | } 50 | 51 | func (customT *Translator) GetId() string { return customT.id } 52 | func (customT *Translator) GetShortId() string { return "hw" } 53 | func (customT *Translator) GetName() string { return customT.name } 54 | func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg } 55 | func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported } 56 | func (customT *Translator) GetSep() string { return customT.sep } 57 | 58 | func (customT *Translator) IsValid() bool { 59 | return customT.cfg != nil && 60 | customT.cfg.GetAK() != "" && customT.cfg.GetSK() != "" && 61 | customT.cfg.GetRegion() != "" 62 | } 63 | 64 | func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) { 65 | timeStart := carbon.Now() 66 | ret := new(translator.TranslateRes) 67 | 68 | request := &model.RunTextTranslationRequest{} 69 | sceneTextTranslationReq := model.GetTextTranslationReqSceneEnum().COMMON 70 | request.Body = &model.TextTranslationReq{ 71 | Scene: &sceneTextTranslationReq, 72 | To: langTo[args.ToLang], 73 | From: langFrom[args.FromLang], 74 | Text: args.TextContent, 75 | } 76 | if err := restrictor.Singleton().Wait(customT.GetId(), ctx); err != nil { 77 | return nil, fmt.Errorf("限流异常, 错误: %s", err.Error()) 78 | } 79 | resp, err := customT.getClient().RunTextTranslation(request) 80 | 81 | if err != nil { 82 | log.Singleton().ErrorF("调用接口失败, 引擎: %s, 错误: %s", customT.GetName(), err) 83 | return nil, fmt.Errorf("调用接口失败(%s)", err) 84 | } 85 | if resp.ErrorCode != nil && *resp.ErrorCode != "" { 86 | log.Singleton().ErrorF("接口响应错误, 引擎: %s, 错误: %s(%s)", customT.GetName(), *resp.ErrorCode, *resp.ErrorMsg) 87 | return nil, fmt.Errorf("响应错误(代码: %s, 错误: %s)", *resp.ErrorCode, *resp.ErrorMsg) 88 | } 89 | 90 | srcTexts := strings.Split(*resp.SrcText, customT.GetSep()) 91 | translatedTexts := strings.Split(*resp.TranslatedText, customT.GetSep()) 92 | if len(srcTexts) != len(translatedTexts) { 93 | return nil, translator.ErrSrcAndTgtNotMatched 94 | } 95 | for idx, text := range srcTexts { 96 | ret.Results = append(ret.Results, &translator.TranslateResBlock{ 97 | Id: text, 98 | TextTranslated: translatedTexts[idx], 99 | }) 100 | } 101 | 102 | ret.TimeUsed = int(carbon.Now().DiffAbsInSeconds(timeStart)) 103 | return ret, nil 104 | 105 | } 106 | 107 | func (customT *Translator) getAuth() *basic.Credentials { 108 | return basic.NewCredentialsBuilder().WithAk(customT.cfg.GetAK()).WithSk(customT.cfg.GetSK()).Build() 109 | } 110 | 111 | func (customT *Translator) getClient() *nlp.NlpClient { 112 | return nlp.NewNlpClient( 113 | nlp.NlpClientBuilder(). 114 | WithRegion(region.ValueOf(customT.cfg.GetRegion())). 115 | WithCredential(customT.getAuth()). 116 | Build(), 117 | ) 118 | } 119 | -------------------------------------------------------------------------------- /domain/service/translator/ling_va/config.go: -------------------------------------------------------------------------------- 1 | package ling_va 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "github.com/spf13/viper" 6 | ) 7 | 8 | type Config struct { 9 | *translator.DefaultConfig 10 | DataId string `mapstructure:"data_id"` 11 | QPS int `mapstructure:"qps"` 12 | MaxCharNum int `mapstructure:"max_single_text_length"` 13 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"` 14 | } 15 | 16 | func (config *Config) Default() translator.ImplConfig { 17 | return &Config{ 18 | DataId: "3qnDcUVykFKnSC3cdRX2t", 19 | MaxCharNum: 1000, QPS: 10, MaxCoroutineNum: 5, 20 | } 21 | } 22 | 23 | func (config *Config) SyncDisk(currentViper *viper.Viper) error { 24 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure") 25 | 26 | for tag, val := range tagAndVal { 27 | currentViper.Set(tag, val) 28 | } 29 | return nil 30 | } 31 | 32 | func (config *Config) GetAK() string { return config.DataId } 33 | 34 | func (config *Config) GetQPS() int { return config.QPS } 35 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum } 36 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum } 37 | 38 | func (config *Config) SetAK(str string) error { 39 | if err := config.ValidatorStr(str); err != nil { 40 | return err 41 | } 42 | config.DataId = str 43 | return nil 44 | } 45 | 46 | func (config *Config) SetQPS(num int) error { 47 | if err := config.ValidatorNum(num); err != nil { 48 | return err 49 | } 50 | config.QPS = num 51 | return nil 52 | } 53 | 54 | func (config *Config) SetMaxCharNum(num int) error { 55 | if err := config.ValidatorNum(num); err != nil { 56 | return err 57 | } 58 | config.MaxCharNum = num 59 | return nil 60 | } 61 | 62 | func (config *Config) SetMaxCoroutineNum(num int) error { 63 | if err := config.ValidatorNum(num); err != nil { 64 | return err 65 | } 66 | config.MaxCoroutineNum = num 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /domain/service/translator/ling_va/lang.go: -------------------------------------------------------------------------------- 1 | package ling_va 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | ) 6 | 7 | var langSupported = []translator.LangPair{ 8 | {"zh", "中文"}, 9 | {"en", "英语"}, 10 | {"ja", "日语"}, 11 | {"ru", "俄语"}, 12 | {"fr", "法语"}, 13 | {"ko", "韩语"}, 14 | {"de", "德语"}, 15 | {"da", "丹麦语"}, 16 | {"nl", "荷兰语"}, 17 | {"it", "意大利语"}, 18 | {"ar", "阿拉伯语"}, 19 | {"be", "白俄罗斯语"}, 20 | } 21 | -------------------------------------------------------------------------------- /domain/service/translator/ling_va/translator.go: -------------------------------------------------------------------------------- 1 | package ling_va 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "anto/lib/log" 6 | "context" 7 | "encoding/json" 8 | "fmt" 9 | "github.com/golang-module/carbon" 10 | "go.uber.org/zap" 11 | "net/url" 12 | "strings" 13 | "sync" 14 | ) 15 | 16 | var ( 17 | apiTranslator *Translator 18 | onceTranslator sync.Once 19 | ) 20 | 21 | func API() *Translator { 22 | onceTranslator.Do(func() { 23 | apiTranslator = New() 24 | }) 25 | return apiTranslator 26 | } 27 | 28 | func New() *Translator { 29 | return &Translator{ 30 | id: "ling_va", 31 | name: "Lingva", 32 | sep: "\n", 33 | langSupported: langSupported, 34 | } 35 | } 36 | 37 | // Translator LingVA翻译已崩溃, 当前处于不可用状态, 所以直接禁用 38 | type Translator struct { 39 | id string 40 | name string 41 | cfg translator.ImplConfig 42 | langSupported []translator.LangPair 43 | sep string 44 | } 45 | 46 | func (customT *Translator) Init(cfg translator.ImplConfig) { customT.cfg = cfg } 47 | 48 | func (customT *Translator) GetId() string { return customT.id } 49 | func (customT *Translator) GetShortId() string { return "lv" } 50 | func (customT *Translator) GetName() string { return customT.name } 51 | func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg } 52 | func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported } 53 | func (customT *Translator) GetSep() string { return customT.sep } 54 | func (customT *Translator) IsValid() bool { return false } 55 | 56 | func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) { 57 | timeStart := carbon.Now() 58 | 59 | var api = fmt.Sprintf("https://lingva.ml/_next/data/%s/", customT.cfg.GetAK()) 60 | queryUrl := fmt.Sprintf( 61 | "%s/%s/%s/%s.json", api, 62 | args.FromLang, args.ToLang, url.PathEscape(args.TextContent), 63 | ) 64 | respBytes, err := translator.RequestSimpleHttp(ctx, customT, queryUrl, false, nil, nil) 65 | if err != nil { 66 | return nil, err 67 | } 68 | lingVaResp := new(lingVaMTResp) 69 | if err := json.Unmarshal(respBytes, lingVaResp); err != nil { 70 | fmt.Println(string(respBytes)) 71 | log.Singleton().ErrorF("解析报文异常, 引擎: %s, 错误: %s", customT.GetName(), err) 72 | return nil, fmt.Errorf("解析报文出现异常, 错误: %s", err.Error()) 73 | } 74 | if lingVaResp.State == false { 75 | log.Singleton().ErrorF("接口响应异常, 引擎: %s, 错误: %s", customT.GetName(), err, zap.String("result", string(respBytes))) 76 | return nil, fmt.Errorf("翻译异常") 77 | } 78 | textTranslatedList := strings.Split(lingVaResp.Props.TextTranslated, customT.sep) 79 | textSourceList := strings.Split(lingVaResp.Props.Params.TextSource, customT.sep) 80 | if len(textSourceList) != len(textTranslatedList) { 81 | return nil, translator.ErrSrcAndTgtNotMatched 82 | } 83 | 84 | ret := new(translator.TranslateRes) 85 | for textIdx, textSource := range textSourceList { 86 | ret.Results = append(ret.Results, &translator.TranslateResBlock{ 87 | Id: textSource, 88 | TextTranslated: textTranslatedList[textIdx], 89 | }) 90 | } 91 | 92 | ret.TimeUsed = int(carbon.Now().DiffAbsInSeconds(timeStart)) 93 | return ret, nil 94 | 95 | } 96 | 97 | type lingVaMTResp struct { 98 | State bool `json:"__N_SSG"` 99 | Props struct { 100 | Type int `json:"type"` 101 | TextTranslated string `json:"translation"` 102 | Params struct { 103 | FromLanguage string `json:"source"` 104 | ToLanguage string `json:"target"` 105 | TextSource string `json:"query"` 106 | } `json:"initial"` 107 | } `json:"pageProps"` 108 | } 109 | -------------------------------------------------------------------------------- /domain/service/translator/microsoft_edge/config.go: -------------------------------------------------------------------------------- 1 | package microsoft_edge 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "github.com/spf13/viper" 6 | ) 7 | 8 | type Config struct { 9 | *translator.DefaultConfig 10 | QPS int `mapstructure:"qps"` 11 | MaxCharNum int `mapstructure:"max_single_text_length"` 12 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"` 13 | } 14 | 15 | func (config *Config) Default() translator.ImplConfig { 16 | return &Config{ 17 | MaxCharNum: 1000, QPS: 10, MaxCoroutineNum: 5, 18 | } 19 | } 20 | 21 | func (config *Config) SyncDisk(currentViper *viper.Viper) error { 22 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure") 23 | 24 | for tag, val := range tagAndVal { 25 | currentViper.Set(tag, val) 26 | } 27 | return nil 28 | } 29 | 30 | func (config *Config) GetQPS() int { return config.QPS } 31 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum } 32 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum } 33 | 34 | func (config *Config) SetQPS(num int) error { 35 | if err := config.ValidatorNum(num); err != nil { 36 | return err 37 | } 38 | config.QPS = num 39 | return nil 40 | } 41 | 42 | func (config *Config) SetMaxCharNum(num int) error { 43 | if err := config.ValidatorNum(num); err != nil { 44 | return err 45 | } 46 | config.MaxCharNum = num 47 | return nil 48 | } 49 | 50 | func (config *Config) SetMaxCoroutineNum(num int) error { 51 | if err := config.ValidatorNum(num); err != nil { 52 | return err 53 | } 54 | config.MaxCoroutineNum = num 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /domain/service/translator/microsoft_edge/lang.go: -------------------------------------------------------------------------------- 1 | package microsoft_edge 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | ) 6 | 7 | var langSupported = []translator.LangPair{ 8 | {"zh-cn", "中文"}, 9 | {"en", "英语"}, 10 | {"ja", "日语"}, 11 | {"ru", "俄语"}, 12 | {"fr", "法语"}, 13 | {"ko", "韩语"}, 14 | {"de", "德语"}, 15 | {"da", "丹麦语"}, 16 | {"nl", "荷兰语"}, 17 | {"it", "意大利语"}, 18 | {"ar", "阿拉伯语"}, 19 | {"be", "白俄罗斯语"}, 20 | } 21 | -------------------------------------------------------------------------------- /domain/service/translator/microsoft_edge/translator.go: -------------------------------------------------------------------------------- 1 | package microsoft_edge 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "anto/lib/log" 6 | "context" 7 | "encoding/json" 8 | "fmt" 9 | "github.com/golang-module/carbon" 10 | "strings" 11 | "sync" 12 | ) 13 | 14 | var ( 15 | apiTranslator *Translator 16 | onceTranslator sync.Once 17 | ) 18 | 19 | func API() *Translator { 20 | onceTranslator.Do(func() { 21 | apiTranslator = New() 22 | }) 23 | return apiTranslator 24 | } 25 | 26 | func New() *Translator { 27 | return &Translator{ 28 | id: "edge", 29 | name: "微软翻译", 30 | sep: "\n", 31 | langSupported: langSupported, 32 | } 33 | } 34 | 35 | // Translator LingVA翻译已崩溃, 当前处于不可用状态, 所以直接禁用 36 | type Translator struct { 37 | id string 38 | name string 39 | cfg translator.ImplConfig 40 | langSupported []translator.LangPair 41 | sep string 42 | token string 43 | } 44 | 45 | func (customT *Translator) Init(cfg translator.ImplConfig) { customT.cfg = cfg } 46 | 47 | func (customT *Translator) GetId() string { return customT.id } 48 | func (customT *Translator) GetShortId() string { return "me" } 49 | func (customT *Translator) GetName() string { return customT.name } 50 | func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg } 51 | func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported } 52 | func (customT *Translator) GetSep() string { return customT.sep } 53 | func (customT *Translator) IsValid() bool { return true } 54 | 55 | func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) { 56 | timeStart := carbon.Now() 57 | token, err := customT.getToken(ctx) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | var queryUrl = fmt.Sprintf( 63 | "https://api-edge.cognitive.microsofttranslator.com/translate?from=%s&to=%s&api-version=3.0&includeSentenceLength=true", 64 | args.FromLang, args.ToLang, 65 | ) 66 | req := []microsoftEdgeReq{} 67 | textReq := strings.Split(args.TextContent, customT.sep) 68 | for _, text := range textReq { 69 | req = append(req, microsoftEdgeReq{Text: text}) 70 | } 71 | respBytes, err := translator.RequestSimpleHttp(ctx, customT, queryUrl, true, req, map[string]string{ 72 | "Authorization": fmt.Sprintf("Bearer Bearer %s", token), 73 | }) 74 | if err != nil { 75 | return nil, err 76 | } 77 | resp := []microsoftEdgeResp{} 78 | if err := json.Unmarshal(respBytes, &resp); err != nil { 79 | log.Singleton().ErrorF("解析报文异常, 引擎: %s, 错误: %s", customT.GetName(), err) 80 | return nil, fmt.Errorf("解析报文出现异常, 错误: %s", err.Error()) 81 | } 82 | textResp := []string{} 83 | for _, edgeResp := range resp { 84 | if edgeResp.Error.Code != 0 { 85 | // The request is not authorized because credentials are missing or invalid. 86 | if edgeResp.Error.Code == 401001 { 87 | customT.token = "" 88 | } 89 | return nil, fmt.Errorf("翻译异常: %s", edgeResp.Error.Message) 90 | } 91 | for _, translation := range edgeResp.Translations { 92 | textResp = append(textResp, translation.Text) 93 | } 94 | } 95 | if len(textReq) != len(textResp) { 96 | return nil, translator.ErrSrcAndTgtNotMatched 97 | } 98 | 99 | ret := new(translator.TranslateRes) 100 | for textIdx, textSource := range textReq { 101 | ret.Results = append(ret.Results, &translator.TranslateResBlock{ 102 | Id: textSource, 103 | TextTranslated: textResp[textIdx], 104 | }) 105 | } 106 | 107 | ret.TimeUsed = int(carbon.Now().DiffAbsInSeconds(timeStart)) 108 | return ret, nil 109 | 110 | } 111 | 112 | func (customT *Translator) getToken(ctx context.Context) (token string, err error) { 113 | if customT.token == "" { 114 | tokenBytes := []byte{} 115 | tokenBytes, err = translator.RequestSimpleHttp(ctx, customT, "https://edge.microsoft.com/translate/auth", false, nil, nil) 116 | if err != nil { 117 | return 118 | } 119 | customT.token = string(tokenBytes) 120 | } 121 | token = customT.token 122 | return 123 | } 124 | 125 | type microsoftEdgeReq struct { 126 | Text string `json:"text"` 127 | } 128 | 129 | type microsoftEdgeResp struct { 130 | Error struct { 131 | Code int `json:"code"` 132 | Message string `json:"message"` 133 | } `json:"error"` 134 | DetectedLanguage struct { 135 | Language string `json:"language"` 136 | Score float64 `json:"score"` 137 | } `json:"detectedLanguage"` 138 | Translations []struct { 139 | Text string `json:"text"` 140 | To string `json:"to"` 141 | SentLen struct { 142 | SrcSentLen []int `json:"srcSentLen"` 143 | TransSentLen []int `json:"transSentLen"` 144 | } `json:"sentLen"` 145 | } `json:"translations"` 146 | } 147 | -------------------------------------------------------------------------------- /domain/service/translator/niutrans/config.go: -------------------------------------------------------------------------------- 1 | package niutrans 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "github.com/spf13/viper" 6 | ) 7 | 8 | type Config struct { 9 | *translator.DefaultConfig 10 | AppKey string `mapstructure:"app_key"` 11 | QPS int `mapstructure:"qps"` 12 | MaxCharNum int `mapstructure:"max_single_text_length"` 13 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"` 14 | } 15 | 16 | func (config *Config) Default() translator.ImplConfig { 17 | return &Config{ 18 | AppKey: "", 19 | MaxCharNum: 5000, QPS: 50, MaxCoroutineNum: 20, 20 | } 21 | } 22 | 23 | func (config *Config) SyncDisk(currentViper *viper.Viper) error { 24 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure") 25 | 26 | for tag, val := range tagAndVal { 27 | currentViper.Set(tag, val) 28 | } 29 | return nil 30 | } 31 | 32 | func (config *Config) GetAK() string { return config.AppKey } 33 | 34 | func (config *Config) GetQPS() int { return config.QPS } 35 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum } 36 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum } 37 | 38 | func (config *Config) SetAK(str string) error { 39 | if err := config.ValidatorStr(str); err != nil { 40 | return err 41 | } 42 | config.AppKey = str 43 | return nil 44 | } 45 | 46 | func (config *Config) SetQPS(num int) error { 47 | if err := config.ValidatorNum(num); err != nil { 48 | return err 49 | } 50 | config.QPS = num 51 | return nil 52 | } 53 | 54 | func (config *Config) SetMaxCharNum(num int) error { 55 | if err := config.ValidatorNum(num); err != nil { 56 | return err 57 | } 58 | config.MaxCharNum = num 59 | return nil 60 | } 61 | 62 | func (config *Config) SetMaxCoroutineNum(num int) error { 63 | if err := config.ValidatorNum(num); err != nil { 64 | return err 65 | } 66 | config.MaxCoroutineNum = num 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /domain/service/translator/niutrans/lang.go: -------------------------------------------------------------------------------- 1 | package niutrans 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | ) 6 | 7 | // https://niutrans.com/documents/contents/trans_text#languageList 8 | var langSupported = []translator.LangPair{ 9 | {"zh", "中文"}, 10 | {"en", "英语"}, 11 | {"ko", "韩语"}, 12 | {"ja", "日语"}, 13 | {"fr", "法语"}, 14 | {"de", "德语"}, 15 | {"it", "意大利语"}, 16 | } 17 | -------------------------------------------------------------------------------- /domain/service/translator/niutrans/translator.go: -------------------------------------------------------------------------------- 1 | package niutrans 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "anto/lib/log" 6 | "context" 7 | "encoding/json" 8 | "fmt" 9 | "github.com/golang-module/carbon" 10 | "strings" 11 | "sync" 12 | ) 13 | 14 | const apiTranslate = "https://api.niutrans.com/NiuTransServer/translation" 15 | 16 | var ( 17 | apiSingleton *Translator 18 | onceSingleton sync.Once 19 | ) 20 | 21 | func API() *Translator { 22 | onceSingleton.Do(func() { 23 | apiSingleton = New() 24 | }) 25 | return apiSingleton 26 | } 27 | 28 | func New() *Translator { 29 | return &Translator{ 30 | id: "niutrans", 31 | name: "小牛翻译", 32 | sep: "\n", 33 | langSupported: langSupported, 34 | } 35 | } 36 | 37 | type Translator struct { 38 | id string 39 | name string 40 | cfg translator.ImplConfig 41 | langSupported []translator.LangPair 42 | sep string 43 | } 44 | 45 | func (customT *Translator) Init(cfg translator.ImplConfig) { customT.cfg = cfg } 46 | 47 | func (customT *Translator) GetId() string { return customT.id } 48 | func (customT *Translator) GetShortId() string { return "nt" } 49 | func (customT *Translator) GetName() string { return customT.name } 50 | func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg } 51 | func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported } 52 | func (customT *Translator) GetSep() string { return customT.sep } 53 | func (customT *Translator) IsValid() bool { return customT.cfg != nil && customT.cfg.GetAK() != "" } 54 | 55 | func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) { 56 | timeStart := carbon.Now() 57 | tr := &translateRequest{ 58 | Apikey: customT.cfg.GetAK(), 59 | SrcText: args.TextContent, 60 | From: args.FromLang, 61 | To: args.ToLang, 62 | } 63 | reqBytes, _ := json.Marshal(tr) 64 | respBytes, err := translator.RequestSimpleHttp(ctx, customT, apiTranslate, true, reqBytes, nil) 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | resp := new(translateResponse) 70 | if err = json.Unmarshal(respBytes, resp); err != nil { 71 | log.Singleton().ErrorF("解析报文异常, 引擎: %s, 错误: %s", customT.GetName(), err) 72 | return nil, fmt.Errorf("解析报文出现异常, 错误: %s", err.Error()) 73 | } 74 | 75 | if resp.ErrorMsg != "" { 76 | log.Singleton().ErrorF("接口响应异常, 引擎: %s, 错误: %s(%s)", customT.GetName(), resp.ErrorMsg, resp.ErrorCode) 77 | return nil, fmt.Errorf("接口响应异常, 引擎: %s, 错误: %s", customT.GetName(), resp.ErrorMsg) 78 | } 79 | 80 | srcTexts := strings.Split(args.TextContent, customT.GetSep()) 81 | tgtTexts := strings.Split(resp.TgtText, customT.GetSep()) 82 | if len(srcTexts) != len(tgtTexts) { 83 | return nil, translator.ErrSrcAndTgtNotMatched 84 | } 85 | 86 | ret := new(translator.TranslateRes) 87 | 88 | for textIdx, textTarget := range tgtTexts { 89 | ret.Results = append(ret.Results, &translator.TranslateResBlock{ 90 | Id: srcTexts[textIdx], TextTranslated: textTarget, 91 | }) 92 | } 93 | 94 | ret.TimeUsed = int(carbon.Now().DiffAbsInSeconds(timeStart)) 95 | return ret, nil 96 | 97 | } 98 | 99 | type translateRequest struct { 100 | Apikey string `json:"apikey"` 101 | SrcText string `json:"src_text"` 102 | From string `json:"from"` 103 | To string `json:"to"` 104 | } 105 | 106 | type translateResponse struct { 107 | TgtText string `json:"tgt_text,omitempty"` 108 | To string `json:"to"` 109 | From string `json:"from"` 110 | ErrorCode string `json:"error_code,omitempty"` 111 | ErrorMsg string `json:"error_msg,omitempty"` 112 | } 113 | -------------------------------------------------------------------------------- /domain/service/translator/openai/config.go: -------------------------------------------------------------------------------- 1 | package openai 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "github.com/spf13/viper" 6 | ) 7 | 8 | type Config struct { 9 | *translator.DefaultConfig 10 | AppKey string `mapstructure:"app_key"` 11 | ProjectKey string `mapstructure:"project_key"` 12 | QPS int `mapstructure:"qps"` 13 | MaxCharNum int `mapstructure:"max_single_text_length"` 14 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"` 15 | } 16 | 17 | func (config *Config) Default() translator.ImplConfig { 18 | return &Config{ 19 | AppKey: "", ProjectKey: "gpt-3.5-turbo", 20 | MaxCharNum: 2000, QPS: 1, MaxCoroutineNum: 1, 21 | } 22 | } 23 | 24 | func (config *Config) SyncDisk(currentViper *viper.Viper) error { 25 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure") 26 | 27 | for tag, val := range tagAndVal { 28 | currentViper.Set(tag, val) 29 | } 30 | return nil 31 | } 32 | 33 | func (config *Config) GetAK() string { return config.AppKey } 34 | func (config *Config) GetProjectKey() string { return config.ProjectKey } 35 | func (config *Config) GetQPS() int { return config.QPS } 36 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum } 37 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum } 38 | 39 | func (config *Config) SetAK(str string) error { 40 | if err := config.ValidatorStr(str); err != nil { 41 | return err 42 | } 43 | config.AppKey = str 44 | return nil 45 | } 46 | 47 | func (config *Config) SetProjectKey(projectKey string) error { 48 | if projectKey == "" { 49 | projectKey = "gpt-3.5-turbo" 50 | } 51 | config.ProjectKey = projectKey 52 | return nil 53 | } 54 | 55 | func (config *Config) SetQPS(num int) error { 56 | config.QPS = 1 57 | return nil 58 | } 59 | 60 | func (config *Config) SetMaxCharNum(num int) error { 61 | if err := config.ValidatorNum(num); err != nil { 62 | return err 63 | } 64 | if num > 2000 { 65 | num = 2000 66 | } 67 | config.MaxCharNum = num 68 | return nil 69 | } 70 | 71 | func (config *Config) SetMaxCoroutineNum(num int) error { 72 | config.MaxCoroutineNum = 1 73 | return nil 74 | } 75 | -------------------------------------------------------------------------------- /domain/service/translator/openai/lang.go: -------------------------------------------------------------------------------- 1 | package openai 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | ) 6 | 7 | var langSupported = []translator.LangPair{ 8 | {"Chinese", "中文"}, 9 | {"English", "英语"}, 10 | } 11 | -------------------------------------------------------------------------------- /domain/service/translator/openai_sweet/config.go: -------------------------------------------------------------------------------- 1 | package openai_sweet 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "github.com/spf13/viper" 6 | ) 7 | 8 | type Config struct { 9 | *translator.DefaultConfig 10 | AppKey string `mapstructure:"app_key"` 11 | ProjectKey string `mapstructure:"project_key"` 12 | QPS int `mapstructure:"qps"` 13 | MaxCharNum int `mapstructure:"max_single_text_length"` 14 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"` 15 | } 16 | 17 | func (config *Config) Default() translator.ImplConfig { 18 | return &Config{ 19 | AppKey: "", ProjectKey: "gpt-3.5-turbo", 20 | MaxCharNum: 2000, QPS: 1, MaxCoroutineNum: 1, 21 | } 22 | } 23 | 24 | func (config *Config) SyncDisk(currentViper *viper.Viper) error { 25 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure") 26 | 27 | for tag, val := range tagAndVal { 28 | currentViper.Set(tag, val) 29 | } 30 | return nil 31 | } 32 | 33 | func (config *Config) GetAK() string { return config.AppKey } 34 | func (config *Config) GetProjectKey() string { return config.ProjectKey } 35 | func (config *Config) GetQPS() int { return config.QPS } 36 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum } 37 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum } 38 | 39 | func (config *Config) SetAK(str string) error { 40 | if err := config.ValidatorStr(str); err != nil { 41 | return err 42 | } 43 | config.AppKey = str 44 | return nil 45 | } 46 | 47 | func (config *Config) SetProjectKey(projectKey string) error { 48 | if projectKey == "" { 49 | projectKey = "gpt-3.5-turbo" 50 | } 51 | config.ProjectKey = projectKey 52 | return nil 53 | } 54 | 55 | func (config *Config) SetQPS(num int) error { 56 | config.QPS = 1 57 | return nil 58 | } 59 | 60 | func (config *Config) SetMaxCharNum(num int) error { 61 | if err := config.ValidatorNum(num); err != nil { 62 | return err 63 | } 64 | if num > 2000 { 65 | num = 2000 66 | } 67 | config.MaxCharNum = num 68 | return nil 69 | } 70 | 71 | func (config *Config) SetMaxCoroutineNum(num int) error { 72 | config.MaxCoroutineNum = 1 73 | return nil 74 | } 75 | -------------------------------------------------------------------------------- /domain/service/translator/openai_sweet/lang.go: -------------------------------------------------------------------------------- 1 | package openai_sweet 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | ) 6 | 7 | var langSupported = []translator.LangPair{ 8 | {"Chinese", "中文"}, 9 | {"English", "英语"}, 10 | } 11 | -------------------------------------------------------------------------------- /domain/service/translator/openapi_youdao/config.go: -------------------------------------------------------------------------------- 1 | package openapi_youdao 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "github.com/spf13/viper" 6 | ) 7 | 8 | type Config struct { 9 | *translator.DefaultConfig 10 | AppKey string `mapstructure:"app_key"` // 应用ID 11 | AppSecret string `mapstructure:"app_secret"` // 应用密钥 12 | QPS int `mapstructure:"qps"` 13 | MaxCharNum int `mapstructure:"max_single_text_length"` 14 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"` 15 | } 16 | 17 | func (config *Config) Default() translator.ImplConfig { 18 | return &Config{ 19 | AppKey: "", AppSecret: "", 20 | MaxCharNum: 5000, QPS: 1, MaxCoroutineNum: 1, 21 | } 22 | } 23 | 24 | func (config *Config) SyncDisk(currentViper *viper.Viper) error { 25 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure") 26 | 27 | for tag, val := range tagAndVal { 28 | currentViper.Set(tag, val) 29 | } 30 | return nil 31 | } 32 | 33 | func (config *Config) GetAK() string { return config.AppKey } 34 | func (config *Config) GetSK() string { return config.AppSecret } 35 | 36 | func (config *Config) GetQPS() int { return config.QPS } 37 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum } 38 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum } 39 | 40 | func (config *Config) SetAK(str string) error { 41 | if err := config.ValidatorStr(str); err != nil { 42 | return err 43 | } 44 | config.AppKey = str 45 | return nil 46 | } 47 | 48 | func (config *Config) SetSK(str string) error { 49 | if err := config.ValidatorStr(str); err != nil { 50 | return err 51 | } 52 | config.AppSecret = str 53 | return nil 54 | } 55 | 56 | func (config *Config) SetQPS(num int) error { 57 | if err := config.ValidatorNum(num); err != nil { 58 | return err 59 | } 60 | config.QPS = num 61 | return nil 62 | } 63 | 64 | func (config *Config) SetMaxCharNum(num int) error { 65 | if err := config.ValidatorNum(num); err != nil { 66 | return err 67 | } 68 | config.MaxCharNum = num 69 | return nil 70 | } 71 | 72 | func (config *Config) SetMaxCoroutineNum(num int) error { 73 | if err := config.ValidatorNum(num); err != nil { 74 | return err 75 | } 76 | config.MaxCoroutineNum = num 77 | return nil 78 | } 79 | -------------------------------------------------------------------------------- /domain/service/translator/openapi_youdao/error.go: -------------------------------------------------------------------------------- 1 | package openapi_youdao 2 | 3 | var errorMap = map[string]string{ 4 | "101": "缺少必填的参数,首先确保必填参数齐全, 然后确认参数书写是否正确", 5 | "102": "不支持的语言类型", 6 | "103": "翻译文本过长", 7 | "104": "不支持的API类型", 8 | "105": "不支持的签名类型", 9 | "106": "不支持的响应类型", 10 | "107": "不支持的传输加密类型", 11 | "108": "应用ID无效, 注册账号, 登录后台创建应用和实例并完成绑定, 可获得应用ID和应用密钥等信息", 12 | "109": "batchLog格式不正确", 13 | "110": "无相关服务的有效实例,应用没有绑定服务, 可以新建服务, 绑定服务", 14 | "111": "开发者账号无效", 15 | "112": "请求服务无效", 16 | "113": "翻译字符串(q)不能为空", 17 | "118": "detectLevel取值错误", 18 | "201": "解密失败, 可能为DES,BASE64,URLDecode的错误", 19 | "202": "签名检验失败", 20 | "203": "访问IP地址不在可访问IP列表", 21 | "205": "请求的接口与应用的平台类型不一致", 22 | "206": "因为时间戳无效导致签名校验失败", 23 | "207": "重放请求", 24 | "301": "辞典查询失败", 25 | "302": "翻译查询失败", 26 | "303": "服务端的其它异常", 27 | "304": "会话闲置太久超时", 28 | "401": "账户已经欠费, 请进行账户充值", 29 | "402": "offlinesdk不可用", 30 | "411": "访问频率受限,请稍后访问", 31 | "412": "长请求过于频繁, 请稍后访问", 32 | } 33 | -------------------------------------------------------------------------------- /domain/service/translator/openapi_youdao/lang.go: -------------------------------------------------------------------------------- 1 | package openapi_youdao 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | ) 6 | 7 | var langSupported = []translator.LangPair{ 8 | {"zh-CHS", "中文"}, 9 | {"en", "英语"}, 10 | {"ja", "日语"}, 11 | {"ko", "韩语"}, 12 | {"fr", "法语"}, 13 | {"es", "西班牙语"}, 14 | {"pt", "葡萄牙语"}, 15 | {"it", "意大利语"}, 16 | {"ru", "俄语"}, 17 | {"vi", "越南语"}, 18 | {"de", "德语"}, 19 | {"ar", "阿拉伯语"}, 20 | } 21 | -------------------------------------------------------------------------------- /domain/service/translator/openapi_youdao/translator.go: -------------------------------------------------------------------------------- 1 | package openapi_youdao 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "anto/lib/log" 6 | "anto/lib/util" 7 | "context" 8 | "crypto/sha256" 9 | "encoding/hex" 10 | "encoding/json" 11 | "fmt" 12 | "github.com/golang-module/carbon" 13 | "github.com/google/go-querystring/query" 14 | "strings" 15 | "sync" 16 | ) 17 | 18 | var ( 19 | apiTranslator *Translator 20 | onceTranslator sync.Once 21 | ) 22 | 23 | func API() *Translator { 24 | onceTranslator.Do(func() { 25 | apiTranslator = New() 26 | }) 27 | return apiTranslator 28 | } 29 | 30 | func New() *Translator { 31 | return &Translator{ 32 | id: "openapi_youdao", 33 | name: "有道智云", 34 | api: "https://openapi.youdao.com/v2/api", 35 | sep: "\n", 36 | langSupported: langSupported, 37 | } 38 | } 39 | 40 | type Translator struct { 41 | id string 42 | name string 43 | cfg translator.ImplConfig 44 | api string 45 | langSupported []translator.LangPair 46 | sep string 47 | } 48 | 49 | func (customT *Translator) Init(cfg translator.ImplConfig) { customT.cfg = cfg } 50 | 51 | func (customT *Translator) GetId() string { return customT.id } 52 | func (customT *Translator) GetShortId() string { return "oy" } 53 | func (customT *Translator) GetName() string { return customT.name } 54 | func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg } 55 | func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported } 56 | func (customT *Translator) GetSep() string { return customT.sep } 57 | func (customT *Translator) IsValid() bool { 58 | return customT.cfg != nil && customT.cfg.GetAK() != "" && customT.cfg.GetSK() != "" 59 | } 60 | 61 | func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) { 62 | timeStart := carbon.Now() 63 | texts := strings.Split(args.TextContent, customT.GetSep()) 64 | newReq := &remoteReq{ 65 | TextQuery: texts, 66 | From: args.FromLang, To: args.ToLang, 67 | AppKey: customT.cfg.GetAK(), Salt: util.Uid(), SignType: "v3", 68 | CurrentTime: fmt.Sprintf("%d", carbon.Now().Timestamp()), 69 | } 70 | newReq.Sign = customT.signBuilder(strings.Join(texts, ""), newReq.Salt, newReq.CurrentTime) 71 | params, _ := query.Values(newReq) 72 | urlQueried := fmt.Sprintf("%s?%s", customT.api, params.Encode()) 73 | respBytes, err := translator.RequestSimpleHttp(ctx, customT, urlQueried, false, nil, nil) 74 | if err != nil { 75 | return nil, err 76 | } 77 | newResp := new(remoteResp) 78 | if err = json.Unmarshal(respBytes, newResp); err != nil { 79 | log.Singleton().ErrorF("引擎: %s, 错误: 解析报文异常(%s)", customT.GetName(), err) 80 | return nil, fmt.Errorf("错误: 解析报文出现异常(%s)", err.Error()) 81 | } 82 | 83 | if newResp.ErrorCode != "0" { 84 | errMsg := errorMap[newResp.ErrorCode] 85 | log.Singleton().ErrorF("引擎: %s, 错误: 接口响应异常(%s:%s)", customT.GetName(), newResp.ErrorCode, errMsg) 86 | return nil, fmt.Errorf("错误: 翻译异常(%s:%s)", newResp.ErrorCode, errMsg) 87 | } 88 | 89 | ret := new(translator.TranslateRes) 90 | for _, transBlock := range newResp.TranslateResults { 91 | ret.Results = append(ret.Results, &translator.TranslateResBlock{ 92 | Id: transBlock.Query, 93 | TextTranslated: transBlock.Translation, 94 | }) 95 | } 96 | 97 | ret.TimeUsed = int(carbon.Now().DiffInSeconds(timeStart)) 98 | return ret, nil 99 | } 100 | 101 | func (customT *Translator) signBuilder(textQuery, salt, currentTime string) string { 102 | tmpQuery := textQuery 103 | 104 | if tmpLen := len(textQuery); tmpLen > 20 { 105 | tmpQuery = fmt.Sprintf("%s%d%s", textQuery[0:10], tmpLen, textQuery[tmpLen-10:]) 106 | } 107 | tmpQuery = fmt.Sprintf("%s%s%s%s%s", customT.cfg.GetAK(), tmpQuery, salt, currentTime, customT.cfg.GetSK()) 108 | newSha := sha256.New() 109 | newSha.Write([]byte(tmpQuery)) 110 | 111 | return hex.EncodeToString(newSha.Sum(nil)) 112 | } 113 | 114 | type remoteReq struct { 115 | TextQuery []string `url:"q"` // 要翻译的文本.可指定多个 116 | From string `url:"from"` // 源语言 117 | To string `url:"to"` // 目标语言 118 | AppKey string `url:"appKey"` // 应用标识(应用ID) 119 | Salt string `url:"salt"` // 随机字符串,可使用UUID进行生产 120 | CurrentTime string `url:"curtime"` // 随机字符串,可使用UUID进行生产 121 | Sign string `url:"sign"` // 签名信息sha256(appKey+q+salt+密钥) 122 | SignType string `url:"signType"` // 签名类型(v3) 123 | } 124 | 125 | type remoteResp struct { 126 | ErrorCode string `json:"errorCode"` 127 | ErrorIndex []int `json:"errorIndex"` 128 | TranslateResults []struct { 129 | Query string `json:"query"` 130 | Translation string `json:"translation"` 131 | Type string `json:"type"` 132 | VerifyResult string `json:"verifyResult"` 133 | } `json:"translateResults"` 134 | } 135 | -------------------------------------------------------------------------------- /domain/service/translator/request.go: -------------------------------------------------------------------------------- 1 | package translator 2 | 3 | import ( 4 | "anto/lib/log" 5 | "anto/lib/restrictor" 6 | "context" 7 | "fmt" 8 | "github.com/imroc/req/v3" 9 | "io" 10 | "strings" 11 | ) 12 | 13 | func RequestSimpleHttp(ctx context.Context, engine ImplTranslator, url string, isPost bool, body interface{}, headers map[string]string) ([]byte, error) { 14 | if err := restrictor.Singleton().Wait(engine.GetId(), ctx); err != nil { 15 | return nil, fmt.Errorf("限流异常, 错误: %s", err.Error()) 16 | } 17 | if headers == nil { 18 | headers = make(map[string]string) 19 | } 20 | headers["content-type"] = "application/json" 21 | headers["accept"] = "application/json" 22 | 23 | client := req.C().SetCommonHeaders(headers).SetCommonRetryCount(3) 24 | if strings.Contains(url, "api.openai.com") { 25 | client.SetProxyURL("http://127.0.0.1:7890") 26 | } 27 | request := client.R() 28 | if isPost && body != nil { 29 | request.SetBody(body) 30 | } 31 | var httpResp *req.Response 32 | var err error 33 | if isPost { 34 | httpResp, err = request.Post(url) 35 | } else { 36 | httpResp, err = request.Get(url) 37 | } 38 | defer func() { 39 | if httpResp != nil && httpResp.Body != nil { 40 | _ = httpResp.Body.Close() 41 | } 42 | }() 43 | 44 | if err != nil { 45 | log.Singleton().ErrorF("调用接口失败, 引擎: %s, 错误: %s", engine.GetName(), err) 46 | return nil, fmt.Errorf("网络请求异常, 错误: %s", err.Error()) 47 | } 48 | 49 | if httpResp.StatusCode != 200 { 50 | log.Singleton().ErrorF("调用接口失败, 引擎: %s, 错误: %d(%s)", engine.GetName(), httpResp.StatusCode, httpResp.Status) 51 | return nil, fmt.Errorf("网络响应异常, 错误: %d(%s)", httpResp.StatusCode, httpResp.Status) 52 | } 53 | 54 | respBytes, err := io.ReadAll(httpResp.Body) 55 | if err != nil { 56 | log.Singleton().ErrorF("读取报文异常, 引擎: %s, 错误: %s", engine.GetName(), err) 57 | return nil, fmt.Errorf("读取报文异常, 错误: %s", err.Error()) 58 | } 59 | 60 | return respBytes, nil 61 | } 62 | -------------------------------------------------------------------------------- /domain/service/translator/tencent_cloud_mt/config.go: -------------------------------------------------------------------------------- 1 | package tencent_cloud_mt 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "github.com/spf13/viper" 6 | "strconv" 7 | ) 8 | 9 | type Config struct { 10 | *translator.DefaultConfig 11 | SecretId string `mapstructure:"secret_id"` // 用于标识接口调用者身份 12 | SecretKey string `mapstructure:"secret_key"` // 用于验证接口调用者的身份 13 | Region string `mapstructure:"region"` // 地域参数 14 | ProjectId int64 `mapstructure:"project_id"` // 项目ID, 默认值为0 15 | QPS int `mapstructure:"qps"` 16 | MaxCharNum int `mapstructure:"max_single_text_length"` 17 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"` 18 | } 19 | 20 | func (config *Config) Default() translator.ImplConfig { 21 | return &Config{ 22 | SecretId: "", SecretKey: "", Region: "ap-chengdu", ProjectId: 0, 23 | MaxCharNum: 2000, QPS: 5, MaxCoroutineNum: 4, 24 | } 25 | } 26 | 27 | func (config *Config) SyncDisk(currentViper *viper.Viper) error { 28 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure") 29 | 30 | for tag, val := range tagAndVal { 31 | currentViper.Set(tag, val) 32 | } 33 | return nil 34 | } 35 | 36 | func (config *Config) GetAK() string { return config.SecretId } 37 | func (config *Config) GetSK() string { return config.SecretKey } 38 | func (config *Config) GetRegion() string { return config.Region } 39 | func (config *Config) GetProjectKey() string { return strconv.Itoa(int(config.ProjectId)) } 40 | func (config *Config) GetProjectKeyPtr() *int64 { return &config.ProjectId } 41 | 42 | func (config *Config) GetQPS() int { return config.QPS } 43 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum } 44 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum } 45 | 46 | func (config *Config) SetAK(str string) error { 47 | if err := config.ValidatorStr(str); err != nil { 48 | return err 49 | } 50 | config.SecretId = str 51 | return nil 52 | } 53 | 54 | func (config *Config) SetSK(str string) error { 55 | if err := config.ValidatorStr(str); err != nil { 56 | return err 57 | } 58 | config.SecretKey = str 59 | return nil 60 | } 61 | 62 | func (config *Config) SetProjectKey(projectKey string) error { 63 | if err := config.ValidatorStr(projectKey); err != nil { 64 | return err 65 | } 66 | tmpPK, err := strconv.Atoi(projectKey) 67 | if err != nil { 68 | return err 69 | } 70 | config.ProjectId = int64(tmpPK) 71 | return nil 72 | } 73 | 74 | func (config *Config) SetQPS(num int) error { 75 | if err := config.ValidatorNum(num); err != nil { 76 | return err 77 | } 78 | config.QPS = num 79 | return nil 80 | } 81 | 82 | func (config *Config) SetMaxCharNum(num int) error { 83 | if err := config.ValidatorNum(num); err != nil { 84 | return err 85 | } 86 | config.MaxCharNum = num 87 | return nil 88 | } 89 | 90 | func (config *Config) SetMaxCoroutineNum(num int) error { 91 | if err := config.ValidatorNum(num); err != nil { 92 | return err 93 | } 94 | config.MaxCoroutineNum = num 95 | return nil 96 | } 97 | -------------------------------------------------------------------------------- /domain/service/translator/tencent_cloud_mt/lang.go: -------------------------------------------------------------------------------- 1 | package tencent_cloud_mt 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | ) 6 | 7 | var langSupported = []translator.LangPair{ 8 | {"zh", "中文"}, 9 | {"en", "英语"}, 10 | {"ja", "日语"}, 11 | {"ko", "韩语"}, 12 | {"fr", "法语"}, 13 | {"es", "西班牙语"}, 14 | {"it", "意大利语"}, 15 | {"de", "德语"}, 16 | {"tr", "土耳其语"}, 17 | {"ru", "俄语"}, 18 | {"pt", "葡萄牙语"}, 19 | {"vi", "越南语"}, 20 | {"id", "印尼语"}, 21 | {"th", "泰语"}, 22 | {"ms", "马来西亚语"}, 23 | {"ar", "阿拉伯语"}, 24 | {"hi", "印地语"}, 25 | } 26 | -------------------------------------------------------------------------------- /domain/service/translator/translator.go: -------------------------------------------------------------------------------- 1 | package translator 2 | 3 | import "context" 4 | 5 | type ImplTranslator interface { 6 | Init(cfg ImplConfig) 7 | GetId() string 8 | GetShortId() string 9 | GetName() string 10 | GetCfg() ImplConfig 11 | GetLangSupported() []LangPair 12 | GetSep() string 13 | IsValid() bool 14 | Translate(context.Context, *TranslateArgs) (*TranslateRes, error) 15 | } 16 | 17 | type TranslateArgs struct { 18 | FromLang string 19 | ToLang string 20 | TextContent string 21 | } 22 | 23 | type TranslateRes struct { 24 | TimeUsed int 25 | Msg []string 26 | Results []*TranslateResBlock 27 | } 28 | 29 | type TranslateResBlock struct { 30 | Id string // 采用原文做ID 31 | TextTranslated string 32 | } 33 | 34 | type LangPair struct { 35 | Key string // 语种编码 36 | Name string // 语种名称 37 | } 38 | -------------------------------------------------------------------------------- /domain/service/translator/volcengine/config.go: -------------------------------------------------------------------------------- 1 | package volcengine 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "github.com/spf13/viper" 6 | ) 7 | 8 | type Config struct { 9 | *translator.DefaultConfig 10 | AccessKey string `mapstructure:"access_key"` 11 | SecretKey string `mapstructure:"secret_key"` 12 | QPS int `mapstructure:"qps"` 13 | MaxCharNum int `mapstructure:"max_single_text_length"` 14 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"` 15 | } 16 | 17 | func (config *Config) Default() translator.ImplConfig { 18 | return &Config{ 19 | AccessKey: "", SecretKey: "", 20 | MaxCharNum: 5000, QPS: 10, MaxCoroutineNum: 20, 21 | } 22 | } 23 | 24 | func (config *Config) SyncDisk(currentViper *viper.Viper) error { 25 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure") 26 | 27 | for tag, val := range tagAndVal { 28 | currentViper.Set(tag, val) 29 | } 30 | return nil 31 | } 32 | 33 | func (config *Config) GetAK() string { return config.AccessKey } 34 | func (config *Config) GetSK() string { return config.SecretKey } 35 | func (config *Config) GetQPS() int { return config.QPS } 36 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum } 37 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum } 38 | 39 | func (config *Config) SetAK(str string) error { 40 | if err := config.ValidatorStr(str); err != nil { 41 | return err 42 | } 43 | config.AccessKey = str 44 | return nil 45 | } 46 | 47 | func (config *Config) SetSK(str string) error { 48 | if err := config.ValidatorStr(str); err != nil { 49 | return err 50 | } 51 | config.SecretKey = str 52 | return nil 53 | } 54 | 55 | func (config *Config) SetQPS(num int) error { 56 | if err := config.ValidatorNum(num); err != nil { 57 | return err 58 | } 59 | config.QPS = num 60 | return nil 61 | } 62 | 63 | func (config *Config) SetMaxCharNum(num int) error { 64 | if err := config.ValidatorNum(num); err != nil { 65 | return err 66 | } 67 | config.MaxCharNum = num 68 | return nil 69 | } 70 | 71 | func (config *Config) SetMaxCoroutineNum(num int) error { 72 | if err := config.ValidatorNum(num); err != nil { 73 | return err 74 | } 75 | config.MaxCoroutineNum = num 76 | return nil 77 | } 78 | -------------------------------------------------------------------------------- /domain/service/translator/volcengine/lang.go: -------------------------------------------------------------------------------- 1 | package volcengine 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | ) 6 | 7 | var langSupported = []translator.LangPair{ 8 | {"zh", "中文"}, 9 | {"en", "英语"}, 10 | {"ja", "日语"}, 11 | {"ru", "俄语"}, 12 | {"fr", "法语"}, 13 | {"ko", "韩语"}, 14 | {"de", "德语"}, 15 | } 16 | -------------------------------------------------------------------------------- /domain/service/translator/volcengine/translator.go: -------------------------------------------------------------------------------- 1 | package volcengine 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "anto/lib/log" 6 | "anto/lib/restrictor" 7 | "context" 8 | "encoding/json" 9 | "fmt" 10 | "github.com/golang-module/carbon" 11 | "github.com/volcengine/volc-sdk-golang/base" 12 | "net/http" 13 | "net/url" 14 | "strings" 15 | "sync" 16 | "time" 17 | ) 18 | 19 | const ( 20 | host = "open.volcengineapi.com" 21 | service = "translate" 22 | version = "2020-06-01" 23 | ) 24 | 25 | var ( 26 | apiSingleton *Translator 27 | onceSingleton sync.Once 28 | ) 29 | 30 | func API() *Translator { 31 | onceSingleton.Do(func() { 32 | apiSingleton = New() 33 | }) 34 | return apiSingleton 35 | } 36 | 37 | func New() *Translator { 38 | return &Translator{ 39 | id: "volc_engine", 40 | name: "火山引擎", 41 | sep: "\n", 42 | langSupported: langSupported, 43 | } 44 | } 45 | 46 | type Translator struct { 47 | id string 48 | name string 49 | cfg translator.ImplConfig 50 | langSupported []translator.LangPair 51 | sep string 52 | mtClient *base.Client 53 | } 54 | 55 | func (customT *Translator) Init(cfg translator.ImplConfig) { customT.cfg = cfg } 56 | 57 | func (customT *Translator) GetId() string { return customT.id } 58 | func (customT *Translator) GetShortId() string { return "ve" } 59 | func (customT *Translator) GetName() string { return customT.name } 60 | func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg } 61 | func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported } 62 | func (customT *Translator) GetSep() string { return customT.sep } 63 | func (customT *Translator) IsValid() bool { 64 | return customT.cfg != nil && customT.cfg.GetAK() != "" && customT.cfg.GetSK() != "" 65 | } 66 | 67 | func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) { 68 | timeStart := carbon.Now() 69 | 70 | params := &translateRequestParams{ 71 | SourceLanguage: args.FromLang, 72 | TargetLanguage: args.ToLang, 73 | } 74 | params.TextList = append(params.TextList, args.TextContent) 75 | jsonBytes, _ := json.Marshal(params) 76 | if err := restrictor.Singleton().Wait(customT.GetId(), ctx); err != nil { 77 | return nil, fmt.Errorf("限流异常, 错误: %s", err.Error()) 78 | } 79 | respBytes, _, err := customT.client().Json("TranslateText", nil, string(jsonBytes)) 80 | if err != nil { 81 | return nil, err 82 | } 83 | 84 | resp := new(translateResponse) 85 | if err = json.Unmarshal(respBytes, resp); err != nil { 86 | log.Singleton().ErrorF("解析报文异常, 引擎: %s, 错误: %s", customT.GetName(), err) 87 | return nil, fmt.Errorf("解析报文出现异常, 错误: %s", err.Error()) 88 | } 89 | 90 | if resp.ResponseMetaData.Error.Code != "" { 91 | log.Singleton().ErrorF("接口响应异常, 引擎: %s, 错误: %s(%s)", customT.GetName(), resp.ResponseMetaData.Error.Message, resp.ResponseMetaData.Error.Code) 92 | return nil, fmt.Errorf("接口响应异常, 引擎: %s, 错误: %s", customT.GetName(), resp.ResponseMetaData.Error.Message) 93 | } 94 | srcTexts := strings.Split(args.TextContent, customT.GetSep()) 95 | tgtTexts := strings.Split(resp.TranslationList[0].Translation, customT.GetSep()) 96 | if len(srcTexts) != len(tgtTexts) { 97 | return nil, translator.ErrSrcAndTgtNotMatched 98 | } 99 | 100 | ret := new(translator.TranslateRes) 101 | 102 | for textIdx, textTarget := range tgtTexts { 103 | ret.Results = append(ret.Results, &translator.TranslateResBlock{ 104 | Id: srcTexts[textIdx], TextTranslated: textTarget, 105 | }) 106 | } 107 | 108 | ret.TimeUsed = int(carbon.Now().DiffAbsInSeconds(timeStart)) 109 | return ret, nil 110 | 111 | } 112 | 113 | func (customT *Translator) client() *base.Client { 114 | if customT.mtClient == nil { 115 | serviceInfo := &base.ServiceInfo{ 116 | Timeout: 5 * time.Second, Host: host, 117 | Header: http.Header{"Accept": []string{"application/json"}}, 118 | Credentials: base.Credentials{Region: base.RegionCnNorth1, Service: service}, 119 | } 120 | 121 | apiInfoList := map[string]*base.ApiInfo{ 122 | "TranslateText": { 123 | Method: http.MethodPost, Path: "/", 124 | Query: url.Values{"Action": []string{"TranslateText"}, "Version": []string{version}}, 125 | }, 126 | } 127 | client := base.NewClient(serviceInfo, apiInfoList) 128 | client.SetAccessKey(customT.cfg.GetAK()) 129 | client.SetSecretKey(customT.cfg.GetSK()) 130 | customT.mtClient = client 131 | } 132 | 133 | return customT.mtClient 134 | } 135 | 136 | type translateRequestParams struct { 137 | SourceLanguage string 138 | TargetLanguage string 139 | TextList []string 140 | } 141 | 142 | type translateResponse struct { 143 | ResponseMetaData struct { 144 | RequestId string `json:"RequestId"` 145 | Action string `json:"Action"` 146 | Version string `json:"Version"` 147 | Service string `json:"Service"` 148 | Region string `json:"Region"` 149 | Error struct { 150 | Code string `json:"Code"` 151 | Message string `json:"Message"` 152 | } `json:"Error"` 153 | } `json:"ResponseMetaData"` 154 | TranslationList []struct { 155 | Translation string `json:"Translation"` 156 | //DetectedSourceLanguage string `json:"DetectedSourceLanguage"` 157 | //Extra interface{} `json:"Extra"` 158 | } `json:"TranslationList"` 159 | } 160 | -------------------------------------------------------------------------------- /domain/service/translator/xfyun/config.go: -------------------------------------------------------------------------------- 1 | package xfyun 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "github.com/spf13/viper" 6 | ) 7 | 8 | type Config struct { 9 | *translator.DefaultConfig 10 | AppId string `mapstructure:"app_id"` 11 | ApiKey string `mapstructure:"api_key"` 12 | ApiSecret string `mapstructure:"api_secret"` 13 | QPS int `mapstructure:"qps"` 14 | MaxCharNum int `mapstructure:"max_single_text_length"` 15 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"` 16 | } 17 | 18 | func (config *Config) Default() translator.ImplConfig { 19 | return &Config{ 20 | AppId: "", ApiKey: "", ApiSecret: "", 21 | MaxCharNum: 5000, QPS: 50, MaxCoroutineNum: 20, 22 | } 23 | } 24 | 25 | func (config *Config) SyncDisk(currentViper *viper.Viper) error { 26 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure") 27 | 28 | for tag, val := range tagAndVal { 29 | currentViper.Set(tag, val) 30 | } 31 | return nil 32 | } 33 | 34 | func (config *Config) GetProjectKey() string { return config.AppId } 35 | func (config *Config) GetAK() string { return config.ApiKey } 36 | func (config *Config) GetSK() string { return config.ApiSecret } 37 | 38 | func (config *Config) GetQPS() int { return config.QPS } 39 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum } 40 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum } 41 | 42 | func (config *Config) SetProjectKey(str string) error { 43 | if err := config.ValidatorStr(str); err != nil { 44 | return err 45 | } 46 | config.AppId = str 47 | return nil 48 | } 49 | 50 | func (config *Config) SetAK(str string) error { 51 | if err := config.ValidatorStr(str); err != nil { 52 | return err 53 | } 54 | config.ApiKey = str 55 | return nil 56 | } 57 | 58 | func (config *Config) SetSK(str string) error { 59 | if err := config.ValidatorStr(str); err != nil { 60 | return err 61 | } 62 | config.ApiSecret = str 63 | return nil 64 | } 65 | 66 | func (config *Config) SetQPS(num int) error { 67 | if err := config.ValidatorNum(num); err != nil { 68 | return err 69 | } 70 | config.QPS = num 71 | return nil 72 | } 73 | 74 | func (config *Config) SetMaxCharNum(num int) error { 75 | if err := config.ValidatorNum(num); err != nil { 76 | return err 77 | } 78 | config.MaxCharNum = num 79 | return nil 80 | } 81 | 82 | func (config *Config) SetMaxCoroutineNum(num int) error { 83 | if err := config.ValidatorNum(num); err != nil { 84 | return err 85 | } 86 | config.MaxCoroutineNum = num 87 | return nil 88 | } 89 | -------------------------------------------------------------------------------- /domain/service/translator/xfyun/lang.go: -------------------------------------------------------------------------------- 1 | package xfyun 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | ) 6 | 7 | // @link https://www.xfyun.cn/doc/nlp/xftrans_new/API.html#%E8%AF%AD%E7%A7%8D%E5%88%97%E8%A1%A8 8 | var langSupported = []translator.LangPair{ 9 | {"cn", "中文"}, {"en", "英语"}, {"cs", "捷克语"}, {"ha", "豪萨语"}, 10 | {"ja", "日语"}, {"ro", "罗马尼亚语"}, {"hu", "匈牙利语"}, {"ko", "韩语"}, 11 | {"sv", "瑞典语"}, {"sw", "斯瓦希里语"}, {"th", "泰语"}, {"nl", "荷兰语"}, 12 | {"uz", "乌兹别克语"}, {"ru", "俄语"}, {"pl", "波兰语"}, {"zu", "祖鲁语"}, 13 | {"bg", "保加利亚语"}, {"ar", "阿拉伯语"}, {"el", "希腊语"}, {"uk", "乌克兰语"}, 14 | {"fa", "波斯语"}, {"he", "希伯来语"}, {"vi", "越南语"}, {"ps", "普什图语"}, 15 | {"hy", "亚美尼亚语"}, {"ms", "马来语"}, {"ur", "乌尔都语"}, {"hy", "亚美尼亚语"}, 16 | {"ms", "马来语"}, {"ur", "乌尔都语"}, {"ka", "格鲁吉亚语"}, {"id", "印尼语"}, 17 | {"yue", "广东话"}, {"tl", "菲律宾语"}, {"bn", "孟加拉语"}, {"ii", "彝语"}, 18 | {"de", "德语"}, {"nm", "外蒙语"}, {"zua", "壮语"}, {"es", "西班牙语"}, 19 | {"kk", "外哈语"}, {"mn", "内蒙语"}, {"fr", "法语"}, {"tr", "土耳其语"}, 20 | {"kka", "内哈萨克语"}, 21 | } 22 | -------------------------------------------------------------------------------- /domain/service/translator/youdao/config.go: -------------------------------------------------------------------------------- 1 | package youdao 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "github.com/spf13/viper" 6 | ) 7 | 8 | type Config struct { 9 | *translator.DefaultConfig `mapstructure:"-"` 10 | QPS int `mapstructure:"qps"` 11 | MaxCharNum int `mapstructure:"max_single_text_length"` 12 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"` 13 | } 14 | 15 | func (config *Config) Default() translator.ImplConfig { 16 | return &Config{ 17 | MaxCharNum: 2000, QPS: 50, MaxCoroutineNum: 20, 18 | } 19 | } 20 | 21 | func (config *Config) SyncDisk(currentViper *viper.Viper) error { 22 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure") 23 | 24 | for tag, val := range tagAndVal { 25 | currentViper.Set(tag, val) 26 | } 27 | return nil 28 | } 29 | 30 | func (config *Config) GetQPS() int { return config.QPS } 31 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum } 32 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum } 33 | 34 | func (config *Config) SetQPS(num int) error { 35 | if err := config.ValidatorNum(num); err != nil { 36 | return err 37 | } 38 | config.QPS = num 39 | return nil 40 | } 41 | 42 | func (config *Config) SetMaxCharNum(num int) error { 43 | if err := config.ValidatorNum(num); err != nil { 44 | return err 45 | } 46 | config.MaxCharNum = num 47 | return nil 48 | } 49 | 50 | func (config *Config) SetMaxCoroutineNum(num int) error { 51 | if err := config.ValidatorNum(num); err != nil { 52 | return err 53 | } 54 | config.MaxCoroutineNum = num 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /domain/service/translator/youdao/lang.go: -------------------------------------------------------------------------------- 1 | package youdao 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | ) 6 | 7 | var langSupported = []translator.LangPair{ 8 | {"zh_cn", "中文"}, 9 | {"en", "英语"}, 10 | } 11 | -------------------------------------------------------------------------------- /domain/service/translator/youdao/translator.go: -------------------------------------------------------------------------------- 1 | package youdao 2 | 3 | import ( 4 | "anto/domain/service/translator" 5 | "anto/lib/log" 6 | "context" 7 | "encoding/json" 8 | "fmt" 9 | "github.com/golang-module/carbon" 10 | "go.uber.org/zap" 11 | "net/url" 12 | "strings" 13 | "sync" 14 | ) 15 | 16 | var api = "https://fanyi.youdao.com/translate?&doctype=json" 17 | 18 | var ( 19 | apiTranslator *Translator 20 | onceTranslator sync.Once 21 | ) 22 | 23 | func API() *Translator { 24 | onceTranslator.Do(func() { 25 | apiTranslator = New() 26 | }) 27 | return apiTranslator 28 | } 29 | 30 | func New() *Translator { 31 | return &Translator{ 32 | id: "youdao", 33 | name: "有道翻译", 34 | qps: 50, 35 | procMax: 20, 36 | textMaxLen: 2000, 37 | sep: "\n", 38 | langSupported: langSupported, 39 | } 40 | } 41 | 42 | type Translator struct { 43 | id string 44 | name string 45 | cfg translator.ImplConfig 46 | qps int 47 | procMax int 48 | textMaxLen int 49 | langSupported []translator.LangPair 50 | sep string 51 | } 52 | 53 | func (customT *Translator) Init(cfg translator.ImplConfig) { customT.cfg = cfg } 54 | 55 | func (customT *Translator) GetId() string { return customT.id } 56 | func (customT *Translator) GetShortId() string { return "yd" } 57 | func (customT *Translator) GetName() string { return customT.name } 58 | func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg } 59 | func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported } 60 | func (customT *Translator) GetSep() string { return customT.sep } 61 | func (customT *Translator) IsValid() bool { return false } 62 | 63 | func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) { 64 | timeStart := carbon.Now() 65 | urlQueried := fmt.Sprintf( 66 | "%s&type=%s2%s&i=%s", api, 67 | strings.ToUpper(args.FromLang), strings.ToUpper(args.ToLang), 68 | url.QueryEscape(args.TextContent), 69 | ) 70 | 71 | respBytes, err := translator.RequestSimpleHttp(ctx, customT, urlQueried, false, nil, nil) 72 | if err != nil { 73 | return nil, err 74 | } 75 | youDaoResp := new(youDaoMTResp) 76 | if err = json.Unmarshal(respBytes, youDaoResp); err != nil { 77 | log.Singleton().ErrorF("解析报文异常, 引擎: %s, 错误: %s", customT.GetName(), err) 78 | return nil, fmt.Errorf("解析报文出现异常, 错误: %s", err.Error()) 79 | } 80 | if youDaoResp.ErrorCode != 0 { 81 | log.Singleton().ErrorF("接口响应异常, 引擎: %s, 错误: %s", customT.GetName(), err, zap.String("result", string(respBytes))) 82 | return nil, fmt.Errorf("翻译异常, 代码: %d", youDaoResp.ErrorCode) 83 | } 84 | srcArrSplit := strings.Split(args.TextContent, customT.sep) 85 | if len(srcArrSplit) != len(youDaoResp.TransResult) { 86 | return nil, translator.ErrSrcAndTgtNotMatched 87 | } 88 | ret := new(translator.TranslateRes) 89 | for idx, transBlockArray := range youDaoResp.TransResult { 90 | var tgtArrEvaluated []string 91 | // ?+空格 会导致意外分行, 搞不清楚这个服务的换行标识是什么, 多标准的么 92 | for _, block := range transBlockArray { 93 | tgtArrEvaluated = append(tgtArrEvaluated, block.Tgt) 94 | } 95 | ret.Results = append(ret.Results, &translator.TranslateResBlock{ 96 | Id: srcArrSplit[idx], 97 | TextTranslated: strings.Join(tgtArrEvaluated, " "), 98 | }) 99 | 100 | } 101 | 102 | ret.TimeUsed = int(carbon.Now().DiffInSeconds(timeStart)) 103 | return ret, nil 104 | } 105 | 106 | type youDaoMTResp struct { 107 | Type string `json:"type"` 108 | ErrorCode int `json:"errorCode"` 109 | ElapsedTime int `json:"elapsedTime"` 110 | TransResult [][]struct { 111 | Src string `json:"src,omitempty"` // 原文 112 | Tgt string `json:"tgt,omitempty"` // 译文 113 | } `json:"translateResult"` 114 | } 115 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module anto 2 | 3 | go 1.21 4 | 5 | require ( 6 | cloud.google.com/go/translate v1.9.0 7 | github.com/OwO-Network/gdeeplx v0.0.1 8 | github.com/aliyun/alibaba-cloud-sdk-go v1.62.267 9 | github.com/golang-module/carbon v1.7.3 10 | github.com/google/go-querystring v1.1.0 11 | github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.34 12 | github.com/imroc/req/v3 v3.42.0 13 | github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible 14 | github.com/lxn/walk v0.0.0-20210112085537-c389da54e794 15 | github.com/lxn/win v0.0.0-20210218163916-a377121e959e 16 | github.com/spf13/viper v1.15.0 17 | github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.628 18 | github.com/twharmon/gouid v0.5.2 19 | github.com/volcengine/volc-sdk-golang v1.0.95 20 | golang.org/x/time v0.1.0 21 | ) 22 | 23 | require ( 24 | cloud.google.com/go v0.110.2 // indirect 25 | cloud.google.com/go/compute v1.19.3 // indirect 26 | cloud.google.com/go/compute/metadata v0.2.3 // indirect 27 | github.com/abadojack/whatlanggo v1.0.1 // indirect 28 | github.com/andybalholm/brotli v1.0.5 // indirect 29 | github.com/cenkalti/backoff/v4 v4.1.2 // indirect 30 | github.com/cloudflare/circl v1.3.3 // indirect 31 | github.com/gaukas/godicttls v0.0.4 // indirect 32 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect 33 | github.com/gobuffalo/envy v1.7.0 // indirect 34 | github.com/gobuffalo/packd v0.3.0 // indirect 35 | github.com/gobuffalo/packr v1.30.1 // indirect 36 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 37 | github.com/golang/mock v1.6.0 // indirect 38 | github.com/golang/protobuf v1.5.3 // indirect 39 | github.com/google/pprof v0.0.0-20230912144702-c363fe2c2ed8 // indirect 40 | github.com/google/s2a-go v0.1.4 // indirect 41 | github.com/google/uuid v1.3.0 // indirect 42 | github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect 43 | github.com/googleapis/gax-go/v2 v2.11.0 // indirect 44 | github.com/hashicorp/errwrap v1.1.0 // indirect 45 | github.com/hashicorp/go-multierror v1.1.1 // indirect 46 | github.com/jmespath/go-jmespath v0.4.0 // indirect 47 | github.com/joho/godotenv v1.3.0 // indirect 48 | github.com/jonboulle/clockwork v0.3.0 // indirect 49 | github.com/json-iterator/go v1.1.12 // indirect 50 | github.com/klauspost/compress v1.17.0 // indirect 51 | github.com/lestrrat-go/strftime v1.0.6 // indirect 52 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 53 | github.com/modern-go/reflect2 v1.0.2 // indirect 54 | github.com/onsi/ginkgo/v2 v2.12.0 // indirect 55 | github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect 56 | github.com/pkg/errors v0.9.1 // indirect 57 | github.com/quic-go/qpack v0.4.0 // indirect 58 | github.com/quic-go/qtls-go1-20 v0.3.4 // indirect 59 | github.com/quic-go/quic-go v0.38.1 // indirect 60 | github.com/refraction-networking/utls v1.5.3 // indirect 61 | github.com/rogpeppe/go-internal v1.6.1 // indirect 62 | github.com/tidwall/gjson v1.14.4 // indirect 63 | github.com/tidwall/match v1.1.1 // indirect 64 | github.com/tidwall/pretty v1.2.0 // indirect 65 | go.mongodb.org/mongo-driver v1.11.2 // indirect 66 | go.opencensus.io v0.24.0 // indirect 67 | go.uber.org/atomic v1.9.0 // indirect 68 | go.uber.org/multierr v1.8.0 // indirect 69 | golang.org/x/crypto v0.13.0 // indirect 70 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect 71 | golang.org/x/mod v0.12.0 // indirect 72 | golang.org/x/net v0.15.0 // indirect 73 | golang.org/x/oauth2 v0.8.0 // indirect 74 | golang.org/x/tools v0.13.0 // indirect 75 | google.golang.org/api v0.126.0 // indirect 76 | google.golang.org/appengine v1.6.7 // indirect 77 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect 78 | google.golang.org/grpc v1.55.0 // indirect 79 | google.golang.org/protobuf v1.30.0 // indirect 80 | gopkg.in/Knetic/govaluate.v3 v3.0.0 // indirect 81 | ) 82 | 83 | require ( 84 | github.com/fsnotify/fsnotify v1.6.0 // indirect 85 | github.com/hashicorp/hcl v1.0.0 // indirect 86 | github.com/magiconair/properties v1.8.7 // indirect 87 | github.com/mitchellh/mapstructure v1.5.0 // indirect 88 | github.com/pelletier/go-toml/v2 v2.0.6 // indirect 89 | github.com/spf13/afero v1.9.3 // indirect 90 | github.com/spf13/cast v1.5.0 91 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 92 | github.com/spf13/pflag v1.0.5 // indirect 93 | github.com/subosito/gotenv v1.4.2 // indirect 94 | go.uber.org/zap v1.24.0 95 | golang.org/x/sys v0.12.0 // indirect 96 | golang.org/x/text v0.13.0 // indirect 97 | gopkg.in/ini.v1 v1.67.0 // indirect 98 | gopkg.in/yaml.v3 v3.0.1 // indirect 99 | ) 100 | -------------------------------------------------------------------------------- /lib/log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "github.com/golang-module/carbon" 5 | rotatelogs "github.com/lestrrat-go/file-rotatelogs" 6 | "go.uber.org/zap" 7 | "go.uber.org/zap/zapcore" 8 | "io" 9 | "strings" 10 | "sync" 11 | "time" 12 | ) 13 | 14 | var ( 15 | apiLog *Log 16 | onceLog sync.Once 17 | ) 18 | 19 | func Singleton() *Log { 20 | onceLog.Do(func() { 21 | apiLog = new(Log) 22 | apiLog.initEncoder() 23 | apiLog.initLogger() 24 | }) 25 | return apiLog 26 | } 27 | 28 | type Log struct { 29 | logger *zap.SugaredLogger 30 | encoder zapcore.Encoder 31 | } 32 | 33 | func (customLog *Log) Debug(args ...interface{}) { 34 | customLog.logger.Debug(args...) 35 | } 36 | func (customLog *Log) Info(args ...interface{}) { 37 | customLog.logger.Info(args...) 38 | } 39 | func (customLog *Log) InfoF(tpl string, args ...interface{}) { 40 | customLog.logger.Infof(tpl, args...) 41 | } 42 | func (customLog *Log) Warn(args ...interface{}) { 43 | customLog.logger.Warn(args...) 44 | } 45 | func (customLog *Log) WarnF(tpl string, args ...interface{}) { 46 | customLog.logger.Warnf(tpl, args...) 47 | } 48 | func (customLog *Log) Error(args ...interface{}) { 49 | customLog.logger.Error(args...) 50 | } 51 | func (customLog *Log) ErrorF(tpl string, args ...interface{}) { 52 | customLog.logger.Errorf(tpl, args...) 53 | } 54 | func (customLog *Log) Panic(args ...interface{}) { 55 | customLog.logger.Panic(args...) 56 | } 57 | func (customLog *Log) Fatal(args ...interface{}) { 58 | customLog.logger.Fatal(args...) 59 | } 60 | 61 | func (customLog *Log) initLogger() { 62 | infoLevel := zap.LevelEnablerFunc(func(lv zapcore.Level) bool { 63 | return lv >= zapcore.InfoLevel 64 | }) 65 | errorLevel := zap.LevelEnablerFunc(func(lv zapcore.Level) bool { 66 | return lv >= zapcore.ErrorLevel 67 | }) 68 | 69 | ioInfo := customLog.initWriter("./logs/info.log") 70 | ioErr := customLog.initWriter("./logs/error.log") 71 | 72 | core := zapcore.NewTee( 73 | zapcore.NewCore(customLog.encoder, zapcore.AddSync(ioInfo), infoLevel), 74 | zapcore.NewCore(customLog.encoder, zapcore.AddSync(ioErr), errorLevel), 75 | ) 76 | customLog.logger = zap.New(core, zap.AddCaller()).Sugar() 77 | } 78 | 79 | func (customLog *Log) initWriter(filename string) io.Writer { 80 | fd, err := rotatelogs.New(strings.Replace(filename, ".log", "", -1) + ".%Y%m%d.log") 81 | if err != nil { 82 | panic(err) 83 | } 84 | return fd 85 | } 86 | 87 | func (customLog *Log) initEncoder() { 88 | customLog.encoder = zapcore.NewJSONEncoder(zapcore.EncoderConfig{ 89 | MessageKey: "msg", LevelKey: "level", TimeKey: "ts", StacktraceKey: "trace", 90 | EncodeLevel: zapcore.LowercaseLevelEncoder, 91 | EncodeTime: func(time time.Time, encoder zapcore.PrimitiveArrayEncoder) { 92 | encoder.AppendString(carbon.FromStdTime(time).Layout(carbon.ShortDateTimeLayout)) 93 | }, 94 | EncodeDuration: func(d time.Duration, enc zapcore.PrimitiveArrayEncoder) { 95 | enc.AppendInt64(int64(d) / 1000000) 96 | }, 97 | }) 98 | } 99 | -------------------------------------------------------------------------------- /lib/nohup/resident.go: -------------------------------------------------------------------------------- 1 | package nohup 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type IResident interface { 8 | Run(ctx context.Context, stopFn context.CancelFunc) 9 | Close() 10 | } 11 | 12 | func NewResident(parentCtx context.Context, programs ...IResident) { 13 | ctx, stop := context.WithCancel(parentCtx) 14 | defer stop() 15 | for _, program := range programs { 16 | program.Run(ctx, stop) 17 | } 18 | <-ctx.Done() 19 | stop() 20 | 21 | for _, program := range programs { 22 | program.Close() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/restrictor/restrictor.go: -------------------------------------------------------------------------------- 1 | package restrictor 2 | 3 | import ( 4 | "anto/lib/log" 5 | "context" 6 | "golang.org/x/time/rate" 7 | "sync" 8 | ) 9 | 10 | var ( 11 | apiRestrictor *Restrictor 12 | onceRestrictor sync.Once 13 | ) 14 | 15 | func Singleton() *Restrictor { 16 | onceRestrictor.Do(func() { 17 | apiRestrictor = new(Restrictor) 18 | }) 19 | return apiRestrictor 20 | } 21 | 22 | type Restrictor struct { 23 | instances sync.Map 24 | } 25 | 26 | func (o *Restrictor) Get(key string) *rate.Limiter { 27 | currentLimiter, isOk := o.instances.Load(key) 28 | if !isOk || currentLimiter == nil { 29 | currentLimiter = rate.NewLimiter(1, 1) 30 | } 31 | return currentLimiter.(*rate.Limiter) 32 | } 33 | 34 | func (o *Restrictor) Set(key string, limiter *rate.Limiter) { 35 | o.instances.Store(key, limiter) 36 | } 37 | 38 | func (o *Restrictor) Allow(key string) bool { 39 | return o.Get(key).Allow() 40 | } 41 | 42 | func (o *Restrictor) Wait(key string, ctx context.Context) error { 43 | err := o.Get(key).Wait(ctx) 44 | if err != nil { 45 | log.Singleton().ErrorF("等待令牌异常(关键字: %s), 错误: %s", key, err.Error()) 46 | } 47 | 48 | return err 49 | } 50 | -------------------------------------------------------------------------------- /lib/srt/block.go: -------------------------------------------------------------------------------- 1 | package srt 2 | 3 | import ( 4 | "fmt" 5 | "github.com/golang-module/carbon" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | func (customB *Block) IsValid() bool { 11 | return customB.SeqNo > 0 && 12 | customB.TimeStart != "" && customB.TimeEnd != "" && customB.TimeSep != "" && 13 | customB.MainTrack != "" 14 | } 15 | 16 | func (customB *Block) decodeSeqNo(lineStr string) (isSeqNo bool, err error) { 17 | if lineStr == "" || customB.SeqNo > 0 { 18 | return 19 | } 20 | var tmpNum int 21 | tmpNum, err = strconv.Atoi(lineStr) 22 | if err != nil { 23 | return 24 | } 25 | if tmpNum <= 0 { 26 | err = fmt.Errorf("无效序列号[%d]", tmpNum) 27 | return 28 | } 29 | customB.SeqNo = tmpNum 30 | isSeqNo = true 31 | return 32 | } 33 | 34 | func (customB *Block) decodeTimeLine(lineStr string) (isTimeLine bool, err error) { 35 | if lineStr == "" || !strings.Contains(lineStr, timeSep) { 36 | return 37 | } 38 | if customB.SeqNo == 0 { 39 | return 40 | } 41 | timeSplit := strings.Split(lineStr, timeSep) 42 | if len(timeSplit) != 2 { 43 | return 44 | } 45 | firstBlock := strings.TrimSpace(timeSplit[0]) 46 | secondBlock := strings.TrimSpace(timeSplit[1]) 47 | layout := "15:04:05.999" 48 | if carbon.ParseByLayout(firstBlock, layout).IsInvalid() || 49 | carbon.ParseByLayout(secondBlock, layout).IsInvalid() { 50 | return 51 | } 52 | customB.TimeStart = firstBlock 53 | customB.TimeEnd = secondBlock 54 | customB.TimeSep = timeSep 55 | isTimeLine = true 56 | return 57 | } 58 | 59 | func (customB *Block) decodeMainTrack(lineStr string) (isMain bool, err error) { 60 | if lineStr == "" || customB.MainTrack != "" { 61 | return 62 | } 63 | if customB.SeqNo == 0 || customB.TimeStart == "" { 64 | return 65 | } 66 | customB.MainTrack = lineStr 67 | isMain = true 68 | return 69 | } 70 | 71 | func (customB *Block) decodeSubTrack(lineStr string) (isSub bool, err error) { 72 | if lineStr == "" || customB.SubTrack != "" { 73 | return 74 | } 75 | if customB.SeqNo == 0 || customB.TimeStart == "" || customB.MainTrack == "" { 76 | return 77 | } 78 | customB.SubTrack = lineStr 79 | isSub = true 80 | return 81 | } 82 | 83 | func (customB *Block) encode(flagInverse bool, flagTrackExportedMode int) []byte { 84 | blockStr := fmt.Sprintf("%d\n%s %s %s\n", customB.SeqNo, 85 | customB.TimeStart, customB.TimeSep, customB.TimeEnd) 86 | if customB.SubTrack == "" { 87 | blockStr = fmt.Sprintf("%s%s\n", blockStr, customB.MainTrack) 88 | } else { 89 | if flagTrackExportedMode != 0 { 90 | if flagTrackExportedMode == 1 { 91 | if flagInverse == true { 92 | blockStr = fmt.Sprintf("%s%s\n", blockStr, customB.SubTrack) 93 | } else { 94 | blockStr = fmt.Sprintf("%s%s\n", blockStr, customB.MainTrack) 95 | } 96 | } else if flagTrackExportedMode == 2 { 97 | if flagInverse == true { 98 | blockStr = fmt.Sprintf("%s%s\n", blockStr, customB.MainTrack) 99 | } else { 100 | blockStr = fmt.Sprintf("%s%s\n", blockStr, customB.SubTrack) 101 | } 102 | } 103 | } else { 104 | if flagInverse == false { 105 | blockStr = fmt.Sprintf("%s%s\n%s\n", blockStr, customB.MainTrack, customB.SubTrack) 106 | } else { 107 | blockStr = fmt.Sprintf("%s%s\n%s\n", blockStr, customB.SubTrack, customB.MainTrack) 108 | } 109 | } 110 | } 111 | 112 | return []byte(blockStr) 113 | } 114 | -------------------------------------------------------------------------------- /lib/srt/decode.go: -------------------------------------------------------------------------------- 1 | package srt 2 | 3 | import ( 4 | "anto/lib/util" 5 | "bufio" 6 | "fmt" 7 | "io" 8 | "strings" 9 | ) 10 | 11 | func (customS *Srt) Decode(fileStream io.Reader) (err error) { 12 | var lineBytes []byte 13 | var currentLine string 14 | var currentBlock *Block 15 | 16 | fileReader := bufio.NewReader(fileStream) 17 | isHeader := true 18 | for { 19 | lineBytes = []byte{} 20 | currentLine = "" 21 | 22 | lineBytes, err = fileReader.ReadBytes('\n') 23 | if isHeader { 24 | if util.HasUTF8Dom(lineBytes) { 25 | lineBytes = lineBytes[3:] 26 | } 27 | 28 | isHeader = false 29 | } 30 | currentLine = strings.TrimSpace(string(lineBytes)) 31 | 32 | if customS.decodeBroken(err) { 33 | return 34 | } 35 | 36 | if customS.decodeEOFDone(err, currentLine) { 37 | err = nil 38 | break 39 | } 40 | 41 | if currentLine == "" { // 检测到空行 字幕块间隔行 42 | if currentBlock != nil { 43 | if !currentBlock.IsValid() { 44 | err = fmt.Errorf("字幕块[序列号: %d]无效", currentBlock.SeqNo) 45 | return 46 | } 47 | customS.Blocks = append(customS.Blocks, currentBlock) 48 | currentBlock = nil 49 | } 50 | continue // 可能存在连续空行? 51 | } 52 | 53 | if currentBlock == nil { // isSeqNo 54 | currentBlock = new(Block) 55 | if _, err = currentBlock.decodeSeqNo(currentLine); err != nil { 56 | err = fmt.Errorf("解析序列号异常: %s", err) 57 | return 58 | } 59 | continue 60 | } 61 | if isTimeLine, _ := currentBlock.decodeTimeLine(currentLine); isTimeLine { 62 | continue 63 | } 64 | if isMain, _ := currentBlock.decodeMainTrack(currentLine); isMain { 65 | continue 66 | } 67 | if isSub, _ := currentBlock.decodeSubTrack(currentLine); isSub { 68 | continue 69 | } 70 | err = fmt.Errorf("解析字幕块异常, 出现多余行[%s][最近序列号: %d]", currentLine, currentBlock.SeqNo) 71 | return 72 | } 73 | 74 | if currentBlock != nil { 75 | customS.Blocks = append(customS.Blocks, currentBlock) 76 | currentBlock = nil 77 | } 78 | return 79 | } 80 | 81 | func (customS *Srt) decodeBroken(err error) bool { 82 | return err != nil && err != io.EOF 83 | } 84 | 85 | func (customS *Srt) decodeEOFDone(err error, currentLine string) bool { 86 | return err == io.EOF && currentLine == "" 87 | } 88 | -------------------------------------------------------------------------------- /lib/srt/encode.go: -------------------------------------------------------------------------------- 1 | package srt 2 | 3 | type EncodeOpt struct { 4 | FlagTrackExport int // 导出轨道模式 0-全轨 1-主轨 2-副轨 5 | FlagIsInverse bool 6 | } 7 | 8 | func (customS *Srt) Encode(opts *EncodeOpt) ([]byte, error) { 9 | var fsBytes []byte 10 | for _, block := range customS.Blocks { 11 | fsBytes = append(fsBytes, block.encode(opts.FlagIsInverse, opts.FlagTrackExport)...) 12 | fsBytes = append(fsBytes, '\n') 13 | } 14 | return fsBytes, nil 15 | 16 | } 17 | -------------------------------------------------------------------------------- /lib/srt/srt.go: -------------------------------------------------------------------------------- 1 | package srt 2 | 3 | import "strings" 4 | 5 | const timeSep = "-->" 6 | 7 | type Srt struct { 8 | FileName string 9 | FilePath string 10 | FileSign string 11 | FileSize int 12 | CntBlock int 13 | Blocks []*Block 14 | FlagTranslated bool 15 | } 16 | 17 | type Block struct { 18 | SeqNo int 19 | TimeStart string 20 | TimeEnd string 21 | TimeSep string 22 | MainTrack string 23 | SubTrack string 24 | } 25 | 26 | // FileNameSync 从FilePath中解析文件名称-强行覆盖 27 | func (customS *Srt) FileNameSync() { 28 | if customS.FilePath == "" { 29 | return 30 | } 31 | filepathArray := strings.Split(customS.FilePath, "/") 32 | filename := filepathArray[len(filepathArray)-1] // 可能要做其他处理, 先保留这个中间转换 33 | customS.FileName = filename 34 | } 35 | -------------------------------------------------------------------------------- /lib/util/rand.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | _const "anto/common" 5 | "github.com/twharmon/gouid" 6 | ) 7 | 8 | func Uid() string { 9 | return gouid.String(_const.GoUidLen, gouid.LowerCaseAlphaNum) 10 | } 11 | -------------------------------------------------------------------------------- /lib/util/time.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "github.com/golang-module/carbon" 4 | 5 | func GetShortDateTime() string { 6 | return carbon.Now().Layout(carbon.ShortDateTimeLayout) 7 | } 8 | 9 | func GetDateTime() string { 10 | return carbon.Now().Layout(carbon.DateTimeLayout) 11 | } 12 | 13 | func GetSecondsFromTime(timeStart carbon.Carbon) int { 14 | return int(carbon.Now().DiffAbsInSeconds(timeStart)) 15 | } 16 | -------------------------------------------------------------------------------- /lib/util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "os" 5 | "os/exec" 6 | ) 7 | 8 | func HasUTF8Dom(bytes []byte) bool { 9 | return len(bytes) >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF 10 | } 11 | 12 | // IsSrtFile 是否为srt文件 13 | func IsSrtFile(strFilepath string) bool { 14 | if len(strFilepath) <= 4 { 15 | return false 16 | } 17 | return strFilepath[len(strFilepath)-3:] == "srt" 18 | } 19 | 20 | // IsFileOrDirExisted 检测文件或路径是否存在 21 | func IsFileOrDirExisted(strFilepath string) error { 22 | fd, err := os.Open(strFilepath) 23 | defer func() { 24 | if fd != nil { 25 | _ = fd.Close() 26 | } 27 | }() 28 | if err != nil { 29 | return err 30 | } 31 | return nil 32 | } 33 | 34 | // Redirect2DefaultBrowser 跳转到默认浏览器打开指定网址 35 | func Redirect2DefaultBrowser(strUrl string) error { 36 | cmd := exec.Command("cmd", "/C", "start "+strUrl) 37 | return cmd.Run() 38 | } 39 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/speauty/anto/d537a1ad2b935ec8c6973912e1a5ed919d932ea7/logo.png -------------------------------------------------------------------------------- /platform/win/menu.go: -------------------------------------------------------------------------------- 1 | package win 2 | 3 | import ( 4 | "anto/common" 5 | "anto/lib/log" 6 | page2 "anto/platform/win/page" 7 | "anto/platform/win/ui" 8 | "anto/platform/win/ui/msg" 9 | "errors" 10 | "fmt" 11 | "github.com/lxn/walk" 12 | . "github.com/lxn/walk/declarative" 13 | "go.uber.org/zap" 14 | "io" 15 | "io/fs" 16 | "net/http" 17 | "os" 18 | "path/filepath" 19 | "sync" 20 | "time" 21 | ) 22 | 23 | var ( 24 | apiTTMenu *TTMenu 25 | onceTTMenu sync.Once 26 | ) 27 | 28 | func GetInstance() *TTMenu { 29 | onceTTMenu.Do(func() { 30 | apiTTMenu = new(TTMenu) 31 | }) 32 | return apiTTMenu 33 | } 34 | 35 | type TTMenu struct { 36 | mainWindow *walk.MainWindow 37 | actionDownloadHandle *walk.Action 38 | } 39 | 40 | func (customM *TTMenu) GetMenus() []MenuItem { 41 | return []MenuItem{ 42 | Menu{ 43 | Text: "文件", 44 | Items: []MenuItem{ 45 | Action{Text: "设置", OnTriggered: customM.eventSettings}, 46 | Separator{}, 47 | Action{AssignTo: &customM.actionDownloadHandle, Text: "下载新版", OnTriggered: customM.eventActionDownloadLatestVersion}, 48 | Action{Text: "清除日志", OnTriggered: customM.eventActionDelLog}, 49 | Separator{}, 50 | Action{Text: "退出", OnTriggered: customM.eventActionQuit}, 51 | }, 52 | }, 53 | Action{Text: "字幕翻译", OnTriggered: customM.eventSubtitleTranslate}, 54 | } 55 | } 56 | 57 | func (customM *TTMenu) eventSettings() { 58 | currentPage := page2.GetSettings() 59 | customM.eventGoPage(currentPage.GetId(), currentPage.GetName()) 60 | } 61 | 62 | func (customM *TTMenu) eventActionDownloadLatestVersion() { 63 | if customM.actionDownloadHandle.Enabled() { 64 | _ = customM.actionDownloadHandle.SetEnabled(false) 65 | } else { 66 | return 67 | } 68 | mainWindow := ui.Singleton().GetWindow() 69 | isOk, _ := msg.Confirm(mainWindow, fmt.Sprintf("下载应用的最新版本,是否继续?")) 70 | if isOk { 71 | go func() { 72 | defer func() { 73 | _ = customM.actionDownloadHandle.SetEnabled(true) 74 | }() 75 | resp, err := http.Get(common.DownloadLatestVersionUrl) 76 | if err != nil { 77 | tmpErr := fmt.Errorf("下载最新版本异常, 错误: %s", err) 78 | if ui.Singleton().Notification() != nil { 79 | _ = ui.Singleton().Notification().ShowError("", tmpErr.Error()) 80 | } else { 81 | msg.Err(mainWindow, tmpErr) 82 | } 83 | return 84 | } 85 | defer func() { 86 | _ = resp.Body.Close() 87 | }() 88 | appBytes, _ := io.ReadAll(resp.Body) 89 | if resp.StatusCode == http.StatusNotFound { 90 | tmpErr := errors.New("下载最新版本异常, 错误: 暂未找到") 91 | if ui.Singleton().Notification() != nil { 92 | _ = ui.Singleton().Notification().ShowError("", tmpErr.Error()) 93 | } else { 94 | msg.Err(mainWindow, tmpErr) 95 | } 96 | return 97 | } 98 | fileName := filepath.Base(common.DownloadLatestVersionUrl) 99 | 100 | if err := os.WriteFile(fileName, appBytes, os.ModePerm); err != nil { 101 | tmpErr := fmt.Errorf("下载最新版本异常, 错误: %s", err) 102 | if ui.Singleton().Notification() != nil { 103 | _ = ui.Singleton().Notification().ShowError("", tmpErr.Error()) 104 | } else { 105 | msg.Err(mainWindow, tmpErr) 106 | } 107 | return 108 | } 109 | strMsg := fmt.Sprintf("下载最新版本成功[%s], 关闭当前应用, 双击打开对应可执行文件即可", fileName) 110 | if ui.Singleton().Notification() != nil { 111 | _ = ui.Singleton().Notification().ShowInfo("", strMsg) 112 | } else { 113 | msg.Info(mainWindow, strMsg) 114 | } 115 | }() 116 | } else { 117 | _ = customM.actionDownloadHandle.SetEnabled(true) 118 | } 119 | } 120 | 121 | func (customM *TTMenu) eventActionDelLog() { 122 | mainWindow := ui.Singleton().GetWindow() 123 | isOk, _ := msg.Confirm(mainWindow, fmt.Sprintf("清除日志会删除今日之前的所有日志文件,是否继续?")) 124 | if isOk { 125 | now := time.Now() 126 | currentDayZero := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local) 127 | cntDel := 0 128 | _ = filepath.Walk("logs", func(path string, info fs.FileInfo, err error) error { 129 | if info.ModTime().Before(currentDayZero) { 130 | if err := os.Remove(path); err == nil { 131 | cntDel++ 132 | } 133 | } 134 | return nil 135 | }) 136 | if cntDel > 0 { 137 | msg.Info(ui.Singleton().GetWindow(), fmt.Sprintf("删除历史日志文件(数量: %d)", cntDel)) 138 | } else { 139 | msg.Info(ui.Singleton().GetWindow(), "暂无历史日志文件") 140 | } 141 | } 142 | } 143 | 144 | func (customM *TTMenu) eventActionQuit() { 145 | mainWindow := ui.Singleton().GetWindow() 146 | isOk, _ := msg.Confirm(mainWindow, fmt.Sprintf("即将退出当前应用,是否确认?")) 147 | if isOk { 148 | _ = mainWindow.Close() 149 | } 150 | } 151 | 152 | func (customM *TTMenu) eventSubtitleTranslate() { 153 | currentPage := page2.GetSubripTranslate() 154 | customM.eventGoPage(currentPage.GetId(), currentPage.GetName()) 155 | } 156 | 157 | func (customM *TTMenu) eventGoPage(pageId string, name string) { 158 | if pageId == "" { 159 | return 160 | } 161 | if err := ui.Singleton().GoPage(pageId); err != nil { 162 | log.Singleton().Error("跳转页面异常", zap.String("page", name), zap.String("id", pageId), zap.Error(err)) 163 | msg.Err(ui.Singleton().GetWindow(), fmt.Errorf("跳转页面[%s]异常", name)) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /platform/win/page/page.go: -------------------------------------------------------------------------------- 1 | package page 2 | 3 | import ( 4 | "anto/platform/win/ui/pack" 5 | "github.com/lxn/walk" 6 | . "github.com/lxn/walk/declarative" 7 | ) 8 | 9 | func StdRootWidget(rootWidget **walk.Composite, widgets ...Widget) Widget { 10 | return pack.UIComposite( 11 | pack.NewUICompositeArgs(rootWidget). 12 | SetVisible(false). 13 | SetLayoutVBox(false). 14 | SetWidgets(widgets), 15 | ) 16 | } 17 | 18 | func StdBrowserSelectorWidget(title string, btOnClickFn walk.EventHandler, echoTarget **walk.Label) Widget { 19 | return pack.UIComposite( 20 | pack.NewUICompositeArgs(nil).SetLayoutHBox(true).SetWidgets( 21 | pack.NewWidgetGroup().Append( 22 | pack.UILabel(pack.NewUILabelArgs(nil).SetText(title)), 23 | pack.UIPushBtn(pack.NewUIPushBtnArgs(nil).SetText("选择").SetOnClicked(btOnClickFn)), 24 | pack.UILabel(pack.NewUILabelArgs(echoTarget).SetEnabled(false)), 25 | ).AppendZeroHSpacer().GetWidgets())) 26 | } 27 | -------------------------------------------------------------------------------- /platform/win/ui/cfg.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | _const "anto/common" 5 | ) 6 | 7 | type Cfg struct { 8 | Title string `mapstructure:"title"` 9 | Icon string `mapstructure:"icon"` 10 | ResourceDir string `mapstructure:"resource_dir"` 11 | } 12 | 13 | func (customC Cfg) Default() *Cfg { 14 | return &Cfg{Title: _const.UITitle, Icon: _const.UIIcon, ResourceDir: _const.UIResourceDir} 15 | } 16 | -------------------------------------------------------------------------------- /platform/win/ui/event.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import "github.com/lxn/walk" 4 | 5 | func (customG *Gui) eventCustomClose(canceled *bool, reason walk.CloseReason) { 6 | reason = walk.CloseReasonUser 7 | *canceled = false 8 | customG.ctxCancelFn() 9 | } 10 | -------------------------------------------------------------------------------- /platform/win/ui/handle/file_dialog.go: -------------------------------------------------------------------------------- 1 | package handle 2 | 3 | import ( 4 | "anto/platform/win/ui/msg" 5 | "fmt" 6 | "github.com/lxn/walk" 7 | ) 8 | 9 | func FileDialogHandle(args *FileDialogHandleArgs) { 10 | dlg := new(walk.FileDialog) 11 | dlg.Title = args.title 12 | dlg.Filter = args.filter 13 | topic := "" 14 | var isAccepted bool 15 | var err error 16 | if args.isFileSelector { 17 | topic = "文件" 18 | isAccepted, err = dlg.ShowOpen(args.owner) 19 | } else { 20 | topic = "目录" 21 | isAccepted, err = dlg.ShowBrowseFolder(args.owner) 22 | } 23 | if err != nil { 24 | msg.Err(args.owner, fmt.Errorf("选择%s异常, 错误: %s", topic, err)) 25 | return 26 | } else if !isAccepted { 27 | return 28 | } 29 | _ = (*args.pathEchoHandle).SetText(dlg.FilePath) 30 | } 31 | 32 | type FileDialogHandleArgs struct { 33 | owner walk.Form 34 | pathEchoHandle **walk.Label 35 | title string 36 | filter string 37 | isFileSelector bool 38 | } 39 | 40 | func NewFileDialogHandleArgs(owner walk.Form, pathEchoHandle **walk.Label) *FileDialogHandleArgs { 41 | return &FileDialogHandleArgs{owner: owner, pathEchoHandle: pathEchoHandle, isFileSelector: true} 42 | } 43 | 44 | func (customT *FileDialogHandleArgs) File() *FileDialogHandleArgs { 45 | customT.isFileSelector = true 46 | return customT 47 | } 48 | 49 | func (customT *FileDialogHandleArgs) Folder() *FileDialogHandleArgs { 50 | customT.isFileSelector = false 51 | return customT 52 | } 53 | 54 | func (customT *FileDialogHandleArgs) SetOwner(owner walk.Form) *FileDialogHandleArgs { 55 | customT.owner = owner 56 | return customT 57 | } 58 | 59 | func (customT *FileDialogHandleArgs) SetTitle(title string) *FileDialogHandleArgs { 60 | customT.title = title 61 | return customT 62 | } 63 | 64 | func (customT *FileDialogHandleArgs) SetFilter(filter string) *FileDialogHandleArgs { 65 | customT.filter = filter 66 | return customT 67 | } 68 | 69 | func (customT *FileDialogHandleArgs) SetPathEchoHandle(pathEchoHandle **walk.Label) *FileDialogHandleArgs { 70 | customT.pathEchoHandle = pathEchoHandle 71 | return customT 72 | } 73 | -------------------------------------------------------------------------------- /platform/win/ui/msg/msg.go: -------------------------------------------------------------------------------- 1 | package msg 2 | 3 | import "github.com/lxn/walk" 4 | 5 | const msgTitle = "提示" 6 | 7 | func Info(owner walk.Form, msg string) int { 8 | return walk.MsgBox(owner, msgTitle, msg, walk.MsgBoxOK|walk.MsgBoxIconInformation) 9 | } 10 | 11 | func Warn(owner walk.Form, msg string) int { 12 | return walk.MsgBox(owner, msgTitle, msg, walk.MsgBoxOK|walk.MsgBoxIconWarning) 13 | } 14 | 15 | func Err(owner walk.Form, err error) int { 16 | return walk.MsgBox(owner, msgTitle, err.Error(), walk.MsgBoxOK|walk.MsgBoxIconError) 17 | } 18 | 19 | /* 这个。。。似乎意思不大 20 | func Help(owner walk.Form, msg string) int { 21 | return walk.MsgBox(owner, msgTitle, msg, walk.MsgBoxHelp|walk.MsgBoxIconExclamation) 22 | } 23 | */ 24 | 25 | func Confirm(owner walk.Form, msg string) (bool, bool) { 26 | ret := walk.MsgBox(owner, msgTitle, msg, walk.MsgBoxYesNo|walk.MsgBoxIconExclamation) 27 | return ret == 6, ret == 7 28 | } 29 | -------------------------------------------------------------------------------- /platform/win/ui/notification.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "anto/platform/win/ui/msg" 5 | "errors" 6 | "github.com/lxn/walk" 7 | ) 8 | 9 | func (customG *Gui) initNotification() { 10 | if customG.notification == nil { 11 | tmpNotification, err := walk.NewNotifyIcon(customG.GetWindow()) 12 | if err != nil { 13 | msg.Err(customG.GetWindow(), errors.New("初始化系统通知失败")) 14 | return 15 | } 16 | customG.notification = tmpNotification 17 | //defer tmpNotification.Dispose() 18 | } 19 | 20 | _ = customG.notification.SetVisible(true) 21 | } 22 | 23 | func (customG *Gui) Notification() *walk.NotifyIcon { 24 | return customG.notification 25 | } 26 | -------------------------------------------------------------------------------- /platform/win/ui/pack/ui_checkbox.go: -------------------------------------------------------------------------------- 1 | package pack 2 | 3 | import ( 4 | "github.com/lxn/walk" 5 | . "github.com/lxn/walk/declarative" 6 | ) 7 | 8 | func UICheckBox(args *UICheckBoxArgs) CheckBox { 9 | return CheckBox{ 10 | AssignTo: args.assignTo, 11 | Checked: args.checked, 12 | MinSize: args.customSize, 13 | MaxSize: args.customSize, 14 | Text: args.text, 15 | OnClicked: args.onClickedFn, 16 | } 17 | } 18 | 19 | func NewUICheckBoxArgs(assignTo **walk.CheckBox) *UICheckBoxArgs { 20 | return &UICheckBoxArgs{assignTo: assignTo} 21 | } 22 | 23 | type UICheckBoxArgs struct { 24 | assignTo **walk.CheckBox 25 | checked bool 26 | customSize Size 27 | text string 28 | onClickedFn walk.EventHandler 29 | } 30 | 31 | func (customT *UICheckBoxArgs) SetAssignTo(assignTo **walk.CheckBox) *UICheckBoxArgs { 32 | customT.assignTo = assignTo 33 | return customT 34 | } 35 | 36 | func (customT *UICheckBoxArgs) SetChecked(checked bool) *UICheckBoxArgs { 37 | customT.checked = checked 38 | return customT 39 | } 40 | 41 | func (customT *UICheckBoxArgs) SetCustomSize(customSize Size) *UICheckBoxArgs { 42 | customT.customSize = customSize 43 | return customT 44 | } 45 | 46 | func (customT *UICheckBoxArgs) SetText(text string) *UICheckBoxArgs { 47 | customT.text = text 48 | return customT 49 | } 50 | 51 | func (customT *UICheckBoxArgs) SetOnClickedFn(onClickedFn walk.EventHandler) *UICheckBoxArgs { 52 | customT.onClickedFn = onClickedFn 53 | return customT 54 | } 55 | -------------------------------------------------------------------------------- /platform/win/ui/pack/ui_combo_box.go: -------------------------------------------------------------------------------- 1 | package pack 2 | 3 | import ( 4 | "github.com/lxn/walk" 5 | . "github.com/lxn/walk/declarative" 6 | ) 7 | 8 | func UIComboBox(args *UIComboBoxArgs) Widget { 9 | return ComboBox{ 10 | AssignTo: args.assignTo, 11 | Model: args.model, 12 | CurrentIndex: args.currentIdx, 13 | OnCurrentIndexChanged: args.onCurrentIdxChangedFn, 14 | DisplayMember: args.displayMember, 15 | BindingMember: args.bindingMember, 16 | MinSize: args.customSize, 17 | MaxSize: args.customSize, 18 | } 19 | } 20 | 21 | func NewUIComboBoxArgs(assignTo **walk.ComboBox) *UIComboBoxArgs { 22 | return &UIComboBoxArgs{assignTo: assignTo, customSize: Size{Width: 80}} 23 | } 24 | 25 | type UIComboBoxArgs struct { 26 | assignTo **walk.ComboBox 27 | model interface{} 28 | currentIdx interface{} 29 | onCurrentIdxChangedFn walk.EventHandler 30 | displayMember string 31 | bindingMember string 32 | customSize Size 33 | } 34 | 35 | func (customT *UIComboBoxArgs) SetCustomSize(customSize Size) *UIComboBoxArgs { 36 | customT.customSize = customSize 37 | return customT 38 | } 39 | 40 | func (customT *UIComboBoxArgs) SetDisplayMember(displayMember string) *UIComboBoxArgs { 41 | customT.displayMember = displayMember 42 | return customT 43 | } 44 | 45 | func (customT *UIComboBoxArgs) SetBindingMember(bindingMember string) *UIComboBoxArgs { 46 | customT.bindingMember = bindingMember 47 | return customT 48 | } 49 | 50 | func (customT *UIComboBoxArgs) SetAssignTo(assignTo **walk.ComboBox) *UIComboBoxArgs { 51 | customT.assignTo = assignTo 52 | return customT 53 | } 54 | 55 | func (customT *UIComboBoxArgs) SetModel(model interface{}) *UIComboBoxArgs { 56 | customT.model = model 57 | return customT 58 | } 59 | 60 | func (customT *UIComboBoxArgs) SetCurrentIdx(currentIdx interface{}) *UIComboBoxArgs { 61 | customT.currentIdx = currentIdx 62 | return customT 63 | } 64 | 65 | func (customT *UIComboBoxArgs) SetOnCurrentIdxChangedFn(onCurrentIdxChangedFn walk.EventHandler) *UIComboBoxArgs { 66 | customT.onCurrentIdxChangedFn = onCurrentIdxChangedFn 67 | return customT 68 | } 69 | -------------------------------------------------------------------------------- /platform/win/ui/pack/ui_composite.go: -------------------------------------------------------------------------------- 1 | package pack 2 | 3 | import ( 4 | "github.com/lxn/walk" 5 | . "github.com/lxn/walk/declarative" 6 | ) 7 | 8 | func UIComposite(args *UICompositeArgs) Widget { 9 | return Composite{ 10 | MinSize: args.customSize, 11 | MaxSize: args.customSize, 12 | Layout: args.layout, 13 | Alignment: args.alignment, 14 | Children: args.widgets, 15 | AssignTo: args.assignTo, 16 | Visible: args.visible, 17 | } 18 | } 19 | 20 | func NewUICompositeArgs(assignTo **walk.Composite) *UICompositeArgs { 21 | return &UICompositeArgs{ 22 | assignTo: assignTo, 23 | alignment: AlignHCenterVCenter, 24 | layout: VBox{}, 25 | visible: true, 26 | } 27 | } 28 | 29 | type UICompositeArgs struct { 30 | alignment Alignment2D 31 | assignTo **walk.Composite 32 | customSize Size 33 | layout Layout 34 | widgets []Widget 35 | visible Property 36 | } 37 | 38 | func (customT *UICompositeArgs) SetVisible(visible Property) *UICompositeArgs { 39 | customT.visible = visible 40 | return customT 41 | } 42 | func (customT *UICompositeArgs) SetAssignTo(assignTo **walk.Composite) *UICompositeArgs { 43 | customT.assignTo = assignTo 44 | return customT 45 | } 46 | func (customT *UICompositeArgs) SetAlignment(alignment Alignment2D) *UICompositeArgs { 47 | customT.alignment = alignment 48 | return customT 49 | } 50 | func (customT *UICompositeArgs) SetCustomSize(customSize Size) *UICompositeArgs { 51 | customT.customSize = customSize 52 | return customT 53 | } 54 | func (customT *UICompositeArgs) SetLayout(layout Layout) *UICompositeArgs { 55 | customT.layout = layout 56 | return customT 57 | } 58 | func (customT *UICompositeArgs) SetLayoutHBox(flagMarginsZero bool) *UICompositeArgs { 59 | customT.layout = HBox{MarginsZero: flagMarginsZero} 60 | return customT 61 | } 62 | func (customT *UICompositeArgs) SetLayoutVBox(flagMarginsZero bool) *UICompositeArgs { 63 | customT.layout = VBox{MarginsZero: flagMarginsZero} 64 | return customT 65 | } 66 | 67 | func (customT *UICompositeArgs) SetWidgets(widgets []Widget) *UICompositeArgs { 68 | customT.widgets = widgets 69 | return customT 70 | } 71 | -------------------------------------------------------------------------------- /platform/win/ui/pack/ui_group_box.go: -------------------------------------------------------------------------------- 1 | package pack 2 | 3 | import ( 4 | "github.com/lxn/walk" 5 | . "github.com/lxn/walk/declarative" 6 | ) 7 | 8 | func UIGroupBox(args *UIGroupBoxArgs) Widget { 9 | return GroupBox{ 10 | Title: args.title, 11 | MinSize: args.customSize, 12 | MaxSize: args.customSize, 13 | Layout: args.layout, 14 | Alignment: args.alignment, 15 | Children: args.widgets, 16 | AssignTo: args.assignTo, 17 | Visible: args.visible, 18 | } 19 | } 20 | 21 | func NewUIGroupBoxArgs(assignTo **walk.GroupBox) *UIGroupBoxArgs { 22 | return &UIGroupBoxArgs{ 23 | assignTo: assignTo, 24 | alignment: AlignHCenterVCenter, 25 | layout: VBox{}, 26 | visible: true, 27 | } 28 | } 29 | 30 | type UIGroupBoxArgs struct { 31 | title string 32 | alignment Alignment2D 33 | assignTo **walk.GroupBox 34 | customSize Size 35 | layout Layout 36 | widgets []Widget 37 | visible bool 38 | } 39 | 40 | func (customT *UIGroupBoxArgs) SetVisible(visible bool) *UIGroupBoxArgs { 41 | customT.visible = visible 42 | return customT 43 | } 44 | 45 | func (customT *UIGroupBoxArgs) SetTitle(title string) *UIGroupBoxArgs { 46 | customT.title = title 47 | return customT 48 | } 49 | 50 | func (customT *UIGroupBoxArgs) SetAssignTo(assignTo **walk.GroupBox) *UIGroupBoxArgs { 51 | customT.assignTo = assignTo 52 | return customT 53 | } 54 | func (customT *UIGroupBoxArgs) SetAlignment(alignment Alignment2D) *UIGroupBoxArgs { 55 | customT.alignment = alignment 56 | return customT 57 | } 58 | func (customT *UIGroupBoxArgs) SetCustomSize(customSize Size) *UIGroupBoxArgs { 59 | customT.customSize = customSize 60 | return customT 61 | } 62 | func (customT *UIGroupBoxArgs) SetLayout(layout Layout) *UIGroupBoxArgs { 63 | customT.layout = layout 64 | return customT 65 | } 66 | func (customT *UIGroupBoxArgs) SetLayoutHBox(flagMarginsZero bool) *UIGroupBoxArgs { 67 | return customT.SetLayout(HBox{MarginsZero: flagMarginsZero}) 68 | } 69 | func (customT *UIGroupBoxArgs) SetLayoutVBox(flagMarginsZero bool) *UIGroupBoxArgs { 70 | return customT.SetLayout(VBox{MarginsZero: flagMarginsZero}) 71 | } 72 | func (customT *UIGroupBoxArgs) SetWidgets(widgets []Widget) *UIGroupBoxArgs { 73 | customT.widgets = widgets 74 | return customT 75 | } 76 | -------------------------------------------------------------------------------- /platform/win/ui/pack/ui_image_view.go: -------------------------------------------------------------------------------- 1 | package pack 2 | 3 | import ( 4 | "github.com/lxn/walk" 5 | . "github.com/lxn/walk/declarative" 6 | ) 7 | 8 | func UIImageView(args *UIImageViewArgs) Widget { 9 | return ImageView{ 10 | AssignTo: args.assignTo, 11 | Image: args.image, 12 | MinSize: args.customSize, 13 | MaxSize: args.customSize, 14 | Mode: args.mode, 15 | } 16 | } 17 | 18 | func NewUIImageViewArgs(assignTo **walk.ImageView) *UIImageViewArgs { 19 | return &UIImageViewArgs{assignTo: assignTo, mode: ImageViewModeShrink} 20 | } 21 | 22 | type UIImageViewArgs struct { 23 | assignTo **walk.ImageView 24 | image Property 25 | customSize Size 26 | mode ImageViewMode 27 | } 28 | 29 | func (customT *UIImageViewArgs) SetAssignTo(assignTo **walk.ImageView) *UIImageViewArgs { 30 | customT.assignTo = assignTo 31 | return customT 32 | } 33 | 34 | func (customT *UIImageViewArgs) SetImage(image Property) *UIImageViewArgs { 35 | customT.image = image 36 | return customT 37 | } 38 | 39 | func (customT *UIImageViewArgs) SetCustomSize(customSize Size) *UIImageViewArgs { 40 | customT.customSize = customSize 41 | return customT 42 | } 43 | 44 | func (customT *UIImageViewArgs) SetMode(mode ImageViewMode) *UIImageViewArgs { 45 | customT.mode = mode 46 | return customT 47 | } 48 | -------------------------------------------------------------------------------- /platform/win/ui/pack/ui_label.go: -------------------------------------------------------------------------------- 1 | package pack 2 | 3 | import ( 4 | "github.com/lxn/walk" 5 | . "github.com/lxn/walk/declarative" 6 | ) 7 | 8 | func UILabel(args *UILabelArgs) Widget { 9 | return Label{ 10 | Enabled: args.enabled, 11 | MaxSize: args.customSize, 12 | MinSize: args.customSize, 13 | Visible: args.visible, 14 | AssignTo: args.assignTo, 15 | Text: args.text, 16 | } 17 | } 18 | 19 | func NewUILabelArgs(assignTo **walk.Label) *UILabelArgs { 20 | return &UILabelArgs{ 21 | assignTo: assignTo, 22 | visible: true, 23 | enabled: true, 24 | } 25 | } 26 | 27 | type UILabelArgs struct { 28 | assignTo **walk.Label 29 | visible bool 30 | enabled bool 31 | text string 32 | customSize Size 33 | } 34 | 35 | func (customT *UILabelArgs) SetAssignTo(assignTo **walk.Label) *UILabelArgs { 36 | customT.assignTo = assignTo 37 | return customT 38 | } 39 | 40 | func (customT *UILabelArgs) SetVisible(visible bool) *UILabelArgs { 41 | customT.visible = visible 42 | return customT 43 | } 44 | 45 | func (customT *UILabelArgs) SetEnabled(enabled bool) *UILabelArgs { 46 | customT.enabled = enabled 47 | return customT 48 | } 49 | 50 | func (customT *UILabelArgs) SetText(text string) *UILabelArgs { 51 | customT.text = text 52 | return customT 53 | } 54 | 55 | func (customT *UILabelArgs) SetCustomSize(customSize Size) *UILabelArgs { 56 | customT.customSize = customSize 57 | return customT 58 | } 59 | -------------------------------------------------------------------------------- /platform/win/ui/pack/ui_line_edit.go: -------------------------------------------------------------------------------- 1 | package pack 2 | 3 | import ( 4 | "github.com/lxn/walk" 5 | . "github.com/lxn/walk/declarative" 6 | ) 7 | 8 | func UILineEdit(args *UILineEditArgs) Widget { 9 | return LineEdit{ 10 | MinSize: args.customSize, 11 | MaxSize: args.customSize, 12 | AssignTo: args.assignTo, 13 | ReadOnly: args.readOnly, 14 | OnTextChanged: args.onTextChanged, 15 | Text: args.text, 16 | } 17 | } 18 | 19 | func NewUILineEditArgs(assignTo **walk.LineEdit) *UILineEditArgs { 20 | return &UILineEditArgs{assignTo: assignTo} 21 | } 22 | 23 | type UILineEditArgs struct { 24 | assignTo **walk.LineEdit 25 | customSize Size 26 | readOnly bool 27 | onTextChanged walk.EventHandler 28 | text string 29 | } 30 | 31 | func (customT *UILineEditArgs) SetText(text string) *UILineEditArgs { 32 | customT.text = text 33 | return customT 34 | } 35 | 36 | func (customT *UILineEditArgs) SetReadOnly(readOnly bool) *UILineEditArgs { 37 | customT.readOnly = readOnly 38 | return customT 39 | } 40 | 41 | func (customT *UILineEditArgs) SetOnTextChanged(onTextChanged walk.EventHandler) *UILineEditArgs { 42 | customT.onTextChanged = onTextChanged 43 | return customT 44 | } 45 | 46 | func (customT *UILineEditArgs) SetAssignTo(assignTo **walk.LineEdit) *UILineEditArgs { 47 | customT.assignTo = assignTo 48 | return customT 49 | } 50 | 51 | func (customT *UILineEditArgs) SetCustomSize(customSize Size) *UILineEditArgs { 52 | customT.customSize = customSize 53 | return customT 54 | } 55 | -------------------------------------------------------------------------------- /platform/win/ui/pack/ui_push_btn.go: -------------------------------------------------------------------------------- 1 | package pack 2 | 3 | import ( 4 | "github.com/lxn/walk" 5 | . "github.com/lxn/walk/declarative" 6 | ) 7 | 8 | func UIPushBtn(args *UIPushBtnArgs) Widget { 9 | return PushButton{ 10 | AssignTo: args.assignTo, 11 | MinSize: args.customSize, 12 | MaxSize: args.customSize, 13 | Text: args.text, 14 | OnClicked: args.onClicked, 15 | } 16 | } 17 | 18 | func NewUIPushBtnArgs(assignTo **walk.PushButton) *UIPushBtnArgs { 19 | return &UIPushBtnArgs{assignTo: assignTo, text: "btn", customSize: Size{Width: 80}} 20 | } 21 | 22 | type UIPushBtnArgs struct { 23 | assignTo **walk.PushButton 24 | customSize Size 25 | text string 26 | onClicked walk.EventHandler 27 | } 28 | 29 | func (customT *UIPushBtnArgs) SetAssignTo(assignTo **walk.PushButton) *UIPushBtnArgs { 30 | customT.assignTo = assignTo 31 | return customT 32 | } 33 | 34 | func (customT *UIPushBtnArgs) SetCustomSize(customSize Size) *UIPushBtnArgs { 35 | customT.customSize = customSize 36 | return customT 37 | } 38 | 39 | func (customT *UIPushBtnArgs) SetText(text string) *UIPushBtnArgs { 40 | customT.text = text 41 | return customT 42 | } 43 | 44 | func (customT *UIPushBtnArgs) SetOnClicked(onClicked walk.EventHandler) *UIPushBtnArgs { 45 | customT.onClicked = onClicked 46 | return customT 47 | } 48 | -------------------------------------------------------------------------------- /platform/win/ui/pack/ui_scroll_view.go: -------------------------------------------------------------------------------- 1 | package pack 2 | 3 | import ( 4 | "github.com/lxn/walk" 5 | . "github.com/lxn/walk/declarative" 6 | ) 7 | 8 | func UIScrollView(args *UIScrollViewArgs) Widget { 9 | return ScrollView{ 10 | MaxSize: args.customSize, 11 | MinSize: args.customSize, 12 | Visible: args.visible, 13 | AssignTo: args.assignTo, 14 | Children: args.children, 15 | Layout: args.layout, 16 | HorizontalFixed: args.horizontalFixed, 17 | VerticalFixed: args.verticalFixed, 18 | } 19 | } 20 | 21 | func NewUIScrollViewArgs(assignTo **walk.ScrollView) *UIScrollViewArgs { 22 | return &UIScrollViewArgs{ 23 | assignTo: assignTo, 24 | visible: true, 25 | layout: VBox{MarginsZero: true}, 26 | horizontalFixed: false, 27 | verticalFixed: false, 28 | } 29 | } 30 | 31 | type UIScrollViewArgs struct { 32 | assignTo **walk.ScrollView 33 | visible bool 34 | customSize Size 35 | children []Widget 36 | layout Layout 37 | horizontalFixed bool 38 | verticalFixed bool 39 | } 40 | 41 | func (customT *UIScrollViewArgs) SetLayout(layout Layout) *UIScrollViewArgs { 42 | customT.layout = layout 43 | return customT 44 | } 45 | 46 | func (customT *UIScrollViewArgs) SetHorizontalFixed(horizontalFixed bool) *UIScrollViewArgs { 47 | customT.horizontalFixed = horizontalFixed 48 | return customT 49 | } 50 | 51 | func (customT *UIScrollViewArgs) HorizontalFixed() *UIScrollViewArgs { 52 | customT.horizontalFixed = true 53 | return customT 54 | } 55 | 56 | func (customT *UIScrollViewArgs) SetVerticalFixed(verticalFixed bool) *UIScrollViewArgs { 57 | customT.verticalFixed = verticalFixed 58 | return customT 59 | } 60 | 61 | func (customT *UIScrollViewArgs) VerticalFixed() *UIScrollViewArgs { 62 | customT.verticalFixed = true 63 | return customT 64 | } 65 | 66 | func (customT *UIScrollViewArgs) SetChildren(children []Widget) *UIScrollViewArgs { 67 | customT.children = children 68 | return customT 69 | } 70 | 71 | func (customT *UIScrollViewArgs) SetAssignTo(assignTo **walk.ScrollView) *UIScrollViewArgs { 72 | customT.assignTo = assignTo 73 | return customT 74 | } 75 | 76 | func (customT *UIScrollViewArgs) SetVisible(visible bool) *UIScrollViewArgs { 77 | customT.visible = visible 78 | return customT 79 | } 80 | 81 | func (customT *UIScrollViewArgs) SetCustomSize(customSize Size) *UIScrollViewArgs { 82 | customT.customSize = customSize 83 | return customT 84 | } 85 | -------------------------------------------------------------------------------- /platform/win/ui/pack/ui_text_edit.go: -------------------------------------------------------------------------------- 1 | package pack 2 | 3 | import ( 4 | "github.com/lxn/walk" 5 | . "github.com/lxn/walk/declarative" 6 | ) 7 | 8 | func UITextEdit(args *UITextEditArgs) Widget { 9 | return TextEdit{ 10 | MinSize: args.customSize, 11 | MaxSize: args.customSize, 12 | AssignTo: args.assignTo, 13 | VScroll: args.vScroll, 14 | HScroll: args.hScroll, 15 | OnTextChanged: args.onTextChanged, 16 | ReadOnly: args.readOnly, 17 | } 18 | } 19 | 20 | func NewUITextEditArgs(assignTo **walk.TextEdit) *UITextEditArgs { 21 | return &UITextEditArgs{assignTo: assignTo} 22 | } 23 | 24 | type UITextEditArgs struct { 25 | assignTo **walk.TextEdit 26 | customSize Size 27 | vScroll bool 28 | hScroll bool 29 | readOnly bool 30 | onTextChanged walk.EventHandler 31 | } 32 | 33 | func (customT *UITextEditArgs) SetReadOnly(readOnly bool) *UITextEditArgs { 34 | customT.readOnly = readOnly 35 | return customT 36 | } 37 | 38 | func (customT *UITextEditArgs) SetOnTextChanged(onTextChanged walk.EventHandler) *UITextEditArgs { 39 | customT.onTextChanged = onTextChanged 40 | return customT 41 | } 42 | 43 | func (customT *UITextEditArgs) SetAssignTo(assignTo **walk.TextEdit) *UITextEditArgs { 44 | customT.assignTo = assignTo 45 | return customT 46 | } 47 | 48 | func (customT *UITextEditArgs) SetCustomSize(customSize Size) *UITextEditArgs { 49 | customT.customSize = customSize 50 | return customT 51 | } 52 | 53 | func (customT *UITextEditArgs) SetVScroll(vScroll bool) *UITextEditArgs { 54 | customT.vScroll = vScroll 55 | return customT 56 | } 57 | 58 | func (customT *UITextEditArgs) SetHScroll(hScroll bool) *UITextEditArgs { 59 | customT.hScroll = hScroll 60 | return customT 61 | } 62 | -------------------------------------------------------------------------------- /platform/win/ui/pack/ui_text_label.go: -------------------------------------------------------------------------------- 1 | package pack 2 | 3 | import ( 4 | "github.com/lxn/walk" 5 | . "github.com/lxn/walk/declarative" 6 | ) 7 | 8 | func UITextLabel(args *UITextLabelArgs) Widget { 9 | return TextLabel{ 10 | Enabled: args.enabled, 11 | MaxSize: args.customSize, 12 | MinSize: args.customSize, 13 | Visible: args.visible, 14 | AssignTo: args.assignTo, 15 | Text: args.text, 16 | } 17 | } 18 | 19 | func NewUITextLabelArgs(assignTo **walk.TextLabel) *UITextLabelArgs { 20 | return &UITextLabelArgs{ 21 | assignTo: assignTo, 22 | visible: true, 23 | enabled: true, 24 | } 25 | } 26 | 27 | type UITextLabelArgs struct { 28 | assignTo **walk.TextLabel 29 | visible bool 30 | enabled bool 31 | text string 32 | customSize Size 33 | } 34 | 35 | func (customT *UITextLabelArgs) SetAssignTo(assignTo **walk.TextLabel) *UITextLabelArgs { 36 | customT.assignTo = assignTo 37 | return customT 38 | } 39 | 40 | func (customT *UITextLabelArgs) SetVisible(visible bool) *UITextLabelArgs { 41 | customT.visible = visible 42 | return customT 43 | } 44 | 45 | func (customT *UITextLabelArgs) SetEnabled(enabled bool) *UITextLabelArgs { 46 | customT.enabled = enabled 47 | return customT 48 | } 49 | 50 | func (customT *UITextLabelArgs) SetText(text string) *UITextLabelArgs { 51 | customT.text = text 52 | return customT 53 | } 54 | 55 | func (customT *UITextLabelArgs) SetCustomSize(customSize Size) *UITextLabelArgs { 56 | customT.customSize = customSize 57 | return customT 58 | } 59 | -------------------------------------------------------------------------------- /platform/win/ui/pack/widget_group.go: -------------------------------------------------------------------------------- 1 | package pack 2 | 3 | import . "github.com/lxn/walk/declarative" 4 | 5 | func NewWidgetGroup() *WidgetGroup { 6 | return new(WidgetGroup) 7 | } 8 | 9 | type WidgetGroup struct { 10 | widgets []Widget 11 | } 12 | 13 | func (customT *WidgetGroup) Append(widgets ...Widget) *WidgetGroup { 14 | if len(widgets) > 0 { 15 | customT.widgets = append(customT.widgets, widgets...) 16 | } 17 | return customT 18 | } 19 | 20 | func (customT *WidgetGroup) AppendZeroHSpacer() *WidgetGroup { 21 | customT.widgets = append(customT.widgets, HSpacer{}) 22 | return customT 23 | } 24 | 25 | func (customT *WidgetGroup) AppendZeroVSpacer() *WidgetGroup { 26 | customT.widgets = append(customT.widgets, VSpacer{}) 27 | return customT 28 | } 29 | 30 | func (customT *WidgetGroup) GetWidgets() []Widget { 31 | return customT.widgets 32 | } 33 | -------------------------------------------------------------------------------- /platform/win/ui/page.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "fmt" 5 | "github.com/lxn/walk" 6 | . "github.com/lxn/walk/declarative" 7 | "sync" 8 | ) 9 | 10 | type IPage interface { 11 | GetId() string 12 | GetName() string 13 | BindWindow(win *walk.MainWindow) 14 | SetVisible(isVisible bool) 15 | GetWidget() Widget 16 | Reset() 17 | } 18 | 19 | type PageCtl struct { 20 | current IPage 21 | pages sync.Map 22 | menus []MenuItem 23 | } 24 | 25 | func (customPC *PageCtl) PushPages(pages ...IPage) { 26 | for _, page := range pages { 27 | if _, isExist := customPC.pages.Load(page.GetId()); isExist { 28 | continue 29 | } 30 | customPC.pages.Store(page.GetId(), page) 31 | } 32 | } 33 | 34 | func (customPC *PageCtl) SetCurrent(pageId string) error { 35 | currentPage, err := customPC.GetPageById(pageId) 36 | if err != nil { 37 | return err 38 | } 39 | customPC.current = currentPage 40 | defer customPC.Render() 41 | return nil 42 | } 43 | 44 | func (customPC *PageCtl) Render() { 45 | customPC.pages.Range(func(pageId, currentPage any) bool { 46 | currentPage.(IPage).SetVisible(currentPage.(IPage).GetId() == customPC.current.GetId()) 47 | currentPage.(IPage).Reset() 48 | return true 49 | }) 50 | } 51 | 52 | func (customPC *PageCtl) GetPageById(pageId string) (IPage, error) { 53 | currentPage, isOk := customPC.pages.Load(pageId) 54 | if !isOk { 55 | return nil, fmt.Errorf("当前页面[%s]不存在", pageId) 56 | } 57 | return currentPage.(IPage), nil 58 | } 59 | 60 | func (customPC *PageCtl) Bind(win *walk.MainWindow) { 61 | customPC.pages.Range(func(pageId, currentPage any) bool { 62 | currentPage.(IPage).BindWindow(win) 63 | return true 64 | }) 65 | return 66 | } 67 | 68 | func (customPC *PageCtl) GetWidgets() []Widget { 69 | var widgets []Widget 70 | customPC.pages.Range(func(pageId, currentPage any) bool { 71 | widgets = append(widgets, currentPage.(IPage).GetWidget()) 72 | return true 73 | }) 74 | return widgets 75 | } 76 | -------------------------------------------------------------------------------- /platform/win/ui/sets.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "github.com/lxn/walk" 5 | "github.com/lxn/win" 6 | ) 7 | 8 | func (customG *Gui) setWindowFlag() { 9 | win.SetWindowLong(customG.GetWindow().Handle(), win.GWL_STYLE, 10 | win.GetWindowLong(customG.GetWindow().Handle(), win.GWL_STYLE) & ^win.WS_MAXIMIZEBOX & ^win.WS_THICKFRAME) 11 | } 12 | 13 | func (customG *Gui) setWindowCenter() { 14 | scrWidth := win.GetSystemMetrics(win.SM_CXSCREEN) 15 | scrHeight := win.GetSystemMetrics(win.SM_CYSCREEN) 16 | _ = customG.GetWindow().SetBounds(walk.Rectangle{ 17 | X: int((scrWidth - width) / 2), Y: int((scrHeight - height) / 2), 18 | Width: width, Height: height, 19 | }) 20 | } 21 | 22 | func (customG *Gui) setWindowMinAndMaxSize() { 23 | minMaxSize := walk.Size{Width: width, Height: height} 24 | _ = customG.GetWindow().SetMinMaxSize(minMaxSize, minMaxSize) 25 | } 26 | -------------------------------------------------------------------------------- /platform/win/ui/ui.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "anto/lib/log" 5 | "context" 6 | "sync" 7 | 8 | "github.com/lxn/walk" 9 | . "github.com/lxn/walk/declarative" 10 | ) 11 | 12 | const width = 800 13 | const height = 600 14 | 15 | var ( 16 | apiSingleton *Gui 17 | onceSingleton sync.Once 18 | ) 19 | 20 | func Singleton() *Gui { 21 | onceSingleton.Do(func() { 22 | apiSingleton = new(Gui) 23 | apiSingleton.pageCtl = new(PageCtl) 24 | }) 25 | return apiSingleton 26 | } 27 | 28 | type Gui struct { 29 | ctx context.Context 30 | ctxCancelFn context.CancelFunc 31 | win *walk.MainWindow 32 | notification *walk.NotifyIcon 33 | cfg *Cfg 34 | menus []MenuItem 35 | pageCtl *PageCtl 36 | } 37 | 38 | func (customG *Gui) Init(cfg *Cfg) error { 39 | customG.cfg = cfg 40 | 41 | if err := customG.initMainWindow(); err != nil { 42 | return err 43 | } 44 | 45 | customG.initNotification() 46 | 47 | customG.setWindowFlag() 48 | customG.setWindowCenter() 49 | customG.setWindowMinAndMaxSize() 50 | 51 | customG.pageCtl.Bind(customG.GetWindow()) 52 | 53 | customG.GetWindow().SetVisible(true) 54 | 55 | _ = customG.GetWindow().SetFocus() 56 | 57 | return nil 58 | } 59 | 60 | func (customG *Gui) Run(ctx context.Context, fnCancel context.CancelFunc) { 61 | customG.ctx = ctx 62 | customG.ctxCancelFn = fnCancel 63 | 64 | customG.win.Closing().Attach(customG.eventCustomClose) 65 | 66 | customG.GetWindow().Run() 67 | } 68 | 69 | func (customG *Gui) Close() { 70 | _ = customG.win.Close() 71 | } 72 | 73 | func (customG *Gui) GetWindow() *walk.MainWindow { 74 | return customG.win 75 | } 76 | 77 | func (customG *Gui) RegisterPages(pages ...IPage) { 78 | customG.pageCtl.PushPages(pages...) 79 | return 80 | } 81 | 82 | func (customG *Gui) GoPage(pageId string) error { 83 | return customG.pageCtl.SetCurrent(pageId) 84 | } 85 | 86 | func (customG *Gui) RegisterMenus(menus []MenuItem) { 87 | customG.menus = menus 88 | return 89 | } 90 | 91 | func (customG *Gui) initMainWindow() error { 92 | return MainWindow{ 93 | AssignTo: &customG.win, 94 | Title: customG.cfg.Title, Icon: "./favicon.ico", 95 | Visible: false, Persistent: true, 96 | Layout: VBox{MarginsZero: true}, 97 | MenuItems: customG.menus, 98 | Children: customG.pageCtl.GetWidgets(), 99 | }.Create() 100 | } 101 | 102 | func (customG *Gui) log() *log.Log { 103 | return log.Singleton() 104 | } 105 | -------------------------------------------------------------------------------- /platform/win/win.go: -------------------------------------------------------------------------------- 1 | package win 2 | 3 | import ( 4 | "anto/cfg" 5 | "anto/cron/detector" 6 | "anto/cron/reader" 7 | "anto/cron/translate" 8 | "anto/cron/writer" 9 | "anto/lib/log" 10 | "anto/lib/nohup" 11 | "anto/platform/win/page" 12 | "anto/platform/win/ui" 13 | "context" 14 | ) 15 | 16 | func Run(ctx context.Context) { 17 | ui.Singleton().RegisterMenus(GetInstance().GetMenus()) 18 | 19 | ui.Singleton().RegisterPages(page.GetSettings(), page.GetSubripTranslate()) 20 | 21 | if err := ui.Singleton().Init(cfg.Singleton().UI); err != nil { 22 | log.Singleton().ErrorF("UI启动崩溃, 错误: %s", err) 23 | panic(err) 24 | } 25 | 26 | _ = ui.Singleton().GoPage(page.GetSubripTranslate().GetId()) 27 | 28 | nohup.NewResident( 29 | ctx, 30 | detector.Singleton(), reader.Singleton(), translate.Singleton(), writer.Singleton(), 31 | ui.Singleton(), 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /resource/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/speauty/anto/d537a1ad2b935ec8c6973912e1a5ed919d932ea7/resource/favicon.ico -------------------------------------------------------------------------------- /resource/resource.go: -------------------------------------------------------------------------------- 1 | package resource 2 | 3 | import _ "embed" 4 | 5 | //go:embed favicon.ico 6 | var Favicon []byte 7 | --------------------------------------------------------------------------------