mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-06 03:08:55 -05:00
[Mod] Context-based voice checks (#2351)
- Removed `redbot.cogs.mod.checks` module - Moved logic for formatting a user-friendly list of permissions to `redbot.core.utils.chat_formatting` - `[p]voice(un)ban` and `[p](un)mute voice` now check permissions in the user's voice channel Resolves #2296. Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
This commit is contained in:
parent
aac1460240
commit
8eb8848898
@ -1,66 +0,0 @@
|
||||
from redbot.core import commands
|
||||
|
||||
|
||||
def mod_or_voice_permissions(**perms):
|
||||
async def pred(ctx: commands.Context):
|
||||
author = ctx.author
|
||||
guild = ctx.guild
|
||||
if await ctx.bot.is_owner(author) or guild.owner == author:
|
||||
# Author is bot owner or guild owner
|
||||
return True
|
||||
|
||||
admin_role = guild.get_role(await ctx.bot.db.guild(guild).admin_role())
|
||||
mod_role = guild.get_role(await ctx.bot.db.guild(guild).mod_role())
|
||||
|
||||
if admin_role in author.roles or mod_role in author.roles:
|
||||
return True
|
||||
|
||||
for vc in guild.voice_channels:
|
||||
resolved = vc.permissions_for(author)
|
||||
good = resolved.administrator or all(
|
||||
getattr(resolved, name, None) == value for name, value in perms.items()
|
||||
)
|
||||
if not good:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
return commands.permissions_check(pred)
|
||||
|
||||
|
||||
def admin_or_voice_permissions(**perms):
|
||||
async def pred(ctx: commands.Context):
|
||||
author = ctx.author
|
||||
guild = ctx.guild
|
||||
if await ctx.bot.is_owner(author) or guild.owner == author:
|
||||
return True
|
||||
admin_role = guild.get_role(await ctx.bot.db.guild(guild).admin_role())
|
||||
if admin_role in author.roles:
|
||||
return True
|
||||
for vc in guild.voice_channels:
|
||||
resolved = vc.permissions_for(author)
|
||||
good = resolved.administrator or all(
|
||||
getattr(resolved, name, None) == value for name, value in perms.items()
|
||||
)
|
||||
if not good:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
return commands.permissions_check(pred)
|
||||
|
||||
|
||||
def bot_has_voice_permissions(**perms):
|
||||
async def pred(ctx: commands.Context):
|
||||
guild = ctx.guild
|
||||
for vc in guild.voice_channels:
|
||||
resolved = vc.permissions_for(guild.me)
|
||||
good = resolved.administrator or all(
|
||||
getattr(resolved, name, None) == value for name, value in perms.items()
|
||||
)
|
||||
if not good:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
return commands.check(pred)
|
||||
@ -2,19 +2,18 @@ import asyncio
|
||||
import contextlib
|
||||
from datetime import datetime, timedelta
|
||||
from collections import deque, defaultdict, namedtuple
|
||||
from typing import cast
|
||||
from typing import cast, Optional
|
||||
|
||||
import discord
|
||||
|
||||
from redbot.core import checks, Config, modlog, commands
|
||||
from redbot.core.bot import Red
|
||||
from redbot.core.i18n import Translator, cog_i18n
|
||||
from redbot.core.utils.chat_formatting import box, escape
|
||||
from .checks import mod_or_voice_permissions, admin_or_voice_permissions, bot_has_voice_permissions
|
||||
from redbot.core.utils.chat_formatting import box, escape, format_perms_list
|
||||
from redbot.core.utils.common_filters import filter_invites, filter_various_mentions
|
||||
from redbot.core.utils.mod import is_mod_or_superior, is_allowed_by_hierarchy, get_audit_reason
|
||||
from .log import log
|
||||
|
||||
from redbot.core.utils.common_filters import filter_invites, filter_various_mentions
|
||||
|
||||
_ = T_ = Translator("Mod", __file__)
|
||||
|
||||
@ -781,15 +780,60 @@ class Mod(commands.Cog):
|
||||
except discord.HTTPException:
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
async def _voice_perm_check(
|
||||
ctx: commands.Context, user_voice_state: Optional[discord.VoiceState], **perms: bool
|
||||
) -> bool:
|
||||
"""Check if the bot and user have sufficient permissions for voicebans.
|
||||
|
||||
This also verifies that the user's voice state and connected
|
||||
channel are not ``None``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
``True`` if the permissions are sufficient and the user has
|
||||
a valid voice state.
|
||||
|
||||
"""
|
||||
if user_voice_state is None or user_voice_state.channel is None:
|
||||
await ctx.send(_("That user is not in a voice channel."))
|
||||
return False
|
||||
voice_channel: discord.VoiceChannel = user_voice_state.channel
|
||||
required_perms = discord.Permissions()
|
||||
required_perms.update(**perms)
|
||||
if not voice_channel.permissions_for(ctx.me) >= required_perms:
|
||||
await ctx.send(
|
||||
_("I require the {perms} permission(s) in that user's channel to do that.").format(
|
||||
perms=format_perms_list(required_perms)
|
||||
)
|
||||
)
|
||||
return False
|
||||
if (
|
||||
ctx.permission_state is commands.PermState.NORMAL
|
||||
and not voice_channel.permissions_for(ctx.author) >= required_perms
|
||||
):
|
||||
await ctx.send(
|
||||
_(
|
||||
"You must have the {perms} permission(s) in that user's channel to use this "
|
||||
"command."
|
||||
).format(perms=format_perms_list(required_perms))
|
||||
)
|
||||
return False
|
||||
return True
|
||||
|
||||
@commands.command()
|
||||
@commands.guild_only()
|
||||
@admin_or_voice_permissions(mute_members=True, deafen_members=True)
|
||||
@bot_has_voice_permissions(mute_members=True, deafen_members=True)
|
||||
@checks.admin_or_permissions(mute_members=True, deafen_members=True)
|
||||
async def voiceban(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
|
||||
"""Ban a user from speaking and listening in the server's voice channels."""
|
||||
user_voice_state = user.voice
|
||||
if user_voice_state is None:
|
||||
await ctx.send(_("No voice state for that user!"))
|
||||
user_voice_state: discord.VoiceState = user.voice
|
||||
if (
|
||||
await self._voice_perm_check(
|
||||
ctx, user_voice_state, deafen_members=True, mute_members=True
|
||||
)
|
||||
is False
|
||||
):
|
||||
return
|
||||
needs_mute = True if user_voice_state.mute is False else False
|
||||
needs_deafen = True if user_voice_state.deaf is False else False
|
||||
@ -824,13 +868,15 @@ class Mod(commands.Cog):
|
||||
|
||||
@commands.command()
|
||||
@commands.guild_only()
|
||||
@admin_or_voice_permissions(mute_members=True, deafen_members=True)
|
||||
@bot_has_voice_permissions(mute_members=True, deafen_members=True)
|
||||
async def voiceunban(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
|
||||
"""Unban a user from speaking and listening in the server's voice channels."""
|
||||
user_voice_state = user.voice
|
||||
if user_voice_state is None:
|
||||
await ctx.send(_("No voice state for that user!"))
|
||||
if (
|
||||
await self._voice_perm_check(
|
||||
ctx, user_voice_state, deafen_members=True, mute_members=True
|
||||
)
|
||||
is False
|
||||
):
|
||||
return
|
||||
needs_unmute = True if user_voice_state.mute else False
|
||||
needs_undeafen = True if user_voice_state.deaf else False
|
||||
@ -912,47 +958,43 @@ class Mod(commands.Cog):
|
||||
|
||||
@mute.command(name="voice")
|
||||
@commands.guild_only()
|
||||
@mod_or_voice_permissions(mute_members=True)
|
||||
@bot_has_voice_permissions(mute_members=True)
|
||||
async def voice_mute(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
|
||||
"""Mute a user in their current voice channel."""
|
||||
user_voice_state = user.voice
|
||||
if (
|
||||
await self._voice_perm_check(
|
||||
ctx, user_voice_state, mute_members=True, manage_channels=True
|
||||
)
|
||||
is False
|
||||
):
|
||||
return
|
||||
guild = ctx.guild
|
||||
author = ctx.author
|
||||
if user_voice_state:
|
||||
channel = user_voice_state.channel
|
||||
if channel:
|
||||
audit_reason = get_audit_reason(author, reason)
|
||||
channel = user_voice_state.channel
|
||||
audit_reason = get_audit_reason(author, reason)
|
||||
|
||||
success, issue = await self.mute_user(guild, channel, author, user, audit_reason)
|
||||
success, issue = await self.mute_user(guild, channel, author, user, audit_reason)
|
||||
|
||||
if success:
|
||||
await ctx.send(
|
||||
_("Muted {user} in channel {channel.name}").format(
|
||||
user=user, channel=channel
|
||||
)
|
||||
)
|
||||
try:
|
||||
await modlog.create_case(
|
||||
self.bot,
|
||||
guild,
|
||||
ctx.message.created_at,
|
||||
"vmute",
|
||||
user,
|
||||
author,
|
||||
reason,
|
||||
until=None,
|
||||
channel=channel,
|
||||
)
|
||||
except RuntimeError as e:
|
||||
await ctx.send(e)
|
||||
else:
|
||||
await channel.send(issue)
|
||||
else:
|
||||
await ctx.send(_("That user is not in a voice channel right now!"))
|
||||
if success:
|
||||
await ctx.send(
|
||||
_("Muted {user} in channel {channel.name}").format(user=user, channel=channel)
|
||||
)
|
||||
try:
|
||||
await modlog.create_case(
|
||||
self.bot,
|
||||
guild,
|
||||
ctx.message.created_at,
|
||||
"vmute",
|
||||
user,
|
||||
author,
|
||||
reason,
|
||||
until=None,
|
||||
channel=channel,
|
||||
)
|
||||
except RuntimeError as e:
|
||||
await ctx.send(e)
|
||||
else:
|
||||
await ctx.send(_("No voice state for the target!"))
|
||||
return
|
||||
await ctx.send(issue)
|
||||
|
||||
@mute.command(name="channel")
|
||||
@commands.guild_only()
|
||||
@ -1068,51 +1110,45 @@ class Mod(commands.Cog):
|
||||
|
||||
@unmute.command(name="voice")
|
||||
@commands.guild_only()
|
||||
@mod_or_voice_permissions(mute_members=True)
|
||||
@bot_has_voice_permissions(mute_members=True)
|
||||
async def unmute_voice(
|
||||
self, ctx: commands.Context, user: discord.Member, *, reason: str = None
|
||||
):
|
||||
"""Unmute a user in their current voice channel."""
|
||||
user_voice_state = user.voice
|
||||
if (
|
||||
await self._voice_perm_check(
|
||||
ctx, user_voice_state, mute_members=True, manage_channels=True
|
||||
)
|
||||
is False
|
||||
):
|
||||
return
|
||||
guild = ctx.guild
|
||||
author = ctx.author
|
||||
if user_voice_state:
|
||||
channel = user_voice_state.channel
|
||||
if channel:
|
||||
audit_reason = get_audit_reason(author, reason)
|
||||
channel = user_voice_state.channel
|
||||
audit_reason = get_audit_reason(author, reason)
|
||||
|
||||
success, message = await self.unmute_user(
|
||||
guild, channel, author, user, audit_reason
|
||||
success, message = await self.unmute_user(guild, channel, author, user, audit_reason)
|
||||
|
||||
if success:
|
||||
await ctx.send(
|
||||
_("Unmuted {user} in channel {channel.name}").format(user=user, channel=channel)
|
||||
)
|
||||
try:
|
||||
await modlog.create_case(
|
||||
self.bot,
|
||||
guild,
|
||||
ctx.message.created_at,
|
||||
"vunmute",
|
||||
user,
|
||||
author,
|
||||
reason,
|
||||
until=None,
|
||||
channel=channel,
|
||||
)
|
||||
|
||||
if success:
|
||||
await ctx.send(
|
||||
_("Unmuted {user} in channel {channel.name}").format(
|
||||
user=user, channel=channel
|
||||
)
|
||||
)
|
||||
try:
|
||||
await modlog.create_case(
|
||||
self.bot,
|
||||
guild,
|
||||
ctx.message.created_at,
|
||||
"vunmute",
|
||||
user,
|
||||
author,
|
||||
reason,
|
||||
until=None,
|
||||
channel=channel,
|
||||
)
|
||||
except RuntimeError as e:
|
||||
await ctx.send(e)
|
||||
else:
|
||||
await ctx.send(_("Unmute failed. Reason: {}").format(message))
|
||||
else:
|
||||
await ctx.send(_("That user is not in a voice channel right now!"))
|
||||
except RuntimeError as e:
|
||||
await ctx.send(e)
|
||||
else:
|
||||
await ctx.send(_("No voice state for the target!"))
|
||||
return
|
||||
await ctx.send(_("Unmute failed. Reason: {}").format(message))
|
||||
|
||||
@checks.mod_or_permissions(administrator=True)
|
||||
@unmute.command(name="channel")
|
||||
@ -1334,8 +1370,8 @@ class Mod(commands.Cog):
|
||||
user = author
|
||||
|
||||
# A special case for a special someone :^)
|
||||
special_date = datetime(2016, 1, 10, 6, 8, 4, 443_000)
|
||||
is_special = user.id == 96_130_341_705_637_888 and guild.id == 133_049_272_517_001_216
|
||||
special_date = datetime(2016, 1, 10, 6, 8, 4, 443000)
|
||||
is_special = user.id == 96130341705637888 and guild.id == 133049272517001216
|
||||
|
||||
roles = sorted(user.roles)[1:]
|
||||
names, nicks = await self.get_names_and_nicks(user)
|
||||
|
||||
@ -15,7 +15,7 @@ from pkg_resources import DistributionNotFound
|
||||
|
||||
from . import __version__ as red_version, version_info as red_version_info, VersionInfo, commands
|
||||
from .data_manager import storage_type
|
||||
from .utils.chat_formatting import inline, bordered, humanize_list
|
||||
from .utils.chat_formatting import inline, bordered, format_perms_list
|
||||
from .utils import fuzzy_command_search, format_fuzzy_results
|
||||
|
||||
log = logging.getLogger("red")
|
||||
@ -234,18 +234,13 @@ def init_events(bot, cli_flags):
|
||||
else:
|
||||
await ctx.send(await format_fuzzy_results(ctx, fuzzy_commands, embed=False))
|
||||
elif isinstance(error, commands.BotMissingPermissions):
|
||||
missing_perms: List[str] = []
|
||||
for perm, value in error.missing:
|
||||
if value is True:
|
||||
perm_name = '"' + perm.replace("_", " ").title() + '"'
|
||||
missing_perms.append(perm_name)
|
||||
if len(missing_perms) == 1:
|
||||
if bin(error.missing.value).count("1") == 1: # Only one perm missing
|
||||
plural = ""
|
||||
else:
|
||||
plural = "s"
|
||||
await ctx.send(
|
||||
"I require the {perms} permission{plural} to execute that command.".format(
|
||||
perms=humanize_list(missing_perms), plural=plural
|
||||
perms=format_perms_list(error.missing), plural=plural
|
||||
)
|
||||
)
|
||||
elif isinstance(error, commands.CheckFailure):
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import itertools
|
||||
from typing import Sequence, Iterator, List
|
||||
|
||||
import discord
|
||||
|
||||
from redbot.core.i18n import Translator
|
||||
|
||||
_ = Translator("UtilsChatFormatting", __file__)
|
||||
@ -329,7 +332,7 @@ def escape(text: str, *, mass_mentions: bool = False, formatting: bool = False)
|
||||
return text
|
||||
|
||||
|
||||
def humanize_list(items: Sequence[str]):
|
||||
def humanize_list(items: Sequence[str]) -> str:
|
||||
"""Get comma-separted list, with the last element joined with *and*.
|
||||
|
||||
This uses an Oxford comma, because without one, items containing
|
||||
@ -357,3 +360,29 @@ def humanize_list(items: Sequence[str]):
|
||||
if len(items) == 1:
|
||||
return items[0]
|
||||
return ", ".join(items[:-1]) + _(", and ") + items[-1]
|
||||
|
||||
|
||||
def format_perms_list(perms: discord.Permissions) -> str:
|
||||
"""Format a list of permission names.
|
||||
|
||||
This will return a humanized list of the names of all enabled
|
||||
permissions in the provided `discord.Permissions` object.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
perms : discord.Permissions
|
||||
The permissions object with the requested permissions to list
|
||||
enabled.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
The humanized list.
|
||||
|
||||
"""
|
||||
perm_names: List[str] = []
|
||||
for perm, value in perms:
|
||||
if value is True:
|
||||
perm_name = '"' + perm.replace("_", " ").title() + '"'
|
||||
perm_names.append(perm_name)
|
||||
return humanize_list(perm_names).replace("Guild", "Server")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user