LoginSignup
11
4

More than 1 year has passed since last update.

Node-REDとRinnaAPIでAIお絵描きLINE Botを実装してみる

Last updated at Posted at 2022-11-06

Node-REDNode.js + express でおうむ返しのLINE Botを作れる方が主に対象となります。参考記事は掲載していますが、webhook URLなどわからないと難しいかもしれません

AIお絵描きが流行ったので私もやってみたい😍

そう思った方も多いかと思います。

stable diffusionの発表からいち早く実装されたこのBotは凄まじい登録者数を獲得しました。LINE Botだったら自分にもできそう!!私も作ってみたい!!と思いstable diffusionのGitHubを訪ねてみると、待ち受けているのは残酷な現実です。

英語

Python環境構築

ここで自分にはハードルが高いと感じ、諦めてしまった方もいらっしゃるのではないでしょうか。

本記事ではNode-REDrinna Developersを活用し、ノーコードでお絵描きLINE Botの実装を目指します。

下準備

Node-RED

この記事の通りRailway上にデプロイしておくと後々楽です。

rinna Developers

こちらText to Image APIのサブスクリプションキーを発行しておいてください。適当に名前入力してSubscribeのボタンを押すだけです。

rinna API

こちらのNodeをお借りします。Node-RED上にインストールしておいてください。

LINE Developers

Messaging API 使うので登録しておいてください。

gyazo API

生成した画像をアップロードしてURLを生成、LINEに送ります。
参考記事はこちら。

Step.0 Node-RED上でテキストから画像生成してみる

再掲となりますがこちらのGitHub上にあるサンプル通り、テキスト入力から画像を生成してみます。わかる方は飛ばしていただいて構いません。

詳細はこちら

1.画像の通りノードを並べてつなげます

2.ノードの中身を設定します。(injectは送りたい文字列、image previesは設定不要です)

3.injectのボタンを押すと画像が生成されます。

お好きな言葉で画像を生成することができます。

Step.1 Node-REDの全体像

インポート用JSON配列
[
    {
        "id": "679b23ea477d9d4c",
        "type": "rinna",
        "z": "736a148da489128d",
        "name": "",
        "api": "ldm",
        "subscription": "",
        "x": 330,
        "y": 400,
        "wires": [
            [
                "286ed77437845460",
                "f2a8012cd7145063",
                "24a5cb304229cf7c"
            ]
        ]
    },
    {
        "id": "286ed77437845460",
        "type": "image",
        "z": "736a148da489128d",
        "name": "",
        "width": 160,
        "data": "payload",
        "dataType": "msg",
        "thumbnail": false,
        "active": true,
        "pass": false,
        "outputs": 0,
        "x": 320,
        "y": 540,
        "wires": []
    },
    {
        "id": "a26acea9f7fe5dff",
        "type": "Webhook",
        "z": "736a148da489128d",
        "name": "",
        "url": "/webhook",
        "x": 160,
        "y": 400,
        "wires": [
            [
                "6c8d62d1fed760fb",
                "679b23ea477d9d4c"
            ]
        ]
    },
    {
        "id": "6c8d62d1fed760fb",
        "type": "debug",
        "z": "736a148da489128d",
        "name": "debug 5",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 160,
        "y": 480,
        "wires": []
    },
    {
        "id": "02eb29bdbc5713d2",
        "type": "ReplyMessage",
        "z": "736a148da489128d",
        "name": "",
        "replyMessage": "",
        "x": 760,
        "y": 760,
        "wires": []
    },
    {
        "id": "9b13730b6012b5b3",
        "type": "change",
        "z": "736a148da489128d",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "headers.content-type",
                "pt": "msg",
                "to": "multipart/form-data",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "temp",
                "pt": "msg",
                "to": "payload",
                "tot": "msg",
                "dc": true
            },
            {
                "t": "delete",
                "p": "payload",
                "pt": "msg"
            },
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "{}",
                "tot": "json"
            },
            {
                "t": "set",
                "p": "payload.access_token",
                "pt": "msg",
                "to": "GYAZO_API_TOKEN",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "payload.imagedata.value",
                "pt": "msg",
                "to": "temp",
                "tot": "msg",
                "dc": true
            },
            {
                "t": "set",
                "p": "payload.imagedata.options.filename",
                "pt": "msg",
                "to": "image.png",
                "tot": "str"
            },
            {
                "t": "delete",
                "p": "tmp",
                "pt": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 720,
        "y": 400,
        "wires": [
            [
                "01bbb139ce04e4ae",
                "523f4e49785e176d"
            ]
        ]
    },
    {
        "id": "f2a8012cd7145063",
        "type": "debug",
        "z": "736a148da489128d",
        "name": "debug 6",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 340,
        "y": 480,
        "wires": []
    },
    {
        "id": "efc81245f7303bb6",
        "type": "debug",
        "z": "736a148da489128d",
        "name": "debug 7",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 520,
        "y": 480,
        "wires": []
    },
    {
        "id": "24a5cb304229cf7c",
        "type": "function",
        "z": "736a148da489128d",
        "name": "function 4",
        "func": "const base64 = msg.payload.split(',')[1];\nmsg.payload = Buffer.from(base64, 'base64') //Buffer形式の画像バイナリに変換\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 500,
        "y": 400,
        "wires": [
            [
                "efc81245f7303bb6",
                "9b13730b6012b5b3"
            ]
        ]
    },
    {
        "id": "01bbb139ce04e4ae",
        "type": "debug",
        "z": "736a148da489128d",
        "name": "debug 8",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 700,
        "y": 480,
        "wires": []
    },
    {
        "id": "523f4e49785e176d",
        "type": "http request",
        "z": "736a148da489128d",
        "name": "",
        "method": "POST",
        "ret": "txt",
        "paytoqs": "ignore",
        "url": "https://upload.gyazo.com/api/upload",
        "tls": "",
        "persist": false,
        "proxy": "",
        "insecureHTTPParser": false,
        "authType": "basic",
        "senderr": false,
        "headers": [],
        "x": 150,
        "y": 760,
        "wires": [
            [
                "4d28f0dadd0a39b0",
                "8037b4c8.94c7c8"
            ]
        ]
    },
    {
        "id": "4d28f0dadd0a39b0",
        "type": "debug",
        "z": "736a148da489128d",
        "name": "debug 9",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 160,
        "y": 840,
        "wires": []
    },
    {
        "id": "cf90dab5.815908",
        "type": "debug",
        "z": "736a148da489128d",
        "name": "debug10",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 340,
        "y": 840,
        "wires": []
    },
    {
        "id": "8037b4c8.94c7c8",
        "type": "json",
        "z": "736a148da489128d",
        "name": "",
        "property": "payload",
        "action": "",
        "pretty": false,
        "x": 350,
        "y": 760,
        "wires": [
            [
                "cf90dab5.815908",
                "0adf342694d1eb65"
            ]
        ]
    },
    {
        "id": "0adf342694d1eb65",
        "type": "change",
        "z": "736a148da489128d",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "temp",
                "pt": "msg",
                "to": "{}",
                "tot": "json"
            },
            {
                "t": "set",
                "p": "temp.thumb_url",
                "pt": "msg",
                "to": "payload.thumb_url",
                "tot": "msg",
                "dc": true
            },
            {
                "t": "set",
                "p": "temp.url",
                "pt": "msg",
                "to": "payload.url",
                "tot": "msg",
                "dc": true
            },
            {
                "t": "delete",
                "p": "payload",
                "pt": "msg"
            },
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "{}",
                "tot": "json"
            },
            {
                "t": "set",
                "p": "payload.type",
                "pt": "msg",
                "to": "image",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "payload.originalContentUrl",
                "pt": "msg",
                "to": "temp.url",
                "tot": "msg",
                "dc": true
            },
            {
                "t": "set",
                "p": "payload.previewImageUrl",
                "pt": "msg",
                "to": "temp.thumb_url",
                "tot": "msg",
                "dc": true
            },
            {
                "t": "delete",
                "p": "tmp",
                "pt": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 540,
        "y": 760,
        "wires": [
            [
                "a92b30d9b13897eb",
                "02eb29bdbc5713d2"
            ]
        ]
    },
    {
        "id": "a92b30d9b13897eb",
        "type": "debug",
        "z": "736a148da489128d",
        "name": "debug 11",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 540,
        "y": 840,
        "wires": []
    }
]

Step.2 LINE側の設定をする

LINE側のwebhook URLは、Railway上にデプロイしているとして、
https://node-red-railway-production-{自身のURL中文字列を入力}.up.railway.app/webhook
Node-RED上のwebhook URLは/webhookで設定しておいてください。

Step.3 各ノードを設定する

画像は折りたたんでいますので必要に応じて参照ください。

1. LINE webhook

Path: /webhook

詳細画像

スクリーンショット 2022-11-06 22.00.40.png

2. rinna

API: Text to Image API
subscrioption: 設定したsubscription key

詳細画像

スクリーンショット 2022-11-06 22.04.47.png

3. function

詳細画像

ノーコードで実装するといったな。あれは嘘だ

const base64 = msg.payload.split(',')[1];
msg.payload = Buffer.from(base64, 'base64') //Buffer形式の画像バイナリに変換

return msg;

これをコピペするだけなので許してください。
msg.payload = data:image/png;base64,{{base64}}とうデータが入っているので、data:image/png;base64,を削除して、base64文字列をBuffer形式に変換できるノードがあればfunction使わずにすみます。

4. change 1つめ

この記事を参考に

msg.headers.content-type = 'multipart/form-data'
msg.payload = {
  access_token: 'Gyazoアクセストークン文字列',
  imagedata: {
    value: (Buffer形式の画像バイナリ),
    options: {
      filename: 'image.png',
    },
  },
}

このようなmsgオブジェクトを生成してください。

インポート用JSON
[
    {
        "id": "9b13730b6012b5b3",
        "type": "change",
        "z": "736a148da489128d",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "headers.content-type",
                "pt": "msg",
                "to": "multipart/form-data",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "temp",
                "pt": "msg",
                "to": "payload",
                "tot": "msg",
                "dc": true
            },
            {
                "t": "delete",
                "p": "payload",
                "pt": "msg"
            },
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "{}",
                "tot": "json"
            },
            {
                "t": "set",
                "p": "payload.access_token",
                "pt": "msg",
                "to": "GYAZO_API_KEY",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "payload.imagedata.value",
                "pt": "msg",
                "to": "temp",
                "tot": "msg",
                "dc": true
            },
            {
                "t": "set",
                "p": "payload.imagedata.options.filename",
                "pt": "msg",
                "to": "image.png",
                "tot": "str"
            },
            {
                "t": "delete",
                "p": "tmp",
                "pt": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 720,
        "y": 400,
        "wires": [
            [
                "01bbb139ce04e4ae",
                "523f4e49785e176d"
            ]
        ]
    }
]

5. http request

メソッド: POST
URL: https://upload.gyazo.com/api/upload
認証を使用にチェックし、種別はBasic認証

詳細画像

スクリーンショット 2022-11-06 22.22.53.png

6. JSON

responseをJSON形式にしてみやすくします。
別段設定は必要ありません。

詳細画像

スクリーンショット 2022-11-06 22.25.12.png

7. change 2つめ

LINE Messaging APIリファレンスより

{
  "type": "image",
  "originalContentUrl": "https://example.com/original.jpg",
  "previewImageUrl": "https://example.com/preview.jpg"
}

このようなJSONを生成します。
originalContentURLは詳細画像なので、GYAZO APIから返ってくるurlの画像URLを、previewImageURLはサムネですのでthumb_urlの画像URLをそれぞれ渡してあげましょう。

インポート用JSON
[
    {
        "id": "0adf342694d1eb65",
        "type": "change",
        "z": "736a148da489128d",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "temp",
                "pt": "msg",
                "to": "{}",
                "tot": "json"
            },
            {
                "t": "set",
                "p": "temp.thumb_url",
                "pt": "msg",
                "to": "payload.thumb_url",
                "tot": "msg",
                "dc": true
            },
            {
                "t": "set",
                "p": "temp.url",
                "pt": "msg",
                "to": "payload.url",
                "tot": "msg",
                "dc": true
            },
            {
                "t": "delete",
                "p": "payload",
                "pt": "msg"
            },
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "{}",
                "tot": "json"
            },
            {
                "t": "set",
                "p": "payload.type",
                "pt": "msg",
                "to": "image",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "payload.originalContentUrl",
                "pt": "msg",
                "to": "temp.url",
                "tot": "msg",
                "dc": true
            },
            {
                "t": "set",
                "p": "payload.previewImageUrl",
                "pt": "msg",
                "to": "temp.thumb_url",
                "tot": "msg",
                "dc": true
            },
            {
                "t": "delete",
                "p": "tmp",
                "pt": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 540,
        "y": 760,
        "wires": [
            [
                "a92b30d9b13897eb",
                "02eb29bdbc5713d2"
            ]
        ]
    }
]

8. LINE ReplyMessage

LINE Developersからチャネルシークレット、チャネルアクセストークンをコピーして貼り付けましょう。

詳細画像

スクリーンショット 2022-11-06 22.33.38.png

Step.4 デプロイ

赤いボタンを押す

言葉から画像を生成してリプライしてもらった

編集で短くしていますが、メッセージ送った後それなりに待たされます。

難点

やめてくれrinna。
待たされた後この画像出すのは俺に効く。

11
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
4