Add Traceback prompt (#5851)

Co-authored-by: Jakub Kuczys <me@jacken.men>
This commit is contained in:
yuansheng1549 2023-04-15 00:50:39 +08:00 committed by GitHub
parent 896d5e9200
commit 030607fb04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 133 additions and 64 deletions

View File

@ -52,9 +52,11 @@ from .settings_caches import (
DisabledCogCache,
I18nManager,
)
from .utils.predicates import MessagePredicate
from .rpc import RPCMixin
from .tree import RedTree
from .utils import can_user_send_messages_in, common_filters, AsyncIter
from .utils.chat_formatting import box, text_to_file
from .utils._internal_utils import send_to_owners_with_prefix_replaced
if TYPE_CHECKING:
@ -81,6 +83,8 @@ PreInvokeCoroutine = Callable[[commands.Context], Awaitable[Any]]
T_BIC = TypeVar("T_BIC", bound=PreInvokeCoroutine)
UserOrRole = Union[int, discord.Role, discord.Member, discord.User]
_ = i18n.Translator("Core", __file__)
def _is_submodule(parent, child):
return parent == child or child.startswith(parent + ".")
@ -2289,3 +2293,102 @@ class Red(
failed_cogs=failures["cog"],
unhandled=failures["unhandled"],
)
async def send_interactive(
self,
channel: discord.abc.Messageable,
messages: Iterable[str],
*,
user: Optional[discord.User] = None,
box_lang: Optional[str] = None,
timeout: int = 15,
join_character: str = "",
) -> 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
----------
channel : discord.abc.Messageable
The channel to send the messages to.
messages : `iterable` of `str`
The messages to send.
user : discord.User
The user that can respond to the prompt.
When this is ``None``, any user can respond.
box_lang : Optional[str]
If specified, each message will be contained within a code block 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.
join_character : str
The character used to join all the messages when the file output
is selected.
Returns
-------
List[discord.Message]
A list of sent messages.
"""
messages = tuple(messages)
ret = []
# using dpy_commands.Context to keep the Messageable contract in full
if isinstance(channel, dpy_commands.Context):
# this is only necessary to ensure that `channel.delete_messages()` works
# when `ctx.channel` has that method
channel = channel.channel
for idx, page in enumerate(messages, 1):
if box_lang is None:
msg = await channel.send(page)
else:
msg = await channel.send(box(page, lang=box_lang))
ret.append(msg)
n_remaining = len(messages) - idx
if n_remaining > 0:
if n_remaining == 1:
prompt_text = _(
"There is still one message remaining. Type {command_1} to continue"
" or {command_2} to upload all contents as a file."
)
else:
prompt_text = _(
"There are still {count} messages remaining. Type {command_1} to continue"
" or {command_2} to upload all contents as a file."
)
query = await channel.send(
prompt_text.format(count=n_remaining, command_1="`more`", command_2="`file`")
)
pred = MessagePredicate.lower_contained_in(
("more", "file"), channel=channel, user=user
)
try:
resp = await self.wait_for(
"message",
check=pred,
timeout=timeout,
)
except asyncio.TimeoutError:
with contextlib.suppress(discord.HTTPException):
await query.delete()
break
else:
try:
await 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()
if pred.result == 1:
ret.append(
await channel.send(file=text_to_file(join_character.join(messages)))
)
break
return ret

View File

@ -9,8 +9,6 @@ import discord
from discord.ext.commands import Context as DPYContext
from .requires import PermState
from ..utils.chat_formatting import box, text_to_file
from ..utils.predicates import MessagePredicate
from ..utils import can_user_react_in
if TYPE_CHECKING:
@ -152,11 +150,12 @@ class Context(DPYContext):
async def send_interactive(
self,
messages: Iterable[str],
box_lang: str = None,
box_lang: Optional[str] = None,
timeout: int = 15,
join_character: str = "",
) -> List[discord.Message]:
"""Send multiple messages interactively.
"""
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
@ -167,7 +166,7 @@ class Context(DPYContext):
messages : `iterable` of `str`
The messages to send.
box_lang : str
If specified, each message will be contained within a codeblock of
If specified, each message will be contained within a code block of
this language.
timeout : int
How long the user has to respond to the prompt before it times out.
@ -176,53 +175,19 @@ class Context(DPYContext):
The character used to join all the messages when the file output
is selected.
Returns
-------
List[discord.Message]
A list of sent messages.
"""
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 or `file` to upload all contents as a file."
"".format(is_are, n_remaining, plural)
)
pred = MessagePredicate.lower_contained_in(("more", "file"), self)
try:
resp = await self.bot.wait_for(
"message",
check=pred,
return await self.bot.send_interactive(
channel=self.channel,
messages=messages,
user=self.author,
box_lang=box_lang,
timeout=timeout,
join_character=join_character,
)
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()
if pred.result == 1:
await self.send(file=text_to_file(join_character.join(messages)))
break
return ret
async def embed_colour(self):
"""

View File

@ -1446,15 +1446,16 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
**Arguments:**
- `[public]` - Whether to send the traceback to the current context. Leave blank to send to your DMs.
"""
if not public:
destination = ctx.author
else:
destination = ctx.channel
channel = ctx.channel if public else ctx.author
if self.bot._last_exception:
for page in pagify(self.bot._last_exception, shorten_by=10):
try:
await destination.send(box(page, lang="py"))
await self.bot.send_interactive(
channel,
pagify(self.bot._last_exception, shorten_by=10),
user=ctx.author,
box_lang="py",
)
except discord.HTTPException:
await ctx.channel.send(
"I couldn't send the traceback message to you in DM. "