【個人開発】Next.jsでLINE BOTアプリ作ってみた

# 目次
[1. アプリ概要](#アプリ概要)
[2. 使用技術](#使用技術)
[3. 開発](#開発)
[4. 終わりに](#終わりに)

# アプリ概要
### QRコード
友達追加お願いします！

<img src="https://github.com/user-attachments/assets/61e35050-e1da-474e-95fe-f18d63cee20e" width="220px" alt="請求書発行BOT">

### 機能
- LIFFのアプリ内で請求書を作成できる
- その請求書をLINEの友達に送信できる

### 作成動機
- 家族や友達とお金の貸し借りをよくする
- お金を返してと言いづらいことを解消したい
- 身近なアプリであるLINEでNext.jsを使用したものを作りたかった
- デプロイまでしたかった

### Githubリンク
https://github.com/yuchi1128/nextjs-line-invoice-bot


# 使用技術
### 技術一覧

- TypeScript (Next.js)
- Image Generation
- LIFF（LINEログインチャネル）
- LINE Official Account Manager
- Vercel
- Git
- Github


# 開発

### 開発の流れ
1. LINEログインチャネルを作成し、LIFFアプリを作成
2. Next.jsでWebアプリを作成し、LIFFアプリに登録
3. LINE公式アカウントを作成し、作成したLIFFアプリを埋め込む

### 1. LINEログインチャネルを作成しLIFFアプリを作成
LIFFアプリを作成するためにLINEログインチャネルを作成します。（LINEログインとLINEミニアプリ以外のチャネルにはLIFFアプリを追加できないみたいです。）
https://developers.line.biz/console/ にアクセスし、LINEアカウントでログインし、プロバイダーを作成し、そのプロバイダーの中にLINEログインチャネルを作成します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3809870/eb2e2bf1-d882-9379-0dd3-79b8dca974b2.png" width="500px" alt="請求書発行BOT">

ログインチャネルを作成したら、「LIFF」タブからLIFFアプリを作成します。
エンドポイントURLは一旦 `http://localhost:3000/` としておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3809870/35b6763e-904c-ae69-eaa1-524d2ca4287a.png)



### 2. Next.jsでWebアプリを作成

#### 環境変数
先ほど作成したLIFFアプリのLIFF IDをenvファイルに記載します。
LIFF IDはLIFFアプリ詳細画面に記載してあります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3809870/ad9c84b5-8853-4016-af85-c0cf7324befe.png" width="380px" alt="請求書発行BOT">

```.env.local
NEXT_PUBLIC_LIFF_ID=my-liff-id
```

#### 画像生成API
今回のアプリでは、ユーザーからの入力に応じて請求書の画像を動的に生成したいので、Next.jsの機能である Image Generation を利用してAPIを作成しました。

参考記事

https://www.newt.so/docs/tutorials/nextjs-og-image-generation

```src/app/api/og/invoice/route.ts
import { NextRequest } from "next/server";
import { ImageResponse } from "@vercel/og";

export const runtime = "edge";

export function GET(req: NextRequest) {
  if (req.method !== "GET") {
    return new Response("Method Not Allowed", { status: 405 });
  }

  try {
    const { searchParams } = new URL(req.url);

    const issueDate = searchParams.get("issueDate") || "";
    const dueDate = searchParams.get("dueDate") || "";
    const amount = searchParams.get("amount") || "";
    const message = searchParams.get("message") || "";

    return new ImageResponse(

      (略)
    
    );
  } catch (e: unknown) {
    if (e instanceof Error) {
      console.log(`${e.message}`);
    } else {
      console.log("An unknown error occurred");
    }
    return new Response("画像の生成に失敗しました", {
      status: 500,
    });
  }
}
```
例えば
`http://localhost:3000/api/og/invoice?issueDate=2024-09-25&dueDate=2024-10-01&amount=1000&message=支払いお願いします。`
というURLにアクセスすると

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3809870/5bab4795-1e42-755d-b19e-db4db14f10b4.png" width="430px" alt="請求書発行BOT">

このような画像が返ってきます。

#### フロントエンド
```src/app/page.tsx
"use client";

import { useEffect, useState } from 'react';
import liff from '@line/liff';

interface Profile {
  name: string;
  picture: string;
}

export default function Home() {
  const [isLoggedIn, setIsLoggedIn] = useState(false);
  const [profile, setProfile] = useState<Profile>({ name: '', picture: '' });
  const [amount, setAmount] = useState('');
  const [dueDate, setDueDate] = useState('');
  const [message, setMessage] = useState('');

  useEffect(() => {
    const liffId = process.env.NEXT_PUBLIC_LIFF_ID;
    console.log('LIFF ID:', liffId);
    console.log('All env variables:', process.env);

    if (!liffId) {
      console.error('LIFF ID is missing');
      return;
    }

    liff
      .init({ liffId })
      .then(() => {
        if (liff.isLoggedIn()) {
          const idToken = liff.getDecodedIDToken();
          setProfile({ name: idToken?.name ?? '', picture: idToken?.picture ?? '' });
          setIsLoggedIn(true);
        }
      })
      .catch((error) => {
        console.log('LIFF initialization failed', error);
      });
  }, []);

  const handleLogin = () => {
    liff.login();
  };

  const getCurrentDate = () => {
    const today = new Date();
    const year = today.getFullYear();
    const month = String(today.getMonth() + 1).padStart(2, '0');
    const day = String(today.getDate()).padStart(2, '0');
    return `${year}-${month}-${day}`;
  };

  function handleCreateInvoice() {
    if (!amount || !dueDate || !message) {
      alert('金額、期日、メッセージを入力してください');
      return;
    }

    const issueDate = getCurrentDate();

    const invoiceImageUrl = `https://nextjs-line-invoice-bot.vercel.app/api/og/invoice?amount=${amount}&dueDate=${dueDate}&issueDate=${issueDate}&message=${encodeURIComponent(message)}`;

    liff.shareTargetPicker([
      {
        type: "text",
        text: "下記の内容にて請求書をお送りいたしますので、ご確認ください。",
      },
      {
        type: "image",
        originalContentUrl: invoiceImageUrl, 
        previewImageUrl: invoiceImageUrl,
      },
      {
        type: 'text',
        text: `このメッセージは請求書送信BOTから送信されています。\nhttps://lin.ee/qeZlCxi`,
      },
    ]);
  }

  return (

    (略)

  );
}
```

#### デプロイ
今回はVercelを使ってデプロイしました。
Vercelの管理画面の Settings の Environment Variables からLIFF IDを設定する必要があります。
デプロイが完了したら、LIFFアプリのエンドポイントURLをデプロイ先のURLに変更します。

### 3. LINE公式アカウントを作成し、作成したLIFFアプリを埋め込む

#### LIFFの設定
LIFFアプリを自分以外の人が使えるように、ステータスを「開発中」から「公開済み」に変更します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3809870/544a184e-1fbd-3721-d55f-df982b2c9a39.png" width="300px" alt="請求書発行BOT">

またLIFFアプリのシェアターゲットピッカーはオンにしておきます。

#### 公式LINEアカウントの作成
https://manager.line.biz/ にアクセスし公式LINEアカウントを作成します。

#### LIFFアプリを埋め込む

作成した公式アカウント内でLIFFアプリを使用します。
LIFFアプリのURLはLIFFアプリの管理画面に記載されています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3809870/1cceddda-a507-504e-5894-94b708a595cb.png" width="380px" alt="請求書発行BOT">　

今回はリッチメニューから作成したLIFFアプリにアクセスできるようにしました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3809870/e2b48007-24a0-1850-dff5-a518a4eeae02.png" width="250px" alt="請求書発行BOT">　　<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3809870/3b139552-4ddf-dbaf-9c3d-2a7e0d6e0281.png" width="250px" alt="請求書発行BOT">

色々面白い設定もできて触っていて楽しかったです。


# 終わりに
今回作成したアプリはシンプルなものなので、機能を追加してもっと便利なアプリにしたいと思いました。
はじめてLINEのアプリを作成してみて、さまざまな機能があることを知り、使いこなせるともっと面白くて便利なアプリを作成できると思いました。


# 追記
このアプリを改良して、機能の追加やデザインの修正をしてみました。

https://qiita.com/yuchi1128/items/de8d5f0cc136838d64cc