Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

More than 1 year has passed since last update.

HL向け 初めてのLINE API

Last updated at Posted at 2023-09-16

1. ヒーローズリーグについて

What's HL

Proto Pedia作品チラ見!

LINE APIを使った作品が盛りだくさん紹介!!

image.png

2. LINE APIを見てみよう!

Messaging API

LINE Bot を開発するならまずはこれです!

グループに追加されていれば、グループ内でメッセージを送信することも

image.png

ハンズオン!

以下を使用します。
LINE Developer console: https://developers.line.biz/ja/docs/line-developers-console/
Replit: https://replit.com/
Make: https://www.make.com/en
LINE Notify: https://notify-bot.line.me/ja/
Open AI: https://platform.openai.com/

とにもかくにも、まずはおうむ返しボットを作成してみます。

以下を参考に進めます。

Replit

ボットをホストするサーバー を準備とありますが、ここでは Replit を使います。
ハッカソンなどでも役立ちそうです!

アカウントを作成後、node.js プロジェクトを新規に作成し、以下のサンプルコードをまるっとコピペします。

無料プランの場合はプロジェクトがpublicになるので、アクセスキーなどの取り扱いにはご注意ください。

今回使うサンプルコード

クリック

参考: https://developers.line.biz/ja/docs/messaging-api/line-bot-sdk/

'use strict';

const line = require('@line/bot-sdk');
const express = require('express');
const OpenAI = require("openai");
const axios = require('axios');


// create LINE SDK config from env variables
const config = {
  channelAccessToken: process.env.LINE_CHANNEL_ACCESS_TOKEN,
  channelSecret: process.env.LINE_CHANNEL_SECRET,
};

const IMG_URL = 'https://mashandroom.org/wp-content/uploads/2017/04/logo-320x320.png';

// ユーザーとGPTのメッセージを合わせて最大6つ(ユーザー3、GPT3)
const MAX_HISTORY = 6;
const userMessages = {};

// create LINE SDK client
const client = new line.Client(config);

// create Express app
// about Express itself: https://expressjs.com/
const app = express();



// register a webhook handler with middleware
// about the middleware, please refer to doc
app.post('/callback', line.middleware(config), (req, res) => {
  Promise
    .all(req.body.events.map(handleEvent))
    // .all(req.body.events.map(handleEventWithGPT))
    // .all(req.body.events.map(handleEventWithGPTHistory))
    .then((result) => res.status(200).json(result))
    .catch((err) => {
      console.error(err);
      res.status(500).end();
    });
});

// event handler
function handleEvent(event) {
  if (event.type !== 'message' || event.message.type !== 'text') {
    // ignore non-text-message event
    return Promise.resolve(null);
  }

  if (event.message.text === "キノコ") {
    return notify();
  }

  const messageHandlers = {
    // おうむ返し+スタンプ
    'スタンプ': createEchoWithStamp,
    // おうむ返し+画像
    '画像': createEchoWithImage,
    // おうむ返し+Flex Message
    'フレックス1': createFlex1,
    'フレックス2': createFlex2,
    "フレックス3": createFlex3
  };

  const handler = messageHandlers[event.message.text];
  const reply = handler ? handler(event.message.text) : createEcho(event.message.text);

  // use reply API
  return client.replyMessage(event.replyToken, reply);
}

function createEcho(userMessage) {
  return { type: 'text', text: userMessage };
}

function createEchoWithStamp(userMessage) {
  return [
    { type: 'text', text: userMessage },
    { type: 'sticker', packageId: '446', stickerId: '1989' }
  ];
}

function createEchoWithImage(userMessage) {
  return [
    { type: 'text', text: userMessage },
    { type: 'image', originalContentUrl: IMG_URL, previewImageUrl: IMG_URL }
  ];
}

function createFlex1(userMessage) {
  return [
    { type: 'text', text: userMessage },
    {
      "type": "flex",
      "altText": "this is a flex message",
      "contents": {
        "type": "bubble",
        "body": {
          "type": "box",
          "layout": "vertical",
          "contents": [
            {
              "type": "text",
              "text": "hello"
            },
            {
              "type": "text",
              "text": "world"
            }
          ]
        }
      }
    }];
}

function createFlex2(userMessage) {
  return [
    { type: 'text', text: userMessage },
    {
      "type": "flex",
      "altText": "this is a flex message",
      "contents": {
        "type": "bubble",
        "body": {
          "type": "box",
          "layout": "vertical",
          "contents": [
            {
              "type": "image",
              "url": IMG_URL
            },
            {
              "type": "separator"
            },
            {
              "type": "text",
              "text": "Text in the box"
            },
            {
              "type": "box",
              "layout": "vertical",
              "contents": [],
              "width": "30px",
              "height": "30px",
              "background": {
                "type": "linearGradient",
                "angle": "90deg",
                "startColor": "#FFFF00",
                "endColor": "#0080ff"
              }
            }
          ],
          "height": "400px",
          "justifyContent": "space-evenly",
          "alignItems": "center"
        }
      }
    }];
}

function createFlex3(userMessage) {
  return [
    { type: 'text', text: userMessage },
    {
      "type": "flex",
      "altText": "this is a flex message",
      "contents": {
        "type": "carousel",
        "contents": [
          {
            "type": "bubble",
            "size": "micro",
            "hero": {
              "type": "image",
              "url": "https://scdn.line-apps.com/n/channel_devcenter/img/flexsnapshot/clip/clip10.jpg",
              "size": "full",
              "aspectMode": "cover",
              "aspectRatio": "320:213"
            },
            "body": {
              "type": "box",
              "layout": "vertical",
              "contents": [
                {
                  "type": "text",
                  "text": "Brown Cafe",
                  "weight": "bold",
                  "size": "sm",
                  "wrap": true
                },
                {
                  "type": "box",
                  "layout": "baseline",
                  "contents": [
                    {
                      "type": "icon",
                      "size": "xs",
                      "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png"
                    },
                    {
                      "type": "icon",
                      "size": "xs",
                      "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png"
                    },
                    {
                      "type": "icon",
                      "size": "xs",
                      "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png"
                    },
                    {
                      "type": "icon",
                      "size": "xs",
                      "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png"
                    },
                    {
                      "type": "icon",
                      "size": "xs",
                      "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gray_star_28.png"
                    },
                    {
                      "type": "text",
                      "text": "4.0",
                      "size": "xs",
                      "color": "#8c8c8c",
                      "margin": "md",
                      "flex": 0
                    }
                  ]
                },
                {
                  "type": "box",
                  "layout": "vertical",
                  "contents": [
                    {
                      "type": "box",
                      "layout": "baseline",
                      "spacing": "sm",
                      "contents": [
                        {
                          "type": "text",
                          "text": "東京旅行",
                          "wrap": true,
                          "color": "#8c8c8c",
                          "size": "xs",
                          "flex": 5
                        }
                      ]
                    }
                  ]
                }
              ],
              "spacing": "sm",
              "paddingAll": "13px"
            }
          },
          {
            "type": "bubble",
            "size": "micro",
            "hero": {
              "type": "image",
              "url": "https://scdn.line-apps.com/n/channel_devcenter/img/flexsnapshot/clip/clip11.jpg",
              "size": "full",
              "aspectMode": "cover",
              "aspectRatio": "320:213"
            },
            "body": {
              "type": "box",
              "layout": "vertical",
              "contents": [
                {
                  "type": "text",
                  "text": "Brow&Cony's Restaurant",
                  "weight": "bold",
                  "size": "sm",
                  "wrap": true
                },
                {
                  "type": "box",
                  "layout": "baseline",
                  "contents": [
                    {
                      "type": "icon",
                      "size": "xs",
                      "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png"
                    },
                    {
                      "type": "icon",
                      "size": "xs",
                      "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png"
                    },
                    {
                      "type": "icon",
                      "size": "xs",
                      "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png"
                    },
                    {
                      "type": "icon",
                      "size": "xs",
                      "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png"
                    },
                    {
                      "type": "icon",
                      "size": "xs",
                      "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gray_star_28.png"
                    },
                    {
                      "type": "text",
                      "text": "4.0",
                      "size": "sm",
                      "color": "#8c8c8c",
                      "margin": "md",
                      "flex": 0
                    }
                  ]
                },
                {
                  "type": "box",
                  "layout": "vertical",
                  "contents": [
                    {
                      "type": "box",
                      "layout": "baseline",
                      "spacing": "sm",
                      "contents": [
                        {
                          "type": "text",
                          "text": "東京旅行",
                          "wrap": true,
                          "color": "#8c8c8c",
                          "size": "xs",
                          "flex": 5
                        }
                      ]
                    }
                  ]
                }
              ],
              "spacing": "sm",
              "paddingAll": "13px"
            }
          },
          {
            "type": "bubble",
            "size": "micro",
            "hero": {
              "type": "image",
              "url": "https://scdn.line-apps.com/n/channel_devcenter/img/flexsnapshot/clip/clip12.jpg",
              "size": "full",
              "aspectMode": "cover",
              "aspectRatio": "320:213"
            },
            "body": {
              "type": "box",
              "layout": "vertical",
              "contents": [
                {
                  "type": "text",
                  "text": "Tata",
                  "weight": "bold",
                  "size": "sm"
                },
                {
                  "type": "box",
                  "layout": "baseline",
                  "contents": [
                    {
                      "type": "icon",
                      "size": "xs",
                      "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png"
                    },
                    {
                      "type": "icon",
                      "size": "xs",
                      "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png"
                    },
                    {
                      "type": "icon",
                      "size": "xs",
                      "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png"
                    },
                    {
                      "type": "icon",
                      "size": "xs",
                      "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png"
                    },
                    {
                      "type": "icon",
                      "size": "xs",
                      "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gray_star_28.png"
                    },
                    {
                      "type": "text",
                      "text": "4.0",
                      "size": "sm",
                      "color": "#8c8c8c",
                      "margin": "md",
                      "flex": 0
                    }
                  ]
                },
                {
                  "type": "box",
                  "layout": "vertical",
                  "contents": [
                    {
                      "type": "box",
                      "layout": "baseline",
                      "spacing": "sm",
                      "contents": [
                        {
                          "type": "text",
                          "text": "東京旅行",
                          "wrap": true,
                          "color": "#8c8c8c",
                          "size": "xs",
                          "flex": 5
                        }
                      ]
                    }
                  ]
                }
              ],
              "spacing": "sm",
              "paddingAll": "13px"
            }
          }
        ]
      }
    }
  ];
}

// GhatGPT event handler
// !!!!SecretsにOPENAI_API_KEYを設定してください。
async function handleEventWithGPT(event) {
  if (event.type !== 'message' || event.message.type !== 'text') {
    // ignore non-text-message event
    return Promise.resolve(null);
  }

  const messages =
    [
      // systemメッセージ: gptの役割
      // ChatGPTに役割を与えることで、精度を高められる
      { "role": "system", "content": "優秀な詩人です。" },
      // userメッセージ: ユーザーの入力テキスト
      { "role": "user", "content": event.message.text }
      // assistantメッセージ: gptの応答
      // 応答を保存することで履歴に沿った文脈で会話ができる
    ]

  const gptResponse = await getGptResponse(messages);
  const assistantMessage = gptResponse.choices[0].message.content;

  const reply = [
    {
      type: 'text',
      text: assistantMessage
    },
    {
      type: 'text',
      text: createUsageMessage(gptResponse.usage)
    }];

  // use reply API
  return client.replyMessage(event.replyToken, reply);
}

// GhatGPT event handler(会話履歴を保持)
// !!!!SecretsにOPENAI_API_KEYを設定してください。
async function handleEventWithGPTHistory(event) {
  if (event.type !== 'message' || event.message.type !== 'text') {
    // ignore non-text-message event
    return Promise.resolve(null);
  }

  const userId = event.source.userId;

  // 初めてのユーザーの場合、データ構造を初期化
  if (!userMessages[userId]) {
    userMessages[userId] = [
      { "role": "system", "content": "優秀な詩人です。" }
    ];
  }

  // ユーザーのメッセージをデータ構造に追加
  userMessages[userId].push({ "role": "user", "content": event.message.text });

  const gptResponse = await getGptResponse(userMessages[userId]);
  const assistantMessage = gptResponse.choices[0].message.content;

  // GPTの応答をデータ構造に追加
  userMessages[userId].push({
    "role": "assistant",
    "content": assistantMessage
  });

  // 履歴が最大数を超えた場合、古いメッセージを削除
  if (userMessages[userId].length - 1 > MAX_HISTORY) {
    // role以降先頭のuser, assistantのペアを削除
    userMessages[userId].splice(1, 2);
  }

  const reply = [
    {
      type: 'text',
      text: assistantMessage
    },
    {
      type: 'text',
      text: createUsageMessage(gptResponse.usage)
    }];

  // use reply API
  return client.replyMessage(event.replyToken, reply);
}

async function getGptResponse(messages) {
  const openai = new OpenAI({
    apiKey: process.env.OPENAI_API_KEY,
  });
  const response = await openai.chat.completions.create({
    model: "gpt-3.5-turbo",
    messages: messages,
    temperature: 0,
    max_tokens: 150,
  });

  console.log(response);
  console.log(response.choices[0].message);
  return response;
}

function createUsageMessage(usage) {
  return `prompt_tokens: ${usage.prompt_tokens}\ncompletion_tokens:${usage.prompt_tokens}\ntotal_tokens: ${usage.total_tokens}`;
}

async function notify() {
  const message = '\n何か呼んだ?';

  const options = {
    method: 'post',
    url: 'https://notify-api.line.me/api/notify',
    headers: {
      'Authorization': `Bearer ${process.env.LINE_NOTIFY_TOKEN}`,
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    data: 'message=' + encodeURIComponent(message)
  };

  await axios(options);
}

// listen on port
const port = process.env.PORT || 3000;
app.listen(port, () => {
  console.log(`listening on ${port}`);
});

Secretsに以下を設定

Replitで発行されたURLをWebhookに以下の通り設定

3. ChatGPTと連携

Chat Completion API

ライブラリ

モデル

レスポンス

Secretsに OPENAI_API_KEY を追加

image.png

ソース修正

app.post('/callback', line.middleware(config), (req, res) => {
  Promise
    // .all(req.body.events.map(handleEvent))       // ← 削除 or コメントアウト
    .all(req.body.events.map(handleEventWithGPT))   // ← 有効に
    // .all(req.body.events.map(handleEventWithGPTHistory)) // ←3つの会話履歴を保持
    .then((result) => res.status(200).json(result))
    .catch((err) => {
      console.error(err);
      res.status(500).end();
    });
});

4. LINE Notify

LINEと外部のサービスやアプリを連携して、LINE Notifyという公式アカウント(Bot)から通知を受け取れるサービス

Make x LINE Notify 3分クッキング

MAKE もハッカソンでは活躍しそうなのでLINE Notifyと連携させてみましょう!

ChatGPTも

プログラムからもNotify

LINE_NOTIFY_TOKEN を追加

  if (event.message.text === "キノコ") {
    return notify();
  }
async function notify() {
  const message = '\n何か呼んだ?';

  const options = {
    method: 'post',
    url: 'https://notify-api.line.me/api/notify',
    headers: {
      'Authorization': `Bearer ${process.env.LINE_NOTIFY_TOKEN}`,
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    data: 'message=' + encodeURIComponent(message)
  };

  await axios(options);
}

LIFF , LINE ログイン(参考)

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?