VOICEROID2(紲星あかり)をプログラムから動かしてみる

#やりたいこと
コマンドラインとかからVOICEROID2に話してほしい言葉を与えて話してくれる仕組みはない（手動入力しないと話してくれないのは頂けないよね・・・）
　　　　　　　↓
何かのアクションがあった場合に自動で話してほしい
例）Twitterで返信されたら内容を話してくれる
　　新刊の発売があったら声で教えてほしい
#やったこと
例に挙げた「Twitterで返信されたら内容を話してくれる」をやってみた。
![フロー.png](https://qiita-image-store.s3.amazonaws.com/0/230806/afb74afb-aa2d-e310-a475-fbca40d7cea6.png)
図のようにTwitter側からのメンションのデータ検知、送信をIFTTTの連携で処理し、BeebottleにPOSTする。POSTデータはPythonで監視し、POSTデータがあったらVOICEROID2を操作し、話させる処理を行う。

#IFTTTの連携側～BeebotteへのMQTTPublishとPythonでの監視
ググったら出てきます。
<a href ='https://qiita.com/minatomirai21/items/4c4e777b43ede1e42900'>IFTTT から Raspberry Pi に指示を出す(@minatomirai21氏)</a>
<a href = 'https://qiita.com/msquare33/items/9f0312585bb4707c686b'>IFTTTとBeebotteを使ってGoogleHomeからRaspberryPiを操作する(@msquare33氏)</a>
などが参考になりました。

#PythonからVOICEROID2を操作する方法？
初めはVOICEROID2のウィンドウハンドルを取得し、その子ウィンドウハンドルにあるだろうテキストエリアの箇所にテキストを入れようと思っていました。
（参考：<a href='https://qiita.com/spicapaula/items/00883525fa0f08294375'>すまーときりたん (Google Homeときりたんが家電を制御するよ！)(@spicapaula氏)</a>）
ですが、VOICEROID EX+では上手くいくらしいですが、VOICEROID2では上手くいきません。
というのも子ウィンドウハンドルが見つかりません・・・(spy++でみてもなにもない・・・)
![spy++.png](https://qiita-image-store.s3.amazonaws.com/0/230806/7e394216-b60a-6806-8b34-6a088c94a8ed.png)

なので別の方法を模索。
UI Automationならできるという記事がありました。
（<a href='https://hgotoh.jp/wiki/doku.php/documents/voiceroid/voiceroid-008'>VOICEROID2をコマンドラインから操作するための簡単なサンプルソース - 努力したWiki</a>）
こちらの記事はC#なのですが、今回はPythonでやります。

#PythonでUI Automation
ここからコードのお話です。~~（ここが一番時間かかりました）~~
PythonでのUI Automationの記事はほとんどないので手探り状態で進めます。幸いUI Automationできそうなモジュールが見つかったので利用します。
## pywinautoの導入
<a href='http://pywinauto.readthedocs.io/en/uia/contents.html'>pywinautoのサイト</a>
pipで入れます
``` 
pip3 install pywinauto
```
## VOICEROID2を操作
イメージはウィンドウハンドルの子要素を探すのと同じ感じで
デスクトップのエレメント→VOICEROID2のエレメント(デスクトップの子エレメント)→テキストエリアのエレメント（VOICEROID2の子エレメント）　の順でエレメントを取得して操作していきます

``` python:VoiceRoid2_Automation.py
# -*- coding: utf-8 -*-
import pywinauto

def search_child_byclassname(class_name, uiaElementInfo, target_all = False):
    target = []
    # 全ての子要素検索
    for childElement in uiaElementInfo.children():
        # ClassNameの一致確認
        if childElement.class_name == class_name:
            if target_all == False:
                return childElement
            else:
                target.append(childElement)
    if target_all == False:
        # 無かったらFalse
        return False
    else:
        return target


def search_child_byname(name, uiaElementInfo):
    # 全ての子要素検索
    for childElement in uiaElementInfo.children():
        # Nameの一致確認
        if childElement.name == name:
            return childElement
    # 無かったらFalse
    return False

def talkVOICEROID2(speakPhrase):
    # デスクトップのエレメント
    parentUIAElement = pywinauto.uia_element_info.UIAElementInfo()
    # voiceroidを捜索する
    voiceroid2 = search_child_byname("VOICEROID2",parentUIAElement)
    # *がついている場合
    if voiceroid2 == False:
        voiceroid2 = search_child_byname("VOICEROID2*",parentUIAElement)

    # テキスト要素のElementInfoを取得
    TextEditViewEle = search_child_byclassname("TextEditView",voiceroid2)
    textBoxEle      = search_child_byclassname("TextBox",TextEditViewEle)

    # コントロール取得
    textBoxEditControl = pywinauto.controls.uia_controls.EditWrapper(textBoxEle)

    # テキスト登録
    textBoxEditControl.set_edit_text(speakPhrase)


    # ボタン取得
    buttonsEle = search_child_byclassname("Button",TextEditViewEle,target_all = True)
    # 再生ボタンを探す
    playButtonEle = ""
    for buttonEle in buttonsEle:
        # テキストブロックを捜索
        textBlockEle = search_child_byclassname("TextBlock",buttonEle)
        if textBlockEle.name == "再生":
            playButtonEle = buttonEle
            break
        
    # ボタンコントロール取得
    playButtonControl = pywinauto.controls.uia_controls.ButtonWrapper(playButtonEle)

    # 再生ボタン押下
    playButtonControl.click()

```
実際の動きは`talkVOICEROID2`関数を見てもらえればわかります。
子要素を辿って該当エレメントを取得し、操作します。（エレメントがわかりづらいのでInspectツールを使うのをお勧めします）
テキストのエレメント取得→話したい言葉を入れる→再生ボタンエレメント取得→ボタン押下　で処理します。

これで、任意の言葉を話してくれるようになったので、Beebotteの監視と連携させてOKです。

#VOICEROID2とbeebotte監視の連携
beebotteのメッセージ受信時の処理にVOICEROID2の処理を入れればOKです。

```python:Twitter_Notification.py
import paho.mqtt.client as mqtt
import json
from VoiceRoid2_Automate import talkVOICEROID2
import time


TOKEN = "XXXXXXXXXXXXXXXXXXXX"
HOSTNAME = "mqtt.beebotte.com"
PORT = XXXX
TOPIC = "XXXX/XXXX"
CACERT = "mqtt.beebotte.com.pem"

def on_connect(client, userdata, flags, respons_code):
    client.subscribe(TOPIC)

def on_message(client, userdata, msg):
    print (json.loads(msg.payload.decode("utf-8"))["data"])
    data = json.loads(msg.payload.decode("utf-8"))["data"]
    for i in data:
        talkPhrase = "{0}さんからメンションです。\n{1}".format(i["from"],i["text"])
        talkVOICEROID2(talkPhrase)
        print ('sleep:{0}s'.format(len(talkPhrase)/5))
        time.sleep(len(talkPhrase)/5)
        print('sleep_end')


client = mqtt.Client()
client.username_pw_set("token:%s"%TOKEN)
client.on_connect = on_connect
client.on_message = on_message
client.tls_set(CACERT)
client.connect(HOSTNAME, port=PORT, keepalive=60)
client.loop_forever()
```
<blockquote class="twitter-tweet" data-lang="ja"><p lang="und" dir="ltr"><a href="https://t.co/dYmNM1nTni">pic.twitter.com/dYmNM1nTni</a></p>&mdash; てあら.php (@Teara_exe) <a href="https://twitter.com/Teara_exe/status/977958217304064001?ref_src=twsrc%5Etfw">2018年3月25日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

言葉を発するのに時間がかかり、発話中に動かそうとすると異常終了してしまうので、ある程度ウェイトする必要があります・・・

#最後に
じつはこれにはいくつか問題があります

- 改行に対応できない(webhooksとBeebotteの連携時の問題)
- 数時間後にしか話してくれない(TwitterとIFTTTの連携の問題)
- VOICEROID2のウィンドウが必ずアクティブになる（コードの問題）

上二つはPCからTwitterを見れば解決するので、どうにかしたいなあと。
最後のはちょっと解決策がわからないかも・・・って感じです。。。
何かご存知の方いらっしゃったら教えていただければ嬉しいです。。。