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
123 changes: 20 additions & 103 deletions commands/ping.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const { SlashCommandBuilder, EmbedBuilder, Colors } = require('discord.js');
const { SlashCommandBuilder, Colors } = require('discord.js');
const os = require('os');
const { createStandardEmbed } = require('../src/utils/embedBuilder');

module.exports = {
data: new SlashCommandBuilder()
Expand All @@ -8,71 +9,52 @@ module.exports = {

async execute(interaction) {
try {
// 初回応答時刻を記録
const startTime = Date.now();

const sent = await interaction.reply({
content: '🏓 Pong! 詳細測定中...',
fetchReply: true
});

// 各種遅延の計算
const endTime = Date.now();
const roundtripLatency = sent.createdTimestamp - interaction.createdTimestamp;
const editLatency = endTime - startTime;
const websocketLatency = Math.round(interaction.client.ws.ping);
const apiLatency = Math.max(0, roundtripLatency - websocketLatency);

// システム情報の取得
const uptime = process.uptime();
const memUsage = process.memoryUsage();
const totalMem = os.totalmem();
const freeMem = os.freemem();
const usedMem = totalMem - freeMem;

// 遅延レベルの判定
function getLatencyLevel(ms) {
if (ms < 100) return { level: 'excellent', emoji: '🟢', color: Colors.Green, status: '優秀' };
if (ms < 200) return { level: 'good', emoji: '🟡', color: Colors.Yellow, status: '良好' };
if (ms < 500) return { level: 'fair', emoji: '🟠', color: Colors.Orange, status: '普通' };
return { level: 'poor', emoji: '🔴', color: Colors.Red, status: '遅延' };
if (ms < 100) return { emoji: '🟢', color: Colors.Green, status: '優秀' };
if (ms < 200) return { emoji: '🟡', color: Colors.Yellow, status: '良好' };
if (ms < 500) return { emoji: '🟠', color: Colors.Orange, status: '普通' };
return { emoji: '🔴', color: Colors.Red, status: '遅延' };
}

const wsLatencyInfo = getLatencyLevel(websocketLatency);
const rtLatencyInfo = getLatencyLevel(roundtripLatency);
const overallInfo = getLatencyLevel(Math.max(websocketLatency, roundtripLatency));

// 全体的な接続状態の判定
const overallLatency = Math.max(websocketLatency, roundtripLatency);
const overallInfo = getLatencyLevel(overallLatency);

// 時間フォーマット関数
function formatUptime(seconds) {
const days = Math.floor(seconds / 86400);
const hours = Math.floor((seconds % 86400) / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);

let result = '';
if (days > 0) result += `${days}日 `;
if (hours > 0) result += `${hours}時間 `;
if (minutes > 0) result += `${minutes}分 `;
result += `${secs}秒`;

return result;
return `${days > 0 ? days + '日 ' : ''}${hours > 0 ? hours + '時間 ' : ''}${minutes > 0 ? minutes + '分 ' : ''}${secs}秒`;
}

// メモリ使用量フォーマット
function formatBytes(bytes) {
const mb = bytes / 1024 / 1024;
return `${mb.toFixed(1)}MB`;
return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
}

// メイン埋め込み
const embed = new EmbedBuilder()
.setColor(overallInfo.color)
.setTitle(`${overallInfo.emoji} Pong! 接続状態: ${overallInfo.status}`)
.setDescription('🚀 **最新技術搭載Discord Bot** の詳細ステータス')
.addFields([
const embed = createStandardEmbed({
title: `${overallInfo.emoji} Pong! 接続状態: ${overallInfo.status}`,
description: '🚀 **最新技術搭載Discord Bot** の詳細ステータス',
color: overallInfo.color,
fields: [
{
name: '📡 接続遅延情報',
value: [
Expand Down Expand Up @@ -105,82 +87,17 @@ module.exports = {
].join('\n'),
inline: false
}
])
.setFooter({
text: `実行者: ${interaction.user.tag} | 測定完了時刻`,
],
footer: {
text: `実行者: ${interaction.user.tag}`,
iconURL: interaction.user.displayAvatarURL()
})
.setTimestamp();

// パフォーマンス評価
let performanceNote = '';
if (overallLatency < 100) {
performanceNote = '🚀 **素晴らしい接続状態です!** 全ての機能が高速で動作します。';
} else if (overallLatency < 200) {
performanceNote = '✅ **良好な接続状態です。** 快適にご利用いただけます。';
} else if (overallLatency < 500) {
performanceNote = '⚠️ **接続にやや遅延があります。** 機能は正常に動作します。';
} else {
performanceNote = '🔴 **接続遅延が発生しています。** Discord側の問題の可能性があります。';
}

embed.addFields([
{
name: '📊 パフォーマンス評価',
value: performanceNote,
inline: false
}
]);

// リアルタイムステータス
const connectionStatus = interaction.client.ws.status;
const statusMap = {
0: '🟢 Ready (準備完了)',
1: '🟡 Connecting (接続中)',
2: '🟠 Reconnecting (再接続中)',
3: '🔴 Idle (待機中)',
4: '⚫ Nearly (ほぼ切断)',
5: '❌ Disconnected (切断済み)',
6: '🔄 Waiting for Guilds (ギルド待機)',
7: '🔄 Identifying (認証中)',
8: '🔄 Resuming (再開中)'
};

embed.addFields([
{
name: '🔌 接続ステータス',
value: `${statusMap[connectionStatus] || '❓ 不明'} (コード: ${connectionStatus})`,
inline: true
},
{
name: '🕐 測定日時',
value: `<t:${Math.floor(Date.now()/1000)}:F>`,
inline: true
}
]);

await interaction.editReply({
content: '',
embeds: [embed]
});

// コンソールログ
console.log(`🏓 Ping測定完了: WS=${websocketLatency}ms, RT=${roundtripLatency}ms | ユーザー: ${interaction.user.tag}`);

await interaction.editReply({ content: '', embeds: [embed] });
} catch (error) {
console.error('Ping コマンドエラー:', error);

const errorEmbed = new EmbedBuilder()
.setColor(Colors.Red)
.setTitle('❌ エラーが発生しました')
.setDescription('Ping測定中にエラーが発生しました。しばらくしてから再度お試しください。')
.setTimestamp();

if (interaction.deferred || interaction.replied) {
await interaction.editReply({ embeds: [errorEmbed] }).catch(() => {});
} else {
await interaction.reply({ embeds: [errorEmbed], ephemeral: true }).catch(() => {});
}
await interaction.editReply({ content: '❌ エラーが発生しました。' }).catch(() => {});
}
},
};
};
2 changes: 1 addition & 1 deletion commands/profile-card.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ try {

// --- ヘルパー関数 ---

const calculateRequiredXp = (level) => 5 * (level ** 2) + 50 * level + 100;
const { calculateRequiredXp } = require('../src/services/levelingService');

function formatDuration(milliseconds) {
if (milliseconds < 60000) {
Expand Down
2 changes: 1 addition & 1 deletion commands/rank-card.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ try {
console.error("フォントの読み込みに失敗しました。`fonts`ディレクトリに指定のフォントファイルがあるか確認してください。");
}

const calculateRequiredXp = (level) => 5 * (level ** 2) + 50 * level + 100;
const { calculateRequiredXp } = require('../src/services/levelingService');

// 角丸の四角形を描画するヘルパー関数
function roundRect(ctx, x, y, width, height, radius) {
Expand Down
145 changes: 20 additions & 125 deletions commands/rank.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
const { getFirestore, collection, query, where, orderBy, limit, getDocs, doc, getDoc } = require('firebase/firestore');

// レベルアップに必要なXPを計算する関数
const calculateRequiredXp = (level) => 5 * (level ** 2) + 50 * level + 100;
const { SlashCommandBuilder } = require('discord.js');
const { getLevelData, getRank, calculateRequiredXp } = require('../src/services/levelingService');
const { createStandardEmbed } = require('../src/utils/embedBuilder');

module.exports = {
data: new SlashCommandBuilder()
Expand All @@ -20,143 +18,40 @@ module.exports = {
const guildId = interaction.guild.id;

try {
// ユーザーデータの取得
const userRef = doc(db, 'levels', `${guildId}_${targetUser.id}`);
const userSnap = await getDoc(userRef);

if (!userSnap.exists()) {
return interaction.editReply({
content: `📊 ${targetUser.username} さんにはまだランクデータがありません。\nメッセージを送信するとランクが記録されます。`
});
}

const rawData = userSnap.data();
const userData = {
level: rawData.level || 0,
xp: rawData.xp || 0,
messageCount: rawData.messageCount || 0,
userId: rawData.userId,
guildId: rawData.guildId
};

const userData = await getLevelData(db, guildId, targetUser.id);
const rank = await getRank(db, guildId, targetUser.id);
const requiredXp = calculateRequiredXp(userData.level);

// ランキングの取得
const usersRef = collection(db, 'levels');
const q = query(
usersRef,
where('guildId', '==', guildId),
orderBy('level', 'desc'),
orderBy('xp', 'desc')
);
const snapshot = await getDocs(q);

let rank = -1;
snapshot.docs.forEach((doc, index) => {
if (doc.data().userId === targetUser.id) {
rank = index + 1;
}
});

// 進捗バーの計算(修正版)
let progress = 0;
let progressPercentage = 0;

if (requiredXp > 0) {
// XPが必要値を超える場合も100%として扱う
progressPercentage = Math.min((userData.xp / requiredXp) * 100, 100);
// 進捗バーは0〜10の範囲で表示
progress = Math.min(Math.floor((userData.xp / requiredXp) * 10), 10);
}

const progressBar = '🟩'.repeat(progress) + '⬛'.repeat(10 - progress);

// メンバー情報の取得
const member = await interaction.guild.members.fetch(targetUser.id).catch(() => null);
const displayName = member ? member.displayName : targetUser.username;
const avatarColor = member ? member.displayHexColor : '#5865F2';

// Embedの作成
const embed = new EmbedBuilder()
.setColor(avatarColor)
.setTitle(`🏆 ${displayName} のランク`)
.setThumbnail(targetUser.displayAvatarURL({ dynamic: true, size: 256 }))
.addFields(
{
name: '📊 レベル',
value: `**Lv.${userData.level}**`,
inline: true
},
{
name: '🎖️ 順位',
value: rank !== -1 ? `**#${rank}**` : '計測中...',
inline: true
},
{
name: '💬 総メッセージ数',
value: `**${userData.messageCount.toLocaleString()}** 回`,
inline: true
},
{
name: '✨ 経験値 (XP)',
value: `**${userData.xp.toLocaleString()}** / ${requiredXp.toLocaleString()} XP`,
inline: false
},
{
name: '📈 次のレベルへの進捗',
value: `${progressBar} **${progressPercentage.toFixed(1)}%**`,
inline: false
}
)
.setFooter({
text: `${interaction.guild.name} のランキング`,
iconURL: interaction.guild.iconURL()
})
.setTimestamp();
const embed = createStandardEmbed({
title: `🏆 ${displayName} のランク`,
color: avatarColor,
thumbnail: targetUser.displayAvatarURL({ dynamic: true, size: 256 }),
fields: [
{ name: '📊 レベル', value: `**Lv.${userData.level}**`, inline: true },
{ name: '🎖️ 順位', value: rank !== -1 ? `**#${rank}**` : '計測中...', inline: true },
{ name: '💬 総メッセージ数', value: `**${(userData.messageCount || 0).toLocaleString()}** 回`, inline: true },
{ name: '✨ 経験値 (XP)', value: `**${Math.floor(userData.xp).toLocaleString()}** / ${requiredXp.toLocaleString()} XP`, inline: false },
{ name: '📈 次のレベルへの進捗', value: `${progressBar} **${progressPercentage.toFixed(1)}%**`, inline: false }
],
footer: { text: interaction.guild.name, iconURL: interaction.guild.iconURL() }
});

await interaction.editReply({ embeds: [embed] });

} catch (error) {
console.error('❌ ランクコマンドの実行エラー:', error);
console.error('エラー詳細:', error.message);
console.error('エラーコード:', error.code);

// エラーコード別の詳細な対応
if (error.code === 'failed-precondition') {
await interaction.editReply({
content: '❌ **データベースインデックスエラー**\n\n' +
'⚠️ ランキング機能に必要なFirestoreインデックスが作成されていません。\n\n' +
'**管理者向け手順:**\n' +
'1. コンソールログに表示されているURLにアクセス\n' +
'2. Firebaseコンソールでインデックスを作成\n' +
'3. インデックス作成完了まで数分お待ちください\n\n' +
'詳細: https://firebase.google.com/docs/firestore/query-data/indexing'
});
} else if (error.code === 'permission-denied') {
await interaction.editReply({
content: '❌ **権限エラー**\n\n' +
'データベースへのアクセス権限がありません。\n' +
'Firestoreのセキュリティルールを確認してください。'
});
} else if (error.code === 'unavailable') {
await interaction.editReply({
content: '❌ **接続エラー**\n\n' +
'データベースに接続できませんでした。\n' +
'しばらく時間をおいてから再度お試しください。'
});
} else if (error.code === 'not-found') {
await interaction.editReply({
content: '❌ **データ取得エラー**\n\n' +
'指定されたデータが見つかりませんでした。'
});
} else {
await interaction.editReply({
content: '❌ **予期しないエラーが発生しました**\n\n' +
'ランク情報の取得中に問題が発生しました。\n' +
'Bot管理者に連絡してください。\n\n' +
`エラーコード: \`${error.code || 'UNKNOWN'}\``
});
}
await interaction.editReply({ content: '❌ ランク情報の取得中にエラーが発生しました。' });
}
}
};
};
Loading