fix(api): correct guild permission and mfa checks (#2499)

This commit is contained in:
Hampus authored and GitHub committed 2026-09-06 15:28:01 +02:00
1 parent f392636857
commit 908e1b8bd4
12 files changed
+303 -86

No files matched your search

@@ -246,6 +246,7 @@ export function useVoiceParticipantMenuData(options: VoiceParticipantMenuDataOpt
const isBlocked = relationshipType === RelationshipTypes.BLOCKED;
const hasActiveDirectCall = hasActiveDirectCallWithUser(user.id);
const canMuteMembers = guildId ? Permission.can(Permissions.MUTE_MEMBERS, {guildId}) : false;
const canDeafenMembers = guildId ? Permission.can(Permissions.DEAFEN_MEMBERS, {guildId}) : false;
const canMoveMembers = guildId ? Permission.can(Permissions.MOVE_MEMBERS, {guildId}) : false;
const canKickMembers = guildId ? Permission.can(Permissions.KICK_MEMBERS, {guildId}) : false;
const canBanMembers = guildId ? Permission.can(Permissions.BAN_MEMBERS, {guildId}) : false;
@@ -1181,49 +1182,53 @@ export function useVoiceParticipantMenuData(options: VoiceParticipantMenuDataOpt
}
if (guildId && member) {
const nextModerationActions: Array<MenuItemType | MenuCheckboxType> = [];
if (canMuteMembers && (isCurrentUser || canManageTarget(user.id))) {
if ((canMuteMembers || canDeafenMembers) && (isCurrentUser || canManageTarget(user.id))) {
const isGuildMuted = memberMute;
const isGuildDeafened = memberDeaf;
nextModerationActions.push({
icon: (
<GuildMuteIcon
size={16}
data-flx="ui.action-menu.items.voice-participant-menu-data.groups.guild-mute-icon"
/>
),
label: i18n._(VOICE_COMMUNITY_MUTE_DESCRIPTOR),
checked: isGuildMuted,
onChange: async (checked: boolean) => {
try {
await GuildMemberCommands.update(guildId, user.id, {mute: checked});
if (checked) SoundCommands.playSound(SoundType.Mute);
else SoundCommands.playSound(SoundType.Unmute);
} catch (error) {
logger.error('Failed to update community mute:', error);
showVoiceMemberModerationFailedModal(error, VOICE_COMMUNITY_MUTE_DESCRIPTOR);
}
},
});
nextModerationActions.push({
icon: (
<GuildDeafenIcon
size={16}
data-flx="ui.action-menu.items.voice-participant-menu-data.groups.guild-deafen-icon"
/>
),
label: i18n._(VOICE_COMMUNITY_DEAFEN_DESCRIPTOR),
checked: isGuildDeafened,
onChange: async (checked: boolean) => {
try {
await GuildMemberCommands.update(guildId, user.id, {deaf: checked});
if (checked) SoundCommands.playSound(SoundType.Deaf);
else SoundCommands.playSound(SoundType.Undeaf);
} catch (error) {
logger.error('Failed to update community deafen:', error);
showVoiceMemberModerationFailedModal(error, VOICE_COMMUNITY_DEAFEN_DESCRIPTOR);
}
},
});
if (canMuteMembers) {
nextModerationActions.push({
icon: (
<GuildMuteIcon
size={16}
data-flx="ui.action-menu.items.voice-participant-menu-data.groups.guild-mute-icon"
/>
),
label: i18n._(VOICE_COMMUNITY_MUTE_DESCRIPTOR),
checked: isGuildMuted,
onChange: async (checked: boolean) => {
try {
await GuildMemberCommands.update(guildId, user.id, {mute: checked});
if (checked) SoundCommands.playSound(SoundType.Mute);
else SoundCommands.playSound(SoundType.Unmute);
} catch (error) {
logger.error('Failed to update community mute:', error);
showVoiceMemberModerationFailedModal(error, VOICE_COMMUNITY_MUTE_DESCRIPTOR);
}
},
});
}
if (canDeafenMembers) {
nextModerationActions.push({
icon: (
<GuildDeafenIcon
size={16}
data-flx="ui.action-menu.items.voice-participant-menu-data.groups.guild-deafen-icon"
/>
),
label: i18n._(VOICE_COMMUNITY_DEAFEN_DESCRIPTOR),
checked: isGuildDeafened,
onChange: async (checked: boolean) => {
try {
await GuildMemberCommands.update(guildId, user.id, {deaf: checked});
if (checked) SoundCommands.playSound(SoundType.Deaf);
else SoundCommands.playSound(SoundType.Undeaf);
} catch (error) {
logger.error('Failed to update community deafen:', error);
showVoiceMemberModerationFailedModal(error, VOICE_COMMUNITY_DEAFEN_DESCRIPTOR);
}
},
});
}
}
if (!isCurrentUser && canMoveMembers && canManageTarget(user.id) && !isParentGroupedItem) {
nextModerationActions.push({
@@ -1396,6 +1401,7 @@ export function useVoiceParticipantMenuData(options: VoiceParticipantMenuDataOpt
canMention,
focusedChannelId,
canMuteMembers,
canDeafenMembers,
canMoveMembers,
canManageTarget,
canKickTarget,
@@ -17,6 +17,7 @@ const DEFAULT_PERMISSIONS: VoiceChannelPermissions = {
canUseVideo: true,
canConnect: true,
canPrioritySpeaker: false,
canUseVad: true,
};
function permissions(overrides: Partial<VoiceChannelPermissions> = {}): VoiceChannelPermissions {
@@ -62,6 +62,7 @@ export const DEFAULT_VOICE_PERMISSION_STATE: VoiceChannelPermissions = {
canUseVideo: true,
canConnect: true,
canPrioritySpeaker: false,
canUseVad: true,
};
const EMPTY_COMMANDS: ReadonlyArray<VoicePermissionCommand> = [];
@@ -86,7 +87,8 @@ function permissionsEqual(left: VoiceChannelPermissions, right: VoiceChannelPerm
left.canStream === right.canStream &&
left.canUseVideo === right.canUseVideo &&
left.canConnect === right.canConnect &&
left.canPrioritySpeaker === right.canPrioritySpeaker
left.canPrioritySpeaker === right.canPrioritySpeaker &&
left.canUseVad === right.canUseVad
);
}
@@ -11,6 +11,7 @@ export interface VoiceChannelPermissions {
canUseVideo: boolean;
canConnect: boolean;
canPrioritySpeaker: boolean;
canUseVad: boolean;
}
export const DEFAULT_VOICE_CHANNEL_PERMISSIONS: VoiceChannelPermissions = {
@@ -19,6 +20,7 @@ export const DEFAULT_VOICE_CHANNEL_PERMISSIONS: VoiceChannelPermissions = {
canUseVideo: true,
canConnect: true,
canPrioritySpeaker: false,
canUseVad: true,
};
export type VoicePermissionMuteReason = 'server_suppress' | 'missing_speak_permission' | null;
@@ -38,6 +40,7 @@ export function getVoiceChannelPermissions(channelId: string | null | undefined)
canUseVideo: (permissions & Permissions.STREAM) === Permissions.STREAM,
canConnect: (permissions & Permissions.CONNECT) === Permissions.CONNECT,
canPrioritySpeaker: (permissions & Permissions.PRIORITY_SPEAKER) === Permissions.PRIORITY_SPEAKER,
canUseVad: (permissions & Permissions.USE_VAD) === Permissions.USE_VAD,
};
}