まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:bot

(今回のエントリはこの続編的な内容になっています)
LINE Bot を IBM Bluemix で作る


IBM Bluemix のサードパーティ製サービスの中に "Statica" というものがあります:
2016041400


このサービスはサーバー間プロクシーを実現するものです。Bluemix 上のランタイムから Bluemix 内外の API を呼び出す際の、リクエスト元 IP アドレスを隠して固定の IP アドレスにすることができるようになります。

例えば Bluemix 上のランタイム IP アドレスをリクエスト先サーバーのホワイトリストにあらかじめ登録しておかないと利用できないような API がある場合、Bluemix ランタイムの IP アドレスは再ステージ毎に変わってしまい、かつ変わった後の IP アドレスを調べるのが面倒でした(参照)。そんな場合にこのサービスを使うことでランタイムサーバーの IP アドレスが固定化されているかのように振る舞うことができるようになります。

実はこのサービスを使うと、LINE Bot API を作る際に非常に便利です。このページで紹介したように LINE Bot API でもコール元の IP アドレスをホワイトリスト登録しておく必要があって非常に面倒だったのですが、この Statica サービスを併用することで解決でき、再ステージング毎のホワイトリスト更新が不要になります。

なお、Statica は有償のサービスですが、IBM Bluemix の Starter プランを選択することで月 250 リクエスト&帯域 100MB までは無料でお使いいただくことが可能です。お試しになる場合はこちらをご利用ください:
2016041406


実際に LINE Bot API で試してみた様子がこちらです。まず Statica サービスをランタイムに追加しました。サービスの「資格情報の表示」をクリックしてクレデンシャル情報を確認します:
2016041401


このような画面が表示されます。モザイクのかかっている "STATICA_URL" の値がプロクシー URL です。この値をメモリしておくか、または実行時に動的に取り出して使うようにします:
2016041402


Statica サービスをクリックすると、作成した Statica サービスインスタンスの管理ダッシュボード画面にアクセスするためのリンクが現れます。これをクリックしてダッシュボードを開きます:
2016041403


Statica のダッシュボード画面が表示されました。この画面内の "Your Static IPs are X.X.X.X and Y.Y.Y.Y" と書かれている部分の X.X.X.X と Y.Y.Y.Y が固定に見せかける IP アドレスです:
2016041404


この2つの IP アドレスを LINE developers サイトのホワイトリストに登録します:
2016041405


後は実際にコールバック API を実行する際に、この Statica サービスインスタンスをプロクシーとして利用すればいいだけです。例えばこのようなコードにしました。変更前と異なる部分を赤く記しておきます:
<?php

$json_string = file_get_contents( 'php://input' );
$jsonObj = json_decode( $json_string );
$results = $jsonObj->{"result"};
$cnt = count($results);
for( $i = 0; $i < $cnt; $i ++ ){
  $result = $results[$i];
  $to = $result->{"content"}->{"from"};


  $cur = "USDJPY";
  $_cur = $result->{"content"}->{"text"};
  if( $_cur ){
    $cur = $_cur;
  }
  $cur = strtoupper( $cur );

  $fx_url = "http://fx.mybluemix.net/";
  $text = file_get_contents( $fx_url );
  $json = json_decode( $text );
  $datetime = $json->datetime;
  
  $rate = $json->rate;
  $r = "(" . $datetime . ")" . $cur . ":" . $rate->{$cur};

  $response_format_text = ['contentType'=>1,"toType"=>1,"text"=>$r];
  $post_data = ["to"=>[$to],"toChannel"=>"1383378250","eventType"=>"138311608800106203","content"=>$response_format_text];


  //. statica をチェック
  $proxy_url = '';
  $proxy_auth = '';
  if( getenv( 'VCAP_SERVICES' ) ){
    $vcap = json_decode( getenv( 'VCAP_SERVICES' ), true );
  
    $credentials1 = $vcap['statica'][0]['credentials'];
    if( $credentials1 != NULL ){
      $statica_url = $credentials1['STATICA_URL'];
      list($v1,$v2) = split( '@', $statica_url );
      list($v3,$v4) = split( '//', $v1 );
      $proxy_url = $v3 . '//' . $v2;
      $proxy_auth = $v4;
    }
  }

  $ch = curl_init("https://trialbot-api.line.me/v1/events");
  curl_setopt($ch, CURLOPT_POST, true);
  curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post_data));
  curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    'Content-Type: application/json; charser=UTF-8',
    'X-Line-ChannelID: (Channel ID)',
    'X-Line-ChannelSecret: (Channel Secret)',
    'X-Line-Trusted-User-With-ACL: (MID)'
    ));
  if( $proxy_url && $proxy_auth ){
    curl_setopt( $ch, CURLOPT_PROXY, $proxy_url );
    curl_setopt( $ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC );
    curl_setopt( $ch, CURLOPT_PROXYUSERPWD, $proxy_auth );
  }
  $res = curl_exec($ch);
  curl_close($ch);
}
?>
↑Statica サービスが使われている場合のみプロクシー指定して実行するようなコードにしています


このコールバック PHP ファイルをデプロイすれば動きますし、(前回苦労したような部分が消えるという意味でも) Bluemix 上での LINE Bot 開発がより簡単になると思われます。


4月7日に LINE が LINE トークを使った bot 開発用のアカウント "BOT API Trial Account" を先着1万名で提供する、という発表がありました:
http://www.itmedia.co.jp/news/articles/1604/07/news134.html


このニュースはあっという間に広まり、その作り方や実際に作ったボットが早くも多く公開されています。これまでは LINE ビジネスコネクトを通したビジネスアカウントがないと作れなかった LINE のボットが誰でも作れるようになりました。今回の発表で提供されている機能では作ったボットは 50 名のユーザーに対してのみ公開可能(ボットは 50 人までとしか友達になれない)という制限は付きますが、仲間内で使うぶんには充分だし、今後この制約が拡張されることがあればより便利に使えると思っています。 ちなみに昨日友人が試した限りでは、まだアカウントの申し込みが可能でした。

自分も早速 Bot を作って動かしてみました。自分の場合は IBM Bluemix を使って Bot を動かしています。Bot の作り方そのものは Qiita などに数多くの投稿がされているようなので、そちらを参照ください。以下は IBM Bluemix を使って Bot を動かす場合のコツや注意点を中心にまとめたものです。



(1) Bluemix のランタイム作成

まず Bluemix 上に Bot として動くことになるウェブアプリケーションのランタイムを作成します。ちなみに Bluemix のランタイムは標準で SSL が動き、Let's Encrypt 問題のような「この SSL 証明書はダメ」のような現象はおきていません。要するに Bluemix でランタイムを作って動かせばあまり深く考えなくても LINE Bot で使える、ということです。

Bluemix に用意されたランタイムはどの種類を使っても大丈夫だと思います。自分は PHP を使ってコールバック部分を作るつもりなので PHP にしました。Bluemix のサービスは必要に応じて追加してください(なくても動きます):
2016041304
 ↑dotnsf-line.mybluemix.net で PHP サーバーを作成


(2) BOT API Trial Account 作成

次に LINE BUSINESS CENTER へ行き、BOT API Trial Account を作成します:
2016041301


で、Bot を新規に作成して登録します。Channel ID と Channel Secret、MID は後で使うことになるのでメモしておきましょう。App icon は Bot が実際に LINE トーク上に現れる時のアイコンになるので、何か入れておいたほうが面白いと思います:
2016041302


重要な要素が Callback URL です。LINE トーク上でこの Bot に話しかけると、ここで指定した URL が呼ばれて実行されます。ここに指定できる URL は SSL が前提で、かつポート番号 443 を明示指定する必要があります。今回は上記で作成した Bluemix 上のランタイムに callback.php(この下で作ります)をデプロイして動かすので、Callback URL としては https://dotnsf-line.mybluemix.net:443/callback.php という指定になります:
2016041303

White List はこの後で指定します。


(3) Bot 作成&デプロイ

コールバックで動くことになる Bot アプリケーションを作ってデプロイします。ここは Bluemix だから特別に・・・という部分ではありません。自分はこの API を使って、呼びかけた通過ペア(例えば "USDJPY" とか "EURJPY" とか)に対してリアルタイムの為替価格を表示する、という Bot にしてみました:
2016041301
 ↑実際に動いている様子。問いかけた通貨の現在価格を返答してくれます。


ソースコードはこんな感じ。コード内の (Channel ID), (Channel Secret), (MID) の部分には上記で取得した実際の値を指定して書き換えてください:
<?php

$json_string = file_get_contents( 'php://input' );
$jsonObj = json_decode( $json_string );
$results = $jsonObj->{"result"};
$cnt = count($results);
for( $i = 0; $i < $cnt; $i ++ ){
  $result = $results[$i];
  $to = $result->{"content"}->{"from"};


  $cur = "USDJPY";
  $_cur = $result->{"content"}->{"text"};
  if( $_cur ){
    $cur = $_cur;
  }
  $cur = strtoupper( $cur );

  $fx_url = "http://fx.mybluemix.net/";
  $text = file_get_contents( $fx_url );
  $json = json_decode( $text );
  $datetime = $json->datetime;
  
  $rate = $json->rate;
  $r = "(" . $datetime . ")" . $cur . ":" . $rate->{$cur};

  $response_format_text = ['contentType'=>1,"toType"=>1,"text"=>$r];
  $post_data = ["to"=>[$to],"toChannel"=>"1383378250","eventType"=>"138311608800106203","content"=>$response_format_text];

  $ch = curl_init("https://trialbot-api.line.me/v1/events");
  curl_setopt($ch, CURLOPT_POST, true);
  curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post_data));
  curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    'Content-Type: application/json; charser=UTF-8',
    'X-Line-ChannelID: (Channel ID)',
    'X-Line-ChannelSecret: (Channel Secret)',
    'X-Line-Trusted-User-With-ACL: (MID)'
    ));
  $res = curl_exec($ch);
  curl_close($ch);
}
?>

このコードを Bluemix にデプロイすればOK、なのですが、ちょっとまだ足りない部分があるのでデプロイは次の (4) の後にまとめて行うことにします。


(注 この (4) の作業に関してはこちらの作業を行うことで不要にすることもできます)

(4) WhiteList 登録

LINE Bot の、特に Bluemix ランタイムで LINE Bot を動かす上での最大の関所がこの WhiteList 登録だと思ってます。Bot が LINE のサーバーに対して返答の POST を実行する際、あらかじめ許可されたサーバーからのリクエストのみが受け付けられるようなホワイトリストの仕組みが用意されています。つまり LINE Bot を動かすにはどの IP アドレスからリクエストするのかをあらかじめ登録しておく必要があります。

一方で Bluemix のランタイムは再起動や再ステージングを行う度にリクエスト元の IP アドレスが変わる仕様になっています。Bluemix のアプリケーションはデプロイするたびに再ステージングを行うので、要するに作りなおしたアプリをデプロイする度にリクエスト元の IP アドレスを調べてホワイトリストを更新する必要があるのでした。この仕組がちと面倒で、注意する必要があります。

そして更に話がややこしいことに、このリクエスト元の IP アドレスを調べる方法というのがまた面倒だったりします。IaaS のアプリケーションサーバーであれば SSH などでログインして「確認くん」などで調べる方法もありますが、PaaS のランタイムである Bluemix だとそれも簡単ではありません。

方法は何通りかあると思うのですが、基本的な考え方は外部の HTTP サーバーや DB サーバーを用意し、Bluemix からそのサーバーにアクセスを行って、ログを見る(ログのアクセス元の IP アドレスを調べる)という方法です。自宅サーバーなどでダイナミック DNS を使う時のようなテクニックが必要になると思っています。

僕がやってるのはこんな感じ。わざとつながらない DB サーバーに Bluemix ランタイムからアクセスして(アクセスするような PHP アプリを書いて実行して)エラーログを参照する、という方法です。良い子はマネしちゃいけません:
2016041302


というわけで、順序としては (3) で作ったコールバックのアプリケーションに加え、ここでランタイムの IP アドレスをチェックするためのアプリも作り、それらをまとめてデプロイ(プッシュ)する、ということになります。

そして、ランタイムの IP アドレスを取得して LINE Bot の White List IP アドレスに設定します(ネットマスクは 24 を指定して動きました)。この作業はランタイムをステージングするたびに行う必要がある点に注意が必要です:
2016041303


このホワイトリストの反映には数分かかっているようですが、ホワイトリストが正しく反映されていれば自分のボットも動くようになっているはず、たぶん。


Bluemix 環境を活用した LINE Bot は他にも色々作れそうなので、いずれまたブログエントリとして書くつもりです。 上記のようにホワイトリストの所だけちと面倒ではあるのですが、ランタイムが自由に作れて SSL も問題なさそうだし、これにワトソンなどのコグニティブエンジンを組み合わせた Bot を作ると面白いかな、と考えています。



BotKit を使って導入した Slack のボットの挙動を自分なりにカスタマイズしてみましょう。まずは以下のエントリを参考にして、BotKit を導入し、ボットがデフォルトの挙動で動くような状態を作っておいてください:
Slack の BotKit を使う


さて、ボットの実体は bot.js ファイルです。このファイルをテキストエディタで開くと、初期化処理などの後にこのような contoller.hears() の処理がいくつか定義されていることに気付きます:
controller.hears( コマンド, 種類, 処理 );

例えばコマンドとして ['hello','hi'] が定義されているものとして、このような記載を見つけることができます:
controller.hears( ['hello','hi'], 'direct_message,direct_mention,mention', function(bot, message){
       :
  (処理の中身)
       :
} );

これは次のような意味になります:
(1) 'hello' または 'hi' というコマンドを、
(2) 'direct_message'(1対1でのメッセージ)か、'direct_mention'(@XXX hello のように直接メンションされた場合)か、'mention'(hello @XXX のようにメッセージ中でメンションされた場合)に、
(3) function(bot,message){ ... } の内容を実行する

つまり前回のエントリでボットに対して @XXX hello というメッセージをダイレクトメンションで送った時に実行される処理がこれ、ということになります。


では簡単な処理でカスタマイズしてみましょう。bot.js 内に以下のようなコードを追加します:
controller.hears(['double (.*)'],'direct_mention',function(bot, message) {
    var matches = message.text.match(/double (.*)/i);
    var msg = matches[1];
    bot.reply(message,msg+' '+msg);
});

これはボットに対してダイレクトメンションで "@XXX double ABCDE" のようなメッセージが送られた時にハンドルし、"ABCDE ABCDE" のように double に続く部分を2度喋る、という処理をするようなコードです。

この bot.js を保存して実行(実行方法は前回のエントリ参照)し、"@XXX double Happy" のようにダイレクトメンションでメッセージを送ると、"Happy Happy" のような反応をしてくれるはずです:
2016031802


この例では処理内容がかなりシンプルな例でしたが、5行程度の追加でここまでは出来てしまいました。実際には受け取ったパラメータを元にデータベースや外部などから情報を作り出して返す、といったより本格的な Slack ボットを作ることもできると思っています。

応用編というわけではないのですが、REST API を使って外部から情報を取り出して表示する、というサンプルも1つ作ってみました。こちらも同様に bot.js に追加します:
   :
   :
controller.hears(['(usd|eur|gbp|aud|nzd|cad|chf)'],'direct_mention',function    (bot, message) {
  var matches = message.text.match(/(usd|eur|gbp|aud|nzd|cad|chf)/i);
  var cur = matches[0].toUpperCase() + "JPY";
  var request = require( 'request' );
  url = 'http://fx.mybluemix.net/';
  request( url, function( error, response, body ){
    if( !error && response.statusCode == 200 ){
      var json = JSON.parse( body );
      var dt = json['datetime'];
      var rate = json['rate'];
      var p = rate[cur];
      bot.reply( message, dt + ' ' + p );
    }
  });
});

以前作った為替の JSON サービス(http://fx.mybluemix.net/)を使い、ダイレクトメンションで USD(米ドル)EUR(ユーロ)GBP(英ポンド)AUD(豪ドル)NZD(NZドル)CAD(カナダドル)CHF(スイスフラン)のいずれかを受け取った場合、それらの通貨が日本円だといくらになるのか、をリアルタイムに計算して返信してくれる、というボットです:
2016032101


Node.js、というか JavaScript での実装に慣れない人がいるかもしれませんが、比較的簡単な言語だと思うので、この Slack ボットを作る機会と一緒に勉強して慣れておくと面白いと思います。


こんなニュースがありました:
グーグルが「Gmail」の利用規約を更新 - メール内容の解析を明示

グーグルは Gmail に書かれた内容を読んでいる、ということ。
その是非が色々言われているようですが、その件についてコメントは控えます。

僕は知ってました。というか、そういうものだと思ってました。
普段から自分でいくつかのウェブサービスを遊びで開発して公開したりしてます。
ある時、まだ誰にも話してないし、リンクも作っていない作りたてのウェブサイトのURLを Gmail で知り合いに送った所、その数日後から Google のクローラーボットがやってくるようになりました(苦笑)。本当にこのメールがきっかけだったのかどうかは分からないのですが、原因がメール内容しか思い当たらなかったので、以来そういうものだと思っていました。

で、それを逆手にとったというわけではないですが、裏ワザを1つ紹介します。

これから公開しようとしているウェブサービスを開発したら、公開前にわざと GMail にその URL を書いて誰かに(例えば自分の別のアドレスに)送ります。それでグーグルはその新サービスの URL を知ることになるので、数日中にクローラーボットが送り込まれて、グーグルの検索エンジンにインデックスしてくれます。

これで、サービスがスタートする際には既にサービス内の大半のページがグーグル検索結果に表示される状態を作っておくことができます。厳密には、サービススタート前に(キーワード次第では)検索結果に表示されるのはともかく、実際に訪問されては困る、ということもあると思います。そういう場合は UserAgent を見て、グーグルボットの場合だけは訪問を許し、それ以外の場合はエラーにする、という処理を加えておいて、サービス開始直前に無効にする、といった対策で訪問を回避することもできます。


と、まあグーグルが勝手にメールを解析してくれるのであれば、それを勝手に有効活用させてもらいましょう、という方法でした。


 

まずはこの2つのスクリーンショットを見比べてください:

(スクリーンショット1)
2014031401

(スクリーンショット2)
2014031402


どちらもアマゾンのウェブページを同じ Chrome ブラウザで見ている時に取得したものです。全般的に似たような内容ですが、パッと見で「同じではない」ことはわかりますよね。

でもどちらも全く同じページ(同じURL)を指しています。どちらのページもアマゾンのトップページから "ThinkPad" を検索した時の結果ページです。PC 用とモバイル用、というわけでもなさそうですよね。 ではこの違いはなんでしょう?



・・・答は User-Agent の違いでした。(1)は普通の Windows 向け Chrome 標準の User-Agent でアマゾン内を検索したものですが、(2)は Google のクローリングボットの User-Agent(Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)) に偽装してから検索したものです。
2014031403

ここからわかることはおそらくアマゾンはGoogle のクローリングボットが来た時用の、なんらかの手を加えて結果を返すようにしている、ということです。そしてそれはおそらく SEO 対策であろう、と推測されます。

普通に人間がウェブページを見る時に見やすい〈求める)情報と、SEO 対策を重視した画面が同じとは限りません。SEO 的に有利なページを作りたい一方で、ユーザーにはパーソナライズやレコメンドを表示するなど使い勝手を損ないたくもない。そのためにはこのような表示出し分けの対策をしている、ということだと思います。

ちなみに (1) の HTML は約80Kバイト、(2) は約110Kバイトでした。SEO 対策済み(?)のサイトの方が3割程度情報量が多いと考えるべきなのか、人間向けのサイトの方が情報をまとめていると考えるべきなのか。


詳しい内容を調べたわけではないのですが、とりあえずこの2つのページの大きな違いとして、(1) は Shift_JIS 、(2) は UTF-8 で記述されていました。まあ根本的に違うエンジンが使われていると考えるべきでしょうね・・・



このページのトップヘ