3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Cloudflare workersのLINE Botに重めの処理を接続する

Posted at

以前、Clouflare wokersにhonoでLINE Botを作成する記事を作成しました

その末尾にあとはよしなになんかと繋げて使ってください!というメッセージを残したのですが、流石に投げっぱなしは忍びないと思いまして、

  • ノーコードツール、makeでwebhookを作る
  • cloudflare workersからそのwebhookにリクエストを送る
  • 結果をBotのメッセージとして返す

というところを実際に実装してみたいと思います。

0. 準備

0.1. makeのアカウント登録

そもそもmakeとは、ノーコードでアプリケーションを繋ぎ合わせることができるサービスです。
いわゆるiPaaS(integration Platform as a Service) に該当し、さまざまなサービスやアプリケーションを繋ぎ合わせることで、独自のアプリケーションを構築することができます。

こちらからGoogleなどのソーシャルログインで問題ありませんので登録しておいてください。

0.2. makeでGPTと会話できるLINE Botの作成

少々古いですが、こちらの3-3までを実施いただけると、本記事の意味合いがわかりやすいかと思います

0.3. Cloudflare workers上で動作するecho botの作成

この記事の続きですので、おうむ返しBotができているものとします。

1. ハンズオン

1.0. 全体を通して作るもの

今回はCloudflareからmakeにリクエストを送り、makeの中で具体的なロジックを実行(サンプル例はGPTのAPIです)。再度Cloudflareに返して、結果を返答するLINE Botを作成します。
そのために、make側の準備を整えたのちCloudflare側のコードを書き換えつなぎこむ、という手順で進めます

1.1. makeのLINE Botをwebhook経由のAPIに切り替える

まずは0.2で作成したLINE Botについて、これをAPIに切り替えることでCloudflareと繋げる準備をします。

1.1.1. 両端のLINE モジュールを削除して、webhook モジュールに切り替える

  • 今、こんな感じだと思いますので、両側のLINEモジュールを削除します
  • 削除したら、Webhooksのモジュールを選んでください
  • 選ぶのはCustom webhookです
  • Create a webhookのボタンを押します
  • わかりやすい名前をつけて、saveボタンを押してください
  • こんな感じの待機画面になったらひとまずOKです
  • 一旦stopを押して止めてください
  • 末尾に同様のwebhooksモジュール(webhook response)を作ったら完成です

1.1.2. 補足|さっきの待機画面が気になる方向け

詳細は折りたたみで表示、読み飛ばして問題ないです

先ほどは、webからのリクエストを待機していました。
表示されていたwebhook URLに対して、実際のリクエストを送ることで、データ構造を定義して、makeの中で使えるようにしてくれます。
なので、このあとcloudflare側の準備を整えて、繋ぎ込んでいきます。

1.2. Cloudflareとmakeと繋ぐ

それでは、makeとCloudflareを繋いでいきます。まずは、Cloudflare側で作成したLINE Botを触っていきます。」

1.2.1. Cloudflareのコードを書き換える

次のコードをindex.tsの中に貼り付けます(前回記事の通り実施している前提です)。

index.ts
import { 
  messagingApi,
  webhook,
} from '@line/bot-sdk'
import { ExecutionContext, Hono } from 'hono'
import HmacSHA256 from "crypto-js/hmac-sha256";
import Base64 from "crypto-js/enc-base64";

type Bindings = {
  CHANNEL_ACCESS_TOKEN: string,
  CHANNEL_SECRET: string,
  WEBHOOK_URL: string,
}

const app = new Hono<{ Bindings: Bindings }>()

app.get('/', (c) => {
  return c.text('Hello hono & line!')
})

app.post('/webhook', async (c) => {
  const body = await c.req.text() // JSONではなくテキストで取得
  const channelAccessToken = c.env.CHANNEL_ACCESS_TOKEN || ''
  const channelSecret = c.env.CHANNEL_SECRET || ''
  const webhookURL = c.env.WEBHOOK_URL || ''

  // シグネチャの取得
  const signature = c.req.header('x-line-signature')
  if (!signature) {
    return c.text('Missing signature', 400)
  }

  // HMACを使ってシグネチャを生成
  const hash = await generateHmac(channelSecret, body);

  // シグネチャの検証
  if (signature !== hash) {
    return c.text('Invalid signature', 403)
  }

  const events = JSON.parse(body).events
  const promises = events.map((event: webhook.Event) => handleEvent(event, channelAccessToken, webhookURL, c.executionCtx))
  await Promise.all(promises)

  return c.text('OK')
})

const handleEvent = async (
  event: webhook.Event,
  accessToken: string,
  webhookURL: string,
  ctx: ExecutionContext
) => {
  if (event.type !== 'message' || event.message.type !== 'text') return;

  try {
    fetch('https://api.line.me/v2/bot/chat/loading/start', {
      method: 'POST',
      headers: {
        "Authorization": `Bearer ${accessToken}`,
        "Content-Type": "application/json"
      },
      body: JSON.stringify({"chatId": event.source?.userId})
    })

    ctx.waitUntil(
      (async () => {
        const res = await fetch(webhookURL, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify(event)
        })
        const text = await res.text();
        if(!event.replyToken) return
        const responseBody: messagingApi.ReplyMessageRequest = {
          replyToken: event.replyToken,
          messages: [
            {'type': 'text', 'text': text}
          ] 
        }
        
        return fetch('https://api.line.me/v2/bot/message/reply', {
          method: 'POST',
          headers: {
            "Authorization": `Bearer ${accessToken}`,
            "Content-Type": "application/json"
          },
          body: JSON.stringify(responseBody)
        }) 
      })()
    )
  } catch (error) {
    console.error(error);
  }
}

export default app

const generateHmac = async (secret: string, message: string) => {
  const hmac = HmacSHA256(message, secret);
  return Base64.stringify(hmac);
}

1.2.2. 環境変数の設定

環境変数として、makeのwebhook URLを設定し、Cloudflare側からリクエストを送れるようにします。

  • 次のコマンドをターミナルに入力してください
bunx wrangler secret put WEBHOOK_URL
  • 次のようにコピペしてくれと言われます
  • make側に戻り、Redetermine data structureボタンを押します
  • Copy address to clipboardボタンを押して、URLをコピーします
  • コピーしたコードをコピペしてenter
  • 成功したら、画面のようになります
  • 次のコマンドを入力してデプロイ
bun run deploy

1.2.3. webhookにリクエストを送る

  • なんでも良いので、LINEからメッセージを送ってみます
  • うまくいくと、LINEにAcceptedと表示されます
  • またモジュール側にもSuccessfuly determinedと表示されます

1.2.4. OpenAIのモジュールにwebhookの値を入れる

  • 先ほどの手順で、OpenAIのモジュールにデータ構造が定義されています
  • text contentにwebhooks経由できた値を入れます
  • webhooksのBodyにOpenAIから返ってきた値を入れます

1.2.5. 補足|コードの変更点

詳細は折りたたみで表示、読み飛ばして問題ないです

先ほどは、webからのリクエストを待機していました。
表示されていたwebhook URLに対して、実際のリクエストを送ることで、データ構造を定義して、makeの中で使えるようにしてくれます。
なので、このあとcloudflare側の準備を整えて、繋ぎ込んでいきます。

1.3. 実行してみる

  • Run onceを押してみます
  • LINE からメッセージを送ってみましょう

まあはやいかな

2. 動作しているところ

動作の肝はこれですね
honoのExecutionContextにあるwaitUntilがポイントです

3
3
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
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?