diff --git a/src/commands/utility/help.ts b/src/commands/utility/help.ts index c3d7d18..5a145ea 100644 --- a/src/commands/utility/help.ts +++ b/src/commands/utility/help.ts @@ -4,15 +4,19 @@ import { SlashCommand } from '../../utils/types'; const command: SlashCommand = { data: new SlashCommandBuilder().setName('help').setDescription('Zeigt Befehle und Module.'), async execute(interaction: ChatInputCommandInteraction) { + const avatar = interaction.client.user?.displayAvatarURL({ size: 256 }) ?? null; const embed = new EmbedBuilder() - .setTitle('Papo Hilfe') - .setDescription('Multi-Guild ready | Admin, Tickets, Musik, Automod, Dashboard') + .setTitle('✨ Papo Hilfe') + .setColor(0xf97316) + .setThumbnail(avatar) + .setDescription('Dein All-in-One Assistant: Tickets, Automod, Musik, Stats, Dashboard.') .addFields( - { name: 'Admin', value: '/ban /kick /mute /timeout /clear', inline: false }, - { name: 'Tickets', value: '/ticket /ticketpanel /ticketpriority /ticketstatus /transcript', inline: false }, - { name: 'Musik', value: '/play /pause /resume /skip /stop /queue /loop', inline: false }, - { name: 'Utility', value: '/ping /configure /serverinfo /rank', inline: false } - ); + { name: '🛡️ Admin', value: '`/ban` `/kick` `/mute` `/timeout` `/clear`', inline: false }, + { name: '🎫 Tickets', value: '`/ticket` `/ticketpanel` `/ticketpriority` `/ticketstatus` `/transcript`', inline: false }, + { name: '🎵 Musik', value: '`/play` `/pause` `/resume` `/skip` `/stop` `/queue` `/loop`', inline: false }, + { name: '📊 Server-Tools', value: '`/configure` `/serverinfo` `/rank`', inline: false } + ) + .setFooter({ text: 'Tipp: Nutze /configure für Module & Dashboard-Link' }); await interaction.reply({ embeds: [embed], ephemeral: true }); } }; diff --git a/src/services/statsService.ts b/src/services/statsService.ts index 8a57375..9dfa4e2 100644 --- a/src/services/statsService.ts +++ b/src/services/statsService.ts @@ -49,6 +49,7 @@ export class StatsService { private client: Client | null = null; private interval?: NodeJS.Timeout; private lastRun = new Map(); + private syncLocks = new Map>(); public setClient(client: Client) { this.client = client; @@ -72,21 +73,25 @@ export class StatsService { } public async saveConfig(guildId: string, config: Partial) { - const previous = await this.getConfig(guildId); - const normalized = this.normalizeConfig({ ...previous, ...config }); - const synced = await this.syncGuild(guildId, normalized, previous); - await settingsStore.set(guildId, { serverStatsEnabled: synced.enabled, serverStatsConfig: synced } as any); - this.lastRun.set(guildId, Date.now()); - return synced; + return this.withGuildLock(guildId, async () => { + const previous = await this.getConfig(guildId); + const normalized = this.normalizeConfig({ ...previous, ...config }); + const synced = await this.syncGuild(guildId, normalized, previous); + await settingsStore.set(guildId, { serverStatsEnabled: synced.enabled, serverStatsConfig: synced } as any); + this.lastRun.set(guildId, Date.now()); + return synced; + }); } public async refreshGuild(guildId: string) { - const cfg = await this.getConfig(guildId); - if (!cfg.enabled) return cfg; - const synced = await this.syncGuild(guildId, cfg, cfg); - await settingsStore.set(guildId, { serverStatsEnabled: synced.enabled, serverStatsConfig: synced } as any); - this.lastRun.set(guildId, Date.now()); - return synced; + return this.withGuildLock(guildId, async () => { + const cfg = await this.getConfig(guildId); + if (!cfg.enabled) return cfg; + const synced = await this.syncGuild(guildId, cfg, cfg); + await settingsStore.set(guildId, { serverStatsEnabled: synced.enabled, serverStatsConfig: synced } as any); + this.lastRun.set(guildId, Date.now()); + return synced; + }); } public async disableGuild(guildId: string) { @@ -248,4 +253,19 @@ export class StatsService { await this.refreshGuild(guildId).catch(() => undefined); } } + + private async withGuildLock(guildId: string, task: () => Promise): Promise { + const waitFor = this.syncLocks.get(guildId) || Promise.resolve(); + const run = (async () => { + await waitFor.catch(() => undefined); + return task(); + })(); + this.syncLocks.set(guildId, run.then(() => undefined, () => undefined)); + try { + return await run; + } finally { + const current = this.syncLocks.get(guildId); + if (current === run) this.syncLocks.delete(guildId); + } + } }