CloudflareアラートをWorker + Gemini APIでDiscordに要約通知する

Cloudflareの通知をDiscord Webhookに流していたら、通知が大量に届くようになってしまった。重要なアラートが埋もれるので、Cloudflare Workers + Gemini APIで要約してから投稿するようにした。 方針 Cloudflareの通知設定で追加できるアラートはとりあえず全部有効にしている。各アラートが実際に役立つかは様子を見ながら判断する予定。 ただしセキュリティアラートはだいたい30分に数回届いたため、現時点では通知をスキップするようにした。それ以外のアラートは引き続き通知して様子見中。 構成 Cloudflare Alert → Worker受信 → フィルタリング(スキップ対象なら早期リターン) → Gemini API で日本語要約 → Discord Webhook に送信 前提 Cloudflare WorkersにDiscord Webhook通知のWorkerが既にある Google AI StudioのAPIキーを持っている モデル選定 Geminiのモデルは料金帯がいくつかある。 gemini-2.5-pro > gemini-2.5-flash > gemini-2.5-flash-lite Cloudflareアラートの要約程度であれば gemini-2.5-flash-lite で十分。一番安い。 最新のモデル名は公式ドキュメントで確認すること。 https://ai.google.dev/gemini-api/docs/models 実装 フィルタリングの考え方 頻度の高いアラートはWorker側でスキップできるようにしている。SKIP_ALERT_TYPES に列挙したタイプが一致した場合、Gemini APIを呼ばずに早期リターンする。不要なAPI呼び出しも減るのでコスト面でも良い。 どのアラートがどの alert_type を持つかはCloudflareの公式ドキュメントで確認できる。 https://developers.cloudflare.com/notifications/notification-available/ Worker コード // スキップしたいアラートタイプ(頻度が高くて邪魔なものを列挙) const SKIP_ALERT_TYPES = [ "security_alerts", // セキュリティアラート:30分に数回来るので無効化中 ]; async function summarizeWithGemini(apiKey, input) { const prompt = `以下のCloudflareアラートを日本語で3行以内に要約してください。重要度(🔴高/🟡中/🟢低)も判定してください。\n\n${input}`; const res = await fetch( `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-lite-preview-06-17:generateContent?key=${apiKey}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }] }) } ); const data = await res.json(); console.log("Gemini response:", JSON.stringify(data)); return data.candidates?.[0]?.content?.parts?.[0]?.text || "要約失敗"; } export default { async fetch(request, env) { if (request.method !== "POST") { return new Response("ok"); } let body; try { body = await request.json(); } catch { return new Response("invalid json", { status: 400 }); } // アラートタイプによるフィルタリング const alertType = body.text?.alert_type || body.alert_type || ""; if (SKIP_ALERT_TYPES.some(t => alertType.includes(t))) { console.log(`Skipped alert type: ${alertType}`); return new Response("skipped"); } // Geminiには常にbody全体を渡す const input = JSON.stringify(body).slice(0, 500); const summary = await summarizeWithGemini(env.GEMINI_API_KEY, input); const message = { username: "Cloudflare", embeds: [{ title: body.text?.title || body.text?.description || "Cloudflare Notification", description: summary, color: 0xF6821F, timestamp: new Date().toISOString() }] }; await fetch(env.DISCORD_WEBHOOK, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(message) }); return new Response("ok"); } } シークレット登録 wrangler secret put GEMINI_API_KEY wrangler secret put DISCORD_WEBHOOK 動作確認 curl -X POST https://<your-worker>.workers.dev \ -H "Content-Type: application/json" \ -d '{"text": {"title": "テスト通知", "description": "これはテストです"}}' スキップ確認: ...

April 12, 2026 · 2 min

Canvas 2D API でピクセルアートタイルを「手書き」する実装テクニック

Canvas 2D API でピクセルアートタイルを「手書き」する実装テクニック 1. 概要 Web ゲーム開発、特にローグライクやタクティカル RPG を開発する際、避けて通れないのが「マップの描画」だ。通常、これらは「タイルセット」と呼ばれる画像ファイルを読み込んで描画するが、開発の初期段階や、あえて外部アセットに頼りたくない場合、Canvas 2D API を使ってプログラムで直接タイルを描画する「手書き(プログラマティック描画)」の手法が非常に強力な武器になる。 本記事では、HTML5 Canvas の fillRect や beginPath などの基本命令のみを使い、擬似的なピクセルアート風のタイルを描画するテクニックを解説する。画像を用意する手間を省きつつ、動的に色や形状を変更できる柔軟な描画システムを構築しよう。 2. タイル描画の基本構造 まずは、どのようなタイルでも共通して利用できる描画の入り口を作る。ピクセル座標ではなく、タイル座標(x, y)とタイルサイズ(tileSize)を受け取る設計にすることで、グリッドベースのシステムと統合しやすくなる。 /** * タイルを描画するメイン関数 * @param {CanvasRenderingContext2D} ctx - Canvasコンテキスト * @param {string} tileType - タイルの種類 ('wall', 'floor', 'grass', etc.) * @param {number} x - タイルのX座標(グリッド単位) * @param {number} y - タイルのY座標(グリッド単位) * @param {number} tileSize - 1タイルのピクセルサイズ * @param {string} fieldType - フィールドの種類 ('meadow', 'forest', 'mountain') */ function drawTile(ctx, tileType, x, y, tileSize, fieldType = 'meadow') { const px = x * tileSize; const py = y * tileSize; ctx.save(); ctx.translate(px, py); switch (tileType) { case 'floor': drawFloor(ctx, tileSize, fieldType); break; case 'wall': drawWall(ctx, tileSize, fieldType); break; case 'object_grass': drawFloor(ctx, tileSize, fieldType); drawGrass(ctx, tileSize); break; case 'object_tree': drawFloor(ctx, tileSize, fieldType); drawTree(ctx, tileSize); break; case 'stairs_down': drawFloor(ctx, tileSize, fieldType); drawStairs(ctx, tileSize, false); break; default: ctx.fillStyle = '#333'; ctx.fillRect(0, 0, tileSize, tileSize); } ctx.restore(); } この設計のポイントは、ctx.translate を使ってタイルの左上を原点 (0, 0) に固定することだ。これにより、各タイルの描画ロジック内で座標計算を簡略化できる。 ...

March 28, 2026 · 4 min