mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-05 18:58:53 -05:00
* 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
356 lines
11 KiB
Python
356 lines
11 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import contextlib
|
|
import os
|
|
import re
|
|
from typing import Iterable, List, Union, Optional, TYPE_CHECKING
|
|
import discord
|
|
from discord.ext.commands import Context as DPYContext
|
|
|
|
from .requires import PermState
|
|
from ..utils.chat_formatting import box
|
|
from ..utils.predicates import MessagePredicate
|
|
from ..utils import can_user_react_in, common_filters
|
|
|
|
if TYPE_CHECKING:
|
|
from .commands import Command
|
|
from ..bot import Red
|
|
|
|
TICK = "\N{WHITE HEAVY CHECK MARK}"
|
|
|
|
__all__ = ["Context", "GuildContext", "DMContext"]
|
|
|
|
|
|
class Context(DPYContext):
|
|
"""Command invocation context for Red.
|
|
|
|
All context passed into commands will be of this type.
|
|
|
|
This class inherits from `discord.ext.commands.Context`.
|
|
|
|
Attributes
|
|
----------
|
|
assume_yes: bool
|
|
Whether or not interactive checks should
|
|
be skipped and assumed to be confirmed.
|
|
|
|
This is intended for allowing automation of tasks.
|
|
|
|
An example of this would be scheduled commands
|
|
not requiring interaction if the cog developer
|
|
checks this value prior to confirming something interactively.
|
|
|
|
Depending on the potential impact of a command,
|
|
it may still be appropriate not to use this setting.
|
|
permission_state: PermState
|
|
The permission state the current context is in.
|
|
"""
|
|
|
|
command: "Command"
|
|
invoked_subcommand: "Optional[Command]"
|
|
bot: "Red"
|
|
|
|
def __init__(self, **attrs):
|
|
self.assume_yes = attrs.pop("assume_yes", False)
|
|
super().__init__(**attrs)
|
|
self.permission_state: PermState = PermState.NORMAL
|
|
|
|
async def send(self, content=None, **kwargs):
|
|
"""Sends a message to the destination with the content given.
|
|
|
|
This acts the same as `discord.ext.commands.Context.send`, with
|
|
one added keyword argument as detailed below in *Other Parameters*.
|
|
|
|
Parameters
|
|
----------
|
|
content : str
|
|
The content of the message to send.
|
|
|
|
Other Parameters
|
|
----------------
|
|
filter : callable (`str`) -> `str`, optional
|
|
A function which is used to filter the ``content`` before
|
|
it is sent.
|
|
This must take a single `str` as an argument, and return
|
|
the processed `str`. When `None` is passed, ``content`` won't be touched.
|
|
Defaults to `None`.
|
|
**kwargs
|
|
See `discord.ext.commands.Context.send`.
|
|
|
|
Returns
|
|
-------
|
|
discord.Message
|
|
The message that was sent.
|
|
|
|
"""
|
|
|
|
_filter = kwargs.pop("filter", None)
|
|
|
|
if _filter and content:
|
|
content = _filter(str(content))
|
|
|
|
return await super().send(content=content, **kwargs)
|
|
|
|
async def send_help(self, command=None):
|
|
"""Send the command help message."""
|
|
# This allows people to manually use this similarly
|
|
# to the upstream d.py version, while retaining our use.
|
|
command = command or self.command
|
|
await self.bot.send_help_for(self, command)
|
|
|
|
async def tick(self, *, message: Optional[str] = None) -> bool:
|
|
"""Add a tick reaction to the command message.
|
|
|
|
Keyword Arguments
|
|
-----------------
|
|
message : str, optional
|
|
The message to send if adding the reaction doesn't succeed.
|
|
|
|
Returns
|
|
-------
|
|
bool
|
|
:code:`True` if adding the reaction succeeded.
|
|
|
|
"""
|
|
return await self.react_quietly(TICK, message=message)
|
|
|
|
async def react_quietly(
|
|
self,
|
|
reaction: Union[discord.Emoji, discord.Reaction, discord.PartialEmoji, str],
|
|
*,
|
|
message: Optional[str] = None,
|
|
) -> bool:
|
|
"""Adds a reaction to the command message.
|
|
|
|
Parameters
|
|
----------
|
|
reaction : Union[discord.Emoji, discord.Reaction, discord.PartialEmoji, str]
|
|
The emoji to react with.
|
|
|
|
Keyword Arguments
|
|
-----------------
|
|
message : str, optional
|
|
The message to send if adding the reaction doesn't succeed.
|
|
|
|
Returns
|
|
-------
|
|
bool
|
|
:code:`True` if adding the reaction succeeded.
|
|
"""
|
|
try:
|
|
if not can_user_react_in(self.me, self.channel):
|
|
raise RuntimeError
|
|
await self.message.add_reaction(reaction)
|
|
except (RuntimeError, discord.HTTPException):
|
|
if message is not None:
|
|
await self.send(message)
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
async def send_interactive(
|
|
self, messages: Iterable[str], box_lang: str = None, timeout: int = 15
|
|
) -> List[discord.Message]:
|
|
"""Send multiple messages interactively.
|
|
|
|
The user will be prompted for whether or not they would like to view
|
|
the next message, one at a time. They will also be notified of how
|
|
many messages are remaining on each prompt.
|
|
|
|
Parameters
|
|
----------
|
|
messages : `iterable` of `str`
|
|
The messages to send.
|
|
box_lang : str
|
|
If specified, each message will be contained within a codeblock of
|
|
this language.
|
|
timeout : int
|
|
How long the user has to respond to the prompt before it times out.
|
|
After timing out, the bot deletes its prompt message.
|
|
|
|
"""
|
|
messages = tuple(messages)
|
|
ret = []
|
|
|
|
for idx, page in enumerate(messages, 1):
|
|
if box_lang is None:
|
|
msg = await self.send(page)
|
|
else:
|
|
msg = await self.send(box(page, lang=box_lang))
|
|
ret.append(msg)
|
|
n_remaining = len(messages) - idx
|
|
if n_remaining > 0:
|
|
if n_remaining == 1:
|
|
plural = ""
|
|
is_are = "is"
|
|
else:
|
|
plural = "s"
|
|
is_are = "are"
|
|
query = await self.send(
|
|
"There {} still {} message{} remaining. "
|
|
"Type `more` to continue."
|
|
"".format(is_are, n_remaining, plural)
|
|
)
|
|
try:
|
|
resp = await self.bot.wait_for(
|
|
"message",
|
|
check=MessagePredicate.lower_equal_to("more", self),
|
|
timeout=timeout,
|
|
)
|
|
except asyncio.TimeoutError:
|
|
with contextlib.suppress(discord.HTTPException):
|
|
await query.delete()
|
|
break
|
|
else:
|
|
try:
|
|
await self.channel.delete_messages((query, resp))
|
|
except (discord.HTTPException, AttributeError):
|
|
# In case the bot can't delete other users' messages,
|
|
# or is not a bot account
|
|
# or channel is a DM
|
|
with contextlib.suppress(discord.HTTPException):
|
|
await query.delete()
|
|
return ret
|
|
|
|
async def embed_colour(self):
|
|
"""
|
|
Helper function to get the colour for an embed.
|
|
|
|
Returns
|
|
-------
|
|
discord.Colour:
|
|
The colour to be used
|
|
"""
|
|
return await self.bot.get_embed_color(self)
|
|
|
|
@property
|
|
def embed_color(self):
|
|
# Rather than double awaiting.
|
|
return self.embed_colour
|
|
|
|
async def embed_requested(self):
|
|
"""
|
|
Short-hand for calling bot.embed_requested with permission checks.
|
|
|
|
Equivalent to:
|
|
|
|
.. code:: python
|
|
|
|
await ctx.bot.embed_requested(ctx)
|
|
|
|
Returns
|
|
-------
|
|
bool:
|
|
:code:`True` if an embed is requested
|
|
"""
|
|
return await self.bot.embed_requested(self)
|
|
|
|
async def maybe_send_embed(self, message: str) -> discord.Message:
|
|
"""
|
|
Simple helper to send a simple message to context
|
|
without manually checking ctx.embed_requested
|
|
This should only be used for simple messages.
|
|
|
|
Parameters
|
|
----------
|
|
message: `str`
|
|
The string to send
|
|
|
|
Returns
|
|
-------
|
|
discord.Message:
|
|
the message which was sent
|
|
|
|
Raises
|
|
------
|
|
discord.Forbidden
|
|
see `discord.abc.Messageable.send`
|
|
discord.HTTPException
|
|
see `discord.abc.Messageable.send`
|
|
ValueError
|
|
when the message's length is not between 1 and 2000 characters.
|
|
"""
|
|
if not message or len(message) > 2000:
|
|
raise ValueError("Message length must be between 1 and 2000")
|
|
if await self.embed_requested():
|
|
return await self.send(
|
|
embed=discord.Embed(description=message, color=(await self.embed_colour()))
|
|
)
|
|
else:
|
|
return await self.send(
|
|
message,
|
|
allowed_mentions=discord.AllowedMentions(everyone=False, roles=False, users=False),
|
|
)
|
|
|
|
@property
|
|
def me(self) -> Union[discord.ClientUser, discord.Member]:
|
|
"""
|
|
discord.abc.User: The bot member or user object.
|
|
|
|
If the context is DM, this will be a `discord.User` object.
|
|
"""
|
|
if self.guild is not None:
|
|
return self.guild.me
|
|
else:
|
|
return self.bot.user
|
|
|
|
|
|
if TYPE_CHECKING or os.getenv("BUILDING_DOCS", False):
|
|
|
|
class DMContext(Context):
|
|
"""
|
|
At runtime, this will still be a normal context object.
|
|
|
|
This lies about some type narrowing for type analysis in commands
|
|
using a dm_only decorator.
|
|
|
|
It is only correct to use when those types are already narrowed
|
|
"""
|
|
|
|
@property
|
|
def author(self) -> discord.User:
|
|
...
|
|
|
|
@property
|
|
def channel(self) -> discord.DMChannel:
|
|
...
|
|
|
|
@property
|
|
def guild(self) -> None:
|
|
...
|
|
|
|
@property
|
|
def me(self) -> discord.ClientUser:
|
|
...
|
|
|
|
class GuildContext(Context):
|
|
"""
|
|
At runtime, this will still be a normal context object.
|
|
|
|
This lies about some type narrowing for type analysis in commands
|
|
using a guild_only decorator.
|
|
|
|
It is only correct to use when those types are already narrowed
|
|
"""
|
|
|
|
@property
|
|
def author(self) -> discord.Member:
|
|
...
|
|
|
|
@property
|
|
def channel(self) -> Union[discord.TextChannel, discord.Thread]:
|
|
...
|
|
|
|
@property
|
|
def guild(self) -> discord.Guild:
|
|
...
|
|
|
|
@property
|
|
def me(self) -> discord.Member:
|
|
...
|
|
|
|
else:
|
|
GuildContext = Context
|
|
DMContext = Context
|