LoginSignup
7
6

More than 5 years have passed since last update.

「GoogleHomeでポケモン図鑑」を改良してヒカリちゃんの声にしてみた

Posted at

ヒカリちゃんと言ってもVoiceTextのHIKARIちゃんです。

以前記事にした「GoogleHomeでポケモン図鑑作ってみた」を改良したので、また記事にしました。

改良点

  • Beebotteの依存を排除し、ngrokを使うように変更
    • BeeBotteの設定が面倒だったため
  • VoiceTextによる自然な日本語に変更
    • google-home-notifierはGoogle翻訳の日本語音声を使うが、これがちょっと不自然だったため
  • callback地獄をPromiseで整理し、コードを100行未満

構成の変化

改良前の方式と改良後の方式は次のようになっています。

  • 改良前
    GoogleHomePokemon1.png

    • ① 【IFTTTのthen】Google Assistantによるテキスト通知
    • ② 【IFTTTのthat】WebhookによるBeebotteのキューへのメッセージポスト
    • ③ nodeからキューをポーリング
    • ④ nodeでpokemon APIから図鑑説明の取得、google-home-notifierで音声再生
  • 改良後
    GoogleHomePokemon2.png

    • ① 【IFTTTのthen】Google Assistantによるテキスト通知
    • ② 【IFTTTのthat】Webhookでngrokのドメイン = nodeサーバーにメッセージをポスト
    • ③ nodeでpokemon APIから図鑑説明の取得、VoiceText APIによるmp3ファイルの生成、google-home-notifierで音声再生

ソースコード

reireias/google-home-pokemon-dictionary

const bodyParser = require('body-parser')
const config = require('config')
const express = require('express')
const fs = require('fs')
const googlehome = require('google-home-notifier')
const path = require('path')
const request = require('request')
const VoiceText = require('voicetext')

const searchUrl = 'https://pokeapi.co/api/v2/pokemon-species/'
const nameIdMap = JSON.parse(fs.readFileSync('./pokemon.json', 'utf-8'))
const voice = new VoiceText(config.voice.key)
const app = express()

const main = () => {
  googlehome.ip(config.googlehome.ip, config.googlehome.language)
  app.use(express.static(path.join(__dirname, '/tmp')))
  app.use(bodyParser.json())
  app.post('/pokemon', postPokemon)
  app.listen(config.port, () => {
    console.log('started')
  })
}

const postPokemon = (req, res) => {
  var promise = Promise.resolve(req.body.name)
  promise.then(search).then(notify).catch(handleError)
  res.send('ok')
}

const search = name => {
  return new Promise((resolve, reject) => {
    if (nameIdMap[name]) {
      let options = {
        url: searchUrl + nameIdMap[name],
        json: true
      }
      request.get(options, (error, response, body) => {
        if (error) {
          reject(error)
        }
        if (response.statusCode === 200) {
          let message = createNotifyMessage(body)
          resolve(message)
        } else {
          reject(new Error('検索時にエラーが発生しました。'))
        }
      })
    } else {
      reject(new Error('ポケモンが見つかりませんでした。'))
    }
  })
}

const notify = message => {
  return new Promise((resolve, reject) => {
    console.info(message)
    voice.speaker(voice.SPEAKER.HIKARI).speak(message, (error, buf) => {
      if (error) {
        reject(error)
      }
      fs.writeFileSync('./tmp/tmp.wav', buf, 'binary')
      googlehome.play(`http://${config.server.ip}:${config.port}/tmp.wav`, response => {
        console.info(response)
      })
    })
  })
}

const handleError = error => {
  googlehome.notify(error.message, () => {
    console.error(error)
  })
}

const createNotifyMessage = body => {
  let language = config.pokeapi.language
  let ftLanguage = config.pokeapi.flavorText.language
  let version = config.pokeapi.flavorText.version
  let names = body.names.filter(name => language === name.language.name)
  let genera = body.genera.filter(genus => language === genus.language.name)
  let flavorTexts = body.flavor_text_entries.filter(text => {
    return ftLanguage === text.language.name && version === text.version.name
  })
  let message =
    names[0].name + '' + genera[0].genus + '' + flavorTexts[0].flavor_text
  message = message.replace(/\s/g, '')
  return message
}

if (require.main === module) {
  main()
}

使用方法

curl編

ここでは実際に自身のGoogleHomeでポケモン図鑑の説明音声を再生する手順を紹介します。
まずは、ngrokを使用せずに、curlで音声を再生してみましょう。
構成で言うところの、②と③に該当します。

  • 事前準備として以下を実施しておきます。

    • GoogleHomeのIPアドレスを調べておく(GoogleHomeアプリから確認可能)
    • nodeを実行するマシンのIPを調べておく(GoogleHomeからアクセスできるIPであること)
    • VoiceTextのAPIサイトに登録してAPI Keyを発行する
  • コードの取得とnpmモジュールのinstallを実施します。

# コードの取得
git clone https://github.com/reireias/google-home-pokemon-dictionary.git
# install
npm install
# or
yarn install
  • configディレクトリ直下に以下の内容でlocal.yamlを作成します。
local.yaml
googlehome:
  ip: 'google_home_ip'
voice:
  key: 'your_voice_text_key'
server:
  ip: 'node_server_ip'

これで準備はできました。
node main.jsでサーバーを起動しましょう。
続いて、下記のコマンドでメッセージを再生してみます。

curl -X POST -H 'Content-Type:application/json' -d '{"name": "フシギダネ"}' http://node_server_ip:8080/pokemon

ngrok編

ngrokを導入し、nodeを実行しているサーバーにグローバルにアクセスできるドメインを取得することで、GoogleHome -> IFTTT -> nodeのパスが使えるようにします。

  • 公式サイトにしたがって、マシンにngrokを導入します
  • ユーザー登録を実施しない場合は、8時間で取得したドメインが使えなくなってしまうので、ユーザー登録を行います(試すだけの人はスキップしてOK)
    • 公式サイトからユーザー登録し、Authを選びAuth Tokenを取得します。
    • ngrok authtoken <取得したtoken> を実行することで、設定ファイルへtokenを記録します。
  • ngrok http 8080 で起動し、取得したドメインを控えておきます。

最後にIFTTTの設定を行います。
詳細は以前の記事でキャプチャ付きで紹介しているので、そちらが参考になります。

  • thenにはGoogleAssistantを設定し、任意のフレーズの後に$を指定する。
    • 以降では仮に「ポケモン図鑑」というフレーズを指定したと仮定する。
  • thatはWebhookを指定し、以下の設定とする。
    • 宛先は<ngrokで取得したドメイン>/pokemonとする。
    • メソッドはPOSTとする。
    • bodyは{"name": "{{TextField}}"}と指定する。

これで準備は完了です。
あなたのGoogleHomeに向かって「ポケモン図鑑 ○○○(好きなポケモンの名前)」と言ってみましょう。

7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6