はじめに
以前、AWS Lambdaを使ってWebページのスクリーンショットをDiscordに送る仕組みを構築したが、今回はCloudflare Workersを使って同様の機能を実装してみた。
AWS Lambda版では複雑だったレイヤーの管理やライブラリの準備が、Cloudflare Workersでは大幅に簡略化され、よりシンプルな構成で実現できた。
また、DiscordのWebhookからBot APIに変更をしてみた。
環境
Windows 11 Professional
Node.js 24
Cloudflare Workers
TypeScript
@cloudflare/playwright
Discord Bot API準備
- Cloudflareアカウント
- Discordサーバのチャンネル
- Discord Bot(Webhook の代わりに使用)
全体の流れ
Discord Botを作成し、必要な権限を設定
Cloudflare WorkersにTypeScriptのコードをデプロイ
Cron Triggersにて定期的にWorkerを実行するように設定
動作確認
Discord Botを作成する
Discord Developer Portalにアクセスする
「New Application」でアプリケーションを作成する
「Bot」セクションに移動し、「Create Bot」でBotを作成する
Bot TokenをコピーしてDiscord Bot用のトークンとして控えておく
「OAuth2」→「URL Generator」でBotをサーバーに追加する
- Scopesで「bot」を選択
- Bot Permissionsで「View Channels」「Send Messages」「Attach Files」を選択
- 生成されたURLをコピーする
生成されたURLをブラウザで開き、Botを対象のDiscordサーバーに招待する
- サーバー選択画面で対象のサーバーを選択
- 「認証」をクリックしてBotの招待を完了する
- 招待後、対象のサーバーでBotがオンライン状態になっていることを確認する
Discord Channel IDの取得
Discordの設定から「詳細設定」→「開発者モード」を有効にする
対象のチャンネルを右クリックし、「IDをコピー」でChannel IDを取得する
Cloudflare Workers構築
プロジェクトのセットアップ
リポジトリをクローンしてローカル環境を準備する。
git clone https://github.com/katsuobushiFPGA/capture-screenshot-to-discord-bot.git
cd capture-screenshot-to-discord-bot
npm install設定ファイルの準備
cp wrangler.toml wrangler.local.tomlwrangler.local.tomlの[vars]セクションに環境変数を設定する。
[vars]
TARGET_URL = "https://www.frontier-direct.jp/direct/e/ej-sale/"
DISCORD_BOT_TOKEN = "[取得したBot Token]"
DISCORD_CHANNEL_ID = "[取得したChannel ID]"※前回と同じフロンティアのセールページを対象としている。
コードの詳細
メインのコードはsrc/index.tsに実装されている。
スクリーンショット撮影部分
async function captureScreenshot(url: string, browser: BrowserWorker, retries = 3): Promise<Blob> {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
console.log(`Screenshot attempt ${attempt}/${retries} for URL: ${url}`);
// Cloudflare Browser Rendering (Playwright)を使用してブラウザを起動
const browserInstance = await launch(browser);
try {
const page = await browserInstance.newPage();
// ビューポートサイズを設定
await page.setViewportSize({ width: 1920, height: 1080 });
// 対象URLにアクセス
await page.goto(url, {
waitUntil: 'load',
timeout: 30000
});
// 遅延読み込み画像のためにページをスクロール
await autoScroll(page);
// スクリーンショットを撮影
const screenshot = await page.screenshot({
type: 'png',
fullPage: true
});
// BufferをBlobに変換
const blob = new Blob([screenshot], { type: 'image/png' });
console.log(`Screenshot captured successfully (${blob.size} bytes)`);
return blob;
} finally {
await browserInstance.close();
}
} catch (error) {
console.error(`Screenshot attempt ${attempt} failed:`, error);
if (attempt === retries) {
throw new Error(`Failed to capture screenshot after ${retries} attempts: ${(error as Error).message}`);
}
await new Promise(resolve => setTimeout(resolve, 2000 * attempt));
}
}
throw new Error('Unexpected error in screenshot capture');
}Discord投稿部分
async function sendToDiscord(env: Environment, screenshot: Blob, retries = 3): Promise<void> {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
console.log(`Discord posting attempt ${attempt}/${retries}`);
const formData = new FormData();
formData.append('file', screenshot, 'screenshot.png');
const response = await fetch(`https://discord.com/api/v10/channels/${env.DISCORD_CHANNEL_ID}/messages`, {
method: 'POST',
headers: {
'Authorization': `Bot ${env.DISCORD_BOT_TOKEN}`,
},
body: formData,
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Discord API error: ${response.status} ${response.statusText} - ${errorText}`);
}
console.log('Screenshot posted to Discord successfully');
return;
} catch (error) {
console.error(`Discord posting attempt ${attempt} failed:`, error);
if (attempt === retries) {
throw new Error(`Failed to post to Discord after ${retries} attempts: ${(error as Error).message}`);
}
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
}
}
}デプロイ
- Cloudflareにログインする
npx wrangler login- Workerをデプロイする
npx wrangler deploy --config wrangler.local.tomlCron Triggersの設定
wrangler.tomlにスケジュール設定が含まれている。
[triggers]
crons = ["0 6 * * 6"]この設定により、毎週土曜日の15:00(JST)に自動実行される。
動作確認
- ログを確認する
npm run tailまたは
npx wrangler tailAWS Lambda版との違い
簡単になった点
| 項目 | AWS Lambda | Cloudflare Workers |
|---|---|---|
| ライブラリ管理 | chromium、fonts、axiosを別々にレイヤーとして管理する必要があった | @cloudflare/playwrightのみで完結 |
| 環境構築 | Cloud9やローカル環境でのライブラリビルドが必要だった | 設定ファイルに環境変数を記述するだけ |
| デプロイ | 複数のzipファイルをS3にアップロードしてレイヤー作成 | wrangler deployコマンド一発 |
変更した点
| 項目 | AWS Lambda | Cloudflare Workers |
|---|---|---|
| Discord連携方法 | WebhookによるPOST送信 | Bot APIによるメッセージ送信 |
| 言語とランタイム | Node.js(JavaScript) | TypeScript |
| ブラウザエンジン | playwright-core + chromiumパッケージ | @cloudflare/playwright(Cloudflare提供の統合パッケージ) |
メリット・デメリット
メリット
| 項目 | 説明 |
|---|---|
| 環境構築の簡単さ | ライブラリやレイヤーの管理が不要 |
| デプロイの高速性 | コマンド一つで即座にデプロイ完了 |
| 設定管理 | 設定ファイルに環境変数を記述するだけ |
| 型安全性 | TypeScriptによる開発時エラー検出 |
| 実行性能 | Cloudflareのエッジネットワークを活用 |
デメリット
| 項目 | 説明 |
|---|---|
| 実行時間制限 | CPUタイムに制約がある |
| 処理の複雑さ | 複雑なスクリーンショット処理には不向きな場合がある |
| リソース調整 | AWS Lambdaと比べて細かな調整ができない |
参考
Cloudflare Workers Documentation
https://developers.cloudflare.com/workers/Cloudflare Playwright Documentation
https://developers.cloudflare.com/browser-rendering/platform/playwright/Discord Bot API Documentation
https://discord.com/developers/docs/introWrangler CLI Documentation
https://developers.cloudflare.com/workers/wrangler/
GitHub
- capture-screenshot-to-discord-bot
https://github.com/katsuobushiFPGA/capture-screenshot-to-discord-bot
おわりに
AWS Lambda版では複雑だったライブラリ管理やデプロイプロセスが、Cloudflare Workersでは大幅に簡略化された。
特に、レイヤーの作成やS3へのzipファイルアップロードなどの煩雑な作業が不要になり、設定ファイルの記述とコマンド一つでデプロイできるのは非常に快適だった。
一方で、Cloudflare Workersの実行時間制限やリソース制約は理解しておく必要があるが、今回のようなシンプルなスクリーンショット撮影程度であれば全く問題なく動作している。
とりあえずはこれで運用していこうと思う。
※AWS Lambdaで作ったやつも、AWS SAMを使って構築したらもっと楽かもな~。