LoginSignup
13
11

More than 5 years have passed since last update.

LINE BOTをJava Servletでお手軽に作る その2:画像メッセージやテンプレートを試してみた

Last updated at Posted at 2017-04-02

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からのお返事として画像を返す例を紹介します

実行例
example02.png

ソースコード

LineBotExample02Servlet.java
@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側でファイルに保存します。
example03.png

ソースコード

LineBotExample03Servlet.java

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;
    }

解説

この例では、ユーザー画像chara2.pngをおくり、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つ選択する以下のようなインタフェースをつくってみました。

example04.png

ソースコード

LineBotExample04Servlet.java

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つ用意されています。
- Buttons 複数のボタン(4つまで)と画像をレイアウトしたもの
- Confirm YES,NOのような2択を選択させるもの
- Carousel 選択肢のセットを並べて提示するもの

テンプレートには選択肢を設置できますが、それに相当するのがアクションです。

アクションは今のところ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をつかっていきます。

実行例
example05.png

選択肢(ここでは"Google"か"Bing")をタップすると、
↓のように指定したWebサイト(インライン)にジャンプします。

example05_b.png

ソースコード

LineBotExample05Servlet.java
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に記述したら、以下のようにコードを書いて実行しましょう。
コメントアウトしているところは適宜コメント解除すると上で紹介したサンプルが動作します。

AppMain.java
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が開始して以下のような画面が表示されます
ngrok.png

たったこれだけで、
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 にあります
13
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
11