ども!nest.jsでLINE BOTを作成する方法についてまとめました。署名検証用のGuardを作成して、LINE側からのアクセスを担保しています。内容はオウム返しで送られてきた内容をそのまま返答します。またローカル開発のためにngrokでHTTPS化して、ローカルのまま開発を進める設定をしています。
はじめも
どもども!会社の業務ではおそらく使わないであろうLINE関係の投稿を重ねている龍ちゃんです。もちろん本業をほったらかしてやっているわけではないので、その点はご安心ください。APIやSDKを触ってみることは良い経験になりますね。
さて!今回は、nest.jsでLINE Bot(LINE Messaging API)を触っていく内容になります。ちょうど一年弱前にGoogle Apps Script(GAS)でLINE Botを作成する方法についてまとめました。GASの場合だと、Header情報にアクセスすることができないので、単体では署名の検証を行うことができません。なので、nest.js
でその辺に入門していきたいと思います。
今回の記事でわかることは以下になります。
- nest.jsでLINE Messaging APIオウム返しを作成する
- LINEからのアクセスを署名の検証Guardを作成して実装する
LINEの設定周りに関しては、今回の記事では取り扱いません。前回記事を参考にしてもらえればうれしいです。
nest.jsでLINE Messaging APIを触ってみる
段階を追って解説をしていきます。順序としては以下になります。
- 必要な情報の環境変数を整理して設定する
- 署名検証用Guardの作成
- オウム返しの処理(テキストメッセージを返送する)
今回作成するアプリケーションのディレクトリ構造になります。
.
├── app.controller.spec.ts
├── app.controller.ts
├── app.module.ts
├── app.service.ts
├── common
│ └── guard
│ └── line-bot-signature
│ ├── line-bot-signature.guard.spec.ts
│ └── line-bot-signature.guard.ts
├── config
│ ├── enviroments.module.ts
│ └── enviroments.service.ts
├── line-bot
│ ├── line-bot.controller.spec.ts
│ ├── line-bot.controller.ts
│ ├── line-bot.module.ts
│ ├── line-bot.service.spec.ts
│ └── line-bot.service.ts
└── main.ts
初期設定として必要になるのは、LINEが提供している公式SDKとnest.jsで環境変数を便利にする追加のモジュールですね。
npm i --save @nestjs/config @line/bot-sdk
LINE Messaging API側の設定としては、以下の記事を参考にしてもらえればと思います。
LINE Messaging API側からの取得
BOTを作成するためには、LINE Developers側からチャンネルシークレットとアクセストークンを取得する必要があります。取得したファイルは、.envとして以下の形式で保存します。
BOT_CHANNEL_ACCESS_TOKEN="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
BOT_CHANNEL_SECRET="xxxxxxxxxxxxxxxxxxxxxxxxxx"
そのままでは、nest.js側で扱いにくいので環境変数の取り回し設定を行います。
環境変数の整理
こちらでは、nest.jsで環境変数を便利に取りまわすための設定を行います。僕の場合では、config
の中に設定をまとめています。
import { Global, Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { EnvironmentsService } from './enviroments.service';
@Global()
@Module({
imports: [ConfigModule.forRoot({ envFilePath: ['.env', '.env.local'] })],
providers: [EnvironmentsService],
exports: [EnvironmentsService],
})
export class EnvironmentsModule {}
グローバルでモジュールを作成することで、いろいろなところからアクセスできるようになります。
import { MessagingApiClient } from '@line/bot-sdk/dist/messaging-api/api';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class EnvironmentsService {
constructor(private configService: ConfigService) {}
get ChannelSecret(): string {
return this.configService.get('BOT_CHANNEL_SECRET');
}
get ChannelAccessToken(): string {
return this.configService.get('BOT_CHANNEL_ACCESS_TOKEN');
}
createLinebotClient() {
const token = { channelAccessToken: this.ChannelAccessToken };
return new MessagingApiClient(token);
}
}
サービス内では、実際に環境変数を取りまわすための設定が記載されています。こちらのサービスを呼び出すことで、環境変数を引き出すことができます。また、LINE SDKの返信用の関数であるMessagingApiClient
の初期化も併せて行っています。
署名検証用Guard作成
ここでは、署名検証用Guardを作成します。Webhookで飛んできたアクセスがLINE側からのアクセスであることを確かめなければ、悪意の持ったアクセスを素通りする可能性があります。公式の情報としては、こちらに記載されています。common/gurad配下にline-bot-signature.guard.ts
を作成します。
import { CanActivate, ExecutionContext, Injectable, RawBodyRequest } from '@nestjs/common';
import { Observable } from 'rxjs';
import { EnvironmentsService } from 'src/config/enviroments.service';
import { createHmac } from 'crypto';
@Injectable()
export class LineBotSignatureGuard implements CanActivate {
constructor(private readonly env: EnvironmentsService) {}
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest<RawBodyRequest<Request>>();
const body = request.rawBody;
const secret = this.env.ChannelSecret;
const signature = createHmac('SHA256', secret).update(body).digest('base64');
const sig: string = request['headers']['x-line-signature'];
if (!sig) return false;
if (signature !== sig) return false;
return true;
}
}
header情報に含まれているx-line-signature
とチャンネルシークレットとbodyから作成した情報を比較する必要があります。加工前の素のBodyを取得する方法は、main.ts
の設定を以下に変更する必要があります。
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
rawBody: true,
bodyParser: true,
});
await app.listen(3000);
}
bootstrap();
署名検証用Guardを作成したので、LINEからのアクセスを検証して担保することができます。それでは、実際のオウム返しを実装していきます。
オウム返し処理
それでは、実際にオウム返しを設定していきます。LINE SDKで型定義が提供されているので、楽に実装することができます。先ほど設定したGuardを挟み込みます。コントローラーで処理の振り分けをサービスでは、メッセージイベントのテキストメッセージを取得してオウム返しを行う処理を記載しています。
line-bot配下のコントローラーとサービスになります。
import { WebhookEvent, WebhookRequestBody } from '@line/bot-sdk';
import { Body, Controller, Post, UseGuards } from '@nestjs/common';
import { LineBotSignatureGuard } from 'src/common/guard/line-bot-signature/line-bot-signature.guard';
import { LineBotService } from './line-bot.service';
@Controller('line-bot')
export class LineBotController {
constructor(private readonly botService: LineBotService) {}
@Post()
@UseGuards(LineBotSignatureGuard)
async getHello(@Body() req: WebhookRequestBody): Promise<string> {
const events: WebhookEvent[] = req.events;
if (events.length === 0) return;
const event = events[0];
if (event.type !== 'message' || event.message.type !== 'text') {
return;
}
this.botService.replyParrot(event.replyToken, event.message);
return;
}
}
import { TextEventMessage } from '@line/bot-sdk';
import { Injectable } from '@nestjs/common';
import { EnvironmentsService } from 'src/config/enviroments.service';
@Injectable()
export class LineBotService {
// 環境変数をロードする
constructor(private readonly env: EnvironmentsService) {}
async replyParrot(replyToken: string, textEventMessage: TextEventMessage): Promise<void> {
const client = this.env.createLinebotClient();
client.replyMessage({
replyToken: replyToken,
messages: [
{ type: 'text', text: textEventMessage.text },
],
});
return;
}
}
今回は、オウム返しなので以下のアクセスでは早期リターンします。
- Webhook検証用のアクセス
- メッセージイベント
- メッセージイベントだけどもテキストメッセージじゃない場合
Webhookで飛んでくるイベント情報では、公式ドキュメントを参考にしてください。いろいろなイベントをキャッチすることができます。
メッセージイベントには、テキスト以外にも複数のパターンがあり複合して送信される場合もあります。公式リファレンスはこちら。共通しているのは、各メッセージイベントにreplyTokenが含まれるという点です。こちらのトークンを使用して返信を作成することができます。
ngrokでHTTPS化してBOTのテスト
開発体験のためにもngrokを使用していきたいと思います。(変更の度にデプロイすると面倒なので…)簡単に言うと、「ローカルのポートをhttps化」するとこができます。登録してダッシュボードにログインするとセットアップ方法が書いてあります。公式がわかりやすいのでそちらを参考にして構築してください。一応コマンドをかいておきます。
choco install ngrok
認証情報を追加します。
ngrok config add-authtoken xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
起動しているポートをngrokを用いて設定します。サンプルでは80番ポートのSPAを実行しています。
ngrok http 3000
実行すると実行結果にhttps化されたURLが払い出されます。こちらをコンソール画面でWebhook URLに設定することで実際に動かすことができます。
おわり
ども!初めてLINE BOTを作成してから五年が経過していてびっくりしています。いろんな分野に手を出していてできることは増えているように感じます。過去にブログを書く習慣があまりなかったのが、悔やまれるところです。もし、五年前から書いていれば今頃400本ぐらいはブログを書いていることになっていたでしょうw。
今年は200本を目指して頑張ります。
Twitterもよろしくお願いします。