diff --git a/docs/cog_permissions.rst b/docs/cog_permissions.rst index dbbe9a7fc..f9b263f7f 100644 --- a/docs/cog_permissions.rst +++ b/docs/cog_permissions.rst @@ -31,8 +31,8 @@ In terms of scope, global rules will be checked first, then server rules. For each of those, the first rule pertaining to one of the following models will be used: 1. User -2. Voice channel -3. Text channel (parent text channel in case of invocations in threads) +2. Voice channel a user is connected to +3. The channel command was issued in (parent channel in case of invocations in threads) 4. Channel category 5. Roles, highest to lowest 6. Server (can only be in global rules) diff --git a/docs/framework_bank.rst b/docs/framework_bank.rst index 9f5e676f8..fc1dddb9c 100644 --- a/docs/framework_bank.rst +++ b/docs/framework_bank.rst @@ -21,9 +21,7 @@ Basic Usage class MyCog(commands.Cog): @commands.command() - async def balance(self, ctx, user: discord.Member = None): - if user is None: - user = ctx.author + async def balance(self, ctx, user: discord.Member = commands.Author): bal = await bank.get_balance(user) currency = await bank.get_currency_name(ctx.guild) await ctx.send( diff --git a/redbot/cogs/admin/admin.py b/redbot/cogs/admin/admin.py index 142d627f0..e3ceac670 100644 --- a/redbot/cogs/admin/admin.py +++ b/redbot/cogs/admin/admin.py @@ -1,6 +1,6 @@ import asyncio import logging -from typing import Tuple +from typing import Tuple, Union import discord from redbot.core import Config, checks, commands @@ -153,7 +153,7 @@ class Admin(commands.Cog): async def _addrole( self, ctx: commands.Context, member: discord.Member, role: discord.Role, *, check_user=True ): - if role in member.roles: + if member.get_role(role.id) is not None: await ctx.send( _("{member.display_name} already has the role {role.name}.").format( role=role, member=member @@ -183,7 +183,7 @@ class Admin(commands.Cog): async def _removerole( self, ctx: commands.Context, member: discord.Member, role: discord.Role, *, check_user=True ): - if role not in member.roles: + if member.get_role(role.id) is None: await ctx.send( _("{member.display_name} does not have the role {role.name}.").format( role=role, member=member @@ -214,7 +214,11 @@ class Admin(commands.Cog): @commands.guild_only() @checks.admin_or_permissions(manage_roles=True) async def addrole( - self, ctx: commands.Context, rolename: discord.Role, *, user: discord.Member = None + self, + ctx: commands.Context, + rolename: discord.Role, + *, + user: discord.Member = commands.Author, ): """ Add a role to a user. @@ -222,15 +226,17 @@ class Admin(commands.Cog): Use double quotes if the role contains spaces. If user is left blank it defaults to the author of the command. """ - if user is None: - user = ctx.author await self._addrole(ctx, user, rolename) @commands.command() @commands.guild_only() @checks.admin_or_permissions(manage_roles=True) async def removerole( - self, ctx: commands.Context, rolename: discord.Role, *, user: discord.Member = None + self, + ctx: commands.Context, + rolename: discord.Role, + *, + user: discord.Member = commands.Author, ): """ Remove a role from a user. @@ -238,8 +244,6 @@ class Admin(commands.Cog): Use double quotes if the role contains spaces. If user is left blank it defaults to the author of the command. """ - if user is None: - user = ctx.author await self._removerole(ctx, user, rolename) @commands.group() @@ -349,7 +353,9 @@ class Admin(commands.Cog): pass @announceset.command(name="channel") - async def announceset_channel(self, ctx, *, channel: discord.TextChannel): + async def announceset_channel( + self, ctx, *, channel: Union[discord.TextChannel, discord.VoiceChannel] + ): """Change the channel where the bot will send announcements.""" await self.config.guild(ctx.guild).announce_channel.set(channel.id) await ctx.send( @@ -389,7 +395,7 @@ class Admin(commands.Cog): Server admins must have configured the role as user settable. NOTE: The role is case sensitive! """ - if selfrole in ctx.author.roles: + if ctx.author.get_role(selfrole.id) is not None: return await self._removerole(ctx, ctx.author, selfrole, check_user=False) else: return await self._addrole(ctx, ctx.author, selfrole, check_user=False) diff --git a/redbot/cogs/audio/core/abc.py b/redbot/cogs/audio/core/abc.py index 5e4d27a9c..49a516bf2 100644 --- a/redbot/cogs/audio/core/abc.py +++ b/redbot/cogs/audio/core/abc.py @@ -196,7 +196,9 @@ class MixinMeta(ABC): async def is_query_allowed( self, config: Config, - ctx_or_channel: Optional[Union[Context, discord.TextChannel, discord.Thread]], + ctx_or_channel: Optional[ + Union[Context, discord.TextChannel, discord.VoiceChannel, discord.Thread] + ], query: str, query_obj: Query, ) -> bool: @@ -250,7 +252,9 @@ class MixinMeta(ABC): raise NotImplementedError() @abstractmethod - def _has_notify_perms(self, channel: Union[discord.TextChannel, discord.Thread]) -> bool: + def _has_notify_perms( + self, channel: Union[discord.TextChannel, discord.VoiceChannel, discord.Thread] + ) -> bool: raise NotImplementedError() @abstractmethod diff --git a/redbot/cogs/audio/core/commands/controller.py b/redbot/cogs/audio/core/commands/controller.py index b12b0a318..8cc971469 100644 --- a/redbot/cogs/audio/core/commands/controller.py +++ b/redbot/cogs/audio/core/commands/controller.py @@ -657,7 +657,7 @@ class PlayerControllerCommands(MixinMeta, metaclass=CompositeMetaClass): if not self._player_check(ctx): player = await lavalink.connect( ctx.author.voice.channel, - deafen=await self.config.guild_from_id(ctx.guild.id).auto_deafen(), + self_deaf=await self.config.guild_from_id(ctx.guild.id).auto_deafen(), ) player.store("notify_channel", ctx.channel.id) else: @@ -675,7 +675,7 @@ class PlayerControllerCommands(MixinMeta, metaclass=CompositeMetaClass): ) await player.move_to( ctx.author.voice.channel, - deafen=await self.config.guild_from_id(ctx.guild.id).auto_deafen(), + self_deaf=await self.config.guild_from_id(ctx.guild.id).auto_deafen(), ) await ctx.tick() except AttributeError: diff --git a/redbot/cogs/audio/core/commands/player.py b/redbot/cogs/audio/core/commands/player.py index d2f5ef134..a925c7a46 100644 --- a/redbot/cogs/audio/core/commands/player.py +++ b/redbot/cogs/audio/core/commands/player.py @@ -85,7 +85,7 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass): ) await lavalink.connect( ctx.author.voice.channel, - deafen=await self.config.guild_from_id(ctx.guild.id).auto_deafen(), + self_deaf=await self.config.guild_from_id(ctx.guild.id).auto_deafen(), ) except AttributeError: return await self.send_embed_msg( @@ -193,7 +193,7 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass): ) await lavalink.connect( ctx.author.voice.channel, - deafen=await self.config.guild_from_id(ctx.guild.id).auto_deafen(), + self_deaf=await self.config.guild_from_id(ctx.guild.id).auto_deafen(), ) except AttributeError: return await self.send_embed_msg( @@ -456,7 +456,7 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass): ) await lavalink.connect( ctx.author.voice.channel, - deafen=await self.config.guild_from_id(ctx.guild.id).auto_deafen(), + self_deaf=await self.config.guild_from_id(ctx.guild.id).auto_deafen(), ) except AttributeError: return await self.send_embed_msg( @@ -572,7 +572,7 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass): ) await lavalink.connect( ctx.author.voice.channel, - deafen=await self.config.guild_from_id(ctx.guild.id).auto_deafen(), + self_deaf=await self.config.guild_from_id(ctx.guild.id).auto_deafen(), ) except AttributeError: return await self.send_embed_msg( @@ -697,7 +697,7 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass): ) await lavalink.connect( ctx.author.voice.channel, - deafen=await self.config.guild_from_id(ctx.guild.id).auto_deafen(), + self_deaf=await self.config.guild_from_id(ctx.guild.id).auto_deafen(), ) except AttributeError: return await self.send_embed_msg( diff --git a/redbot/cogs/audio/core/commands/queue.py b/redbot/cogs/audio/core/commands/queue.py index 80aacc318..d2a51fdb2 100644 --- a/redbot/cogs/audio/core/commands/queue.py +++ b/redbot/cogs/audio/core/commands/queue.py @@ -344,7 +344,7 @@ class QueueCommands(MixinMeta, metaclass=CompositeMetaClass): ) player = await lavalink.connect( ctx.author.voice.channel, - deafen=await self.config.guild_from_id(ctx.guild.id).auto_deafen(), + self_deaf=await self.config.guild_from_id(ctx.guild.id).auto_deafen(), ) player.store("notify_channel", ctx.channel.id) except AttributeError: diff --git a/redbot/cogs/audio/core/events/dpy.py b/redbot/cogs/audio/core/events/dpy.py index 2e6652a30..10f52f97d 100644 --- a/redbot/cogs/audio/core/events/dpy.py +++ b/redbot/cogs/audio/core/events/dpy.py @@ -62,7 +62,7 @@ HUMANIZED_PERM = { "manage_roles": _("Manage Roles"), "manage_webhooks": _("Manage Webhooks"), "manage_emojis": _("Manage Emojis"), - "use_slash_commands": _("Use Slash Commands"), + "use_application_commands": _("Use Application Commands"), "request_to_speak": _("Request to Speak"), "manage_events": _("Manage Events"), "manage_threads": _("Manage Threads"), @@ -187,7 +187,7 @@ class DpyEvents(MixinMeta, metaclass=CompositeMetaClass): ) surpass_ignore = ( - isinstance(ctx.channel, discord.abc.PrivateChannel) + ctx.guild is None or await ctx.bot.is_owner(ctx.author) or await ctx.bot.is_admin(ctx.author) ) @@ -210,7 +210,7 @@ class DpyEvents(MixinMeta, metaclass=CompositeMetaClass): ) raise CheckFailure(message=text) - current_perms = ctx.channel.permissions_for(ctx.me) + current_perms = ctx.bot_permissions if guild and not current_perms.is_superset(self.permission_cache): current_perms_set = set(iter(current_perms)) expected_perms_set = set(iter(self.permission_cache)) diff --git a/redbot/cogs/audio/core/events/lavalink.py b/redbot/cogs/audio/core/events/lavalink.py index ab0688bef..2f0324738 100644 --- a/redbot/cogs/audio/core/events/lavalink.py +++ b/redbot/cogs/audio/core/events/lavalink.py @@ -90,7 +90,11 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass): self._ws_resume[guild_id].set() await self._websocket_closed_handler( - guild=guild, player=player, extra=extra, deafen=deafen, disconnect=disconnect + guild=guild, + player=player, + extra=extra, + self_deaf=deafen, + disconnect=disconnect, ) except Exception as exc: log.debug( @@ -335,7 +339,7 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass): guild: discord.Guild, player: lavalink.Player, extra: Dict, - deafen: bool, + self_deaf: bool, disconnect: bool, ) -> None: guild_id = guild.id @@ -415,7 +419,7 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass): if has_perm and player.current and player.is_playing: player.store("resumes", player.fetch("resumes", 0) + 1) - await player.connect(deafen=deafen) + await player.connect(self_deaf=self_deaf) await player.resume(player.current, start=player.position, replace=True) ws_audio_log.info( "Voice websocket reconnected Reason: Error code %s & Currently playing", @@ -429,7 +433,7 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass): ) elif has_perm and player.paused and player.current: player.store("resumes", player.fetch("resumes", 0) + 1) - await player.connect(deafen=deafen) + await player.connect(self_deaf=self_deaf) await player.resume( player.current, start=player.position, replace=True, pause=True ) @@ -445,7 +449,7 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass): ) elif has_perm and (not disconnect) and (not player.is_playing): player.store("resumes", player.fetch("resumes", 0) + 1) - await player.connect(deafen=deafen) + await player.connect(self_deaf=self_deaf) ws_audio_log.info( "Voice websocket reconnected " "Reason: Error code %s & Not playing, but auto disconnect disabled", @@ -497,7 +501,7 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass): ).currently_auto_playing_in.set([]) elif code in (42069,) and has_perm and player.current and player.is_playing: player.store("resumes", player.fetch("resumes", 0) + 1) - await player.connect(deafen=deafen) + await player.connect(self_deaf=self_deaf) await player.resume(player.current, start=player.position, replace=True) ws_audio_log.info("Player resumed - Reason: Error code %s & %s", code, reason) ws_audio_log.debug( @@ -514,7 +518,7 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass): ) await asyncio.sleep(delay) if has_perm and player.current and player.is_playing: - await player.connect(deafen=deafen) + await player.connect(self_deaf=self_deaf) await player.resume(player.current, start=player.position, replace=True) ws_audio_log.info( "Voice websocket reconnected Reason: Error code %s & Player is active", @@ -528,7 +532,7 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass): ) elif has_perm and player.paused and player.current: player.store("resumes", player.fetch("resumes", 0) + 1) - await player.connect(deafen=deafen) + await player.connect(self_deaf=self_deaf) await player.resume( player.current, start=player.position, replace=True, pause=True ) @@ -544,7 +548,7 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass): ) elif has_perm and (not disconnect) and (not player.is_playing): player.store("resumes", player.fetch("resumes", 0) + 1) - await player.connect(deafen=deafen) + await player.connect(self_deaf=self_deaf) ws_audio_log.info( "Voice websocket reconnected " "to channel %s in guild: %s | " diff --git a/redbot/cogs/audio/core/tasks/startup.py b/redbot/cogs/audio/core/tasks/startup.py index 351ab2e31..c38e65dd1 100644 --- a/redbot/cogs/audio/core/tasks/startup.py +++ b/redbot/cogs/audio/core/tasks/startup.py @@ -138,7 +138,7 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass): if not (perms.connect and perms.speak): vc = None break - player = await lavalink.connect(vc, deafen=auto_deafen) + player = await lavalink.connect(vc, self_deaf=auto_deafen) player.store("notify_channel", notify_channel_id) break except NodeNotFound: @@ -222,7 +222,7 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass): if not (perms.connect and perms.speak): vc = None break - player = await lavalink.connect(vc, deafen=auto_deafen) + player = await lavalink.connect(vc, self_deaf=auto_deafen) player.store("notify_channel", notify_channel_id) break except NodeNotFound: diff --git a/redbot/cogs/audio/core/utilities/formatting.py b/redbot/cogs/audio/core/utilities/formatting.py index 2ba32a3d5..4ca3b0bf5 100644 --- a/redbot/cogs/audio/core/utilities/formatting.py +++ b/redbot/cogs/audio/core/utilities/formatting.py @@ -100,7 +100,7 @@ class FormattingUtilities(MixinMeta, metaclass=CompositeMetaClass): try: await lavalink.connect( ctx.author.voice.channel, - deafen=await self.config.guild_from_id(ctx.guild.id).auto_deafen(), + self_deaf=await self.config.guild_from_id(ctx.guild.id).auto_deafen(), ) except AttributeError: return await self.send_embed_msg(ctx, title=_("Connect to a voice channel first.")) diff --git a/redbot/cogs/audio/core/utilities/miscellaneous.py b/redbot/cogs/audio/core/utilities/miscellaneous.py index c91ffd0de..959b61351 100644 --- a/redbot/cogs/audio/core/utilities/miscellaneous.py +++ b/redbot/cogs/audio/core/utilities/miscellaneous.py @@ -99,7 +99,9 @@ class MiscellaneousUtilities(MixinMeta, metaclass=CompositeMetaClass): embed.set_author(name=name) return await ctx.send(embed=embed) - def _has_notify_perms(self, channel: Union[discord.TextChannel, discord.Thread]) -> bool: + def _has_notify_perms( + self, channel: Union[discord.TextChannel, discord.VoiceChannel, discord.Thread] + ) -> bool: perms = channel.permissions_for(channel.guild.me) return all((can_user_send_messages_in(channel.guild.me, channel), perms.embed_links)) diff --git a/redbot/cogs/audio/core/utilities/player.py b/redbot/cogs/audio/core/utilities/player.py index 1a6e1a45d..cd305937e 100644 --- a/redbot/cogs/audio/core/utilities/player.py +++ b/redbot/cogs/audio/core/utilities/player.py @@ -114,8 +114,7 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass): dj_role = self._dj_role_cache.setdefault( ctx.guild.id, await self.config.guild(ctx.guild).dj_role() ) - dj_role_obj = ctx.guild.get_role(dj_role) - return dj_role_obj in ctx.guild.get_member(member.id).roles + return member.get_role(dj_role) is not None async def is_requester(self, ctx: commands.Context, member: discord.Member) -> bool: try: @@ -711,7 +710,7 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass): ): await player.move_to( user_channel, - deafen=await self.config.guild_from_id(ctx.guild.id).auto_deafen(), + self_deaf=await self.config.guild_from_id(ctx.guild.id).auto_deafen(), ) return True else: diff --git a/redbot/cogs/audio/core/utilities/playlists.py b/redbot/cogs/audio/core/utilities/playlists.py index 55d85bd6e..08ec3e4b4 100644 --- a/redbot/cogs/audio/core/utilities/playlists.py +++ b/redbot/cogs/audio/core/utilities/playlists.py @@ -545,7 +545,7 @@ class PlaylistUtilities(MixinMeta, metaclass=CompositeMetaClass): return False await lavalink.connect( ctx.author.voice.channel, - deafen=await self.config.guild_from_id(ctx.guild.id).auto_deafen(), + self_deaf=await self.config.guild_from_id(ctx.guild.id).auto_deafen(), ) except NodeNotFound: await self.send_embed_msg( diff --git a/redbot/cogs/audio/core/utilities/validation.py b/redbot/cogs/audio/core/utilities/validation.py index 7b825ce7e..4ba82c96f 100644 --- a/redbot/cogs/audio/core/utilities/validation.py +++ b/redbot/cogs/audio/core/utilities/validation.py @@ -60,7 +60,9 @@ class ValidationUtilities(MixinMeta, metaclass=CompositeMetaClass): async def is_query_allowed( self, config: Config, - ctx_or_channel: Optional[Union[Context, discord.TextChannel, discord.Thread]], + ctx_or_channel: Optional[ + Union[Context, discord.TextChannel, discord.VoiceChannel, discord.Thread] + ], query: str, query_obj: Query, ) -> bool: diff --git a/redbot/cogs/cleanup/cleanup.py b/redbot/cogs/cleanup/cleanup.py index 09110ebfe..d3996e343 100644 --- a/redbot/cogs/cleanup/cleanup.py +++ b/redbot/cogs/cleanup/cleanup.py @@ -75,7 +75,9 @@ class Cleanup(commands.Cog): @staticmethod async def get_messages_for_deletion( *, - channel: Union[discord.TextChannel, discord.DMChannel, discord.Thread], + channel: Union[ + discord.TextChannel, discord.VoiceChannel, discord.DMChannel, discord.Thread + ], number: Optional[PositiveInt] = None, check: Callable[[discord.Message], bool] = lambda x: True, limit: Optional[PositiveInt] = None, @@ -129,7 +131,9 @@ class Cleanup(commands.Cog): async def send_optional_notification( self, num: int, - channel: Union[discord.TextChannel, discord.DMChannel, discord.Thread], + channel: Union[ + discord.TextChannel, discord.VoiceChannel, discord.DMChannel, discord.Thread + ], *, subtract_invoking: bool = False, ) -> None: @@ -149,7 +153,8 @@ class Cleanup(commands.Cog): @staticmethod async def get_message_from_reference( - channel: Union[discord.TextChannel, discord.Thread], reference: discord.MessageReference + channel: Union[discord.TextChannel, discord.VoiceChannel, discord.Thread], + reference: discord.MessageReference, ) -> Optional[discord.Message]: message = None resolved = reference.resolved @@ -621,7 +626,7 @@ class Cleanup(commands.Cog): can_mass_purge = False if type(author) is discord.Member: me = ctx.guild.me - can_mass_purge = channel.permissions_for(me).manage_messages + can_mass_purge = ctx.bot_permissions.manage_messages if match_pattern: diff --git a/redbot/cogs/economy/economy.py b/redbot/cogs/economy/economy.py index d13a85865..973884c06 100644 --- a/redbot/cogs/economy/economy.py +++ b/redbot/cogs/economy/economy.py @@ -181,7 +181,7 @@ class Economy(commands.Cog): pass @_bank.command() - async def balance(self, ctx: commands.Context, user: discord.Member = None): + async def balance(self, ctx: commands.Context, user: discord.Member = commands.Author): """Show the user's account balance. Example: @@ -192,9 +192,6 @@ class Economy(commands.Cog): - `` The user to check the balance of. If omitted, defaults to your own balance. """ - if user is None: - user = ctx.author - bal = await bank.get_balance(user) currency = await bank.get_currency_name(ctx.guild) max_bal = await bank.get_max_balance(ctx.guild) diff --git a/redbot/cogs/filter/filter.py b/redbot/cogs/filter/filter.py index 8a50d01a1..c2d2f8897 100644 --- a/redbot/cogs/filter/filter.py +++ b/redbot/cogs/filter/filter.py @@ -251,28 +251,25 @@ class Filter(commands.Cog): await ctx.send(_("I can't send direct messages to you.")) @_filter_channel.command(name="add", require_var_positional=True) - async def filter_channel_add(self, ctx: commands.Context, *words: str): + async def filter_channel_add( + self, + ctx: commands.Context, + channel: Union[discord.TextChannel, discord.VoiceChannel, discord.ForumChannel], + *words: str, + ): """Add words to the filter. Use double quotes to add sentences. Examples: - - `[p]filter channel add word1 word2 word3` - - `[p]filter channel add "This is a sentence"` + - `[p]filter channel add #channel word1 word2 word3` + - `[p]filter channel add #channel "This is a sentence"` **Arguments:** + - `` The text, voice, or forum channel to add filtered words to. - `[words...]` The words or sentences to filter. """ - channel = ctx.channel - if isinstance(channel, discord.Thread): - await ctx.send( - _( - "Threads can't have a filter list set up. If you want to add words to" - " the list of the parent channel, send the command in that channel." - ) - ) - return added = await self.add_to_filter(channel, words) if added: self.invalidate_cache(ctx.guild, ctx.channel) @@ -281,28 +278,25 @@ class Filter(commands.Cog): await ctx.send(_("Words already in the filter.")) @_filter_channel.command(name="delete", aliases=["remove", "del"], require_var_positional=True) - async def filter_channel_remove(self, ctx: commands.Context, *words: str): + async def filter_channel_remove( + self, + ctx: commands.Context, + channel: Union[discord.TextChannel, discord.VoiceChannel, discord.ForumChannel], + *words: str, + ): """Remove words from the filter. Use double quotes to remove sentences. Examples: - - `[p]filter channel remove word1 word2 word3` - - `[p]filter channel remove "This is a sentence"` + - `[p]filter channel remove #channel word1 word2 word3` + - `[p]filter channel remove #channel "This is a sentence"` **Arguments:** + - `` The text, voice, or forum channel to add filtered words to. - `[words...]` The words or sentences to no longer filter. """ - channel = ctx.channel - if isinstance(channel, discord.Thread): - await ctx.send( - _( - "Threads can't have a filter list set up. If you want to remove words from" - " the list of the parent channel, send the command in that channel." - ) - ) - return removed = await self.remove_from_filter(channel, words) if removed: await ctx.send(_("Words removed from filter.")) @@ -371,7 +365,11 @@ class Filter(commands.Cog): await ctx.send(_("Names and nicknames will now be filtered.")) def invalidate_cache( - self, guild: discord.Guild, channel: Optional[discord.TextChannel] = None + self, + guild: discord.Guild, + channel: Optional[ + Union[discord.TextChannel, discord.VoiceChannel, discord.ForumChannel] + ] = None, ) -> None: """Invalidate a cached pattern""" self.pattern_cache.pop((guild.id, channel and channel.id), None) @@ -381,7 +379,11 @@ class Filter(commands.Cog): self.pattern_cache.pop(keyset, None) async def add_to_filter( - self, server_or_channel: Union[discord.Guild, discord.TextChannel], words: list + self, + server_or_channel: Union[ + discord.Guild, discord.TextChannel, discord.VoiceChannel, discord.ForumChannel + ], + words: list, ) -> bool: added = False if isinstance(server_or_channel, discord.Guild): @@ -391,7 +393,7 @@ class Filter(commands.Cog): cur_list.append(w.lower()) added = True - elif isinstance(server_or_channel, discord.TextChannel): + else: async with self.config.channel(server_or_channel).filter() as cur_list: for w in words: if w.lower() not in cur_list and w: @@ -401,7 +403,11 @@ class Filter(commands.Cog): return added async def remove_from_filter( - self, server_or_channel: Union[discord.Guild, discord.TextChannel], words: list + self, + server_or_channel: Union[ + discord.Guild, discord.TextChannel, discord.VoiceChannel, discord.ForumChannel + ], + words: list, ) -> bool: removed = False if isinstance(server_or_channel, discord.Guild): @@ -411,7 +417,7 @@ class Filter(commands.Cog): cur_list.remove(w.lower()) removed = True - elif isinstance(server_or_channel, discord.TextChannel): + else: async with self.config.channel(server_or_channel).filter() as cur_list: for w in words: if w.lower() in cur_list: @@ -423,7 +429,9 @@ class Filter(commands.Cog): async def filter_hits( self, text: str, - server_or_channel: Union[discord.Guild, discord.TextChannel, discord.Thread], + server_or_channel: Union[ + discord.Guild, discord.TextChannel, discord.VoiceChannel, discord.Thread + ], ) -> Set[str]: if isinstance(server_or_channel, discord.Guild): guild = server_or_channel diff --git a/redbot/cogs/mod/slowmode.py b/redbot/cogs/mod/slowmode.py index 261c62fac..ad6f3ce45 100644 --- a/redbot/cogs/mod/slowmode.py +++ b/redbot/cogs/mod/slowmode.py @@ -1,3 +1,4 @@ +import discord import re from .abc import MixinMeta from datetime import timedelta @@ -24,11 +25,14 @@ class Slowmode(MixinMeta): minimum=timedelta(seconds=0), maximum=timedelta(hours=6), default_unit="seconds" ) = timedelta(seconds=0), ): - """Changes thread's or channel's slowmode setting. + """Changes thread's or text channel's slowmode setting. Interval can be anything from 0 seconds to 6 hours. Use without parameters to disable. """ + if not isinstance(ctx.channel, (discord.TextChannel, discord.Thread)): + await ctx.send(_("Slowmode can only be set in text channels and threads.")) + return seconds = interval.total_seconds() await ctx.channel.edit(slowmode_delay=seconds) if seconds > 0: diff --git a/redbot/cogs/mutes/mutes.py b/redbot/cogs/mutes/mutes.py index e1a5b4ad0..426b68ca4 100644 --- a/redbot/cogs/mutes/mutes.py +++ b/redbot/cogs/mutes/mutes.py @@ -52,6 +52,11 @@ MUTE_UNMUTE_ISSUES = { "voice_mute_permission": _( "Because I don't have the Move Members permission, this will take into effect when the user rejoins." ), + "is_not_voice_mute": _( + "That user is channel muted in their current voice channel, not just voice muted." + " If you want to fully unmute this user in the channel," + " use {command} in their voice channel's text channel instead." + ), } _ = T_ @@ -503,7 +508,7 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass): del muted_users[str(member.id)] if success["success"]: if create_case: - if isinstance(channel, discord.VoiceChannel): + if data.get("voice_mute", False): unmute_type = "vunmute" notification_title = _("Voice unmute") else: @@ -692,16 +697,21 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass): o.id: {name: attr for name, attr in p} for o, p in after.overwrites.items() } to_del: List[int] = [] - for user_id in self._channel_mutes[after.id].keys(): + for user_id, mute_data in self._channel_mutes[after.id].items(): unmuted = False + voice_mute = mute_data.get("voice_mute", False) if user_id in after_perms: - for perm_name in ( - "send_messages", - "send_messages_in_threads", - "create_public_threads", - "create_private_threads", - "speak", - ): + perms_to_check = ["speak"] + if not voice_mute: + perms_to_check.extend( + ( + "send_messages", + "send_messages_in_threads", + "create_public_threads", + "create_private_threads", + ) + ) + for perm_name in perms_to_check: unmuted = unmuted or after_perms[user_id][perm_name] is not False # explicit is better than implicit :thinkies: if user_id in before_perms and (user_id not in after_perms or unmuted): @@ -713,7 +723,7 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass): log.debug(f"{user} - {type(user)}") to_del.append(user_id) log.debug("creating case") - if isinstance(after, discord.VoiceChannel): + if voice_mute: unmute_type = "vunmute" notification_title = _("Voice unmute") else: @@ -848,7 +858,9 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass): @muteset.command(name="notification") @checks.admin_or_permissions(manage_channels=True) async def notification_channel_set( - self, ctx: commands.Context, channel: Optional[discord.TextChannel] = None + self, + ctx: commands.Context, + channel: Optional[Union[discord.TextChannel, discord.VoiceChannel]] = None, ): """ Set the notification channel for automatic unmute issues. @@ -932,6 +944,7 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass): send_messages_in_threads=False, create_public_threads=False, create_private_threads=False, + use_application_commands=False, speak=False, add_reactions=False, ) @@ -979,6 +992,7 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass): overs.send_messages_in_threads = False overs.create_public_threads = False overs.create_private_threads = False + overs.use_application_commands = False overs.add_reactions = False overs.speak = False try: @@ -1681,6 +1695,8 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass): user: discord.Member, until: Optional[datetime] = None, reason: Optional[str] = None, + *, + voice_mute: bool = False, ) -> Dict[str, Optional[Union[discord.abc.GuildChannel, str, bool]]]: """Mutes the specified user in the specified channel""" overwrites = channel.overwrites_for(user) @@ -1693,16 +1709,7 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass): "reason": _(MUTE_UNMUTE_ISSUES["is_admin"]), } - new_overs: dict = {} move_channel = False - new_overs.update( - send_messages=False, - send_messages_in_threads=False, - create_public_threads=False, - create_private_threads=False, - add_reactions=False, - speak=False, - ) send_reason = None if user.voice and user.voice.channel: if channel.permissions_for(guild.me).move_members: @@ -1717,16 +1724,38 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass): "reason": _(MUTE_UNMUTE_ISSUES["hierarchy_problem"]), } - old_overs = {k: getattr(overwrites, k) for k in new_overs} - overwrites.update(**new_overs) if channel.id not in self._channel_mutes: self._channel_mutes[channel.id] = {} - if user.id in self._channel_mutes[channel.id]: + current_mute = self._channel_mutes.get(channel.id) + + # Determine if this is voice mute -> channel mute upgrade + is_mute_upgrade = ( + current_mute is not None and not voice_mute and current_mute.get("voice_mute", False) + ) + # We want to continue if this is a new mute or a mute upgrade, + # otherwise we should return with failure. + if current_mute is not None and not is_mute_upgrade: return { "success": False, "channel": channel, "reason": _(MUTE_UNMUTE_ISSUES["already_muted"]), } + new_overs: Dict[str, Optional[bool]] = {"speak": False} + if not voice_mute: + new_overs.update( + send_messages=False, + send_messages_in_threads=False, + create_public_threads=False, + create_private_threads=False, + use_application_commands=False, + add_reactions=False, + ) + old_overs = {k: getattr(overwrites, k) for k in new_overs} + if is_mute_upgrade: + perms_cache = await self.config.member(user).perms_cache() + if "speak" in perms_cache: + old_overs["speak"] = perms_cache["speak"] + overwrites.update(**new_overs) if not channel.permissions_for(guild.me).manage_permissions: return { "success": False, @@ -1738,6 +1767,7 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass): "guild": guild.id, "member": user.id, "until": until.timestamp() if until else None, + "voice_mute": voice_mute, } try: await channel.set_permissions(user, overwrite=overwrites, reason=reason) @@ -1795,6 +1825,8 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass): author: discord.Member, user: discord.Member, reason: Optional[str] = None, + *, + voice_mute: bool = False, ) -> Dict[str, Optional[Union[discord.abc.GuildChannel, str, bool]]]: """Unmutes the specified user in a specified channel""" overwrites = channel.overwrites_for(user) @@ -1809,6 +1841,7 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass): "send_messages_in_threads": None, "create_public_threads": None, "create_private_threads": None, + "use_application_commands": None, "add_reactions": None, "speak": None, } @@ -1826,13 +1859,21 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass): overwrites.update(**old_values) if channel.id in self._channel_mutes and user.id in self._channel_mutes[channel.id]: - del self._channel_mutes[channel.id][user.id] + current_mute = self._channel_mutes[channel.id].pop(user.id) else: return { "success": False, "channel": channel, "reason": _(MUTE_UNMUTE_ISSUES["already_unmuted"]), } + if not current_mute["voice_mute"] and voice_mute: + return { + "success": False, + "channel": channel, + "reason": _(MUTE_UNMUTE_ISSUES["is_not_voice_mute"]).format( + command=inline("unmutechannel") + ), + } if not channel.permissions_for(guild.me).manage_permissions: return { "success": False, diff --git a/redbot/cogs/mutes/voicemutes.py b/redbot/cogs/mutes/voicemutes.py index d737eeba5..1d1a58c38 100644 --- a/redbot/cogs/mutes/voicemutes.py +++ b/redbot/cogs/mutes/voicemutes.py @@ -124,7 +124,7 @@ class VoiceMutes(MixinMeta): audit_reason = get_audit_reason(author, reason, shorten=True) success = await self.channel_mute_user( - guild, channel, author, user, until, audit_reason + guild, channel, author, user, until, audit_reason, voice_mute=True ) if success["success"]: @@ -200,7 +200,7 @@ class VoiceMutes(MixinMeta): audit_reason = get_audit_reason(author, reason, shorten=True) success = await self.channel_unmute_user( - guild, channel, author, user, audit_reason + guild, channel, author, user, audit_reason, voice_mute=True ) if success["success"]: diff --git a/redbot/cogs/permissions/permissions.py b/redbot/cogs/permissions/permissions.py index 8bb8c81bb..8856f9cc2 100644 --- a/redbot/cogs/permissions/permissions.py +++ b/redbot/cogs/permissions/permissions.py @@ -221,8 +221,8 @@ class Permissions(commands.Cog): "Global rules (set by the owner) are checked first, then rules set for servers. If " "multiple global or server rules apply to the case, the order they are checked in is:\n" " 1. Rules about a user.\n" - " 2. Rules about the voice channel a user is in.\n" - " 3. Rules about the text channel or a parent of the thread a command was issued in.\n" + " 2. Rules about the voice channel a user is connected to.\n" + " 3. Rules about the channel or a parent of the thread a command was issued in.\n" " 4. Rules about a role the user has (The highest role they have with a rule will be " "used).\n" " 5. Rules about the server a user is in (Global rules only).\n\n" @@ -330,7 +330,7 @@ class Permissions(commands.Cog): except discord.Forbidden: await ctx.send(_("I'm not allowed to DM you.")) else: - if not isinstance(ctx.channel, discord.DMChannel): + if ctx.guild is not None: await ctx.send(_("I've just sent the file to you via DM.")) finally: file.close() diff --git a/redbot/cogs/reports/reports.py b/redbot/cogs/reports/reports.py index 149f9c1c4..24c049efd 100644 --- a/redbot/cogs/reports/reports.py +++ b/redbot/cogs/reports/reports.py @@ -106,7 +106,9 @@ class Reports(commands.Cog): @checks.admin_or_permissions(manage_guild=True) @reportset.command(name="output") - async def reportset_output(self, ctx: commands.Context, channel: discord.TextChannel): + async def reportset_output( + self, ctx: commands.Context, channel: Union[discord.TextChannel, discord.VoiceChannel] + ): """Set the channel where reports will be sent.""" await self.config.guild(ctx.guild).output_channel.set(channel.id) await ctx.send(_("The report channel has been set.")) @@ -325,7 +327,7 @@ class Reports(commands.Cog): if ctx.author.id in self.user_cache: self.user_cache.remove(ctx.author.id) if ctx.guild and ctx.invoked_subcommand is None: - if ctx.channel.permissions_for(ctx.guild.me).manage_messages: + if ctx.bot_permissions.manage_messages: try: await ctx.message.delete() except discord.NotFound: diff --git a/redbot/cogs/streams/streams.py b/redbot/cogs/streams/streams.py index fb3c698bd..6de0774ed 100644 --- a/redbot/cogs/streams/streams.py +++ b/redbot/cogs/streams/streams.py @@ -303,14 +303,19 @@ class Streams(commands.Cog): self, ctx: commands.Context, channel_name: str, - discord_channel: discord.TextChannel = None, + discord_channel: Union[discord.TextChannel, discord.VoiceChannel] = None, ): """Manage Twitch stream notifications.""" await ctx.invoke(self.twitch_alert_channel, channel_name, discord_channel) @_twitch.command(name="channel") async def twitch_alert_channel( - self, ctx: commands.Context, channel_name: str, discord_channel: discord.TextChannel = None + self, + ctx: commands.Context, + channel_name: str, + discord_channel: Union[ + discord.TextChannel, discord.VoiceChannel + ] = commands.CurrentChannel, ): """Toggle alerts in this or the given channel for a Twitch stream.""" if re.fullmatch(r"<#\d+>", channel_name): @@ -325,14 +330,21 @@ class Streams(commands.Cog): self, ctx: commands.Context, channel_name_or_id: str, - discord_channel: discord.TextChannel = None, + discord_channel: Union[ + discord.TextChannel, discord.VoiceChannel + ] = commands.CurrentChannel, ): """Toggle alerts in this channel for a YouTube stream.""" await self.stream_alert(ctx, YoutubeStream, channel_name_or_id, discord_channel) @streamalert.command(name="picarto") async def picarto_alert( - self, ctx: commands.Context, channel_name: str, discord_channel: discord.TextChannel = None + self, + ctx: commands.Context, + channel_name: str, + discord_channel: Union[ + discord.TextChannel, discord.VoiceChannel + ] = commands.CurrentChannel, ): """Toggle alerts in this channel for a Picarto stream.""" await self.stream_alert(ctx, PicartoStream, channel_name, discord_channel) @@ -401,8 +413,6 @@ class Streams(commands.Cog): await ctx.send(page) async def stream_alert(self, ctx: commands.Context, _class, channel_name, discord_channel): - if discord_channel is None: - discord_channel = ctx.channel if isinstance(discord_channel, discord.Thread): await ctx.send("Stream alerts cannot be set up in threads.") return @@ -757,7 +767,7 @@ class Streams(commands.Cog): async def _send_stream_alert( self, stream, - channel: discord.TextChannel, + channel: Union[discord.TextChannel, discord.VoiceChannel], embed: discord.Embed, content: str = None, *, @@ -904,7 +914,10 @@ class Streams(commands.Cog): await self.save_streams() async def _get_mention_str( - self, guild: discord.Guild, channel: discord.TextChannel, guild_data: dict + self, + guild: discord.Guild, + channel: Union[discord.TextChannel, discord.VoiceChannel], + guild_data: dict, ) -> Tuple[str, List[discord.Role]]: """Returns a 2-tuple with the string containing the mentions, and a list of all roles which need to have their `mentionable` property set back to False. @@ -930,7 +943,9 @@ class Streams(commands.Cog): mentions.append(role.mention) return " ".join(mentions), edited_roles - async def filter_streams(self, streams: list, channel: discord.TextChannel) -> list: + async def filter_streams( + self, streams: list, channel: Union[discord.TextChannel, discord.VoiceChannel] + ) -> list: filtered = [] for stream in streams: tw_id = str(stream["channel"]["_id"]) diff --git a/redbot/cogs/trivia/trivia.py b/redbot/cogs/trivia/trivia.py index 6fcfec48c..5b6e23e16 100644 --- a/redbot/cogs/trivia/trivia.py +++ b/redbot/cogs/trivia/trivia.py @@ -412,7 +412,7 @@ class Trivia(commands.Cog): subcommands for a more customised leaderboard. """ cmd = self.trivia_leaderboard_server - if isinstance(ctx.channel, discord.abc.PrivateChannel): + if ctx.guild is None: cmd = self.trivia_leaderboard_global await ctx.invoke(cmd, "wins", 10) @@ -710,7 +710,7 @@ class Trivia(commands.Cog): await ctx.send(_("Saved Trivia list as {filename}.").format(filename=filename)) def _get_trivia_session( - self, channel: Union[discord.TextChannel, discord.Thread] + self, channel: Union[discord.TextChannel, discord.VoiceChannel, discord.Thread] ) -> TriviaSession: return next( (session for session in self.trivia_sessions if session.ctx.channel == channel), None diff --git a/redbot/cogs/warnings/warnings.py b/redbot/cogs/warnings/warnings.py index 947619afa..708a056ff 100644 --- a/redbot/cogs/warnings/warnings.py +++ b/redbot/cogs/warnings/warnings.py @@ -156,7 +156,11 @@ class Warnings(commands.Cog): @warningset.command() @commands.guild_only() - async def warnchannel(self, ctx: commands.Context, channel: discord.TextChannel = None): + async def warnchannel( + self, + ctx: commands.Context, + channel: Union[discord.TextChannel, discord.VoiceChannel] = None, + ): """Set the channel where warnings should be sent to. Leave empty to use the channel `[p]warn` command was called in. diff --git a/redbot/core/_diagnoser.py b/redbot/core/_diagnoser.py index a6be078b8..42976f992 100644 --- a/redbot/core/_diagnoser.py +++ b/redbot/core/_diagnoser.py @@ -38,7 +38,7 @@ class IssueDiagnoserBase: self, bot: Red, original_ctx: commands.Context, - channel: Union[discord.TextChannel, discord.Thread], + channel: Union[discord.TextChannel, discord.VoiceChannel, discord.Thread], author: discord.Member, command: commands.Command, ) -> None: diff --git a/redbot/core/bot.py b/redbot/core/bot.py index 83a714398..faf3b3940 100644 --- a/redbot/core/bot.py +++ b/redbot/core/bot.py @@ -220,8 +220,6 @@ class Red( self._main_dir = bot_dir self._cog_mgr = CogManager() self._use_team_features = cli_flags.use_team_features - # to prevent multiple calls to app info during startup - self._app_info = None super().__init__(*args, help_command=None, **kwargs) # Do not manually use the help formatter attribute here, see `send_help_for`, # for a documented API. The internals of this object are still subject to change. @@ -815,7 +813,7 @@ class Red( return False if guild: - assert isinstance(channel, (discord.abc.GuildChannel, discord.Thread)) + assert isinstance(channel, (discord.TextChannel, discord.VoiceChannel, discord.Thread)) if not can_user_send_messages_in(guild.me, channel): return False if not (await self.ignored_channel_or_guild(message)): @@ -1207,16 +1205,12 @@ class Red( if self.rpc_enabled: await self.rpc.initialize(self.rpc_port) - async def _pre_fetch_owners(self) -> None: - app_info = await self.application_info() - - if app_info.team: + def _setup_owners(self) -> None: + if self.application.team: if self._use_team_features: - self.owner_ids.update(m.id for m in app_info.team.members) + self.owner_ids.update(m.id for m in self.application.team.members) elif self._owner_id_overwrite is None: - self.owner_ids.add(app_info.owner.id) - - self._app_info = app_info + self.owner_ids.add(self.application.owner.id) if not self.owner_ids: raise _NoOwnerSet("Bot doesn't have any owner set!") @@ -1229,7 +1223,7 @@ class Red( await self.connect() async def setup_hook(self) -> None: - await self._pre_fetch_owners() + self._setup_owners() await self._pre_connect() async def send_help_for( @@ -1249,7 +1243,12 @@ class Red( async def embed_requested( self, channel: Union[ - discord.TextChannel, commands.Context, discord.User, discord.Member, discord.Thread + discord.TextChannel, + discord.VoiceChannel, + commands.Context, + discord.User, + discord.Member, + discord.Thread, ], *, command: Optional[commands.Command] = None, @@ -1260,7 +1259,7 @@ class Red( Arguments --------- - channel : Union[`discord.TextChannel`, `commands.Context`, `discord.User`, `discord.Member`, `discord.Thread`] + channel : Union[`discord.TextChannel`, `discord.VoiceChannel`, `commands.Context`, `discord.User`, `discord.Member`, `discord.Thread`] The target messageable object to check embed settings for. Keyword Arguments @@ -1307,7 +1306,7 @@ class Red( "You cannot pass a GroupChannel, DMChannel, or PartialMessageable to this method." ) - if isinstance(channel, (discord.TextChannel, discord.Thread)): + if isinstance(channel, (discord.TextChannel, discord.VoiceChannel, discord.Thread)): channel_id = channel.parent_id if isinstance(channel, discord.Thread) else channel.id if check_permissions and not channel.permissions_for(channel.guild.me).embed_links: @@ -1371,7 +1370,7 @@ class Red( scopes = ("bot", "applications.commands") if commands_scope else ("bot",) perms_int = data["invite_perm"] permissions = discord.Permissions(perms_int) - return discord.utils.oauth_url(self._app_info.id, permissions=permissions, scopes=scopes) + return discord.utils.oauth_url(self.application_id, permissions=permissions, scopes=scopes) async def is_invite_url_public(self) -> bool: """ @@ -1613,7 +1612,6 @@ class Red( cogname: str, /, *, - # DEP-WARN: MISSING is implementation detail guild: Optional[discord.abc.Snowflake] = discord.utils.MISSING, guilds: List[discord.abc.Snowflake] = discord.utils.MISSING, ) -> Optional[commands.Cog]: @@ -1725,7 +1723,6 @@ class Red( /, *, override: bool = False, - # DEP-WARN: MISSING is implementation detail guild: Optional[discord.abc.Snowflake] = discord.utils.MISSING, guilds: List[discord.abc.Snowflake] = discord.utils.MISSING, ) -> None: @@ -1880,7 +1877,7 @@ class Red( async def get_owner_notification_destinations( self, - ) -> List[Union[discord.TextChannel, discord.User]]: + ) -> List[Union[discord.TextChannel, discord.VoiceChannel, discord.User]]: """ Gets the users and channels to send to """ diff --git a/redbot/core/commands/__init__.py b/redbot/core/commands/__init__.py index 187cbd513..09a0e708f 100644 --- a/redbot/core/commands/__init__.py +++ b/redbot/core/commands/__init__.py @@ -45,7 +45,6 @@ from .help import ( ) from .requires import ( CheckPredicate as CheckPredicate, - DM_PERMS as DM_PERMS, GlobalPermissionModel as GlobalPermissionModel, GuildPermissionModel as GuildPermissionModel, PermissionModel as PermissionModel, @@ -189,4 +188,14 @@ from discord.ext.commands import ( bot_has_any_role as bot_has_any_role, before_invoke as before_invoke, after_invoke as after_invoke, + CurrentChannel as CurrentChannel, + Author as Author, + param as param, + MissingRequiredAttachment as MissingRequiredAttachment, + Parameter as Parameter, + ForumChannelConverter as ForumChannelConverter, + CurrentGuild as CurrentGuild, + Range as Range, + RangeError as RangeError, + parameter as parameter, ) diff --git a/redbot/core/commands/context.py b/redbot/core/commands/context.py index d385eff74..02d87239d 100644 --- a/redbot/core/commands/context.py +++ b/redbot/core/commands/context.py @@ -339,7 +339,7 @@ if TYPE_CHECKING or os.getenv("BUILDING_DOCS", False): ... @property - def channel(self) -> Union[discord.TextChannel, discord.Thread]: + def channel(self) -> Union[discord.TextChannel, discord.VoiceChannel, discord.Thread]: ... @property diff --git a/redbot/core/commands/help.py b/redbot/core/commands/help.py index 745ea6606..ad69e1315 100644 --- a/redbot/core/commands/help.py +++ b/redbot/core/commands/help.py @@ -842,12 +842,12 @@ class RedHelpFormatter(HelpFormatterABC): if ( not use_DMs # we're not in DMs and delete_delay > 0 # delete delay is enabled - and ctx.channel.permissions_for(ctx.me).manage_messages # we can manage messages + and ctx.bot_permissions.manage_messages # we can manage messages ): # We need to wrap this in a task to not block after-sending-help interactions. # The channel has to be TextChannel or Thread as we can't bulk-delete from DMs async def _delete_delay_help( - channel: Union[discord.TextChannel, discord.Thread], + channel: Union[discord.TextChannel, discord.VoiceChannel, discord.Thread], messages: List[discord.Message], delay: int, ): diff --git a/redbot/core/commands/requires.py b/redbot/core/commands/requires.py index d64518d83..5ef689536 100644 --- a/redbot/core/commands/requires.py +++ b/redbot/core/commands/requires.py @@ -40,7 +40,6 @@ if TYPE_CHECKING: __all__ = [ "CheckPredicate", - "DM_PERMS", "GlobalPermissionModel", "GuildPermissionModel", "PermissionModel", @@ -75,6 +74,7 @@ GlobalPermissionModel = Union[ discord.User, discord.VoiceChannel, discord.TextChannel, + discord.ForumChannel, discord.CategoryChannel, discord.Role, discord.Guild, @@ -83,6 +83,7 @@ GuildPermissionModel = Union[ discord.Member, discord.VoiceChannel, discord.TextChannel, + discord.ForumChannel, discord.CategoryChannel, discord.Role, discord.Guild, @@ -90,22 +91,6 @@ GuildPermissionModel = Union[ PermissionModel = Union[GlobalPermissionModel, GuildPermissionModel] CheckPredicate = Callable[["Context"], Union[Optional[bool], Awaitable[Optional[bool]]]] -# Here we are trying to model DM permissions as closely as possible. The only -# discrepancy I've found is that users can pin messages, but they cannot delete them. -# This means manage_messages is only half True, so it's left as False. -# This is also the same as the permissions returned when `permissions_for` is used in DM. -DM_PERMS = discord.Permissions.none() -DM_PERMS.update( - add_reactions=True, - attach_files=True, - embed_links=True, - external_emojis=True, - mention_everyone=True, - read_message_history=True, - read_messages=True, - send_messages=True, -) - class PrivilegeLevel(enum.IntEnum): """Enumeration for special privileges.""" @@ -520,15 +505,11 @@ class Requires: return await self._transition_state(ctx) async def _verify_bot(self, ctx: "Context") -> None: - if ctx.guild is None: - bot_user = ctx.bot.user - else: - bot_user = ctx.guild.me - cog = ctx.cog - if cog and await ctx.bot.cog_disabled_in_guild(cog, ctx.guild): - raise discord.ext.commands.DisabledCommand() + cog = ctx.cog + if ctx.guild is not None and cog and await ctx.bot.cog_disabled_in_guild(cog, ctx.guild): + raise discord.ext.commands.DisabledCommand() - bot_perms = ctx.channel.permissions_for(bot_user) + bot_perms = ctx.bot_permissions if not (bot_perms.administrator or bot_perms >= self.bot_perms): raise BotMissingPermissions(missing=self._missing_perms(self.bot_perms, bot_perms)) @@ -574,7 +555,7 @@ class Requires: return False if self.user_perms is not None: - user_perms = ctx.channel.permissions_for(ctx.author) + user_perms = ctx.permissions if user_perms.administrator or user_perms >= self.user_perms: return True @@ -633,17 +614,6 @@ class Requires: return True return await discord.utils.async_all(check(ctx) for check in self.checks) - @staticmethod - def _get_perms_for(ctx: "Context", user: discord.abc.User) -> discord.Permissions: - if ctx.guild is None: - return DM_PERMS - else: - return ctx.channel.permissions_for(user) - - @classmethod - def _get_bot_perms(cls, ctx: "Context") -> discord.Permissions: - return cls._get_perms_for(ctx, ctx.guild.me if ctx.guild else ctx.bot.user) - @staticmethod def _missing_perms( required: discord.Permissions, actual: discord.Permissions @@ -656,13 +626,6 @@ class Requires: relative_complement = required.value & ~actual.value return discord.Permissions(relative_complement) - @staticmethod - def _member_as_user(member: discord.abc.User) -> discord.User: - if isinstance(member, discord.Member): - # noinspection PyProtectedMember - return member._user - return member - def __repr__(self) -> str: return ( f"` - The text, voice, or forum channel to set embed setting for. - `[enabled]` - Whether to use embeds in this channel. Leave blank to reset to default. """ - if isinstance(ctx.channel, discord.Thread): - await ctx.send( - _( - "This setting cannot be set for threads. If you want to set this for" - " the parent channel, send the command in that channel." - ) - ) - return - if enabled is None: - await self.bot._config.channel(ctx.channel).embeds.clear() + await self.bot._config.channel(channel).embeds.clear() await ctx.send(_("Embeds will now fall back to the global setting.")) return - await self.bot._config.channel(ctx.channel).embeds.set(enabled) + await self.bot._config.channel(channel).embeds.set(enabled) await ctx.send( _("Embeds are now {} for this channel.").format( _("enabled") if enabled else _("disabled") @@ -2247,7 +2245,11 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): @modlogset.command(aliases=["channel"], name="modlog") @commands.guild_only() - async def modlogset_modlog(self, ctx: commands.Context, channel: discord.TextChannel = None): + async def modlogset_modlog( + self, + ctx: commands.Context, + channel: Union[discord.TextChannel, discord.VoiceChannel] = None, + ): """Set a channel as the modlog. Omit `[channel]` to disable the modlog. @@ -3131,7 +3133,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): view=SetApiView(default_service=service), ) else: - if ctx.channel.permissions_for(ctx.me).manage_messages: + if ctx.bot_permissions.manage_messages: await ctx.message.delete() await ctx.bot.set_shared_api_tokens(service, **tokens) await ctx.send(_("`{service}` API tokens have been set.").format(service=service)) @@ -3241,7 +3243,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): @_set_ownernotifications.command(name="adddestination") async def _set_ownernotifications_adddestination( - self, ctx: commands.Context, *, channel: discord.TextChannel + self, ctx: commands.Context, *, channel: Union[discord.TextChannel, discord.VoiceChannel] ): """ Adds a destination text channel to receive owner notifications. @@ -3263,7 +3265,10 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): name="removedestination", aliases=["remdestination", "deletedestination", "deldestination"] ) async def _set_ownernotifications_removedestination( - self, ctx: commands.Context, *, channel: Union[discord.TextChannel, int] + self, + ctx: commands.Context, + *, + channel: Union[discord.TextChannel, discord.VoiceChannel, int], ): """ Removes a destination text channel from receiving owner notifications. @@ -4136,8 +4141,11 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): async def diagnoseissues( self, ctx: commands.Context, - channel: Optional[Union[discord.TextChannel, discord.Thread]], - member: Union[discord.Member, discord.User], + channel: Optional[ + Union[discord.TextChannel, discord.VoiceChannel, discord.Thread] + ] = commands.CurrentChannel, + # avoid non-default argument following default argument by using empty param() + member: Union[discord.Member, discord.User] = commands.param(), *, command_name: str, ) -> None: @@ -4155,16 +4163,14 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): - `` - The member that should be considered as the command caller. - `` - The name of the command to test. """ - if channel is None: - channel = ctx.channel - if not isinstance(channel, (discord.TextChannel, discord.Thread)): - await ctx.send( - _( - "The text channel or thread needs to be passed" - " when using this command in DMs." - ) + if ctx.guild is None: + await ctx.send( + _( + "A text channel, voice channel, or thread needs to be passed" + " when using this command in DMs." ) - return + ) + return command = self.bot.get_command(command_name) if command is None: @@ -5124,9 +5130,13 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): async def ignore_channel( self, ctx: commands.Context, - channel: Optional[ - Union[discord.TextChannel, discord.CategoryChannel, discord.Thread] - ] = None, + channel: Union[ + discord.TextChannel, + discord.VoiceChannel, + discord.ForumChannel, + discord.CategoryChannel, + discord.Thread, + ] = commands.CurrentChannel, ): """ Ignore commands in the channel, thread, or category. @@ -5144,8 +5154,6 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): **Arguments:** - `` - The channel to ignore. This can also be a thread or category channel. """ - if not channel: - channel = ctx.channel if not await self.bot._ignored_cache.get_ignored_channel(channel): await self.bot._ignored_cache.set_ignored_channel(channel, True) await ctx.send(_("Channel added to ignore list.")) @@ -5180,9 +5188,13 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): async def unignore_channel( self, ctx: commands.Context, - channel: Optional[ - Union[discord.TextChannel, discord.CategoryChannel, discord.Thread] - ] = None, + channel: Union[ + discord.TextChannel, + discord.VoiceChannel, + discord.ForumChannel, + discord.CategoryChannel, + discord.Thread, + ] = commands.CurrentChannel, ): """ Remove a channel, thread, or category from the ignore list. @@ -5198,9 +5210,6 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): **Arguments:** - `` - The channel to unignore. This can also be a thread or category channel. """ - if not channel: - channel = ctx.channel - if await self.bot._ignored_cache.get_ignored_channel(channel): await self.bot._ignored_cache.set_ignored_channel(channel, False) await ctx.send(_("Channel removed from ignore list.")) @@ -5225,7 +5234,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): async def count_ignored(self, ctx: commands.Context): category_channels: List[discord.CategoryChannel] = [] - text_channels: List[discord.TextChannel] = [] + channels: List[Union[discord.TextChannel, discord.VoiceChannel, discord.ForumChannel]] = [] threads: List[discord.Thread] = [] if await self.bot._ignored_cache.get_ignored_guild(ctx.guild): return _("This server is currently being ignored.") @@ -5234,7 +5243,19 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): if await self.bot._ignored_cache.get_ignored_channel(channel.category): category_channels.append(channel.category) if await self.bot._ignored_cache.get_ignored_channel(channel, check_category=False): - text_channels.append(channel) + channels.append(channel) + for channel in ctx.guild.voice_channels: + if channel.category and channel.category not in category_channels: + if await self.bot._ignored_cache.get_ignored_channel(channel.category): + category_channels.append(channel.category) + if await self.bot._ignored_cache.get_ignored_channel(channel, check_category=False): + channels.append(channel) + for channel in ctx.guild.forum_channels: + if channel.category and channel.category not in category_channels: + if await self.bot._ignored_cache.get_ignored_channel(channel.category): + category_channels.append(channel.category) + if await self.bot._ignored_cache.get_ignored_channel(channel, check_category=False): + channels.append(channel) for thread in ctx.guild.threads: if await self.bot._ignored_cache.get_ignored_channel(thread, check_category=False): threads.append(thread) @@ -5242,9 +5263,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): cat_str = ( humanize_list([c.name for c in category_channels]) if category_channels else _("None") ) - chan_str = ( - humanize_list([c.mention for c in text_channels]) if text_channels else _("None") - ) + chan_str = humanize_list([c.mention for c in channels]) if channels else _("None") thread_str = humanize_list([c.mention for c in threads]) if threads else _("None") msg = _( "Currently ignored categories: {categories}\n" @@ -5255,7 +5274,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): # Removing this command from forks is a violation of the GPLv3 under which it is licensed. # Otherwise interfering with the ability for this command to be accessible is also a violation. - @commands.cooldown(1, 180, lambda msg: (msg.channel.id, msg.author.id)) + @commands.cooldown(1, 180, lambda ctx: (ctx.message.channel.id, ctx.message.author.id)) @commands.command( cls=commands.commands._AlwaysAvailableCommand, name="licenseinfo", diff --git a/redbot/core/events.py b/redbot/core/events.py index 2db71d493..8aaed3e90 100644 --- a/redbot/core/events.py +++ b/redbot/core/events.py @@ -30,7 +30,6 @@ from .utils._internal_utils import ( expected_version, fetch_latest_red_version_info, send_to_owners_with_prefix_replaced, - get_converter, ) from .utils.chat_formatting import inline, bordered, format_perms_list, humanize_timedelta @@ -71,7 +70,7 @@ def init_events(bot, cli_flags): guilds = len(bot.guilds) users = len(set([m for m in bot.get_all_members()])) - invite_url = discord.utils.oauth_url(bot._app_info.id, scopes=("bot",)) + invite_url = discord.utils.oauth_url(bot.application_id, scopes=("bot",)) prefixes = cli_flags.prefix or (await bot._config.prefix()) lang = await bot._config.locale() @@ -222,7 +221,7 @@ def init_events(bot, cli_flags): await ctx.send_help() elif isinstance(error, commands.BadArgument): if isinstance(error.__cause__, ValueError): - converter = get_converter(ctx.current_parameter) + converter = ctx.current_parameter.converter argument = ctx.current_argument if converter is int: await ctx.send(_('"{argument}" is not an integer.').format(argument=argument)) diff --git a/redbot/core/modlog.py b/redbot/core/modlog.py index 4141da78b..d7ad94365 100644 --- a/redbot/core/modlog.py +++ b/redbot/core/modlog.py @@ -349,12 +349,12 @@ class Case: self.message = message @property - def parent_channel(self) -> Optional[discord.TextChannel]: + def parent_channel(self) -> Optional[Union[discord.TextChannel, discord.ForumChannel]]: """ - The parent text channel of the thread in `channel`. + The parent text/forum channel of the thread in `channel`. This will be `None` if `channel` is not a thread - and when the parent text channel is not in cache (probably due to removal). + and when the parent text/forum channel is not in cache (probably due to removal). """ if self.parent_channel_id is None: return None @@ -645,13 +645,18 @@ class Case: @classmethod async def from_json( - cls, mod_channel: discord.TextChannel, bot: Red, case_number: int, data: dict, **kwargs + cls, + mod_channel: Union[discord.TextChannel, discord.VoiceChannel], + bot: Red, + case_number: int, + data: dict, + **kwargs, ): """Get a Case object from the provided information Parameters ---------- - mod_channel: discord.TextChannel + mod_channel: `discord.TextChannel` or `discord.VoiceChannel` The mod log channel for the guild bot: Red The bot's instance. Needed to get the target user @@ -1228,7 +1233,9 @@ async def register_casetypes(new_types: List[dict]) -> List[CaseType]: return type_list -async def get_modlog_channel(guild: discord.Guild) -> discord.TextChannel: +async def get_modlog_channel( + guild: discord.Guild, +) -> Union[discord.TextChannel, discord.VoiceChannel]: """ Get the current modlog channel. @@ -1239,7 +1246,7 @@ async def get_modlog_channel(guild: discord.Guild) -> discord.TextChannel: Returns ------- - `discord.TextChannel` + `discord.TextChannel` or `discord.VoiceChannel` The channel object representing the modlog channel. Raises @@ -1259,7 +1266,7 @@ async def get_modlog_channel(guild: discord.Guild) -> discord.TextChannel: async def set_modlog_channel( - guild: discord.Guild, channel: Union[discord.TextChannel, None] + guild: discord.Guild, channel: Union[discord.TextChannel, discord.VoiceChannel, None] ) -> bool: """ Changes the modlog channel @@ -1268,7 +1275,7 @@ async def set_modlog_channel( ---------- guild: `discord.Guild` The guild to set a mod log channel for - channel: `discord.TextChannel` or `None` + channel: `discord.TextChannel`, `discord.VoiceChannel`, or `None` The channel to be set as modlog channel Returns diff --git a/redbot/core/settings_caches.py b/redbot/core/settings_caches.py index 7f593c0e0..7d3ecd7ae 100644 --- a/redbot/core/settings_caches.py +++ b/redbot/core/settings_caches.py @@ -154,7 +154,11 @@ class IgnoreManager: self._cached_guilds: Dict[int, bool] = {} async def get_ignored_channel( - self, channel: Union[discord.TextChannel, discord.Thread], check_category: bool = True + self, + channel: Union[ + discord.TextChannel, discord.VoiceChannel, discord.ForumChannel, discord.Thread + ], + check_category: bool = True, ) -> bool: ret: bool @@ -181,7 +185,13 @@ class IgnoreManager: async def set_ignored_channel( self, - channel: Union[discord.TextChannel, discord.Thread, discord.CategoryChannel], + channel: Union[ + discord.TextChannel, + discord.VoiceChannel, + discord.Thread, + discord.ForumChannel, + discord.CategoryChannel, + ], set_to: bool, ): cid: int = channel.id diff --git a/redbot/core/utils/__init__.py b/redbot/core/utils/__init__.py index ad5faa41b..179343c9a 100644 --- a/redbot/core/utils/__init__.py +++ b/redbot/core/utils/__init__.py @@ -34,7 +34,9 @@ from discord.utils import maybe_coroutine from redbot.core import commands if TYPE_CHECKING: - GuildMessageable = Union[commands.GuildContext, discord.abc.GuildChannel, discord.Thread] + GuildMessageable = Union[ + commands.GuildContext, discord.TextChannel, discord.VoiceChannel, discord.Thread + ] DMMessageable = Union[commands.DMContext, discord.Member, discord.User, discord.DMChannel] __all__ = ( diff --git a/redbot/core/utils/_internal_utils.py b/redbot/core/utils/_internal_utils.py index 30679e2ab..d6c1ed381 100644 --- a/redbot/core/utils/_internal_utils.py +++ b/redbot/core/utils/_internal_utils.py @@ -32,7 +32,6 @@ from typing import ( import aiohttp import discord import pkg_resources -from discord.ext.commands.converter import get_converter # DEP-WARN from fuzzywuzzy import fuzz, process from rich.progress import ProgressColumn from rich.progress_bar import ProgressBar @@ -60,7 +59,6 @@ __all__ = ( "deprecated_removed", "RichIndefiniteBarColumn", "cli_level_to_log_level", - "get_converter", ) _T = TypeVar("_T") diff --git a/redbot/core/utils/mod.py b/redbot/core/utils/mod.py index a2a3e5756..bef4069c7 100644 --- a/redbot/core/utils/mod.py +++ b/redbot/core/utils/mod.py @@ -10,7 +10,8 @@ if TYPE_CHECKING: async def mass_purge( - messages: List[discord.Message], channel: Union[discord.TextChannel, discord.Thread] + messages: List[discord.Message], + channel: Union[discord.TextChannel, discord.VoiceChannel, discord.Thread], ): """Bulk delete messages from a channel. @@ -26,7 +27,7 @@ async def mass_purge( ---------- messages : `list` of `discord.Message` The messages to bulk delete. - channel : `discord.TextChannel` or `discord.Thread` + channel : `discord.TextChannel`, `discord.VoiceChannel`, or `discord.Thread` The channel to delete messages from. Raises @@ -247,7 +248,7 @@ async def check_permissions(ctx: "Context", perms: Dict[str, bool]) -> bool: return True elif not perms: return False - resolved = ctx.channel.permissions_for(ctx.author) + resolved = ctx.permissions return resolved.administrator or all( getattr(resolved, name, None) == value for name, value in perms.items() diff --git a/redbot/core/utils/predicates.py b/redbot/core/utils/predicates.py index 95393f5d6..11a1d035e 100644 --- a/redbot/core/utils/predicates.py +++ b/redbot/core/utils/predicates.py @@ -67,7 +67,9 @@ class MessagePredicate(Callable[[discord.Message], bool]): def same_context( cls, ctx: Optional[commands.Context] = None, - channel: Optional[Union[discord.TextChannel, discord.Thread, discord.DMChannel]] = None, + channel: Optional[ + Union[discord.TextChannel, discord.VoiceChannel, discord.Thread, discord.DMChannel] + ] = None, user: Optional[discord.abc.User] = None, ) -> "MessagePredicate": """Match if the message fits the described context. @@ -76,7 +78,7 @@ class MessagePredicate(Callable[[discord.Message], bool]): ---------- ctx : Optional[Context] The current invocation context. - channel : Optional[Union[`discord.TextChannel`, `discord.Thread`, `discord.DMChannel`]] + channel : Optional[Union[`discord.TextChannel`, `discord.VoiceChannel`, `discord.Thread`, `discord.DMChannel`]] The channel we expect a message in. If unspecified, defaults to ``ctx.channel``. If ``ctx`` is unspecified too, the message's channel will be ignored. @@ -104,7 +106,9 @@ class MessagePredicate(Callable[[discord.Message], bool]): def cancelled( cls, ctx: Optional[commands.Context] = None, - channel: Optional[Union[discord.TextChannel, discord.Thread, discord.DMChannel]] = None, + channel: Optional[ + Union[discord.TextChannel, discord.VoiceChannel, discord.Thread, discord.DMChannel] + ] = None, user: Optional[discord.abc.User] = None, ) -> "MessagePredicate": """Match if the message is ``[p]cancel``. @@ -113,7 +117,7 @@ class MessagePredicate(Callable[[discord.Message], bool]): ---------- ctx : Optional[Context] Same as ``ctx`` in :meth:`same_context`. - channel : Optional[Union[`discord.TextChannel`, `discord.Thread`, `discord.DMChannel`]] + channel : Optional[Union[`discord.TextChannel`, `discord.VoiceChannel`, `discord.Thread`, `discord.DMChannel`]] Same as ``channel`` in :meth:`same_context`. user : Optional[discord.abc.User] Same as ``user`` in :meth:`same_context`. @@ -133,7 +137,9 @@ class MessagePredicate(Callable[[discord.Message], bool]): def yes_or_no( cls, ctx: Optional[commands.Context] = None, - channel: Optional[Union[discord.TextChannel, discord.Thread, discord.DMChannel]] = None, + channel: Optional[ + Union[discord.TextChannel, discord.VoiceChannel, discord.Thread, discord.DMChannel] + ] = None, user: Optional[discord.abc.User] = None, ) -> "MessagePredicate": """Match if the message is "yes"/"y" or "no"/"n". @@ -145,7 +151,7 @@ class MessagePredicate(Callable[[discord.Message], bool]): ---------- ctx : Optional[Context] Same as ``ctx`` in :meth:`same_context`. - channel : Optional[Union[`discord.TextChannel`, `discord.Thread`, `discord.DMChannel`]] + channel : Optional[Union[`discord.TextChannel`, `discord.VoiceChannel`, `discord.Thread`, `discord.DMChannel`]] Same as ``channel`` in :meth:`same_context`. user : Optional[discord.abc.User] Same as ``user`` in :meth:`same_context`. @@ -176,7 +182,9 @@ class MessagePredicate(Callable[[discord.Message], bool]): def valid_int( cls, ctx: Optional[commands.Context] = None, - channel: Optional[Union[discord.TextChannel, discord.Thread, discord.DMChannel]] = None, + channel: Optional[ + Union[discord.TextChannel, discord.VoiceChannel, discord.Thread, discord.DMChannel] + ] = None, user: Optional[discord.abc.User] = None, ) -> "MessagePredicate": """Match if the response is an integer. @@ -187,7 +195,7 @@ class MessagePredicate(Callable[[discord.Message], bool]): ---------- ctx : Optional[Context] Same as ``ctx`` in :meth:`same_context`. - channel : Optional[Union[`discord.TextChannel`, `discord.Thread`, `discord.DMChannel`]] + channel : Optional[Union[`discord.TextChannel`, `discord.VoiceChannel`, `discord.Thread`, `discord.DMChannel`]] Same as ``channel`` in :meth:`same_context`. user : Optional[discord.abc.User] Same as ``user`` in :meth:`same_context`. @@ -216,7 +224,9 @@ class MessagePredicate(Callable[[discord.Message], bool]): def valid_float( cls, ctx: Optional[commands.Context] = None, - channel: Optional[Union[discord.TextChannel, discord.Thread, discord.DMChannel]] = None, + channel: Optional[ + Union[discord.TextChannel, discord.VoiceChannel, discord.Thread, discord.DMChannel] + ] = None, user: Optional[discord.abc.User] = None, ) -> "MessagePredicate": """Match if the response is a float. @@ -227,7 +237,7 @@ class MessagePredicate(Callable[[discord.Message], bool]): ---------- ctx : Optional[Context] Same as ``ctx`` in :meth:`same_context`. - channel : Optional[Union[`discord.TextChannel`, `discord.Thread`, `discord.DMChannel`]] + channel : Optional[Union[`discord.TextChannel`, `discord.VoiceChannel`, `discord.Thread`, `discord.DMChannel`]] Same as ``channel`` in :meth:`same_context`. user : Optional[discord.abc.User] Same as ``user`` in :meth:`same_context`. @@ -256,7 +266,9 @@ class MessagePredicate(Callable[[discord.Message], bool]): def positive( cls, ctx: Optional[commands.Context] = None, - channel: Optional[Union[discord.TextChannel, discord.Thread, discord.DMChannel]] = None, + channel: Optional[ + Union[discord.TextChannel, discord.VoiceChannel, discord.Thread, discord.DMChannel] + ] = None, user: Optional[discord.abc.User] = None, ) -> "MessagePredicate": """Match if the response is a positive number. @@ -267,7 +279,7 @@ class MessagePredicate(Callable[[discord.Message], bool]): ---------- ctx : Optional[Context] Same as ``ctx`` in :meth:`same_context`. - channel : Optional[Union[`discord.TextChannel`, `discord.Thread`, `discord.DMChannel`]] + channel : Optional[Union[`discord.TextChannel`, `discord.VoiceChannel`, `discord.Thread`, `discord.DMChannel`]] Same as ``channel`` in :meth:`same_context`. user : Optional[discord.abc.User] Same as ``user`` in :meth:`same_context`. @@ -300,7 +312,7 @@ class MessagePredicate(Callable[[discord.Message], bool]): def valid_role( cls, ctx: Optional[commands.Context] = None, - channel: Optional[Union[discord.TextChannel, discord.Thread]] = None, + channel: Optional[Union[discord.TextChannel, discord.VoiceChannel, discord.Thread]] = None, user: Optional[discord.abc.User] = None, ) -> "MessagePredicate": """Match if the response refers to a role in the current guild. @@ -313,7 +325,7 @@ class MessagePredicate(Callable[[discord.Message], bool]): ---------- ctx : Optional[Context] Same as ``ctx`` in :meth:`same_context`. - channel : Optional[Union[`discord.TextChannel`, `discord.Thread`]] + channel : Optional[Union[`discord.TextChannel`, `discord.VoiceChannel`, `discord.Thread`]] Same as ``channel`` in :meth:`same_context`. user : Optional[discord.abc.User] Same as ``user`` in :meth:`same_context`. @@ -344,7 +356,7 @@ class MessagePredicate(Callable[[discord.Message], bool]): def valid_member( cls, ctx: Optional[commands.Context] = None, - channel: Optional[Union[discord.TextChannel, discord.Thread]] = None, + channel: Optional[Union[discord.TextChannel, discord.VoiceChannel, discord.Thread]] = None, user: Optional[discord.abc.User] = None, ) -> "MessagePredicate": """Match if the response refers to a member in the current guild. @@ -357,7 +369,7 @@ class MessagePredicate(Callable[[discord.Message], bool]): ---------- ctx : Optional[Context] Same as ``ctx`` in :meth:`same_context`. - channel : Optional[Union[`discord.TextChannel`, `discord.Thread`]] + channel : Optional[Union[`discord.TextChannel`, `discord.VoiceChannel`, `discord.Thread`]] Same as ``channel`` in :meth:`same_context`. user : Optional[discord.abc.User] Same as ``user`` in :meth:`same_context`. @@ -392,7 +404,7 @@ class MessagePredicate(Callable[[discord.Message], bool]): def valid_text_channel( cls, ctx: Optional[commands.Context] = None, - channel: Optional[Union[discord.TextChannel, discord.Thread]] = None, + channel: Optional[Union[discord.TextChannel, discord.VoiceChannel, discord.Thread]] = None, user: Optional[discord.abc.User] = None, ) -> "MessagePredicate": """Match if the response refers to a text channel in the current guild. @@ -405,7 +417,7 @@ class MessagePredicate(Callable[[discord.Message], bool]): ---------- ctx : Optional[Context] Same as ``ctx`` in :meth:`same_context`. - channel : Optional[Union[`discord.TextChannel`, `discord.Thread`]] + channel : Optional[Union[`discord.TextChannel`, `discord.VoiceChannel`, `discord.Thread`]] Same as ``channel`` in :meth:`same_context`. user : Optional[discord.abc.User] Same as ``user`` in :meth:`same_context`. @@ -440,7 +452,7 @@ class MessagePredicate(Callable[[discord.Message], bool]): def has_role( cls, ctx: Optional[commands.Context] = None, - channel: Optional[Union[discord.TextChannel, discord.Thread]] = None, + channel: Optional[Union[discord.TextChannel, discord.VoiceChannel, discord.Thread]] = None, user: Optional[discord.abc.User] = None, ) -> "MessagePredicate": """Match if the response refers to a role which the author has. @@ -454,7 +466,7 @@ class MessagePredicate(Callable[[discord.Message], bool]): ---------- ctx : Optional[Context] Same as ``ctx`` in :meth:`same_context`. - channel : Optional[Union[`discord.TextChannel`, `discord.Thread`]] + channel : Optional[Union[`discord.TextChannel`, `discord.VoiceChannel`, `discord.Thread`]] Same as ``channel`` in :meth:`same_context`. user : Optional[discord.abc.User] Same as ``user`` in :meth:`same_context`. @@ -479,7 +491,7 @@ class MessagePredicate(Callable[[discord.Message], bool]): return False role = self._find_role(guild, m.content) - if role is None or role not in user.roles: + if role is None or user.get_role(role.id) is None: return False self.result = role @@ -492,7 +504,9 @@ class MessagePredicate(Callable[[discord.Message], bool]): cls, value: str, ctx: Optional[commands.Context] = None, - channel: Optional[Union[discord.TextChannel, discord.Thread, discord.DMChannel]] = None, + channel: Optional[ + Union[discord.TextChannel, discord.VoiceChannel, discord.Thread, discord.DMChannel] + ] = None, user: Optional[discord.abc.User] = None, ) -> "MessagePredicate": """Match if the response is equal to the specified value. @@ -503,7 +517,7 @@ class MessagePredicate(Callable[[discord.Message], bool]): The value to compare the response with. ctx : Optional[Context] Same as ``ctx`` in :meth:`same_context`. - channel : Optional[Union[`discord.TextChannel`, `discord.Thread`, `discord.DMChannel`]] + channel : Optional[Union[`discord.TextChannel`, `discord.VoiceChannel`, `discord.Thread`, `discord.DMChannel`]] Same as ``channel`` in :meth:`same_context`. user : Optional[discord.abc.User] Same as ``user`` in :meth:`same_context`. @@ -522,7 +536,9 @@ class MessagePredicate(Callable[[discord.Message], bool]): cls, value: str, ctx: Optional[commands.Context] = None, - channel: Optional[Union[discord.TextChannel, discord.Thread, discord.DMChannel]] = None, + channel: Optional[ + Union[discord.TextChannel, discord.VoiceChannel, discord.Thread, discord.DMChannel] + ] = None, user: Optional[discord.abc.User] = None, ) -> "MessagePredicate": """Match if the response *as lowercase* is equal to the specified value. @@ -533,7 +549,7 @@ class MessagePredicate(Callable[[discord.Message], bool]): The value to compare the response with. ctx : Optional[Context] Same as ``ctx`` in :meth:`same_context`. - channel : Optional[Union[`discord.TextChannel`, `discord.Thread`, `discord.DMChannel`]] + channel : Optional[Union[`discord.TextChannel`, `discord.VoiceChannel`, `discord.Thread`, `discord.DMChannel`]] Same as ``channel`` in :meth:`same_context`. user : Optional[discord.abc.User] Same as ``user`` in :meth:`same_context`. @@ -552,7 +568,9 @@ class MessagePredicate(Callable[[discord.Message], bool]): cls, value: Union[int, float], ctx: Optional[commands.Context] = None, - channel: Optional[Union[discord.TextChannel, discord.Thread, discord.DMChannel]] = None, + channel: Optional[ + Union[discord.TextChannel, discord.VoiceChannel, discord.Thread, discord.DMChannel] + ] = None, user: Optional[discord.abc.User] = None, ) -> "MessagePredicate": """Match if the response is less than the specified value. @@ -563,7 +581,7 @@ class MessagePredicate(Callable[[discord.Message], bool]): The value to compare the response with. ctx : Optional[Context] Same as ``ctx`` in :meth:`same_context`. - channel : Optional[Union[`discord.TextChannel`, `discord.Thread`, `discord.DMChannel`]] + channel : Optional[Union[`discord.TextChannel`, `discord.VoiceChannel`, `discord.Thread`, `discord.DMChannel`]] Same as ``channel`` in :meth:`same_context`. user : Optional[discord.abc.User] Same as ``user`` in :meth:`same_context`. @@ -583,7 +601,9 @@ class MessagePredicate(Callable[[discord.Message], bool]): cls, value: Union[int, float], ctx: Optional[commands.Context] = None, - channel: Optional[Union[discord.TextChannel, discord.Thread, discord.DMChannel]] = None, + channel: Optional[ + Union[discord.TextChannel, discord.VoiceChannel, discord.Thread, discord.DMChannel] + ] = None, user: Optional[discord.abc.User] = None, ) -> "MessagePredicate": """Match if the response is greater than the specified value. @@ -594,7 +614,7 @@ class MessagePredicate(Callable[[discord.Message], bool]): The value to compare the response with. ctx : Optional[Context] Same as ``ctx`` in :meth:`same_context`. - channel : Optional[Union[`discord.TextChannel`, `discord.Thread`, `discord.DMChannel`]] + channel : Optional[Union[`discord.TextChannel`, `discord.VoiceChannel`, `discord.Thread`, `discord.DMChannel`]] Same as ``channel`` in :meth:`same_context`. user : Optional[discord.abc.User] Same as ``user`` in :meth:`same_context`. @@ -614,7 +634,9 @@ class MessagePredicate(Callable[[discord.Message], bool]): cls, length: int, ctx: Optional[commands.Context] = None, - channel: Optional[Union[discord.TextChannel, discord.Thread, discord.DMChannel]] = None, + channel: Optional[ + Union[discord.TextChannel, discord.VoiceChannel, discord.Thread, discord.DMChannel] + ] = None, user: Optional[discord.abc.User] = None, ) -> "MessagePredicate": """Match if the response's length is less than the specified length. @@ -625,7 +647,7 @@ class MessagePredicate(Callable[[discord.Message], bool]): The value to compare the response's length with. ctx : Optional[Context] Same as ``ctx`` in :meth:`same_context`. - channel : Optional[Union[`discord.TextChannel`, `discord.Thread`, `discord.DMChannel`]] + channel : Optional[Union[`discord.TextChannel`, `discord.VoiceChannel`, `discord.Thread`, `discord.DMChannel`]] Same as ``channel`` in :meth:`same_context`. user : Optional[discord.abc.User] Same as ``user`` in :meth:`same_context`. @@ -644,7 +666,9 @@ class MessagePredicate(Callable[[discord.Message], bool]): cls, length: int, ctx: Optional[commands.Context] = None, - channel: Optional[Union[discord.TextChannel, discord.Thread, discord.DMChannel]] = None, + channel: Optional[ + Union[discord.TextChannel, discord.VoiceChannel, discord.Thread, discord.DMChannel] + ] = None, user: Optional[discord.abc.User] = None, ) -> "MessagePredicate": """Match if the response's length is greater than the specified length. @@ -655,7 +679,7 @@ class MessagePredicate(Callable[[discord.Message], bool]): The value to compare the response's length with. ctx : Optional[Context] Same as ``ctx`` in :meth:`same_context`. - channel : Optional[Union[`discord.TextChannel`, `discord.Thread`, `discord.DMChannel`]] + channel : Optional[Union[`discord.TextChannel`, `discord.VoiceChannel`, `discord.Thread`, `discord.DMChannel`]] Same as ``channel`` in :meth:`same_context`. user : Optional[discord.abc.User] Same as ``user`` in :meth:`same_context`. @@ -674,7 +698,9 @@ class MessagePredicate(Callable[[discord.Message], bool]): cls, collection: Sequence[str], ctx: Optional[commands.Context] = None, - channel: Optional[Union[discord.TextChannel, discord.Thread, discord.DMChannel]] = None, + channel: Optional[ + Union[discord.TextChannel, discord.VoiceChannel, discord.Thread, discord.DMChannel] + ] = None, user: Optional[discord.abc.User] = None, ) -> "MessagePredicate": """Match if the response is contained in the specified collection. @@ -688,7 +714,7 @@ class MessagePredicate(Callable[[discord.Message], bool]): The collection containing valid responses. ctx : Optional[Context] Same as ``ctx`` in :meth:`same_context`. - channel : Optional[Union[`discord.TextChannel`, `discord.Thread`, `discord.DMChannel`]] + channel : Optional[Union[`discord.TextChannel`, `discord.VoiceChannel`, `discord.Thread`, `discord.DMChannel`]] Same as ``channel`` in :meth:`same_context`. user : Optional[discord.abc.User] Same as ``user`` in :meth:`same_context`. @@ -718,7 +744,9 @@ class MessagePredicate(Callable[[discord.Message], bool]): cls, collection: Sequence[str], ctx: Optional[commands.Context] = None, - channel: Optional[Union[discord.TextChannel, discord.Thread, discord.DMChannel]] = None, + channel: Optional[ + Union[discord.TextChannel, discord.VoiceChannel, discord.Thread, discord.DMChannel] + ] = None, user: Optional[discord.abc.User] = None, ) -> "MessagePredicate": """Same as :meth:`contained_in`, but the response is set to lowercase before matching. @@ -729,7 +757,7 @@ class MessagePredicate(Callable[[discord.Message], bool]): The collection containing valid lowercase responses. ctx : Optional[Context] Same as ``ctx`` in :meth:`same_context`. - channel : Optional[Union[`discord.TextChannel`, `discord.Thread`, `discord.DMChannel`]] + channel : Optional[Union[`discord.TextChannel`, `discord.VoiceChannel`, `discord.Thread`, `discord.DMChannel`]] Same as ``channel`` in :meth:`same_context`. user : Optional[discord.abc.User] Same as ``user`` in :meth:`same_context`. @@ -759,7 +787,9 @@ class MessagePredicate(Callable[[discord.Message], bool]): cls, pattern: Union[Pattern[str], str], ctx: Optional[commands.Context] = None, - channel: Optional[Union[discord.TextChannel, discord.Thread, discord.DMChannel]] = None, + channel: Optional[ + Union[discord.TextChannel, discord.VoiceChannel, discord.Thread, discord.DMChannel] + ] = None, user: Optional[discord.abc.User] = None, ) -> "MessagePredicate": """Match if the response matches the specified regex pattern. @@ -774,7 +804,7 @@ class MessagePredicate(Callable[[discord.Message], bool]): The pattern to search for in the response. ctx : Optional[Context] Same as ``ctx`` in :meth:`same_context`. - channel : Optional[Union[`discord.TextChannel`, `discord.Thread`, `discord.DMChannel`]] + channel : Optional[Union[`discord.TextChannel`, `discord.VoiceChannel`, `discord.Thread`, `discord.DMChannel`]] Same as ``channel`` in :meth:`same_context`. user : Optional[discord.abc.User] Same as ``user`` in :meth:`same_context`. @@ -816,7 +846,7 @@ class MessagePredicate(Callable[[discord.Message], bool]): @staticmethod def _get_guild( ctx: Optional[commands.Context], - channel: Optional[Union[discord.TextChannel, discord.Thread]], + channel: Optional[Union[discord.TextChannel, discord.VoiceChannel, discord.Thread]], user: Optional[discord.Member], ) -> discord.Guild: if ctx is not None: diff --git a/redbot/core/utils/tunnel.py b/redbot/core/utils/tunnel.py index 028afd384..9e8c2f7c4 100644 --- a/redbot/core/utils/tunnel.py +++ b/redbot/core/utils/tunnel.py @@ -57,7 +57,7 @@ class Tunnel(metaclass=TunnelMeta): ---------- sender: `discord.Member` The person who opened the tunnel - origin: `discord.TextChannel` or `discord.Thread` + origin: `discord.TextChannel`, `discord.VoiceChannel`, or `discord.Thread` The channel in which it was opened recipient: `discord.User` The user on the other end of the tunnel @@ -67,7 +67,7 @@ class Tunnel(metaclass=TunnelMeta): self, *, sender: discord.Member, - origin: Union[discord.TextChannel, discord.Thread], + origin: Union[discord.TextChannel, discord.VoiceChannel, discord.Thread], recipient: discord.User, ): self.sender = sender @@ -164,14 +164,14 @@ class Tunnel(metaclass=TunnelMeta): if images_only and a.height is None: # if this is None, it's not an image continue - _fp = io.BytesIO() try: - await a.save(_fp, use_cached=use_cached) + file = await a.to_file() except discord.HTTPException as e: # this is required, because animated webp files aren't cached if not (e.status == 415 and images_only and use_cached): raise - files.append(discord.File(_fp, filename=a.filename)) + else: + files.append(file) return files # Backwards-compatible typo fix (GH-2496) diff --git a/redbot/logging.py b/redbot/logging.py index e52c18b4f..5b5ef5991 100644 --- a/redbot/logging.py +++ b/redbot/logging.py @@ -282,12 +282,10 @@ class RedRichHandler(RichHandler): def init_logging(level: int, location: pathlib.Path, cli_flags: argparse.Namespace) -> None: root_logger = logging.getLogger() - base_logger = logging.getLogger("red") - base_logger.setLevel(level) + root_logger.setLevel(level) + # DEBUG logging for discord.py is a bit too ridiculous :) dpy_logger = logging.getLogger("discord") - dpy_logger.setLevel(logging.WARNING) - warnings_logger = logging.getLogger("py.warnings") - warnings_logger.setLevel(logging.WARNING) + dpy_logger.setLevel(logging.INFO) rich_console = rich.get_console() rich.reconfigure(tab_size=4) diff --git a/setup.cfg b/setup.cfg index 935806314..7bb1233f9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -51,7 +51,7 @@ install_requires = colorama==0.4.4 commonmark==0.9.1 contextlib2==21.6.0 - discord.py @ git+https://github.com/Rapptz/discord.py@3d914e08e0c7df370987592affb3655d2a12f7d1#egg=discord.py + discord.py==2.0.1 distro==1.6.0; sys_platform == "linux" fuzzywuzzy==0.18.0 idna==3.2 @@ -66,7 +66,7 @@ install_requires = pytz==2021.1 PyYAML==5.4.1 Red-Commons==1.0.0 - Red-Lavalink==0.11.0rc0 + Red-Lavalink==0.11.0rc1 rich==10.9.0 schema==0.7.4 six==1.16.0 diff --git a/tests/core/test_commands.py b/tests/core/test_commands.py index 229d482c2..62d45c723 100644 --- a/tests/core/test_commands.py +++ b/tests/core/test_commands.py @@ -50,8 +50,21 @@ def test_dpy_commands_reexports(): dpy_attrs.add(attr_name) missing_attrs = dpy_attrs - set(commands.__dict__.keys()) + # temporarily ignore things related to app commands as the work on that is done separately + missing_attrs -= { + "GroupCog", + "HybridGroup", + "hybrid_group", + "hybrid_command", + "HybridCommand", + "HybridCommandError", + } - assert not missing_attrs + if missing_attrs: + pytest.fail( + "redbot.core.commands is missing these names from discord.ext.commands: " + + ", ".join(missing_attrs) + ) def test_converter_timedelta():