ノビィからの返答を伺かに話させる仕組み その①

# はじめに
この記事は「[大垣Pythonハッカソン@ヒーローズ・リーグ 2019 by MA](https://mashupawards.connpass.com/event/146061/)」にて開発したシステム「[二次元執事](https://protopedia.net/prototype/cb79f8fa58b91d3af6c9c991f63962d3)」の技術的な解説をしていきます。Qiitaへの投稿は初めてなので分からない事だらけですが、頑張って書いていきます！

今回の開発チームの紹介をします。

**チーム名: 特攻野朗A**
朗の字が違うのは**仕様**です。(チーム名決める時に学生さんが間違えたのでそのまま採用！)

- 社会人 1人　**僕です(=ﾟωﾟ)ﾉ**
- 学生 3人

このようなチーム構成で今回のシステムを開発しました。
学生さんの内の１人がハッカソン経験者で、チームの雰囲気を非常に盛り上げてくれました。

# 伺か(うかがか)とは？
そもそもタイトルにある **伺か（うかがか）**とは何なのか？
その謎を明らかにすべく我々はアマゾンの奥地へ向かった。(違う)

[【Wikipedia】伺か](https://ja.wikipedia.org/wiki/%E4%BC%BA%E3%81%8B)
[【ニコニコ大百科】伺かとは？](https://dic.nicovideo.jp/a/%E4%BC%BA%E3%81%8B)

伺かとは**2000年初頭**からネット上に存在していた**デスクトップマスコット**です。
制御に関しては [さくらスクリプト](http://ssp.shillest.net/ukadoc/manual/list_sakura_script.html) という制御文字と読み上げ文章からなる専用の言語で行います。
![2zigensituzi (2).PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165208/99b2a930-6c57-3e1f-f6f4-d52f46a1baa3.png)
**↑ 今回のハッカソンにて登場した伺か「二次元執事」**

皆さんが今回のハッカソンで目撃したデスクトップマスコットこそが伺かと呼ばれるものです！


伺かについての用語等をインプットも兼ねて下記のリンク先にまとめているので、詳しく知りたい方は見て下さい！
[伺かマインドマップ](https://mm.tt/1328501936?t=KioMw5lZzf)

以降は皆さんに馴染みの少ない「伺か」という語をハッカソンで開発した「二次元執事」に統一します。
# 二次元執事の技術的なお話
プレゼンではざっくりとしか話せなかった箇所を解説していきたいと思います。
プレゼンで使った構成図では１か所だけやり取りが欠けていたので修正を加えました（ごめんなさい）
![talk2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165208/d3c5e91a-daa4-9603-2b78-9cefcd5cb06a.png)

## 1. 音声認識Webアプリケーションの解説
Angular製のWebアプリケーションで音声認識をしています。
UIは非常にシンプルです。画面下のボタンを押すと音声認識が開始されます。
<img width="250" alt="Screenshot_20191003-215205_1.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165208/3d4f68a8-c8cb-cec6-72e7-4490c8dd6e99.jpeg">

音声認識の機能に関しては外部のライブラリ等を使用せずに、Chromeで使用出来る [WebSpeechAPI](https://developer.mozilla.org/ja/docs/Web/API/Web_Speech_API) を使用しています。
使用方法は極めてシンプルです(説明に必要の無い import 等のコードは省略しています)

### 1-1. ソースコードの全容
音声認識のロジックを記述しているのは実質１ファイルです。

```typescript:MicrophoneComponent.component.ts
// TypeScriptでグローバルオブジェクトのwebkitSpeechRecognitionを動かせるようにする
declare const webkitSpeechRecognition;

export class MicrophoneComponent {
  recognition: any;

  constructor(private http: HttpClient, private sharedService: SharedService) {
    // WebSpeechAPIのインスタンスを生成後、recognitionフィールドに代入
    this.recognition = new webkitSpeechRecognition();
  }

  // ボタンを押した後に走るメソッドを実装
  async recordVoice() {

    // 言語設定を日本語に設定
    this.recognition.lang = 'ja';

    // 音声認識後に走る処理を設定(やりとり１の後に走る)
    this.recognition.onresult = (event) => {
      // nobyの設定(送信するテキストや人格設定など)
      const params = {
        // Nobyのキー
        appkey: 'XXXXXXXXXXXXXXXXXXXXXXX',
        // 下記の event.results[0][0].transcript に音声認識で解析された文章が入っている
        text: event.results[0][0].transcript,
        // 学習機能あり 1 なし 0
        study: '1',
        // 人格 :0:ノーマル」,「1:ツンデレ(女)」,「2:ツンデレ(男)」,「3:神」
        persona: '0',
        // 語尾
        ending: 'だね'
      };

      // NobyAPIに対してパラメタを指定してGetリクエスト(やりとり２)
      this.http.get('https://app.cotogoto.ai/webapi/noby.json', {params: params}).subscribe((data: any) => {
        const paramsSsp = {message: data.text, nobydata: data};

        // NobyAPIから受け取ったレスポンスをlocalhostのNode.jsのサーバにPostリクエスト(やりとり３)
        // localhostを外部へ公開する手段としてはServeoを採用(https://serveo.net/)
        this.http.post('https://hogehuga.serveo.net/api/readText', paramsSsp).subscribe(() => {
          console.log('success');
        });
      });
    };

    // 音声認識開始(やりとり１)
    this.recognition.start();
    // トーストの起動
    await this.sharedService.presentToast('マイクに話しかけて下さい');
  }
}
```

### 1-2. WebSpeechAPI の生成
コンストラクタで WebSpeechAPI のインスタンスを生成後にrecognitionフィールドに代入しています。

```typescript:MicrophoneComponent.component.ts

// TypeScriptでグローバルオブジェクトのwebkitSpeechRecognitionを動かせるようにする
declare const webkitSpeechRecognition;

export class MicrophoneComponent {
  recognition: any;

  constructor(private http: HttpClient, private sharedService: SharedService) {
    // WebSpeechAPIのインスタンスを生成後、recognitionフィールドに代入
    this.recognition = new webkitSpeechRecognition();
  }
}
```

### 1-3. ボタンが押された後に走るメソッドの実装
Angularのイベントバインディングを使用して、ボタンが押されたタイミングで
下記のメソッドが発火されるようになっています。(テンプレート側にちょっとした記述がありますが、今回は割愛)

```typescript:MicrophoneComponent.component.ts
  // ボタンを押した後に走るメソッドを実装
  async recordVoice() {
    // 言語設定を日本語に設定
    this.recognition.lang = 'ja';

    // 音声認識後に走る処理を設定(やりとり１の後に走る)
    this.recognition.onresult = (event) => {
      // nobyの設定(送信するテキストや人格設定など)
      const params = {
        // Nobyのキー
        appkey: 'XXXXXXXXXXXXXXXXXXXXXXX',
        // 下記の event.results[0][0].transcript に音声認識で解析された文章が入っている
        text: event.results[0][0].transcript,
        // 学習機能あり 1 なし 0
        study: '1',
        // 人格 :0:ノーマル」,「1:ツンデレ(女)」,「2:ツンデレ(男)」,「3:神」
        persona: '0',
        // 語尾
        ending: 'だね'
      };

      // NobyAPIに対してパラメタを指定してGetリクエスト(やりとり２)
      this.http.get('https://app.cotogoto.ai/webapi/noby.json', {params: params}).subscribe((data: any) => {
        const paramsSsp = {message: data.text, nobydata: data};

        // NobyAPIから受け取ったレスポンスをlocalhostのNode.jsのサーバにPostリクエスト(やりとり３)
        // localhostを外部へ公開する手段としてはServeoを採用(https://serveo.net/)
        this.http.post('https://hogehuga.serveo.net/api/readText', paramsSsp).subscribe(() => {
          console.log('success');
        });
      });
    };

    // 音声認識開始(やりとり１)
    this.recognition.start();
    // トーストの起動
    await this.sharedService.presentToast('マイクに話しかけて下さい');
  }
```

# ちょっと一休み...
この記事に全ての解説を載せてまとめて公開しようと思いましたが、書いてる人の貧弱なモチベーションが死にそうだったので
とりあえず今回はウェブアプリケーション側の解説だけに留めたいと思います😅
(アウトプットつよつよマンになりたい🙃)

次回は **ExpressAPIサーバ** と **二次元執事本体** の解説をしていきます！

# 最後に
惜しくも時間内に作品を最後まで作り切る事は出来ませんでしたが、いくつかの賞を頂けたのは
良きチームメンバーや、粘り強くサポートして頂いた企業の方のお陰だと思っております。

運営の方々も２日間お世話になりました！
また来年もよろしくお願いいたします。