bitFlyerのチャット音声自動読み上げシステムを作るやり方

取引所でデイトレードしているとチャットが目につくことがあります。

注視するほど内容は濃くはないのですが、
作業しながら自動音声読み上げできたら便利だなーと思ったので、
今回はそれを作ってみようと思います。

 

システムの開発の大まかな流れ

流れとしては、

取引所APIのpublicメソッドでチャットを取得(PHP)

取得したチャットをAJAXでリアルタイム表示(javascript&HTML)

取得した内容を音声読み上げAPIに投げる

返ってきた音声ファイルを再帰的に読み上げる

感じです。

目標はシンプルですが、実装するとなると複合的な感じがします。
必要な知識は、
API処理、PHP、Javascript(AJAX)あたりです。

音声処理とかめんどくさいことは今のご時世しなくていいようにできているので、
非同期処理とAPIができればだいたいOKです。

はじめはコインチェックでやろうとしたのですが、
どうやらチャットのAPIメソッドはないようです。(あったような気がしたんだけどな)

なので、今回はbitFlyerのチャットを引っ張ってきます。

チャットの取得ができれば2chのときのようにスクレイピングやテキストマイニングができるので、
将来的にはチャット内容に応じたトレードなんかもできると思います。

 

実装方法

bitFlyerのチャットメソッドは
https://lightning.bitflyer.jp/docs?lang=ja#チャット
にドキュメントがあります。

メソッドはPublicなので、認証とかハッシュとかは必要ありません。

POLONIEXのヒストリーデータ取得メソッドのようにパラメータに時刻を指定することで、
それ以降のチャットを返します。

リクエスト

https://api.bitflyer.jp/v1/getchats?from_date=2017-12-10T08:02:15.253

 

レスポンス

[{"nickname":"横浜海月姫","message":"いま、いるのはみな勝ち組。","date":"2017-12-10T08:02:15.253"},{"nickname":"ミドリムシ","message":"虎ノ門にビルほしい","date":"2017-12-10T08:02:15.443"},{"nickname":"実穂ノア♚world🍀LOVE💑","message":"いてら","date":"2017-12-10T08:02:18.413"},{"nickname":"User1DBA371","message":"男なのに、車いらないとか、あかんたれ","date":"2017-12-10T08:02:22.077"},{"nickname":"ヤギ","message":"こんばんは","date":"2017-12-10T08:02:22.233"},{"nickname":"グーフィー","message":"来年稼いでたら、一夫多妻目指す","date":"2017-12-10T08:02:22.507"},{"nickname":"pere","message":"車もトップギアみて好きになったから欲しい・・・","date":"2017-12-10T08:02:23.123"},{"nickname":"SUGUHA@batty1209","message":"レンタルで高級車はますますかっこ悪くない?","date":"2017-12-10T08:02:23.33"},{"nickname":"ヤギ","message":"やってますか","date":"2017-12-10T08:02:26.92"},{"nickname":"にゃんたコイン","message":"おらべこ三頭買うだ","date":"2017-12-10T08:02:27.2"},{"nickname":"マカフィマカフィー","message":"マンション建てたい","date":"2017-12-10T08:02:27.463"},{"nickname":"SUGUHA@batty1209","message":"わ ナンバーだしw","date":"2017-12-10T08:02:28.323"},{"nickname":"勃起マン","message":"ちんこ","date":"2017-12-10T08:02:32.547"},{"nickname":"大正の煽り王(92)【ノーポジ】","message":"草コインにばらまくのがいいで。9割詐欺だけど。笑えるでwww","date":"2017-12-10T08:02:33.973"},{"nickname":"UserEC68D68","message":"もな2000円に戻ると思う?","date":"2017-12-10T08:02:35.563"},{"nickname":"さい","message":"15万乖離で絶望に変わったら織だな","date":"2017-12-10T08:02:35.777"}]

(publicでだれでも閲覧できる状態なのでプライバシーとかはもともとありません。)

いろいろ試してみて分かったのですが、このパラメータ指定には癖があります。

ドキュメントでは、

クエリパラメータ

from_date: 指定された日付以降の発言を取得します。省略された場合は、5 日前からの発言を返します。

と書いてあるだけですが、

実は、この指定する時間に一致する発言がないと、うまくレスポンスが返ってこないことがあります。
(というかほぼ返ってきません。)

そのため、現在時刻をこのフォーマットに成形してパラメータで渡しても上手く表示されない結果になります。(日付だけだと上手くいく)

一回目のリクエストは仕方がないとして、2回目以降でも同じように大量にレスポンスが返ってくると処理が重くなるので、2回目以降はセッションに保存した時刻を使います。

<?php
session_start();

	if( isset($_SESSION["date"]) ) $date_now = $_SESSION["date"]; 
	else $date_now = date("Y-m-d");

・・・ここでいろいろ処理

	$_SESSION["date"] = $r_json[$arraysize-10]["date"];
	echo $r_json[$arraysize-10]["date"];
?>

 

またAPIを叩くときはfile_get_contentsを使うと汎用性がないので、
例によってCURLを使ってください。(file_get_contentsでは取得できないことがよくあります)

$URL = "https://api.bitflyer.jp/v1/getchats?from_date=".$date_now;	
	$curl = curl_init();
	curl_setopt($curl,CURLOPT_RETURNTRANSFER,true);
	curl_setopt($curl,CURLOPT_CUSTOMREQUEST, "GET");
	curl_setopt($curl,CURLOPT_URL, $URL);
	$res =  curl_exec($curl);
	curl_close($curl);

 

あとはこれをjsonデコードして連想配列に入れれば、チャットの内容だけを取得できます。
2chよりはるかに簡単ですね。

$r_json = json_decode($res,true);

 

注意しなければならないのは配列の向きが時間軸と同じ方向になっているため、
最新のチャット内容を取得するには配列のサイズを取得する必要があります。

$arraysize = count($r_json);	
	for($i=$arraysize;$arraysize-$i<10;$i--)
	{
		if( mb_strlen($r_json[$i]["message"],"UTF-8") == mb_strwidth($r_json[$i]["message"],"UTF-8") ) continue;
		if( strpos($subject,'買)') === false )
		{
			echo $r_json[$i]["message"]."<br>";
		}
	}

 

あとは、任意で10件程度ループして出力すれば出来上がりです。

bitFlyerのチャットには建玉情報を流す機能がありますが、他人の建玉情報はいらないので、
オーダーの自動発言の場合には非表示にしています。(買いオーダーだけ)

また、半角文字しか入っていない場合も非表示にしています。

ここら辺は適宜追加してみてください。

bandicam 2017-12-10 17-52-45-645

<?php
session_start();

	if( isset($_SESSION["date"]) ) $date_now = $_SESSION["date"]; 
	else $date_now = date("Y-m-d");
	
	echo $_SESSION["date"];//$date_now;
	echo "<br><br>";
	
	$URL = "https://api.bitflyer.jp/v1/getchats?from_date=".$date_now;	
	$curl = curl_init();
	curl_setopt($curl,CURLOPT_RETURNTRANSFER,true);
	curl_setopt($curl,CURLOPT_CUSTOMREQUEST, "GET");
	curl_setopt($curl,CURLOPT_URL, $URL);
	$res =  curl_exec($curl);
	curl_close($curl);
	
	$r_json = json_decode($res,true);
	$arraysize = count($r_json);	
	for($i=$arraysize;$arraysize-$i<10;$i--)
	{
		if( mb_strlen($r_json[$i]["message"],"UTF-8") == mb_strwidth($r_json[$i]["message"],"UTF-8") ) continue;
		if( strpos($subject,'買)') === false )
		{
			echo $r_json[$i]["message"]."<br>";
		}
	}
	$_SESSION["date"] = $r_json[$arraysize-10]["date"];
	echo $r_json[$arraysize-10]["date"];
?>

リアルタイムで更新させる

さて、bitFlyerのAPIから最新のチャットを取得するところまでやったので、
次はそれをリアルタイムで更新させるところまでやります。

WEBサイト制作をちょっとでも触ったお分かりだと思いますが、
基本的にWEBページというものは静的なもので、動かしたりするには別途記述が必要です。

動かすにはJavasciprtによる記述が必要ですが、今回はその中でもAJAXというものを使います。
AJAXというのはリアルタイムで非同期処理をするための処理です。

まずjqueryを読み込む必要があります。

<script src="https://code.jquery.com/jquery-3.0.0.min.js"></script>

 

あとはシンプルに処理を書きます。

<script>
      $(function() {
        $('#button').click(
          function() {
            $.ajax({
              url: 'ajax_call.php',
              dataType: 'html',
              success: function(data) {
                $('#text').html(data);
              }
             });
            }
          }
        );
      });
    </script>

 

これはボタンを押すとajax_call.phpを非同期処理で呼び出します。
このphpファイルの中に前回のbitFlyerのチャット呼び出しの記述をすればいいわけです。

bandicam 2017-12-10 18-00-23-512

ボタンを押すとページが更新されることなくチャットが表示されます。

しかし、これではボタンを一回押しただけで終わってしまい、
最新の情報を取得するにはボタンを連打する必要があります。

今回は自動的に更新&表示してほしいので、
javascriptの関数にループ処理を付け加えます。

javascriptにはスリープ関数がないので、今回はただのループです。
javascriptでむりやりスリープしても良いのですが、次回以降にphpと組み合わせるときにphp側でスリープを入れます。

<script>
      $(function() {
        $('#button').click(
          function() {
           while(true){

            $.ajax({
              url: 'ajax_call.php',
              dataType: 'html',
              success: function(data) {
                $('#text').html(data);
              },
              error: function(data) {
                //alert('error');
              }
             });
            }
          }
        );
      });
    </script>

 

ezgif-4-acf6b67ca1

これで自動的に更新されるようになりました。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <script src="https://code.jquery.com/jquery-3.0.0.min.js"></script>
    <script>
      $(function() {
        $('#button').click(
          function() {
            const time1 = new Date();
          	while(true){
          	const time2 = new Date();
          	if( time2 - time1 > 5000 ){
          		break;
          	}
            $.ajax({
              url: 'ajax_call.php',
              dataType: 'html',
              success: function(data) {
                $('#text').html(data);
              },
              error: function(data) {
                //alert('error');
              }
             });
            }
          }
        );
      });
    </script>
  </head>
  <body>
    
    <div id="text"></div>
    <br>
    <input type="button" id="button" value="START"/>
  </body>
</html>

音声読み上げAPIに読み上げさせる

最近は便利なものでWEB上でAPIを使って音声読み上げができるんですよね。
一昔前はソフトークというフリーソフト(いわゆる ”ゆっくり”)が有名で、
もし読み上げを自動化するのであれば、WEBとオフラインのソフトークをマクロでつないでやる必要がありました。

今はWEB APIがあるので、それを有難く使います。
https://cloud.voicetext.jp/webapi

配布、商用利用する場合は金を払えということなので、それぞれ各自APIを取得してみてください。

メールアドレスを登録するとAPIキーが送られてくるので、それを使います。

$post_array = array("text" => "からのー 
									でも普通はここもっと上がるんだけど売りが強いかも 
									さいこおおおおおおおおおおおおおおお 
									これ今日40回見た 
									80%! 
									昨日25ストローク確認があったから 
									上げてきましたね 
									日足、黒三兵?", 
						"speaker" => "takeru", 
						"emotion" => "happiness", 
						"pitch" => "100", 
						"speed" => "150", 
						"emotion_level" => "2");

 

今回はとりあえずAPIの実行がメインなので、送るテキストは固定にします。

いろいろ話者を選択できるので、楽しいですね。
日本語 show(男性)
日本語 haruka(女性)
日本語 hikari(女性)
日本語 takeru(男性)
日本語 santa(サンタ)
日本語 bear(凶暴なクマ)

とりあえず今回はtakeruにしました。
凶暴なクマが気になったのですが、こんな感じでした。

https://fxantenna.com/kumavoice.wav
(意外とまとも?)

このAPIを叩くと指定した音声ファイルが保存できるので、とりあえず自サーバーに保存します。

<?php
	$ch = curl_init();
	curl_setopt($ch, CURLOPT_URL, "https://api.voicetext.jp/v1/tts");
	curl_setopt($ch, CURLOPT_HEADER, false);
	curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);
	$key = "キー:";
	curl_setopt($ch,CURLOPT_HTTPAUTH,CURLAUTH_BASIC);
	curl_setopt($ch,CURLOPT_USERPWD,$key);
	$post_array = array("text" => "からのー 
									でも普通はここもっと上がるんだけど売りが強いかも 
									さいこおおおおおおおおおおおおおおお 
									これ今日40回見た 
									80%! 
									昨日25ストローク確認があったから 
									上げてきましたね 
									日足、黒三兵?", 
						"speaker" => "takeru", 
						"emotion" => "happiness", 
						"pitch" => "100", 
						"speed" => "150", 
						"emotion_level" => "2");
	$postdata = "";
	foreach ($post_array as $key => $val) 
	{
	    $postdata.= urlencode($key) . '=' . urlencode($val) . '&';
	}
	curl_setopt($ch, CURLOPT_POST, true);
	curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata);
	$output = curl_exec($ch);
	curl_close($ch);

	$fp = fopen("chattext.wav", "w");
	fwrite($fp, $output);
	fclose($fp);
?>

 

ちなみにtakeruの場合、こんな感じです。
https://fxantenna.com/chattext.wav

もっと残念な感じだと想像していましたが、最近の読み上げAIはすごいですね。
これなら長い間聞いていても疲れなさそうです。

APIも特に癖はないので、すんなりいけます。

エラーになることもありますが、レスポンスのエラーのステータスコードは変更される可能性があるので、プログラムに組み込まない方が良さそうです。

ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
レスポンスボディの JSON では以下の形式でエラーメッセージを返します。 このエラーメッセージの内容は予告無く変更する可能性があるため、 プログラムから利用する用途には適しません。
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー

また、あまり長い文章にするとエラーになるので、体感としては10から20までのコメントにしておいた方が安定します。

しかしこれ、デバッグのときに何度も何度も同じ文章を聞く羽目になるため、軽く洗脳されます。

 

音声読み上げに特化する

 

これまでの内容で
・取引所のチャット取得
・音声読み上げAPIで音声ファイルを取得
・AJAXでリアルタイム更新
ができたわけですが、

ここから悪戦苦闘がありました。

まず、ストレートにリアルタイムで取得した音声ファイルをAJAXで再生しようとしたのですが、
どうしてもブラウザがキャッシュを優先してしまい、更新された音声ファイルではなく古い音声ファイルを繰り返し読み上げてしまいました。

htmlのメタタグでキャッシュを不可にする記述もあるのですが、他のブログさんで紹介しているように、まったく効果を発揮しません。
また、JavascriptでAudioオブジェクトのload()メソッドとかもあるのですが、これらも上手くいきませんでした。

さらにオブジェクトを一度削除して新しくインスタンスを生成してもダメでした。

しかも音声ファイルの再生ってブラウザとそのバージョンによって、再生できたりできなかったりするので、もし製品化とかだったらプログラマさん大変ですね。

という訳で、
当初の仕様から方針をかなり変えました。

すべてWEBアプリで完結できればよかったのですが、上記もろもろの理由で現在の仕様では困難なため、
クライアント側から強制的にページそのものを更新する仕様にします。

ページそのものが更新されればブラウザはキャッシュがあっても読み込んでくれるため、新しい音声ファイルを呼び出してくれます。

この場合、ページそのものを更新してしまうので、リアルタイムなチャットテキストの更新は失われてしまいますが、AJAXを使う必要もありません。(何のためのAJAXだったのか…)

そもそもリアルタイムなテキストの更新が目的なら、取引所のページを開けば良いので、今回に限っては音声の読み上げに特化したいと思います。

と言っても、これまでにやった内容から難易度が一気に下がるので、今までの内容をつなぎ合わせるだけでできちゃいます。

<!DOCTYPE html>
<html>
 	<head>
    	<meta charset="utf-8">
		<meta http-equiv="Pragma" content="no-cache">
		<meta http-equiv="Cache-Control" content="no-cache">
	</head>
	<body>
<?php
session_start();
	if( isset($_SESSION["date"]) ) $date_now = $_SESSION["date"]; 
	else $date_now = date("Y-m-d");
	
	$URL = "https://api.bitflyer.jp/v1/getchats?from_date=".$date_now;	
	$curl = curl_init();
	curl_setopt($curl,CURLOPT_RETURNTRANSFER,true);
	curl_setopt($curl,CURLOPT_CUSTOMREQUEST, "GET");
	curl_setopt($curl,CURLOPT_URL, $URL);
	$res =  curl_exec($curl);
	curl_close($curl);
	
	$r_json = json_decode($res,true);
	$arraysize = count($r_json);	
	$text_for_voice = "";
	$textnumber = 10;
	for($i=$arraysize;$arraysize-$i<$textnumber;$i--)
	{
		if( mb_strlen($r_json[$i]["message"],"UTF-8") == mb_strwidth($r_json[$i]["message"],"UTF-8") ) continue;
		if( strpos($r_json[$i]["message"],'http') == true ) continue;
		echo $r_json[$i]["message"]." <br>";
		$text_for_voice = $text_for_voice.$r_json[$i]["message"]."  ";
	}

	echo "<br>".$r_json[$arraysize-$textnumber]["date"];
	
	$text_for_voice = str_replace("w","",$text_for_voice);
	$text_for_voice = str_replace("w","",$text_for_voice);
	
	$ch = curl_init();
	curl_setopt($ch, CURLOPT_URL, "https://api.voicetext.jp/v1/tts");
	curl_setopt($ch, CURLOPT_HEADER, false);
	curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);
	$key = "キー:";
	curl_setopt($ch,CURLOPT_HTTPAUTH,CURLAUTH_BASIC);
	curl_setopt($ch,CURLOPT_USERPWD,$key);
	$post_array = array("text" => $text_for_voice, 
						"speaker" => "takeru", 
						"emotion" => "happiness", 
						"pitch" => "100", 
						"speed" => "110", 
						"emotion_level" => "2");
	$postdata = "";
	foreach ($post_array as $key => $val) 
	{
	    $postdata.= urlencode($key) . '=' . urlencode($val) . '&';
	}
	curl_setopt($ch, CURLOPT_POST, true);
	curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata);
	$output = curl_exec($ch);
	curl_close($ch);
	
	
	$fp = fopen("chattext.wav", "w");
	fwrite($fp, $output);
	fclose($fp);
?>

	<bgsound src="chattext.wav" loop="3" balance="0" volume="0">
		
	</body>
</html>

 

あとはこいつを適当なPHPサーバーに上げて何度も呼び出してあげればOKです。(ゴリ押しー)

呼び出しは何でもいいですが、下記はUWSCのコードです。

IE = CREATEOLEOBJ("InternetExplorer.Application")
IE.Navigate("URL")

WHILE true
   IE = GETACTIVEOLEOBJ("InternetExplorer.Application","")
   IE.Visible = True
   
   IE.refresh
   SLEEP(10)
Wend

 

今まで再帰的にページ内容を更新するときには、IE.Navigateを使っていたのですが、ループの中でこれを使っても音声ファイルが更新されなかったので、refreshメソッドにすれば更新されました。

navigateで同じページに推移した場合とrefreshで更新した場合は、読み込みが微妙に違うんですね

たまにIEオブジェクトの取得に失敗することがあるので、try文でフェイルセイフをかけると安定すると思います。

読み上げのスピードよりもテキストが更新されるスピードの方が早いので、読み上げが途中でも次のテキストに移行する仕様にしました。

以下は稼働している様子を録画したものです。

https://fxantenna.com/chatvoice.html

音量にムラがありますが、これは録画ソフトのせいなので、実際に使う際にはムラはありません。
実ソフトはここに上げてしまうと二次配布になってしまい、音声読み上げAPIの運営に怒られるので、各自キーを入れ替えて実装してください。

これで画面上は他の作業をしながら、もしレートに何かあった場合にはチャット民の悲鳴や歓喜を察知することができます。(ていうか作業が進まない)

bitFlyerのチャットはビットコインに関するコメントが多いですが、アルトコインに関する情報は比較的あんまり流れません。

アルトコインガチホ勢の身としてはコインチェックのチャットの方が聞きたいのですが、コインチェックはチャットAPIがないので、ページからむりやり抽出してみたいですね。

しかし、コインチェックのスマホアプリでチャットが見れるということは、サーバーとアプリの橋渡しはWEB APIで行っているはずですが、それをドキュメントで公開していないということは、何かしらの意図があってAPIを公開していないのかもしれません。

Message

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA


 

上に戻る