こんにちは。WowTalkのAndroid開発担当している岡野です。
今回はAndroidと全く関係ないのですが、AWSのサービスを使ってWowTalkのユーザサポート用のボットを作って1週間で社内リリースした話を書いていこうと思います。
1. なぜ作ろうと思ったのか
WowTalkには電話やメールでのサポート窓口というものがあるですが、ここに来る問い合わせ件数を減らせないかというのがそもそもの始まりでした。
WowTalkには操作説明が載っているユーザーサイトもあるのですが、せっかくWowTalkには自分でカスタマイズできるボット機能と、WowTalkにメッセージを送信できるOpenAPIもあるのでそれをうまく利用したいと考えました。
今回は検証も兼ねて、社内からの問い合わせに対応できるよう社内公開しましたが、最終的にはユーザの皆様に公開し、利用していただけるようなボットを提供できればと考えています。
2.開発環境
Lambda : node.js 8.10
ServerlessFramework : 1.40
3. サーバー立てずに実装したい
WowTalkは裏側でAWSを使っているのですが、今回は機能上EC2でインスタンスを立てる必要もないと判断したためAPIGateWayとLambdaを使うことにしました。
ただしインスタンスを立てないことに伴いLambdaの設定を工夫する必要がありました。
4. IPアドレスがない問題
WowTalkのOpenAPIの利用にはセキュアアクセスのためアクセス元にIPアドレスが必要ですが、LambdaはサーバーレスなFaaSサービスのためIPアドレスがありません。
ではどうするかというと、OpenAPIと同じサブネットにLambdaを設置することで対応しました。OpenAPIは社内で検証を行うことがしばしばあるので、同じサービスネットワーク内からのアクセスは許可されています。なのでLambda関数のVPC設定から、WowTalkのインスタンス群と同じサブネットに配置することでOpenAPIの呼び出しを行えるようにしました。
5.Lamdaから外部へアクセスできない問題
ただこれだけでは不十分で、今度はLambdaからユーザーサイトにアクセスできなくなりました。上の画像の注意書きにもありますが、NAT GateWayを設定していないため外部ネットワークとのアクセスができず、ドメイン解決ができなくなるので当然と言えば当然なのですが(汗)
これはRoute53のプライベートホストゾーン設定にユーザーサイトへアクセスするためのレコードセットを設定することで解決しました。OpenAPIのサーバーへのアクセスも同様に通常のドメイン設定ではアクセスできなくなったので合わせて設定することで対応しました。
6.回答しよう
ここまでできたらあとは裏側の処理をnode.jsでガリガリ書くだけです。まずはシンプルにキーワードに関連する記事のURLをボットのそれっぽく返却するだけにしました。
'use strict';
const crypto = require('crypto');
const URLSafeBase64 = require('urlsafe-base64');
const Buffertrim = require('buffertrim');
const https = require("request");
const Config = require('./config');
var util = require('util');
var configs;
module.exports.handleMessage = async (event, context, callback) => {
const body = event['body'];
const alias = event['stageVariables']['alias'];
configs = new Config(alias).getConfigs();
if (body != null) {
var json = getJsonFromBody(body);
const userId = json['source']['userId'];
const content = json['message']['content'];
const companyId = json['source']['companyId']
getURLFromKeyWord(content, function(answer){
sendMessageBuddy(configs.BOT_ID, configs.BOT_PASSWORD, userId, companyId, answer);
});
}
callback();
};
function getJsonFromBody(body) {
}
function getURLFromKeyWord(keyword, callback) {
var encodedKeyWord = encodeURI(keyword);
var opt = {
url: configs.SUPPORT_URL,
headers: {
"Accept" : "application/json"
},
qs : {
search : encodedKeyWord,
per_page : 100
},
qsStringifyOptions : {
encode : false
},
rejectUnauthorized : false
}
https.get(opt, function(error, response, body) {
if (body != null) {
var JSONBody = JSON.parse(body);
var answers = [];
var answer;
if (body.length == 0 || body == "[]") {
answer = configs.HINT_TEXT_FOR_NO_RESULT;
} else {
answer = util.format(configs.HINT_TEXT_FOR_RESULT, keyword);
for (var i=0; i<JSONBody.length; i++) {
var val = JSONBody[i];
var title = val["title"]["rendered"];
var link = val["link"];
var textAfterJoin = answer + title + "\n" + link + "\n";
if (answer.length < configs.MAX_LENGTH
&& textAfterJoin.length >= configs.MAX_LENGTH) {
answers.push(answer);
answer = util.format(configs.HINT_TEXT_FOR_RESULT, keyword);
}
if (link != null) {
answer += title + "\n" + link + "\n";
}
}
}
answers.push(answer);
for (var j=0; j< answers.length ; j++) {
var ans = answers[j];
callback(ans);
}
} else if (error != null) {
callback(configs.HINT_TEXT_FOR_REQUEST_ERROR);
}
});
}
function sendMessageBuddy(fromAccount, fromPassword, toAccount, companyId, content) {
const message = {
from_account : fromAccount,
from_account_password: fromPassword,
to_account : toAccount,
message_content: content
};
const messageJsonString = JSON.stringify(message);
var opt = {
uri: configs.OPENAPI_URL,
method: 'POST',
headers:{
"Content-type": "application/x-www-form-urlencoded",
},
form: {
"company": companyId,
"request_id": "req123",
"language":"jp",
"request_data":"暗号化された回答"
}
}
https.post(opt,function(error,response,body){
});
}
こうして記事のURLを返却するボットを無事(?)社内リリースしました。
7. これからのお話
ただこれでは通常の質問形式や複数キーワードで検索をかけようとした時に、WP REST APIでは投稿が引っかからないので正直ほとんど使い物になりませんでした orz
というわけで「投げられたメッセージをキーワード解析しながらWP REST APIへの検索方法も改善しないと使い物にならないな〜」となりました。そちらについてはまた今後追記していきたいと思います。
8. おわりに
今回の記事では(ボットのクオリティはさておき)お手製のサポートボットをリリースするまでを簡単に追いました。
タイトルにもある通り、AWSのサーバーレスアーキテクチャを利用すると、アーキテクチャを考えてから社内リリースするまで1週間という短期間で実装ができました。EC2でインスタンスを立てると利用していないリソースにまで料金が発生したり負荷が高くなった時のスケーリングとかリソースの配分なども考えないといけませんが、そこを考慮しなくて済むのは魅力的です。
Androidアプリ開発する傍ら、サービスがもっと使いやすくなるような機能の模索もしていきたいと思います。
参考URL
AWS関連
Serverless関連
我々ワウテック技術開発部はこのような環境で開発をしています。
興味を持った方がいらっしゃればいつでもご連絡下さい。