Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 14 additions & 24 deletions commands/announcement-config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const { SlashCommandBuilder, ChannelType, PermissionFlagsBits, EmbedBuilder } = require('discord.js');
const { doc, setDoc, getDoc } = require('firebase/firestore');
const { SlashCommandBuilder, ChannelType, PermissionFlagsBits } = require('discord.js');
const { doc, setDoc } = require('firebase/firestore');
const { createSuccessEmbed, createStandardEmbed, COLORS } = require('../src/utils/embedBuilder');

module.exports = {
data: new SlashCommandBuilder()
Expand All @@ -23,32 +24,21 @@ module.exports = {

try {
if (channel) {
// チャンネルを設定
await setDoc(settingsRef, {
announcementChannelId: channel.id
}, { merge: true });

const embed = new EmbedBuilder()
.setColor(0x00ff00)
.setTitle('✅ 設定完了')
.setDescription(`ボットからのお知らせを ${channel} で受信するように設定しました。`);
await setDoc(settingsRef, { announcementChannelId: channel.id }, { merge: true });
const embed = createSuccessEmbed('設定完了', `お知らせを ${channel} で受信するように設定しました。`);
await interaction.editReply({ embeds: [embed] });

} else {
// チャンネル設定を解除
await setDoc(settingsRef, {
announcementChannelId: null
}, { merge: true });

const embed = new EmbedBuilder()
.setColor(0xffcc00)
.setTitle('設定解除')
.setDescription('ボットからのお知らせ受信を無効にしました。');
await setDoc(settingsRef, { announcementChannelId: null }, { merge: true });
const embed = createStandardEmbed({
title: '[OK] 設定解除',
description: 'お知らせ受信を無効にしました。',
color: COLORS.WARNING
});
await interaction.editReply({ embeds: [embed] });
}
} catch (error) {
console.error('announcement-config コマンドエラー:', error);
await interaction.editReply({ content: '❌ 設定中にエラーが発生しました。' });
console.error('[ERROR] announcement-config error:', error);
await interaction.editReply({ content: '[ERROR] 設定に失敗しました。' });
}
}
};
};
59 changes: 26 additions & 33 deletions commands/feedback.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const { SlashCommandBuilder, EmbedBuilder, ModalBuilder, TextInputBuilder, TextInputStyle, ActionRowBuilder } = require('discord.js');
const { collection, addDoc, Timestamp } = require('firebase/firestore');
const { SlashCommandBuilder, ModalBuilder, TextInputBuilder, TextInputStyle, ActionRowBuilder, Timestamp } = require('discord.js');
const { collection, addDoc } = require('firebase/firestore');
const chalk = require('chalk');
const { createSuccessEmbed, COLORS } = require('../src/utils/embedBuilder');

module.exports = {
data: new SlashCommandBuilder()
Expand All @@ -10,68 +11,60 @@ module.exports = {
async execute(interaction) {
const modal = new ModalBuilder()
.setCustomId('feedback_modal')
.setTitle('フィードバックの送信');
.setTitle('FEEDBACK');

const typeInput = new TextInputBuilder()
.setCustomId('feedback_type')
.setLabel('種類を選択してください')
.setLabel('種類')
.setStyle(TextInputStyle.Short)
.setPlaceholder('例: 感想、機能要望、不具合報告')
.setPlaceholder('例: 要望、不具合報告')
.setRequired(true);

const contentInput = new TextInputBuilder()
.setCustomId('feedback_content')
.setLabel('内容を詳しくお聞かせください')
.setLabel('内容')
.setStyle(TextInputStyle.Paragraph)
.setPlaceholder('どのような機能が欲しいですか?\n不具合の場合は、どのコマンドでどのような問題が発生したか教えてください。')
.setPlaceholder('内容を入力してください')
.setRequired(true);

const firstActionRow = new ActionRowBuilder().addComponents(typeInput);
const secondActionRow = new ActionRowBuilder().addComponents(contentInput);

modal.addComponents(firstActionRow, secondActionRow);
modal.addComponents(
new ActionRowBuilder().addComponents(typeInput),
new ActionRowBuilder().addComponents(contentInput)
);

await interaction.showModal(modal);

try {
const submitted = await interaction.awaitModalSubmit({
time: 60000 * 5, // 5分
time: 300000,
filter: i => i.user.id === interaction.user.id,
});

const type = submitted.fields.getTextInputValue('feedback_type');
const content = submitted.fields.getTextInputValue('feedback_content');
const user = interaction.user;
const guild = interaction.guild;

// Firestoreにフィードバックを保存
await addDoc(collection(interaction.client.db, 'feedbacks'), {
userId: user.id,
userTag: user.tag,
guildId: guild.id,
guildName: guild.name,
userId: interaction.user.id,
userTag: interaction.user.tag,
guildId: interaction.guild.id,
type: type,
content: content,
timestamp: Timestamp.now()
});

const embed = new EmbedBuilder()
.setColor(0x00ff00)
.setTitle('✅ 送信完了')
.setDescription('貴重なご意見ありがとうございます!\nいただいたフィードバックは、今後の開発の参考にさせていただきます。')
.addFields(
{ name: '種類', value: type },
{ name: '内容', value: `\`\`\`${content}\`\`\`` }
);
const embed = createSuccessEmbed(
'送信完了',
'貴重なご意見ありがとうございます!今後の開発の参考にさせていただきます。'
).addFields([
{ name: '種類', value: type, inline: true },
{ name: '内容', value: content }
]);

await submitted.reply({ embeds: [embed], ephemeral: true });
console.log(chalk.green(`[Feedback] Received from ${user.tag} in ${guild.name}`));
console.log(chalk.green(`[Feedback] Received from ${interaction.user.tag}`));

} catch (error) {
// モーダルがタイムアウトした場合など
if (error.code !== 'InteractionCollectorError') {
console.error(chalk.red('❌ Feedback modal error:'), error);
}
if (error.code !== 'InteractionCollectorError') console.error('[ERROR] Feedback error:', error);
}
},
};
};
43 changes: 22 additions & 21 deletions commands/help.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
const { SlashCommandBuilder } = require('discord.js');
const fs = require('node:fs');
const path = require('node:path');
const { createStandardEmbed, COLORS } = require('../src/utils/embedBuilder');

module.exports = {
data: new SlashCommandBuilder()
Expand All @@ -17,11 +18,10 @@ module.exports = {
general: []
};

const commandsPath = path.join(__dirname); // Get current 'commands' directory
const commandsPath = __dirname;
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));

for (const file of commandFiles) {
// help.js自身は除外
if (file === 'help.js') continue;

const filePath = path.join(commandsPath, file);
Expand All @@ -33,7 +33,6 @@ module.exports = {
description: command.data.description,
};

// コマンド名に基づいてカテゴリ分け
if (command.data.name.includes('config') || command.data.name.includes('list') || command.data.name.includes('board') || command.data.name.includes('ticket') || command.data.name.includes('automod')) {
commands.management.push(commandData);
} else if (command.data.name.includes('role')) {
Expand All @@ -45,44 +44,46 @@ module.exports = {
}
}
} catch (error) {
console.error(`コマンドファイル ${file} の読み込みに失敗しました:`, error);
console.error(`[ERROR] コマンドファイル ${file} の読み込みに失敗しました:`, error);
}
}

const helpEmbed = new EmbedBuilder()
.setColor(0x5865F2)
.setTitle('🤖 OrderlyCore コマンドヘルプ')
.setDescription('`/` を入力すると、各コマンドの詳細な説明が表示されます。')
.setThumbnail(interaction.client.user.displayAvatarURL())
.addFields(
const helpEmbed = createStandardEmbed({
title: '[HELP] OrderlyCore コマンド一覧',
description: 'スラッシュコマンドを使用して、ボットの様々な機能を呼び出すことができます。',
color: COLORS.PRIMARY,
thumbnail: interaction.client.user.displayAvatarURL(),
fields: [
{
name: '⚙️ サーバー管理',
name: '[ADMIN] サーバー管理',
value: commands.management.map(cmd => `> </${cmd.name}:${interaction.client.application.id}>: ${cmd.description}`).join('\n') || 'コマンドなし',
inline: false
},
{
name: '🎭 ロール管理',
name: '[ROLE] ロール管理',
value: commands.roles.map(cmd => `> </${cmd.name}:${interaction.client.application.id}>: ${cmd.description}`).join('\n') || 'コマンドなし',
inline: false
},
{
name: '🔊 ボイスチャンネル',
name: '[VOICE] ボイスチャンネル',
value: commands.voice.map(cmd => `> </${cmd.name}:${interaction.client.application.id}>: ${cmd.description}`).join('\n') || 'コマンドなし',
inline: false
},
{
name: '🔧 一般',
// 自分自身(helpコマンド)を手動で追加
name: '[INFO] 一般',
value: [
...commands.general.map(cmd => `> </${cmd.name}:${interaction.client.application.id}>: ${cmd.description}`),
`> </help:${interaction.client.application.id}>: このヘルプメッセージを表示します。`
`> </help:${interaction.client.application.id}>: ヘルプを表示します。`
].join('\n') || 'コマンドなし',
inline: false
}
)
.setFooter({ text: `${interaction.guild.name} | Bot Version: ${require('../package.json').version}`, iconURL: interaction.guild.iconURL() })
.setTimestamp();
],
footer: {
text: `${interaction.guild.name} | System Version: ${require('../package.json').version}`,
iconURL: interaction.guild.iconURL()
}
});

await interaction.editReply({ embeds: [helpEmbed] });
},
};
};
34 changes: 12 additions & 22 deletions commands/list-vc-logs.js
Original file line number Diff line number Diff line change
@@ -1,48 +1,38 @@
const { SlashCommandBuilder, PermissionFlagsBits, EmbedBuilder, MessageFlags } = require('discord.js');
const { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } = require('discord.js');
const { doc, getDoc } = require('firebase/firestore');
const chalk = require('chalk');
const { createStandardEmbed, COLORS } = require('../src/utils/embedBuilder');

module.exports = {
data: new SlashCommandBuilder()
.setName('list-vc-logs')
.setDescription('設定されているボイスチャンネルのログ設定一覧を表示します。')
.setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild),
async execute(interaction) {
// 最初に必ず応答を保留し、タイムアウトを防ぐ
await interaction.deferReply({ flags: [MessageFlags.Ephemeral] });

const guildId = interaction.guild.id;
const db = interaction.client.db;

if (!db) {
return interaction.editReply({ content: '❌ データベースへの接続に失敗しました。' });
}
if (!db) return interaction.editReply({ content: '[ERROR] データベース接続失敗。' });

const settingsRef = doc(db, 'guild_settings', guildId);
const docSnap = await getDoc(settingsRef);

const embed = new EmbedBuilder()
.setTitle('🔊 ボイスチャンネルログ設定一覧')
.setColor(0x5865F2)
.setFooter({ text: interaction.guild.name, iconURL: interaction.guild.iconURL() })
.setTimestamp();
const embed = createStandardEmbed({
title: '[VOICE] ログ設定一覧',
color: COLORS.INFO,
footer: { text: interaction.guild.name, iconURL: interaction.guild.iconURL() }
});

if (docSnap.exists() && docSnap.data().voiceChannelMappings) {
const mappings = docSnap.data().voiceChannelMappings;
const description = Object.entries(mappings)
.map(([vcId, tcId]) => `🎤 <#${vcId}> ➔ ✍️ <#${tcId}>`)
.map(([vcId, tcId]) => `[VC] <#${vcId}> -> [LOG] <#${tcId}>`)
.join('\n');

if (description) {
embed.setDescription(description);
} else {
embed.setDescription('ログ設定はまだありません。\n`/set-vc-log` コマンドで設定してください。');
}
embed.setDescription(description || '設定なし');
} else {
embed.setDescription('ログ設定はまだありません。\n`/set-vc-log` コマンドで設定してください。');
embed.setDescription('設定なし');
}

// 成功した場合の応答
await interaction.editReply({ embeds: [embed] });
},
};
};
45 changes: 18 additions & 27 deletions commands/login.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
const { SlashCommandBuilder, EmbedBuilder, PermissionsBitField } = require('discord.js');
const { SlashCommandBuilder, PermissionsBitField } = require('discord.js');
const { ref, set } = require('firebase/database');
const { v4: uuidv4 } = require('uuid');
const { createStandardEmbed, COLORS } = require('../src/utils/embedBuilder');

module.exports = {
data: new SlashCommandBuilder()
.setName('login')
.setDescription('Webダッシュボードにログインするためのワンタイムトークンを発行します。'),
async execute(interaction) {
// サーバー管理者のみが実行可能
if (!interaction.member.permissions.has(PermissionsBitField.Flags.ManageGuild)) {
return interaction.reply({
content: ' このコマンドを実行するには「サーバーの管理」権限が必要です。',
content: '[ERROR] このコマンドを実行するには「サーバーの管理」権限が必要です。',
ephemeral: true,
});
}
Expand All @@ -25,43 +25,34 @@ module.exports = {
userId: interaction.user.id,
guildId: interaction.guild.id,
createdAt: Date.now(),
expiresAt: Date.now() + 300000, // 5分間有効
expiresAt: Date.now() + 300000,
};

try {
await set(tokenRef, tokenData);

const loginUrl = `${process.env.APP_URL || 'http://localhost:8000'}login`;

const embed = new EmbedBuilder()
.setColor(0x5865F2)
.setTitle('🔑 Webダッシュボード ログイン')
.setDescription('以下のトークンをWebサイトで入力してログインしてください。このトークンは **5分間** のみ有効です。')
.addFields(
const embed = createStandardEmbed({
title: '[AUTH] Web Dashboard Login',
description: '以下のトークンを使用してログインしてください。このトークンは **5分間** 有効です。',
color: COLORS.PRIMARY,
fields: [
{ name: '認証トークン', value: `\`\`\`${token}\`\`\`` },
{ name: 'ログインページ', value: `[こちらをクリック](${loginUrl})` }
)
.setFooter({ text: 'このメッセージはあなたにのみ表示されています。トークンを他人と共有しないでください。' })
.setTimestamp();
{ name: 'ログインURL', value: `[こちらをクリック](${loginUrl})` }
],
footer: { text: '他人と共有しないでください' }
});

await interaction.user.send({ embeds: [embed] }).catch(async () => {
await interaction.editReply({
content: '❌ あなたのDMにメッセージを送信できませんでした。DMの受信設定を確認してください。',
});
// DM送信失敗時はトークンを削除
await remove(tokenRef);
await interaction.editReply({ content: '[ERROR] DMの送信に失敗しました。受信設定を確認してください。' });
return;
});

await interaction.editReply({
content: '✅ あなたのDMにログイン用のトークンを送信しました。確認してください。',
});
await interaction.editReply({ content: '[OK] DMにログイン用トークンを送信しました。' });

} catch (error) {
console.error('Login command error:', error);
await interaction.editReply({
content: '❌ ログインセッションの作成中にエラーが発生しました。',
});
console.error('[ERROR] Login command error:', error);
await interaction.editReply({ content: '[ERROR] エラーが発生しました。' });
}
},
};
};
Loading