最近毎週沼津に行くようになったCTO室のJ.Nです。
CTO室では研究期間というものがあり、好きなテーマで一定期間技術検証することができるため今回はFacebookメッセンジャーbotの検証も兼ねて監視カメラサービスをMacAirを使い作成してみました。
上記はFacebookボットに「写真(マシン名)撮ってね」と問いかけると、対象のMacAirのカメラが写真を撮って送信してくれるサービスです。
カフェでMacAirを扱っている時(ドヤリングしている時)に、机にMacAirを置いたままトイレに行った時などに使うと簡易盗難防止になります。
Facebookメッセンジャーbotとは?
Facebookメッセンジャーbotとはメッセンジャーの会話の相手となってユーザーとの会話から自動的に何らかの処理をしてくれるアプリケーションになります。
LINEもボットサービスを展開していますが、ボットサービスの狙いとしてはEC、クーポン、MAP、銀行、賃貸、宿泊など各社のサービスアプリの機能を全てボットの会話で完結することにあるようです。
https://developers.facebook.com/docs/messenger-platform/
Facebookメッセンジャーbot開発に必要なもの
- Facebookアプリケーション (Facebook開発者登録をしてアプリを作成)
- Facebookページ (botはFacebookページに紐づける必要があるため)
- Facebookからメッセージを受け取るHTTPSのコールバックサーバー (VPSやクラウドサービスをレンタルして構築)
- HTTPS証明書 (Let’s Encryptで無料で取得可能)
上記までの手順は特に難しくはないでしょう。
HTTPSのサーバーは今回ConoHa(https://www.conoha.jp/conoha/)でRuby Sinatra+Nginxで作成しました。
FacebookメッセンジャーBotサーバー概要図
- クライアントからBotへメッセージを送信
- Facebootのメッセンジャーサーバーがメッセージを指定されたコールバックURLへ転送
- ボットアプリケーション・サーバーが何らかの処理をして応答メッセージをメッセンジャーサーバーに送信
- メッセンジャーサーバーがクライアントにメッセージを転送
上記の手順のポイントは、ボットアプリケーションサーバーはクライアントに直接メッセージを送れないという事です。
コーディング自体は簡単でエコーサーバーや、Amazon商品検索ボットなどはすぐ作成できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
post "/fb/callback" do request_body = JSON.parse(request.body.read) messaging_events = request_body["entry"][0]["messaging"] messaging_events.each do |event| sender = event["sender"]["id"] if !event["message"].nil? && !event["message"]["text"].nil? text = event["message"]["text"] if /^アマゾン\s|^amazon\s|^Amazon\s/ =~ text bot_amazon_response(sender, text) else bot_response(sender, text) end end end status 201 body '' end # エコーするだけ def bot_response(sender, text) request_endpoint = "https://graph.facebook.com/v2.6/me/messages?access_token=#{settings.config['fb']['facebook_page_token']}" request_body = text_message_request_body(sender, text) RestClient.post request_endpoint, request_body, content_type: :json, accept: :json end def bot_amazon_response(sender, text) request_endpoint = "https://graph.facebook.com/v2.6/me/messages?access_token=#{settings.config['fb']['facebook_page_token']}" search_keyword = text.sub(/^アマゾン\s|^amazon\s|^Amazon\s/, "") product_map_list = AmazonConnecter.new().search(search_keyword) if product_map_list.size > 1 product = product_map_list[0] request_body = image_url_and_button_request_body(sender, product['text'], product['url'], product['image_url']) else request_body = text_message_request_body(sender, "見つかりませんでした。") end RestClient.post request_endpoint, request_body, content_type: :json, accept: :json end |
作成した監視カメラサービス「SAKIMORI」
概要
Bot Application Server
基本的な構成は「FacebookメッセンジャーBotサーバー概要図」と同じですが、カメラマシン(MacAir)がカメラ撮影命令を待機(ポーリング)するため、今回はAWSのSQSを利用しています。メッセージ・キューシステムが使えればなんでもいいのですが、kafkaやRabbitMQをグローバルIPでポート開放するのもセキュリティ的に不安なのとサーバー構築にも工数がかかるのでHTTPでキューがやり取りできるAmazon Simple Queue Serviceを選定しました。リクエスト100万までは無料なので良いです(https://aws.amazon.com/jp/sqs/pricing/)。
Bot Application Serverはクライアント(メッセンジャー)からの撮影メッセージ「写真 (マシン名) 撮ってね」を変換し、SQSにメッセージキューを格納します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
def bot_camera_response(sender, text) request_endpoint = "https://graph.facebook.com/v2.6/me/messages?access_token=#{settings.config['fb']['facebook_page_token']}" sqs = Aws::SQS::Client.new(region: 'us-east-1') request_queue_url = settings.config['sakimori']['request_queue_url'] image_queue_url = settings.config['sakimori']['image_queue_url'] image_url = nil #メッセージ例「写真 撮って mac1」 #sakimori client_id client_id = text.split(' ')[1] m = sqs.send_message( { queue_url: request_queue_url, message_body: client_id + " HELLO" }) |
カメラマシン
クライアントには今回MacAirを使用します。
プログラムからMacAirのカメラを制御するには「imagesnap」というアプリケーションが必要になるのでインストールします。
home brewでインストール可能です。(http://brewformulas.org/Imagesnap)
SQSをポーリングし撮影命令を受け取ると撮影し画像ファイルをImage Serverに送信し、画像URLを取得しBot Application Serverに返却します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
require 'aws-sdk-core' require 'pp' config = open('./config/conf.json') do |io| JSON.load(io) end @client_keyword = config['client_keyword'] @queue_url = config['queue_url'] @image_folder = config['image_folder'] @image_server_url = config['image_server_url'] @image_url_queue = config['image_url_queue'] def capture_and_send() filename = Time.now.strftime('%Y%m%d%H%M%S') + '.jpg' filepath = @image_folder + filename system("imagesnap -q -w 1.5 #{filepath}") #send image_file cmd = "curl --upload-file #{filepath} #{@image_server_url}" puts cmd `#{cmd}` end sqs = Aws::SQS::Client.new(region: 'us-east-1') while true do begin msg = sqs.receive_message({ queue_url: @queue_url, max_number_of_messages: 1 }) if msg.messages[0] && msg.messages[0].body.start_with?(@client_keyword) puts msg.messages[0].body image_url = capture_and_send() #SQS image_url sqs.send_message( { queue_url: @image_url_queue, message_body: image_url }) sqs.delete_message({ queue_url: @queue_url, receipt_handle: msg.messages[0].receipt_handle }) end rescue Exception => ex p ex sleep 30; sqs = Aws::SQS::Client.new(region: 'us-east-1') ensure sleep 3 end end |
Image Server
メッセンジャーBotの仕様として、画像ファイルを直接クライアントに送信することができない(画像URLとして送信する必要がある)ため画像ファイルを一度どこかのサーバーにアップロードする必要があります。そのためcurlのコマンドでファイルがアップロードできる簡易アップロードサーバーをSinatraで作成しました。
カメラマシンからのアップロード要求を受付し、画像ファイル格納後の画像URLをカメラマシンに返却します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
require 'sinatra' require 'json' require 'fileutils' configure do # 静的コンテンツ参照のためのパス設定 set :public, File.dirname(__FILE__) + '/public' set(:config) { json_data = open('./config/conf.json') do |io| JSON.load(io) end json_data } end # アップロード処理 put '/upload/:id' do p params File.open('./public/' + params[:id], 'w+') do |file| file.write(request.body.read) end base = settings.config['sakimori']['image_server']['base_url'] base + params[:id] end |
ソースコード
SAKIMORI Server
SAKIMORI Client for mac
SAKIMORI Image Server
作ってみた感想
- botサーバーを作る事自体は簡単なのでボットサービスではアイディアが必要。
- FacebookメッセンジャーのメッセージはコールバックURLとの通信がうまくいかなかった場合再送を繰り返すので、メッセージの重複チェックが必要。
- 一通りサービスを作ってしばらく使っていくと作成したアプリで安定しない個所がわかるので、まずは自分が使いたくなるボットを作ると良い。
- Facebookボットはcallbackサーバーと一定時間通信がないと一度アプリケーションごと遮断するのでreactiveの作業が必要(下記の画面)
改善点
今回はサービスの完成度として自分が使える程度に作ったのでちゃんとしたサービスとしてリリースする場合以下の改善点が残っています。
- 複数人が使える想定に作っていないため、サービスとして作るにはユーザーIDやマシンIDの採番を考える必要がある。
- 画像はHTTP/HTTPSで公開しているのでGoogleなどのキャッシュに残って困る画像が映る場合は、画像のURLのパラメーターに認証キーをつけるなどの対策が必要。
- クライアントからの撮影完了メッセージを受け取るのは別バッチサーバーを立てるべき。HTTPのやりとりの中でポーリングしない、
- Facebookメッセンジャーのメッセージはうまく届かないときなど同じメッセージを再送し続けるので、重複したメッセージは処理しないようにする。(そうしないと延々と画像がメッセージに送られてくる)
- MacAirを持ち運びするとWiFiが切れた場合SQSポーリングアプリはネットワークエラーを起こすので対策が必要。
- クライアントはMacだけでなくラズパイやintel EdisonなどのIoT機器でも動作できるようにするべき。
上記の改善点をプルリクエストしてくれたりforkして改善してもらえるとありがたいです!!