mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-12-21 16:52:31 -05:00
Migration to discord.py 2.0 (#5600)
* Temporarily set d.py to use latest git revision
* Remove `bot` param to Client.start
* Switch to aware datetimes
A lot of this is removing `.replace(...)` which while not technically
needed, simplifies the code base. There's only a few changes that are
actually necessary here.
* Update to work with new Asset design
* [threads] Update core ModLog API to support threads
- Added proper support for passing `Thread` to `channel`
when creating/editing case
- Added `parent_channel_id` attribute to Modlog API's Case
- Added `parent_channel` property that tries to get parent channel
- Updated case's content to show both thread and parent information
* [threads] Disallow usage of threads in some of the commands
- announceset channel
- filter channel clear
- filter channel add
- filter channel remove
- GlobalUniqueObjectFinder converter
- permissions addglobalrule
- permissions removeglobalrule
- permissions removeserverrule
- Permissions cog does not perform any validation for IDs
when setting through YAML so that has not been touched
- streamalert twitch/youtube/picarto
- embedset channel
- set ownernotifications adddestination
* [threads] Handle threads in Red's permissions system (Requires)
- Made permissions system apply rules of (only) parent in threads
* [threads] Update embed_requested to support threads
- Threads don't have their own embed settings and inherit from parent
* [threads] Update Red.message_eligible_as_command to support threads
* [threads] Properly handle invocation of [p](un)mutechannel in threads
Usage of a (un)mutechannel will mute/unmute user in the parent channel
if it's invoked in a thread.
* [threads] Update Filter cog to properly handle threads
- `[p]filter channel list` in a threads sends list for parent channel
- Checking for filter hits for a message in a thread checks its parent
channel's word list. There's no separate word list for threads.
* [threads] Support threads in Audio cog
- Handle threads being notify channels
- Update type hint for `is_query_allowed()`
* [threads] Update type hints and documentation to reflect thread support
- Documented that `{channel}` in CCs might be a thread
- Allowed (documented) usage of threads with `Config.channel()`
- Separate thread scope is still in the picture though
if it were to be done, it's going to be in separate in PR
- GuildContext.channel might be Thread
* Use less costy channel check in customcom's on_message_without_command
This isn't needed for d.py 2.0 but whatever...
* Update for in-place edits
* Embed's bool changed behavior, I'm hoping it doesn't affect us
* Address User.permissions_in() removal
* Swap VerificationLevel.extreme with VerificationLevel.highest
* Change to keyword-only parameters
* Change of `Guild.vanity_invite()` return type
* avatar -> display_avatar
* Fix metaclass shenanigans with Converter
* Update Red.add_cog() to be inline with `dpy_commands.Bot.add_cog()`
This means adding `override` keyword-only parameter and causing
small breakage by swapping RuntimeError with discord.ClientException.
* Address all DEP-WARNs
* Remove Context.clean_prefix and use upstream implementation instead
* Remove commands.Literal and use upstream implementation instead
Honestly, this was a rather bad implementation anyway...
Breaking but actually not really - it was provisional.
* Update Command.callback's setter
Support for functools.partial is now built into d.py
* Add new perms in HUMANIZED_PERM mapping (some from d.py 1.7 it seems)
BTW, that should really be in core instead of what we have now...
* Remove the part of do_conversion that has not worked for a long while
* Stop wrapping BadArgument in ConversionFailure
This is breaking but it's best to resolve it like this.
The functionality of ConversionFailure can be replicated with
Context.current_parameter and Context.current_argument.
* Add custom errors for int and float converters
* Remove Command.__call__ as it's now implemented in d.py
* Get rid of _dpy_reimplements
These were reimplemented for the purpose of typing
so it is no longer needed now that d.py is type hinted.
* Add return to Red.remove_cog
* Ensure we don't delete messages that differ only by used sticker
* discord.InvalidArgument->ValueError
* Move from raw <t:...> syntax to discord.utils.format_dt()
* Address AsyncIter removal
* Swap to pos-only for params that are pos-only in upstream
* Update for changes to Command.params
* [threads] Support threads in ignore checks and allow ignoring them
- Updated `[p](un)ignore channel` to accept threads
- Updated `[p]ignore list` to list ignored threads
- Updated logic in `Red.ignored_channel_or_guild()`
Ignores for guild channels now work as follows (only changes for threads):
- if channel is not a thread:
- check if user has manage channels perm in channel
and allow command usage if so
- check if channel is ignored and disallow command usage if so
- allow command usage if none of the conditions above happened
- if channel is a thread:
- check if user has manage channels perm in parent channel
and allow command usage if so
- check if parent channel is ignored and disallow command usage
if so
- check if user has manage thread perm in parent channel
and allow command usage if so
- check if thread is ignored and disallow command usage if so
- allow command usage if none of the conditions above happened
* [partial] Raise TypeError when channel is of PartialMessageable type
- Red.embed_requested
- Red.ignored_channel_or_guild
* [partial] Discard command messages when channel is PartialMessageable
* [threads] Add utilities for checking appropriate perms in both channels & threads
* [threads] Update code to use can_react_in() and @bot_can_react()
* [threads] Update code to use can_send_messages_in
* [threads] Add send_messages_in_threads perm to mute role and overrides
* [threads] Update code to use (bot/user)_can_manage_channel
* [threads] Update [p]diagnoseissues to work with threads
* Type hint fix
* [threads] Patch vendored discord.ext.menus to check proper perms in threads
I guess we've reached time when we have to patch the lib we vendor...
* Make docs generation work with non-final d.py releases
* Update discord.utils.oauth_url() usage
* Swap usage of discord.Embed.Empty/discord.embeds.EmptyEmbed to None
* Update usage of Guild.member_count to work with `None`
* Switch from Guild.vanity_invite() to Guild.vanity_url
* Update startup process to work with d.py's new asynchronous startup
* Use setup_hook() for pre-connect actions
* Update core's add_cog, remove_cog, and load_extension methods
* Update all setup functions to async and add awaits to bot.add_cog calls
* Modernize cogs by using async cog_load and cog_unload
* Address StoreChannel removal
* [partial] Disallow passing PartialMessageable to Case.channel
* [partial] Update cogs and utils to work better with PartialMessageable
- Ignore messages with PartialMessageable channel in CustomCommands cog
- In Filter cog, don't pass channel to modlog.create_case()
if it's PartialMessageable
- In Trivia cog, only compare channel IDs
- Make `.utils.menus.menu()` work for messages
with PartialMessageable channel
- Make checks in `.utils.tunnel.Tunnel.communicate()` more rigid
* Add few missing DEP-WARNs
This commit is contained in:
@@ -106,7 +106,7 @@ async def _init(bot: Red):
|
||||
except RuntimeError:
|
||||
return # No modlog channel so no point in continuing
|
||||
|
||||
when = datetime.utcnow()
|
||||
when = datetime.now(timezone.utc)
|
||||
before = when + timedelta(minutes=1)
|
||||
after = when - timedelta(minutes=1)
|
||||
await asyncio.sleep(10) # prevent small delays from causing a 5 minute delay on entry
|
||||
@@ -116,9 +116,12 @@ async def _init(bot: Red):
|
||||
while attempts < 12 and guild.me.guild_permissions.view_audit_log:
|
||||
attempts += 1
|
||||
try:
|
||||
entry = await guild.audit_logs(
|
||||
action=discord.AuditLogAction.ban, before=before, after=after
|
||||
).find(lambda e: e.target.id == member.id and after < e.created_at < before)
|
||||
entry = await discord.utils.find(
|
||||
lambda e: e.target.id == member.id and after < e.created_at < before,
|
||||
guild.audit_logs(
|
||||
action=discord.AuditLogAction.ban, before=before, after=after
|
||||
),
|
||||
)
|
||||
except discord.Forbidden:
|
||||
break
|
||||
except discord.HTTPException:
|
||||
@@ -128,7 +131,7 @@ async def _init(bot: Red):
|
||||
if entry.user.id != guild.me.id:
|
||||
# Don't create modlog entires for the bot's own bans, cogs do this.
|
||||
mod, reason = entry.user, entry.reason
|
||||
date = entry.created_at.replace(tzinfo=timezone.utc)
|
||||
date = entry.created_at
|
||||
await create_case(_bot_ref, guild, date, "ban", member, mod, reason)
|
||||
return
|
||||
|
||||
@@ -143,7 +146,7 @@ async def _init(bot: Red):
|
||||
except RuntimeError:
|
||||
return # No modlog channel so no point in continuing
|
||||
|
||||
when = datetime.utcnow()
|
||||
when = datetime.now(timezone.utc)
|
||||
before = when + timedelta(minutes=1)
|
||||
after = when - timedelta(minutes=1)
|
||||
await asyncio.sleep(10) # prevent small delays from causing a 5 minute delay on entry
|
||||
@@ -153,9 +156,12 @@ async def _init(bot: Red):
|
||||
while attempts < 12 and guild.me.guild_permissions.view_audit_log:
|
||||
attempts += 1
|
||||
try:
|
||||
entry = await guild.audit_logs(
|
||||
action=discord.AuditLogAction.unban, before=before, after=after
|
||||
).find(lambda e: e.target.id == user.id and after < e.created_at < before)
|
||||
entry = await discord.utils.find(
|
||||
lambda e: e.target.id == user.id and after < e.created_at < before,
|
||||
guild.audit_logs(
|
||||
action=discord.AuditLogAction.unban, before=before, after=after
|
||||
),
|
||||
)
|
||||
except discord.Forbidden:
|
||||
break
|
||||
except discord.HTTPException:
|
||||
@@ -165,7 +171,7 @@ async def _init(bot: Red):
|
||||
if entry.user.id != guild.me.id:
|
||||
# Don't create modlog entires for the bot's own unbans, cogs do this.
|
||||
mod, reason = entry.user, entry.reason
|
||||
date = entry.created_at.replace(tzinfo=timezone.utc)
|
||||
date = entry.created_at
|
||||
await create_case(_bot_ref, guild, date, "unban", user, mod, reason)
|
||||
return
|
||||
|
||||
@@ -268,13 +274,16 @@ class Case:
|
||||
until: Optional[int]
|
||||
The UNIX time the action is in effect until.
|
||||
`None` if the action is permanent.
|
||||
channel: Optional[Union[discord.abc.GuildChannel, int]]
|
||||
channel: Optional[Union[discord.abc.GuildChannel, discord.Thread, int]]
|
||||
The channel the action was taken in.
|
||||
`None` if the action was not related to a channel.
|
||||
|
||||
.. note::
|
||||
This attribute will be of type `int`
|
||||
if the channel seems to no longer exist.
|
||||
parent_channel_id: Optional[int]
|
||||
The parent channel ID of the thread in ``channel``.
|
||||
`None` if the action was not done in a thread.
|
||||
amended_by: Optional[Union[discord.abc.User, int]]
|
||||
The moderator who made the last change to the case.
|
||||
`None` if the case was never edited.
|
||||
@@ -310,7 +319,8 @@ class Case:
|
||||
case_number: int,
|
||||
reason: Optional[str] = None,
|
||||
until: Optional[int] = None,
|
||||
channel: Optional[Union[discord.abc.GuildChannel, int]] = None,
|
||||
channel: Optional[Union[discord.abc.GuildChannel, discord.Thread, int]] = None,
|
||||
parent_channel_id: Optional[int] = None,
|
||||
amended_by: Optional[Union[discord.Object, discord.abc.User, int]] = None,
|
||||
modified_at: Optional[float] = None,
|
||||
message: Optional[Union[discord.PartialMessage, discord.Message]] = None,
|
||||
@@ -330,6 +340,7 @@ class Case:
|
||||
self.reason = reason
|
||||
self.until = until
|
||||
self.channel = channel
|
||||
self.parent_channel_id = parent_channel_id
|
||||
self.amended_by = amended_by
|
||||
if isinstance(amended_by, discord.Object):
|
||||
self.amended_by = amended_by.id
|
||||
@@ -337,6 +348,18 @@ class Case:
|
||||
self.case_number = case_number
|
||||
self.message = message
|
||||
|
||||
@property
|
||||
def parent_channel(self) -> Optional[discord.TextChannel]:
|
||||
"""
|
||||
The parent text 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).
|
||||
"""
|
||||
if self.parent_channel_id is None:
|
||||
return None
|
||||
return self.guild.get_channel(self.parent_channel_id)
|
||||
|
||||
async def _set_message(self, message: discord.Message, /) -> None:
|
||||
# This should only be used for setting the message right after case creation
|
||||
# in order to avoid making an API request to "edit" the message with changes.
|
||||
@@ -359,6 +382,8 @@ class Case:
|
||||
# last username is set based on passed user object
|
||||
data.pop("last_known_username", None)
|
||||
for item, value in data.items():
|
||||
if item == "channel" and isinstance(value, discord.PartialMessageable):
|
||||
raise TypeError("Can't use PartialMessageable as the channel for a modlog case.")
|
||||
if isinstance(value, discord.Object):
|
||||
# probably expensive to call but meh should capture all cases
|
||||
setattr(self, item, value.id)
|
||||
@@ -369,6 +394,9 @@ class Case:
|
||||
if not isinstance(self.user, int):
|
||||
self.last_known_username = f"{self.user.name}#{self.user.discriminator}"
|
||||
|
||||
if isinstance(self.channel, discord.Thread):
|
||||
self.parent_channel_id = self.channel.parent_id
|
||||
|
||||
await _config.custom(_CASES, str(self.guild.id), str(self.case_number)).set(self.to_json())
|
||||
self.bot.dispatch("modlog_case_edit", self)
|
||||
if not self.message:
|
||||
@@ -443,7 +471,7 @@ class Case:
|
||||
if self.until:
|
||||
start = datetime.fromtimestamp(self.created_at, tz=timezone.utc)
|
||||
end = datetime.fromtimestamp(self.until, tz=timezone.utc)
|
||||
end_fmt = f"<t:{int(end.timestamp())}>"
|
||||
end_fmt = discord.utils.format_dt(end)
|
||||
duration = end - start
|
||||
dur_fmt = _strfdelta(duration)
|
||||
until = end_fmt
|
||||
@@ -463,7 +491,9 @@ class Case:
|
||||
|
||||
last_modified = None
|
||||
if self.modified_at:
|
||||
last_modified = f"<t:{int(self.modified_at)}>"
|
||||
last_modified = discord.utils.format_dt(
|
||||
datetime.fromtimestamp(self.modified_at, tz=timezone.utc)
|
||||
)
|
||||
|
||||
if isinstance(self.user, int):
|
||||
if self.user == 0xDE1:
|
||||
@@ -490,6 +520,31 @@ class Case:
|
||||
)
|
||||
) # Invites and spoilers get rendered even in embeds.
|
||||
|
||||
channel_value = None
|
||||
if isinstance(self.channel, int):
|
||||
if self.parent_channel_id is not None:
|
||||
if (parent_channel := self.parent_channel) is not None:
|
||||
channel_value = _(
|
||||
"Deleted or archived thread ({thread_id}) in {channel_name}"
|
||||
).format(thread_id=self.channel, channel_name=parent_channel)
|
||||
else:
|
||||
channel_value = _("Thread {thread_id} in {channel_id} (deleted)").format(
|
||||
thread_id=self.channel, channel_id=self.parent_channel_id
|
||||
)
|
||||
else:
|
||||
channel_value = _("{channel_id} (deleted)").format(channel_id=self.channel)
|
||||
elif self.channel is not None:
|
||||
channel_value = self.channel.name
|
||||
if self.parent_channel_id is not None:
|
||||
if (parent_channel := self.parent_channel) is not None:
|
||||
channel_value = _("Thread {thread_name} in {channel_name}").format(
|
||||
thread_name=self.channel, channel_name=parent_channel
|
||||
)
|
||||
else:
|
||||
channel_value = _("Thread {thread_name} in {channel_id} (deleted)").format(
|
||||
thread_name=self.channel, channel_id=self.parent_channel_id
|
||||
)
|
||||
|
||||
if embed:
|
||||
if self.reason:
|
||||
reason = f"{bold(_('Reason:'))} {self.reason}"
|
||||
@@ -510,20 +565,13 @@ class Case:
|
||||
if until and duration:
|
||||
emb.add_field(name=_("Until"), value=until)
|
||||
emb.add_field(name=_("Duration"), value=duration)
|
||||
|
||||
if isinstance(self.channel, int):
|
||||
emb.add_field(
|
||||
name=_("Channel"),
|
||||
value=_("{channel} (deleted)").format(channel=self.channel),
|
||||
inline=False,
|
||||
)
|
||||
elif self.channel is not None:
|
||||
emb.add_field(name=_("Channel"), value=self.channel.name, inline=False)
|
||||
if channel_value:
|
||||
emb.add_field(name=_("Channel"), value=channel_value, inline=False)
|
||||
if amended_by:
|
||||
emb.add_field(name=_("Amended by"), value=amended_by)
|
||||
if last_modified:
|
||||
emb.add_field(name=_("Last modified at"), value=last_modified)
|
||||
emb.timestamp = datetime.utcfromtimestamp(self.created_at)
|
||||
emb.timestamp = datetime.fromtimestamp(self.created_at, tz=timezone.utc)
|
||||
return emb
|
||||
else:
|
||||
if self.reason:
|
||||
@@ -549,9 +597,9 @@ class Case:
|
||||
case_text += f"{bold(_('Until:'))} {until}\n{bold(_('Duration:'))} {duration}\n"
|
||||
if self.channel:
|
||||
if isinstance(self.channel, int):
|
||||
case_text += f"{bold(_('Channel:'))} {self.channel} {_('(Deleted)')}\n"
|
||||
case_text += f"{bold(_('Channel:'))} {channel_value}\n"
|
||||
else:
|
||||
case_text += f"{bold(_('Channel:'))} {self.channel.name}\n"
|
||||
case_text += f"{bold(_('Channel:'))} {channel_value}\n"
|
||||
if amended_by:
|
||||
case_text += f"{bold(_('Amended by:'))} {amended_by}\n"
|
||||
if last_modified:
|
||||
@@ -590,6 +638,7 @@ class Case:
|
||||
"reason": self.reason,
|
||||
"until": self.until,
|
||||
"channel": self.channel.id if hasattr(self.channel, "id") else None,
|
||||
"parent_channel": self.parent_channel_id,
|
||||
"amended_by": amended_by,
|
||||
"modified_at": self.modified_at,
|
||||
"message": self.message.id if hasattr(self.message, "id") else None,
|
||||
@@ -650,7 +699,11 @@ class Case:
|
||||
user_object = bot.get_user(user_id) or user_id
|
||||
user_objects[user_key] = user_object
|
||||
|
||||
channel = kwargs.get("channel") or guild.get_channel(data["channel"]) or data["channel"]
|
||||
channel = (
|
||||
kwargs.get("channel")
|
||||
or guild.get_channel_or_thread(data["channel"])
|
||||
or data["channel"]
|
||||
)
|
||||
case_guild = kwargs.get("guild") or bot.get_guild(data["guild"])
|
||||
return cls(
|
||||
bot=bot,
|
||||
@@ -661,6 +714,7 @@ class Case:
|
||||
reason=data["reason"],
|
||||
until=data["until"],
|
||||
channel=channel,
|
||||
parent_channel_id=data.get("parent_channel_id"),
|
||||
modified_at=data["modified_at"],
|
||||
message=message,
|
||||
last_known_username=data.get("last_known_username"),
|
||||
@@ -917,7 +971,7 @@ async def create_case(
|
||||
moderator: Optional[Union[discord.Object, discord.abc.User, int]] = None,
|
||||
reason: Optional[str] = None,
|
||||
until: Optional[datetime] = None,
|
||||
channel: Optional[discord.abc.GuildChannel] = None,
|
||||
channel: Optional[Union[discord.abc.GuildChannel, discord.Thread]] = None,
|
||||
last_known_username: Optional[str] = None,
|
||||
) -> Optional[Case]:
|
||||
"""
|
||||
@@ -947,12 +1001,17 @@ async def create_case(
|
||||
The time the action is in effect until.
|
||||
If naive `datetime` object is passed, it's treated as a local time
|
||||
(similarly to how Python treats naive `datetime` objects).
|
||||
channel: Optional[discord.abc.GuildChannel]
|
||||
channel: Optional[Union[discord.abc.GuildChannel, discord.Thread]]
|
||||
The channel the action was taken in
|
||||
last_known_username: Optional[str]
|
||||
The last known username of the user
|
||||
Note: This is ignored if a Member or User object is provided
|
||||
in the user field
|
||||
|
||||
Raises
|
||||
------
|
||||
TypeError
|
||||
If ``channel`` is of type `discord.PartialMessageable`.
|
||||
"""
|
||||
case_type = await get_casetype(action_type, guild)
|
||||
if case_type is None:
|
||||
@@ -964,6 +1023,11 @@ async def create_case(
|
||||
if user == bot.user:
|
||||
return
|
||||
|
||||
if isinstance(channel, discord.PartialMessageable):
|
||||
raise TypeError("Can't use PartialMessageable as the channel for a modlog case.")
|
||||
|
||||
parent_channel_id = channel.parent_id if isinstance(channel, discord.Thread) else None
|
||||
|
||||
async with _config.guild(guild).latest_case_number.get_lock():
|
||||
# We're getting the case number from config, incrementing it, awaiting something, then
|
||||
# setting it again. This warrants acquiring the lock.
|
||||
@@ -980,6 +1044,7 @@ async def create_case(
|
||||
reason,
|
||||
int(until.timestamp()) if until else None,
|
||||
channel,
|
||||
parent_channel_id,
|
||||
amended_by=None,
|
||||
modified_at=None,
|
||||
message=None,
|
||||
|
||||
Reference in New Issue
Block a user