LINE Massaging API 応答メッセージの送信

LINE Massaging API 応答メッセージの送信

投稿日: 2024年10月29日

Tips
要約
  • LINEのWebhookイベントで参加イベントを受け取り、応答メッセージを送信する実装を説明。
  • 参加したユーザーにURLと合言葉を含むメッセージを送るために、データベースに登録し、APIリクエストを行う。
  • チャネルアクセストークンやreplyTokenなどを使用し、適切なリクエストボディを構成してメッセージを送信する方法を解説。

はじめに

前回の続編です。

データベースに必要な情報の登録が出来たので、登録したグループやユーザーな必要なURLと合言葉(ポケモンの名前)をLINEで送信します。

使うエンドポイント探す

メッセージ送信する方法は5つあります。

応答メッセージ

プッシュメッセージ:1対1

マルチキャストメッセージ:1対多(ユーザーID指定)

ナローキャストメッセージ:1対多(絞り込み配信)

ブロードキャストメッセージ:1対多(すべての友だち)

大きく分けると2つ。

ユーザーからのメッセージやアクションに応答する(応答メッセージ)

任意のタイミングでメッセージを送信する

ユーザーからのメッセージやアクションに応答するというのは、webhookのイベントが発生した時と解釈しました。

今回は参加イベントの発火に応答して返信したいので、応答メッセージで実装するのかなと考えました。

応答メッセージを送る

公式にリクエストの仕方が詳しく書いてあります。

ポイントとなるのは応答トークンを利用することかなと思います。

webhookイベントオブジェクトにreplyTokenが含まれていて、それをリクエストボディに含める必要があります。

下記、公式のサンプルコードです。

curl -v -X POST https://api.line.me/v2/bot/message/reply \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {channel access token}' \
-d '{
    "replyToken":"nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
    "messages":[
        {
            "type":"text",
            "text":"Hello, user"
        },
        {
            "type":"text",
            "text":"May I help you?"
        }
    ]
}'

Shell知らないですけど、見たら何となくわかりますね。

methodPOST

エンドポイントはhttps://api.line.me/v2/bot/message/reply

headersContent-Type: application/jsonAuthorizationBearerスペース挟んでchannel access tokenが必要。

リクエストボディにreplyTokenmessagesという配列が必要。

messagestypetextという文字列のプロパティが必要。こんなところっぽいです。

文字の強調の仕方が正しいかはよくわかりませんw

公式にとにかく詳しく書いていますのでご安心ください。

では、出来上がったコードをぺたっと貼ります。

コード

前回と重複ありますのでご了承ください!

webhookのエンドポイントURLに設定しているところからです。

import { NextRequest, NextResponse } from "next/server";
import { WebhookRequest } from "./_types/WebhookRequest";
import { buildPrisma } from "@/app/_utils/prisma";
import { sendMassage } from "./_utils/sendMassage";
import { pokeApi } from "./_utils/pokeApi";
import { randomBytes } from "crypto";
export const POST = async (req: NextRequest) => {
  const prisma = await buildPrisma();

  try {
    const body: WebhookRequest = await req.json();
    const events = body.events;

    const joinEvent = events.find(event => event.type === "join");

    if (!joinEvent)
      return NextResponse.json(
        {
          message: "参加イベントじゃない",
        },
        { status: 200 }
      );

    const { groupId, roomId, userId } = joinEvent.source;
    const lineId = groupId || roomId || userId;
    if (!lineId) throw new Error("IDの取得が出来ませんでした");

    //既存のIDじゃないか確認
    const room = await prisma.room.findUnique({
      where: {
        roomUrlId: lineId,
      },
    });

    if (room)
      return NextResponse.json(
        {
          message: "登録済のroomid",
        },
        { status: 200 }
      );

    //合言葉の生成
    const pokeName = await pokeApi();
    //URLの生成
    const buffer = randomBytes(16);
    const roomUrlId = buffer.toString("hex");

    await prisma.room.create({
      data: {
        adminUserId: process.env.ADMIN_USER as string,
        lineId,
        roomUrlId,
        password: pokeName,
      },
    });

    await sendMassage(joinEvent.replyToken, roomUrlId, pokeName);

    return NextResponse.json(
      {
        message: "success",
      },
      { status: 200 }
    );
  } catch (e) {
    if (e instanceof Error) {
      console.log(e.message);
      return NextResponse.json({ error: e.message }, { status: 400 });
    }
  }
};

実際に送信しているのがこちら。

import { fetcher } from "../../_utils/fetcher";
import { SendMassageResponse } from "../_types/SendMassageResponse";
import { SendMessageRequest } from "../_types/SendMessageRequest";
export const sendMassage = async (
  replyToken: string,
  roomId: string,
  password: string
) => {
  const endpoint = "https://api.line.me/v2/bot/message/reply";
  const options = {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.CHANNEL_ACCESS_TOKEN}`,
    },
    body: {
      replyToken,
      messages: [
        {
          type: "text",
          text: `予定を登録するページのURL:${process.env.NEXT_PUBLIC_APP_URL}/room/${roomId}\n合言葉:${password}`,
        },
      ],
    },
  };
  try {
    const resp = await fetcher<SendMassageResponse, SendMessageRequest>(
      endpoint,
      options
    );
    return resp.sentMessages;
  } catch (error) {
    console.error(`Error fetching data for ID ${roomId}:`, error);
    throw new Error("LINE応答に失敗しました");
  } finally {
  }
};

ポイント

チャネルアクセストークン

headersのAuthorizationに設定しているchannel access tokenですがこちらは、LINE developerで確認できます。

channel access tokenもいくつかあります

今回は長期を使いました。

LINE developerのMessaging API設定の画面の一番下にあります。

これを環境変数に設定しています。

replyToken

こちらはwebhookイベントオブジェクトに含まれている今回のイベントを識別するトークンというイメージです。

このイベントに対して応答メッセージを送る際に使用する応答トークン

応答メッセージの時にしか使わないようです。

こちらはリクエストボディに必ず含めないといけないです。

const joinEvent = events.find(event => event.type === "join");
//中略
await sendMassage(joinEvent.replyToken, roomUrlId, pokeName);

joinEvent.replyTokenをsendMessagie.tsに渡して送信時に使用しています。

body: {
      replyToken,
      messages: [
        {
          type: "text",
          text: `予定を登録するページのURL:${process.env.NEXT_PUBLIC_APP_URL}/room/${roomId}\n合言葉:${password}`,
        },
      ],
    },

messagesは一つであろうと必ず配列なので注意しましょう。

おわりに

これで公式アカウントを友だち登録したり、グループのメンバーに追加した時に、

  1. webhookイベント受け取る
  2. 参加イベントでなければなにもしない
  3. 必要な処理(PokéAPIでランダムにポケモンの名前ゲット)をして
  4. 応答メッセージの送信をする

までの実装が出来ました!

しょうもないことで私は時間溶かしましたが、意外とハードル低いですよね・・!

また通知が必要なことがあり、今回の情報に加えてデータベースに登録必要な気がするので、もう少しドキュメント見ながら最初に登録すべき項目は見極めていきたいと思います(多分行き当たりばったりになると思いますがw)!

シェア!

Threads
user
吉本茜
山口在住/二児の母
Loading...
記事一覧に戻る
Threads
0