LIFFをSpring Bootで動かす

## はじめに

LIFFをJavaのWebアプリ上で動かす手段が投稿されていない様だったので、やってみました。

[line/line-bot-sdk-java](https://github.com/line/line-bot-sdk-java/)　を使う場合、Bot側はWebhookできるように、Spring Boot等でサーバにホストされた状態で利用しているはずなので、その中でLIFF用の画面を作ればいいじゃんね？

ということで、Spring Boot上のThymeleafでLIFFアプリ用の画面を作れることを試しました。


## 手順

すでに [line/line-bot-sdk-java](https://github.com/line/line-bot-sdk-java/) がSpring Boot等で動作している状態を前提とする。URLは [ngrok](https://ngrok.com/) で発行している。

（宣伝：前提までの進め方もふくめて、[Javaユーザグループ北海道のハンズオン資料](https://github.com/gishi-yama/linebot-java-handson)で公開しています）

1. ライブラリにThymeleafを追加する
2. Spring BootでThymeleafのWebページを表示する
3. LIFFのサンプルを表示する

の手順で進める。

### 1. ライブラリにThymeleafを追加する

プロジェクトファイル（ルートフォルダ）の pom.xml の `<properties>〜</properties>` の中に、Thymeleafの記述を追加する。

```xml
<properties>
  (中略）
  <thymeleaf.version>3.0.9.RELEASE</thymeleaf.version>
  <thymeleaf-layout-dialect.version>2.3.0</thymeleaf-layout-dialect.version>
</properties>
```

プロジェクトファイル（ルートフォルダ）の pom.xml の `<dependencies>〜</dependencies>` の中に、Thymeleafの記述を追加する。（spring-boot-starter-webの下あたり）

```xml
<dependencies>
  （中略）
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
  </dependency>
  （中略）
</dependencies>
```

その他のソース（src/main/resources）の application.properties の末尾に、Thymeleafの設定を追加する

```properties
## thymeleaf
spring.thymeleaf.mode=HTML
```


### 2. Spring BootでThymeleafのWebページを表示する

#### ファイルの作成

その他のソース（src/main/resources）の templates フォルダの中に liff.html を作成する<br>（フォルダがない場合は作成する）

```html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8" />
  <title>Hello Thymeleaf</title>
</head>
<body>
<h1>[[${test}]]</h1>
</body>
</html>
```

ソース・パッケージ（src/main/java）に com.example.linebot.web パッケージを作成し、その中に LIFFController クラスを作成する
 
```java
package com.example.linebot.web;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class LIFFController {

  @GetMapping("/liff")
  public String hello(Model model) {
    // [[${test}]] の部分を Hello... で書き換えて、liff.htmlを表示する
    model.addAttribute("test", "Hello Tymeleaf!");
    return "liff";
  }

}
```

#### Thymeleafの動作確認

1. LineBotApplication を一度停止して、再起動する
2. [http://localhost:m8080/liff](http://localhost:8080/liff) にアクセスする
3. ブラウザに下のように表示されることを確認する<br />![Liff_P1_01.jpg](https://qiita-image-store.s3.amazonaws.com/0/8064/acc5dd42-1f30-8d3d-8114-78d21fde9d3e.jpeg)<br />（ liff.html の`[[${test}]]`の部分を、 LIFFController でセットしたModelの情報で書き換えている）



### 3. LIFFのサンプルを表示する

#### ファイルの作成

[line/line-liff-starter](https://github.com/line/line-liff-starter) のサンプルコードを（少し変更して）動作させる。

上記のサイトの liff-starter.js と style.css をコピーして、その他のソース（src/main/resources）の static フォルダの中に複製する<br/>（フォルダがない場合は作成する）

上記のサイトの index.html の内容をもとに、その他のソース（src/main/resources）の templates/liff.html を書き換える。

```html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<!-- The html based on https://github.com/line/line-liff-starter/blob/master/index.html -->
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>LIFF Starter</title>
  <link rel="stylesheet" href="style.css">
</head>

<body>

<h1>[[${test}]]</h1>

<div class="buttongroup">
  <div class="buttonrow">
    <button id="openwindowbutton">Open Window</button>
    <button id="closewindowbutton">Close Window</button>
  </div>
  <div class="buttonrow">
    <button id="getprofilebutton">Get Profile</button>
    <button id="sendmessagebutton">Send Message</button>
  </div>
</div>

<div id="profileinfo">
  <h2>Profile</h2>
  <a href="#" onclick="toggleProfileData()">Close Profile</a>
  <div id="profilepicturediv">
  </div>
  <table border="1">
    <tr>
      <th>userId</th>
      <td id="useridprofilefield"></td>
    </tr>
    <tr>
      <th>displayName</th>
      <td id="displaynamefield"></td>
    </tr>
    <tr>
      <th>statusMessage</th>
      <td id="statusmessagefield"></td>
    </tr>
  </table>
</div>

<div id="liffdata">
  <h2>LIFF Data</h2>
  <table border="1">
    <tr>
      <th>language</th>
      <td id="languagefield"></td>
    </tr>
    <tr>
      <th>context.viewType</th>
      <td id="viewtypefield"></td>
    </tr>
    <tr>
      <th>context.userId</th>
      <td id="useridfield"></td>
    </tr>
    <tr>
      <th>context.utouId</th>
      <td id="utouidfield"></td>
    </tr>
    <tr>
      <th>context.roomId</th>
      <td id="roomidfield"></td>
    </tr>
    <tr>
      <th>context.groupId</th>
      <td id="groupidfield"></td>
    </tr>
  </table>
</div>

<script src="https://d.line-scdn.net/liff/1.0/sdk.js"></script>
<script src="liff-starter.js"></script>
</body>
</html>
```

#### LIFFアプリとして追加する

LIFFアプリを追加するために、下のコマンドを端末（ターミナル）から実行する。

Windowsの場合はcurlをインストールするか、同等のパラメータでHTTPリクエストが行えるツール（[ARC](https://chrome.google.com/webstore/detail/advanced-rest-client/hgmloofddffdnphfgcellkdfbfbjeloo?hl=ja)など）を利用する。<br>また、[line/line-bot-sdk-java](https://github.com/line/line-bot-sdk-java/) には、Javaで動作するLIFF/リッチメニュー登録用のコマンドラインツール([line-bot-cli](https://github.com/line/line-bot-sdk-java/tree/master/line-bot-cli))も用意されている。

コマンドを実行する場合、以下の部分は個別に編集が必要。

- `"Authorization: Bearer xxxxxx..."` の `xxxxxx...` には、Botのアクセストークン（ロングターム）の値を改行なしで貼り付ける（ので、コマンドがとても長くなる）
- `"https://xxx.ngrok.io/liff"` の `xxx.ngrok.io` は、ngrokで取得したURLにする
- `Type` には `compact`, `tall` , `full`　の三種類があり、LIFFアプリのウィンドウの高さを決める
- `url` は https のURLを指定する

```sh
curl -XPOST \
-H "Authorization: Bearer xxxxxx..." \
-H "Content-Type: application/json" \
-d '{
    "view": {
        "type": "tall",
        "url": "https://xxx.ngrok.io/liff"
    }
}' \
https://api.line.me/liff/v1/apps
```

成功すれば、liffIdが返信される。

```
{"liffId":"0000000000-nnnnnnnn"}%
```

#### LIFFアプリの動作確認

上記の手順で取得した liffId をもとに、アプリにアクセスするURLを作成する。

URLは、 `line://app/` と liffId を結合した `line://app/0000000000-nnnnnnnn` となる。

本来はトリガとなる行動にあわせてBotがユーザにURLを発話すれば良いが、ここでは簡易的な動作確認のため、自分でURLを投稿する。

![Liff_P1_02.jpg](https://qiita-image-store.s3.amazonaws.com/0/8064/e4ba6173-1394-e6f5-9114-92ac98d8b96d.jpeg)

自分が投稿したURL（もしくはBotが発話したURL)をクリックすると、下のようにLIFFアプリが表示される。<br>特に、Thymeleafにより `Hello Thymeleaf!` を表示していること、LIFF APIにより表の中の`language`, `context.viewType`, `context.userId`, `context.utouId` などの項目に値が表示されていることを確認する。 

![Liff_P1_03.jpg](https://qiita-image-store.s3.amazonaws.com/0/8064/e1fe7d61-9d5d-662d-13c1-854594ca1617.jpeg)

`Open window` ボタンを押すと、アプリ内ブラウザでlineのホームページが表示される。

`Get profile` ボタンを押すと、自分のLINEに設定しているアイコンとプロフィールが表示される。

![Liff_P1_04.jpg](https://qiita-image-store.s3.amazonaws.com/0/8064/5a48b274-ae40-f9a2-0e48-077321593a06.jpeg)

`Send Message` ボタンを押すと、メッセージを送信した旨のダイアログが表示され、自分に `You've successfully sent a message! Hooray!` というメッセージと、スタンプが表示される。

![Liff_P1_05.jpg](https://qiita-image-store.s3.amazonaws.com/0/8064/418436ec-ce65-c97b-1ac5-f98082eb2ad6.jpeg)

このように、LIFFアプリを用いると、LINEのWebサイトにLINEの情報を連携させたり、Webサイト側からLINEのクライアントにイベントを発生させることができる。

#### LIFFアプリの削除

追加したLIFFアプリを削除するには、下のコマンドを端末（ターミナル）から実行する。

- `0000000000-nnnnnnnn` の部分は、 liffId と置き換える 
- `"Authorization: Bearer xxxxxx..."` の `xxxxxx...` の部分には、Botのアクセストークン（ロングターム）の値を改行なしで貼り付ける

```sh
curl -X DELETE https://api.line.me/liff/v1/apps/0000000000-nnnnnnnn \
-H "Authorization: Bearer xxxxxx..."
```

成功すれば何も表示されない。（失敗時にはエラーメッセージが表示される）

## おわりに

Spring Boot上のThymeleafでLINE Appを作れることが確認できた。

Thymeleaf側でFormとかを用意して、フォームで投稿した情報を使うようなLIFFアプリや、ThymeleafとLIFF APIをうまく連携させたLIFFアプリも作れそうですね（作りたい）。

## 参考文献

- [Spring Boot で Thymeleaf 使い方メモ](https://qiita.com/opengl-8080/items/eb3bf3b5301bae398cc2)