概要
前回の記事では、discord botを無料でデプロイしてスラッシュコマンドを受け付ける方法を解説しました。
今回は、実際に有用なスラッシュコマンドを登録してみて、それを実行してみるまでを記載しようと思います。
登録するのは、以下のような形式で任意のGitHub actionを起動できるコマンドです。
text
/run repository:<リポジトリ> workflow_file:<ワークフローYAML> ref:<ブランチ(任意)>
事前準備
前回記事の内容の通りにテスト用のrunコマンドの登録まで行う。
手順
1. PAT払い出し & Vercel環境変数に登録
- GitHubでアカウントのSettings > Developer settings > Personal access tokens からトークンを発行
- 必要権限: Contents(Fine-grained tokens)/repo(Classic tokens)
- Vercelのプロジェクトページ > Settings > Environment Variables に
GITHUB_TOKENとして登録
2. ファイル配置(前回記事との差分のみ)
- api/handlers/runCommand.ts
-
runコマンドの処理を定義しています。エラーの場合、入力の問題関連は実行者にだけ見える形で返信、サーバーの問題と思われる場合はチャンネルに通知します。 - URLの
ownerは自身のGitHub環境に合わせて書き換えてください
-
api/handlers/runCommand.ts
import { Context } from 'hono';
import {
InteractionResponseType,
InteractionResponseFlags
} from 'discord-interactions';
export const runCommandHandler = {
name: 'run',
execute: async (c: Context, data: any) => {
const repository = data.options?.find((opt: any) => opt.name === 'repository')?.value;
const workflowFile = data.options?.find((opt: any) => opt.name === 'workflow_file')?.value;
const ref = data.options?.find((opt: any) => opt.name === 'ref')?.value || 'main';
if (!repository || !workflowFile) {
return c.json({
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
data: {
content: '❌ リポジトリ名とワークフローファイル名は必須です。',
flags: InteractionResponseFlags.EPHEMERAL
}
});
}
try {
// GitHub Actions ワークフロー実行
const response = await fetch(
`https://api.github.com/repos/owner/${repository}/actions/workflows/${workflowFile}/dispatches`,
{
method: 'POST',
headers: {
'Authorization': `token ${process.env.GITHUB_TOKEN}`,
'Accept': 'application/vnd.github.v3+json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
ref: ref,
}),
}
);
if (response.ok) {
return c.json({
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
data: {
content: `✅ GitHub Actions を起動しました!\nリポジトリ: ${repository}\nワークフロー: ${workflowFile}\nブランチ: ${ref}`,
}
});
} else {
const errorData = await response.json();
// 404エラーの場合、利用可能なワークフローを取得
if (response.status === 404) {
const workflowsResponse = await fetch(
`https://api.github.com/repos/owner/${repository}/contents/.github/workflows?ref=${ref}`,
{
headers: {
'Authorization': `token ${process.env.GITHUB_TOKEN}`,
'Accept': 'application/vnd.github.v3+json',
},
}
);
if (workflowsResponse.ok) {
const workflows = await workflowsResponse.json() as any[];
const availableWorkflows = workflows
.filter((wf: any) => wf.type === 'file' && wf.name.endsWith('.yml'))
.map((wf: any) => wf.name)
.join(', ');
return c.json({
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
data: {
content: `❌ ワークフロー "${workflowFile}" が見つかりません。\n利用可能なワークフロー: ${availableWorkflows}`,
flags: InteractionResponseFlags.EPHEMERAL
}
});
}
}
return c.json({
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
data: {
content: `❌ GitHub Actions の起動に失敗しました: ${JSON.stringify(errorData)}`,
}
});
}
} catch (error) {
return c.json({
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
data: {
content: `❌ エラーが発生しました: ${error}`,
}
});
}
}
};
- scripts/registerCommand.ts
- コマンド登録用スクリプト
-
REPOSITORY_CHOICESは自身のGitHub環境に合わせて書き換えてください
scripts/registerCommand.ts
const fetch = require('node-fetch');
require('dotenv').config();
// Discord API定数(discord-interactionsにないので手動定義)
const ApplicationCommandOptionType = {
STRING: 3,
} as const;
const DISCORD_TOKEN = process.env.DISCORD_TOKEN;
const DISCORD_CLIENT_ID = process.env.DISCORD_CLIENT_ID;
const DISCORD_GUILD_ID = process.env.DISCORD_GUILD_ID;
const REPOSITORY_CHOICES = [
{ name: 'api', value: 'api' },
{ name: 'front', value: 'front' },
];
const command = {
name: 'run',
description: 'GitHub Actions を実行します',
options: [
{
name: 'repository',
description: 'リポジトリ名',
type: ApplicationCommandOptionType.STRING,
required: true,
choices: REPOSITORY_CHOICES,
},
{
name: 'workflow_file',
description: 'ワークフローファイル名 (例: ci.yml)',
type: ApplicationCommandOptionType.STRING,
required: true,
},
{
name: 'ref',
description: 'ブランチ名 (デフォルト: main)',
type: ApplicationCommandOptionType.STRING,
required: false,
},
],
};
async function registerCommand() {
const res = await fetch(`https://discord.com/api/v10/applications/${DISCORD_CLIENT_ID}/guilds/${DISCORD_GUILD_ID}/commands`, {
method: 'POST',
headers: {
Authorization: `Bot ${DISCORD_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(command),
});
const responseText = await res.text();
if (!res.ok) {
console.error('❌ コマンド登録に失敗しました');
console.error(`エラー: ${res.status} - ${responseText}`);
process.exit(1);
}
const responseData = JSON.parse(responseText);
console.log('コマンド登録が成功しました!');
console.log(`コマンドID: ${responseData.id}`);
}
registerCommand().catch(console.error);
3. 再デプロイ
vercel --prod
4. コマンド登録しなおし
ts-node scripts/registerCommand.ts
5. 対象リポジトリでYAMLファイル準備
- 対象リポジトリの
.github/workflows/<任意>.ymlにアクションファイルを作成-
workflow_dispatch:があることを確認
-
最小例:
name: Discord Bot Test
on:
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Show message
run: echo "Hello from Discord bot!"
使い方
- Discordの対象サーバーで
/runを入力 -
repositoryとworkflow_fileを指定 - 必要なら
refを指定(未指定の場合はmain) - botから実行結果に応じた返信が来ます
おまけ:アクションの開始/終了時にdiscordに通知する
GitHubマーケットプレイスで配布されている、Action Status Discordを利用することで比較的簡単にdiscordへの通知を行えます。
手順
- discordのサーバー設定 > 連携サービス > ウェブフック > 新しいウェブフック からウェブフックURLを発行
- GitHubのリポジトリ > Settings > Secrets and variables > Actions に
DISCORD_WEBHOOKという名前で発行したウェブフックURLを登録 -
.github/workflowsのymlファイルに通知アクションを追加
アクションファイル例:
name: Discord Bot Test
on:
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Notify Start
uses: sarisia/actions-status-discord@v1
if: always()
with:
webhook: ${{ secrets.DISCORD_WEBHOOK }}
noprefix: true
color: 0x5865F2
- name: Simulate long task
run: |
echo "Starting long task..."
for i in {1..5}; do
echo "Working... step $i/5"
sleep 10
done
echo "Long task finished."
- name: Notify End
uses: sarisia/actions-status-discord@v1
if: always()
with:
webhook: ${{ secrets.DISCORD_WEBHOOK }}
まとめ
今回はDiscord Botを活用したCI/CDについて紹介しました。
Github Actionsに限らず色々活用できると思いますので、Discordを使った開発をする際は有効に活用していきたいですね。
最後まで読んでいただきありがとうございます。


