forked from lorniu/go-translate
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgts-engine-bing.el
155 lines (126 loc) · 7.07 KB
/
gts-engine-bing.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
;;; gts-engine-bing.el --- Microsoft Translate -*- lexical-binding: t -*-
;; Copyright (C) 2021 lorniu <[email protected]>
;; SPDX-License-Identifier: MIT
;;; Commentary:
;; https://bing.com/translator
;;; Code:
(require 'gts-implements)
(defclass gts-bing-parser (gts-parser) ())
(defclass gts-bing-engine (gts-engine)
((tag :initform "Bing")
(base-url :initform "https://www.bing.com")
(sub-url :initform "/ttranslatev3")
(tld-url :initform nil)
(ig :initform nil)
(key :initform nil)
(token :initform nil)
(last-time :initform nil)
(expired-time :initform (* 30 60))
(ttsk-url :initform "/tfetspktok")
(tts-url :initform "https://%s.tts.speech.microsoft.com/cognitiveservices/v1")
(tts-tpl :initform "<speak version='1.0' xml:lang='%s'><voice xml:lang='%s' xml:gender='Female' name='%s'><prosody rate='-20.00%%'>%s</prosody></voice></speak>")
(parser :initform (gts-bing-parser))))
;;; Engine
(defvar gts-bing-extra-langs-mapping '(("zh" . "zh-Hans")))
(defvar gts-bing-token-maybe-invalid nil)
(cl-defmethod gts-get-lang ((_ gts-bing-engine) lang)
(or (cdr-safe (assoc lang gts-bing-extra-langs-mapping)) lang))
(cl-defmethod gts-token-available-p ((engine gts-bing-engine))
(with-slots (token key ig last-time expired-time) engine
(and token key ig last-time
(not gts-bing-token-maybe-invalid)
(< (- (time-to-seconds) last-time) expired-time))))
(cl-defmethod gts-with-token ((engine gts-bing-engine) done fail)
(with-slots (token key ig base-url) engine
(if (gts-token-available-p engine)
(funcall done)
(gts-do-request (concat base-url "/translator")
:done
(lambda ()
(let (key token ig tld)
(re-search-forward "curUrl=.*/\\([a-z]+\\.bing.com\\)")
(setq tld (match-string 1))
(re-search-forward "\"ig\":\"\\([^\"]+\\).*params_AbusePreventionHelper = \\[\\([0-9]+\\),\"\\([^\"]+\\)")
(setq ig (match-string 1) key (match-string 2) token (match-string 3))
(oset engine ig ig)
(oset engine key key)
(oset engine token token)
(oset engine last-time (time-to-seconds))
(oset engine tld-url (concat "https://" tld))
(setq gts-bing-token-maybe-invalid nil)
(gts-do-log 'bing (format "url: %s\nkey: %s\ntoken: %s\nig: %s" tld key token ig))
(funcall done)))
:fail fail))))
(cl-defmethod gts-translate ((engine gts-bing-engine) task rendercb)
(gts-with-token engine
(lambda ()
(with-slots (text from to raw) task
(with-slots (tld-url sub-url token key ig parser) engine
(gts-do-request (format "%s%s?isVertical=1&IG=%s&IID=translator.5022.1" tld-url sub-url ig)
:headers `(("Content-Type" . "application/x-www-form-urlencoded;charset=UTF-8"))
:data `(("fromLang" . ,(gts-get-lang engine from))
("to" . ,(gts-get-lang engine to))
("text" . ,text)
("key" . ,key)
("token" . ,token))
:done (lambda ()
(gts-update-raw task (buffer-string))
(gts-parse parser task)
(funcall rendercb))
:fail (lambda (err)
(gts-render-fail task
(cond ((ignore-errors (= (caddar err) 429))
"[429] Too many requests! Please retry later")
(t err))))))))
(lambda (err)
(gts-render-fail task
(format "Error occurred when request for token.\n\n%s" err)))))
;;; TTS
(defvar gts-bing-tts-langs-mapping '(("zh" . ("zh-CN" . "zh-CN-XiaoxiaoNeural"))
("en" . ("en-US" . "en-US-AriaNeural"))
("fr" . ("fr-CA" . "fr-CA-SylvieNeural"))
("de" . ("de-DE" . "de-DE-KatjaNeural"))))
(cl-defmethod gts-tts-payload ((engine gts-bing-engine) lang text)
(with-slots (tts-tpl) engine
(let (l n (mt (assoc lang gts-bing-tts-langs-mapping)))
(if mt (setq l (cadr mt) n (cddr mt))
(user-error "Add the mapping of your language into `gts-bing-tts-langs-mapping' :)"))
(format tts-tpl l l n (encode-coding-string text 'utf-8)))))
(cl-defmethod gts-tts ((engine gts-bing-engine) text lang)
(gts-with-token engine
(lambda ()
(with-slots (tld-url sub-url token key ig parser ttsk-url tts-url tts-tpl) engine
(gts-do-request (format "%s%s?isVertical=1&IG=%s&IID=translator.5022.2" tld-url ttsk-url ig)
:headers '(("content-type" . "application/x-www-form-urlencoded"))
:data `(("token" . ,token) ("key" . ,key))
:done (lambda ()
(let* ((json (json-read))
(token (cdr (assoc 'token json)))
(region (cdr (assoc 'region json))))
(gts-do-log 'bing-tts (format "token: %s\nregion: %s" token region))
(gts-do-request (format tts-url region)
:data (gts-tts-payload engine lang text)
:headers `(("content-type" . "application/ssml+xml")
("authorization" . ,(format "Bearer %s" token))
("x-microsoft-outputformat" . "audio-16khz-32kbitrate-mono-mp3"))
:done (lambda ()
(gts-tts-speak-buffer-data))
:fail (lambda (_)
(user-error "[BING-TTS] error when play sound")))))
:fail (lambda (err) (user-error "%s" err)))))
(lambda (_) (user-error "Failed to get token"))))
;;; Parser
(cl-defmethod gts-parse ((_ gts-bing-parser) task)
(with-temp-buffer
(set-buffer-multibyte t)
(insert (oref task raw))
(decode-coding-region (point-min) (point-max) 'utf-8)
(gts-do-log 'bing (string-trim (buffer-string)))
(goto-char (point-min))
(if-let* ((json (json-read))
(result (ignore-errors (cdr (assoc 'text (aref (cdr (assoc 'translations (aref json 0))) 0))))))
(gts-update-parsed task result)
(setq gts-bing-token-maybe-invalid t) ; refresh token when error occurred
(gts-update-raw task nil (buffer-string)))))
(provide 'gts-engine-bing)
;;; gts-engine-bing.el ends here