Next.jsでLINE Messaging APIを叩いてFlex messageを送る方法（応答メッセージ）

Next.jsを使ってLINEの予約機能を作りたい！と考えて始まった個人開発の記録。
前回の記事

https://qiita.com/kuma_radcof/items/b919b6720876a7b00b12

前々回の記事

https://qiita.com/kuma_radcof/items/87d2a83acbe671263bc8

今回はただのテキストメッセージではなくFlex messageを送るために必要な手順を書いていきたいと思います。
オブジェクトの書き方が複雑、というか自分は理解するのに時間がかかったので、Youtubeや先人たちの知恵、メンター（ChatGPT）にも頼りながら実装していきました。

# ディレクトリ構造
今回はAPI利用の部分だけを抜き出しますが、`src/app/webhook-flexreply`というディレクトリを作成し、その中に`route.ts`と、もう一つFlex message切り分けのための`flex-message.ts`というファイルを作成しました。

## プログラム
```typescript:src/app/webhook-flexreply/flex-message.ts
export function createflexMessageBody(replyToken: string, userMessage: string|null){
    return {
        "replyToken": replyToken,
        "messages": [
                {
                    "type": "text",
                    "text": `Hi! ${userMessage}だね！`
                },
                {
                    "type": "text",
                    "text": "希望日を教えてください"
                },
                {
                    "type": "flex",
                    "altText": "フレックスメッセージ",
                    "contents": {
                        "type": "bubble",
                        "size": "giga",
                        // ヘッダーパーツ
                        "header": {
                                "type": "box",
                                "layout": "vertical",
                                "contents": [
                                        {
                                                "type": "text",
                                                "text": "予約"
                                        },
                                ]
                        },
                    // ボディーパーツ
                    "body": {
                            "type": "box",
                            "layout": "vertical",
                            "contents": [
                                    {
                                            "type": "text",
                                            "text": "希望日を下のボタンから選んでください"
                                    }
                            ]
                    },
                    // フッターパーツ
                    "footer" : {
                        "type": "box",
                        "layout": "vertical",
                        "contents" :[
                            {
                                "type": "button",
                                "style": "primary",
                                "action": {
                                    "type": "postback",
                                    "label": "today",
                                    "data": "action=today",
                                    "displayText": "今日の予約"
                                }
                            },
                            {
                                "type": "button",
                                "style": "secondary",
                                "action": {
                                        "type": "message",
                                        "label": "明日の予約",
                                        "text": "tomorrow",
                                }
                            },
                            {
                                "type": "button",
                                "style": "link",
                                "action": {
                                    "type": "datetimepicker",
                                    "label": "日時を指定する",
                                    "data": "action=selectedData",
                                    "mode": "datetime",
                                }
                            }
                        ]
                    },
                    // 全体のスタイル定義
                    "styles": {
                            "header":{
                                    "backgroundColor": "#f0f8ff"
                            },
                            "body": {
                                    "backgroundColor": "#ffff00"
                            },
                            "footer": {
                                    "separator": true,
                                    "backgroundColor": "#7fff00"
                            }
                    }
                }
            }
        ]
    }
}
```
Flex messageはどのようなアクションで送るかによっても変わってきますが、今回は応答メッセージとしてFlex messageを送りたいというイメージでしたので、replyTokenを取得してメッセージ部分を作成しています。
`type: flex`とし、中身を色々と書いていきます。

公式のドキュメントだと、あっちこっちにリンク飛んでちょっとわかりにくいと感じましたが、
flex messageの中に、contentsを用意しておき、そこにはtype・size・header・body・footer・stylesを指定することができます。
header・body・footerについては、さらにその中身として`type：box`やlayoutを指定し、個別のcontentsを指定していきます。
個別のcontentsではtypeにtextがあったりbuttonがあったりできます。
buttonの場合には、そこからさらにどんなアクションを選択するかということを決めることができます。
…というふうに、結構ネストして色々作っていくような感じなので、慣れるまでは大人しくSimulatorを使った方がいいかもしれません。

```typescript:src/app/api/webhook-flexreply/route.ts
import {createflexMessageBody} from "./flex-message";
// webhookとして応答メッセージを送る
export async function POST(request: Request){
	try{
		// LINEへのアクセストークン（.env管理）とエンドポイントURLの指定		
		const LINE_ACCESS_TOKEN = process.env.CHANNEL_ACCESS_TOKEN ? process.env.CHANNEL_ACCESS_TOKEN : process.env.NEXT_PUBLIC_CHANNEL_ACCESS_TOKEN;
		const ENDPOINT_URL = "https://api.line.me/v2/bot/message/reply";

		// 引数リクエストのjson受信
		const requestData = await request.json();
		if(!requestData || !requestData.events) {
				console.log("リクエストデータがありませんでした");
				return new Response("Bad request", {status:400});
		}
		// リクエストデータがあったときに、eventプロパティを取得しておく。
		const events = requestData.events;
		for (const event of events) {
				if(event.type === "message" && event.message.text === "予約"){
						const REPLY_TOKEN = event.replyToken;
						const userMessage = event.message.text;

						const flexMessageBody = createflexMessageBody(REPLY_TOKEN, userMessage);

						const options = {
								method: "POST",
								headers: {
										"Content-Type": "application/json",
										"Authorization": `Bearer ${LINE_ACCESS_TOKEN}`
								},
								body: JSON.stringify(flexMessageBody)
						}
						const response = await fetch(ENDPOINT_URL, options);

						if(!response.ok){
								const errorData = await response.json();
								console.error(
										`APIエラー：${errorData || "詳細不明"}`
								)
						}
				}
		}
	} catch(e: unknown){
		const errorMessage = e instanceof Error ? e.message : "不明なエラー";
		const responseStatus = {
			status: 400,
			headers: {
					"Content-Type": "application/json"
			}
		}
		return new Response(JSON.stringify({error: errorMessage}), responseStatus)
	}
}
```
という感じでAPIを作成し、あとは前回の記事同様にwebhookURLを指定してあげれば動作するようになります。


今度の課題は、日時選択アクションで帰ってきたデータを使ってGoogleカレンダーと連携することになっていきまs