fix: Enhance temporary voice channel management with improved Discord API error handling for ghost channels and detailed logging.

This commit is contained in:
이정수 2026-03-27 14:39:38 +09:00
parent b93255a2be
commit cb80d9dffe
2 changed files with 91 additions and 72 deletions

View File

@ -11,7 +11,7 @@ model GuildConfig {
guildId String @id guildId String @id
prefix String @default("!") prefix String @default("!")
mimicEnabled Boolean @default(true) mimicEnabled Boolean @default(true)
locale String? // Server locale override (e.g. 'ko', 'en') locale String?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
} }
@ -26,18 +26,6 @@ model InviteRole {
@@unique([guildId, inviteCode]) @@unique([guildId, inviteCode])
} }
enum SubscriptionTier {
FREE
STANDARD
PRO
PREMIUM
}
enum DeleteCondition {
OWNER_LEAVE
EMPTY
}
model UserSubscription { model UserSubscription {
userId String @id userId String @id
tier SubscriptionTier @default(FREE) tier SubscriptionTier @default(FREE)
@ -49,8 +37,8 @@ model UserSubscription {
model GuildOwnership { model GuildOwnership {
guildId String @id guildId String @id
ownerId String ownerId String
owner UserSubscription @relation(fields: [ownerId], references: [userId], onDelete: Cascade)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
owner UserSubscription @relation(fields: [ownerId], references: [userId], onDelete: Cascade)
@@index([ownerId]) @@index([ownerId])
} }
@ -86,8 +74,19 @@ model UserVoiceProfile {
model UserLocale { model UserLocale {
userId String @id userId String @id
locale String // User's personal locale (e.g. 'ko', 'en') locale String
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
} }
enum SubscriptionTier {
FREE
STANDARD
PRO
PREMIUM
}
enum DeleteCondition {
OWNER_LEAVE
EMPTY
}

View File

@ -63,6 +63,8 @@ export class VoiceService {
const member = newState.member; const member = newState.member;
if (!member) return; if (!member) return;
logger.debug(`VoiceService: handleVoiceStateUpdate - old: ${oldState.channelId}, new: ${newState.channelId}`);
if (!oldState.channelId && newState.channelId) { if (!oldState.channelId && newState.channelId) {
await this.handleJoin(newState); await this.handleJoin(newState);
} else if (oldState.channelId && newState.channelId && oldState.channelId !== newState.channelId) { } else if (oldState.channelId && newState.channelId && oldState.channelId !== newState.channelId) {
@ -82,7 +84,12 @@ export class VoiceService {
where: { channelId } where: { channelId }
}); });
if (!generator) return; if (!generator) {
logger.debug(`VoiceService: handleJoin - channel ${channelId} is NOT a generator`);
return;
}
logger.info(`VoiceService: handleJoin - detected generator join for ${member.user.tag} in ${channelId}`);
const botMember = guild.members.me; const botMember = guild.members.me;
if (!botMember?.permissions.has([ if (!botMember?.permissions.has([
@ -101,10 +108,18 @@ export class VoiceService {
if (existingTemp) { if (existingTemp) {
try { try {
await member.voice.setChannel(existingTemp.channelId); await member.voice.setChannel(existingTemp.channelId);
} catch (e) { return; // Success, moved to existing channel
logger.error(`${ErrorDefs.DISCORD_API_ERROR.code}: Could not move user to existing voice channel`, e); } catch (e: any) {
// If the channel no longer exists in Discord, clean up DB and proceed to create new one
if (e.code === 10003 || e.status === 404) {
logger.warn(`VoiceService: Found ghost channel ${existingTemp.channelId} in DB. Cleaning up and creating fresh one.`);
await prisma.tempVoiceChannel.delete({ where: { channelId: existingTemp.channelId } }).catch(() => {});
// FALL THROUGH to channel creation logic below
} else {
logger.error(`${ErrorDefs.DISCORD_API_ERROR.code}: Unexpected error moving user to existing voice channel`, e);
return; // Stop for other errors to prevent spamming creations
}
} }
return;
} }
// Resolve locale for this context // Resolve locale for this context
@ -202,9 +217,14 @@ export class VoiceService {
await channel.delete(); await channel.delete();
await prisma.tempVoiceChannel.delete({ where: { channelId } }); await prisma.tempVoiceChannel.delete({ where: { channelId } });
logger.info(`VoiceService: Successfully deleted temp channel ${channel.name}`); logger.info(`VoiceService: Successfully deleted temp channel ${channel.name}`);
} catch (error) { } catch (error: any) {
logger.error(`${ErrorDefs.DISCORD_MISSING_PERMISSIONS.code}: Failed to delete channel ${channel.name}`, error); // If already deleted in Discord, just clean up DB
// deliberately NOT deleting the database entry so it remains synchronized with Discord's state. if (error.code === 10003 || error.status === 404) {
await prisma.tempVoiceChannel.delete({ where: { channelId } }).catch(() => {});
logger.info(`VoiceService: Purged ghost channel ${channelId} from DB`);
} else {
logger.error(`${ErrorDefs.DISCORD_MISSING_PERMISSIONS.code}: Failed to delete channel ${channelId}`, error);
}
} }
} }
else if (tempChannel.deleteWhen === 'EMPTY' && tempChannel.ownerId === member.id && channel.members.filter(m => !m.user.bot).size > 0) { else if (tempChannel.deleteWhen === 'EMPTY' && tempChannel.ownerId === member.id && channel.members.filter(m => !m.user.bot).size > 0) {