24日目にLINEミニアプリが完成する初心者 ── Day14. 問い合わせメッセージ

# はじめに

前回は「24 日目に LINE ミニアプリが完成する初心者 ── Day13. 問い合わせ機能の実装」と題して、Flex Message を使用した問い合わせ種別選択の実装を行いました。

https://qiita.com/non_watanabe/items/e6cad008b5cc0e8d5273

<br/>
今回は、前回の続きで問い合わせ内容の入力を促し、入力完了後にはお礼メッセージを送るよう実装していきたいと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2743174/7fa19c73-ff30-49f7-9bcc-649e385338a9.png)

# リファクタリング

前回実装したコードの一部をリファクタリングします。

問い合わせ種別を options としてまとめておきます。

```ts:src/nextjs/pages/api/webhook.ts
      const options = [
        "物件情報の問い合わせ",
        "お家探しの情報収集のやり方",
        "住宅購入資金の考え方・予算について",
        "新築と中古どちらを買うべきか？",
        "リノベーションの流れについて",
        "湘南エリアの魅力",
        "リノベ不動産辻堂羽鳥店について",
        "その他",
      ];
```

<br/>
これでflexMessageで繰り返しになっている箇所をmapとしてまとめて記述したいと思います。

```ts:src/nextjs/pages/api/webhook.ts
        const flexMessage = {
          type: "flex",
          altText: "問い合わせカテゴリ",
          contents: {
            type: "bubble",
            body: {
              type: "box",
              layout: "vertical",
              contents: [
                {
                  type: "box",
                  layout: "vertical",
                  contents: [
                    {
                      type: "text",
                      text: "お問い合わせの種類を選択してください。",
                      weight: "regular",
                      offsetStart: "xxl",
                      margin: "sm",
                      offsetTop: "md",
                    },
                    {
                      type: "separator",
                      margin: "xxl",
                      color: "#e5e5e5",
                    },
                  ],
                  backgroundColor: "#fafafa",
                },
                {
                  type: "box",
                  layout: "vertical",
                  contents: [
                    ...options.map((option) => ({
                      type: "button",
                      action: {
                        type: "message",
                        label: option,
                        text: option,
                      },
                    })),
                  ],
                  paddingTop: "md",
                  paddingBottom: "md",
                },
              ],
              paddingAll: "none",
            },
            styles: {
              footer: {
                separator: true,
              },
            },
          },
        };
```

<br/>
メッセージ送信処理は関数としてまとめておきたいと思います。

```ts:src/nextjs/pages/api/webhook.ts
async function sendReply(replyToken, messages) {
  const url = "https://api.line.me/v2/bot/message/reply";
  await fetch(url, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.NEXT_PUBLIC_LINE_ACCESS_TOKEN}`,
    },
    body: JSON.stringify({ replyToken, messages }),
  });
}
```

# 返信内容の追加

リファクタリングが完了したので、ここから実際に返信内容を追加していきます。

先にステータスが保持できるようコード冒頭で Map を作成しておきます。

```ts:src/nextjs/pages/api/webhook.ts
const userState = new Map();
```

今回はユーザー ID ごとに状態を追跡していきます。

これに伴い、お問い合わせがあった際に`userId`で`userState`を更新する行を追加しておきます。

```ts:src/nextjs/pages/api/webhook.ts
      if (userMessage === ">お問い合わせ") {
        userState.set(userId, { step: "categorySelection" });

        const flexMessage = {
          // （中略）
        };

        await sendReply(event.replyToken, [flexMessage]);
      }
```

## お問い合わせ内容確認

問い合わせ種別が選択されたら、「問い合わせ内容を入力してください。」という返信を返すよう追加していきます。

`else if`として`userMessage`が options に含まれているかを確認します。

```ts:src/nextjs/pages/api/webhook.ts
      if (userMessage === ">お問い合わせ") {
        // （中略）
      } else if (options.includes(userMessage)) {
        userState.set(userId, { step: "detailsInput", category: userMessage });

        await sendReply(event.replyToken, [
          { type: "text", text: "お問い合わせ内容を入力してください。" },
        ]);
      }
```

## お礼メッセージ

上記同様`else if`で条件を追加していきます。

```ts:src/nextjs/pages/api/webhook.ts
      else if (options.includes(userMessage)) {
        // （中略）
      } else if (
        userState.has(userId) &&
        userState.get(userId).step === "detailsInput"
      ) {
        userState.get(userId);

        userState.delete(userId);

        await sendReply(event.replyToken, [
          {
            type: "text",
            text: "お問い合わせいただきありがとうございます！ご回答には通常2営業日以内に返答いたします。なにかご不明点があれば、お気軽にお問合せください！",
          },
        ]);
      }
```

<br/>
コードの全体像は以下の通りです。

```ts:src/nextjs/pages/api/webhook.ts
const userState = new Map();

export default async function handler(req, res) {
  if (req.method === "POST") {
    const { events } = req.body;

    if (!events || events.length === 0) {
      return res.status(200).json({ message: "No events" });
    }

    const event = events[0];
    if (event.type === "message" && event.message.type === "text") {
      const userMessage = event.message.text;
      const userId = event.source.userId;

      const options = [
        "物件情報の問い合わせ",
        "お家探しの情報収集のやり方",
        "住宅購入資金の考え方・予算について",
        "新築と中古どちらを買うべきか？",
        "リノベーションの流れについて",
        "湘南エリアの魅力",
        "リノベ不動産辻堂羽鳥店について",
        "その他",
      ];

      if (userMessage === ">お問い合わせ") {
        userState.set(userId, { step: "categorySelection" });

        const flexMessage = {
          type: "flex",
          altText: "問い合わせカテゴリ",
          contents: {
            type: "bubble",
            body: {
              type: "box",
              layout: "vertical",
              contents: [
                {
                  type: "box",
                  layout: "vertical",
                  contents: [
                    {
                      type: "text",
                      text: "お問い合わせの種類を選択してください。",
                      weight: "regular",
                      offsetStart: "xxl",
                      margin: "sm",
                      offsetTop: "md",
                    },
                    {
                      type: "separator",
                      margin: "xxl",
                      color: "#e5e5e5",
                    },
                  ],
                  backgroundColor: "#fafafa",
                },
                {
                  type: "box",
                  layout: "vertical",
                  contents: [
                    ...options.map((option) => ({
                      type: "button",
                      action: {
                        type: "message",
                        label: option,
                        text: option,
                      },
                    })),
                  ],
                  paddingTop: "md",
                  paddingBottom: "md",
                },
              ],
              paddingAll: "none",
            },
            styles: {
              footer: {
                separator: true,
              },
            },
          },
        };

        await sendReply(event.replyToken, [flexMessage]);
      } else if (options.includes(userMessage)) {
        userState.set(userId, { step: "detailsInput", category: userMessage });

        await sendReply(event.replyToken, [
          { type: "text", text: "お問い合わせ内容を入力してください。" },
        ]);
      } else if (
        userState.has(userId) &&
        userState.get(userId).step === "detailsInput"
      ) {
        userState.get(userId);

        userState.delete(userId);

        await sendReply(event.replyToken, [
          {
            type: "text",
            text: "お問い合わせいただきありがとうございます！ご回答には通常2営業日以内に返答いたします。なにかご不明点があれば、お気軽にお問合せください！",
          },
        ]);
      }
    }

    return res.status(200).json({ message: "Event processed" });
  } else {
    res.setHeader("Allow", ["POST"]);
    return res.status(405).json({ error: `Method ${req.method} Not Allowed` });
  }
}

// メッセージ返信
async function sendReply(replyToken, messages) {
  const url = "https://api.line.me/v2/bot/message/reply";
  await fetch(url, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.NEXT_PUBLIC_LINE_ACCESS_TOKEN}`,
    },
    body: JSON.stringify({ replyToken, messages }),
  });
}
```

# デプロイ

以下のコマンドでいつも通りデプロイを行っていきましょう。

```terminal
$ netlify deploy --build --prod
```

# 動作確認

LINE の画面で「>お問い合わせ」->「お問い合わせ種別の選択」と進んでみると、先ほど設定したお問い合わせ内容確認と、お礼メッセージが表示されると思います！

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2743174/22ebfee7-3665-87df-93ee-397922c68e43.png" width="50%" />

# まとめ

ここまでで、Flex Message による問い合わせの流れが完成しました。

次回は、問い合わせのあった内容のスプレッドシート出力を実装していきたいと思います。

残りは 10 日！
気になる方は是非フォローやカレンダー購読をお願いします:star: