From 2e41fa800c4373f1347c463629b026f90f4273bb Mon Sep 17 00:00:00 2001 From: eeeXun Date: Sat, 15 Apr 2023 01:35:09 +0800 Subject: [PATCH] feat(translator): add BingTranslate --- README.md | 2 + config.go | 2 + internal/translate/bingtranslate/lang.go | 256 ++++++++++++++++++ .../translate/bingtranslate/translator.go | 126 +++++++++ internal/translate/core/utils.go | 5 + .../translate/reversotranslate/translator.go | 5 +- internal/translate/translator.go | 11 +- 7 files changed, 403 insertions(+), 4 deletions(-) create mode 100644 internal/translate/bingtranslate/lang.go create mode 100644 internal/translate/bingtranslate/translator.go create mode 100644 internal/translate/core/utils.go diff --git a/README.md b/README.md index 74d56de..a0cdb4e 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ Google Translate TUI (Originally) Supported Translator: [`ApertiumTranslate`](https://www.apertium.org/), [`ArgosTranslate`](https://translate.argosopentech.com/), +[`BingTranslate`](https://www.bing.com/translator), [`GoogleTranslate`](https://translate.google.com/), [`ReversoTranslate`](https://www.reverso.net/text-translation) @@ -48,6 +49,7 @@ See language on: - [Apertium Translate](https://www.apertium.org/) for `ApertiumTranslate` - [argosopentech/argos-translate](https://github.com/argosopentech/argos-translate#supported-languages) for `ArgosTranslate` +- [Bing Translate](https://www.bing.com/translator) for `BingTranslate` - [Google Language support](https://cloud.google.com/translate/docs/languages) for `GoogleTranslate` - [Reverso Translation](https://www.reverso.net/text-translation) for `ReversoTranslate` diff --git a/config.go b/config.go index e7b7276..a284137 100644 --- a/config.go +++ b/config.go @@ -22,6 +22,8 @@ func configInit() { "destination.language.apertiumtranslate": "English", "source.language.argostranslate": "English", "destination.language.argostranslate": "English", + "source.language.bingtranslate": "English", + "destination.language.bingtranslate": "English", "source.language.googletranslate": "English", "destination.language.googletranslate": "English", "source.language.reversotranslate": "English", diff --git a/internal/translate/bingtranslate/lang.go b/internal/translate/bingtranslate/lang.go new file mode 100644 index 0000000..eea459e --- /dev/null +++ b/internal/translate/bingtranslate/lang.go @@ -0,0 +1,256 @@ +package bingtranslate + +var ( + lang = []string{ + "Afrikaans", + "Albanian", + "Amharic", + "Auto", + "Arabic", + "Armenian", + "Assamese", + "Azerbaijani", + "Bangla", + "Bashkir", + "Basque", + "Bosnian", + "Bulgarian", + "Cantonese (Traditional)", + "Catalan", + "Chinese (Literary)", + "Chinese Simplified", + "Chinese Traditional", + "Croatian", + "Czech", + "Danish", + "Dari", + "Divehi", + "Dutch", + "English", + "Estonian", + "Faroese", + "Fijian", + "Filipino", + "Finnish", + "French", + "French (Canada)", + "Galician", + "Ganda", + "Georgian", + "German", + "Greek", + "Gujarati", + "Haitian Creole", + "Hausa", + "Hebrew", + "Hindi", + "Hmong Daw", + "Hungarian", + "Icelandic", + "Igbo", + "Indonesian", + "Inuinnaqtun", + "Inuktitut", + "Inuktitut (Latin)", + "Irish", + "Italian", + "Japanese", + "Kannada", + "Kazakh", + "Khmer", + "Kinyarwanda", + "Klingon (Latin)", + "Korean", + "Kurdish (Central)", + "Kurdish (Northern)", + "Kyrgyz", + "Lao", + "Latvian", + "Lingala", + "Lithuanian", + "Lower Sorbian", + "Macedonian", + "Malagasy", + "Malay", + "Malayalam", + "Maltese", + "Marathi", + "Mongolian (Cyrillic)", + "Mongolian (Traditional)", + "Myanmar (Burmese)", + "Māori", + "Nepali", + "Norwegian", + "Nyanja", + "Odia", + "Pashto", + "Persian", + "Polish", + "Portuguese (Brazil)", + "Portuguese (Portugal)", + "Punjabi", + "Querétaro Otomi", + "Romanian", + "Rundi", + "Russian", + "Samoan", + "Serbian (Cyrillic)", + "Serbian (Latin)", + "Sesotho", + "Sesotho sa Leboa", + "Setswana", + "Shona", + "Slovak", + "Slovenian", + "Somali", + "Spanish", + "Swahili", + "Swedish", + "Tahitian", + "Tamil", + "Tatar", + "Telugu", + "Thai", + "Tibetan", + "Tigrinya", + "Tongan", + "Turkish", + "Turkmen", + "Ukrainian", + "Urdu", + "Uyghur", + "Uzbek (Latin)", + "Vietnamese", + "Welsh", + "Xhosa", + "Yoruba", + "Yucatec Maya", + "Zulu", + } + langCode = map[string]string{ + "Afrikaans": "af", + "Albanian": "sq", + "Amharic": "am", + "Auto": "auto-detect", + "Arabic": "ar", + "Armenian": "hy", + "Assamese": "as", + "Azerbaijani": "az", + "Bangla": "bn", + "Bashkir": "ba", + "Basque": "eu", + "Bosnian": "bs", + "Bulgarian": "bg", + "Cantonese (Traditional)": "yue", + "Catalan": "ca", + "Chinese (Literary)": "lzh", + "Chinese Simplified": "zh-Hans", + "Chinese Traditional": "zh-Hant", + "Croatian": "hr", + "Czech": "cs", + "Danish": "da", + "Dari": "prs", + "Divehi": "dv", + "Dutch": "nl", + "English": "en", + "Estonian": "et", + "Faroese": "fo", + "Fijian": "fj", + "Filipino": "fil", + "Finnish": "fi", + "French": "fr", + "French (Canada)": "fr-CA", + "Galician": "gl", + "Ganda": "lug", + "Georgian": "ka", + "German": "de", + "Greek": "el", + "Gujarati": "gu", + "Haitian Creole": "ht", + "Hausa": "ha", + "Hebrew": "he", + "Hindi": "hi", + "Hmong Daw": "mww", + "Hungarian": "hu", + "Icelandic": "is", + "Igbo": "ig", + "Indonesian": "id", + "Inuinnaqtun": "ikt", + "Inuktitut": "iu", + "Inuktitut (Latin)": "iu-Latn", + "Irish": "ga", + "Italian": "it", + "Japanese": "ja", + "Kannada": "kn", + "Kazakh": "kk", + "Khmer": "km", + "Kinyarwanda": "rw", + "Klingon (Latin)": "tlh-Latn", + "Korean": "ko", + "Kurdish (Central)": "ku", + "Kurdish (Northern)": "kmr", + "Kyrgyz": "ky", + "Lao": "lo", + "Latvian": "lv", + "Lingala": "ln", + "Lithuanian": "lt", + "Lower Sorbian": "dsb", + "Macedonian": "mk", + "Malagasy": "mg", + "Malay": "ms", + "Malayalam": "ml", + "Maltese": "mt", + "Marathi": "mr", + "Mongolian (Cyrillic)": "mn-Cyrl", + "Mongolian (Traditional)": "mn-Mong", + "Myanmar (Burmese)": "my", + "Māori": "mi", + "Nepali": "ne", + "Norwegian": "nb", + "Nyanja": "nya", + "Odia": "or", + "Pashto": "ps", + "Persian": "fa", + "Polish": "pl", + "Portuguese (Brazil)": "pt", + "Portuguese (Portugal)": "pt-PT", + "Punjabi": "pa", + "Querétaro Otomi": "otq", + "Romanian": "ro", + "Rundi": "run", + "Russian": "ru", + "Samoan": "sm", + "Serbian (Cyrillic)": "sr-Cyrl", + "Serbian (Latin)": "sr-Latn", + "Sesotho": "st", + "Sesotho sa Leboa": "nso", + "Setswana": "tn", + "Shona": "sn", + "Slovak": "sk", + "Slovenian": "sl", + "Somali": "so", + "Spanish": "es", + "Swahili": "sw", + "Swedish": "sv", + "Tahitian": "ty", + "Tamil": "ta", + "Tatar": "tt", + "Telugu": "te", + "Thai": "th", + "Tibetan": "bo", + "Tigrinya": "ti", + "Tongan": "to", + "Turkish": "tr", + "Turkmen": "tk", + "Ukrainian": "uk", + "Urdu": "ur", + "Uyghur": "ug", + "Uzbek (Latin)": "uz", + "Vietnamese": "vi", + "Welsh": "cy", + "Xhosa": "xh", + "Yoruba": "yo", + "Yucatec Maya": "yua", + "Zulu": "zu", + } +) diff --git a/internal/translate/bingtranslate/translator.go b/internal/translate/bingtranslate/translator.go new file mode 100644 index 0000000..8341ae6 --- /dev/null +++ b/internal/translate/bingtranslate/translator.go @@ -0,0 +1,126 @@ +package bingtranslate + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "regexp" + "strings" + + "github.com/eeeXun/gtt/internal/translate/core" +) + +const ( + setUpURL = "https://www.bing.com/translator" + textURL = "https://www.bing.com/ttranslatev3?IG=%s&IID=%s" +) + +type BingTranslate struct { + *core.Language + *core.TTSLock + core.EngineName +} + +type setUpData struct { + ig string + iid string + key string + token string +} + +func NewBingTranslate() *BingTranslate { + return &BingTranslate{ + Language: core.NewLanguage(), + TTSLock: core.NewTTSLock(), + EngineName: core.NewEngineName("BingTranslate"), + } +} + +func (t *BingTranslate) GetAllLang() []string { + return lang +} + +func (t *BingTranslate) setUp() (*setUpData, error) { + var data setUpData + + res, err := http.Get(setUpURL) + if err != nil { + return nil, err + } + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + bodyStr := string(body) + igData := regexp.MustCompile(`IG:"([^"]+)"`).FindStringSubmatch(bodyStr) + if len(igData) < 2 { + return nil, errors.New(t.GetEngineName() + " IG not found") + } + data.ig = igData[1] + iidData := regexp.MustCompile(`data-iid="([^"]+)`).FindStringSubmatch(bodyStr) + if len(iidData) < 2 { + return nil, errors.New(t.GetEngineName() + " IID not found") + } + data.iid = iidData[1] + params := regexp.MustCompile(`params_AbusePreventionHelper = ([^;]+);`).FindStringSubmatch(bodyStr) + if len(params) < 2 { + return nil, errors.New(t.GetEngineName() + " Key and Token not found") + } + paramsStr := strings.Split(params[1][1:len(params[1])-1], ",") + data.key = paramsStr[0] + data.token = paramsStr[1][1 : len(paramsStr[1])-1] + + return &data, nil +} + +func (t *BingTranslate) Translate(message string) (translation, definition, partOfSpeech string, err error) { + var data []interface{} + + initData, err := t.setUp() + if err != nil { + return "", "", "", err + } + userData := url.Values{ + "fromLang": {langCode[t.GetSrcLang()]}, + "to": {langCode[t.GetDstLang()]}, + "text": {message}, + "key": {initData.key}, + "token": {initData.token}, + } + req, err := http.NewRequest("POST", + fmt.Sprintf(textURL, initData.ig, initData.iid), + strings.NewReader(userData.Encode()), + ) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add("User-Agent", core.UserAgent) + res, err := http.DefaultClient.Do(req) + if err != nil { + return "", "", "", err + } + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return "", "", "", err + } + if err = json.Unmarshal(body, &data); err != nil { + return "", "", "", err + } + + if len(data) <= 0 { + return "", "", "", errors.New("Translation not found") + } + + // translation + translation = fmt.Sprintf("%v", + data[0].(map[string]interface{})["translations"].([]interface{})[0].(map[string]interface{})["text"]) + + return translation, definition, partOfSpeech, nil +} + +func (t *BingTranslate) PlayTTS(lang, message string) error { + defer t.ReleaseLock() + + return errors.New(t.GetEngineName() + " does not support text to speech") +} diff --git a/internal/translate/core/utils.go b/internal/translate/core/utils.go new file mode 100644 index 0000000..28dfdac --- /dev/null +++ b/internal/translate/core/utils.go @@ -0,0 +1,5 @@ +package core + +const ( + UserAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" +) diff --git a/internal/translate/reversotranslate/translator.go b/internal/translate/reversotranslate/translator.go index 9d969af..15a122e 100644 --- a/internal/translate/reversotranslate/translator.go +++ b/internal/translate/reversotranslate/translator.go @@ -19,7 +19,6 @@ import ( const ( textURL = "https://api.reverso.net/translate/v1/translation" ttsURL = "https://voice.reverso.net/RestPronunciation.svc/v1/output=json/GetVoiceStream/voiceName=%s?voiceSpeed=80&inputText=%s" - userAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36" ) type ReversoTranslate struct { @@ -65,7 +64,7 @@ func (t *ReversoTranslate) Translate(message string) (translation, definition, p textURL, bytes.NewBuffer([]byte(userData))) req.Header.Add("Content-Type", "application/json") - req.Header.Add("User-Agent", userAgent) + req.Header.Add("User-Agent", core.UserAgent) res, err := http.DefaultClient.Do(req) if err != nil { return "", "", "", err @@ -124,7 +123,7 @@ func (t *ReversoTranslate) PlayTTS(lang, message string) error { base64.StdEncoding.EncodeToString([]byte(message)), ) req, _ := http.NewRequest("GET", urlStr, nil) - req.Header.Add("User-Agent", userAgent) + req.Header.Add("User-Agent", core.UserAgent) res, err := http.DefaultClient.Do(req) if err != nil { return err diff --git a/internal/translate/translator.go b/internal/translate/translator.go index 36f5621..3a942dc 100644 --- a/internal/translate/translator.go +++ b/internal/translate/translator.go @@ -3,12 +3,19 @@ package translate import ( "github.com/eeeXun/gtt/internal/translate/apertiumtranslate" "github.com/eeeXun/gtt/internal/translate/argostranslate" + "github.com/eeeXun/gtt/internal/translate/bingtranslate" "github.com/eeeXun/gtt/internal/translate/googletranslate" "github.com/eeeXun/gtt/internal/translate/reversotranslate" ) var ( - AllTranslator = []string{"ApertiumTranslate", "ArgosTranslate", "GoogleTranslate", "ReversoTranslate"} + AllTranslator = []string{ + "ApertiumTranslate", + "BingTranslate", + "ArgosTranslate", + "GoogleTranslate", + "ReversoTranslate", + } ) type Translator interface { @@ -57,6 +64,8 @@ func NewTranslator(name string) Translator { translator = apertiumtranslate.NewApertiumTranslate() case "ArgosTranslate": translator = argostranslate.NewArgosTranslate() + case "BingTranslate": + translator = bingtranslate.NewBingTranslate() case "GoogleTranslate": translator = googletranslate.NewGoogleTranslate() case "ReversoTranslate":