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) )
    }
  }
}