fix: Enhance temporary voice channel management with improved Discord API error handling for ghost channels and detailed logging.
This commit is contained in:
parent
b93255a2be
commit
cb80d9dffe
|
|
@ -8,24 +8,77 @@ datasource db {
|
|||
}
|
||||
|
||||
model GuildConfig {
|
||||
guildId String @id
|
||||
prefix String @default("!")
|
||||
mimicEnabled Boolean @default(true)
|
||||
locale String? // Server locale override (e.g. 'ko', 'en')
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
guildId String @id
|
||||
prefix String @default("!")
|
||||
mimicEnabled Boolean @default(true)
|
||||
locale String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model InviteRole {
|
||||
id String @id @default(uuid())
|
||||
guildId String
|
||||
id String @id @default(uuid())
|
||||
guildId String
|
||||
inviteCode String
|
||||
roleId String
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
roleId String
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@unique([guildId, inviteCode])
|
||||
}
|
||||
|
||||
model UserSubscription {
|
||||
userId String @id
|
||||
tier SubscriptionTier @default(FREE)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
guilds GuildOwnership[]
|
||||
}
|
||||
|
||||
model GuildOwnership {
|
||||
guildId String @id
|
||||
ownerId String
|
||||
createdAt DateTime @default(now())
|
||||
owner UserSubscription @relation(fields: [ownerId], references: [userId], onDelete: Cascade)
|
||||
|
||||
@@index([ownerId])
|
||||
}
|
||||
|
||||
model VoiceGenerator {
|
||||
channelId String @id
|
||||
guildId String
|
||||
categoryId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([guildId])
|
||||
}
|
||||
|
||||
model TempVoiceChannel {
|
||||
channelId String @id
|
||||
guildId String
|
||||
ownerId String
|
||||
deleteWhen DeleteCondition @default(EMPTY)
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([guildId])
|
||||
@@index([ownerId])
|
||||
}
|
||||
|
||||
model UserVoiceProfile {
|
||||
userId String @id
|
||||
customName String?
|
||||
userLimit Int?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model UserLocale {
|
||||
userId String @id
|
||||
locale String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
enum SubscriptionTier {
|
||||
FREE
|
||||
STANDARD
|
||||
|
|
@ -37,57 +90,3 @@ enum DeleteCondition {
|
|||
OWNER_LEAVE
|
||||
EMPTY
|
||||
}
|
||||
|
||||
model UserSubscription {
|
||||
userId String @id
|
||||
tier SubscriptionTier @default(FREE)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
guilds GuildOwnership[]
|
||||
}
|
||||
|
||||
model GuildOwnership {
|
||||
guildId String @id
|
||||
ownerId String
|
||||
owner UserSubscription @relation(fields: [ownerId], references: [userId], onDelete: Cascade)
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([ownerId])
|
||||
}
|
||||
|
||||
model VoiceGenerator {
|
||||
channelId String @id
|
||||
guildId String
|
||||
categoryId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([guildId])
|
||||
}
|
||||
|
||||
model TempVoiceChannel {
|
||||
channelId String @id
|
||||
guildId String
|
||||
ownerId String
|
||||
deleteWhen DeleteCondition @default(EMPTY)
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([guildId])
|
||||
@@index([ownerId])
|
||||
}
|
||||
|
||||
model UserVoiceProfile {
|
||||
userId String @id
|
||||
customName String?
|
||||
userLimit Int?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model UserLocale {
|
||||
userId String @id
|
||||
locale String // User's personal locale (e.g. 'ko', 'en')
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -63,6 +63,8 @@ export class VoiceService {
|
|||
const member = newState.member;
|
||||
if (!member) return;
|
||||
|
||||
logger.debug(`VoiceService: handleVoiceStateUpdate - old: ${oldState.channelId}, new: ${newState.channelId}`);
|
||||
|
||||
if (!oldState.channelId && newState.channelId) {
|
||||
await this.handleJoin(newState);
|
||||
} else if (oldState.channelId && newState.channelId && oldState.channelId !== newState.channelId) {
|
||||
|
|
@ -82,7 +84,12 @@ export class VoiceService {
|
|||
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;
|
||||
if (!botMember?.permissions.has([
|
||||
|
|
@ -101,10 +108,18 @@ export class VoiceService {
|
|||
if (existingTemp) {
|
||||
try {
|
||||
await member.voice.setChannel(existingTemp.channelId);
|
||||
} catch (e) {
|
||||
logger.error(`${ErrorDefs.DISCORD_API_ERROR.code}: Could not move user to existing voice channel`, e);
|
||||
return; // Success, moved to existing channel
|
||||
} 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
|
||||
|
|
@ -202,9 +217,14 @@ export class VoiceService {
|
|||
await channel.delete();
|
||||
await prisma.tempVoiceChannel.delete({ where: { channelId } });
|
||||
logger.info(`VoiceService: Successfully deleted temp channel ${channel.name}`);
|
||||
} catch (error) {
|
||||
logger.error(`${ErrorDefs.DISCORD_MISSING_PERMISSIONS.code}: Failed to delete channel ${channel.name}`, error);
|
||||
// deliberately NOT deleting the database entry so it remains synchronized with Discord's state.
|
||||
} catch (error: any) {
|
||||
// If already deleted in Discord, just clean up DB
|
||||
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) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue