Closed7

Hono + Line Messaging API + Cloudflare Workers + R2トライアル

ta1kt0meta1kt0me
wrangler.jsonc
  /**
   * Bindings
   * Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform, including
   * databases, object storage, AI inference, real-time communication and more.
   * https://developers.cloudflare.com/workers/runtime-apis/bindings/
   */
  "r2_buckets": [
    {
      "binding": "R2_BUCKET",
      "bucket_name": "[bucket_name]",
      "preview_bucket_name": "[bucket_name]"
    }
  ]

こちらは開発用途なので適当に

LINE_CHANNEL_ID=[random number]
LINE_CHANNEL_SECRET=[secret string]
index.ts
type Bindings = {
  R2_BUCKET: R2Bucket;
  LINE_CHANNEL_ID: string;
  LINE_CHANNEL_SECRET: string;
}

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

CFの Workers & Pages > 設定 > 変数とシークレット に以下をシークレットとして登録する

  • LINE_CHANNEL_ID: LINE Messaging API の Channel ID
  • LINE_CHANNEL_SECRET: LINE Messaging API の Channel secret

R2 にbucketを作成する

wrangler r2 bucket create [bucket_name]
ta1kt0meta1kt0me

やりたいことはこんな感じ。

  1. Lineで画像をアップロード
  2. Messaging APIのwebhookを使ってメッセージの情報を取得
  3. BearerTokenを生成
  4. BearerTokenを使って画像データを取得
  5. 画像データをR2にアップロード
ta1kt0meta1kt0me

BearerTokenを生成

ちょっと型は雑に...

ステートレスチャネルアクセストークンを利用する

https://developers.line.biz/ja/reference/messaging-api/#issue-stateless-channel-access-token

  • チャネルIDとチャネルシークレットから発行する
  • Content-Type は application/x-www-form-urlencoded
index.ts
async function fetchBearerToken(channelId: string, channelSecret: string) : Promise<string> {
  const params = new URLSearchParams();
  params.append('grant_type', 'client_credentials');
  params.append('client_id', channelId);
  params.append('client_secret', channelSecret);
  const res = await fetch('https://api.line.me/oauth2/v3/token', {
    method: 'POST',
    body: params,
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
  });  if (res.status !== 200) {
    throw new Error(`Failed to fetch bearer token: ${res.status} ${res.statusText}`);
  }
  const data: any = await res.json();
  return data.access_token;
}

app.post("/api/webhook", async (c) => {
  const j = await c.req.json();
  if (j.events[0].message.type !== 'text') {
    const token = await fetchBearerToken(c.env.LINE_CHANNEL_ID, c.env.LINE_CHANNEL_SECRET);
  }

  return c.json({ message: "Hello World!" });
})
ta1kt0meta1kt0me

BearerTokenを使って画像データを取得

https://developers.line.biz/ja/reference/messaging-api/#get-content

  • responseはblobとして扱う
index.ts
async function fetchContent(token: string, messageId: string) : Promise<Blob> {
  const res = await fetch(`https://api-data.line.me/v2/bot/message/${messageId}/content`, {
    headers: { 'Authorization': `Bearer ${token}` }
  })

  if (res.status !== 200) {
    throw new Error(`Failed to fetch bearer token: ${res.status} ${res.statusText}`);
  }

  const data = await res.blob()
  return data;
}

app.post("/api/webhook", async (c) => {
  const j = await c.req.json();
  if (j.events[0].message.type !== 'text') {
    const token = await fetchBearerToken(c.env.LINE_CHANNEL_ID, c.env.LINE_CHANNEL_SECRET);
    const messageId = j.events[0].message.id;
    const data = await fetchContent(token, messageId)
  }

  return c.json({ message: "Hello World!" });
});
ta1kt0meta1kt0me

https://developers.cloudflare.com/r2/api/workers/workers-api-reference/#bucket-method-definitions

  • blob.type で mime-type を取得する
  • mime-typeのスラッシュ以降の文字列をオブジェクトの拡張子として扱う
index.ts
async function uploadContent(bucket: R2Bucket, messageId: string, data: Blob) : Promise<void> {
  const type = data.type;
  const suffix = type.split("/")[1];
  const uploaded = await bucket.put(messageId + "." + suffix, data, { httpMetadata: { contentType: type } });
  console.log(JSON.stringify(uploaded))
}

app.post("/api/webhook", async (c) => {
  const j = await c.req.json();
  if (j.events[0].message.type !== 'text') {
    const token = await fetchBearerToken(c.env.LINE_CHANNEL_ID, c.env.LINE_CHANNEL_SECRET);
    const messageId = j.events[0].message.id;
    const data = await fetchContent(token, messageId)
    await uploadContent(c.env.R2_BUCKET, messageId, data)
  }
  return c.json({ message: "Hello World!" });
});
ta1kt0meta1kt0me

deployして、ログを確認という流れで便利

  • npm run deploy
  • npm run tail
package.json
{
  "name": "line-bot-cf-worker-sample",
  ...
  "scripts": {
    "deploy": "wrangler deploy",
    "tail": "wrangler tail",
    ...
  },
このスクラップは2025/02/22にクローズされました