mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-12-11 11:52:34 -05:00
Compare commits
18 Commits
3.5.19
...
f68580fab9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f68580fab9 | ||
|
|
ec66666036 | ||
|
|
3fd23d4163 | ||
|
|
2dbbb51208 | ||
|
|
b177c80b4e | ||
|
|
029029e9a5 | ||
|
|
c6ff2191f3 | ||
|
|
6603cd1a86 | ||
|
|
1daf56f3d8 | ||
|
|
b3f0349ba2 | ||
|
|
bfc3561928 | ||
|
|
8d8918b3c6 | ||
|
|
550cf49bc8 | ||
|
|
313eeffc84 | ||
|
|
88e1f72467 | ||
|
|
3c6146d6ca | ||
|
|
bfab9cc5f8 | ||
|
|
07ee31a88f |
@@ -17,7 +17,7 @@ jobs:
|
|||||||
- name: Install script's pre-requirements
|
- name: Install script's pre-requirements
|
||||||
run: |
|
run: |
|
||||||
python -m pip install -U pip
|
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
|
- name: Check label pattern exhaustiveness
|
||||||
run: |
|
run: |
|
||||||
python .github/workflows/scripts/check_label_pattern_exhaustiveness.py
|
python .github/workflows/scripts/check_label_pattern_exhaustiveness.py
|
||||||
|
|||||||
30
CHANGES.rst
30
CHANGES.rst
@@ -1,5 +1,35 @@
|
|||||||
.. Red changelogs
|
.. Red changelogs
|
||||||
|
|
||||||
|
Redbot 3.5.20 (2025-05-03)
|
||||||
|
==========================
|
||||||
|
|
||||||
|
| Thanks to all these amazing people that contributed to this release:
|
||||||
|
| :ghuser:`aikaterna`, :ghuser:`Jackenmen`, :ghuser:`Kreusada`
|
||||||
|
|
||||||
|
Read before updating
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
#. Information for Audio users that are using an external Lavalink instance (if you don't know what that is, you should skip this point):
|
||||||
|
|
||||||
|
We've updated our default application.yml file and you should update your instance's ``application.yml`` accordingly.
|
||||||
|
More specifically, we bumped the version of YT source plugin.
|
||||||
|
`Download Red 3.5.20's default application.yml file <https://github.com/Cog-Creators/Red-DiscordBot/releases/download/3.5.20/Red-DiscordBot-3.5.20-default-lavalink-application.yml>`__
|
||||||
|
|
||||||
|
End-user changelog
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Changes
|
||||||
|
*******
|
||||||
|
|
||||||
|
- **Core - Dependencies** - Red's dependencies have been bumped (:issue:`6568`)
|
||||||
|
|
||||||
|
Fixes
|
||||||
|
*****
|
||||||
|
|
||||||
|
- |cool| **Cogs - Audio** - Fixed recent YT playback issues (:issue:`6566`, :issue:`6567`)
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
Redbot 3.5.19 (2025-04-27)
|
Redbot 3.5.19 (2025-04-27)
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
|
|||||||
@@ -253,7 +253,23 @@ modset dm
|
|||||||
|
|
||||||
.. code-block:: none
|
.. 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**
|
**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|
|
* ``[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:
|
.. _mod-command-modset-requirereason:
|
||||||
|
|
||||||
""""""""""""""""""""
|
""""""""""""""""""""
|
||||||
|
|||||||
@@ -155,6 +155,22 @@ Here is an example of the :code:`async with` syntax:
|
|||||||
blah.append(new_blah)
|
blah.append(new_blah)
|
||||||
await ctx.send("The new blah value has been added!")
|
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::
|
.. 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
|
# 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 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::
|
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.
|
know how end users have configured their bot.
|
||||||
|
|
||||||
This does come with some performance costs, so keep the following in mind when choosing to
|
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
|
* Config use in events should be kept minimal and should only occur
|
||||||
after confirming the event needs to interact with config
|
after confirming the event needs to interact with config
|
||||||
|
|||||||
@@ -14,36 +14,96 @@ Basic Usage
|
|||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
from redbot.core.i18n import Translator, cog_i18n
|
from redbot.core.i18n import Translator, cog_i18n, set_contextual_locales_from_guild
|
||||||
|
|
||||||
|
# The translator should be defined in the module scope, with __file__ as the second parameter
|
||||||
_ = Translator("ExampleCog", __file__)
|
_ = Translator("ExampleCog", __file__)
|
||||||
|
|
||||||
|
# This decorator must be used for cog and command docstrings to be translated!
|
||||||
@cog_i18n(_)
|
@cog_i18n(_)
|
||||||
class ExampleCog:
|
class ExampleCog(commands.Cog):
|
||||||
"""description"""
|
"""Cog description"""
|
||||||
|
def __init__(self, bot):
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def mycom(self, ctx):
|
async def mycom(self, ctx):
|
||||||
"""command description"""
|
"""Command description"""
|
||||||
await ctx.send(_("This is a test command"))
|
# Correct way to translate strings:
|
||||||
|
await ctx.send(_("This is a test command run by {author}!").format(author=ctx.author.display_name))
|
||||||
|
|
||||||
|
# !!! Do not do this - String interpolation should happen after translation
|
||||||
|
await ctx.send(_("This is a test command run by {author}!".format(author=ctx.author.display_name)))
|
||||||
|
|
||||||
|
# !!! Do not use f-strings - String interpolation should happen after translation
|
||||||
|
await ctx.send(_(f"This is a test command run by {ctx.author.display_name}!"))
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_message(self, message):
|
||||||
|
# In non-command locations, you must manually call this method for guild locale settings to apply
|
||||||
|
await set_contextual_locales_from_guild(self.bot, message.guild)
|
||||||
|
if message.author.bot:
|
||||||
|
return
|
||||||
|
await message.channel.send(_("This is a non command with translation support!"))
|
||||||
|
|
||||||
--------
|
--------
|
||||||
Tutorial
|
Tutorial
|
||||||
--------
|
--------
|
||||||
|
|
||||||
After making your cog, generate a :code:`messages.pot` file
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
Preparing your cog for translations
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
We recommend using redgettext - a modified version of pygettext for Red.
|
The first step to adding translations to your cog is to add Red's internationalization framework
|
||||||
You can install redgettext by running :code:`pip install redgettext` in a command prompt.
|
to the strings in your cog. The first step is to instantiate an instance of
|
||||||
|
`redbot.core.i18n.Translator` just after the imports in each file. This object is traditionally
|
||||||
|
stored in the variable ``_`` to reduce its character count and visual impact on the code. Next,
|
||||||
|
add the `redbot.core.i18n.cog_i18n` decorator to your cog class. This will allow docstrings of
|
||||||
|
the class and its commands to be translated. Every user-facing string that is not a docstring
|
||||||
|
should then be wrapped by the Translator object. If variables are included in a string,
|
||||||
|
``.format()`` must be used, and should be called after the translation function call. This is
|
||||||
|
because ``.format()`` within the translation function call and f-strings cause the interpolation
|
||||||
|
to happen **before** the translation is applied. The translation logic needs to match the template
|
||||||
|
string to translate it, and will be unable to successfully match after interpolation occurs.
|
||||||
|
Finally, any non-command portions of your code, including listeners, tasks, and views, should call
|
||||||
|
`redbot.core.i18n.set_contextual_locales_from_guild` prior to translating any strings, as only
|
||||||
|
commands are able to implicitly determine which guild's configured locale to use. See the example
|
||||||
|
above for the exact recommended syntax.
|
||||||
|
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
Generating a messages.pot file
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
A ``messages.pot`` file is a template for translating all of the strings in your cog. It should
|
||||||
|
be generated using ``redgettext`` - a modified version of ``pygettext`` for use with Red cogs.
|
||||||
|
You can install ``redgettext`` by running :code:`pip install redgettext` in your development
|
||||||
|
environment.
|
||||||
|
|
||||||
|
Once you have ``redgettext`` installed, you will now need to run
|
||||||
|
|
||||||
|
:code:`python -m redgettext -c [path_to_cog_folder]`
|
||||||
|
|
||||||
|
This will generate a ``messages.pot`` file in ``path_to_cog_folder/locales``. This file will
|
||||||
|
contain all strings to be translated, including docstrings.
|
||||||
|
|
||||||
To generate the :code:`messages.pot` file, you will now need to run
|
|
||||||
:code:`python -m redgettext -c [path_to_cog]`
|
|
||||||
This file will contain all strings to be translated, including
|
|
||||||
docstrings.
|
|
||||||
(For advanced usage check :code:`python -m redgettext -h`)
|
(For advanced usage check :code:`python -m redgettext -h`)
|
||||||
|
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
Creating language specific translations
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
You can now use a tool like `poedit
|
You can now use a tool like `poedit
|
||||||
<https://poedit.net/>`_ to translate the strings in your messages.pot file.
|
<https://poedit.net/>`_ to translate the strings in your ``messages.pot`` file.
|
||||||
|
|
||||||
|
Alternatively, you can use any text editor to manually create translations. To do this, first
|
||||||
|
create a copy of the ``messages.pot`` file in the same folder, and name the copy
|
||||||
|
``LANGUAGE-CODE.po``, where ``LANGUAGE-CODE`` is a five character language code supported by
|
||||||
|
``[p]set locale``. Open the copy in your text editor of choice. This file contains the strings
|
||||||
|
in your cog prefixed by ``msgid`` and an empty string for you to apply translations prefixed by
|
||||||
|
``msgstr``. The original string should be translated to the target language by modifying the
|
||||||
|
associated ``msgstr``. Any variables within curly braces should **not** be translated to avoid
|
||||||
|
breaking the code when translations are applied. If keyword arguments were used in ``.format()``
|
||||||
|
calls, it may be safe to re-order variables if the grammer of the language requires doing so.
|
||||||
|
|
||||||
-------------
|
-------------
|
||||||
API Reference
|
API Reference
|
||||||
@@ -51,4 +111,4 @@ API Reference
|
|||||||
|
|
||||||
.. automodule:: redbot.core.i18n
|
.. automodule:: redbot.core.i18n
|
||||||
:members:
|
:members:
|
||||||
:special-members: __call__
|
:special-members: __call__, __init__
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ Keys common to both repo and cog info.json (case sensitive)
|
|||||||
is installed or a repo is added
|
is installed or a repo is added
|
||||||
|
|
||||||
.. tip:: You can use the ``[p]`` key in your string to use the prefix
|
.. 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
|
- ``short`` (string) - A short description of the cog or repo. For cogs, this info
|
||||||
is displayed when a user executes ``[p]cog list``
|
is displayed when a user executes ``[p]cog list``
|
||||||
|
|||||||
@@ -8,40 +8,20 @@ About (privileged) intents and public bots
|
|||||||
==========================================
|
==========================================
|
||||||
|
|
||||||
This page aims to explain Red's current intents requirements,
|
This page aims to explain Red's current intents requirements,
|
||||||
our stance regarding "public bots" and the impact of some announced
|
our stance regarding "public bots", and the discord bot verification process.
|
||||||
Discord changes coming in April 2022.
|
|
||||||
|
|
||||||
To clarify:
|
To clarify:
|
||||||
|
|
||||||
- **Small bots** are bots under 100 servers. They currently do not need to undergo Discord's
|
- **Small bots** are bots under 100 servers. They currently do not need to undergo Discord's
|
||||||
bot verification process
|
bot verification process
|
||||||
- **Public bots** (or big bots) are bots that have reached 100 servers. They need to be
|
- **Public bots** (or big bots) are bots that have reached 100 servers. They need to be
|
||||||
`verified <https://support.discord.com/hc/en-us/articles/360040720412-Bot-Verification-and-Data-Whitelisting>`_
|
`verified <https://support-dev.discord.com/hc/en-us/articles/23926564536471-How-Do-I-Get-My-App-Verified>`_
|
||||||
by Discord to join more than 100 servers and gain privileged intents
|
by Discord to join more than 100 servers and gain privileged intents
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
It is **very** important that you fully read this page if you're the owner of a public bot or strive to scale your bot at that level.
|
It is **very** important that you fully read this page if you're the owner of a public bot or strive to scale your bot at that level.
|
||||||
|
|
||||||
.. _intents-intents:
|
|
||||||
|
|
||||||
-------
|
|
||||||
Intents
|
|
||||||
-------
|
|
||||||
|
|
||||||
Red currently requires **all intents** to be active in order to function properly.
|
|
||||||
|
|
||||||
The reason for this requirement is that there are some technical challenges that need
|
|
||||||
to be overcome before we're able to adapt Red to function with only *some* intents:
|
|
||||||
these challenges are mainly due to the modular / extensible nature of Red and the fact
|
|
||||||
that Red has a long history (dating back to 2016!), making big changes naturally slower
|
|
||||||
to happen. In comparison, intents have been introduced fairly recently. |br|
|
|
||||||
This is not a problem if you have a small bot: you can simply go to the
|
|
||||||
`Discord development portal <https://discord.com/developers/applications/me>`_
|
|
||||||
and enable them. However, if you have a public bot Discord will want you to attain
|
|
||||||
verified status: you should read :ref:`our stance regarding public bots <intents-public-bots>`
|
|
||||||
and our guidelines for the :ref:`verification process <intents-bot-verification-process>`.
|
|
||||||
|
|
||||||
.. _intents-public-bots:
|
.. _intents-public-bots:
|
||||||
|
|
||||||
-----------
|
-----------
|
||||||
@@ -54,8 +34,10 @@ Red was designed with one single goal in mind: a bot that you can host on your o
|
|||||||
and customize to your needs, making it really *your* bot. **The target audience of Red are server
|
and customize to your needs, making it really *your* bot. **The target audience of Red are server
|
||||||
owners with a few servers**, often with specific needs that can be covered by the vast cog ecosystem
|
owners with a few servers**, often with specific needs that can be covered by the vast cog ecosystem
|
||||||
that the community has built over the years. |br| Red was never built with big bots in mind,
|
that the community has built over the years. |br| Red was never built with big bots in mind,
|
||||||
bots with thousands upon thousands of servers: these bots face unique challenges.
|
bots with thousands upon thousands of servers: these bots face unique challenges. Large bots need
|
||||||
Such Red instances *do exist*, it is not impossible to adapt Red and meet those criteria,
|
to be extremely efficient to handle the large amount of requests they receive, and often need to
|
||||||
|
distribute this work across multiple processes or machines to keep up.
|
||||||
|
Such Red instances *do exist*, and it is not impossible to adapt Red and meet those criteria,
|
||||||
but it requires work and bot owners with the technical knowledge to make it happen.
|
but it requires work and bot owners with the technical knowledge to make it happen.
|
||||||
It is **not** something that we support. |br|
|
It is **not** something that we support. |br|
|
||||||
When your bot reaches the public bot scale and it is therefore required to be verified it
|
When your bot reaches the public bot scale and it is therefore required to be verified it
|
||||||
@@ -75,8 +57,8 @@ the verification process.
|
|||||||
|
|
||||||
Regardless of our stance, we do feel the need to give some pointers: many bot owners reach this point
|
Regardless of our stance, we do feel the need to give some pointers: many bot owners reach this point
|
||||||
and become fairly lost, as they've simply been *users* so far.
|
and become fairly lost, as they've simply been *users* so far.
|
||||||
They have installed their bot, some cogs, personalized it, yadda yadda. Again, they have been users,
|
They have installed their bot, some cogs, personalized it, but have not needed to write any code.
|
||||||
not developers. Unless they also have an interest in development, they will likely not have a clue about
|
Unless they also have an interest in development, they will likely not have a clue about
|
||||||
what's going under the hood, much like you're not expected to be a mechanic to drive your car. And there's
|
what's going under the hood, much like you're not expected to be a mechanic to drive your car. And there's
|
||||||
nothing wrong with that! Red has been designed to be as user friendly as possible. |br|
|
nothing wrong with that! Red has been designed to be as user friendly as possible. |br|
|
||||||
The problem is this: Red is an outlier. Discord has built the bot verification process with the expectation
|
The problem is this: Red is an outlier. Discord has built the bot verification process with the expectation
|
||||||
@@ -94,41 +76,44 @@ out your application:
|
|||||||
of people that in their naivety went with the bad answer and it seems that at this point merely mentioning Red
|
of people that in their naivety went with the bad answer and it seems that at this point merely mentioning Red
|
||||||
is a guaranteed way to have your application rejected.
|
is a guaranteed way to have your application rejected.
|
||||||
|
|
||||||
.. _intents-slash-commands:
|
.. _intents-intents:
|
||||||
|
|
||||||
---------------------------------
|
-------
|
||||||
Message intent and slash commands
|
Intents
|
||||||
---------------------------------
|
-------
|
||||||
|
|
||||||
.. warning::
|
Red expects **all intents** to be active. It is possible, but not recommended, to disable
|
||||||
|
specific intents using the ``--disable-intent`` flag. If an intent is missing, you may
|
||||||
|
experience errors due to Red expecting information provided by the intent to be present.
|
||||||
|
|
||||||
If you own a public bot it is extremely important that you read this section.
|
Discord currently considers 3 intents to be
|
||||||
|
`privileged <https://support-dev.discord.com/hc/en-us/articles/6205754771351-How-do-I-get-Privileged-Intents-for-my-bot>`_,
|
||||||
|
and requires large bots to additionally apply for access to these intents. **If you have a small
|
||||||
|
bot**, you can simply follow :ref:`these instructions <enabling-privileged-intents>` to enable them.
|
||||||
|
|
||||||
Discord has announced that **starting April 2022** the content of users' messages
|
A breakdown of how privileged intents are used in Red is provided below.
|
||||||
`will be "locked" behind message intent <https://support-dev.discord.com/hc/en-us/articles/4404772028055>`_ |br|
|
|
||||||
If you're the owner of a small bot, fear not, this is yet another box that you have to tick from the
|
|
||||||
`Discord development portal <https://discord.com/developers/applications/me>`_. |br|
|
|
||||||
But if you're the owner of a public bot, things might be a lot less pleasant.
|
|
||||||
|
|
||||||
To recap, unless you have
|
The **Message Content** intent is required to use text based commands and inputs for
|
||||||
message intent, you will only receive message content for:
|
configuration and all built in functionality. App commands (also known as slash commands)
|
||||||
|
are limited to a total of 100 top level commands, which is difficult to manage on
|
||||||
|
a modular bot. The approach we have taken to address this issue is to allow 3rd party
|
||||||
|
cogs to provide slash commands, but require bot owners to pick which slash commands
|
||||||
|
they actually want to use with the ``[p]slash`` command.
|
||||||
|
Under this system, bot management commands that are not exposed to users are still
|
||||||
|
expected to be provided as text commands, which requires the bot to be able to access
|
||||||
|
message content. There are no current plans to provide slash versions of core commands.
|
||||||
|
|
||||||
- Messages that your bot sends
|
.. note::
|
||||||
- Messages that your bot receives in DM
|
It is possible to work around this intent by using the ``--mentionable``
|
||||||
- Messages in which your bot is mentioned
|
flag, and using the bot mention as a prefix to use text based commands.
|
||||||
|
|
||||||
In case it's not clear by now, your bot needs message content to parse (see) the commands it receives. And if
|
The **Guild Members** intent is required to properly cache member information, including
|
||||||
you don't attain message intent, your bot will not be able to... well, do anything. |br|
|
what users are in each server, what roles they have, what their name is, etc. It is also
|
||||||
The *bandaid fix* is for you to change your bot's prefix to a mention and a good portion of your commands will likely
|
required to receive events corresponding to when members join or leave a server, and when
|
||||||
still work. You will however lose many functions, namely anything that relies on seeing message content to act. |br|
|
they change their nickname or other server options. Almost all cogs expect to be able
|
||||||
The more *proper fix* is also not easy. You will need to justify your need for the message intent to Discord and
|
to reference the member cache in order to avoid making API requests, and are not set
|
||||||
they will only accept "compelling use cases".
|
up to check if the intent is present before doing so.
|
||||||
`It is not known what those even entail <https://gist.github.com/spiralw/091714718718379b6efcdbcaf807a024#q-what-usecases-will-be-valid>`_ at this point, but they have already stated that "parsing commands" is not a valid justification. |br|
|
|
||||||
To make the matter worse, Discord is making `a huge push for all bot developers to implement slash commands <https://support.discord.com/hc/en-us/articles/1500000368501-Slash-Commands-FAQ>`_, which at the moment
|
The **Guild Presences** intent is required to view the activities and status of
|
||||||
are rather lacking in features and cannot cover all the functionalities that standard commands offer. |br|
|
users. Cogs which perform actions on users based on their activity or status will
|
||||||
Discord staff
|
be unable to access this information if this intent is not enabled.
|
||||||
`stated that they will want your bot to have slash commands when you ask for message intent <https://gist.github.com/spiralw/091714718718379b6efcdbcaf807a024#q-if-we-are-granted-this-intent-will-bots-be-sanctioned-if-they-use-it-for-their-own-use-case-but-also-to-continue-to-run-normal-non-slash-commands-or-do-we-assume-that-if-you-are-granted-the-intent-you-are-trusted-with-it-and-are-allowed-to-use-it-for-additional-uses>`_. |br|
|
|
||||||
Slash commands might very well turn out to be a big undertaking for the Red team to implement, even more now that our
|
|
||||||
underlying library, `discord.py <https://github.com/Rapptz/discord.py>`_, has been discontinued. |br|
|
|
||||||
The time window that Discord is giving us to adapt is very narrow: **Red will likely not be able to support slash
|
|
||||||
commands for April 2022** and you should plan accordingly.
|
|
||||||
|
|||||||
@@ -339,7 +339,7 @@ def _early_init():
|
|||||||
|
|
||||||
|
|
||||||
# This is bumped automatically by release workflow (`.github/workflows/scripts/bump_version.py`)
|
# This is bumped automatically by release workflow (`.github/workflows/scripts/bump_version.py`)
|
||||||
_VERSION = "3.5.19"
|
_VERSION = "3.5.21.dev1"
|
||||||
|
|
||||||
__version__, version_info = VersionInfo._get_version()
|
__version__, version_info = VersionInfo._get_version()
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,11 @@ class AliasEntry:
|
|||||||
extra = []
|
extra = []
|
||||||
while not view.eof:
|
while not view.eof:
|
||||||
prev = view.index
|
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:
|
if len(word) < view.index - prev:
|
||||||
word = "".join((view.buffer[prev], word, view.buffer[view.index - 1]))
|
word = "".join((view.buffer[prev], word, view.buffer[view.index - 1]))
|
||||||
extra.append(word.strip(" "))
|
extra.append(word.strip(" "))
|
||||||
|
|||||||
@@ -49,13 +49,27 @@ DEFAULT_LAVALINK_YAML = {
|
|||||||
"yaml__plugins__youtube__allowDirectPlaylistIds": True,
|
"yaml__plugins__youtube__allowDirectPlaylistIds": True,
|
||||||
"yaml__plugins__youtube__clients": [
|
"yaml__plugins__youtube__clients": [
|
||||||
"MUSIC",
|
"MUSIC",
|
||||||
"WEB",
|
|
||||||
"WEBEMBEDDED",
|
"WEBEMBEDDED",
|
||||||
"MWEB",
|
"ANDROID_VR",
|
||||||
|
"ANDROID_MUSIC",
|
||||||
"TVHTML5EMBEDDED",
|
"TVHTML5EMBEDDED",
|
||||||
"TV",
|
"TV",
|
||||||
"IOS",
|
"IOS",
|
||||||
|
"WEB",
|
||||||
|
"MWEB",
|
||||||
],
|
],
|
||||||
|
"yaml__plugins__youtube__ANDROID_MUSIC__playback": True,
|
||||||
|
"yaml__plugins__youtube__ANDROID_MUSIC__playlistLoading": False,
|
||||||
|
"yaml__plugins__youtube__ANDROID_MUSIC__searching": True,
|
||||||
|
"yaml__plugins__youtube__ANDROID_MUSIC__videoLoading": False,
|
||||||
|
"yaml__plugins__youtube__ANDROID_VR__playback": False,
|
||||||
|
"yaml__plugins__youtube__ANDROID_VR__playlistLoading": True,
|
||||||
|
"yaml__plugins__youtube__ANDROID_VR__searching": True,
|
||||||
|
"yaml__plugins__youtube__ANDROID_VR__videoLoading": False,
|
||||||
|
"yaml__plugins__youtube__IOS__playback": True,
|
||||||
|
"yaml__plugins__youtube__IOS__playlistLoading": True,
|
||||||
|
"yaml__plugins__youtube__IOS__searching": True,
|
||||||
|
"yaml__plugins__youtube__IOS__videoLoading": False,
|
||||||
"yaml__plugins__youtube__MUSIC__playback": False,
|
"yaml__plugins__youtube__MUSIC__playback": False,
|
||||||
"yaml__plugins__youtube__MUSIC__playlistLoading": False,
|
"yaml__plugins__youtube__MUSIC__playlistLoading": False,
|
||||||
"yaml__plugins__youtube__MUSIC__searching": True,
|
"yaml__plugins__youtube__MUSIC__searching": True,
|
||||||
@@ -122,7 +136,11 @@ def generate_server_config(config_data: Dict[str, Any]) -> Dict[str, Any]:
|
|||||||
|
|
||||||
|
|
||||||
# This assumes all keys with `_` should be converted from `part1_part2` to `part1-part2`
|
# This assumes all keys with `_` should be converted from `part1_part2` to `part1-part2`
|
||||||
|
# unless it's all uppercase which we assume to be a special enum value
|
||||||
|
# (e.g. ANDROID_VR will not be converted)
|
||||||
def _convert_function(key: str) -> str:
|
def _convert_function(key: str) -> str:
|
||||||
|
if key.isupper():
|
||||||
|
return key
|
||||||
return key.replace("_", "-")
|
return key.replace("_", "-")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ __all__ = (
|
|||||||
|
|
||||||
|
|
||||||
JAR_VERSION: Final[LavalinkVersion] = LavalinkVersion(3, 7, 12, red=1)
|
JAR_VERSION: Final[LavalinkVersion] = LavalinkVersion(3, 7, 12, red=1)
|
||||||
YT_PLUGIN_VERSION: Final[str] = "1.13.0"
|
YT_PLUGIN_VERSION: Final[str] = "1.13.1"
|
||||||
# keep this sorted from oldest to latest
|
# keep this sorted from oldest to latest
|
||||||
SUPPORTED_JAVA_VERSIONS: Final[Tuple[int, ...]] = (11, 17)
|
SUPPORTED_JAVA_VERSIONS: Final[Tuple[int, ...]] = (11, 17)
|
||||||
LATEST_SUPPORTED_JAVA_VERSION: Final = SUPPORTED_JAVA_VERSIONS[-1]
|
LATEST_SUPPORTED_JAVA_VERSION: Final = SUPPORTED_JAVA_VERSIONS[-1]
|
||||||
|
|||||||
@@ -143,6 +143,8 @@ class KickBanMixin(MixinMeta):
|
|||||||
|
|
||||||
toggle = await self.config.guild(guild).dm_on_kickban()
|
toggle = await self.config.guild(guild).dm_on_kickban()
|
||||||
if toggle:
|
if toggle:
|
||||||
|
extra_embed = await self.config.guild(guild).ban_show_extra()
|
||||||
|
|
||||||
with contextlib.suppress(discord.HTTPException):
|
with contextlib.suppress(discord.HTTPException):
|
||||||
em = discord.Embed(
|
em = discord.Embed(
|
||||||
title=bold(_("You have been banned from {guild}.").format(guild=guild)),
|
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."),
|
value=reason if reason is not None else _("No reason was given."),
|
||||||
inline=False,
|
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)
|
await user.send(embed=em)
|
||||||
|
|
||||||
ban_type = "ban"
|
ban_type = "ban"
|
||||||
@@ -658,16 +671,38 @@ class KickBanMixin(MixinMeta):
|
|||||||
|
|
||||||
with contextlib.suppress(discord.HTTPException):
|
with contextlib.suppress(discord.HTTPException):
|
||||||
# We don't want blocked DMs preventing us from banning
|
# 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:
|
if invite:
|
||||||
msg += _("\n\nHere is an invite for when your ban expires: {invite_link}").format(
|
em.add_field(
|
||||||
invite_link=invite
|
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)
|
audit_reason = get_audit_reason(author, reason, shorten=True)
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,9 @@ class Mod(
|
|||||||
"default_days": 0,
|
"default_days": 0,
|
||||||
"default_tempban_duration": 60 * 60 * 24,
|
"default_tempban_duration": 60 * 60 * 24,
|
||||||
"track_nicknames": True,
|
"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}
|
default_channel_settings = {"ignored": False}
|
||||||
|
|||||||
@@ -309,9 +309,9 @@ class ModInfo(MixinMeta):
|
|||||||
usernames, display_names, nicks = await self.get_names(member)
|
usernames, display_names, nicks = await self.get_names(member)
|
||||||
parts = []
|
parts = []
|
||||||
for header, names in (
|
for header, names in (
|
||||||
(_("Past 20 usernames:"), usernames),
|
(_("Past 20 usernames: "), usernames),
|
||||||
(_("Past 20 global display names:"), display_names),
|
(_("Past 20 global display names: "), display_names),
|
||||||
(_("Past 20 server nicknames:"), nicks),
|
(_("Past 20 server nicknames: "), nicks),
|
||||||
):
|
):
|
||||||
if names:
|
if names:
|
||||||
parts.append(bold(header) + ", ".join(names))
|
parts.append(bold(header) + ", ".join(names))
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ class ModSettings(MixinMeta):
|
|||||||
dm_on_kickban = data["dm_on_kickban"]
|
dm_on_kickban = data["dm_on_kickban"]
|
||||||
default_days = data["default_days"]
|
default_days = data["default_days"]
|
||||||
default_tempban_duration = data["default_tempban_duration"]
|
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:
|
if not track_all_names and track_nicknames:
|
||||||
yes_or_no = _("Overridden by another setting")
|
yes_or_no = _("Overridden by another setting")
|
||||||
else:
|
else:
|
||||||
@@ -98,9 +101,18 @@ class ModSettings(MixinMeta):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
msg += _("Default message history delete on ban: Don't delete any\n")
|
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)
|
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))
|
await ctx.send(box(msg))
|
||||||
|
|
||||||
@modset.command()
|
@modset.command()
|
||||||
@@ -347,9 +359,15 @@ class ModSettings(MixinMeta):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@modset.command()
|
@modset.group()
|
||||||
@commands.guild_only()
|
@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.
|
"""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
|
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.")
|
_("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()
|
@modset.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def requirereason(self, ctx: commands.Context, enabled: bool = None):
|
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):
|
with contextlib.suppress(discord.Forbidden, discord.HTTPException):
|
||||||
if val is None:
|
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(
|
await author.send(
|
||||||
_(
|
_(
|
||||||
"This server has no reports channel set up. Please contact a server admin."
|
"This server has no reports channel set up. Please contact a server admin."
|
||||||
|
|||||||
@@ -314,6 +314,19 @@ def parse_cli_flags(args):
|
|||||||
default=None,
|
default=None,
|
||||||
help="Forcefully disables the Rich logging handlers.",
|
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(
|
parser.add_argument(
|
||||||
"--rich-traceback-extra-lines",
|
"--rich-traceback-extra-lines",
|
||||||
type=non_negative_int,
|
type=non_negative_int,
|
||||||
|
|||||||
@@ -2477,23 +2477,34 @@ class Red(
|
|||||||
msg = await channel.send(box(page, lang=box_lang))
|
msg = await channel.send(box(page, lang=box_lang))
|
||||||
ret.append(msg)
|
ret.append(msg)
|
||||||
n_remaining = len(messages) - idx
|
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 > 0:
|
||||||
if n_remaining == 1:
|
if n_remaining == 1:
|
||||||
prompt_text = _(
|
if files_perm:
|
||||||
"There is still one message remaining. Type {command_1} to continue"
|
prompt_text = _(
|
||||||
" or {command_2} to upload all contents as a file."
|
"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:
|
else:
|
||||||
prompt_text = _(
|
if files_perm:
|
||||||
"There are still {count} messages remaining. Type {command_1} to continue"
|
prompt_text = _(
|
||||||
" or {command_2} to upload all contents as a file."
|
"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(
|
query = await channel.send(
|
||||||
prompt_text.format(count=n_remaining, command_1="`more`", command_2="`file`")
|
prompt_text.format(count=n_remaining, command_1="`more`", command_2="`file`")
|
||||||
)
|
)
|
||||||
pred = MessagePredicate.lower_contained_in(
|
pred = MessagePredicate.lower_contained_in(options, channel=channel, user=user)
|
||||||
("more", "file"), channel=channel, user=user
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
resp = await self.wait_for(
|
resp = await self.wait_for(
|
||||||
"message",
|
"message",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import argparse
|
import argparse
|
||||||
|
import logging
|
||||||
import logging.handlers
|
import logging.handlers
|
||||||
import pathlib
|
import pathlib
|
||||||
import re
|
import re
|
||||||
@@ -33,6 +34,7 @@ from rich.traceback import PathHighlighter, Traceback # DEP-WARN
|
|||||||
|
|
||||||
|
|
||||||
MAX_OLD_LOGS = 8
|
MAX_OLD_LOGS = 8
|
||||||
|
log = logging.getLogger("red.logging")
|
||||||
|
|
||||||
|
|
||||||
class RotatingFileHandler(logging.handlers.RotatingFileHandler):
|
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="{")
|
rich_formatter = logging.Formatter("{message}", datefmt="[%X]", style="{")
|
||||||
|
|
||||||
stdout_handler = RedRichHandler(
|
stdout_handler = RedRichHandler(
|
||||||
rich_tracebacks=True,
|
rich_tracebacks=cli_flags.rich_tracebacks,
|
||||||
show_path=False,
|
show_path=False,
|
||||||
highlighter=NullHighlighter(),
|
highlighter=NullHighlighter(),
|
||||||
tracebacks_extra_lines=cli_flags.rich_traceback_extra_lines,
|
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):
|
for fhandler in (latest_fhandler, all_fhandler):
|
||||||
fhandler.setFormatter(file_formatter)
|
fhandler.setFormatter(file_formatter)
|
||||||
root_logger.addHandler(fhandler)
|
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."
|
||||||
|
)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ alabaster==0.7.13
|
|||||||
# via sphinx
|
# via sphinx
|
||||||
certifi==2025.4.26
|
certifi==2025.4.26
|
||||||
# via requests
|
# via requests
|
||||||
charset-normalizer==3.4.1
|
charset-normalizer==3.4.2
|
||||||
# via requests
|
# via requests
|
||||||
docutils==0.20.1
|
docutils==0.20.1
|
||||||
# via
|
# via
|
||||||
|
|||||||
Reference in New Issue
Block a user