📘

LINE Messaging APIを使用して、自動でリプライを返すLINE Bot開発

2023/01/05に公開

概要


LINEのmessaging APIを使用し、ユーザーからのメッセージを受け取ると自動で返信してくれるBotを作成してみたいと思います。
LINEプラットフォームから送信されるHTTPS POSTリクエストを受け取るBotサーバーにAWS Lambda、デプロイ管理にServerless Frameworkを使用します。

目次


  • 環境
  • 構成
  • フロー
    • LINE DeveloperコンソールでLineアプリの登録
    • AWSアプリケーションの作成
    • Webhook URLの登録
  • 動作確認
  • 参考URL

環境


  • Node.js
  • AWS Lambda
  • Serverless Framework
  • linebot(Client library)

構成


linebot.drawio (1)

開発手順

LINE Developerコンソールでアプリの作成


まずは、LINE Developerコンソールでアプリの登録を行います。基本的な登録手順は、公式チュートリアルに沿って作成すれば問題ないと思うので、詳しい手順は割愛しますが、アプリ登録時に注意すべきポイントを2点取り上げます。

チャネルアクセストークンの種類

作成するBotサーバーは、ユーザーからLineへ送信されたメッセージをWebhookでイベントオブジェクトとして受け取ります。そしてBotサーバーは、Line Messaging APIのエンドポイントを叩きます。この時、チャネルアクセストークンを使用する必要があります。このチャネルトークンには、3種類あります。

  • 1.任意の有効期間を指定できるチャネルアクセストークン(チャネルアクセストークンv2.1)

チャネルアクセストークンv2.1では、開発者が任意の有効期間(最大30日)を指定でき、またチャネルシークレットの代わりにJSON Web Token(JWT)を使用することで、セキュリティをさらに強化できます。このバージョンのチャネルアクセストークンの使用をお勧めします。

  • 2.短期のチャネルアクセストークン

短期のチャネルアクセストークンとは、30日間有効なMessaging APIのチャネルアクセストークンです。

  • 3.長期のチャネルアクセストークン

長期のチャネルアクセストークンとは、有効期限を持たないMessaging APIチャネルでのみ発行できるチャネルアクセストークンです。

この記事で作成するBotでは、3の長期のチャネルアクセストークンを使用しますが、実際の開発では任意の有効期間を指定できるチャネルアクセストークン(v2.1)の使用が推奨されているので、生成方法をまとめておこうと思います。

チャネルアクセストークンv2.1を発行する手順

1.アサーション署名キーのキーペア(秘密鍵・公開鍵)を作成する。

アサーション署名キーとは
・JWTを生成するために使用する暗号鍵の事。この暗号鍵を下記の形式を満たすJWK(JSON Web Key/暗号鍵を表現するJSONオブジェクト)として生成する。

(async () => {
  const pair = await crypto.subtle.generateKey(
    {
      name: 'RSASSA-PKCS1-v1_5',
      modulusLength: 2048,
      publicExponent: new Uint8Array([1, 0, 1]),
      hash: 'SHA-256'
    },
    true,
    ['sign', 'verify']
  );

  console.log('=== private key ===');
  console.log(JSON.stringify(await crypto.subtle.exportKey('jwk', pair.privateKey), null, '  '));

  console.log('=== public key ===');
  console.log(JSON.stringify(await crypto.subtle.exportKey('jwk', pair.publicKey), null, '  '));
})();

成功すると以下のような秘密鍵(private.key)と公開鍵(public.key)が生成されます。

//private key
{
  "alg": "RS256",
  "d": "GaDzOmc4......",
  "dp": "WAByrYmh......",
  "dq": "WLwjYun0......",
  "e": "AQ......",
  "ext": true,
  "key_ops": [
    "sign"
  ],
  "kty": "RSA",
  "n": "vsbOUoFA......",
  "p": "5QJitCu9......",
  "q": "1ULfGui5......",
  "qi": "2cK4apee......"
}

//public key
{
  "alg": "RS256",
  "e": "AQ......",
  "ext": true,
  "key_ops": [
    "verify"
  ],
  "kty": "RSA",
  "n": "vsbOUoFA......"
}

2.生成したJWK形式のpublic key(公開鍵)をコンソールに登録。

スクリーンショット 2021-09-09 15.58.48

成功すると、kidが表示されます。kidとは、JWKのパラメータの事で、特定の鍵を識別するために用いられるKey IDの事(公式引用)。このkidを利用して、JWTを生成します。

3.チャネルアクセストークンの有効期限をもつJWTの生成。
Lineコンソールでの公開鍵の登録が済んで、kidが入手できたのでJWTの生成を行います。JWTの生成には、node-js-noseを使用します。JWT生成の際に、ヘッダープロパティにkidを使用し、ペイロードに秘密鍵を使用することでJWTを生成します。

let jose = require('node-jose');
const configKey = require("./jsConfigs.js").configKey

// 生成したJWK privatekeyを入力

let privateKey = configKey;

let header = {
    alg: "RS256",
    typ: "JWT",
    //入手したkid
    kid: "入手したkid"
};

let payload = {
    iss: "1234567890",
    sub: "1234567890",
    aud: "https://api.line.me/",
    // チャネルアクセストークンの有効期間を指定。
    exp: Math.floor(new Date().getTime() / 1000) + 60 * 30,
    token_exp: 60 * 60 * 24 * 30
};

//秘密鍵で、署名
jose.JWS.createSign({format: 'compact', fields: header}, JSON.parse(privateKey))
    .update(JSON.stringify(payload))
    .final()
    .then(result => {
        console.log(result);
    });

これで、エンコードされたJWTが入手できました。

下記は公式にこうしきに記載されているサンプルコード

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ijk4NjllNDQ2LTM0ODktNDUxNi1hODNmLWVjOTIxNGFkOTRkMCJ9.eyJpc3MiOiIxMjM0NTY3ODkwIiwic3ViIjoiMTIzNDU2Nzg5MCIsImF1ZCI6Imh0dHBzOi8vYXBpLmxpbmUubWUvIiwiZXhwIjoxNjIzOTk1NTk5LCJ0b2tlbl9leHAiOjI1OTIwMDB9.Zf32xTqgUHSYw2C2Mlmunqz_AtkaqvGh0msx9XJMX6QYLPT4m4QYF3PsER-zfbhbByNT4rH09JEMRP7bzcNMQ8l4n_WXwTyLkNciZYzF-sTiVHiZu4ucJm4_l8ni5NaqOVEntsCp1wQi8-VLjaMpQlQ7crCdouEMFFeyVwgERfH8ui6UZaJeIlJKRZTnO6iYvKYuLyUsqzowfwZo0hcnnZIXKnjZ81ukjH3_78EHXOD5ivovAT7CtmBoglm3Bvsi0N6PlEONLhHqpCleaYTXRmCykxDLP600JRvi5TYApaN-8n2Bo3FskXJLuxquWLP-LTfMDlkakmfEfcQCiz7daQ

このJWTをLineのOAuthのエンドポイントにPOSTする事で、line有効期限が設定されているアクセストークンを入手できます。

curl -v -X POST https://api.line.me/oauth2/v2.1/token \

-H 'Content-Type: application/x-www-form-urlencoded' \

--data-urlencode
 'grant_type=client_credentials' \

--data-urlencode 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer' \

--data-urlencode 'client_assertion=生成したJWT

(レスポンス)
# {"access_token":"アクセストークン","token_type":"Bearer","expires_in":2592000,"key_id":"キーID"}

2.AWSアプリケーションの作成


Botサーバー本体部分を作成していきます。AWSアプリケーションのビルドデプロイはServerless Frameworkで管理します。

1.サービス作成

sls create template aws-nodejs --name lineBot --path lineBot

2.AWSアプリケーションの設定

service: line-bot-demo
frameworkVersion: '2'

provider:
  name: aws
  runtime: nodejs12.x
  lambdaHashingVersion: 20201221
  ## line developerコンソールで入手
  environment:
    CHANNEL_ID: ${file(env.yml):CHANNEL_ID}
    CHANNEL_SECRET: ${file(env.yml):CHANNEL_SECRET}
    CHANNEL_ACCESS_TOKEN: ${file(env.yml):CHANNEL_ACCESS_TOKEN}

  stage: dev
  region: ap-northeast-1

functions:
  hello:
    handler: handler.hello
    name: ${sls:stage}-line-bot-demo
    events:
     - httpApi:
         path: /
         method: post

3.Lambda関数の登録
Botサーバーの本体になります。Webhookでlineからユーザーのイベントオブジェクトが送信されます。
このイベントオブジェクトを使って、さまざまなBotアクションを提供する事が出来ます。

'use strict';

// lineBotのclient
const linebot = require('linebot');
const crypto = require('crypto');

// Line Apiを叩くのに必要な変数
var bot = linebot({
  channelId: process.env.CHANNEL_ID,
  channelSecret: process.env.CHANNEL_SECRET,
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN
});

// Bot server
module.exports.hello = async (event) => {
  // Webhookで、受け取るPOSTリクエストがLineからのリクエストか確認するために、
  // イベントオブジェクトに含まれるx_line_signatureで確認。
  var x_line_signature = event.headers["x-line-signature"];
  const signature = crypto
  .createHmac('SHA256', process.env.CHANNEL_SECRET)
  .update(event.body).digest('base64');

  if (signature === x_line_signature) {
    // ユーザが送信したイベントの詳細が含まれるオブジェクト
    var body = JSON.parse(event.body);
    switch(body["events"][0].type){
      // ユーザーがBotに対してメッセージを送信した時
      case "message" :
        var userText = body["events"][0].message.text;
        var replyToken = body["events"][0].replyToken;
        var replyText = { type: 'text', text: userText }

        await bot.reply(replyToken, replyText)
          .then(function (data) {
            console.log('Success', data);
          }).catch(function (error) {
            console.log('Error', error);
          });
      break;
      // ユーザーがBotを友達追加した時
      case "follow": break;
      // ユーザーがBotをブロックした時
      case "unfollow": break;
      // ユーザーがpostbackした時
      case "postback": break;
    }
  }

  return {
    statusCode: 200,
  };
};

イベントオブジェクトにはuser_idが含まれているので、イベントオブジェクトを受信したタイミングで、DBやSpread Sheetなどのデータストアと連携して、ユーザーとの会話記録の状態を保持してBotのアクションを変更させる事が出来ます。

4.デプロイ

sls create template aws-nodejs --name lineBot --path lineBot

3.Webhook URLの登録

Bot作成の際には、Webhook URLの登録が必要になります。Webhookに登録するURLは自作したサーバーのデプロイ先を登録してもいいのですが、今回はバックエンドにAWSアプリケーションを使用するのでAPI GatewayのエンドポイントURLをWebhook URLとして登録します。

スクリーンショット 2021-09-09 16.04.25

また公式にも記載されているように、Botの場合は自動応答や挨拶機能は開発者側が設定する事が想定されるのでオフにしておきます。

動作確認


これでユーザーからのメッセージをリプライするBotが完成したので、動作確認してみます。

まずは、Botアカウントを友達追加してあいさつしてみます。

スクリーンショット 2021-09-09 16.23.11

返事を返してくれました。今回は返事を返すだけのBotでしたが、次はデータストアと連携してユーザーとの会話状態で、処理を切り替える実用的なBotを作成してみたいと思います。

参考URL

Messaging API
LINEのBot開発 超入門(前編) ゼロから応答ができるまで
LambdaではじめてのLINE Botを作る

Discussion