nest.jsでLINE Botオウム返しを作成する

nest.jsのLINEBotオウム返し
◆ Live配信スケジュール ◆
サイオステクノロジーでは、Microsoft MVPの武井による「わかりみの深いシリーズ」など、定期的なLive配信を行っています。
⇒ 詳細スケジュールはこちらから
⇒ 見逃してしまった方はYoutubeチャンネルをご覧ください
【5/21開催】Azure OpenAI ServiceによるRAG実装ガイドを公開しました
生成AIを活用したユースケースで最も一番熱いと言われているRAGの実装ガイドを公開しました。そのガイドの紹介をおこなうイベントです!!
https://tech-lab.connpass.com/event/315703/

ども!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が提供している公式SDKnest.jsで環境変数を便利にする追加のモジュールですね。

npm i --save @nestjs/config @line/bot-sdk

LINE Messaging API側の設定としては、以下の記事を参考にしてもらえればと思います。

いまさらGASでLineBotを作る

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で飛んでくるイベント情報では、公式ドキュメントを参考にしてください。いろいろなイベントをキャッチすることができます。

LINEの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もよろしくお願いします。

アバター画像
About 龍:Ryu 107 Articles
2022年入社で主にフロントエンドの業務でTailwindと遊ぶ日々。お酒とうまいご飯が好きで、運動がちょっと嫌いなエンジニアです。しゃべれるエンジニアを目指しておしゃべりとブログ執筆に注力中(業務もね)//
ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

0人がこの投稿は役に立ったと言っています。


ご覧いただきありがとうございます。
ブログの最新情報はSNSでも発信しております。
ぜひTwitterのフォロー&Facebookページにいいねをお願い致します!



>> 雑誌等の執筆依頼を受付しております。
   ご希望の方はお気軽にお問い合わせください!

Be the first to comment

Leave a Reply

Your email address will not be published.


*


質問はこちら 閉じる