miiboだけで画像生成LINE Botを作る - カスタムアクション


前回試したカスタムアクションで画像メッセージを送る仕組みを応用し、画像を動的に生成してLINEで送る仕組みを作ってみます。

https://qiita.com/n0bisuke/items/cbbfd9ea03d4aa042804

先に完成系はこんな感じです。

https://x.com/n0bisuke/status/1918585910389535176

## 画像送信の流れ

LINE Botの画像送信では画像送信をする際に画像ファイル直接送信をしているわけではなく、一度サーバーに画像をアップロードしてそのURLを参照する形で相手に送信しています。

なので画像を生成した後に

- STEP1. 画像生成
- STEP2. 画像をどこかのサーバーにアップロード
- STEP3. アップロードされた画像をLINEで送信

という3ステップの処理になります。

## 事前準備

STEP1では何かしら画像生成ができれば良いですが、OpenAIの`gpt-image-1`を使ってみたかったので使ってみます。

STEP2ではGyazoやimgurなどいくつかのサービスがありますが、使い方がシンプルだったのとmiiboのカスタムアクションで利用できたimgBBというサービスを今回利用してみます。

- imgBBのAPIキー
    - https://api.imgbb.com
- OpenAIのAPIキー (バリデーションチェック済み）
    - https://platform.openai.com/api-keys
- LINEのチャンネルアクセストークンとユーザーID
    - これは[前回の記事](https://qiita.com/n0bisuke/items/cbbfd9ea03d4aa042804)同様です。


## 1. カスタムアクションの作成

基本は画像送信と同じなのでカスタムアクション作成から。

作る時は結構苦戦しましたが、こちらがSTEP1,2,3をまとめたコードです。
3分クッキング的な。

名前は`create_image_and_send_line`というアクション名にしました。

```js
const result = {
    success: false,
    messages: [],
    error: null
};

try {
    // 入力値のバリデーション
    if (!input.prompt) {
        throw new Error('Prompt is required');
    }

    // 環境変数の検証
    if (!env.OPENAI_API_KEY) {
        throw new Error('OpenAI API key is not configured');
    }

    if (!input.to) {
        throw new Error('Recipient is required');
    }

    // 環境変数の検証
    if (!env.LINE_CHANNEL_ACCESS_TOKEN) {
        throw new Error('LINE Channel Access Token is not configured');
    }

    /**
    STEP1. 画像生成　
    **/
    const response = fetch('https://api.openai.com/v1/images/generations', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${env.OPENAI_API_KEY}`
        },
        body: JSON.stringify({
            model: 'gpt-image-1',
            prompt: input.prompt,
            n: 1,
            size: 'auto',
            output_format: 'jpeg',
            output_compression: 30 
        })
    });

    if (response.status !== 200) {
        throw new Error(`OpenAI API request failed: `);
    }

    const b64 = response.body.data[0].b64_json;
    const snippet = b64.substring(0, 10);
    
    /**
    STEP2. imgBBに画像のアップロード　
    **/
    const imgBBreqBody = 'image=' + encodeURIComponent(b64);
    // クエリパラメータ付き URL
    const imgBBurl = 
      'https://api.imgbb.com/1/upload'
      + '?expiration=600'
      + '&key=' + encodeURIComponent(env.IMGBB_API_KEY);
    
    const imgBBres = fetch(imgBBurl, {
      method: 'POST',
      headers: {'Content-Type': 'application/x-www-form-urlencoded'},
      body: imgBBreqBody
    });

    const uploadedUrl = imgBBres.body.data.display_url;

    /**
    STEP3. LINEに送信　
    **/
    const lineRes = fetch('https://api.line.me/v2/bot/message/push', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${env.LINE_CHANNEL_ACCESS_TOKEN}`
        },
        body: JSON.stringify({
            to: input.to,
            messages: [
                {
                    type: 'image',
                    originalContentUrl: uploadedUrl,
                    previewImageUrl: uploadedUrl
                }
            ]
        })
    });

    result.success = true;
    result.messages = ['Image generated successfully', snippet, imgBBurl, imgBBres, uploadedUrl];

} catch (err) {
    result.error = err.message;
}

result;
```

- 環境変数: 3つをそれぞれ
    - IMGBB_API_KEY
    - LINE_CHANNEL_ACCESS_TOKEN
    - OPENAI_API_KEY
- input Parameters: toがLINEの送信先ユーザー、promptは画像生成のプロンプト
    - to
    - prompt

> ![CleanShot 2025-05-04 at 16.07.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/d265faca-079b-4da4-8503-4c2742561d18.png)

## 2. Webhookの設定

次はWebhookの設定です。

基本的に前回同様の設定です。

Function Callingを指定しつつ、Function Desctriptionに以下のように指定します。

```
ユーザーからのリクエストに応じて画像生成のプロンプトを作成してLINEに送信をします。toにはLINEのIDが入りますが、特に指定がない場合はUdxxxxxxxxxxxxxxxxxxxxxxx（自身のユーザーID）を指定してください。
```

## 3. 試す。

"アルコールの化学式をモチーフにしたキャラクターの画像を作成してください。"などのメッセージを送ってみます。

https://x.com/n0bisuke/status/1918585910389535176

このように画像が生成されて、LINEに送信されました。

imgBBが処理が通信が重たく、表示がなかなかされないことがありましたが、上手くいっていれば少し待つと表示されます。

> ![CleanShot 2025-05-04 at 16.17.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/fa1f39c9-0eef-4c9b-87e6-b819471d5395.png)