discord.py 2.0 update (3d914e08->2.0.1) (#5709)

This commit is contained in:
Jakub Kuczys 2022-10-03 16:07:15 +02:00 committed by GitHub
parent d7d6ab46f4
commit f02528378f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 454 additions and 317 deletions

View File

@ -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: For each of those, the first rule pertaining to one of the following models will be used:
1. User 1. User
2. Voice channel 2. Voice channel a user is connected to
3. Text channel (parent text channel in case of invocations in threads) 3. The channel command was issued in (parent channel in case of invocations in threads)
4. Channel category 4. Channel category
5. Roles, highest to lowest 5. Roles, highest to lowest
6. Server (can only be in global rules) 6. Server (can only be in global rules)

View File

@ -21,9 +21,7 @@ Basic Usage
class MyCog(commands.Cog): class MyCog(commands.Cog):
@commands.command() @commands.command()
async def balance(self, ctx, user: discord.Member = None): async def balance(self, ctx, user: discord.Member = commands.Author):
if user is None:
user = ctx.author
bal = await bank.get_balance(user) bal = await bank.get_balance(user)
currency = await bank.get_currency_name(ctx.guild) currency = await bank.get_currency_name(ctx.guild)
await ctx.send( await ctx.send(

View File

@ -1,6 +1,6 @@
import asyncio import asyncio
import logging import logging
from typing import Tuple from typing import Tuple, Union
import discord import discord
from redbot.core import Config, checks, commands from redbot.core import Config, checks, commands
@ -153,7 +153,7 @@ class Admin(commands.Cog):
async def _addrole( async def _addrole(
self, ctx: commands.Context, member: discord.Member, role: discord.Role, *, check_user=True 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( await ctx.send(
_("{member.display_name} already has the role {role.name}.").format( _("{member.display_name} already has the role {role.name}.").format(
role=role, member=member role=role, member=member
@ -183,7 +183,7 @@ class Admin(commands.Cog):
async def _removerole( async def _removerole(
self, ctx: commands.Context, member: discord.Member, role: discord.Role, *, check_user=True 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( await ctx.send(
_("{member.display_name} does not have the role {role.name}.").format( _("{member.display_name} does not have the role {role.name}.").format(
role=role, member=member role=role, member=member
@ -214,7 +214,11 @@ class Admin(commands.Cog):
@commands.guild_only() @commands.guild_only()
@checks.admin_or_permissions(manage_roles=True) @checks.admin_or_permissions(manage_roles=True)
async def addrole( 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. Add a role to a user.
@ -222,15 +226,17 @@ class Admin(commands.Cog):
Use double quotes if the role contains spaces. Use double quotes if the role contains spaces.
If user is left blank it defaults to the author of the command. 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) await self._addrole(ctx, user, rolename)
@commands.command() @commands.command()
@commands.guild_only() @commands.guild_only()
@checks.admin_or_permissions(manage_roles=True) @checks.admin_or_permissions(manage_roles=True)
async def removerole( 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. Remove a role from a user.
@ -238,8 +244,6 @@ class Admin(commands.Cog):
Use double quotes if the role contains spaces. Use double quotes if the role contains spaces.
If user is left blank it defaults to the author of the command. 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) await self._removerole(ctx, user, rolename)
@commands.group() @commands.group()
@ -349,7 +353,9 @@ class Admin(commands.Cog):
pass pass
@announceset.command(name="channel") @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.""" """Change the channel where the bot will send announcements."""
await self.config.guild(ctx.guild).announce_channel.set(channel.id) await self.config.guild(ctx.guild).announce_channel.set(channel.id)
await ctx.send( await ctx.send(
@ -389,7 +395,7 @@ class Admin(commands.Cog):
Server admins must have configured the role as user settable. Server admins must have configured the role as user settable.
NOTE: The role is case sensitive! 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) return await self._removerole(ctx, ctx.author, selfrole, check_user=False)
else: else:
return await self._addrole(ctx, ctx.author, selfrole, check_user=False) return await self._addrole(ctx, ctx.author, selfrole, check_user=False)

View File

@ -196,7 +196,9 @@ class MixinMeta(ABC):
async def is_query_allowed( async def is_query_allowed(
self, self,
config: Config, 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: str,
query_obj: Query, query_obj: Query,
) -> bool: ) -> bool:
@ -250,7 +252,9 @@ class MixinMeta(ABC):
raise NotImplementedError() raise NotImplementedError()
@abstractmethod @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() raise NotImplementedError()
@abstractmethod @abstractmethod

View File

@ -657,7 +657,7 @@ class PlayerControllerCommands(MixinMeta, metaclass=CompositeMetaClass):
if not self._player_check(ctx): if not self._player_check(ctx):
player = await lavalink.connect( player = await lavalink.connect(
ctx.author.voice.channel, 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) player.store("notify_channel", ctx.channel.id)
else: else:
@ -675,7 +675,7 @@ class PlayerControllerCommands(MixinMeta, metaclass=CompositeMetaClass):
) )
await player.move_to( await player.move_to(
ctx.author.voice.channel, 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() await ctx.tick()
except AttributeError: except AttributeError:

View File

@ -85,7 +85,7 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
) )
await lavalink.connect( await lavalink.connect(
ctx.author.voice.channel, 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: except AttributeError:
return await self.send_embed_msg( return await self.send_embed_msg(
@ -193,7 +193,7 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
) )
await lavalink.connect( await lavalink.connect(
ctx.author.voice.channel, 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: except AttributeError:
return await self.send_embed_msg( return await self.send_embed_msg(
@ -456,7 +456,7 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
) )
await lavalink.connect( await lavalink.connect(
ctx.author.voice.channel, 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: except AttributeError:
return await self.send_embed_msg( return await self.send_embed_msg(
@ -572,7 +572,7 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
) )
await lavalink.connect( await lavalink.connect(
ctx.author.voice.channel, 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: except AttributeError:
return await self.send_embed_msg( return await self.send_embed_msg(
@ -697,7 +697,7 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
) )
await lavalink.connect( await lavalink.connect(
ctx.author.voice.channel, 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: except AttributeError:
return await self.send_embed_msg( return await self.send_embed_msg(

View File

@ -344,7 +344,7 @@ class QueueCommands(MixinMeta, metaclass=CompositeMetaClass):
) )
player = await lavalink.connect( player = await lavalink.connect(
ctx.author.voice.channel, 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) player.store("notify_channel", ctx.channel.id)
except AttributeError: except AttributeError:

View File

@ -62,7 +62,7 @@ HUMANIZED_PERM = {
"manage_roles": _("Manage Roles"), "manage_roles": _("Manage Roles"),
"manage_webhooks": _("Manage Webhooks"), "manage_webhooks": _("Manage Webhooks"),
"manage_emojis": _("Manage Emojis"), "manage_emojis": _("Manage Emojis"),
"use_slash_commands": _("Use Slash Commands"), "use_application_commands": _("Use Application Commands"),
"request_to_speak": _("Request to Speak"), "request_to_speak": _("Request to Speak"),
"manage_events": _("Manage Events"), "manage_events": _("Manage Events"),
"manage_threads": _("Manage Threads"), "manage_threads": _("Manage Threads"),
@ -187,7 +187,7 @@ class DpyEvents(MixinMeta, metaclass=CompositeMetaClass):
) )
surpass_ignore = ( 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_owner(ctx.author)
or await ctx.bot.is_admin(ctx.author) or await ctx.bot.is_admin(ctx.author)
) )
@ -210,7 +210,7 @@ class DpyEvents(MixinMeta, metaclass=CompositeMetaClass):
) )
raise CheckFailure(message=text) 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): if guild and not current_perms.is_superset(self.permission_cache):
current_perms_set = set(iter(current_perms)) current_perms_set = set(iter(current_perms))
expected_perms_set = set(iter(self.permission_cache)) expected_perms_set = set(iter(self.permission_cache))

View File

@ -90,7 +90,11 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
self._ws_resume[guild_id].set() self._ws_resume[guild_id].set()
await self._websocket_closed_handler( 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: except Exception as exc:
log.debug( log.debug(
@ -335,7 +339,7 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
guild: discord.Guild, guild: discord.Guild,
player: lavalink.Player, player: lavalink.Player,
extra: Dict, extra: Dict,
deafen: bool, self_deaf: bool,
disconnect: bool, disconnect: bool,
) -> None: ) -> None:
guild_id = guild.id guild_id = guild.id
@ -415,7 +419,7 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
if has_perm and player.current and player.is_playing: if has_perm and player.current and player.is_playing:
player.store("resumes", player.fetch("resumes", 0) + 1) 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) await player.resume(player.current, start=player.position, replace=True)
ws_audio_log.info( ws_audio_log.info(
"Voice websocket reconnected Reason: Error code %s & Currently playing", "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: elif has_perm and player.paused and player.current:
player.store("resumes", player.fetch("resumes", 0) + 1) player.store("resumes", player.fetch("resumes", 0) + 1)
await player.connect(deafen=deafen) await player.connect(self_deaf=self_deaf)
await player.resume( await player.resume(
player.current, start=player.position, replace=True, pause=True 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): elif has_perm and (not disconnect) and (not player.is_playing):
player.store("resumes", player.fetch("resumes", 0) + 1) player.store("resumes", player.fetch("resumes", 0) + 1)
await player.connect(deafen=deafen) await player.connect(self_deaf=self_deaf)
ws_audio_log.info( ws_audio_log.info(
"Voice websocket reconnected " "Voice websocket reconnected "
"Reason: Error code %s & Not playing, but auto disconnect disabled", "Reason: Error code %s & Not playing, but auto disconnect disabled",
@ -497,7 +501,7 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
).currently_auto_playing_in.set([]) ).currently_auto_playing_in.set([])
elif code in (42069,) and has_perm and player.current and player.is_playing: elif code in (42069,) and has_perm and player.current and player.is_playing:
player.store("resumes", player.fetch("resumes", 0) + 1) 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) 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.info("Player resumed - Reason: Error code %s & %s", code, reason)
ws_audio_log.debug( ws_audio_log.debug(
@ -514,7 +518,7 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
) )
await asyncio.sleep(delay) await asyncio.sleep(delay)
if has_perm and player.current and player.is_playing: 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) await player.resume(player.current, start=player.position, replace=True)
ws_audio_log.info( ws_audio_log.info(
"Voice websocket reconnected Reason: Error code %s & Player is active", "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: elif has_perm and player.paused and player.current:
player.store("resumes", player.fetch("resumes", 0) + 1) player.store("resumes", player.fetch("resumes", 0) + 1)
await player.connect(deafen=deafen) await player.connect(self_deaf=self_deaf)
await player.resume( await player.resume(
player.current, start=player.position, replace=True, pause=True 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): elif has_perm and (not disconnect) and (not player.is_playing):
player.store("resumes", player.fetch("resumes", 0) + 1) player.store("resumes", player.fetch("resumes", 0) + 1)
await player.connect(deafen=deafen) await player.connect(self_deaf=self_deaf)
ws_audio_log.info( ws_audio_log.info(
"Voice websocket reconnected " "Voice websocket reconnected "
"to channel %s in guild: %s | " "to channel %s in guild: %s | "

View File

@ -138,7 +138,7 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
if not (perms.connect and perms.speak): if not (perms.connect and perms.speak):
vc = None vc = None
break 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) player.store("notify_channel", notify_channel_id)
break break
except NodeNotFound: except NodeNotFound:
@ -222,7 +222,7 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
if not (perms.connect and perms.speak): if not (perms.connect and perms.speak):
vc = None vc = None
break 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) player.store("notify_channel", notify_channel_id)
break break
except NodeNotFound: except NodeNotFound:

View File

@ -100,7 +100,7 @@ class FormattingUtilities(MixinMeta, metaclass=CompositeMetaClass):
try: try:
await lavalink.connect( await lavalink.connect(
ctx.author.voice.channel, 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: except AttributeError:
return await self.send_embed_msg(ctx, title=_("Connect to a voice channel first.")) return await self.send_embed_msg(ctx, title=_("Connect to a voice channel first."))

View File

@ -99,7 +99,9 @@ class MiscellaneousUtilities(MixinMeta, metaclass=CompositeMetaClass):
embed.set_author(name=name) embed.set_author(name=name)
return await ctx.send(embed=embed) 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) perms = channel.permissions_for(channel.guild.me)
return all((can_user_send_messages_in(channel.guild.me, channel), perms.embed_links)) return all((can_user_send_messages_in(channel.guild.me, channel), perms.embed_links))

View File

@ -114,8 +114,7 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
dj_role = self._dj_role_cache.setdefault( dj_role = self._dj_role_cache.setdefault(
ctx.guild.id, await self.config.guild(ctx.guild).dj_role() ctx.guild.id, await self.config.guild(ctx.guild).dj_role()
) )
dj_role_obj = ctx.guild.get_role(dj_role) return member.get_role(dj_role) is not None
return dj_role_obj in ctx.guild.get_member(member.id).roles
async def is_requester(self, ctx: commands.Context, member: discord.Member) -> bool: async def is_requester(self, ctx: commands.Context, member: discord.Member) -> bool:
try: try:
@ -711,7 +710,7 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
): ):
await player.move_to( await player.move_to(
user_channel, 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 return True
else: else:

View File

@ -545,7 +545,7 @@ class PlaylistUtilities(MixinMeta, metaclass=CompositeMetaClass):
return False return False
await lavalink.connect( await lavalink.connect(
ctx.author.voice.channel, 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: except NodeNotFound:
await self.send_embed_msg( await self.send_embed_msg(

View File

@ -60,7 +60,9 @@ class ValidationUtilities(MixinMeta, metaclass=CompositeMetaClass):
async def is_query_allowed( async def is_query_allowed(
self, self,
config: Config, 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: str,
query_obj: Query, query_obj: Query,
) -> bool: ) -> bool:

View File

@ -75,7 +75,9 @@ class Cleanup(commands.Cog):
@staticmethod @staticmethod
async def get_messages_for_deletion( 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, number: Optional[PositiveInt] = None,
check: Callable[[discord.Message], bool] = lambda x: True, check: Callable[[discord.Message], bool] = lambda x: True,
limit: Optional[PositiveInt] = None, limit: Optional[PositiveInt] = None,
@ -129,7 +131,9 @@ class Cleanup(commands.Cog):
async def send_optional_notification( async def send_optional_notification(
self, self,
num: int, num: int,
channel: Union[discord.TextChannel, discord.DMChannel, discord.Thread], channel: Union[
discord.TextChannel, discord.VoiceChannel, discord.DMChannel, discord.Thread
],
*, *,
subtract_invoking: bool = False, subtract_invoking: bool = False,
) -> None: ) -> None:
@ -149,7 +153,8 @@ class Cleanup(commands.Cog):
@staticmethod @staticmethod
async def get_message_from_reference( 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]: ) -> Optional[discord.Message]:
message = None message = None
resolved = reference.resolved resolved = reference.resolved
@ -621,7 +626,7 @@ class Cleanup(commands.Cog):
can_mass_purge = False can_mass_purge = False
if type(author) is discord.Member: if type(author) is discord.Member:
me = ctx.guild.me me = ctx.guild.me
can_mass_purge = channel.permissions_for(me).manage_messages can_mass_purge = ctx.bot_permissions.manage_messages
if match_pattern: if match_pattern:

View File

@ -181,7 +181,7 @@ class Economy(commands.Cog):
pass pass
@_bank.command() @_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. """Show the user's account balance.
Example: Example:
@ -192,9 +192,6 @@ class Economy(commands.Cog):
- `<user>` The user to check the balance of. If omitted, defaults to your own balance. - `<user>` 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) bal = await bank.get_balance(user)
currency = await bank.get_currency_name(ctx.guild) currency = await bank.get_currency_name(ctx.guild)
max_bal = await bank.get_max_balance(ctx.guild) max_bal = await bank.get_max_balance(ctx.guild)

View File

@ -251,28 +251,25 @@ class Filter(commands.Cog):
await ctx.send(_("I can't send direct messages to you.")) await ctx.send(_("I can't send direct messages to you."))
@_filter_channel.command(name="add", require_var_positional=True) @_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. """Add words to the filter.
Use double quotes to add sentences. Use double quotes to add sentences.
Examples: Examples:
- `[p]filter channel add word1 word2 word3` - `[p]filter channel add #channel word1 word2 word3`
- `[p]filter channel add "This is a sentence"` - `[p]filter channel add #channel "This is a sentence"`
**Arguments:** **Arguments:**
- `<channel>` The text, voice, or forum channel to add filtered words to.
- `[words...]` The words or sentences to filter. - `[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) added = await self.add_to_filter(channel, words)
if added: if added:
self.invalidate_cache(ctx.guild, ctx.channel) self.invalidate_cache(ctx.guild, ctx.channel)
@ -281,28 +278,25 @@ class Filter(commands.Cog):
await ctx.send(_("Words already in the filter.")) await ctx.send(_("Words already in the filter."))
@_filter_channel.command(name="delete", aliases=["remove", "del"], require_var_positional=True) @_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. """Remove words from the filter.
Use double quotes to remove sentences. Use double quotes to remove sentences.
Examples: Examples:
- `[p]filter channel remove word1 word2 word3` - `[p]filter channel remove #channel word1 word2 word3`
- `[p]filter channel remove "This is a sentence"` - `[p]filter channel remove #channel "This is a sentence"`
**Arguments:** **Arguments:**
- `<channel>` The text, voice, or forum channel to add filtered words to.
- `[words...]` The words or sentences to no longer filter. - `[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) removed = await self.remove_from_filter(channel, words)
if removed: if removed:
await ctx.send(_("Words removed from filter.")) 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.")) await ctx.send(_("Names and nicknames will now be filtered."))
def invalidate_cache( 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: ) -> None:
"""Invalidate a cached pattern""" """Invalidate a cached pattern"""
self.pattern_cache.pop((guild.id, channel and channel.id), None) 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) self.pattern_cache.pop(keyset, None)
async def add_to_filter( 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: ) -> bool:
added = False added = False
if isinstance(server_or_channel, discord.Guild): if isinstance(server_or_channel, discord.Guild):
@ -391,7 +393,7 @@ class Filter(commands.Cog):
cur_list.append(w.lower()) cur_list.append(w.lower())
added = True added = True
elif isinstance(server_or_channel, discord.TextChannel): else:
async with self.config.channel(server_or_channel).filter() as cur_list: async with self.config.channel(server_or_channel).filter() as cur_list:
for w in words: for w in words:
if w.lower() not in cur_list and w: if w.lower() not in cur_list and w:
@ -401,7 +403,11 @@ class Filter(commands.Cog):
return added return added
async def remove_from_filter( 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: ) -> bool:
removed = False removed = False
if isinstance(server_or_channel, discord.Guild): if isinstance(server_or_channel, discord.Guild):
@ -411,7 +417,7 @@ class Filter(commands.Cog):
cur_list.remove(w.lower()) cur_list.remove(w.lower())
removed = True removed = True
elif isinstance(server_or_channel, discord.TextChannel): else:
async with self.config.channel(server_or_channel).filter() as cur_list: async with self.config.channel(server_or_channel).filter() as cur_list:
for w in words: for w in words:
if w.lower() in cur_list: if w.lower() in cur_list:
@ -423,7 +429,9 @@ class Filter(commands.Cog):
async def filter_hits( async def filter_hits(
self, self,
text: str, 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]: ) -> Set[str]:
if isinstance(server_or_channel, discord.Guild): if isinstance(server_or_channel, discord.Guild):
guild = server_or_channel guild = server_or_channel

View File

@ -1,3 +1,4 @@
import discord
import re import re
from .abc import MixinMeta from .abc import MixinMeta
from datetime import timedelta from datetime import timedelta
@ -24,11 +25,14 @@ class Slowmode(MixinMeta):
minimum=timedelta(seconds=0), maximum=timedelta(hours=6), default_unit="seconds" minimum=timedelta(seconds=0), maximum=timedelta(hours=6), default_unit="seconds"
) = timedelta(seconds=0), ) = 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. Interval can be anything from 0 seconds to 6 hours.
Use without parameters to disable. 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() seconds = interval.total_seconds()
await ctx.channel.edit(slowmode_delay=seconds) await ctx.channel.edit(slowmode_delay=seconds)
if seconds > 0: if seconds > 0:

View File

@ -52,6 +52,11 @@ MUTE_UNMUTE_ISSUES = {
"voice_mute_permission": _( "voice_mute_permission": _(
"Because I don't have the Move Members permission, this will take into effect when the user rejoins." "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_ _ = T_
@ -503,7 +508,7 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
del muted_users[str(member.id)] del muted_users[str(member.id)]
if success["success"]: if success["success"]:
if create_case: if create_case:
if isinstance(channel, discord.VoiceChannel): if data.get("voice_mute", False):
unmute_type = "vunmute" unmute_type = "vunmute"
notification_title = _("Voice unmute") notification_title = _("Voice unmute")
else: 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() o.id: {name: attr for name, attr in p} for o, p in after.overwrites.items()
} }
to_del: List[int] = [] 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 unmuted = False
voice_mute = mute_data.get("voice_mute", False)
if user_id in after_perms: if user_id in after_perms:
for perm_name in ( perms_to_check = ["speak"]
if not voice_mute:
perms_to_check.extend(
(
"send_messages", "send_messages",
"send_messages_in_threads", "send_messages_in_threads",
"create_public_threads", "create_public_threads",
"create_private_threads", "create_private_threads",
"speak", )
): )
for perm_name in perms_to_check:
unmuted = unmuted or after_perms[user_id][perm_name] is not False unmuted = unmuted or after_perms[user_id][perm_name] is not False
# explicit is better than implicit :thinkies: # explicit is better than implicit :thinkies:
if user_id in before_perms and (user_id not in after_perms or unmuted): 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)}") log.debug(f"{user} - {type(user)}")
to_del.append(user_id) to_del.append(user_id)
log.debug("creating case") log.debug("creating case")
if isinstance(after, discord.VoiceChannel): if voice_mute:
unmute_type = "vunmute" unmute_type = "vunmute"
notification_title = _("Voice unmute") notification_title = _("Voice unmute")
else: else:
@ -848,7 +858,9 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
@muteset.command(name="notification") @muteset.command(name="notification")
@checks.admin_or_permissions(manage_channels=True) @checks.admin_or_permissions(manage_channels=True)
async def notification_channel_set( 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. Set the notification channel for automatic unmute issues.
@ -932,6 +944,7 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
send_messages_in_threads=False, send_messages_in_threads=False,
create_public_threads=False, create_public_threads=False,
create_private_threads=False, create_private_threads=False,
use_application_commands=False,
speak=False, speak=False,
add_reactions=False, add_reactions=False,
) )
@ -979,6 +992,7 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
overs.send_messages_in_threads = False overs.send_messages_in_threads = False
overs.create_public_threads = False overs.create_public_threads = False
overs.create_private_threads = False overs.create_private_threads = False
overs.use_application_commands = False
overs.add_reactions = False overs.add_reactions = False
overs.speak = False overs.speak = False
try: try:
@ -1681,6 +1695,8 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
user: discord.Member, user: discord.Member,
until: Optional[datetime] = None, until: Optional[datetime] = None,
reason: Optional[str] = None, reason: Optional[str] = None,
*,
voice_mute: bool = False,
) -> Dict[str, Optional[Union[discord.abc.GuildChannel, str, bool]]]: ) -> Dict[str, Optional[Union[discord.abc.GuildChannel, str, bool]]]:
"""Mutes the specified user in the specified channel""" """Mutes the specified user in the specified channel"""
overwrites = channel.overwrites_for(user) overwrites = channel.overwrites_for(user)
@ -1693,16 +1709,7 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
"reason": _(MUTE_UNMUTE_ISSUES["is_admin"]), "reason": _(MUTE_UNMUTE_ISSUES["is_admin"]),
} }
new_overs: dict = {}
move_channel = False 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 send_reason = None
if user.voice and user.voice.channel: if user.voice and user.voice.channel:
if channel.permissions_for(guild.me).move_members: 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"]), "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: if channel.id not in self._channel_mutes:
self._channel_mutes[channel.id] = {} 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 { return {
"success": False, "success": False,
"channel": channel, "channel": channel,
"reason": _(MUTE_UNMUTE_ISSUES["already_muted"]), "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: if not channel.permissions_for(guild.me).manage_permissions:
return { return {
"success": False, "success": False,
@ -1738,6 +1767,7 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
"guild": guild.id, "guild": guild.id,
"member": user.id, "member": user.id,
"until": until.timestamp() if until else None, "until": until.timestamp() if until else None,
"voice_mute": voice_mute,
} }
try: try:
await channel.set_permissions(user, overwrite=overwrites, reason=reason) await channel.set_permissions(user, overwrite=overwrites, reason=reason)
@ -1795,6 +1825,8 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
author: discord.Member, author: discord.Member,
user: discord.Member, user: discord.Member,
reason: Optional[str] = None, reason: Optional[str] = None,
*,
voice_mute: bool = False,
) -> Dict[str, Optional[Union[discord.abc.GuildChannel, str, bool]]]: ) -> Dict[str, Optional[Union[discord.abc.GuildChannel, str, bool]]]:
"""Unmutes the specified user in a specified channel""" """Unmutes the specified user in a specified channel"""
overwrites = channel.overwrites_for(user) overwrites = channel.overwrites_for(user)
@ -1809,6 +1841,7 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
"send_messages_in_threads": None, "send_messages_in_threads": None,
"create_public_threads": None, "create_public_threads": None,
"create_private_threads": None, "create_private_threads": None,
"use_application_commands": None,
"add_reactions": None, "add_reactions": None,
"speak": None, "speak": None,
} }
@ -1826,13 +1859,21 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
overwrites.update(**old_values) overwrites.update(**old_values)
if channel.id in self._channel_mutes and user.id in self._channel_mutes[channel.id]: 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: else:
return { return {
"success": False, "success": False,
"channel": channel, "channel": channel,
"reason": _(MUTE_UNMUTE_ISSUES["already_unmuted"]), "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: if not channel.permissions_for(guild.me).manage_permissions:
return { return {
"success": False, "success": False,

View File

@ -124,7 +124,7 @@ class VoiceMutes(MixinMeta):
audit_reason = get_audit_reason(author, reason, shorten=True) audit_reason = get_audit_reason(author, reason, shorten=True)
success = await self.channel_mute_user( 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"]: if success["success"]:
@ -200,7 +200,7 @@ class VoiceMutes(MixinMeta):
audit_reason = get_audit_reason(author, reason, shorten=True) audit_reason = get_audit_reason(author, reason, shorten=True)
success = await self.channel_unmute_user( success = await self.channel_unmute_user(
guild, channel, author, user, audit_reason guild, channel, author, user, audit_reason, voice_mute=True
) )
if success["success"]: if success["success"]:

View File

@ -221,8 +221,8 @@ class Permissions(commands.Cog):
"Global rules (set by the owner) are checked first, then rules set for servers. If " "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" "multiple global or server rules apply to the case, the order they are checked in is:\n"
" 1. Rules about a user.\n" " 1. Rules about a user.\n"
" 2. Rules about the voice channel a user is in.\n" " 2. Rules about the voice channel a user is connected to.\n"
" 3. Rules about the text channel or a parent of the thread a command was issued in.\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 " " 4. Rules about a role the user has (The highest role they have with a rule will be "
"used).\n" "used).\n"
" 5. Rules about the server a user is in (Global rules only).\n\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: except discord.Forbidden:
await ctx.send(_("I'm not allowed to DM you.")) await ctx.send(_("I'm not allowed to DM you."))
else: 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.")) await ctx.send(_("I've just sent the file to you via DM."))
finally: finally:
file.close() file.close()

View File

@ -106,7 +106,9 @@ class Reports(commands.Cog):
@checks.admin_or_permissions(manage_guild=True) @checks.admin_or_permissions(manage_guild=True)
@reportset.command(name="output") @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.""" """Set the channel where reports will be sent."""
await self.config.guild(ctx.guild).output_channel.set(channel.id) await self.config.guild(ctx.guild).output_channel.set(channel.id)
await ctx.send(_("The report channel has been set.")) 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: if ctx.author.id in self.user_cache:
self.user_cache.remove(ctx.author.id) self.user_cache.remove(ctx.author.id)
if ctx.guild and ctx.invoked_subcommand is None: 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: try:
await ctx.message.delete() await ctx.message.delete()
except discord.NotFound: except discord.NotFound:

View File

@ -303,14 +303,19 @@ class Streams(commands.Cog):
self, self,
ctx: commands.Context, ctx: commands.Context,
channel_name: str, channel_name: str,
discord_channel: discord.TextChannel = None, discord_channel: Union[discord.TextChannel, discord.VoiceChannel] = None,
): ):
"""Manage Twitch stream notifications.""" """Manage Twitch stream notifications."""
await ctx.invoke(self.twitch_alert_channel, channel_name, discord_channel) await ctx.invoke(self.twitch_alert_channel, channel_name, discord_channel)
@_twitch.command(name="channel") @_twitch.command(name="channel")
async def twitch_alert_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.""" """Toggle alerts in this or the given channel for a Twitch stream."""
if re.fullmatch(r"<#\d+>", channel_name): if re.fullmatch(r"<#\d+>", channel_name):
@ -325,14 +330,21 @@ class Streams(commands.Cog):
self, self,
ctx: commands.Context, ctx: commands.Context,
channel_name_or_id: str, 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.""" """Toggle alerts in this channel for a YouTube stream."""
await self.stream_alert(ctx, YoutubeStream, channel_name_or_id, discord_channel) await self.stream_alert(ctx, YoutubeStream, channel_name_or_id, discord_channel)
@streamalert.command(name="picarto") @streamalert.command(name="picarto")
async def picarto_alert( 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.""" """Toggle alerts in this channel for a Picarto stream."""
await self.stream_alert(ctx, PicartoStream, channel_name, discord_channel) await self.stream_alert(ctx, PicartoStream, channel_name, discord_channel)
@ -401,8 +413,6 @@ class Streams(commands.Cog):
await ctx.send(page) await ctx.send(page)
async def stream_alert(self, ctx: commands.Context, _class, channel_name, discord_channel): 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): if isinstance(discord_channel, discord.Thread):
await ctx.send("Stream alerts cannot be set up in threads.") await ctx.send("Stream alerts cannot be set up in threads.")
return return
@ -757,7 +767,7 @@ class Streams(commands.Cog):
async def _send_stream_alert( async def _send_stream_alert(
self, self,
stream, stream,
channel: discord.TextChannel, channel: Union[discord.TextChannel, discord.VoiceChannel],
embed: discord.Embed, embed: discord.Embed,
content: str = None, content: str = None,
*, *,
@ -904,7 +914,10 @@ class Streams(commands.Cog):
await self.save_streams() await self.save_streams()
async def _get_mention_str( 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]]: ) -> Tuple[str, List[discord.Role]]:
"""Returns a 2-tuple with the string containing the mentions, and a list of """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. 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) mentions.append(role.mention)
return " ".join(mentions), edited_roles 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 = [] filtered = []
for stream in streams: for stream in streams:
tw_id = str(stream["channel"]["_id"]) tw_id = str(stream["channel"]["_id"])

View File

@ -412,7 +412,7 @@ class Trivia(commands.Cog):
subcommands for a more customised leaderboard. subcommands for a more customised leaderboard.
""" """
cmd = self.trivia_leaderboard_server cmd = self.trivia_leaderboard_server
if isinstance(ctx.channel, discord.abc.PrivateChannel): if ctx.guild is None:
cmd = self.trivia_leaderboard_global cmd = self.trivia_leaderboard_global
await ctx.invoke(cmd, "wins", 10) 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)) await ctx.send(_("Saved Trivia list as {filename}.").format(filename=filename))
def _get_trivia_session( def _get_trivia_session(
self, channel: Union[discord.TextChannel, discord.Thread] self, channel: Union[discord.TextChannel, discord.VoiceChannel, discord.Thread]
) -> TriviaSession: ) -> TriviaSession:
return next( return next(
(session for session in self.trivia_sessions if session.ctx.channel == channel), None (session for session in self.trivia_sessions if session.ctx.channel == channel), None

View File

@ -156,7 +156,11 @@ class Warnings(commands.Cog):
@warningset.command() @warningset.command()
@commands.guild_only() @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. """Set the channel where warnings should be sent to.
Leave empty to use the channel `[p]warn` command was called in. Leave empty to use the channel `[p]warn` command was called in.

View File

@ -38,7 +38,7 @@ class IssueDiagnoserBase:
self, self,
bot: Red, bot: Red,
original_ctx: commands.Context, original_ctx: commands.Context,
channel: Union[discord.TextChannel, discord.Thread], channel: Union[discord.TextChannel, discord.VoiceChannel, discord.Thread],
author: discord.Member, author: discord.Member,
command: commands.Command, command: commands.Command,
) -> None: ) -> None:

View File

@ -220,8 +220,6 @@ class Red(
self._main_dir = bot_dir self._main_dir = bot_dir
self._cog_mgr = CogManager() self._cog_mgr = CogManager()
self._use_team_features = cli_flags.use_team_features 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) super().__init__(*args, help_command=None, **kwargs)
# Do not manually use the help formatter attribute here, see `send_help_for`, # 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. # for a documented API. The internals of this object are still subject to change.
@ -815,7 +813,7 @@ class Red(
return False return False
if guild: 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): if not can_user_send_messages_in(guild.me, channel):
return False return False
if not (await self.ignored_channel_or_guild(message)): if not (await self.ignored_channel_or_guild(message)):
@ -1207,16 +1205,12 @@ class Red(
if self.rpc_enabled: if self.rpc_enabled:
await self.rpc.initialize(self.rpc_port) await self.rpc.initialize(self.rpc_port)
async def _pre_fetch_owners(self) -> None: def _setup_owners(self) -> None:
app_info = await self.application_info() if self.application.team:
if app_info.team:
if self._use_team_features: 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: elif self._owner_id_overwrite is None:
self.owner_ids.add(app_info.owner.id) self.owner_ids.add(self.application.owner.id)
self._app_info = app_info
if not self.owner_ids: if not self.owner_ids:
raise _NoOwnerSet("Bot doesn't have any owner set!") raise _NoOwnerSet("Bot doesn't have any owner set!")
@ -1229,7 +1223,7 @@ class Red(
await self.connect() await self.connect()
async def setup_hook(self) -> None: async def setup_hook(self) -> None:
await self._pre_fetch_owners() self._setup_owners()
await self._pre_connect() await self._pre_connect()
async def send_help_for( async def send_help_for(
@ -1249,7 +1243,12 @@ class Red(
async def embed_requested( async def embed_requested(
self, self,
channel: Union[ 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, command: Optional[commands.Command] = None,
@ -1260,7 +1259,7 @@ class Red(
Arguments 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. The target messageable object to check embed settings for.
Keyword Arguments Keyword Arguments
@ -1307,7 +1306,7 @@ class Red(
"You cannot pass a GroupChannel, DMChannel, or PartialMessageable to this method." "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 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: 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",) scopes = ("bot", "applications.commands") if commands_scope else ("bot",)
perms_int = data["invite_perm"] perms_int = data["invite_perm"]
permissions = discord.Permissions(perms_int) 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: async def is_invite_url_public(self) -> bool:
""" """
@ -1613,7 +1612,6 @@ class Red(
cogname: str, cogname: str,
/, /,
*, *,
# DEP-WARN: MISSING is implementation detail
guild: Optional[discord.abc.Snowflake] = discord.utils.MISSING, guild: Optional[discord.abc.Snowflake] = discord.utils.MISSING,
guilds: List[discord.abc.Snowflake] = discord.utils.MISSING, guilds: List[discord.abc.Snowflake] = discord.utils.MISSING,
) -> Optional[commands.Cog]: ) -> Optional[commands.Cog]:
@ -1725,7 +1723,6 @@ class Red(
/, /,
*, *,
override: bool = False, override: bool = False,
# DEP-WARN: MISSING is implementation detail
guild: Optional[discord.abc.Snowflake] = discord.utils.MISSING, guild: Optional[discord.abc.Snowflake] = discord.utils.MISSING,
guilds: List[discord.abc.Snowflake] = discord.utils.MISSING, guilds: List[discord.abc.Snowflake] = discord.utils.MISSING,
) -> None: ) -> None:
@ -1880,7 +1877,7 @@ class Red(
async def get_owner_notification_destinations( async def get_owner_notification_destinations(
self, self,
) -> List[Union[discord.TextChannel, discord.User]]: ) -> List[Union[discord.TextChannel, discord.VoiceChannel, discord.User]]:
""" """
Gets the users and channels to send to Gets the users and channels to send to
""" """

View File

@ -45,7 +45,6 @@ from .help import (
) )
from .requires import ( from .requires import (
CheckPredicate as CheckPredicate, CheckPredicate as CheckPredicate,
DM_PERMS as DM_PERMS,
GlobalPermissionModel as GlobalPermissionModel, GlobalPermissionModel as GlobalPermissionModel,
GuildPermissionModel as GuildPermissionModel, GuildPermissionModel as GuildPermissionModel,
PermissionModel as PermissionModel, PermissionModel as PermissionModel,
@ -189,4 +188,14 @@ from discord.ext.commands import (
bot_has_any_role as bot_has_any_role, bot_has_any_role as bot_has_any_role,
before_invoke as before_invoke, before_invoke as before_invoke,
after_invoke as after_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,
) )

View File

@ -339,7 +339,7 @@ if TYPE_CHECKING or os.getenv("BUILDING_DOCS", False):
... ...
@property @property
def channel(self) -> Union[discord.TextChannel, discord.Thread]: def channel(self) -> Union[discord.TextChannel, discord.VoiceChannel, discord.Thread]:
... ...
@property @property

View File

@ -842,12 +842,12 @@ class RedHelpFormatter(HelpFormatterABC):
if ( if (
not use_DMs # we're not in DMs not use_DMs # we're not in DMs
and delete_delay > 0 # delete delay is enabled 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. # 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 # The channel has to be TextChannel or Thread as we can't bulk-delete from DMs
async def _delete_delay_help( async def _delete_delay_help(
channel: Union[discord.TextChannel, discord.Thread], channel: Union[discord.TextChannel, discord.VoiceChannel, discord.Thread],
messages: List[discord.Message], messages: List[discord.Message],
delay: int, delay: int,
): ):

View File

@ -40,7 +40,6 @@ if TYPE_CHECKING:
__all__ = [ __all__ = [
"CheckPredicate", "CheckPredicate",
"DM_PERMS",
"GlobalPermissionModel", "GlobalPermissionModel",
"GuildPermissionModel", "GuildPermissionModel",
"PermissionModel", "PermissionModel",
@ -75,6 +74,7 @@ GlobalPermissionModel = Union[
discord.User, discord.User,
discord.VoiceChannel, discord.VoiceChannel,
discord.TextChannel, discord.TextChannel,
discord.ForumChannel,
discord.CategoryChannel, discord.CategoryChannel,
discord.Role, discord.Role,
discord.Guild, discord.Guild,
@ -83,6 +83,7 @@ GuildPermissionModel = Union[
discord.Member, discord.Member,
discord.VoiceChannel, discord.VoiceChannel,
discord.TextChannel, discord.TextChannel,
discord.ForumChannel,
discord.CategoryChannel, discord.CategoryChannel,
discord.Role, discord.Role,
discord.Guild, discord.Guild,
@ -90,22 +91,6 @@ GuildPermissionModel = Union[
PermissionModel = Union[GlobalPermissionModel, GuildPermissionModel] PermissionModel = Union[GlobalPermissionModel, GuildPermissionModel]
CheckPredicate = Callable[["Context"], Union[Optional[bool], Awaitable[Optional[bool]]]] 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): class PrivilegeLevel(enum.IntEnum):
"""Enumeration for special privileges.""" """Enumeration for special privileges."""
@ -520,15 +505,11 @@ class Requires:
return await self._transition_state(ctx) return await self._transition_state(ctx)
async def _verify_bot(self, ctx: "Context") -> None: 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 cog = ctx.cog
if cog and await ctx.bot.cog_disabled_in_guild(cog, ctx.guild): if ctx.guild is not None and cog and await ctx.bot.cog_disabled_in_guild(cog, ctx.guild):
raise discord.ext.commands.DisabledCommand() 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): if not (bot_perms.administrator or bot_perms >= self.bot_perms):
raise BotMissingPermissions(missing=self._missing_perms(self.bot_perms, bot_perms)) raise BotMissingPermissions(missing=self._missing_perms(self.bot_perms, bot_perms))
@ -574,7 +555,7 @@ class Requires:
return False return False
if self.user_perms is not None: 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: if user_perms.administrator or user_perms >= self.user_perms:
return True return True
@ -633,17 +614,6 @@ class Requires:
return True return True
return await discord.utils.async_all(check(ctx) for check in self.checks) 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 @staticmethod
def _missing_perms( def _missing_perms(
required: discord.Permissions, actual: discord.Permissions required: discord.Permissions, actual: discord.Permissions
@ -656,13 +626,6 @@ class Requires:
relative_complement = required.value & ~actual.value relative_complement = required.value & ~actual.value
return discord.Permissions(relative_complement) 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: def __repr__(self) -> str:
return ( return (
f"<Requires privilege_level={self.privilege_level!r} user_perms={self.user_perms!r} " f"<Requires privilege_level={self.privilege_level!r} user_perms={self.user_perms!r} "

View File

@ -591,7 +591,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
""" """
# Can't check this as a command check, and want to prompt DMs as an option. # Can't check this as a command check, and want to prompt DMs as an option.
if not ctx.channel.permissions_for(ctx.me).attach_files: if not ctx.bot_permissions.attach_files:
ctx.command.reset_cooldown(ctx) ctx.command.reset_cooldown(ctx)
return await ctx.send(_("I need to be able to attach files (try in DMs?).")) return await ctx.send(_("I need to be able to attach files (try in DMs?)."))
@ -1349,7 +1349,12 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
@embedset.command(name="channel") @embedset.command(name="channel")
@checks.guildowner_or_permissions(administrator=True) @checks.guildowner_or_permissions(administrator=True)
@commands.guild_only() @commands.guild_only()
async def embedset_channel(self, ctx: commands.Context, enabled: bool = None): async def embedset_channel(
self,
ctx: commands.Context,
channel: Union[discord.TextChannel, discord.VoiceChannel, discord.ForumChannel],
enabled: bool = None,
):
""" """
Set's a channel's embed setting. Set's a channel's embed setting.
@ -1361,27 +1366,20 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
To see full evaluation order of embed settings, run `[p]help embedset`. To see full evaluation order of embed settings, run `[p]help embedset`.
**Examples:** **Examples:**
- `[p]embedset channel False` - Disables embeds in this channel. - `[p]embedset channel #text-channel False` - Disables embeds in the #text-channel.
- `[p]embedset channel` - Resets value to use guild default. - `[p]embedset channel #forum-channel disable` - Disables embeds in the #forum-channel.
- `[p]embedset channel #text-channel` - Resets value to use guild default in the #text-channel .
**Arguments:** **Arguments:**
- `<channel>` - 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. - `[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: 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.")) await ctx.send(_("Embeds will now fall back to the global setting."))
return return
await self.bot._config.channel(ctx.channel).embeds.set(enabled) await self.bot._config.channel(channel).embeds.set(enabled)
await ctx.send( await ctx.send(
_("Embeds are now {} for this channel.").format( _("Embeds are now {} for this channel.").format(
_("enabled") if enabled else _("disabled") _("enabled") if enabled else _("disabled")
@ -2247,7 +2245,11 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
@modlogset.command(aliases=["channel"], name="modlog") @modlogset.command(aliases=["channel"], name="modlog")
@commands.guild_only() @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. """Set a channel as the modlog.
Omit `[channel]` to disable 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), view=SetApiView(default_service=service),
) )
else: else:
if ctx.channel.permissions_for(ctx.me).manage_messages: if ctx.bot_permissions.manage_messages:
await ctx.message.delete() await ctx.message.delete()
await ctx.bot.set_shared_api_tokens(service, **tokens) await ctx.bot.set_shared_api_tokens(service, **tokens)
await ctx.send(_("`{service}` API tokens have been set.").format(service=service)) 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") @_set_ownernotifications.command(name="adddestination")
async def _set_ownernotifications_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. 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"] name="removedestination", aliases=["remdestination", "deletedestination", "deldestination"]
) )
async def _set_ownernotifications_removedestination( 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. Removes a destination text channel from receiving owner notifications.
@ -4136,8 +4141,11 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
async def diagnoseissues( async def diagnoseissues(
self, self,
ctx: commands.Context, ctx: commands.Context,
channel: Optional[Union[discord.TextChannel, discord.Thread]], channel: Optional[
member: Union[discord.Member, discord.User], 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, command_name: str,
) -> None: ) -> None:
@ -4155,12 +4163,10 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
- `<member>` - The member that should be considered as the command caller. - `<member>` - The member that should be considered as the command caller.
- `<command_name>` - The name of the command to test. - `<command_name>` - The name of the command to test.
""" """
if channel is None: if ctx.guild is None:
channel = ctx.channel
if not isinstance(channel, (discord.TextChannel, discord.Thread)):
await ctx.send( await ctx.send(
_( _(
"The text channel or thread needs to be passed" "A text channel, voice channel, or thread needs to be passed"
" when using this command in DMs." " when using this command in DMs."
) )
) )
@ -5124,9 +5130,13 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
async def ignore_channel( async def ignore_channel(
self, self,
ctx: commands.Context, ctx: commands.Context,
channel: Optional[ channel: Union[
Union[discord.TextChannel, discord.CategoryChannel, discord.Thread] discord.TextChannel,
] = None, discord.VoiceChannel,
discord.ForumChannel,
discord.CategoryChannel,
discord.Thread,
] = commands.CurrentChannel,
): ):
""" """
Ignore commands in the channel, thread, or category. Ignore commands in the channel, thread, or category.
@ -5144,8 +5154,6 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
**Arguments:** **Arguments:**
- `<channel>` - The channel to ignore. This can also be a thread or category channel. - `<channel>` - 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): if not await self.bot._ignored_cache.get_ignored_channel(channel):
await self.bot._ignored_cache.set_ignored_channel(channel, True) await self.bot._ignored_cache.set_ignored_channel(channel, True)
await ctx.send(_("Channel added to ignore list.")) await ctx.send(_("Channel added to ignore list."))
@ -5180,9 +5188,13 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
async def unignore_channel( async def unignore_channel(
self, self,
ctx: commands.Context, ctx: commands.Context,
channel: Optional[ channel: Union[
Union[discord.TextChannel, discord.CategoryChannel, discord.Thread] discord.TextChannel,
] = None, discord.VoiceChannel,
discord.ForumChannel,
discord.CategoryChannel,
discord.Thread,
] = commands.CurrentChannel,
): ):
""" """
Remove a channel, thread, or category from the ignore list. Remove a channel, thread, or category from the ignore list.
@ -5198,9 +5210,6 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
**Arguments:** **Arguments:**
- `<channel>` - The channel to unignore. This can also be a thread or category channel. - `<channel>` - 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): if await self.bot._ignored_cache.get_ignored_channel(channel):
await self.bot._ignored_cache.set_ignored_channel(channel, False) await self.bot._ignored_cache.set_ignored_channel(channel, False)
await ctx.send(_("Channel removed from ignore list.")) 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): async def count_ignored(self, ctx: commands.Context):
category_channels: List[discord.CategoryChannel] = [] category_channels: List[discord.CategoryChannel] = []
text_channels: List[discord.TextChannel] = [] channels: List[Union[discord.TextChannel, discord.VoiceChannel, discord.ForumChannel]] = []
threads: List[discord.Thread] = [] threads: List[discord.Thread] = []
if await self.bot._ignored_cache.get_ignored_guild(ctx.guild): if await self.bot._ignored_cache.get_ignored_guild(ctx.guild):
return _("This server is currently being ignored.") 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): if await self.bot._ignored_cache.get_ignored_channel(channel.category):
category_channels.append(channel.category) category_channels.append(channel.category)
if await self.bot._ignored_cache.get_ignored_channel(channel, check_category=False): 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: for thread in ctx.guild.threads:
if await self.bot._ignored_cache.get_ignored_channel(thread, check_category=False): if await self.bot._ignored_cache.get_ignored_channel(thread, check_category=False):
threads.append(thread) threads.append(thread)
@ -5242,9 +5263,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
cat_str = ( cat_str = (
humanize_list([c.name for c in category_channels]) if category_channels else _("None") humanize_list([c.name for c in category_channels]) if category_channels else _("None")
) )
chan_str = ( chan_str = humanize_list([c.mention for c in channels]) if channels else _("None")
humanize_list([c.mention for c in text_channels]) if text_channels else _("None")
)
thread_str = humanize_list([c.mention for c in threads]) if threads else _("None") thread_str = humanize_list([c.mention for c in threads]) if threads else _("None")
msg = _( msg = _(
"Currently ignored categories: {categories}\n" "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. # 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. # 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( @commands.command(
cls=commands.commands._AlwaysAvailableCommand, cls=commands.commands._AlwaysAvailableCommand,
name="licenseinfo", name="licenseinfo",

View File

@ -30,7 +30,6 @@ from .utils._internal_utils import (
expected_version, expected_version,
fetch_latest_red_version_info, fetch_latest_red_version_info,
send_to_owners_with_prefix_replaced, send_to_owners_with_prefix_replaced,
get_converter,
) )
from .utils.chat_formatting import inline, bordered, format_perms_list, humanize_timedelta 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) guilds = len(bot.guilds)
users = len(set([m for m in bot.get_all_members()])) 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()) prefixes = cli_flags.prefix or (await bot._config.prefix())
lang = await bot._config.locale() lang = await bot._config.locale()
@ -222,7 +221,7 @@ def init_events(bot, cli_flags):
await ctx.send_help() await ctx.send_help()
elif isinstance(error, commands.BadArgument): elif isinstance(error, commands.BadArgument):
if isinstance(error.__cause__, ValueError): if isinstance(error.__cause__, ValueError):
converter = get_converter(ctx.current_parameter) converter = ctx.current_parameter.converter
argument = ctx.current_argument argument = ctx.current_argument
if converter is int: if converter is int:
await ctx.send(_('"{argument}" is not an integer.').format(argument=argument)) await ctx.send(_('"{argument}" is not an integer.').format(argument=argument))

View File

@ -349,12 +349,12 @@ class Case:
self.message = message self.message = message
@property @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 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: if self.parent_channel_id is None:
return None return None
@ -645,13 +645,18 @@ class Case:
@classmethod @classmethod
async def from_json( 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 """Get a Case object from the provided information
Parameters Parameters
---------- ----------
mod_channel: discord.TextChannel mod_channel: `discord.TextChannel` or `discord.VoiceChannel`
The mod log channel for the guild The mod log channel for the guild
bot: Red bot: Red
The bot's instance. Needed to get the target user 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 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. Get the current modlog channel.
@ -1239,7 +1246,7 @@ async def get_modlog_channel(guild: discord.Guild) -> discord.TextChannel:
Returns Returns
------- -------
`discord.TextChannel` `discord.TextChannel` or `discord.VoiceChannel`
The channel object representing the modlog channel. The channel object representing the modlog channel.
Raises Raises
@ -1259,7 +1266,7 @@ async def get_modlog_channel(guild: discord.Guild) -> discord.TextChannel:
async def set_modlog_channel( async def set_modlog_channel(
guild: discord.Guild, channel: Union[discord.TextChannel, None] guild: discord.Guild, channel: Union[discord.TextChannel, discord.VoiceChannel, None]
) -> bool: ) -> bool:
""" """
Changes the modlog channel Changes the modlog channel
@ -1268,7 +1275,7 @@ async def set_modlog_channel(
---------- ----------
guild: `discord.Guild` guild: `discord.Guild`
The guild to set a mod log channel for 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 The channel to be set as modlog channel
Returns Returns

View File

@ -154,7 +154,11 @@ class IgnoreManager:
self._cached_guilds: Dict[int, bool] = {} self._cached_guilds: Dict[int, bool] = {}
async def get_ignored_channel( 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: ) -> bool:
ret: bool ret: bool
@ -181,7 +185,13 @@ class IgnoreManager:
async def set_ignored_channel( async def set_ignored_channel(
self, self,
channel: Union[discord.TextChannel, discord.Thread, discord.CategoryChannel], channel: Union[
discord.TextChannel,
discord.VoiceChannel,
discord.Thread,
discord.ForumChannel,
discord.CategoryChannel,
],
set_to: bool, set_to: bool,
): ):
cid: int = channel.id cid: int = channel.id

View File

@ -34,7 +34,9 @@ from discord.utils import maybe_coroutine
from redbot.core import commands from redbot.core import commands
if TYPE_CHECKING: 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] DMMessageable = Union[commands.DMContext, discord.Member, discord.User, discord.DMChannel]
__all__ = ( __all__ = (

View File

@ -32,7 +32,6 @@ from typing import (
import aiohttp import aiohttp
import discord import discord
import pkg_resources import pkg_resources
from discord.ext.commands.converter import get_converter # DEP-WARN
from fuzzywuzzy import fuzz, process from fuzzywuzzy import fuzz, process
from rich.progress import ProgressColumn from rich.progress import ProgressColumn
from rich.progress_bar import ProgressBar from rich.progress_bar import ProgressBar
@ -60,7 +59,6 @@ __all__ = (
"deprecated_removed", "deprecated_removed",
"RichIndefiniteBarColumn", "RichIndefiniteBarColumn",
"cli_level_to_log_level", "cli_level_to_log_level",
"get_converter",
) )
_T = TypeVar("_T") _T = TypeVar("_T")

View File

@ -10,7 +10,8 @@ if TYPE_CHECKING:
async def mass_purge( 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. """Bulk delete messages from a channel.
@ -26,7 +27,7 @@ async def mass_purge(
---------- ----------
messages : `list` of `discord.Message` messages : `list` of `discord.Message`
The messages to bulk delete. 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. The channel to delete messages from.
Raises Raises
@ -247,7 +248,7 @@ async def check_permissions(ctx: "Context", perms: Dict[str, bool]) -> bool:
return True return True
elif not perms: elif not perms:
return False return False
resolved = ctx.channel.permissions_for(ctx.author) resolved = ctx.permissions
return resolved.administrator or all( return resolved.administrator or all(
getattr(resolved, name, None) == value for name, value in perms.items() getattr(resolved, name, None) == value for name, value in perms.items()

View File

@ -67,7 +67,9 @@ class MessagePredicate(Callable[[discord.Message], bool]):
def same_context( def same_context(
cls, cls,
ctx: Optional[commands.Context] = None, 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, user: Optional[discord.abc.User] = None,
) -> "MessagePredicate": ) -> "MessagePredicate":
"""Match if the message fits the described context. """Match if the message fits the described context.
@ -76,7 +78,7 @@ class MessagePredicate(Callable[[discord.Message], bool]):
---------- ----------
ctx : Optional[Context] ctx : Optional[Context]
The current invocation 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, The channel we expect a message in. If unspecified,
defaults to ``ctx.channel``. If ``ctx`` is unspecified defaults to ``ctx.channel``. If ``ctx`` is unspecified
too, the message's channel will be ignored. too, the message's channel will be ignored.
@ -104,7 +106,9 @@ class MessagePredicate(Callable[[discord.Message], bool]):
def cancelled( def cancelled(
cls, cls,
ctx: Optional[commands.Context] = None, 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, user: Optional[discord.abc.User] = None,
) -> "MessagePredicate": ) -> "MessagePredicate":
"""Match if the message is ``[p]cancel``. """Match if the message is ``[p]cancel``.
@ -113,7 +117,7 @@ class MessagePredicate(Callable[[discord.Message], bool]):
---------- ----------
ctx : Optional[Context] ctx : Optional[Context]
Same as ``ctx`` in :meth:`same_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`. Same as ``channel`` in :meth:`same_context`.
user : Optional[discord.abc.User] user : Optional[discord.abc.User]
Same as ``user`` in :meth:`same_context`. Same as ``user`` in :meth:`same_context`.
@ -133,7 +137,9 @@ class MessagePredicate(Callable[[discord.Message], bool]):
def yes_or_no( def yes_or_no(
cls, cls,
ctx: Optional[commands.Context] = None, 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, user: Optional[discord.abc.User] = None,
) -> "MessagePredicate": ) -> "MessagePredicate":
"""Match if the message is "yes"/"y" or "no"/"n". """Match if the message is "yes"/"y" or "no"/"n".
@ -145,7 +151,7 @@ class MessagePredicate(Callable[[discord.Message], bool]):
---------- ----------
ctx : Optional[Context] ctx : Optional[Context]
Same as ``ctx`` in :meth:`same_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`. Same as ``channel`` in :meth:`same_context`.
user : Optional[discord.abc.User] user : Optional[discord.abc.User]
Same as ``user`` in :meth:`same_context`. Same as ``user`` in :meth:`same_context`.
@ -176,7 +182,9 @@ class MessagePredicate(Callable[[discord.Message], bool]):
def valid_int( def valid_int(
cls, cls,
ctx: Optional[commands.Context] = None, 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, user: Optional[discord.abc.User] = None,
) -> "MessagePredicate": ) -> "MessagePredicate":
"""Match if the response is an integer. """Match if the response is an integer.
@ -187,7 +195,7 @@ class MessagePredicate(Callable[[discord.Message], bool]):
---------- ----------
ctx : Optional[Context] ctx : Optional[Context]
Same as ``ctx`` in :meth:`same_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`. Same as ``channel`` in :meth:`same_context`.
user : Optional[discord.abc.User] user : Optional[discord.abc.User]
Same as ``user`` in :meth:`same_context`. Same as ``user`` in :meth:`same_context`.
@ -216,7 +224,9 @@ class MessagePredicate(Callable[[discord.Message], bool]):
def valid_float( def valid_float(
cls, cls,
ctx: Optional[commands.Context] = None, 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, user: Optional[discord.abc.User] = None,
) -> "MessagePredicate": ) -> "MessagePredicate":
"""Match if the response is a float. """Match if the response is a float.
@ -227,7 +237,7 @@ class MessagePredicate(Callable[[discord.Message], bool]):
---------- ----------
ctx : Optional[Context] ctx : Optional[Context]
Same as ``ctx`` in :meth:`same_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`. Same as ``channel`` in :meth:`same_context`.
user : Optional[discord.abc.User] user : Optional[discord.abc.User]
Same as ``user`` in :meth:`same_context`. Same as ``user`` in :meth:`same_context`.
@ -256,7 +266,9 @@ class MessagePredicate(Callable[[discord.Message], bool]):
def positive( def positive(
cls, cls,
ctx: Optional[commands.Context] = None, 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, user: Optional[discord.abc.User] = None,
) -> "MessagePredicate": ) -> "MessagePredicate":
"""Match if the response is a positive number. """Match if the response is a positive number.
@ -267,7 +279,7 @@ class MessagePredicate(Callable[[discord.Message], bool]):
---------- ----------
ctx : Optional[Context] ctx : Optional[Context]
Same as ``ctx`` in :meth:`same_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`. Same as ``channel`` in :meth:`same_context`.
user : Optional[discord.abc.User] user : Optional[discord.abc.User]
Same as ``user`` in :meth:`same_context`. Same as ``user`` in :meth:`same_context`.
@ -300,7 +312,7 @@ class MessagePredicate(Callable[[discord.Message], bool]):
def valid_role( def valid_role(
cls, cls,
ctx: Optional[commands.Context] = None, 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, user: Optional[discord.abc.User] = None,
) -> "MessagePredicate": ) -> "MessagePredicate":
"""Match if the response refers to a role in the current guild. """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] ctx : Optional[Context]
Same as ``ctx`` in :meth:`same_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`. Same as ``channel`` in :meth:`same_context`.
user : Optional[discord.abc.User] user : Optional[discord.abc.User]
Same as ``user`` in :meth:`same_context`. Same as ``user`` in :meth:`same_context`.
@ -344,7 +356,7 @@ class MessagePredicate(Callable[[discord.Message], bool]):
def valid_member( def valid_member(
cls, cls,
ctx: Optional[commands.Context] = None, 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, user: Optional[discord.abc.User] = None,
) -> "MessagePredicate": ) -> "MessagePredicate":
"""Match if the response refers to a member in the current guild. """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] ctx : Optional[Context]
Same as ``ctx`` in :meth:`same_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`. Same as ``channel`` in :meth:`same_context`.
user : Optional[discord.abc.User] user : Optional[discord.abc.User]
Same as ``user`` in :meth:`same_context`. Same as ``user`` in :meth:`same_context`.
@ -392,7 +404,7 @@ class MessagePredicate(Callable[[discord.Message], bool]):
def valid_text_channel( def valid_text_channel(
cls, cls,
ctx: Optional[commands.Context] = None, 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, user: Optional[discord.abc.User] = None,
) -> "MessagePredicate": ) -> "MessagePredicate":
"""Match if the response refers to a text channel in the current guild. """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] ctx : Optional[Context]
Same as ``ctx`` in :meth:`same_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`. Same as ``channel`` in :meth:`same_context`.
user : Optional[discord.abc.User] user : Optional[discord.abc.User]
Same as ``user`` in :meth:`same_context`. Same as ``user`` in :meth:`same_context`.
@ -440,7 +452,7 @@ class MessagePredicate(Callable[[discord.Message], bool]):
def has_role( def has_role(
cls, cls,
ctx: Optional[commands.Context] = None, 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, user: Optional[discord.abc.User] = None,
) -> "MessagePredicate": ) -> "MessagePredicate":
"""Match if the response refers to a role which the author has. """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] ctx : Optional[Context]
Same as ``ctx`` in :meth:`same_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`. Same as ``channel`` in :meth:`same_context`.
user : Optional[discord.abc.User] user : Optional[discord.abc.User]
Same as ``user`` in :meth:`same_context`. Same as ``user`` in :meth:`same_context`.
@ -479,7 +491,7 @@ class MessagePredicate(Callable[[discord.Message], bool]):
return False return False
role = self._find_role(guild, m.content) 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 return False
self.result = role self.result = role
@ -492,7 +504,9 @@ class MessagePredicate(Callable[[discord.Message], bool]):
cls, cls,
value: str, value: str,
ctx: Optional[commands.Context] = None, 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, user: Optional[discord.abc.User] = None,
) -> "MessagePredicate": ) -> "MessagePredicate":
"""Match if the response is equal to the specified value. """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. The value to compare the response with.
ctx : Optional[Context] ctx : Optional[Context]
Same as ``ctx`` in :meth:`same_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`. Same as ``channel`` in :meth:`same_context`.
user : Optional[discord.abc.User] user : Optional[discord.abc.User]
Same as ``user`` in :meth:`same_context`. Same as ``user`` in :meth:`same_context`.
@ -522,7 +536,9 @@ class MessagePredicate(Callable[[discord.Message], bool]):
cls, cls,
value: str, value: str,
ctx: Optional[commands.Context] = None, 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, user: Optional[discord.abc.User] = None,
) -> "MessagePredicate": ) -> "MessagePredicate":
"""Match if the response *as lowercase* is equal to the specified value. """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. The value to compare the response with.
ctx : Optional[Context] ctx : Optional[Context]
Same as ``ctx`` in :meth:`same_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`. Same as ``channel`` in :meth:`same_context`.
user : Optional[discord.abc.User] user : Optional[discord.abc.User]
Same as ``user`` in :meth:`same_context`. Same as ``user`` in :meth:`same_context`.
@ -552,7 +568,9 @@ class MessagePredicate(Callable[[discord.Message], bool]):
cls, cls,
value: Union[int, float], value: Union[int, float],
ctx: Optional[commands.Context] = None, 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, user: Optional[discord.abc.User] = None,
) -> "MessagePredicate": ) -> "MessagePredicate":
"""Match if the response is less than the specified value. """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. The value to compare the response with.
ctx : Optional[Context] ctx : Optional[Context]
Same as ``ctx`` in :meth:`same_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`. Same as ``channel`` in :meth:`same_context`.
user : Optional[discord.abc.User] user : Optional[discord.abc.User]
Same as ``user`` in :meth:`same_context`. Same as ``user`` in :meth:`same_context`.
@ -583,7 +601,9 @@ class MessagePredicate(Callable[[discord.Message], bool]):
cls, cls,
value: Union[int, float], value: Union[int, float],
ctx: Optional[commands.Context] = None, 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, user: Optional[discord.abc.User] = None,
) -> "MessagePredicate": ) -> "MessagePredicate":
"""Match if the response is greater than the specified value. """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. The value to compare the response with.
ctx : Optional[Context] ctx : Optional[Context]
Same as ``ctx`` in :meth:`same_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`. Same as ``channel`` in :meth:`same_context`.
user : Optional[discord.abc.User] user : Optional[discord.abc.User]
Same as ``user`` in :meth:`same_context`. Same as ``user`` in :meth:`same_context`.
@ -614,7 +634,9 @@ class MessagePredicate(Callable[[discord.Message], bool]):
cls, cls,
length: int, length: int,
ctx: Optional[commands.Context] = None, 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, user: Optional[discord.abc.User] = None,
) -> "MessagePredicate": ) -> "MessagePredicate":
"""Match if the response's length is less than the specified length. """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. The value to compare the response's length with.
ctx : Optional[Context] ctx : Optional[Context]
Same as ``ctx`` in :meth:`same_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`. Same as ``channel`` in :meth:`same_context`.
user : Optional[discord.abc.User] user : Optional[discord.abc.User]
Same as ``user`` in :meth:`same_context`. Same as ``user`` in :meth:`same_context`.
@ -644,7 +666,9 @@ class MessagePredicate(Callable[[discord.Message], bool]):
cls, cls,
length: int, length: int,
ctx: Optional[commands.Context] = None, 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, user: Optional[discord.abc.User] = None,
) -> "MessagePredicate": ) -> "MessagePredicate":
"""Match if the response's length is greater than the specified length. """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. The value to compare the response's length with.
ctx : Optional[Context] ctx : Optional[Context]
Same as ``ctx`` in :meth:`same_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`. Same as ``channel`` in :meth:`same_context`.
user : Optional[discord.abc.User] user : Optional[discord.abc.User]
Same as ``user`` in :meth:`same_context`. Same as ``user`` in :meth:`same_context`.
@ -674,7 +698,9 @@ class MessagePredicate(Callable[[discord.Message], bool]):
cls, cls,
collection: Sequence[str], collection: Sequence[str],
ctx: Optional[commands.Context] = None, 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, user: Optional[discord.abc.User] = None,
) -> "MessagePredicate": ) -> "MessagePredicate":
"""Match if the response is contained in the specified collection. """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. The collection containing valid responses.
ctx : Optional[Context] ctx : Optional[Context]
Same as ``ctx`` in :meth:`same_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`. Same as ``channel`` in :meth:`same_context`.
user : Optional[discord.abc.User] user : Optional[discord.abc.User]
Same as ``user`` in :meth:`same_context`. Same as ``user`` in :meth:`same_context`.
@ -718,7 +744,9 @@ class MessagePredicate(Callable[[discord.Message], bool]):
cls, cls,
collection: Sequence[str], collection: Sequence[str],
ctx: Optional[commands.Context] = None, 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, user: Optional[discord.abc.User] = None,
) -> "MessagePredicate": ) -> "MessagePredicate":
"""Same as :meth:`contained_in`, but the response is set to lowercase before matching. """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. The collection containing valid lowercase responses.
ctx : Optional[Context] ctx : Optional[Context]
Same as ``ctx`` in :meth:`same_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`. Same as ``channel`` in :meth:`same_context`.
user : Optional[discord.abc.User] user : Optional[discord.abc.User]
Same as ``user`` in :meth:`same_context`. Same as ``user`` in :meth:`same_context`.
@ -759,7 +787,9 @@ class MessagePredicate(Callable[[discord.Message], bool]):
cls, cls,
pattern: Union[Pattern[str], str], pattern: Union[Pattern[str], str],
ctx: Optional[commands.Context] = None, 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, user: Optional[discord.abc.User] = None,
) -> "MessagePredicate": ) -> "MessagePredicate":
"""Match if the response matches the specified regex pattern. """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. The pattern to search for in the response.
ctx : Optional[Context] ctx : Optional[Context]
Same as ``ctx`` in :meth:`same_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`. Same as ``channel`` in :meth:`same_context`.
user : Optional[discord.abc.User] user : Optional[discord.abc.User]
Same as ``user`` in :meth:`same_context`. Same as ``user`` in :meth:`same_context`.
@ -816,7 +846,7 @@ class MessagePredicate(Callable[[discord.Message], bool]):
@staticmethod @staticmethod
def _get_guild( def _get_guild(
ctx: Optional[commands.Context], ctx: Optional[commands.Context],
channel: Optional[Union[discord.TextChannel, discord.Thread]], channel: Optional[Union[discord.TextChannel, discord.VoiceChannel, discord.Thread]],
user: Optional[discord.Member], user: Optional[discord.Member],
) -> discord.Guild: ) -> discord.Guild:
if ctx is not None: if ctx is not None:

View File

@ -57,7 +57,7 @@ class Tunnel(metaclass=TunnelMeta):
---------- ----------
sender: `discord.Member` sender: `discord.Member`
The person who opened the tunnel 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 The channel in which it was opened
recipient: `discord.User` recipient: `discord.User`
The user on the other end of the tunnel The user on the other end of the tunnel
@ -67,7 +67,7 @@ class Tunnel(metaclass=TunnelMeta):
self, self,
*, *,
sender: discord.Member, sender: discord.Member,
origin: Union[discord.TextChannel, discord.Thread], origin: Union[discord.TextChannel, discord.VoiceChannel, discord.Thread],
recipient: discord.User, recipient: discord.User,
): ):
self.sender = sender self.sender = sender
@ -164,14 +164,14 @@ class Tunnel(metaclass=TunnelMeta):
if images_only and a.height is None: if images_only and a.height is None:
# if this is None, it's not an image # if this is None, it's not an image
continue continue
_fp = io.BytesIO()
try: try:
await a.save(_fp, use_cached=use_cached) file = await a.to_file()
except discord.HTTPException as e: except discord.HTTPException as e:
# this is required, because animated webp files aren't cached # this is required, because animated webp files aren't cached
if not (e.status == 415 and images_only and use_cached): if not (e.status == 415 and images_only and use_cached):
raise raise
files.append(discord.File(_fp, filename=a.filename)) else:
files.append(file)
return files return files
# Backwards-compatible typo fix (GH-2496) # Backwards-compatible typo fix (GH-2496)

View File

@ -282,12 +282,10 @@ class RedRichHandler(RichHandler):
def init_logging(level: int, location: pathlib.Path, cli_flags: argparse.Namespace) -> None: def init_logging(level: int, location: pathlib.Path, cli_flags: argparse.Namespace) -> None:
root_logger = logging.getLogger() root_logger = logging.getLogger()
base_logger = logging.getLogger("red") root_logger.setLevel(level)
base_logger.setLevel(level) # DEBUG logging for discord.py is a bit too ridiculous :)
dpy_logger = logging.getLogger("discord") dpy_logger = logging.getLogger("discord")
dpy_logger.setLevel(logging.WARNING) dpy_logger.setLevel(logging.INFO)
warnings_logger = logging.getLogger("py.warnings")
warnings_logger.setLevel(logging.WARNING)
rich_console = rich.get_console() rich_console = rich.get_console()
rich.reconfigure(tab_size=4) rich.reconfigure(tab_size=4)

View File

@ -51,7 +51,7 @@ install_requires =
colorama==0.4.4 colorama==0.4.4
commonmark==0.9.1 commonmark==0.9.1
contextlib2==21.6.0 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" distro==1.6.0; sys_platform == "linux"
fuzzywuzzy==0.18.0 fuzzywuzzy==0.18.0
idna==3.2 idna==3.2
@ -66,7 +66,7 @@ install_requires =
pytz==2021.1 pytz==2021.1
PyYAML==5.4.1 PyYAML==5.4.1
Red-Commons==1.0.0 Red-Commons==1.0.0
Red-Lavalink==0.11.0rc0 Red-Lavalink==0.11.0rc1
rich==10.9.0 rich==10.9.0
schema==0.7.4 schema==0.7.4
six==1.16.0 six==1.16.0

View File

@ -50,8 +50,21 @@ def test_dpy_commands_reexports():
dpy_attrs.add(attr_name) dpy_attrs.add(attr_name)
missing_attrs = dpy_attrs - set(commands.__dict__.keys()) 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(): def test_converter_timedelta():