[TypeScript] deno deployとHonoでLINEbotを作る

本記事では、TypeScriptライブラリであるHonoとLine bot SDKを用いて、LINE botを作成する方法について紹介します。

https://hono.dev/

https://line.github.io/line-bot-sdk-nodejs/

TypeScriptを手軽に実行・デプロイするために、今回はDenoとdeno deployを使用していきます！

https://deno.com

# 1. LINE Developers Consoleに登録

LINE botを作成するには、LINEアカウントを用いてLINE Developers Consoleに登録する必要があります。

https://developers.line.biz/ja/

ユーザーが送信したメッセージに対して応答するbotを作成するには、[LINE Messaging API](https://developers.line.biz/ja/services/messaging-api/)を利用します。
Developers Consoleに登録出来たら、

1. プロバイダの作成
1. LINE Messaging APIを選択してチャネルの作成

の順に行います。この段階で、LINE botの名前やアイコンを登録します。（後から変更も可能です。）

LINE Developers Consoleの操作については[こちらの記事](https://qiita.com/teaparty/items/b48d8ea8496895e2f3d8)が詳しいので、併せて参考にしてみてください。

https://qiita.com/teaparty/items/b48d8ea8496895e2f3d8

続いて、応答設定を変更します。
Messageing API設定タブの中の「応答設定」から、**webhook以外をオフ**にしてください。
この設定をしないと、「申し訳ありませんが、このアカウントでは個別のお問い合わせを受け付けておりません」のような自動返信が入ります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/678625/1f1d66ef-4ea5-6389-4d50-2fd4fc9ff50a.png)


# 2. 自動返信を行うプログラムの作成

LINE botはwebhookという仕組みで動いています。
作成したLINE botにユーザーがメッセージを送信すると、自分が用意したサーバーにPOSTリクエストが送られてきます。そのPOSTリクエストを受け取った際に、返信を返すというプログラムを作成します。


プログラムについて、今回はNode.jsではなくDenoを使ってTypeScriptを書いていきたいと思います。
Denoを使えばTypeScriptに関するツールのインストールや設定ファイルの記述が不要ですし、deno deployというサービスに無料でサーバーを公開できます。

https://deno.com/

https://deno.com/deploy

:::note

#### エディタの設定

多くのエディタでは、デフォルトではTypeScriptに対してNode.js向けの設定が適用されるかと思います。そのため、設定を調整してDeno向けの設定が適用されるようにする必要があります。

筆者はVSCodeを使っているので、`.vscode/settings.json`に以下のような設定を書きました。

```json:.vscode/settings.json
{
  "deno.enable": true,
  "deno.lint": true,
  "deno.cacheOnSave": true,
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "denoland.vscode-deno"
}
```

その他のエディタの設定方法は[こちらの公式ドキュメント](https://docs.deno.com/runtime/manual/getting_started/setup_your_environment/)を参考にしてください。

https://docs.deno.com/runtime/manual/getting_started/setup_your_environment/

:::

続いてプログラムを作成していきます。
完成したプログラムの全体像はこちらになります。

```ts:main.ts
import { Hono, type HonoRequest } from "jsr:@hono/hono@4.4.12";
import {
  messagingApi,
  SignatureValidationFailed,
  validateSignature,
  type WebhookRequestBody,
} from "npm:@line/bot-sdk@9.2.2";

// LINE bot SDKの初期化
const CHANNEL_SECRET = Deno.env.get("CHANNEL_SECRET")!;
const client = new messagingApi.MessagingApiClient({
  channelAccessToken: Deno.env.get("CHANNEL_ACCESS_TOKEN")!,
});

// Honoの初期化
const app = new Hono();

app.get("/", (c) => c.text("hello world"));

// https://<ドメイン>/webhookに対するPOSTリクエストを受け付け
app.post("/webhook", async (c) => {
  // リクエストがLINEプラットフォームから送られたことを確認し、bodyをパース
  const request = await validateAndParseRequest(c.req);
  console.log(request);

  for (const event of request.events) {
    // メッセージイベントのみ処理する
    if (event.type !== "message" || event.message.type !== "text") {
      continue;
    }

    // event.message.textの中に受信したメッセージが入っている
    console.log(event.message.text);

    // LINE bot SDKを用いて返信する
    await client.replyMessage({
      replyToken: event.replyToken,
      messages: [{ type: "text", text: reply(event.message.text) }],
    });
  }

  return c.json({ status: "success" });
});

/**
 * リクエストを検証してbodyをパースする
 * Note:リクエストがLINEプラットフォームから送られたことを確認するために、ボットサーバーでリクエストヘッダーのx-line-signatureに含まれる署名を検証します。
 * ref: https://developers.line.biz/ja/reference/messaging-api/#signature-validation
 */
async function validateAndParseRequest(req: HonoRequest) {
  // bodyを取得
  const body = await req.text();
  // x-line-signatureヘッダーを取得
  const signature = req.header("x-line-signature")!;
  // 署名を検証し、LINEから送られたものではないと判断したらエラーを投げる
  if (!validateSignature(body, CHANNEL_SECRET, signature)) {
    throw new SignatureValidationFailed("signature validation failed", {
      signature,
    });
  }
  // bodyをパースして返す
  return JSON.parse(body) as WebhookRequestBody;
}

/**
 * 受信したメッセージに対する返信を作成する
 * @param message 受信したメッセージ
 * @returns 返信する文字列
 */
function reply(message: string) {
  // 現在はオウム返し（受信した内容をそのまま返信）
  return message;
}

export default app;
```

少し長いですが、全体像は以下のようになります。

1. Honoを使ってLINEからのPOSTリクエストを受け取る
1. 受け取ったリクエストに含まれるメッセージ内容を元に、`client.replyMessage`で返信メッセージを送信する

どのようなメッセージを返すかを制御しているのが`reply`関数です。現在は`reply`関数が受け取ったメッセージをそのまま返すようになっています。
この部分を独自の処理に置き換えることで、任意のメッセージを返すことができるようになります。

# 3. デプロイしてwebhookを登録

deno deployにサーバーをデプロイして、LINE botを動くようにしていきましょう。

deno deployにサーバーをデプロイするには、playgroundを利用する方法と、GitHub連携を利用する方法があります。今回はGitHub連携でデプロイしたいと思います。

### 3-1. deno deployにデプロイ

まずGitHubリポジトリを作成し、ソースコードをGitHubにアップロードします。
ファイル名はなんでもよいですが、ここでは`main.ts`というファイル名を使って話を進めていきます。

GitHubリポジトリにソースコードを上げたら、[deno deployのダッシュボード](https://dash.deno.com/account/overview)から新規プロジェクトを作成します。

https://dash.deno.com/account/overview

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/678625/f4fe77d0-d12c-69c1-ad3d-7fdfc7a60e78.png)

新規プロジェクトを作成すると色々設定を聞かれます。
まずリポジトリを選択後、プロジェクト名を設定します。
最終的にデプロイされるドメインが`https://<プロジェクト名>.deno.dev`になりますので、かっこいい名前を付けておきましょう。

使っているフレームワークなども聞かれますが、今回の場合はビルドステップ等も無いので空欄で大丈夫です。最後にentrypointのところで`main.ts`を選択し、「deploy project」を押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/678625/6f011072-c956-af10-24f5-3558a572adaf.png)

### 3-2. 環境変数の設定

最後にLINEとの連携設定です。

LINEから提供されるAPIトークンを、deno deployの環境変数に設定します。
設定する環境変数は以下の2つです。

- LINE Messaging API設定の「チャネルアクセストークン」から「チャネルアクセストークン（長期）」を選択し、`CHANNEL_ACCESS_TOKEN`に設定
- チャネル基本設定の「チャネルシークレット」をコピーし、`CHANNEL_SECRET`に設定

deno deployの環境変数の設定は、ダッシュボードのsettingsタブから行うことができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/678625/e9c65d6f-bbd8-f664-b9e1-25df2f795133.png)

### 3-3. Webhook URLの設定

LINE botにメッセージが送信された時に、どのサーバーにPOSTするかを設定します。
今回はdeno deployのサーバーのURLを設定します。

Messaging API設定の「Webhook URL」に、`https://<project-id>.deno.dev/webhook`を指定します。

これで完成です！

## 4. 試してみる

それでは実際にLINE botが動くかどうかを試してみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/678625/0f5c3752-3963-4b80-a7bb-174fdf70ab83.png)

ちゃんと動きましたね。

以上、deno deployとHonoでLINE botを作る方法について解説しました！