9
8

Node.jsでLINE Bot開発してみた(備忘録)

Last updated at Posted at 2023-12-12

はじめに

BitStar Advent Calender12日目を担当するBitStarの@tak_11です!

BitStarに入社し半年ほどが経った頃、新しいプロダクト開発でReact, TypeScriptを使うことになりました。

未経験のバックエンドエンジニアとして入社したものの、
React, TypeScriptよくわからん!!けどなんかかっこいい!!自分も触りたい!!
という不純な動機でとりあえずTypeScriptの勉強を始めたのですが、その時のことを備忘録を兼ねて記事にしてみます。

line公式ドキュメントを参考に、送信したメッセージをオウム返しするline botを作成しました。
というかほとんどJavaScriptで書かれているものをTypeScriptに書き直しただけです笑
https://line.github.io/line-bot-sdk-nodejs

実際に作成したのは2023/2頃のため、現在の仕様と異なっている場合があります。
あくまで参考程度に見ていただけると幸いです。

準備

LINE Developersでアカウント作成

line botの作成にはLINE Developersでアカウントを作成してアクセストークン等を取得する必要があります。
こちらに関しては公式や他の方の記事で詳しく説明されているので割愛させていただきます。

ディレクトリ作成

設定はデフォルトで問題ないので-yとします。

ターミナル
$ npm init -y

次に必要なパッケージをインストールします。

ターミナル
$ npm i @line/bot-sdk dotenv express

次に開発環境でのみ必要なパッケージをインストールします。

ターミナル
$ npm i -D @types/express @types/node nodemon

tsconfigを初期化します。

ターミナル
$ tsc --init

最後にtsconfigの設定を少し書き換えます。

tsconfig.json
"rootDir": "./src"
"outDir": "./dist"

これで準備は完了です。

実装

それでは実装していきましょう。
srcディレクトリを作成し、app.tsファイルを作成します。

ターミナル
$ mkdir src
$ touch src/app.ts

サーバーを立ち上げる

lineメッセージを受け取れるようにする前に、まずはローカルにサーバーを立ち上げてアクセスできるか確認しましょう。

src/app.ts
import express, { Request, Response } from "express";

const app = express();
const PORT = 3000;

app.listen(PORT);

app.get("/", (_req: Request, res: Response) => {
  res.send("hello, world!");
});

ターミナルでコマンドを実行し、typescriptをコンパイルします。

ターミナル
$ tsc -w

別のターミナルを開いて、コンパイルしたファイルを実行します。
nodemonをインストールしているので実行したファイルに変更があればサーバーを自動で再起動してくれます。

ターミナル
$ nodemon dist/app.js

localhost:3000にアクセスして、
image.png
と表示されれば成功です!

メッセージを受け取れるようにする

取得したアクセストークン等を環境変数として使用するため、dotenvを使用します。
まずは新しく.envファイルを作成します。

ターミナル
$ touch .env

取得したチャネルシークレットとアクセストークンを.env ファイルに記述します。

.env
LINE_CHANNEL_SECRET="取得したチャネルシークレット"
CHANNEL_ACCESS_TOKEN="取得したアクセストークン"

.env に記述した情報を読み込みます。

src/app.ts
import dotenv from "dotenv";
dotenv.config();

const config: Config = {
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN,
  channelSecret: process.env.LINE_CHANNEL_SECRET,
};

次に必要なモジュールをインポートします。

src/app.ts
import { middleware, Config, MiddlewareConfig } from "@line/bot-sdk";

受け取ったlineメッセージをコンソールに出力したいと思います。
まずは/webhookでPOSTリクエストを受け付けれるようにします。

src/app.ts
app.post("/webhook", middleware(config as MiddlewareConfig), (req: Request, _res: Response) => {
   
});

先ほどインポートしたmiddleware関数を第二引数で指定します。
これでリクエストを受け取った際にそのリクエストが不正でないか確認し、渡ってきたwebhookオブジェクトをJSONとして受け取れるようにします。

また、middleware関数の引数としてチャネルシークレットの値が必要です。
先ほど定義したconfigの中に定義されているのでそちらを使いましょう。
引数の型はMiddlewareConfigと設定されているので型アサーションして記述します。

次にリクエストを受け取った時の処理を追加します。

src/app.ts
app.post("/webhook", middleware(config as MiddlewareConfig), (req: Request, _res: Response) => {
+  console.log(req.body.events[0].message);
});

req.body.eventsの中に配列でwebhookEventオブジェクトが格納されており、そのオブジェクトのmessageプロパティで送信したメッセージ内容を確認できます。

ngrokを使って外部からメッセージを送れるようにする。

次にngrokを使って外部にアプリケーションを公開します。
ちなみにエングロックと読むみたいです。

ターミナル
$ brew install ngrok

アカウントの作成もしましょう。
https://ngrok.com/download

アカウントが作成できたらターミナルでプロジェクトのルートパスに移動し、以下のコマンドを実行します。

ターミナル
$ ngrok http 3000

すると以下のURLが表示されます。

ターミナル
Forwarding https://{hogehoge}.jp.ngrok.io -> http://localhost:3000      

https://{hogehoge}.jp.ngrok.io にアクセスするとサーバー立ち上げの際に確認した、hello, world!が表示されます。
これでサーバーを立ち上げているPC以外からもアクセスできるようになりました!
ちなみに{hogehoge}の部分はngrokを起動するたびに変わる乱数なので再起動ごとにURLが変わります。

最後にLINE Developersコンソールを開き、「Messaging API設定」からWebhook URLを設定します。
ここに先ほどngrokで取得したURLに/webhookを追加して記入します。
image.png

実際にメッセージを送ってみる

まずはLINE Developersのコンソールにline botのQRコードがあるので友達追加しましょう。
そして追加したline botのトークルームで試しに「テスト」と送ってみましょう。
image.png

するとnodemonを実行しているターミナルにメッセージ内容が表示されているはずです。

ターミナル
{ type: 'text', id: 'xxxxxxxxxxxx', text: 'テスト' }

オウム返ししてみる

送信したメッセージをオウム返しするようにします。
追加で必要なモジュールをインポートします。

src/app.ts
// Client, ClientConfig, WebhookEventを追加
- import { middleware, Config, MiddlewareConfig } from "@line/bot-sdk";
+ import { Client, middleware, Config, ClientConfig, MiddlewareConfig, WebhookEvent } from "@line/bot-sdk";

以下のコードを追加します。

src/app.ts
const client = new Client(config as ClientConfig);

const handleEvent = (event: WebhookEvent) => {
  if (event.type !== "message" || event.message.type !== "text") {
    return Promise.resolve(null);
  }

  return client.replyMessage(event.replyToken, {
    type: "text",
    text: event.message.text,
  });
};

handleEvent関数では、まず渡ってきたwebhookイベントの中身がテキストメッセージか確認し、違っていればnullを返します。
テキストメッセージであればreplyMessage関数を呼び出し返信します。
第一引数でeventのreplyTokenを指定し、第二引数に返したいメッセージを格納したオブジェクトを指定します。
typeプロバティには"text"を指定し、オウム返しをしたいのでtextプロパティには渡ってきたメッセージをそのまま格納します。

これでオウム返しする関数の完成です。

ではこれをメッセージが送られてきた時に実行されるようにします。
先ほど書いたコードを一部修正します。

src/app.ts
app.post("/webhook", middleware(config as MiddlewareConfig), (req: Request, res: Response) => {
-  console.log(req.body.events[0].message);
+  Promise.all(req.body.events.map(handleEvent)).then((result) => {
+    res.json(result);
+  });
});

eventsに格納されてるのはWebhookEventオブジェクトの配列です。
また、handleEventで呼び出されるreplyMessageの戻り値はPromiseオブジェクトです。
なのでmapメソッドでPromiseオブジェクトの配列を作成し、Promise.allの引数として渡します。
最後にthenメソッドを繋げてレスポンスを定義したら完了です。

オウム返しを確認する

再度トークルームで「テスト」と送ってみましょう。
image.png

振り返り

いかがだったでしょうか?
改めて振り返るとなぜReact, TypeScriptの組み合わせではなくNode.js, TypeScriptだったのか。笑

最後に

BitStarでは最初に触れたReact, TypeScriptやRuby, Railsを活用した開発を行っており、一緒に開発できるエンジニアを積極募集中です!

ご興味のある方はぜひ以下の募集情報をご確認ください!

9
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
8