Discord
コミュニケーションツール
Discord
概要
Discordは、ゲーム・コミュニティ向けから発展したボイス・テキストチャットプラットフォームです。優秀な音声・動画機能、コミュニティ管理機能とBot機能が充実しています。1億9千万人の月間アクティブユーザーを抱え、開発者コミュニティ、教育機関、オープンソースプロジェクトでの利用が拡大中です。
詳細
Discord(ディスコード)は、2015年にリリースされたボイス・テキストチャットプラットフォームです。当初はゲーマー向けに設計されましたが、現在では1億9千万人の月間アクティブユーザーを抱え、ゲーミング以外の用途でも急速に採用が拡大しています。特に開発者コミュニティ、教育機関、オープンソースプロジェクトでの利用が増加しています。
Discordの最大の特徴は、低遅延・高品質なボイスチャット機能と、開発者フレンドリーなAPI設計です。Discord APIとdiscord.jsライブラリを使用することで、豊富なBot機能を簡単に構築できます。2024年には、Slash Commands機能の大幅改善、discord.js v14の正式リリース、Premium Apps対応など、開発者体験が大幅に向上しました。
Slash Commandsは、従来のテキストベースコマンドと異なり、Discordクライアントに統合された一級の操作方法を提供し、タイプ入力検証、動的選択肢、エフェメラルメッセージ、ポップアップフォーム等の高度な機能を利用できます。また、サーバーレス環境でのBot実行、Webhooksによる外部連携、リッチなEmbed表示など、モダンな開発手法に対応しています。
メリット・デメリット
メリット
- 優秀な音声品質: 低遅延・高品質なボイスチャット機能
- Slash Commands: Discord統合のリッチなコマンドインターフェース
- 豊富なBot API: discord.js、discord.py等の充実したライブラリ
- コミュニティ管理: ロール、権限、モデレーション機能
- 無料利用: 基本機能は完全無料で利用可能
- 開発者フレンドリー: 分かりやすいAPI設計と豊富なドキュメント
- 活発なコミュニティ: 開発者コミュニティとサポート体制
- サーバーレス対応: CloudflareWorkers等での軽量Bot実行
デメリット
- ゲーミング色: ビジネス利用に対するイメージ
- 学習コスト: 高度なBot開発には知識が必要
- API制限: レート制限とDaily limit
- プライバシー懸念: 一部の企業・組織での利用制限
- UI複雑性: 多機能すぎて初心者には複雑
- 検索機能: メッセージ検索機能の制限(無料版)
主要リンク
- Discord公式サイト
- Discord Developer Portal
- discord.js Guide
- discord.js Documentation
- Discord.py
- Discord Bot Examples
- Discord Community
書き方の例
Hello World Bot(discord.js v14)
const { Client, Events, GatewayIntentBits } = require('discord.js');
// Botクライアントの作成
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent
]
});
// Bot起動時の処理
client.once(Events.ClientReady, readyClient => {
console.log(`Ready! Logged in as ${readyClient.user.tag}`);
});
// メッセージ受信時の処理
client.on(Events.MessageCreate, message => {
if (message.author.bot) return;
if (message.content === 'ping') {
message.reply('pong!');
}
});
// Botログイン
client.login(process.env.DISCORD_TOKEN);
Slash Command実装
const { SlashCommandBuilder, REST, Routes } = require('discord.js');
// コマンド定義
const commands = [
new SlashCommandBuilder()
.setName('ping')
.setDescription('Replies with Pong!'),
new SlashCommandBuilder()
.setName('user')
.setDescription('Provides information about the user.')
.addUserOption(option =>
option
.setName('target')
.setDescription('The user to get info about')
.setRequired(false)),
new SlashCommandBuilder()
.setName('echo')
.setDescription('Replies with your input!')
.addStringOption(option =>
option
.setName('input')
.setDescription('The input to echo back')
.setRequired(true))
.addBooleanOption(option =>
option
.setName('ephemeral')
.setDescription('Whether the reply should be ephemeral'))
];
// コマンド登録
const rest = new REST().setToken(process.env.DISCORD_TOKEN);
(async () => {
try {
console.log('Started refreshing application (/) commands.');
await rest.put(
Routes.applicationGuildCommands(process.env.CLIENT_ID, process.env.GUILD_ID),
{ body: commands }
);
console.log('Successfully reloaded application (/) commands.');
} catch (error) {
console.error(error);
}
})();
Slash Command ハンドラー
const { Events, EmbedBuilder } = require('discord.js');
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isChatInputCommand()) return;
const { commandName } = interaction;
switch (commandName) {
case 'ping':
await interaction.reply('Pong!');
break;
case 'user':
const user = interaction.options.getUser('target') || interaction.user;
const member = interaction.guild.members.cache.get(user.id);
const userEmbed = new EmbedBuilder()
.setTitle(`User Info: ${user.username}`)
.setThumbnail(user.displayAvatarURL())
.addFields(
{ name: 'Username', value: user.username, inline: true },
{ name: 'User ID', value: user.id, inline: true },
{ name: 'Account Created', value: user.createdAt.toDateString(), inline: true }
)
.setColor('#0099ff');
if (member) {
userEmbed.addFields(
{ name: 'Joined Server', value: member.joinedAt.toDateString(), inline: true }
);
}
await interaction.reply({ embeds: [userEmbed] });
break;
case 'echo':
const input = interaction.options.getString('input');
const isEphemeral = interaction.options.getBoolean('ephemeral') || false;
await interaction.reply({
content: `Echo: ${input}`,
ephemeral: isEphemeral
});
break;
default:
await interaction.reply({
content: 'Unknown command!',
ephemeral: true
});
}
});
高度なSlash Command(選択肢付き)
const { SlashCommandBuilder } = require('discord.js');
const data = new SlashCommandBuilder()
.setName('weather')
.setDescription('Get weather information!')
.addStringOption(option =>
option
.setName('city')
.setDescription('Select a city')
.setRequired(true)
.addChoices(
{ name: 'Tokyo', value: 'tokyo' },
{ name: 'New York', value: 'newyork' },
{ name: 'London', value: 'london' },
{ name: 'Paris', value: 'paris' }
))
.addStringOption(option =>
option
.setName('unit')
.setDescription('Temperature unit')
.addChoices(
{ name: 'Celsius', value: 'celsius' },
{ name: 'Fahrenheit', value: 'fahrenheit' }
));
// コマンド実行
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === 'weather') {
const city = interaction.options.getString('city');
const unit = interaction.options.getString('unit') || 'celsius';
// 外部API呼び出し例
try {
const weatherData = await getWeatherData(city, unit);
const weatherEmbed = new EmbedBuilder()
.setTitle(`Weather in ${weatherData.cityName}`)
.setDescription(weatherData.description)
.addFields(
{ name: 'Temperature', value: `${weatherData.temp}°${unit === 'celsius' ? 'C' : 'F'}`, inline: true },
{ name: 'Humidity', value: `${weatherData.humidity}%`, inline: true },
{ name: 'Wind Speed', value: `${weatherData.windSpeed} km/h`, inline: true }
)
.setColor('#87CEEB')
.setTimestamp();
await interaction.reply({ embeds: [weatherEmbed] });
} catch (error) {
await interaction.reply({
content: 'Failed to fetch weather data!',
ephemeral: true
});
}
}
});
オートコンプリート機能
const { SlashCommandBuilder } = require('discord.js');
// オートコンプリート対応コマンド
const autocompleteCommand = new SlashCommandBuilder()
.setName('search')
.setDescription('Search for something!')
.addStringOption(option =>
option
.setName('query')
.setDescription('Search query')
.setAutocomplete(true)
.setRequired(true));
// オートコンプリート応答
client.on(Events.InteractionCreate, async interaction => {
if (interaction.isAutocomplete()) {
const command = interaction.commandName;
if (command === 'search') {
const focusedValue = interaction.options.getFocused();
// 検索候補の生成
const choices = [
'JavaScript Tutorial',
'Discord Bot Guide',
'API Documentation',
'Programming Tips',
'Web Development'
];
const filtered = choices.filter(choice =>
choice.toLowerCase().includes(focusedValue.toLowerCase())
).slice(0, 25); // Discord制限: 最大25個
await interaction.respond(
filtered.map(choice => ({ name: choice, value: choice }))
);
}
}
});
モーダル(ポップアップフォーム)
const {
SlashCommandBuilder,
ModalBuilder,
TextInputBuilder,
TextInputStyle,
ActionRowBuilder
} = require('discord.js');
// モーダル表示コマンド
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === 'feedback') {
const modal = new ModalBuilder()
.setCustomId('feedbackModal')
.setTitle('Feedback Form');
const titleInput = new TextInputBuilder()
.setCustomId('feedbackTitle')
.setLabel('Title')
.setStyle(TextInputStyle.Short)
.setMaxLength(100)
.setRequired(true);
const bodyInput = new TextInputBuilder()
.setCustomId('feedbackBody')
.setLabel('Feedback')
.setStyle(TextInputStyle.Paragraph)
.setMaxLength(1000)
.setRequired(true);
const firstActionRow = new ActionRowBuilder().addComponents(titleInput);
const secondActionRow = new ActionRowBuilder().addComponents(bodyInput);
modal.addComponents(firstActionRow, secondActionRow);
await interaction.showModal(modal);
}
});
// モーダル送信処理
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isModalSubmit()) return;
if (interaction.customId === 'feedbackModal') {
const title = interaction.fields.getTextInputValue('feedbackTitle');
const body = interaction.fields.getTextInputValue('feedbackBody');
const feedbackEmbed = new EmbedBuilder()
.setTitle('New Feedback Received')
.addFields(
{ name: 'Title', value: title },
{ name: 'Feedback', value: body },
{ name: 'User', value: interaction.user.username }
)
.setColor('#00ff00')
.setTimestamp();
// フィードバックチャンネルに送信
const feedbackChannel = interaction.guild.channels.cache.get('FEEDBACK_CHANNEL_ID');
if (feedbackChannel) {
await feedbackChannel.send({ embeds: [feedbackEmbed] });
}
await interaction.reply({
content: 'Thank you for your feedback!',
ephemeral: true
});
}
});
Button と Select Menu
const {
ButtonBuilder,
ButtonStyle,
StringSelectMenuBuilder,
StringSelectMenuOptionBuilder,
ActionRowBuilder
} = require('discord.js');
// ボタン付きメッセージ
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === 'menu') {
const confirm = new ButtonBuilder()
.setCustomId('confirm')
.setLabel('Confirm')
.setStyle(ButtonStyle.Success);
const cancel = new ButtonBuilder()
.setCustomId('cancel')
.setLabel('Cancel')
.setStyle(ButtonStyle.Danger);
const info = new ButtonBuilder()
.setCustomId('info')
.setLabel('More Info')
.setStyle(ButtonStyle.Secondary);
const row = new ActionRowBuilder()
.addComponents(confirm, cancel, info);
await interaction.reply({
content: 'Choose an action:',
components: [row]
});
}
});
// ボタンクリック処理
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isButton()) return;
switch (interaction.customId) {
case 'confirm':
await interaction.update({
content: '✅ Confirmed!',
components: []
});
break;
case 'cancel':
await interaction.update({
content: '❌ Cancelled!',
components: []
});
break;
case 'info':
const select = new StringSelectMenuBuilder()
.setCustomId('infoSelect')
.setPlaceholder('Select information type')
.addOptions(
new StringSelectMenuOptionBuilder()
.setLabel('General Info')
.setDescription('Basic information')
.setValue('general'),
new StringSelectMenuOptionBuilder()
.setLabel('Technical Details')
.setDescription('Technical specifications')
.setValue('technical'),
new StringSelectMenuOptionBuilder()
.setLabel('Help & Support')
.setDescription('Get help and support')
.setValue('support')
);
const selectRow = new ActionRowBuilder()
.addComponents(select);
await interaction.update({
content: 'Select information type:',
components: [selectRow]
});
break;
}
});
Webhook連携
const { WebhookClient, EmbedBuilder } = require('discord.js');
// Webhook設定
const webhook = new WebhookClient({
url: 'WEBHOOK_URL_HERE'
});
// GitHub Webhook連携例
app.post('/github-webhook', async (req, res) => {
const payload = req.body;
if (payload.action === 'opened' && payload.pull_request) {
const pr = payload.pull_request;
const embed = new EmbedBuilder()
.setTitle('New Pull Request')
.setURL(pr.html_url)
.setDescription(pr.title)
.addFields(
{ name: 'Author', value: pr.user.login, inline: true },
{ name: 'Repository', value: payload.repository.full_name, inline: true },
{ name: 'Branch', value: `${pr.head.ref} → ${pr.base.ref}`, inline: true }
)
.setColor('#28a745')
.setTimestamp();
await webhook.send({
content: '📝 New Pull Request opened!',
embeds: [embed]
});
}
res.status(200).send('OK');
});
// CI/CD通知例
async function sendBuildNotification(status, buildUrl, commit) {
const color = status === 'success' ? '#28a745' : '#dc3545';
const emoji = status === 'success' ? '✅' : '❌';
const embed = new EmbedBuilder()
.setTitle(`${emoji} Build ${status.toUpperCase()}`)
.setURL(buildUrl)
.setDescription(`Commit: ${commit.message}`)
.addFields(
{ name: 'Author', value: commit.author, inline: true },
{ name: 'Branch', value: commit.branch, inline: true },
{ name: 'Duration', value: commit.duration, inline: true }
)
.setColor(color)
.setTimestamp();
await webhook.send({ embeds: [embed] });
}
環境変数設定
# .env ファイル
DISCORD_TOKEN=your-bot-token
CLIENT_ID=your-client-id
GUILD_ID=your-guild-id
# オプション設定
WEBHOOK_URL=your-webhook-url
DATABASE_URL=your-database-url
REDIS_URL=your-redis-url
# API Key(外部連携用)
WEATHER_API_KEY=your-weather-api-key
GITHUB_TOKEN=your-github-token
package.json例
{
"name": "discord-bot",
"version": "1.0.0",
"description": "Advanced Discord bot with slash commands",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js",
"deploy-commands": "node deploy-commands.js"
},
"dependencies": {
"discord.js": "^14.20.0",
"dotenv": "^16.0.3"
},
"devDependencies": {
"nodemon": "^3.0.0"
},
"engines": {
"node": ">=16.9.0"
}
}