Microsoft TranslatorをGoから使ってみた
前回、Rubyでやった翻訳API使用を今度はGo言語でもやってみた。
http://system.hateblo.jp/entry/2013/05/17/181822
とはいえGoらしい書き方がまだ分かってないんだけど。
前回と同じくCygwinから使う想定(Windowsのプロンプトだと手を加えないとUTF-8文字が表示できないため)。
package main import ( "fmt" "net/http" "net/url" "encoding/json" "encoding/xml" "io/ioutil" "time" "strconv" ) const ( MS_TRANSLATOR_PRIMARY_KEY = "ここに記載する" MS_TRANSLATOR_CLIENT_ID = "ここに記載する" MS_TRANSLATOR_CLIENT_SECRET = "ここに記載する" MS_TRANSLATOR_SCOPE = "http://api.microsofttranslator.com" MS_TRANSLATOR_ACCESSTOKEN_URL = "https://datamarket.accesscontrol.windows.net/v2/OAuth2-13" MS_TRANSLATOR_URL = "http://api.microsofttranslator.com/V2/Http.svc/Translate" MS_TRANSLATOR_GRANT_TYPE = "client_credentials" ) // アクセストークン取得時の型 type AccessTokenMessage struct { TokenType string `json:"token_type"` AccessToken string `json:"access_token"` ExpiresIn string `json:"expires_in"` Scope string `json:"scope"` } func (self *AccessTokenMessage) getExpiresIn() int { i, err := strconv.Atoi(self.ExpiresIn) if err != nil { return 0 } return i } // アクセストークン取得時のキャッシュ型 type AccessTokenMessageCache struct { accessTokenMessage AccessTokenMessage updateTime time.Time } func (self *AccessTokenMessageCache) isRequireRenew() bool { duration := int(time.Since(self.updateTime)) * int(time.Second) if duration >= self.accessTokenMessage.getExpiresIn() { return true } return false } func (self *AccessTokenMessageCache) setUpdateTime(time time.Time) { self.updateTime = time } func (self *AccessTokenMessageCache) getUpdateTime() time.Time { return self.updateTime } func (self *AccessTokenMessageCache) loadNewAccessTokenMessage() { resp, err := http.PostForm(MS_TRANSLATOR_ACCESSTOKEN_URL, url.Values{ "client_id": {MS_TRANSLATOR_CLIENT_ID}, "client_secret": {MS_TRANSLATOR_CLIENT_SECRET}, "scope": {MS_TRANSLATOR_SCOPE}, "grant_type": {MS_TRANSLATOR_GRANT_TYPE}}) if err != nil { panic(err) } defer resp.Body.Close() // mattn@github => Qiita // ReadAll を使うとメモリをガッサリ取るので、Decoderを使うと良いです。 // デバッグする為に標準出力したい場合は、io.TeeReader で片方を os.Stdout 等に出すと良いです。 err = json.NewDecoder(resp.Body).Decode(&self.accessTokenMessage) if err != nil { panic(err) } } func (self *AccessTokenMessageCache) getAccessTokenMessage() AccessTokenMessage { if self.isRequireRenew() { self.loadNewAccessTokenMessage() } return self.accessTokenMessage } func (self *AccessTokenMessageCache) getAccessToken() string { message := self.getAccessTokenMessage() return message.AccessToken } // 翻訳結果の受け取り型 type TransResult struct { String string `xml:"string"` } // 翻訳 func trans(word string, atmc *AccessTokenMessageCache) string { values := url.Values{"text":{word}, "from":{"en"}, "to":{"ja"}} query := values.Encode() accessToken := atmc.getAccessToken() client := &http.Client{} req, err := http.NewRequest("GET", MS_TRANSLATOR_URL + "?" + query, nil) if(err != nil) { panic(err) } req.Header.Add("Authorization", "Bearer " + accessToken) resp, err := client.Do(req) if(err != nil) { panic(err) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) /* 返ってきたデータは<string>結果</string>という形だが、この場合のUnmarshalに与える型の形式が分からないため、 仕方なくルート要素でくるむ。 正しい方法は? */ bodyString := "<result>" + string(body) + "</result>" var x *TransResult err = xml.Unmarshal([]byte(bodyString), &x) if(err != nil) { panic(err) } return x.String } func main() { var input = "" println("input English word") println("'!' to quit") atmc := new(AccessTokenMessageCache); for input != "!" { fmt.Scan(&input) if input != "!" { fmt.Println( " => " + trans(input, atmc) ) } } }