🤖

LINE BotをNext.js + TypeScript + Netlify Functionsで作る

2022/02/13に公開

後でLIFFアプリも作ることを考えてNext.jsで、チャットボットのベースを作ります。
おうむ返しでは面白くないので、getProfile()でユーザーの名前を取得するところまでやってみたいと思います。

プロジェクトを作成

$ yarn create next-app --typescript sampleapp
$ cd sampleapp

まずは動作確認。

$ yarn dev

で起動させて別のターミナルからアクセスすると

$ curl http://localhost:3000/api/hello
{"name":"John Doe"}

OKですね。

tsconfig.jsonにbaseUrlpathsを追加しておきます。

  "compilerOptions": {"baseUrl": "./",
    "paths": {
      "~/*": ["./*"]
    },

デプロイ

GitHubにpushして、NetlifyでAdd new site>Import an existing projectします。
設定は特にいじらなくてOK。Functions directoryも空欄でOK。

デプロイできたらアクセスしてみます。

$ curl https://<your-domain>.netlify.app/api/hello
{"name":"John Doe"}

OKですね。

LINE Bot SDKを組み込む

$ yarn add @line/bot-sdk
$ mkdir lib
$ touch .env.local lib/line.ts pages/api/webhook.ts

↓は https://github.com/line/line-bot-sdk-nodejs/blob/next/examples/echo-bot-ts/index.ts を参考に。

lib/line.ts
import { ClientConfig, Client, middleware as lineMiddleware, MiddlewareConfig } from '@line/bot-sdk';

// Setup all LINE client and Express configurations.
const clientConfig: ClientConfig = {
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN || '',
  channelSecret: process.env.CHANNEL_SECRET,
};

const middlewareConfig: MiddlewareConfig = {
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN,
  channelSecret: process.env.CHANNEL_SECRET || '',
};

export const client = new Client(clientConfig);
export const middleware = lineMiddleware(middlewareConfig);

↓値はLINE Developersコンソールからコピーします。

.env.local
CHANNEL_ACCESS_TOKEN=********
CHANNEL_SECRET=******

↓のconfigrunMiddlewareは https://nextjs.org/docs/api-routes/api-middlewares を参考に。

pages/api/webhook.ts
import { NextApiHandler, NextApiRequest, NextApiResponse } from 'next'
import { WebhookRequestBody } from '@line/bot-sdk';
import { Middleware } from '@line/bot-sdk/lib/middleware';
import * as line from '~/lib/line';

export const config = {
  api: {
    bodyParser: false, // Necessary for line.middleware
  },
};

async function runMiddleware(req: NextApiRequest, res: NextApiResponse, fn: Middleware) {
  return new Promise((resolve, reject) => {
    fn(req, res, (result) => 
      result instanceof Error
        ? reject(result)
        : resolve(result)
    )
  });
}

const handler: NextApiHandler = async (req: NextApiRequest, res: NextApiResponse) => {
  try {
    if (req.method === 'POST') {
      // Validate request
      await runMiddleware(req, res, line.middleware);

      // Handle events
      const body: WebhookRequestBody = req.body;
      await Promise.all(body.events.map(event => (async () => {
        if (event.mode === 'active') {
          switch(event.type) {
            case 'message': {
              const name = event.source.userId
                ? (await line.client.getProfile(event.source.userId)).displayName 
                : 'User';
              await line.client.replyMessage(event.replyToken, {
                type: 'text',
                text: `Hi, ${name}!`
              });
              break;
            }
            case 'follow': {
              // Do something.
              break;
            }
          }
        }
      })()));
      res.status(200).end();
    } else {
      res.status(405).end();
    }
  } catch(e) {
    if (e instanceof Error) {
      res.status(500).json({ name: e.name, message: e.message });
    } else {
      res.status(500).end();
    }
  }
};

export default handler;

ビルドしてみる。

$ yarn build
yarn run v1.22.17
$ next build
info  - Loaded env from ***/sampleapp/.env.local
Failed to compile.

./node_modules/@line/bot-sdk/lib/middleware.ts:69:31
Type error: Object is of type 'unknown'.

  67 |       next();
  68 |     } catch (err) {
> 69 |       next(new JSONParseError(err.message, strBody));
     |                               ^
  70 |     }
  71 |   };
  72 |   return (req, res, next): void => {
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

まぢか。
とりあえずtsconfig.jsonに設定を追加してエラー回避😑

tsconfig.json
{
  "compilerOptions": {
    ...,
    "useUnknownInCatchVariables": false
  },
  ...
}
$ yarn build
yarn run v1.22.17
〜
✨  Done in 5.02s.

ということでビルドが通ったので、git commitしておきます。

Netlifyに環境変数を設定

.env.local ファイルに設定していたのと同じ内容を NetlifyのSite settings>Build & deploy>Environmentで設定します。

デプロイ

先ほどcommitしたソースをGitHubにpushします。
Netlify上でPublishedになったら、追加したAPIを叩いてみます。

$ curl -X POST -H "x-line-signature: dummy-signature" -H "Content-Type: application/json" -d '{"destination":"dummy","events":[]}' https://<your-domain>.netlify.app/api/webhook  
{"name":"Error","message":"signature validation failed"}

想定通りのエラーが返ってきたのでOK。
ということで、LINEのチャネルに登録します。

そしてLINEからこのチャネル当てにメッセージを送ってみると

動きました😄

Discussion