インフラエンジニアだけどLINE Botを作りたい

# はじめに
お仕事的にはインフラエンジニアですが、昨今の技術の進歩からインフラエンジニアいらないんじゃない的なことを感じたり感じなかったりなので、インフラエンジニアでもある程度コードかけないとまずいとは思ってるけど、じゃいざ何作るの？って段階になるとなかなかアイデアで出ない人は多いのではないのでしょうか。

そんな人は簡単なLINE Botを作ってみるとこから初めて見たらいかがでしょうか？という記事です。

# 環境
何かしらでBotサーバーを用意しなければなりません。
選択肢としては、GASやHerokuなどが思いつくと思いますが、普段私がAWSしかいじってないのでAPI Gateway + Lambda(Ruby)をserverless frameworkでデプロイします。

普段AWSをいじってる人はこの構成でやることを是非オススメします


# 手順
ここからはLINE Botが定型文を返すまでの手順となります

# 1. LINE@アカウントの取得
まずは以下のリンクからLINE@アカウント(一般アカウント)を取得する必要があります。

https://entry-at.line.me/

### 一般アカウントとは？
>「一般アカウント」は審査なしで作成できるアカウントです。
一般アカウントの作成後に認証済みアカウントを申し込むことも可能です。
ご利用可能なプランはお住まいの国によって異なります。


# 2. チャネルの作成
LINE@アカウントを作成したらチャネルを作成する必要があります。

### チャネルとは？
>チャネルは、LINEプラットフォームが提供する機能を、プロバイダーが開発するサービスで利用するための通信路です。LINEプラットフォームを利用するには、チャネルを作成し、サービスをチャネルに関連付けます。チャネルを作成するには、名前、説明文、およびアイコン画像が必要です。チャネルを作成すると、固有のチャネルIDが識別用に発行されます。

https://developers.line.biz/ja/docs/messaging-api/getting-started/

チャネル作成の詳細な手順は以下の通りです。

### 2-1. LINEアカウントでLINE Developersコンソールにログイン
### 2-2. 開発者として登録する(初回ログイン時のみ)
### 2-3. 新規プロバイダーを作成する
### 2-4. チャネルの作成
このとき以下のどちらかのプランを選ぶ必要がありますが、お遊びの範囲であればDeveloper Trialを選択してください。

### Developer Trial
>MessagingAPIを利用したBotを試すプランです。友だちとメッセージの送受信を行うことができます。
※追加可能友だち数は50人に制限されています。また、Developer Trialからプランの切り替えやプレミアムIDの購入はできません。

### フリー
>MessagingAPIを利用したBotを開発するプランです。友だちの人数に制限はありませんが、Push messagesを利用してBotから友だちにメッセージを送信することはできません。
※サービス拡張に向けプラン変更が可能です。

# 3. Botの作成
BotはServerless Frameworkを使って、API gatewayとLambda(ruby)で作成してみます。

serverless frameworkがインストールされている前提で進みますが、インストールからという方は以下を参照してインストールしてください。
https://serverless.com/framework/docs/providers/aws/guide/installation/

## 3-1. serverlessコマンドでテンプレート作成
```
$ serverless create --template aws-ruby --path line-bot-test
```

## 3-2. serverless.ymlの編集
とりあえず最低限としてこんな感じにしておきます

```yaml
service: line-bot-test # NOTE: update this with your service name

provider:
  name: aws
  runtime: ruby2.5
  region: ap-northeast-1
  memorySize: 512
  timeout: 900

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: bot/hello
          method: get
```


## 3-3. デプロイ
```
$ serverless deploy
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: WARNING: Function hello has timeout of 900 seconds, however, it's attached to API Gateway so it's automatically limited to 30 seconds.
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service line-bot-test.zip file to S3 (686 B)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
.......................
Serverless: Stack update finished...
Service Information
service: line-bot-test
stage: dev
region: ap-northeast-1
stack: line-bot-test-dev
resources: 11
api keys:
  None
endpoints:
  GET - https://xxxxxx/dev/bot/hello
functions:
  hello: line-bot-test-dev-hello
layers:
  None
```

とりあえずAPI GatewayとLambda関数がデプロイされました。

# 4. LINE Developersコンソールからボットを設定する


## 4-1. チャンネルアクセストークンを発行する
コンソール画面上はChannel Secretのことでコンソール上からこれを発行します。

### チャネルアクセストークンとは？
>チャネルアクセストークンは長期間有効なアクセストークンで、APIを呼び出すときにAuthorizationヘッダーに設定する必要があります。チャネルアクセストークンはいつでもコンソールで再発行できます。


## 4-2. Webhook URLを設定する
先ほど作ったAPI gatewayのURLをWebhook URLとして設定します。

### Webhook URLとは？
>Webhook URLはボットアプリケーションのサーバーのエンドポイントで、Webhookペイロードの送信先です。


コンソール上から接続確認をすると...

<img width="824" alt="スクリーンショット 2019-04-12 22.15.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137707/5b01eaa0-4270-3c15-030a-4d24698bc3c3.png">


原因はAPI Gateway側のメソッドが**GET**になっていることでした...

よくよく考えたら**POST**ですよね...

API Gateway側のメソッドをPOSTに直して再デプロイ

```
$ sls deploy
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service line-bot-test.zip file to S3 (686 B)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
...................
Serverless: Stack update finished...
Service Information
service: line-bot-test
stage: dev
region: ap-northeast-1
stack: line-bot-test-dev
resources: 11
api keys:
  None
endpoints:
  POST - https://xxxxxxx/dev/bot/hello
functions:
  hello: line-bot-test-dev-hello
layers:
  None
```

再度、接続確認

今後はうまくいきました！！

<img width="815" alt="スクリーンショット 2019-04-12 22.23.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137707/f20a5489-a88a-54e3-ba4d-1bb598bf6c38.png">


これで一旦LINEからBotにメッセージを送ってみます

<img width="375" alt="Image from iOS.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137707/24c0224b-5925-f9b2-862c-5f096bc5eaeb.png">

こんな感じで変なメッセージが返ってきちゃいます...

これはチャネル基本設定 > LINE@基本機能の利用 > 自動応答メッセージを**利用しない**にする必要があるそうです

<img width="1081" alt="スクリーンショット 2019-04-13 0.56.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137707/b909025c-5401-abd4-5edc-049e6da99468.png">

自動応答メッセージを**利用しない**にしたので再度試してみます

<img width="375" alt="Image from iOS 2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137707/a369b1da-9d4c-02f6-24fc-f07dd344d312.png">

とりあえず自動応答メッセージはなくなりました

# 5. Lambda関数の作成
Lambda関数がserverless createコマンドで作られたものままなのでちゃんと返信するようにしてきます


とりあえずLINEからどんなリクエストが来るかeventの中身を見てみます

```json
{
    "resource": "/bot/hello",
=================途中省略=================
    "body": "{\"events\":[{\"type\":\"message\",\"replyToken\":\"7e36bf04b9104b26b11b0a2a7df2336c\",\"source\":{\"userId\":\"xxxxccccc\",\"type\":\"user\"},\"timestamp\":1555321188152,\"message\":{\"type\":\"text\",\"id\":\"9697231865658\",\"text\":\"つ\"}}],\"destination\":\"xxxxxxxx\"}",
    "isBase64Encoded": false
}
```

肝心なのはbody部分のみです

### bodyのみ抽出
```json
{
  "body": "{\"events\":[{\"type\":\"message\",\"replyToken\":\"7e36bf04b9104b26b11b0a2a7df2336c\",\"source\":{\"userId\":\"xxxxxxxx\",\"type\":\"user\"},\"timestamp\":1555321188152,\"message\":{\"type\":\"text\",\"id\":\"9697231865658\",\"text\":\"つ\"}}],\"destination\":\"xxxxxxx\"}"
}
```

メッセージイベントの詳細は以下にあります
https://developers.line.biz/ja/reference/messaging-api/#message-event

rubyなのでnet/httpで対応することも可能ですが、LINE公式の[line-bot-sdk-ruby](https://github.com/line/line-bot-sdk-ruby)が存在することのでこちらを使ってみたいと思います。

```ruby
def client
  @client ||= Line::Bot::Client.new { |config|
    config.channel_secret = ENV["LINE_CHANNEL_SECRET"]
    config.channel_token = ENV["LINE_CHANNEL_TOKEN"]
  }
end
```

こんな感じでなんか簡単に作れそうなのでnet/httpでやるより良さそうです

で**channel_secret**と**channel_token**って？？

## channel_secretとは？
これはLINE Developersコンソールのチャネル基本設定 > 基本情報 > Channel Secret　　
から確認出来ます

## channel_tokenとは？
これは2種類あるようです。

>注：この方法では、30日間有効な短期のチャネルアクセストークンが発行されます。長期のチャネルアクセストークンを発行するには、コンソールにある［再発行］ボタンを使います。長期のアクセストークンは、アカウントの種類やプランによってはご利用いただけません。

https://developers.line.biz/ja/reference/messaging-api/#issue-channel-access-token

長期的なものと短期的なものがあります。

短期的なものはLINE messageing APIにアクセスして発行してもらえます。

長期的なものはLINE Developersコンソールのチャネル基本設定 > メッセージ送受信設定 > アクセストークン(ロングターム)から発行することが出来ます。

channel_secretとchannel_tokenはLambdaの環境変数として設定したいのですが、serverless.ymlに書いちゃうとgithubのリポジトリに残ってしまうのでどうしましょ？
※githubのパブリックリポジトリを使ってる前提です

## serverless.yml内で秘匿情報を扱う方法
serverless.yml内で環境変数を参照することが出来ます。
今回はこの方法でいきます。
https://serverless.com/framework/docs/providers/aws/guide/variables/#referencing-environment-variables

こんな感じで使えます。

```yaml
service: new-service
provider: aws
functions:
  hello:
    name: ${env:FUNC_PREFIX}-hello
    handler: handler.hello
```


## Lambdaのrubyランタイムでgemを使う方法
色々方法はあるかと思いますが、[serverless-ruby-package](https://www.npmjs.com/package/serverless-ruby-package)というserverlessのプラグインを見つけたのでこれを利用したいと思います。

使い方はREADMEを読めば簡単です。

注意点はローカルのrubyのバージョンをLambdaに合わせて**2.5.0**にしておいてください。
もしくはdockerを使ってgemをインストールするのもありです。

むしろdockerを使ってgemをインストールすることをオススメします。

なぜrubyのバージョンを2.5.0にする必要があるかは***vendor/bundle/bundler/setup.rb***を見るとわかります

## 定型文を返すようにする
ほとんどLINE公式の[line-bot-sdk-ruby](https://github.com/line/line-bot-sdk-ruby)のサンプルのままですが、以下のようなLambda関数で定型文が返ってくるようになりました

```ruby
load "vendor/bundle/bundler/setup.rb"

require 'json'
require 'line/bot'

def hello(event:, context:)
  message = {
    type: 'text',
    text: 'hello'
  }

  client = Line::Bot::Client.new { |config|
      config.channel_secret = ENV["LINE_CHANNEL_SECRET"]
      config.channel_token = ENV["LINE_CHANNEL_TOKEN"]
  }

  body = event["body"]
  requests = client.parse_events_from(body)
  requests.each do |req|
    case req
    when Line::Bot::Event::Message
      case req.type
      when Line::Bot::Event::MessageType::Text
        response = client.reply_message(req['replyToken'], message)
        p response
      end
    end
  end
end
```

LINEから試してみるとこんな感じです。

<img width="375" alt="Image from iOS (1).png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137707/61183209-3d73-f9dd-7ad9-1195ab07d4be.png">

とりあえず定型文を返すとこまでいけました。

# まとめ
インフラエンジニアだけどコードを書きたいということでしたが、serverless frameworkとの格闘がほとんどでした...

なのでここから役に立つBotとして進化させて、この続編として記事を書きたいと思います。