LINE BOTをJavaのServletを利用してお手軽に作る方法を紹介します
紹介したソースコード一式は https://github.com/riversun/line-bot-servlet-examples.git にあります
前回投稿「LINE BOTをJava Servletでお手軽に作る」の続編です。
LINE BOTとのやりとりのバリエーションを増やす
今回は、前回紹介したテキストメッセージ以外にも画像やダイアログなど4つ試してみました。
ひきつづきLINEのMessaging APIを活用して、LINE BOTをスッピンのJava Servletとして実装していきます
【その1】BOTからのお返事として画像を返す
以下のように、BOTからのお返事として画像を返す例を紹介します
ソースコード
@SuppressWarnings("serial")
public class LineBotExample02Servlet extends LineBotServlet {
private static final String CHANNEL_SECRET ="取得したものをいれる";
private static final String CHANNEL_ACCESS_TOKEN ="取得したものをいれる";
@Override
protected ReplyMessage handleTextMessageEvent(MessageEvent<TextMessageContent> event) throws IOException {
// ユーザーがBOTに送信したメッセージ
TextMessageContent userMessage = event.getMessage();
// ユーザーのProfileを取得する
UserProfileResponse userProfile = getUserProfile(event.getSource().getUserId());
// BOTからの返信メッセージ
String botResponseText = userProfile.getDisplayName() + "さん、"
+ "「" + userMessage.getText() + "」って言いましたね";
// 返信用テキストメッセージ
TextMessage textMessage = new TextMessage(botResponseText);
// 返信用画像(image)のURL
final String originalContentUrl = "https://riversun.github.io/img/riversun_256.png";
final String previewImageUrl = "https://riversun.github.io/img/riversun_144.png";
// 返信用画像(image)メッセージ
ImageMessage imageMessage = new ImageMessage(originalContentUrl, previewImageUrl);
// テキストメッセージと画像を同時に返す
return new ReplyMessage(event.getReplyToken(), Arrays.asList(imageMessage, textMessage));
}
@Override
protected ReplyMessage handleDefaultMessageEvent(Event event) {
// overrideしていないメッセージを受信した場合は何もしない(nullを返す)
return null;
}
@Override
public String getChannelSecret() {
return CHANNEL_SECRET;
}
@Override
public String getChannelAccessToken() {
return CHANNEL_ACCESS_TOKEN;
}
}
解説
画像を返すのは非常にシンプルで、
以下のように画像のURL(original,previewの2種類。両方同じものを指定してもよい)を指定してImageMessageオブジェクトをつくります。
// 返信用画像(image)のURL
final String originalContentUrl = "https://riversun.github.io/img/riversun_256.png";
final String previewImageUrl = "https://riversun.github.io/img/riversun_144.png";
// 返信用画像(image)メッセージ
ImageMessage imageMessage = new ImageMessage(originalContentUrl, previewImageUrl);
そして、以下のように、リプライするだけです。
// テキストメッセージと画像を同時に返す
return new ReplyMessage(event.getReplyToken(), Arrays.asList(imageMessage, textMessage));
Arrays.asList(imageMessage, textMessage)
のようにメッセージをList<>で指定すると、その指定順でBOTから複数のメッセージが返信されます。
【その2】:ユーザーがBOTに対して画像を送信する
次はユーザーから送信された画像をBOT側が受信して処理する例です。
実行例
このように、ユーザーが送信した画像をBOT側でファイルに保存します。
ソースコード
public class LineBotExample03Servlet extends LineBotServlet {
private static final String CHANNEL_SECRET ="取得したものをいれる";
private static final String CHANNEL_ACCESS_TOKEN ="取得したものをいれる";
@Override
protected ReplyMessage handleTextMessageEvent(MessageEvent<TextMessageContent> event) throws IOException {
return new ReplyMessage(event.getReplyToken(), Arrays.asList(new TextMessage("画像を送ってね")));
}
@Override
protected ReplyMessage handleImageMessageEvent(MessageEvent<ImageMessageContent> event) throws IOException {
// Contentについてはこちら https://devdocs.line.me/ja/#content
InputStream is = getContentStream(event.getMessage());
// 画像ファイルの保存先パス
String tempImageFilePath = System.getProperty("user.dir") + "/" + "line_img_" + System.currentTimeMillis() + ".jpg";
// ファイルを保存
Files.copy(is, Paths.get(tempImageFilePath));
return new ReplyMessage(event.getReplyToken(), Arrays.asList(new TextMessage("画像をありがとう!")));
}
@Override
protected ReplyMessage handleDefaultMessageEvent(Event event) {
// overrideしていないメッセージを受信した場合は何もしない(nullを返す)
return null;
}
@Override
public String getChannelSecret() {
return CHANNEL_SECRET;
}
@Override
public String getChannelAccessToken() {
return CHANNEL_ACCESS_TOKEN;
}
解説
この例では、ユーザー画像をおくり、BOTがそれを受信して反応を返します。
ソースコードでは、ユーザーから画像を受け取るために、LineBotServlet#handleImageMessageEventをオーバーライドしています。
@Override
protected ReplyMessage handleTextMessageEvent(MessageEvent<TextMessageContent> event) throws IOException
この例のキモはここで、
InputStream is = getContentStream(event.getMessage());
送られた来た画像のストリームを取得します。
内部の処理に少しふれておくと、LINEのAPIとしては、ユーザーから画像が送られてくる時はWebhook eventとしてImageMessageがWebhook URLにPOSTされる、という動作になりますが、Imagemessage自体は画像が送られてくるよということだけが通知され、実際の画像は送信されてこない仕組みです。
そのため、ImageMessageをうけとったら、そこに記載されているIDをつかって、別途画像をダウンロードしにいく必要があり、それがContentというAPIです
上のgetContentStream(event.getMessage());
ではContentAPIにアクセスして画像をダウンロード(ダウンロードするためのストリームをひっぱる)をしています。
APIの説明をよむと
コンテンツはメッセージが送信されてからある期間経過した時点で自動的に削除されます。保存される期間についての保証はありません。
とあるので、はやめにダウンロードしておく必要があります。
なおこの仕組みは画像だけでなく、動画や音声といったサイズの大きなコンテンツを取り扱う際に毎度つかわれます。
この例では、ダウンロードした画像ファイルは、カレントディレクトリに保存します。
String tempImageFilePath = System.getProperty("user.dir") + "/" + "line_img_" + System.currentTimeMillis() + ".jpg";
Files.copy(is, Paths.get(tempImageFilePath));
【その3】テンプレートメッセージをつかって複数の選択肢の中から選ぶ例
次はテンプレート・メッセージを使ってみます。
テンプレートメッセージは https://devdocs.line.me/ja/#template-message に詳しいですが
Template messageは、あらかじめ定義されたレイアウトのテンプレートにカスタムデータを挿入することによって構築するメッセージ
です。iOS版およびAndroid版のLINE 6.7.0以降で対応しており、今は複数の選択肢の中から1つ選ぶようなダイアログ・ウィジェットのような見た目をつくるテンプレートが利用可能です。
商品選択やホテル予約など、ある決められたフローに従って処理をすすめていくようなシーンで活躍しそうです。
実行例
今回は、好きな料理を1つ選択する以下のようなインタフェースをつくってみました。
ソースコード
public class LineBotExample04Servlet extends LineBotServlet {
private static final String CHANNEL_SECRET ="取得したものをいれる";
private static final String CHANNEL_ACCESS_TOKEN ="取得したものをいれる";
@Override
protected ReplyMessage handleTextMessageEvent(MessageEvent<TextMessageContent> event) throws IOException {
UserProfileResponse userProfile = getUserProfile(event.getSource().getUserId());
String thumbnailImageUrl = "https://riversun.github.io/img/riversun_256.png";
String title = "料理のジャンル選択";
String text = userProfile.getDisplayName() + "さんは、どんな料理が好きですか?";
Action japaneseCuisine = new PostbackAction("和食", "japanese");
Action italianCuisine = new PostbackAction("イタリアン", "italian");
Action frenchCuisine = new PostbackAction("フレンチ", "french");
List<Action> actions = Arrays.asList(japaneseCuisine, italianCuisine, frenchCuisine);
ButtonsTemplate buttonsTemplate = new ButtonsTemplate(thumbnailImageUrl, title, text, actions);
String altText = title;
return new ReplyMessage(event.getReplyToken(), new TemplateMessage(altText, buttonsTemplate));
}
@Override
protected ReplyMessage handlePostbackEvent(PostbackEvent event) {
// ButtonsTemplateでユーザーが選択した結果が、このPostBackEventとして返ってくる
PostbackContent postbackContent = event.getPostbackContent();
// PostbackActionで設定したdataを取得する
String data = postbackContent.getData();
final String replyText;
if ("japanese".equals(data)) {
replyText = "和食がお好きなんですね。";
} else if ("italian".equals(data)) {
replyText = "イタリアン、良いですよね。";
} else {
replyText = "フレンチ、私も食べたいです。";
}
return new ReplyMessage(event.getReplyToken(), Arrays.asList(new TextMessage(replyText)));
}
@Override
protected ReplyMessage handleDefaultMessageEvent(Event event) {
// overrideしていないメッセージを受信した場合は何もしない(nullを返す)
return null;
}
@Override
public String getChannelSecret() {
return CHANNEL_SECRET;
}
@Override
public String getChannelAccessToken() {
return CHANNEL_ACCESS_TOKEN;
}
}
解説
テンプレートは、今のところ3つ用意されています。
テンプレートには選択肢を設置できますが、それに相当するのがアクションです。
アクションは今のところ3つ用意されています
- Postback action タップするとpostback eventが発生する
- Message action タップするとユーザーの発言として送信される
- URI action タップするとブラウザで指定したURIが開く
この例では、テンプレートとして、Buttons Templateを使用しました。
アクションはPostbackActionを指定しました。
Action japaneseCuisine = new PostbackAction("和食", "japanese");
Action italianCuisine = new PostbackAction("イタリアン", "italian");
Action frenchCuisine = new PostbackAction("フレンチ", "french");
List<Action> actions = Arrays.asList(japaneseCuisine, italianCuisine, frenchCuisine);
PostbackActionがタップされると、内部的にはPostback eventが発生しますので、
これを受け取るためにhandlePostbackEvent
をオーバーライドしました。
protected ReplyMessage handlePostbackEvent(PostbackEvent event)
何がタップされたかは、 new PostbackAction("イタリアン", "italian")
の第2引数として指定したdataを以下のように取得して判定します。
String data = postbackContent.getData();
これで固定メッセージの選択インタフェースができました。
【その4】テンプレートメッセージの選択肢をリンクにする例
前の例では、選択肢がすべてPostbackActionでしたが、ここではタップするとWebサイトにジャンプするURIActionをつかっていきます。
選択肢(ここでは"Google"か"Bing")をタップすると、
↓のように指定したWebサイト(インライン)にジャンプします。
ソースコード
public class LineBotExample05Servlet extends LineBotServlet {
private static final String CHANNEL_SECRET ="取得したものをいれる";
private static final String CHANNEL_ACCESS_TOKEN = "取得したものをいれる";
@Override
protected ReplyMessage handleTextMessageEvent(MessageEvent<TextMessageContent> event) throws IOException {
String thumbnailImageUrl = "https://riversun.github.io/img/riversun_256.png";
String title = "好きな検索エンジン";
String text = "好きな検索エンジンを選んでください";
Action google = new URIAction("Google", "https://www.google.co.jp");
Action bing = new URIAction("Bing", "https://www.bing.com");
Action other = new PostbackAction("その他", "other");
List<Action> actions = Arrays.asList(google, bing, other);
ButtonsTemplate buttonsTemplate = new ButtonsTemplate(thumbnailImageUrl, title, text, actions);
String altText = title;
return new ReplyMessage(event.getReplyToken(), new TemplateMessage(altText, buttonsTemplate));
}
@Override
protected ReplyMessage handlePostbackEvent(PostbackEvent event) {
PostbackContent postbackContent = event.getPostbackContent();
String data = postbackContent.getData();
if ("other".equals(data)) {
return new ReplyMessage(event.getReplyToken(), new TextMessage("他の検索エンジンが好きなんですね。"));
} else {
return null;
}
}
@Override
protected ReplyMessage handleDefaultMessageEvent(Event event) {
// overrideしていないメッセージを受信した場合は何もしない(nullを返す)
return null;
}
@Override
public String getChannelSecret() {
return CHANNEL_SECRET;
}
@Override
public String getChannelAccessToken() {
return CHANNEL_ACCESS_TOKEN;
}
}
解説
URIActionはタップすると指定したURIでブラウザが開きます。
アクションは複数の種類を混ぜることも可能なので、以下ではURIActionとPostbackActionを混在させてみました。
Action google = new URIAction("Google", "https://www.google.co.jp");
Action bing = new URIAction("Bing", "https://www.bing.com");
Action other = new PostbackAction("その他", "other");
List<Action> actions = Arrays.asList(google, bing, other);
ローカルPCでお手軽に試す
このサンプルもサーバーやクラウドにアップロードせずに、Jettyとngrokを活用してローカルでサクっと試せます。
Gradle/Mavenに以下を追加します。
Gradle
compile 'org.eclipse.jetty:jetty-server:9.4.0.v20161208'
compile 'org.eclipse.jetty:jetty-webapp:9.4.0.v20161208'
Maven
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>9.4.0.v20161208</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
<version>9.4.0.v20161208</version>
</dependency>
Jettyを起動する
Gradle/Mavenに記述したら、以下のようにコードを書いて実行しましょう。
コメントアウトしているところは適宜コメント解除すると上で紹介したサンプルが動作します。
public class AppMain {
public static void main(String[] args) throws Exception {
ServletHandler handler = new ServletHandler();
handler.addServletWithMapping(LineBotExample01Servlet.class, "/callback");
//handler.addServletWithMapping(LineBotExample02Servlet.class, "/callback");
//handler.addServletWithMapping(LineBotExample03Servlet.class, "/callback");
//handler.addServletWithMapping(LineBotExample04Servlet.class, "/callback");
//handler.addServletWithMapping(LineBotExample05Servlet.class, "/callback");
// loclahost:3000でJettyを起動
Server jetty = new Server(3000);
jetty.setHandler(handler);
jetty.start();
jetty.join();
}
}
外部からアクセスできるようにする
せっかくローカルで立ち上げても、LINE BOTとして振舞うには、サーバーにDeployして外部に公開する必要があります。
テスト用途でお手軽にためすため、さきほどJettyをつかってつくったサーバーを外部からアクセスできるようにしましょう。
ここでは、こうした用途にぴったりな ngrok というサービスを使います。
以下から ngrok をダウンロードしておきます
https://ngrok.com/download
コマンドラインで
ngrok http -region=ap 127.0.0.1:3000
とすると、ngrokが開始して以下のような画面が表示されます
たったこれだけで、
https://xxxxx.ap.ngrok.io というurlが https://127.0.0.1:3000 にマップされました!
(xxxxxの部分はngrokを起動するたびにランダムに変わります。sign upすれば固定することも可能です)
LINE BOTのweb hook urlにはhttpsが求められますのでこれは便利ですね。
ここまでくれば、あとは、以下のurlをWeb Hook URLに登録するだけです。
https://xxxxx.ap.ngrok.io/callback
【まとめ】
- 前回につづき、JavaのスッピンのHttpServletをつかって、LINE BOT APIを試してみました。
- テキストメッセージ以外のメッセージリプライも、わりとお手軽につくることができました。
- 紹介したソースコード一式は https://github.com/riversun/line-bot-servlet-examples.git にあります