mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-12-11 03:42:31 -05:00
Compare commits
10 Commits
3.5.20
...
3fd23d4163
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fd23d4163 | ||
|
|
2dbbb51208 | ||
|
|
b177c80b4e | ||
|
|
029029e9a5 | ||
|
|
c6ff2191f3 | ||
|
|
6603cd1a86 | ||
|
|
1daf56f3d8 | ||
|
|
b3f0349ba2 | ||
|
|
bfc3561928 | ||
|
|
8d8918b3c6 |
@@ -17,7 +17,7 @@ jobs:
|
||||
- name: Install script's pre-requirements
|
||||
run: |
|
||||
python -m pip install -U pip
|
||||
python -m pip install -U pathspec pyyaml rich
|
||||
python -m pip install -U pathspec pyyaml rich typing_extensions
|
||||
- name: Check label pattern exhaustiveness
|
||||
run: |
|
||||
python .github/workflows/scripts/check_label_pattern_exhaustiveness.py
|
||||
|
||||
@@ -253,7 +253,23 @@ modset dm
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
[p]modset dm [enabled]
|
||||
[p]modset dm
|
||||
|
||||
**Description**
|
||||
|
||||
Settings for messaging the user when being kicked or banned.
|
||||
|
||||
.. _mod-command-modset-dm-sendmessage:
|
||||
|
||||
"""""""""""""""""""""
|
||||
modset dm sendmessage
|
||||
"""""""""""""""""""""
|
||||
|
||||
**Syntax**
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
[p]modset dm sendmessage [enabled]
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -266,6 +282,72 @@ and reason as to why they were kicked/banned.
|
||||
|
||||
* ``[enabled]``: Whether a message should be sent to a user when they are kicked/banned. |bool-input|
|
||||
|
||||
.. _mod-command-modset-banshowextrafield:
|
||||
|
||||
"""""""""""""""""""""""""""
|
||||
modset dm banshowextrafield
|
||||
"""""""""""""""""""""""""""
|
||||
|
||||
**Syntax**
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
[p]modset dm banshowextrafield [enabled]
|
||||
|
||||
**Description**
|
||||
|
||||
Toggle whether to show an extra customizable field when banning.
|
||||
|
||||
This can be used to add additional information for the banned user, such as a ban appeal link.
|
||||
|
||||
**Arguments**
|
||||
|
||||
* ``[enabled]``: If an extra customizable embed field should appear when banning. |bool-input|
|
||||
|
||||
.. _mod-command-modset-banextrafieldtitle:
|
||||
|
||||
""""""""""""""""""""""""""""
|
||||
modset dm banextrafieldtitle
|
||||
""""""""""""""""""""""""""""
|
||||
|
||||
**Syntax**
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
[p]modset dm banextrafieldtitle [title]
|
||||
|
||||
**Description**
|
||||
|
||||
Set the title for the optional extra embed on ban.
|
||||
|
||||
Cannot be over 252 characters long.
|
||||
|
||||
**Arguments**
|
||||
|
||||
* ``[title]``: The title of the embed field. Can by any string of text under 252 charcters long.
|
||||
|
||||
.. _mod-command-modset-banextrafieldcontents:
|
||||
|
||||
"""""""""""""""""""""""""""""""
|
||||
modset dm banextrafieldcontents
|
||||
"""""""""""""""""""""""""""""""
|
||||
|
||||
**Syntax**
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
[p]modset dm banextrafieldcontents [contents]
|
||||
|
||||
**Description**
|
||||
|
||||
Set the contents for the optional extra embed on ban
|
||||
|
||||
Cannot be over 1024 characters long.
|
||||
|
||||
**Arguments**
|
||||
|
||||
* ``[contents]``: The contents of the embed field. Can by any string of text under 1024 charcters long.
|
||||
|
||||
.. _mod-command-modset-requirereason:
|
||||
|
||||
""""""""""""""""""""
|
||||
|
||||
@@ -155,6 +155,22 @@ Here is an example of the :code:`async with` syntax:
|
||||
blah.append(new_blah)
|
||||
await ctx.send("The new blah value has been added!")
|
||||
|
||||
There is also a :py:meth:`Group.all` method. This will return all the stored data associated
|
||||
with a specific config group as a :py:class:`dict`. By negating the need to excessively call config,
|
||||
this method can be particularly useful when multiple values are to be retrieved from the same group.
|
||||
|
||||
Here is an example of :py:meth:`Group.all` usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@commands.command()
|
||||
async def getall(self, ctx):
|
||||
all_global_data = await self.config.all()
|
||||
await ctx.send("Foobar is {foobar}, foo baz is {foo_baz}".format(
|
||||
foobar=str(all_global_data["foobar"]),
|
||||
foo_baz=str(all_global_data["foo"]["baz"])
|
||||
))
|
||||
|
||||
|
||||
.. important::
|
||||
|
||||
@@ -398,7 +414,7 @@ We're responsible pet owners here, so we've also got to have a way to feed our p
|
||||
# We could accomplish the same thing a slightly different way
|
||||
await self.config.user(ctx.author).pets.get_attr(pet_name).hunger.set(new_hunger)
|
||||
|
||||
await ctx.send("Your pet is now at {}/100 hunger!".format(new_hunger)
|
||||
await ctx.send("Your pet is now at {}/100 hunger!".format(new_hunger))
|
||||
|
||||
Of course, if we're less than responsible pet owners, there are consequences::
|
||||
|
||||
@@ -481,7 +497,7 @@ Config prioritizes being a safe data store without developers needing to
|
||||
know how end users have configured their bot.
|
||||
|
||||
This does come with some performance costs, so keep the following in mind when choosing to
|
||||
develop using config
|
||||
develop using config.
|
||||
|
||||
* Config use in events should be kept minimal and should only occur
|
||||
after confirming the event needs to interact with config
|
||||
|
||||
@@ -47,7 +47,7 @@ Keys common to both repo and cog info.json (case sensitive)
|
||||
is installed or a repo is added
|
||||
|
||||
.. tip:: You can use the ``[p]`` key in your string to use the prefix
|
||||
used for installing.
|
||||
used for installing, and ``[botname]`` to show the bot's username.
|
||||
|
||||
- ``short`` (string) - A short description of the cog or repo. For cogs, this info
|
||||
is displayed when a user executes ``[p]cog list``
|
||||
|
||||
@@ -339,7 +339,7 @@ def _early_init():
|
||||
|
||||
|
||||
# This is bumped automatically by release workflow (`.github/workflows/scripts/bump_version.py`)
|
||||
_VERSION = "3.5.20"
|
||||
_VERSION = "3.5.21.dev1"
|
||||
|
||||
__version__, version_info = VersionInfo._get_version()
|
||||
|
||||
|
||||
@@ -59,7 +59,11 @@ class AliasEntry:
|
||||
extra = []
|
||||
while not view.eof:
|
||||
prev = view.index
|
||||
word = view.get_quoted_word()
|
||||
try:
|
||||
word = view.get_quoted_word()
|
||||
except discord.ext.commands.errors.UnexpectedQuoteError:
|
||||
view.skip_ws()
|
||||
continue
|
||||
if len(word) < view.index - prev:
|
||||
word = "".join((view.buffer[prev], word, view.buffer[view.index - 1]))
|
||||
extra.append(word.strip(" "))
|
||||
|
||||
@@ -143,6 +143,8 @@ class KickBanMixin(MixinMeta):
|
||||
|
||||
toggle = await self.config.guild(guild).dm_on_kickban()
|
||||
if toggle:
|
||||
extra_embed = await self.config.guild(guild).ban_show_extra()
|
||||
|
||||
with contextlib.suppress(discord.HTTPException):
|
||||
em = discord.Embed(
|
||||
title=bold(_("You have been banned from {guild}.").format(guild=guild)),
|
||||
@@ -153,6 +155,17 @@ class KickBanMixin(MixinMeta):
|
||||
value=reason if reason is not None else _("No reason was given."),
|
||||
inline=False,
|
||||
)
|
||||
if extra_embed:
|
||||
extra_embed_title = await self.config.guild(guild).ban_extra_embed_title()
|
||||
extra_embed_contents = await self.config.guild(
|
||||
guild
|
||||
).ban_extra_embed_contents()
|
||||
|
||||
em.add_field(
|
||||
name=bold(extra_embed_title, escape_formatting=False),
|
||||
value=extra_embed_contents,
|
||||
inline=False,
|
||||
)
|
||||
await user.send(embed=em)
|
||||
|
||||
ban_type = "ban"
|
||||
@@ -658,16 +671,38 @@ class KickBanMixin(MixinMeta):
|
||||
|
||||
with contextlib.suppress(discord.HTTPException):
|
||||
# We don't want blocked DMs preventing us from banning
|
||||
msg = _("You have been temporarily banned from {server_name} until {date}.").format(
|
||||
server_name=guild.name, date=discord.utils.format_dt(unban_time)
|
||||
|
||||
extra_embed = await self.config.guild(guild).ban_show_extra()
|
||||
|
||||
em = discord.Embed(
|
||||
title=bold(
|
||||
_("You have been temporarily banned from {guild} until {date}.").format(
|
||||
guild=guild, date=discord.utils.format_dt(unban_time)
|
||||
)
|
||||
),
|
||||
color=await self.bot.get_embed_color(member),
|
||||
)
|
||||
em.add_field(
|
||||
name=_("**Reason**"),
|
||||
value=reason if reason is not None else _("No reason was given."),
|
||||
inline=False,
|
||||
)
|
||||
if guild_data["dm_on_kickban"] and reason:
|
||||
msg += _("\n\n**Reason:** {reason}").format(reason=reason)
|
||||
if invite:
|
||||
msg += _("\n\nHere is an invite for when your ban expires: {invite_link}").format(
|
||||
invite_link=invite
|
||||
em.add_field(
|
||||
name=bold(_("Here is an invite for when your ban expires")),
|
||||
value=invite,
|
||||
inline=False,
|
||||
)
|
||||
await member.send(msg)
|
||||
if extra_embed:
|
||||
extra_embed_title = await self.config.guild(guild).ban_extra_embed_title()
|
||||
extra_embed_contents = await self.config.guild(guild).ban_extra_embed_contents()
|
||||
|
||||
em.add_field(
|
||||
name=bold(extra_embed_title, escape_formatting=False),
|
||||
value=extra_embed_contents,
|
||||
inline=False,
|
||||
)
|
||||
await member.send(embed=em)
|
||||
|
||||
audit_reason = get_audit_reason(author, reason, shorten=True)
|
||||
|
||||
|
||||
@@ -61,6 +61,9 @@ class Mod(
|
||||
"default_days": 0,
|
||||
"default_tempban_duration": 60 * 60 * 24,
|
||||
"track_nicknames": True,
|
||||
"ban_show_extra": False,
|
||||
"ban_extra_embed_title": "Message from staff",
|
||||
"ban_extra_embed_contents": "Please set me",
|
||||
}
|
||||
|
||||
default_channel_settings = {"ignored": False}
|
||||
|
||||
@@ -309,9 +309,9 @@ class ModInfo(MixinMeta):
|
||||
usernames, display_names, nicks = await self.get_names(member)
|
||||
parts = []
|
||||
for header, names in (
|
||||
(_("Past 20 usernames:"), usernames),
|
||||
(_("Past 20 global display names:"), display_names),
|
||||
(_("Past 20 server nicknames:"), nicks),
|
||||
(_("Past 20 usernames: "), usernames),
|
||||
(_("Past 20 global display names: "), display_names),
|
||||
(_("Past 20 server nicknames: "), nicks),
|
||||
):
|
||||
if names:
|
||||
parts.append(bold(header) + ", ".join(names))
|
||||
|
||||
@@ -48,6 +48,9 @@ class ModSettings(MixinMeta):
|
||||
dm_on_kickban = data["dm_on_kickban"]
|
||||
default_days = data["default_days"]
|
||||
default_tempban_duration = data["default_tempban_duration"]
|
||||
ban_show_extra = data["ban_show_extra"]
|
||||
ban_extra_embed_title = data["ban_extra_embed_title"]
|
||||
ban_extra_embed_contents = data["ban_extra_embed_contents"]
|
||||
if not track_all_names and track_nicknames:
|
||||
yes_or_no = _("Overridden by another setting")
|
||||
else:
|
||||
@@ -98,9 +101,18 @@ class ModSettings(MixinMeta):
|
||||
)
|
||||
else:
|
||||
msg += _("Default message history delete on ban: Don't delete any\n")
|
||||
msg += _("Default tempban duration: {duration}").format(
|
||||
msg += _("Default tempban duration: {duration}\n").format(
|
||||
duration=humanize_timedelta(seconds=default_tempban_duration)
|
||||
)
|
||||
msg += _("Show optional information field in embed: {yes_or_no}\n").format(
|
||||
yes_or_no=_("Yes") if ban_show_extra else _("No")
|
||||
)
|
||||
msg += _("Title of the optional extra field: {ban_embed_title}\n").format(
|
||||
ban_embed_title=ban_extra_embed_title if ban_extra_embed_title else _("None")
|
||||
)
|
||||
msg += _("Contents of the optional extra field: {ban_embed_contents}").format(
|
||||
ban_embed_contents=ban_extra_embed_contents if ban_extra_embed_contents else _("None")
|
||||
)
|
||||
await ctx.send(box(msg))
|
||||
|
||||
@modset.command()
|
||||
@@ -347,9 +359,15 @@ class ModSettings(MixinMeta):
|
||||
)
|
||||
)
|
||||
|
||||
@modset.command()
|
||||
@modset.group()
|
||||
@commands.guild_only()
|
||||
async def dm(self, ctx: commands.Context, enabled: bool = None):
|
||||
async def dm(self, ctx: commands.Context):
|
||||
"""
|
||||
Settings for messaging the user when being kicked or banned.
|
||||
"""
|
||||
|
||||
@dm.command(name="sendmessage")
|
||||
async def dm_sendmessage(self, ctx: commands.Context, enabled: bool = None):
|
||||
"""Toggle whether a message should be sent to a user when they are kicked/banned.
|
||||
|
||||
If this option is enabled, the bot will attempt to DM the user with the guild name
|
||||
@@ -370,6 +388,63 @@ class ModSettings(MixinMeta):
|
||||
_("Bot will no longer attempt to send a DM to user before kick and ban.")
|
||||
)
|
||||
|
||||
@dm.command(name="banshowextrafield")
|
||||
async def dm_banshowextrafield(self, ctx: commands.Context, enabled: bool = None):
|
||||
"""
|
||||
Toggle whether to show an extra customizable field when banning.
|
||||
|
||||
This can be used to add additional information for the banned user, such as a ban appeal link.
|
||||
"""
|
||||
guild = ctx.guild
|
||||
if enabled is None:
|
||||
setting = await self.config.guild(guild).ban_show_extra()
|
||||
await ctx.send(
|
||||
_("The extra embed field is currently set to: {setting}").format(setting=setting)
|
||||
)
|
||||
return
|
||||
await self.config.guild(guild).ban_show_extra.set(enabled)
|
||||
if enabled:
|
||||
await ctx.send(
|
||||
_(
|
||||
"An extra field will be shown when banning. Configure it with `{prefix}modset dm banextrafieldtitle` and `{prefix}modset dm banextrafieldcontents`"
|
||||
).format(prefix=ctx.prefix)
|
||||
)
|
||||
else:
|
||||
await ctx.send(_("An extra field will be no longer be shown when banning."))
|
||||
|
||||
@dm.command(name="banextrafieldtitle")
|
||||
async def dm_banextrafieldtitle(self, ctx: commands.Context, *, title: str) -> None:
|
||||
"""
|
||||
Set the title for the optional extra embed on ban.
|
||||
|
||||
Cannot be over 252 characters long.
|
||||
"""
|
||||
guild = ctx.guild
|
||||
# Bolding the text is 4 characters (**bolded**)
|
||||
# All the bold function used in the embeds does is add those star characters and some other convenience stuffs.
|
||||
# Such as escaping formatting.
|
||||
if len(title) > 252:
|
||||
await ctx.send(_("Embed title cannot be over 252 characters long."))
|
||||
else:
|
||||
await self.config.guild(guild).ban_extra_embed_title.set(title)
|
||||
await ctx.send(_("Embed Title has been set to `{title}`").format(title=title))
|
||||
|
||||
@dm.command(name="banextrafieldcontents")
|
||||
async def dm_banextrafieldcontents(self, ctx: commands.Context, *, contents: str) -> None:
|
||||
"""
|
||||
Set the contents for the optional extra embed on ban
|
||||
|
||||
Cannot be over 1024 characters long.
|
||||
"""
|
||||
guild = ctx.guild
|
||||
if len(contents) > 1024:
|
||||
await ctx.send(_("Embed contents cannot be over 1024 characters long."))
|
||||
else:
|
||||
await self.config.guild(guild).ban_extra_embed_contents.set(contents)
|
||||
await ctx.send(
|
||||
_("Embed Contents has been set to `{contents}`").format(contents=contents)
|
||||
)
|
||||
|
||||
@modset.command()
|
||||
@commands.guild_only()
|
||||
async def requirereason(self, ctx: commands.Context, enabled: bool = None):
|
||||
|
||||
@@ -307,7 +307,7 @@ class Reports(commands.Cog):
|
||||
|
||||
with contextlib.suppress(discord.Forbidden, discord.HTTPException):
|
||||
if val is None:
|
||||
if await self.config.guild(ctx.guild).output_channel() is None:
|
||||
if await self.config.guild(guild).output_channel() is None:
|
||||
await author.send(
|
||||
_(
|
||||
"This server has no reports channel set up. Please contact a server admin."
|
||||
|
||||
@@ -314,6 +314,19 @@ def parse_cli_flags(args):
|
||||
default=None,
|
||||
help="Forcefully disables the Rich logging handlers.",
|
||||
)
|
||||
# DEP-WARN: use argparse.BooleanOptionalAction when we drop support for Python 3.8
|
||||
parser.add_argument(
|
||||
"--rich-tracebacks",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Format the Python exception tracebacks using Rich (with syntax highlighting)."
|
||||
" *May* be useful to increase traceback readability during development.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-rich-tracebacks",
|
||||
action="store_false",
|
||||
dest="rich_tracebacks",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--rich-traceback-extra-lines",
|
||||
type=non_negative_int,
|
||||
|
||||
@@ -2477,23 +2477,34 @@ class Red(
|
||||
msg = await channel.send(box(page, lang=box_lang))
|
||||
ret.append(msg)
|
||||
n_remaining = len(messages) - idx
|
||||
files_perm = (
|
||||
not channel.guild or channel.permissions_for(channel.guild.me).attach_files
|
||||
)
|
||||
options = ("more", "file") if files_perm else ("more",)
|
||||
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."
|
||||
)
|
||||
if files_perm:
|
||||
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 is still one message remaining. Type {command_1} to continue."
|
||||
)
|
||||
else:
|
||||
prompt_text = _(
|
||||
"There are still {count} messages remaining. Type {command_1} to continue"
|
||||
" or {command_2} to upload all contents as a file."
|
||||
)
|
||||
if files_perm:
|
||||
prompt_text = _(
|
||||
"There are still {count} messages 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."
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
pred = MessagePredicate.lower_contained_in(options, channel=channel, user=user)
|
||||
try:
|
||||
resp = await self.wait_for(
|
||||
"message",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import argparse
|
||||
import logging
|
||||
import logging.handlers
|
||||
import pathlib
|
||||
import re
|
||||
@@ -33,6 +34,7 @@ from rich.traceback import PathHighlighter, Traceback # DEP-WARN
|
||||
|
||||
|
||||
MAX_OLD_LOGS = 8
|
||||
log = logging.getLogger("red.logging")
|
||||
|
||||
|
||||
class RotatingFileHandler(logging.handlers.RotatingFileHandler):
|
||||
@@ -322,7 +324,7 @@ def init_logging(level: int, location: pathlib.Path, cli_flags: argparse.Namespa
|
||||
rich_formatter = logging.Formatter("{message}", datefmt="[%X]", style="{")
|
||||
|
||||
stdout_handler = RedRichHandler(
|
||||
rich_tracebacks=True,
|
||||
rich_tracebacks=cli_flags.rich_tracebacks,
|
||||
show_path=False,
|
||||
highlighter=NullHighlighter(),
|
||||
tracebacks_extra_lines=cli_flags.rich_traceback_extra_lines,
|
||||
@@ -379,3 +381,9 @@ def init_logging(level: int, location: pathlib.Path, cli_flags: argparse.Namespa
|
||||
for fhandler in (latest_fhandler, all_fhandler):
|
||||
fhandler.setFormatter(file_formatter)
|
||||
root_logger.addHandler(fhandler)
|
||||
|
||||
if not enable_rich_logging and cli_flags.rich_tracebacks:
|
||||
log.warning(
|
||||
"Rich tracebacks were requested but they will not be enabled"
|
||||
" as Rich logging is not active."
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user