サーバーレスで無料!DifyとCloudflare Workersで作るDiscord AIボット
はじめに
ちょっと前に、私のX(旧Twitter)でこんなポストをしたんですよ。
「Discordコミュニティをもっと活発にしたい?🎉 DifyとReplitを使えば、オリジナルのAI搭載のDiscordボットが簡単に作れる!」って。
あの頃はReplitでボットを作ってみて、確かにブラウザだけでサクッと開発できて便利だったんですけど、無料プランだとプロジェクトが非アクティブになると自動的にスリープモードになって処理が止まっちゃうんですよね。
例えば5〜10分操作がないだけでボットがオフラインになっちゃって、常時稼働が必要なDiscordボットにはちょっと厳しい制限があるんです。
そんな経験から、「自分のDiscordサーバーに、独自の知識を学んだAIボットがいたら、どんなに便利だろう…」って改めて思いました🤔
あの夢を、無料で、しかもサーバーの管理なんて一切不要のサーバーレスで、もっと安定して実現できる方法を見つけたんですよ!
今回は、ノーコードでAIアプリを作れるDify、サーバーレス環境のCloudflare Workers、そしてDiscordを組み合わせ、スラッシュコマンドでAIとチャットできるボットを作ってみましょう。
初心者さんでもわかりやすいように、手順を丁寧に解説しますね😊
この記事で作るもの
- Discordで
/ask
というスラッシュコマンドを打つと、Difyで作ったAIが答えてくれるボットです。 - すべての仕組みはCloudflare Workersで動くので、サーバー管理はゼロ!
- Difyのワークフロー機能を使って、AIの指示や流れを自由にカスタマイズできますよ。簡単につながるだけで、すごいボットができちゃいます!
対象読者
- オリジナルのDiscordボットを作ってみたい方
- サーバーレスに興味がある方
- DifyでAIを他のサービスと連携させたい方
- JavaScriptの基本的な知識がある方(ちょっとしたプログラミングだけですよ)
必要なもの
- Dify のアカウント
- Discord のアカウント
- Cloudflare のアカウント
これらが揃ったら、さっそく始めましょう! ワクワクしますね。
1. 全体の構成
まずは、どんな仕組みで動くのか、全体像をサクッと把握しましょうね。
- Discord が、ユーザーのスラッシュコマンドを受け取って、Cloudflare Workerに情報を送ります。
- Cloudflare Worker は、DiscordからのリクエストをDifyに渡しやすい形に変換して、Difyに送っちゃいます。
- Dify は、受け取った質問を基にAIを動かして、結果をWorkerに戻します。
- Cloudflare Worker は、その結果をDiscordにメッセージとして投稿するんです。
2. DifyでAIワークフローを作成する
最初に、ボットの脳みそ部分をDifyで作っていきましょう。大丈夫です、簡単に作ることができます👍
ステップ1: Difyでアプリを作成
- Difyにログインして、「アプリを作成」から「チャットボット」か「ワークフロー」を選びます。今回は柔軟にカスタムできるワークフローを選ぶと良いですね。
- アプリ名(例:
My Discord Bot
)を入れて、作成ボタンをポチッと押しましょう。
ステップ2: ワークフローを設計する
編集画面(スタジオ)で、AIの動きを決めていきます。一番シンプルな形にしますね。
-
「開始」ノード
ここが外からの入力を受け取る入り口です。inputs
に変数を2つ定義しましょう。
変数名 | タイプ | 説明 |
---|---|---|
command |
選択 |
コマンド名で、今回はask という固定値が入ります。 |
prompt |
長文 |
ユーザーの質問本文ですよ。 |
-
「IF/ELSE」ノード
ここは、将来コマンドを増やしたくなった時のために置いておきます(今は特に意味ないけど、拡張しやすいんです)。 -
「LLM」ノード
ここでAIが実際に考えます。「開始」ノードのprompt
を、このノードのプロンプト入力につなぐだけでOK。ユーザーの質問がそのままAIに伝わりますね! -
「終了」ノード
AIの答えを外に出す部分です。「LLM」の出力をここにつなげて、出力変数名をtext
やanswer
にするとわかりやすいですよ。
これで、質問を受け取って答えを返すシンプルなAIができちゃいました。Difyって、ノードをつなぐだけで本格的なものが作れるんですよ😆
ステップ3: APIキーを取得する
- アプリの左メニューから「API」を選んでください。
- 「APIキー」セクションのキーをコピーして、安全に保管しましょう。
3. Discordでボットアプリを作成する
次は、ユーザーと話す窓口のDiscordボットを作りましょう。Developer Portalを使いますよ。
ステップ1: Discord Developer Portalでアプリを作成
Discord Developer PortalのApplications画面
- Discord Developer Portal にアクセスして、「New Application」をクリック。
- アプリ名を決めて作成します。
- 「Bot」ページでMessage Content Intentをオンに。こうすると、ボットがメッセージを読めるようになります。
Discord Developer PortalのBot画面
ステップ2: 必要な情報を取得する
Discord Developer PortalのGeneral Information画面
あとで使うので、3つの情報をメモしましょう。
-
Application ID
「General Information」ページのAPPLICATION ID
をコピー。 -
Public Key
同じページのPUBLIC KEY
をコピー。 -
Bot Token
Discord Developer PortalのBot画面
「Bot」ページで「Reset Token」を押して、表示されたトークンをコピー。
ステップ3: ボットをサーバーに追加する
-
「OAuth2」>「URL Generator」へ。
-
SCOPES で
bot
とapplications.commands
にチェック。
Discord Developer PortalのOAuth2画面 -
BOT PERMISSIONS でこれらにチェック(拡張を考えた多め設定です)。
View Channels
Send Messages
Embed Links
Attach Files
Read Message History
Discord Developer PortalのOAuth2画面 -
生成されたURLをブラウザで開いて、サーバーを選んで認証。
Discord Developer PortalのOAuth2画面 -
認証画面で「認証」をクリック。完了したら、ボットがサーバーに入りますよ!
Bot招待リンク画面
4. Cloudflare Workerをセットアップする
ここで、DiscordとDifyをつなぐWorkerを作ります。コードを入れるだけですよ!🙌
Cloudflare worker作成画面
ステップ1: Workerを作成する
- Cloudflareダッシュボードから「Workers & Pages」へ。
- 「Create application」>「Create Worker」を選んで、「Hello World」テンプレート。
- Worker名(例:
discord-dify-bot
)を決めて、「Deploy」。
ステップ2: Workerのコードを編集する
Cloudflare worker コード編集画面
「Edit code」を押して、このコードを貼り付けましょう。全部の処理が入ってますよ。コピペオッケーです👌
const InteractionType = {
PING: 1,
APPLICATION_COMMAND: 2,
};
const InteractionResponseType = {
PONG: 1,
CHANNEL_MESSAGE_WITH_SOURCE: 4,
DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE: 5,
};
export default {
async fetch(request, env, ctx) {
if (request.method !== 'POST') {
return new Response('Method Not Allowed', { status: 405 });
}
const isValidRequest = await verifySignature(request, env);
if (!isValidRequest) {
console.error('Invalid signature');
return new Response('Bad request signature.', { status: 401 });
}
const interaction = await request.json();
if (interaction.type === InteractionType.PING) {
return createJsonResponse({ type: InteractionResponseType.PONG });
}
if (interaction.type === InteractionType.APPLICATION_COMMAND && interaction.data.name === 'ask') {
const prompt = interaction.data.options?.find(opt => opt.name === 'prompt')?.value || '';
const userId = `discord:${interaction.member?.user?.id || interaction.user?.id}`;
if (!prompt) {
return createJsonResponse({
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
data: { content: 'プロンプトを入力してください。', flags: 64 },
});
}
ctx.waitUntil(handleDifyRequest(interaction.token, prompt, userId, env));
return createJsonResponse({ type: InteractionResponseType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE });
}
console.warn('Received an unhandled interaction type:', interaction.type);
return createJsonResponse({
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
data: { content: '不明なコマンドです。', flags: 64 },
});
},
};
async function handleDifyRequest(interactionToken, prompt, userId, env) {
try {
const aiResponse = await callDifyWorkflow(prompt, userId, env);
await sendFollowupMessage(env.DISCORD_APPLICATION_ID, interactionToken, aiResponse);
} catch (error) {
console.error('Error in handleDifyRequest:', error);
await sendFollowupMessage(env.DISCORD_APPLICATION_ID, interactionToken, 'AIとの通信中にエラーが発生しました。');
}
}
async function callDifyWorkflow(prompt, userId, env) {
const difyApiUrl = `${env.DIFY_API_BASE_URL || 'https://api.dify.ai'}/v1/workflows/run`;
const response = await fetch(difyApiUrl, {
method: 'POST',
headers: {
'Authorization': `Bearer ${env.DIFY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
inputs: { command: "ask", prompt },
response_mode: 'blocking',
user: userId,
}),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Dify API error: ${response.status} - ${errorText}`);
}
const result = await response.json();
return result.data?.outputs?.text || '応答が空でした。';
}
async function sendFollowupMessage(applicationId, token, content) {
const webhookUrl = `https://discord.com/api/v10/webhooks/${applicationId}/${token}`;
await fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content: String(content).substring(0, 2000) }),
});
}
async function verifySignature(request, env) {
const signature = request.headers.get('x-signature-ed25519');
const timestamp = request.headers.get('x-signature-timestamp');
const body = await request.clone().text();
if (!signature || !timestamp || !body || !env.DISCORD_PUBLIC_KEY) return false;
try {
const signatureBytes = hexToUint8Array(signature);
const publicKeyBytes = hexToUint8Array(env.DISCORD_PUBLIC_KEY);
const messageBytes = new TextEncoder().encode(timestamp + body);
const cryptoKey = await crypto.subtle.importKey('raw', publicKeyBytes, { name: 'Ed25519' }, false, ['verify']);
return await crypto.subtle.verify('Ed25519', cryptoKey, signatureBytes, messageBytes);
} catch (error) {
console.error('Error verifying signature:', error);
return false;
}
}
function createJsonResponse(data) {
return new Response(JSON.stringify(data), { headers: { 'Content-Type': 'application/json' } });
}
function hexToUint8Array(hex) {
return new Uint8Array(hex.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
}
貼り付けたら、「Save and deploy」を押して完了です。コードはもう準備済みなので、あと少しですよ❗
ステップ3: 環境変数を設定する
Cloudflare 設定画面
- Workerの「Settings」>「Variables」へ。
- 「Environment Variables」にこれらを追加。値は暗号化(Encrypt)で安全に!
変数名 | 値 |
---|---|
DIFY_API_KEY |
DifyのAPIキー |
DISCORD_APPLICATION_ID |
DiscordのApplication ID |
DISCORD_PUBLIC_KEY |
DiscordのPublic Key |
DIFY_API_BASE_URL (任意) |
DifyのURL(公式なら不要) |
5. 仕上げ:全てを繋ぎこむ
最後に、DiscordにWorkerのURLを教えて、コマンドを登録しましょう。これで完成です!👏👏👏
ステップ1: Interactions Endpoint URLを設定する
Discord Developer PortalのGeneral Information画面
- CloudflareでWorkerのURL(
xxx.yyy.workers.dev
)をコピー。 - Discord Developer Portalの「General Information」で
INTERACTIONS ENDPOINT URL
に貼り付けて保存。- エラーが出なければOK! 鍵の設定が正しければ通りますよ。
ステップ2: スラッシュコマンドを登録する
ターミナルで一度だけ実行。curlコマンドをあなたの情報に置き換えてくださいね。クラウドだけでもDifyのHTTPノードを使って実行できますよ🙌
-
YOUR_DISCORD_BOT_TOKEN
: Bot Token -
YOUR_DISCORD_APPLICATION_ID
: Application ID
curl -X PUT \
-H "Authorization: Bot YOUR_DISCORD_BOT_TOKEN" \
-H "Content-Type: application/json" \
-d '[
{
"name": "ask",
"description": "AIに質問する",
"options": [
{
"name": "prompt",
"description": "質問内容",
"type": 3,
"required": true
}
]
}
]' \
"https://discord.com/api/v10/applications/YOUR_DISCORD_APPLICATION_ID/commands"
JSONが返ってきたら成功です!⭕
6. 動作確認
お疲れ様でした! Discordサーバーで /ask
を試してみてください。質問を入れて実行すると、AIが答えてくれます!🎉🎉
まとめ
いやー、こんなに簡単にDify、Cloudflare Workers、Discordを連携させてサーバーレスAIボットが作れちゃうなんて、すごいですよね!😁
DifyでAIアプリをサクッと準備して、Cloudflare Workersでお手軽にサーバー管理をゼロにしつつ、Discordのチャットから気軽に質問できるボットが完成です。
Xで投稿した動画で実際に動きを確認できます👇
拡張も自由自在ですよー、知識ベースを追加したり他のAPIとつなげたりして、自分だけのボットをどんどん進化させてみてくださいね。実際に作ってみて、わからないところがあったら遠慮なく聞いてください!
それと、XでDifyのTipsを毎日ポストしてるので、フォローしてもらえるとめっちゃ嬉しいです😊
Discussion