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:
1. User
2. Voice channel
3. Text channel (parent text channel in case of invocations in threads)
2. Voice channel a user is connected to
3. The channel command was issued in (parent channel in case of invocations in threads)
4. Channel category
5. Roles, highest to lowest
6. Server (can only be in global rules)

View File

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

View File

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

View File

@ -196,7 +196,9 @@ class MixinMeta(ABC):
async def is_query_allowed(
self,
config: Config,
ctx_or_channel: Optional[Union[Context, discord.TextChannel, discord.Thread]],
ctx_or_channel: Optional[
Union[Context, discord.TextChannel, discord.VoiceChannel, discord.Thread]
],
query: str,
query_obj: Query,
) -> bool:
@ -250,7 +252,9 @@ class MixinMeta(ABC):
raise NotImplementedError()
@abstractmethod
def _has_notify_perms(self, channel: Union[discord.TextChannel, discord.Thread]) -> bool:
def _has_notify_perms(
self, channel: Union[discord.TextChannel, discord.VoiceChannel, discord.Thread]
) -> bool:
raise NotImplementedError()
@abstractmethod

View File

@ -657,7 +657,7 @@ class PlayerControllerCommands(MixinMeta, metaclass=CompositeMetaClass):
if not self._player_check(ctx):
player = await lavalink.connect(
ctx.author.voice.channel,
deafen=await self.config.guild_from_id(ctx.guild.id).auto_deafen(),
self_deaf=await self.config.guild_from_id(ctx.guild.id).auto_deafen(),
)
player.store("notify_channel", ctx.channel.id)
else:
@ -675,7 +675,7 @@ class PlayerControllerCommands(MixinMeta, metaclass=CompositeMetaClass):
)
await player.move_to(
ctx.author.voice.channel,
deafen=await self.config.guild_from_id(ctx.guild.id).auto_deafen(),
self_deaf=await self.config.guild_from_id(ctx.guild.id).auto_deafen(),
)
await ctx.tick()
except AttributeError:

View File

@ -85,7 +85,7 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
)
await lavalink.connect(
ctx.author.voice.channel,
deafen=await self.config.guild_from_id(ctx.guild.id).auto_deafen(),
self_deaf=await self.config.guild_from_id(ctx.guild.id).auto_deafen(),
)
except AttributeError:
return await self.send_embed_msg(
@ -193,7 +193,7 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
)
await lavalink.connect(
ctx.author.voice.channel,
deafen=await self.config.guild_from_id(ctx.guild.id).auto_deafen(),
self_deaf=await self.config.guild_from_id(ctx.guild.id).auto_deafen(),
)
except AttributeError:
return await self.send_embed_msg(
@ -456,7 +456,7 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
)
await lavalink.connect(
ctx.author.voice.channel,
deafen=await self.config.guild_from_id(ctx.guild.id).auto_deafen(),
self_deaf=await self.config.guild_from_id(ctx.guild.id).auto_deafen(),
)
except AttributeError:
return await self.send_embed_msg(
@ -572,7 +572,7 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
)
await lavalink.connect(
ctx.author.voice.channel,
deafen=await self.config.guild_from_id(ctx.guild.id).auto_deafen(),
self_deaf=await self.config.guild_from_id(ctx.guild.id).auto_deafen(),
)
except AttributeError:
return await self.send_embed_msg(
@ -697,7 +697,7 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
)
await lavalink.connect(
ctx.author.voice.channel,
deafen=await self.config.guild_from_id(ctx.guild.id).auto_deafen(),
self_deaf=await self.config.guild_from_id(ctx.guild.id).auto_deafen(),
)
except AttributeError:
return await self.send_embed_msg(

View File

@ -344,7 +344,7 @@ class QueueCommands(MixinMeta, metaclass=CompositeMetaClass):
)
player = await lavalink.connect(
ctx.author.voice.channel,
deafen=await self.config.guild_from_id(ctx.guild.id).auto_deafen(),
self_deaf=await self.config.guild_from_id(ctx.guild.id).auto_deafen(),
)
player.store("notify_channel", ctx.channel.id)
except AttributeError:

View File

@ -62,7 +62,7 @@ HUMANIZED_PERM = {
"manage_roles": _("Manage Roles"),
"manage_webhooks": _("Manage Webhooks"),
"manage_emojis": _("Manage Emojis"),
"use_slash_commands": _("Use Slash Commands"),
"use_application_commands": _("Use Application Commands"),
"request_to_speak": _("Request to Speak"),
"manage_events": _("Manage Events"),
"manage_threads": _("Manage Threads"),
@ -187,7 +187,7 @@ class DpyEvents(MixinMeta, metaclass=CompositeMetaClass):
)
surpass_ignore = (
isinstance(ctx.channel, discord.abc.PrivateChannel)
ctx.guild is None
or await ctx.bot.is_owner(ctx.author)
or await ctx.bot.is_admin(ctx.author)
)
@ -210,7 +210,7 @@ class DpyEvents(MixinMeta, metaclass=CompositeMetaClass):
)
raise CheckFailure(message=text)
current_perms = ctx.channel.permissions_for(ctx.me)
current_perms = ctx.bot_permissions
if guild and not current_perms.is_superset(self.permission_cache):
current_perms_set = set(iter(current_perms))
expected_perms_set = set(iter(self.permission_cache))

View File

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

View File

@ -138,7 +138,7 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
if not (perms.connect and perms.speak):
vc = None
break
player = await lavalink.connect(vc, deafen=auto_deafen)
player = await lavalink.connect(vc, self_deaf=auto_deafen)
player.store("notify_channel", notify_channel_id)
break
except NodeNotFound:
@ -222,7 +222,7 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
if not (perms.connect and perms.speak):
vc = None
break
player = await lavalink.connect(vc, deafen=auto_deafen)
player = await lavalink.connect(vc, self_deaf=auto_deafen)
player.store("notify_channel", notify_channel_id)
break
except NodeNotFound:

View File

@ -100,7 +100,7 @@ class FormattingUtilities(MixinMeta, metaclass=CompositeMetaClass):
try:
await lavalink.connect(
ctx.author.voice.channel,
deafen=await self.config.guild_from_id(ctx.guild.id).auto_deafen(),
self_deaf=await self.config.guild_from_id(ctx.guild.id).auto_deafen(),
)
except AttributeError:
return await self.send_embed_msg(ctx, title=_("Connect to a voice channel first."))

View File

@ -99,7 +99,9 @@ class MiscellaneousUtilities(MixinMeta, metaclass=CompositeMetaClass):
embed.set_author(name=name)
return await ctx.send(embed=embed)
def _has_notify_perms(self, channel: Union[discord.TextChannel, discord.Thread]) -> bool:
def _has_notify_perms(
self, channel: Union[discord.TextChannel, discord.VoiceChannel, discord.Thread]
) -> bool:
perms = channel.permissions_for(channel.guild.me)
return all((can_user_send_messages_in(channel.guild.me, channel), perms.embed_links))

View File

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

View File

@ -545,7 +545,7 @@ class PlaylistUtilities(MixinMeta, metaclass=CompositeMetaClass):
return False
await lavalink.connect(
ctx.author.voice.channel,
deafen=await self.config.guild_from_id(ctx.guild.id).auto_deafen(),
self_deaf=await self.config.guild_from_id(ctx.guild.id).auto_deafen(),
)
except NodeNotFound:
await self.send_embed_msg(

View File

@ -60,7 +60,9 @@ class ValidationUtilities(MixinMeta, metaclass=CompositeMetaClass):
async def is_query_allowed(
self,
config: Config,
ctx_or_channel: Optional[Union[Context, discord.TextChannel, discord.Thread]],
ctx_or_channel: Optional[
Union[Context, discord.TextChannel, discord.VoiceChannel, discord.Thread]
],
query: str,
query_obj: Query,
) -> bool:

View File

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

View File

@ -181,7 +181,7 @@ class Economy(commands.Cog):
pass
@_bank.command()
async def balance(self, ctx: commands.Context, user: discord.Member = None):
async def balance(self, ctx: commands.Context, user: discord.Member = commands.Author):
"""Show the user's account balance.
Example:
@ -192,9 +192,6 @@ class Economy(commands.Cog):
- `<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)
currency = await bank.get_currency_name(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."))
@_filter_channel.command(name="add", require_var_positional=True)
async def filter_channel_add(self, ctx: commands.Context, *words: str):
async def filter_channel_add(
self,
ctx: commands.Context,
channel: Union[discord.TextChannel, discord.VoiceChannel, discord.ForumChannel],
*words: str,
):
"""Add words to the filter.
Use double quotes to add sentences.
Examples:
- `[p]filter channel add word1 word2 word3`
- `[p]filter channel add "This is a sentence"`
- `[p]filter channel add #channel word1 word2 word3`
- `[p]filter channel add #channel "This is a sentence"`
**Arguments:**
- `<channel>` The text, voice, or forum channel to add filtered words to.
- `[words...]` The words or sentences to filter.
"""
channel = ctx.channel
if isinstance(channel, discord.Thread):
await ctx.send(
_(
"Threads can't have a filter list set up. If you want to add words to"
" the list of the parent channel, send the command in that channel."
)
)
return
added = await self.add_to_filter(channel, words)
if added:
self.invalidate_cache(ctx.guild, ctx.channel)
@ -281,28 +278,25 @@ class Filter(commands.Cog):
await ctx.send(_("Words already in the filter."))
@_filter_channel.command(name="delete", aliases=["remove", "del"], require_var_positional=True)
async def filter_channel_remove(self, ctx: commands.Context, *words: str):
async def filter_channel_remove(
self,
ctx: commands.Context,
channel: Union[discord.TextChannel, discord.VoiceChannel, discord.ForumChannel],
*words: str,
):
"""Remove words from the filter.
Use double quotes to remove sentences.
Examples:
- `[p]filter channel remove word1 word2 word3`
- `[p]filter channel remove "This is a sentence"`
- `[p]filter channel remove #channel word1 word2 word3`
- `[p]filter channel remove #channel "This is a sentence"`
**Arguments:**
- `<channel>` The text, voice, or forum channel to add filtered words to.
- `[words...]` The words or sentences to no longer filter.
"""
channel = ctx.channel
if isinstance(channel, discord.Thread):
await ctx.send(
_(
"Threads can't have a filter list set up. If you want to remove words from"
" the list of the parent channel, send the command in that channel."
)
)
return
removed = await self.remove_from_filter(channel, words)
if removed:
await ctx.send(_("Words removed from filter."))
@ -371,7 +365,11 @@ class Filter(commands.Cog):
await ctx.send(_("Names and nicknames will now be filtered."))
def invalidate_cache(
self, guild: discord.Guild, channel: Optional[discord.TextChannel] = None
self,
guild: discord.Guild,
channel: Optional[
Union[discord.TextChannel, discord.VoiceChannel, discord.ForumChannel]
] = None,
) -> None:
"""Invalidate a cached pattern"""
self.pattern_cache.pop((guild.id, channel and channel.id), None)
@ -381,7 +379,11 @@ class Filter(commands.Cog):
self.pattern_cache.pop(keyset, None)
async def add_to_filter(
self, server_or_channel: Union[discord.Guild, discord.TextChannel], words: list
self,
server_or_channel: Union[
discord.Guild, discord.TextChannel, discord.VoiceChannel, discord.ForumChannel
],
words: list,
) -> bool:
added = False
if isinstance(server_or_channel, discord.Guild):
@ -391,7 +393,7 @@ class Filter(commands.Cog):
cur_list.append(w.lower())
added = True
elif isinstance(server_or_channel, discord.TextChannel):
else:
async with self.config.channel(server_or_channel).filter() as cur_list:
for w in words:
if w.lower() not in cur_list and w:
@ -401,7 +403,11 @@ class Filter(commands.Cog):
return added
async def remove_from_filter(
self, server_or_channel: Union[discord.Guild, discord.TextChannel], words: list
self,
server_or_channel: Union[
discord.Guild, discord.TextChannel, discord.VoiceChannel, discord.ForumChannel
],
words: list,
) -> bool:
removed = False
if isinstance(server_or_channel, discord.Guild):
@ -411,7 +417,7 @@ class Filter(commands.Cog):
cur_list.remove(w.lower())
removed = True
elif isinstance(server_or_channel, discord.TextChannel):
else:
async with self.config.channel(server_or_channel).filter() as cur_list:
for w in words:
if w.lower() in cur_list:
@ -423,7 +429,9 @@ class Filter(commands.Cog):
async def filter_hits(
self,
text: str,
server_or_channel: Union[discord.Guild, discord.TextChannel, discord.Thread],
server_or_channel: Union[
discord.Guild, discord.TextChannel, discord.VoiceChannel, discord.Thread
],
) -> Set[str]:
if isinstance(server_or_channel, discord.Guild):
guild = server_or_channel

View File

@ -1,3 +1,4 @@
import discord
import re
from .abc import MixinMeta
from datetime import timedelta
@ -24,11 +25,14 @@ class Slowmode(MixinMeta):
minimum=timedelta(seconds=0), maximum=timedelta(hours=6), default_unit="seconds"
) = timedelta(seconds=0),
):
"""Changes thread's or channel's slowmode setting.
"""Changes thread's or text channel's slowmode setting.
Interval can be anything from 0 seconds to 6 hours.
Use without parameters to disable.
"""
if not isinstance(ctx.channel, (discord.TextChannel, discord.Thread)):
await ctx.send(_("Slowmode can only be set in text channels and threads."))
return
seconds = interval.total_seconds()
await ctx.channel.edit(slowmode_delay=seconds)
if seconds > 0:

View File

@ -52,6 +52,11 @@ MUTE_UNMUTE_ISSUES = {
"voice_mute_permission": _(
"Because I don't have the Move Members permission, this will take into effect when the user rejoins."
),
"is_not_voice_mute": _(
"That user is channel muted in their current voice channel, not just voice muted."
" If you want to fully unmute this user in the channel,"
" use {command} in their voice channel's text channel instead."
),
}
_ = T_
@ -503,7 +508,7 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
del muted_users[str(member.id)]
if success["success"]:
if create_case:
if isinstance(channel, discord.VoiceChannel):
if data.get("voice_mute", False):
unmute_type = "vunmute"
notification_title = _("Voice unmute")
else:
@ -692,16 +697,21 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
o.id: {name: attr for name, attr in p} for o, p in after.overwrites.items()
}
to_del: List[int] = []
for user_id in self._channel_mutes[after.id].keys():
for user_id, mute_data in self._channel_mutes[after.id].items():
unmuted = False
voice_mute = mute_data.get("voice_mute", False)
if user_id in after_perms:
for perm_name in (
"send_messages",
"send_messages_in_threads",
"create_public_threads",
"create_private_threads",
"speak",
):
perms_to_check = ["speak"]
if not voice_mute:
perms_to_check.extend(
(
"send_messages",
"send_messages_in_threads",
"create_public_threads",
"create_private_threads",
)
)
for perm_name in perms_to_check:
unmuted = unmuted or after_perms[user_id][perm_name] is not False
# explicit is better than implicit :thinkies:
if user_id in before_perms and (user_id not in after_perms or unmuted):
@ -713,7 +723,7 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
log.debug(f"{user} - {type(user)}")
to_del.append(user_id)
log.debug("creating case")
if isinstance(after, discord.VoiceChannel):
if voice_mute:
unmute_type = "vunmute"
notification_title = _("Voice unmute")
else:
@ -848,7 +858,9 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
@muteset.command(name="notification")
@checks.admin_or_permissions(manage_channels=True)
async def notification_channel_set(
self, ctx: commands.Context, channel: Optional[discord.TextChannel] = None
self,
ctx: commands.Context,
channel: Optional[Union[discord.TextChannel, discord.VoiceChannel]] = None,
):
"""
Set the notification channel for automatic unmute issues.
@ -932,6 +944,7 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
send_messages_in_threads=False,
create_public_threads=False,
create_private_threads=False,
use_application_commands=False,
speak=False,
add_reactions=False,
)
@ -979,6 +992,7 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
overs.send_messages_in_threads = False
overs.create_public_threads = False
overs.create_private_threads = False
overs.use_application_commands = False
overs.add_reactions = False
overs.speak = False
try:
@ -1681,6 +1695,8 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
user: discord.Member,
until: Optional[datetime] = None,
reason: Optional[str] = None,
*,
voice_mute: bool = False,
) -> Dict[str, Optional[Union[discord.abc.GuildChannel, str, bool]]]:
"""Mutes the specified user in the specified channel"""
overwrites = channel.overwrites_for(user)
@ -1693,16 +1709,7 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
"reason": _(MUTE_UNMUTE_ISSUES["is_admin"]),
}
new_overs: dict = {}
move_channel = False
new_overs.update(
send_messages=False,
send_messages_in_threads=False,
create_public_threads=False,
create_private_threads=False,
add_reactions=False,
speak=False,
)
send_reason = None
if user.voice and user.voice.channel:
if channel.permissions_for(guild.me).move_members:
@ -1717,16 +1724,38 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
"reason": _(MUTE_UNMUTE_ISSUES["hierarchy_problem"]),
}
old_overs = {k: getattr(overwrites, k) for k in new_overs}
overwrites.update(**new_overs)
if channel.id not in self._channel_mutes:
self._channel_mutes[channel.id] = {}
if user.id in self._channel_mutes[channel.id]:
current_mute = self._channel_mutes.get(channel.id)
# Determine if this is voice mute -> channel mute upgrade
is_mute_upgrade = (
current_mute is not None and not voice_mute and current_mute.get("voice_mute", False)
)
# We want to continue if this is a new mute or a mute upgrade,
# otherwise we should return with failure.
if current_mute is not None and not is_mute_upgrade:
return {
"success": False,
"channel": channel,
"reason": _(MUTE_UNMUTE_ISSUES["already_muted"]),
}
new_overs: Dict[str, Optional[bool]] = {"speak": False}
if not voice_mute:
new_overs.update(
send_messages=False,
send_messages_in_threads=False,
create_public_threads=False,
create_private_threads=False,
use_application_commands=False,
add_reactions=False,
)
old_overs = {k: getattr(overwrites, k) for k in new_overs}
if is_mute_upgrade:
perms_cache = await self.config.member(user).perms_cache()
if "speak" in perms_cache:
old_overs["speak"] = perms_cache["speak"]
overwrites.update(**new_overs)
if not channel.permissions_for(guild.me).manage_permissions:
return {
"success": False,
@ -1738,6 +1767,7 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
"guild": guild.id,
"member": user.id,
"until": until.timestamp() if until else None,
"voice_mute": voice_mute,
}
try:
await channel.set_permissions(user, overwrite=overwrites, reason=reason)
@ -1795,6 +1825,8 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
author: discord.Member,
user: discord.Member,
reason: Optional[str] = None,
*,
voice_mute: bool = False,
) -> Dict[str, Optional[Union[discord.abc.GuildChannel, str, bool]]]:
"""Unmutes the specified user in a specified channel"""
overwrites = channel.overwrites_for(user)
@ -1809,6 +1841,7 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
"send_messages_in_threads": None,
"create_public_threads": None,
"create_private_threads": None,
"use_application_commands": None,
"add_reactions": None,
"speak": None,
}
@ -1826,13 +1859,21 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
overwrites.update(**old_values)
if channel.id in self._channel_mutes and user.id in self._channel_mutes[channel.id]:
del self._channel_mutes[channel.id][user.id]
current_mute = self._channel_mutes[channel.id].pop(user.id)
else:
return {
"success": False,
"channel": channel,
"reason": _(MUTE_UNMUTE_ISSUES["already_unmuted"]),
}
if not current_mute["voice_mute"] and voice_mute:
return {
"success": False,
"channel": channel,
"reason": _(MUTE_UNMUTE_ISSUES["is_not_voice_mute"]).format(
command=inline("unmutechannel")
),
}
if not channel.permissions_for(guild.me).manage_permissions:
return {
"success": False,

View File

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

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

View File

@ -106,7 +106,9 @@ class Reports(commands.Cog):
@checks.admin_or_permissions(manage_guild=True)
@reportset.command(name="output")
async def reportset_output(self, ctx: commands.Context, channel: discord.TextChannel):
async def reportset_output(
self, ctx: commands.Context, channel: Union[discord.TextChannel, discord.VoiceChannel]
):
"""Set the channel where reports will be sent."""
await self.config.guild(ctx.guild).output_channel.set(channel.id)
await ctx.send(_("The report channel has been set."))
@ -325,7 +327,7 @@ class Reports(commands.Cog):
if ctx.author.id in self.user_cache:
self.user_cache.remove(ctx.author.id)
if ctx.guild and ctx.invoked_subcommand is None:
if ctx.channel.permissions_for(ctx.guild.me).manage_messages:
if ctx.bot_permissions.manage_messages:
try:
await ctx.message.delete()
except discord.NotFound:

View File

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

View File

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

View File

@ -156,7 +156,11 @@ class Warnings(commands.Cog):
@warningset.command()
@commands.guild_only()
async def warnchannel(self, ctx: commands.Context, channel: discord.TextChannel = None):
async def warnchannel(
self,
ctx: commands.Context,
channel: Union[discord.TextChannel, discord.VoiceChannel] = None,
):
"""Set the channel where warnings should be sent to.
Leave empty to use the channel `[p]warn` command was called in.

View File

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

View File

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

View File

@ -45,7 +45,6 @@ from .help import (
)
from .requires import (
CheckPredicate as CheckPredicate,
DM_PERMS as DM_PERMS,
GlobalPermissionModel as GlobalPermissionModel,
GuildPermissionModel as GuildPermissionModel,
PermissionModel as PermissionModel,
@ -189,4 +188,14 @@ from discord.ext.commands import (
bot_has_any_role as bot_has_any_role,
before_invoke as before_invoke,
after_invoke as after_invoke,
CurrentChannel as CurrentChannel,
Author as Author,
param as param,
MissingRequiredAttachment as MissingRequiredAttachment,
Parameter as Parameter,
ForumChannelConverter as ForumChannelConverter,
CurrentGuild as CurrentGuild,
Range as Range,
RangeError as RangeError,
parameter as parameter,
)

View File

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

View File

@ -842,12 +842,12 @@ class RedHelpFormatter(HelpFormatterABC):
if (
not use_DMs # we're not in DMs
and delete_delay > 0 # delete delay is enabled
and ctx.channel.permissions_for(ctx.me).manage_messages # we can manage messages
and ctx.bot_permissions.manage_messages # we can manage messages
):
# We need to wrap this in a task to not block after-sending-help interactions.
# The channel has to be TextChannel or Thread as we can't bulk-delete from DMs
async def _delete_delay_help(
channel: Union[discord.TextChannel, discord.Thread],
channel: Union[discord.TextChannel, discord.VoiceChannel, discord.Thread],
messages: List[discord.Message],
delay: int,
):

View File

@ -40,7 +40,6 @@ if TYPE_CHECKING:
__all__ = [
"CheckPredicate",
"DM_PERMS",
"GlobalPermissionModel",
"GuildPermissionModel",
"PermissionModel",
@ -75,6 +74,7 @@ GlobalPermissionModel = Union[
discord.User,
discord.VoiceChannel,
discord.TextChannel,
discord.ForumChannel,
discord.CategoryChannel,
discord.Role,
discord.Guild,
@ -83,6 +83,7 @@ GuildPermissionModel = Union[
discord.Member,
discord.VoiceChannel,
discord.TextChannel,
discord.ForumChannel,
discord.CategoryChannel,
discord.Role,
discord.Guild,
@ -90,22 +91,6 @@ GuildPermissionModel = Union[
PermissionModel = Union[GlobalPermissionModel, GuildPermissionModel]
CheckPredicate = Callable[["Context"], Union[Optional[bool], Awaitable[Optional[bool]]]]
# Here we are trying to model DM permissions as closely as possible. The only
# discrepancy I've found is that users can pin messages, but they cannot delete them.
# This means manage_messages is only half True, so it's left as False.
# This is also the same as the permissions returned when `permissions_for` is used in DM.
DM_PERMS = discord.Permissions.none()
DM_PERMS.update(
add_reactions=True,
attach_files=True,
embed_links=True,
external_emojis=True,
mention_everyone=True,
read_message_history=True,
read_messages=True,
send_messages=True,
)
class PrivilegeLevel(enum.IntEnum):
"""Enumeration for special privileges."""
@ -520,15 +505,11 @@ class Requires:
return await self._transition_state(ctx)
async def _verify_bot(self, ctx: "Context") -> None:
if ctx.guild is None:
bot_user = ctx.bot.user
else:
bot_user = ctx.guild.me
cog = ctx.cog
if cog and await ctx.bot.cog_disabled_in_guild(cog, ctx.guild):
raise discord.ext.commands.DisabledCommand()
cog = ctx.cog
if ctx.guild is not None and cog and await ctx.bot.cog_disabled_in_guild(cog, ctx.guild):
raise discord.ext.commands.DisabledCommand()
bot_perms = ctx.channel.permissions_for(bot_user)
bot_perms = ctx.bot_permissions
if not (bot_perms.administrator or bot_perms >= self.bot_perms):
raise BotMissingPermissions(missing=self._missing_perms(self.bot_perms, bot_perms))
@ -574,7 +555,7 @@ class Requires:
return False
if self.user_perms is not None:
user_perms = ctx.channel.permissions_for(ctx.author)
user_perms = ctx.permissions
if user_perms.administrator or user_perms >= self.user_perms:
return True
@ -633,17 +614,6 @@ class Requires:
return True
return await discord.utils.async_all(check(ctx) for check in self.checks)
@staticmethod
def _get_perms_for(ctx: "Context", user: discord.abc.User) -> discord.Permissions:
if ctx.guild is None:
return DM_PERMS
else:
return ctx.channel.permissions_for(user)
@classmethod
def _get_bot_perms(cls, ctx: "Context") -> discord.Permissions:
return cls._get_perms_for(ctx, ctx.guild.me if ctx.guild else ctx.bot.user)
@staticmethod
def _missing_perms(
required: discord.Permissions, actual: discord.Permissions
@ -656,13 +626,6 @@ class Requires:
relative_complement = required.value & ~actual.value
return discord.Permissions(relative_complement)
@staticmethod
def _member_as_user(member: discord.abc.User) -> discord.User:
if isinstance(member, discord.Member):
# noinspection PyProtectedMember
return member._user
return member
def __repr__(self) -> str:
return (
f"<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.
if not ctx.channel.permissions_for(ctx.me).attach_files:
if not ctx.bot_permissions.attach_files:
ctx.command.reset_cooldown(ctx)
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")
@checks.guildowner_or_permissions(administrator=True)
@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.
@ -1361,27 +1366,20 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
To see full evaluation order of embed settings, run `[p]help embedset`.
**Examples:**
- `[p]embedset channel False` - Disables embeds in this channel.
- `[p]embedset channel` - Resets value to use guild default.
- `[p]embedset channel #text-channel False` - Disables embeds in the #text-channel.
- `[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:**
- `<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.
"""
if isinstance(ctx.channel, discord.Thread):
await ctx.send(
_(
"This setting cannot be set for threads. If you want to set this for"
" the parent channel, send the command in that channel."
)
)
return
if enabled is None:
await self.bot._config.channel(ctx.channel).embeds.clear()
await self.bot._config.channel(channel).embeds.clear()
await ctx.send(_("Embeds will now fall back to the global setting."))
return
await self.bot._config.channel(ctx.channel).embeds.set(enabled)
await self.bot._config.channel(channel).embeds.set(enabled)
await ctx.send(
_("Embeds are now {} for this channel.").format(
_("enabled") if enabled else _("disabled")
@ -2247,7 +2245,11 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
@modlogset.command(aliases=["channel"], name="modlog")
@commands.guild_only()
async def modlogset_modlog(self, ctx: commands.Context, channel: discord.TextChannel = None):
async def modlogset_modlog(
self,
ctx: commands.Context,
channel: Union[discord.TextChannel, discord.VoiceChannel] = None,
):
"""Set a channel as the modlog.
Omit `[channel]` to disable the modlog.
@ -3131,7 +3133,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
view=SetApiView(default_service=service),
)
else:
if ctx.channel.permissions_for(ctx.me).manage_messages:
if ctx.bot_permissions.manage_messages:
await ctx.message.delete()
await ctx.bot.set_shared_api_tokens(service, **tokens)
await ctx.send(_("`{service}` API tokens have been set.").format(service=service))
@ -3241,7 +3243,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
@_set_ownernotifications.command(name="adddestination")
async def _set_ownernotifications_adddestination(
self, ctx: commands.Context, *, channel: discord.TextChannel
self, ctx: commands.Context, *, channel: Union[discord.TextChannel, discord.VoiceChannel]
):
"""
Adds a destination text channel to receive owner notifications.
@ -3263,7 +3265,10 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
name="removedestination", aliases=["remdestination", "deletedestination", "deldestination"]
)
async def _set_ownernotifications_removedestination(
self, ctx: commands.Context, *, channel: Union[discord.TextChannel, int]
self,
ctx: commands.Context,
*,
channel: Union[discord.TextChannel, discord.VoiceChannel, int],
):
"""
Removes a destination text channel from receiving owner notifications.
@ -4136,8 +4141,11 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
async def diagnoseissues(
self,
ctx: commands.Context,
channel: Optional[Union[discord.TextChannel, discord.Thread]],
member: Union[discord.Member, discord.User],
channel: Optional[
Union[discord.TextChannel, discord.VoiceChannel, discord.Thread]
] = commands.CurrentChannel,
# avoid non-default argument following default argument by using empty param()
member: Union[discord.Member, discord.User] = commands.param(),
*,
command_name: str,
) -> None:
@ -4155,16 +4163,14 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
- `<member>` - The member that should be considered as the command caller.
- `<command_name>` - The name of the command to test.
"""
if channel is None:
channel = ctx.channel
if not isinstance(channel, (discord.TextChannel, discord.Thread)):
await ctx.send(
_(
"The text channel or thread needs to be passed"
" when using this command in DMs."
)
if ctx.guild is None:
await ctx.send(
_(
"A text channel, voice channel, or thread needs to be passed"
" when using this command in DMs."
)
return
)
return
command = self.bot.get_command(command_name)
if command is None:
@ -5124,9 +5130,13 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
async def ignore_channel(
self,
ctx: commands.Context,
channel: Optional[
Union[discord.TextChannel, discord.CategoryChannel, discord.Thread]
] = None,
channel: Union[
discord.TextChannel,
discord.VoiceChannel,
discord.ForumChannel,
discord.CategoryChannel,
discord.Thread,
] = commands.CurrentChannel,
):
"""
Ignore commands in the channel, thread, or category.
@ -5144,8 +5154,6 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
**Arguments:**
- `<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):
await self.bot._ignored_cache.set_ignored_channel(channel, True)
await ctx.send(_("Channel added to ignore list."))
@ -5180,9 +5188,13 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
async def unignore_channel(
self,
ctx: commands.Context,
channel: Optional[
Union[discord.TextChannel, discord.CategoryChannel, discord.Thread]
] = None,
channel: Union[
discord.TextChannel,
discord.VoiceChannel,
discord.ForumChannel,
discord.CategoryChannel,
discord.Thread,
] = commands.CurrentChannel,
):
"""
Remove a channel, thread, or category from the ignore list.
@ -5198,9 +5210,6 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
**Arguments:**
- `<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):
await self.bot._ignored_cache.set_ignored_channel(channel, False)
await ctx.send(_("Channel removed from ignore list."))
@ -5225,7 +5234,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
async def count_ignored(self, ctx: commands.Context):
category_channels: List[discord.CategoryChannel] = []
text_channels: List[discord.TextChannel] = []
channels: List[Union[discord.TextChannel, discord.VoiceChannel, discord.ForumChannel]] = []
threads: List[discord.Thread] = []
if await self.bot._ignored_cache.get_ignored_guild(ctx.guild):
return _("This server is currently being ignored.")
@ -5234,7 +5243,19 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
if await self.bot._ignored_cache.get_ignored_channel(channel.category):
category_channels.append(channel.category)
if await self.bot._ignored_cache.get_ignored_channel(channel, check_category=False):
text_channels.append(channel)
channels.append(channel)
for channel in ctx.guild.voice_channels:
if channel.category and channel.category not in category_channels:
if await self.bot._ignored_cache.get_ignored_channel(channel.category):
category_channels.append(channel.category)
if await self.bot._ignored_cache.get_ignored_channel(channel, check_category=False):
channels.append(channel)
for channel in ctx.guild.forum_channels:
if channel.category and channel.category not in category_channels:
if await self.bot._ignored_cache.get_ignored_channel(channel.category):
category_channels.append(channel.category)
if await self.bot._ignored_cache.get_ignored_channel(channel, check_category=False):
channels.append(channel)
for thread in ctx.guild.threads:
if await self.bot._ignored_cache.get_ignored_channel(thread, check_category=False):
threads.append(thread)
@ -5242,9 +5263,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
cat_str = (
humanize_list([c.name for c in category_channels]) if category_channels else _("None")
)
chan_str = (
humanize_list([c.mention for c in text_channels]) if text_channels else _("None")
)
chan_str = humanize_list([c.mention for c in channels]) if channels else _("None")
thread_str = humanize_list([c.mention for c in threads]) if threads else _("None")
msg = _(
"Currently ignored categories: {categories}\n"
@ -5255,7 +5274,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
# Removing this command from forks is a violation of the GPLv3 under which it is licensed.
# Otherwise interfering with the ability for this command to be accessible is also a violation.
@commands.cooldown(1, 180, lambda msg: (msg.channel.id, msg.author.id))
@commands.cooldown(1, 180, lambda ctx: (ctx.message.channel.id, ctx.message.author.id))
@commands.command(
cls=commands.commands._AlwaysAvailableCommand,
name="licenseinfo",

View File

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

View File

@ -349,12 +349,12 @@ class Case:
self.message = message
@property
def parent_channel(self) -> Optional[discord.TextChannel]:
def parent_channel(self) -> Optional[Union[discord.TextChannel, discord.ForumChannel]]:
"""
The parent text channel of the thread in `channel`.
The parent text/forum channel of the thread in `channel`.
This will be `None` if `channel` is not a thread
and when the parent text channel is not in cache (probably due to removal).
and when the parent text/forum channel is not in cache (probably due to removal).
"""
if self.parent_channel_id is None:
return None
@ -645,13 +645,18 @@ class Case:
@classmethod
async def from_json(
cls, mod_channel: discord.TextChannel, bot: Red, case_number: int, data: dict, **kwargs
cls,
mod_channel: Union[discord.TextChannel, discord.VoiceChannel],
bot: Red,
case_number: int,
data: dict,
**kwargs,
):
"""Get a Case object from the provided information
Parameters
----------
mod_channel: discord.TextChannel
mod_channel: `discord.TextChannel` or `discord.VoiceChannel`
The mod log channel for the guild
bot: Red
The bot's instance. Needed to get the target user
@ -1228,7 +1233,9 @@ async def register_casetypes(new_types: List[dict]) -> List[CaseType]:
return type_list
async def get_modlog_channel(guild: discord.Guild) -> discord.TextChannel:
async def get_modlog_channel(
guild: discord.Guild,
) -> Union[discord.TextChannel, discord.VoiceChannel]:
"""
Get the current modlog channel.
@ -1239,7 +1246,7 @@ async def get_modlog_channel(guild: discord.Guild) -> discord.TextChannel:
Returns
-------
`discord.TextChannel`
`discord.TextChannel` or `discord.VoiceChannel`
The channel object representing the modlog channel.
Raises
@ -1259,7 +1266,7 @@ async def get_modlog_channel(guild: discord.Guild) -> discord.TextChannel:
async def set_modlog_channel(
guild: discord.Guild, channel: Union[discord.TextChannel, None]
guild: discord.Guild, channel: Union[discord.TextChannel, discord.VoiceChannel, None]
) -> bool:
"""
Changes the modlog channel
@ -1268,7 +1275,7 @@ async def set_modlog_channel(
----------
guild: `discord.Guild`
The guild to set a mod log channel for
channel: `discord.TextChannel` or `None`
channel: `discord.TextChannel`, `discord.VoiceChannel`, or `None`
The channel to be set as modlog channel
Returns

View File

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

View File

@ -34,7 +34,9 @@ from discord.utils import maybe_coroutine
from redbot.core import commands
if TYPE_CHECKING:
GuildMessageable = Union[commands.GuildContext, discord.abc.GuildChannel, discord.Thread]
GuildMessageable = Union[
commands.GuildContext, discord.TextChannel, discord.VoiceChannel, discord.Thread
]
DMMessageable = Union[commands.DMContext, discord.Member, discord.User, discord.DMChannel]
__all__ = (

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -51,7 +51,7 @@ install_requires =
colorama==0.4.4
commonmark==0.9.1
contextlib2==21.6.0
discord.py @ git+https://github.com/Rapptz/discord.py@3d914e08e0c7df370987592affb3655d2a12f7d1#egg=discord.py
discord.py==2.0.1
distro==1.6.0; sys_platform == "linux"
fuzzywuzzy==0.18.0
idna==3.2
@ -66,7 +66,7 @@ install_requires =
pytz==2021.1
PyYAML==5.4.1
Red-Commons==1.0.0
Red-Lavalink==0.11.0rc0
Red-Lavalink==0.11.0rc1
rich==10.9.0
schema==0.7.4
six==1.16.0

View File

@ -50,8 +50,21 @@ def test_dpy_commands_reexports():
dpy_attrs.add(attr_name)
missing_attrs = dpy_attrs - set(commands.__dict__.keys())
# temporarily ignore things related to app commands as the work on that is done separately
missing_attrs -= {
"GroupCog",
"HybridGroup",
"hybrid_group",
"hybrid_command",
"HybridCommand",
"HybridCommandError",
}
assert not missing_attrs
if missing_attrs:
pytest.fail(
"redbot.core.commands is missing these names from discord.ext.commands: "
+ ", ".join(missing_attrs)
)
def test_converter_timedelta():