From d2b9504c3bce11f07c70270ebe15373e6999819b Mon Sep 17 00:00:00 2001 From: jack1142 <6032823+jack1142@users.noreply.github.com> Date: Sun, 12 Jan 2020 17:13:58 +0100 Subject: [PATCH 01/49] [Docs] Add "Fork me on GitHub" ribbon (#3306) * docs: add "Fork me on GitHub" ribbon * chore(changelog): add towncrier entry --- changelog.d/3306.docs.rst | 1 + docs/_templates/layout.html | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 changelog.d/3306.docs.rst create mode 100644 docs/_templates/layout.html diff --git a/changelog.d/3306.docs.rst b/changelog.d/3306.docs.rst new file mode 100644 index 000000000..e52352c5b --- /dev/null +++ b/changelog.d/3306.docs.rst @@ -0,0 +1 @@ +Add "Fork me on GitHub" ribbon in top right corner of the docs page. \ No newline at end of file diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html new file mode 100644 index 000000000..91fab053e --- /dev/null +++ b/docs/_templates/layout.html @@ -0,0 +1,9 @@ +{% extends '!layout.html' %} +{% block document %} +{{ super() }} + + Fork me on GitHub + +{% endblock %} \ No newline at end of file From 475416005541dd914030b03481596a62528359cd Mon Sep 17 00:00:00 2001 From: jack1142 <6032823+jack1142@users.noreply.github.com> Date: Sun, 12 Jan 2020 22:08:34 +0100 Subject: [PATCH 02/49] [Docs] Update Debian/Raspbian instructions (#3352) * Update install_linux_mac.rst * Update install_linux_mac.rst * Update install_linux_mac.rst --- docs/install_linux_mac.rst | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/docs/install_linux_mac.rst b/docs/install_linux_mac.rst index 26df6f823..1fe908070 100644 --- a/docs/install_linux_mac.rst +++ b/docs/install_linux_mac.rst @@ -51,15 +51,40 @@ CentOS and RHEL 7 Complete the rest of the installation by `installing Python 3.8 with pyenv `. +.. _install-debian-stretch: + +~~~~~~~~~~~~~~ +Debian Stretch +~~~~~~~~~~~~~~ + +.. note:: + + This guide is only for Debian Stretch users, these instructions won't work with + Raspbian Stretch. Raspbian Buster is the only version of Raspbian supported by Red. + +We recommend installing pyenv as a method of installing non-native versions of python on +Debian Stretch. This guide will tell you how. First, run the following commands: + +.. code-block:: none + + sudo echo "deb http://deb.debian.org/debian stretch-backports main" >> /etc/apt/sources.list.d/red-sources.list + sudo apt update + sudo apt -y install make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev \ + libsqlite3-dev wget curl llvm libncurses5-dev xz-utils tk-dev libxml2-dev \ + libxmlsec1-dev libffi-dev liblzma-dev libgdbm-dev uuid-dev python3-openssl git openjdk-11-jre + CXX=/usr/bin/g++ + +Complete the rest of the installation by `installing Python 3.8 with pyenv `. + .. _install-debian: .. _install-raspbian: -~~~~~~~~~~~~~~~~~~~ -Debian and Raspbian -~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~ +Debian and Raspbian Buster +~~~~~~~~~~~~~~~~~~~~~~~~~~ We recommend installing pyenv as a method of installing non-native versions of python on -Debian/Raspbian. This guide will tell you how. First, run the following commands: +Debian/Raspbian Buster. This guide will tell you how. First, run the following commands: .. code-block:: none From e52c20b9e79d2004e901b4ef31ef42f2ea1444ed Mon Sep 17 00:00:00 2001 From: Draper <27962761+Drapersniper@users.noreply.github.com> Date: Sun, 12 Jan 2020 21:20:46 +0000 Subject: [PATCH 03/49] [Audio] Hotfix an edge case where an attribute error can be raised (#3328) * Limit Playlists Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Hotfix Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Hotfix Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> --- changelog.d/audio/3328.hotfix.1.rst | 1 + redbot/cogs/audio/apis.py | 2 ++ redbot/cogs/audio/databases.py | 2 ++ 3 files changed, 5 insertions(+) create mode 100644 changelog.d/audio/3328.hotfix.1.rst diff --git a/changelog.d/audio/3328.hotfix.1.rst b/changelog.d/audio/3328.hotfix.1.rst new file mode 100644 index 000000000..08e7ed7b9 --- /dev/null +++ b/changelog.d/audio/3328.hotfix.1.rst @@ -0,0 +1 @@ +Fixed an attribute error that can be raised on play commands for spotify tracks. \ No newline at end of file diff --git a/redbot/cogs/audio/apis.py b/redbot/cogs/audio/apis.py index eb6dbd7c0..87745d5ce 100644 --- a/redbot/cogs/audio/apis.py +++ b/redbot/cogs/audio/apis.py @@ -750,6 +750,8 @@ class MusicCache: log.debug(f"Querying Local Database for {query}") task = ("update", ("lavalink", {"query": query})) self.append_task(ctx, *task) + else: + val = None if val and not forced: data = val data["query"] = query diff --git a/redbot/cogs/audio/databases.py b/redbot/cogs/audio/databases.py index 6951a91b7..ee136c222 100644 --- a/redbot/cogs/audio/databases.py +++ b/redbot/cogs/audio/databases.py @@ -91,6 +91,8 @@ class CacheFetchResult: k in self.query for k in ["loadType", "playlistInfo", "isSeekable", "isStream"] ): self.query = json.loads(self.query) + else: + self.query = None @dataclass From d6936c87f3422d27d4649534a46fd9852676379f Mon Sep 17 00:00:00 2001 From: Draper <27962761+Drapersniper@users.noreply.github.com> Date: Sun, 12 Jan 2020 22:37:04 +0000 Subject: [PATCH 04/49] chore (#3348) Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> --- changelog.d/audio/3345.enhance.1.rst | 1 + redbot/cogs/audio/utils.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 changelog.d/audio/3345.enhance.1.rst diff --git a/changelog.d/audio/3345.enhance.1.rst b/changelog.d/audio/3345.enhance.1.rst new file mode 100644 index 000000000..1a1767590 --- /dev/null +++ b/changelog.d/audio/3345.enhance.1.rst @@ -0,0 +1 @@ +Fix an issues with the formatting of non existing local tracks. \ No newline at end of file diff --git a/redbot/cogs/audio/utils.py b/redbot/cogs/audio/utils.py index b4cffcb02..e44abab2e 100644 --- a/redbot/cogs/audio/utils.py +++ b/redbot/cogs/audio/utils.py @@ -213,7 +213,7 @@ async def clear_react(bot: Red, message: discord.Message, emoji: MutableMapping def get_track_description(track) -> Optional[str]: if track and getattr(track, "uri", None): query = Query.process_input(track.uri) - if query.is_local: + if query.is_local or "localtracks/" in track.uri: if track.title != "Unknown title": return f'**{escape(f"{track.author} - {track.title}")}**' + escape( f"\n{query.to_string_user()} " @@ -229,7 +229,7 @@ def get_track_description(track) -> Optional[str]: def get_track_description_unformatted(track) -> Optional[str]: if track and hasattr(track, "uri"): query = Query.process_input(track.uri) - if query.is_local: + if query.is_local or "localtracks/" in track.uri: if track.title != "Unknown title": return escape(f"{track.author} - {track.title}") else: From fdfbfe7b601d864cbe9fa3ad299eb149ebce7ef4 Mon Sep 17 00:00:00 2001 From: Draper <27962761+Drapersniper@users.noreply.github.com> Date: Sun, 12 Jan 2020 22:37:50 +0000 Subject: [PATCH 05/49] [3.2.3][Audio] Full fix for #3328 (#3355) * Limit Playlists Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Hotfix Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Hotfix Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * [Audio] Hotfix an edge case where an attribute error can be raised (#3328) * Limit Playlists Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Hotfix Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Hotfix Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * flame's review Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Delete 3328.hotfix.1.rst * lets be extra safe here Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> --- changelog.d/audio/3328.hotfix.2.rst | 1 + redbot/cogs/audio/apis.py | 40 ++++++++++++++++------------- redbot/cogs/audio/audio.py | 39 ++++++++++++++++------------ 3 files changed, 46 insertions(+), 34 deletions(-) create mode 100644 changelog.d/audio/3328.hotfix.2.rst diff --git a/changelog.d/audio/3328.hotfix.2.rst b/changelog.d/audio/3328.hotfix.2.rst new file mode 100644 index 000000000..039b506e0 --- /dev/null +++ b/changelog.d/audio/3328.hotfix.2.rst @@ -0,0 +1 @@ +Check data before it is inserted into the database to avoid corruption. \ No newline at end of file diff --git a/redbot/cogs/audio/apis.py b/redbot/cogs/audio/apis.py index 87745d5ce..5bc359efc 100644 --- a/redbot/cogs/audio/apis.py +++ b/redbot/cogs/audio/apis.py @@ -746,13 +746,13 @@ class MusicCache: (val, update) = await self.database.fetch_one("lavalink", "data", {"query": query}) if update: val = None - if val and not isinstance(val, str): + if val and isinstance(val, dict): log.debug(f"Querying Local Database for {query}") task = ("update", ("lavalink", {"query": query})) self.append_task(ctx, *task) else: val = None - if val and not forced: + if val and not forced and isinstance(val, dict): data = val data["query"] = query results = LoadResult(data) @@ -780,21 +780,25 @@ class MusicCache: ): with contextlib.suppress(SQLError): time_now = int(datetime.datetime.now(datetime.timezone.utc).timestamp()) - task = ( - "insert", - ( - "lavalink", - [ - { - "query": query, - "data": json.dumps(results._raw), - "last_updated": time_now, - "last_fetched": time_now, - } - ], - ), - ) - self.append_task(ctx, *task) + data = json.dumps(results._raw) + if all( + k in data for k in ["loadType", "playlistInfo", "isSeekable", "isStream"] + ): + task = ( + "insert", + ( + "lavalink", + [ + { + "query": query, + "data": data, + "last_updated": time_now, + "last_fetched": time_now, + } + ], + ), + ) + self.append_task(ctx, *task) return results, called_api async def run_tasks(self, ctx: Optional[commands.Context] = None, _id=None): @@ -855,7 +859,7 @@ class MusicCache: query_data["maxage"] = maxage_int vals = await self.database.fetch_all("lavalink", "data", query_data) - recently_played = [r.tracks for r in vals if r] + recently_played = [r.tracks for r in vals if r if isinstance(tracks, dict)] if recently_played: track = random.choice(recently_played) diff --git a/redbot/cogs/audio/audio.py b/redbot/cogs/audio/audio.py index f29da5c6d..356d23924 100644 --- a/redbot/cogs/audio/audio.py +++ b/redbot/cogs/audio/audio.py @@ -246,14 +246,19 @@ class Audio(commands.Cog): uri = t.get("info", {}).get("uri") if uri: t = {"loadType": "V2_COMPACT", "tracks": [t], "query": uri} - database_entries.append( - { - "query": uri, - "data": json.dumps(t), - "last_updated": time_now, - "last_fetched": time_now, - } - ) + data = json.dumps(t) + if all( + k in data + for k in ["loadType", "playlistInfo", "isSeekable", "isStream"] + ): + database_entries.append( + { + "query": uri, + "data": data, + "last_updated": time_now, + "last_fetched": time_now, + } + ) await asyncio.sleep(0) if guild_playlist: all_playlist[str(guild_id)] = guild_playlist @@ -5883,14 +5888,16 @@ class Audio(commands.Cog): uri = t.get("info", {}).get("uri") if uri: t = {"loadType": "V2_COMPACT", "tracks": [t], "query": uri} - database_entries.append( - { - "query": uri, - "data": json.dumps(t), - "last_updated": time_now, - "last_fetched": time_now, - } - ) + data = json.dumps(t) + if all(k in data for k in ["loadType", "playlistInfo", "isSeekable", "isStream"]): + database_entries.append( + { + "query": uri, + "data": data, + "last_updated": time_now, + "last_fetched": time_now, + } + ) if database_entries: await self.music_cache.database.insert("lavalink", database_entries) From 54711b2054e28e9c7d4334645e6cda8c744c3d04 Mon Sep 17 00:00:00 2001 From: jack1142 <6032823+jack1142@users.noreply.github.com> Date: Sun, 12 Jan 2020 23:41:29 +0100 Subject: [PATCH 06/49] [Docs] Update autostart guides to use `-O` flag (#3354) * Update autostart_systemd.rst * Update autostart_pm2.rst --- docs/autostart_pm2.rst | 12 +++++++----- docs/autostart_systemd.rst | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/autostart_pm2.rst b/docs/autostart_pm2.rst index b7e28e38d..27300ec3b 100644 --- a/docs/autostart_pm2.rst +++ b/docs/autostart_pm2.rst @@ -17,18 +17,20 @@ Start by installing Node.JS and NPM via your favorite package distributor. From After PM2 is installed, run the following command to enable your Red instance to be managed by PM2. Replace the brackets with the required information. You can add additional Red based arguments after the instance, such as :code:`--dev`. -:code:`pm2 start redbot --name "" --interpreter "" -- --no-prompt` +.. code-block:: none + + pm2 start redbot --name "" --interpreter "" --interpreter-args "-O" -- --no-prompt .. code-block:: none Arguments to replace. - --name "" + A name to identify the bot within pm2, this is not your Red instance. - --interpreter "" - The location of your Python interpreter, to find out where that is use the following command: - which python3.6 + + The location of your Python interpreter, to find out where that is use the following command inside activated venv: + which python The name of your Red instance. diff --git a/docs/autostart_systemd.rst b/docs/autostart_systemd.rst index 29d404c44..06d5b141a 100644 --- a/docs/autostart_systemd.rst +++ b/docs/autostart_systemd.rst @@ -18,7 +18,7 @@ In order to create the service file, you will first need the location of your :c # If you are using pyenv pyenv shell - which redbot + which python Then create the new service file: @@ -33,7 +33,7 @@ Paste the following and replace all instances of :code:`username` with the usern After=multi-user.target [Service] - ExecStart=path %I --no-prompt + ExecStart=path -O -m redbot %I --no-prompt User=username Group=username Type=idle From ed76454ddb0ea3e1d0451111f7f5d20164511632 Mon Sep 17 00:00:00 2001 From: Draper <27962761+Drapersniper@users.noreply.github.com> Date: Sun, 12 Jan 2020 22:42:17 +0000 Subject: [PATCH 07/49] ... (#3350) Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> --- changelog.d/audio/3349.bugfix.1.rst | 1 + redbot/cogs/audio/audio.py | 23 ++++++++++++----------- 2 files changed, 13 insertions(+), 11 deletions(-) create mode 100644 changelog.d/audio/3349.bugfix.1.rst diff --git a/changelog.d/audio/3349.bugfix.1.rst b/changelog.d/audio/3349.bugfix.1.rst new file mode 100644 index 000000000..193d44958 --- /dev/null +++ b/changelog.d/audio/3349.bugfix.1.rst @@ -0,0 +1 @@ +Fixed a bug where ``[p]audioset dc`` didn't disconnect the bot. \ No newline at end of file diff --git a/redbot/cogs/audio/audio.py b/redbot/cogs/audio/audio.py index 356d23924..afd34253e 100644 --- a/redbot/cogs/audio/audio.py +++ b/redbot/cogs/audio/audio.py @@ -535,17 +535,18 @@ class Audio(commands.Cog): player_check = await self._players_check() await self._status_check(*player_check) - if not autoplay and event_type == lavalink.LavalinkEvents.QUEUE_END and notify: - notify_channel = player.fetch("channel") - if notify_channel: - notify_channel = self.bot.get_channel(notify_channel) - await self._embed_msg(notify_channel, title=_("Queue Ended.")) - elif not autoplay and event_type == lavalink.LavalinkEvents.QUEUE_END and disconnect: - self.bot.dispatch("red_audio_audio_disconnect", guild) - await player.disconnect() - if event_type == lavalink.LavalinkEvents.QUEUE_END and status: - player_check = await self._players_check() - await self._status_check(*player_check) + if event_type == lavalink.LavalinkEvents.QUEUE_END: + if not autoplay: + notify_channel = player.fetch("channel") + if notify_channel and notify: + notify_channel = self.bot.get_channel(notify_channel) + await self._embed_msg(notify_channel, title=_("Queue Ended.")) + if disconnect: + self.bot.dispatch("red_audio_audio_disconnect", guild) + await player.disconnect() + if status: + player_check = await self._players_check() + await self._status_check(*player_check) if event_type in [ lavalink.LavalinkEvents.TRACK_EXCEPTION, From 8514dbe96ab798d2ec919e0c7b3b651b5bf86c82 Mon Sep 17 00:00:00 2001 From: Draper <27962761+Drapersniper@users.noreply.github.com> Date: Sun, 12 Jan 2020 22:42:59 +0000 Subject: [PATCH 08/49] [3.2.3][Docs]Only support venv and virtualenv users (#3351) * Limit Playlists Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * docs Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * jack Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * update pip Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * flame's review Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> --- docs/install_linux_mac.rst | 20 +++++--------------- docs/install_windows.rst | 24 ++++++++++-------------- docs/venv_guide.rst | 17 ++++++----------- 3 files changed, 21 insertions(+), 40 deletions(-) diff --git a/docs/install_linux_mac.rst b/docs/install_linux_mac.rst index 1fe908070..b767c1592 100644 --- a/docs/install_linux_mac.rst +++ b/docs/install_linux_mac.rst @@ -256,7 +256,7 @@ Pyenv is now installed and your system should be configured to run Python 3.8. Creating a Virtual Environment ------------------------------ -We **strongly** recommend installing Red into a virtual environment. Don't be scared, it's very +We require installing Red into a virtual environment. Don't be scared, it's very straightforward. See the section `installing-in-virtual-environment`. .. _installing-red-linux-mac: @@ -267,29 +267,19 @@ Installing Red Choose one of the following commands to install Red. -.. note:: - - If you're not inside an activated virtual environment, include the ``--user`` flag with all - ``python3.8 -m pip install`` commands, like this: - - .. code-block:: none - - python3.8 -m pip install --user -U setuptools wheel - python3.8 -m pip install --user -U Red-DiscordBot - To install without additional config backend support: .. code-block:: none - python3.8 -m pip install -U setuptools wheel - python3.8 -m pip install -U Red-DiscordBot + python -m pip install -U pip setuptools wheel + python -m pip install -U Red-DiscordBot Or, to install with PostgreSQL support: .. code-block:: none - python3.8 -m pip install -U setuptools wheel - python3.8 -m pip install -U Red-DiscordBot[postgres] + python -m pip install -U pip setuptools wheel + python -m pip install -U Red-DiscordBot[postgres] -------------------------- diff --git a/docs/install_windows.rst b/docs/install_windows.rst index 93e390863..36fcc11fa 100644 --- a/docs/install_windows.rst +++ b/docs/install_windows.rst @@ -64,6 +64,13 @@ Manually installing dependencies .. _installing-red-windows: +------------------------------ +Creating a Virtual Environment +------------------------------ + +We require installing Red into a virtual environment. Don't be scared, it's very +straightforward. See the section `installing-in-virtual-environment`. + -------------- Installing Red -------------- @@ -72,31 +79,20 @@ Installing Red for the PATH changes to take effect. 1. Open a command prompt (open Start, search for "command prompt", then click it) -2. Create and activate a virtual environment (strongly recommended), see the section `using-venv` -3. Run **one** of the following commands, depending on what extras you want installed - - .. note:: - - If you're not inside an activated virtual environment, use ``py -3.8`` in place of - ``python``, and include the ``--user`` flag with all ``pip install`` commands, like this: - - .. code-block:: none - - py -3.8 -m pip install --user -U setuptools wheel - py -3.8 -m pip install --user -U Red-DiscordBot +2. Run **one** of the following set of commands, depending on what extras you want installed * Normal installation: .. code-block:: none - python -m pip install -U setuptools wheel + python -m pip install -U pip setuptools wheel python -m pip install -U Red-DiscordBot * With PostgreSQL support: .. code-block:: none - python -m pip install -U setuptools wheel + python -m pip install -U pip setuptools wheel python -m pip install -U Red-DiscordBot[postgres] diff --git a/docs/venv_guide.rst b/docs/venv_guide.rst index 1aa34b93d..dea83eee6 100644 --- a/docs/venv_guide.rst +++ b/docs/venv_guide.rst @@ -9,14 +9,9 @@ problems. Firstly, simply choose how you'd like to create your virtual environme * :ref:`using-venv` (quick and easy, involves two commands) * :ref:`using-pyenv-virtualenv` (recommended if you installed Python with pyenv) -**Why Should I Use a Virtual Environment?** - -90% of the installation and setup issues raised in our support channels are resolved when the user -creates a virtual environment. - **What Are Virtual Environments For?** -Virtual environments allow you to isolate red's library dependencies, cog dependencies and python +Virtual environments allow you to isolate Red's library dependencies, cog dependencies and python binaries from the rest of your system. It also makes sure Red and its dependencies are installed to a predictable location. It makes uninstalling Red as simple as removing a single folder, without worrying about losing your data or other things on your system becoming broken. @@ -31,18 +26,18 @@ python. First, choose a directory where you would like to create your virtual environment. It's a good idea to keep it in a location which is easy to type out the path to. From now, we'll call it -``redenv``. +``redenv`` and it will be located in your home directory. ~~~~~~~~~~~~~~~~~~~~~~~~ ``venv`` on Linux or Mac ~~~~~~~~~~~~~~~~~~~~~~~~ Create your virtual environment with the following command:: - python3.8 -m venv redenv + python3.8 -m venv ~/redenv And activate it with the following command:: - source redenv/bin/activate + source ~/redenv/bin/activate .. important:: @@ -56,11 +51,11 @@ Continue reading `below `. ~~~~~~~~~~~~~~~~~~~ Create your virtual environment with the following command:: - py -3.8 -m venv redenv + py -3.8 -m venv %userprofile%\redenv And activate it with the following command:: - redenv\Scripts\activate.bat + %userprofile%\redenv\Scripts\activate.bat .. important:: From fe7770c833e5cf9c67f92daea54b3c97f99e1b93 Mon Sep 17 00:00:00 2001 From: Flame442 <34169552+Flame442@users.noreply.github.com> Date: Sun, 12 Jan 2020 15:01:45 -0800 Subject: [PATCH 09/49] [Admin] Fixes hierarchy issues in [p]selfrole and [p]selfroleset (#3331) * More fixes * Update admin.py --- redbot/cogs/admin/admin.py | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/redbot/cogs/admin/admin.py b/redbot/cogs/admin/admin.py index ae7412129..e85e302b4 100644 --- a/redbot/cogs/admin/admin.py +++ b/redbot/cogs/admin/admin.py @@ -116,12 +116,14 @@ class Admin(commands.Cog): :param role: :return: """ - return ctx.author.top_role > role + return ctx.author.top_role > role or ctx.author == ctx.guild.owner - async def _addrole(self, ctx: commands.Context, member: discord.Member, role: discord.Role): + async def _addrole( + self, ctx: commands.Context, member: discord.Member, role: discord.Role, *, check_user=True + ): if member is None: member = ctx.author - if not self.pass_user_hierarchy_check(ctx, role): + if check_user and not self.pass_user_hierarchy_check(ctx, role): await ctx.send(_(USER_HIERARCHY_ISSUE_ADD).format(role=role, member=member)) return if not self.pass_hierarchy_check(ctx, role): @@ -141,10 +143,12 @@ class Admin(commands.Cog): ) ) - async def _removerole(self, ctx: commands.Context, member: discord.Member, role: discord.Role): + async def _removerole( + self, ctx: commands.Context, member: discord.Member, role: discord.Role, *, check_user=True + ): if member is None: member = ctx.author - if not self.pass_user_hierarchy_check(ctx, role): + if check_user and not self.pass_user_hierarchy_check(ctx, role): await ctx.send(_(USER_HIERARCHY_ISSUE_REMOVE).format(role=role, member=member)) return if not self.pass_hierarchy_check(ctx, role): @@ -365,7 +369,7 @@ class Admin(commands.Cog): NOTE: The role is case sensitive! """ # noinspection PyTypeChecker - await self._addrole(ctx, ctx.author, selfrole) + await self._addrole(ctx, ctx.author, selfrole, check_user=False) @selfrole.command(name="remove") async def selfrole_remove(self, ctx: commands.Context, *, selfrole: SelfRole): @@ -376,7 +380,7 @@ class Admin(commands.Cog): NOTE: The role is case sensitive! """ # noinspection PyTypeChecker - await self._removerole(ctx, ctx.author, selfrole) + await self._removerole(ctx, ctx.author, selfrole, check_user=False) @selfrole.command(name="list") async def selfrole_list(self, ctx: commands.Context): @@ -406,6 +410,13 @@ class Admin(commands.Cog): NOTE: The role is case sensitive! """ + if not self.pass_user_hierarchy_check(ctx, role): + await ctx.send( + _( + "I cannot let you add {role.name} as a selfrole because that role is higher than or equal to your highest role in the Discord hierarchy." + ).format(role=role) + ) + return async with self.conf.guild(ctx.guild).selfroles() as curr_selfroles: if role.id not in curr_selfroles: curr_selfroles.append(role.id) @@ -421,6 +432,13 @@ class Admin(commands.Cog): NOTE: The role is case sensitive! """ + if not self.pass_user_hierarchy_check(ctx, role): + await ctx.send( + _( + "I cannot let you remove {role.name} from being a selfrole because that role is higher than or equal to your highest role in the Discord hierarchy." + ).format(role=role) + ) + return async with self.conf.guild(ctx.guild).selfroles() as curr_selfroles: curr_selfroles.remove(role.id) From 9f027cc3e080e7825a0681f2610fc8588396098a Mon Sep 17 00:00:00 2001 From: Draper <27962761+Drapersniper@users.noreply.github.com> Date: Sun, 12 Jan 2020 23:19:36 +0000 Subject: [PATCH 10/49] [3.2.3][Audio] Improved Playlist cooldowns (#3342) * Improved Playlist cooldowns Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Improved Playlist cooldowns Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * formatting Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> --- changelog.d/audio/3342.enhance.1.rst | 2 ++ redbot/cogs/audio/audio.py | 51 ++++++++++++++++++---------- 2 files changed, 36 insertions(+), 17 deletions(-) create mode 100644 changelog.d/audio/3342.enhance.1.rst diff --git a/changelog.d/audio/3342.enhance.1.rst b/changelog.d/audio/3342.enhance.1.rst new file mode 100644 index 000000000..38cab120a --- /dev/null +++ b/changelog.d/audio/3342.enhance.1.rst @@ -0,0 +1,2 @@ +Reduce some cooldowns on playlist commands and stop them triggering before command parsing. + diff --git a/redbot/cogs/audio/audio.py b/redbot/cogs/audio/audio.py index afd34253e..e47317403 100644 --- a/redbot/cogs/audio/audio.py +++ b/redbot/cogs/audio/audio.py @@ -4150,8 +4150,8 @@ class Audio(commands.Cog): else None, ) - @commands.cooldown(1, 300, commands.BucketType.member) - @playlist.command(name="copy", usage=" [args]") + @commands.cooldown(1, 150, commands.BucketType.member) + @playlist.command(name="copy", usage=" [args]", cooldown_after_parsing=True) async def _playlist_copy( self, ctx: commands.Context, @@ -4444,7 +4444,9 @@ class Audio(commands.Cog): ) @commands.cooldown(1, 30, commands.BucketType.member) - @playlist.command(name="dedupe", usage=" [args]") + @playlist.command( + name="dedupe", usage=" [args]", cooldown_after_parsing=True + ) async def _playlist_remdupe( self, ctx: commands.Context, @@ -4577,9 +4579,13 @@ class Audio(commands.Cog): ) @checks.is_owner() - @playlist.command(name="download", usage=" [v2=False] [args]") + @playlist.command( + name="download", + usage=" [v2=False] [args]", + cooldown_after_parsing=True, + ) @commands.bot_has_permissions(attach_files=True) - @commands.cooldown(1, 60, commands.BucketType.guild) + @commands.cooldown(1, 30, commands.BucketType.guild) async def _playlist_download( self, ctx: commands.Context, @@ -4721,8 +4727,10 @@ class Audio(commands.Cog): await ctx.send(file=discord.File(to_write, filename=f"{file_name}.txt")) to_write.close() - @commands.cooldown(1, 20, commands.BucketType.member) - @playlist.command(name="info", usage=" [args]") + @commands.cooldown(1, 10, commands.BucketType.member) + @playlist.command( + name="info", usage=" [args]", cooldown_after_parsing=True + ) async def _playlist_info( self, ctx: commands.Context, @@ -4858,8 +4866,8 @@ class Audio(commands.Cog): page_list.append(embed) await menu(ctx, page_list, DEFAULT_CONTROLS) - @commands.cooldown(1, 30, commands.BucketType.guild) - @playlist.command(name="list", usage="[args]") + @commands.cooldown(1, 15, commands.BucketType.guild) + @playlist.command(name="list", usage="[args]", cooldown_after_parsing=True) @commands.bot_has_permissions(add_reactions=True) async def _playlist_list(self, ctx: commands.Context, *, scope_data: ScopeParser = None): """List saved playlists. @@ -4982,8 +4990,8 @@ class Audio(commands.Cog): ) return embed - @playlist.command(name="queue", usage=" [args]") - @commands.cooldown(1, 600, commands.BucketType.member) + @playlist.command(name="queue", usage=" [args]", cooldown_after_parsing=True) + @commands.cooldown(1, 300, commands.BucketType.member) async def _playlist_queue( self, ctx: commands.Context, playlist_name: str, *, scope_data: ScopeParser = None ): @@ -5194,8 +5202,8 @@ class Audio(commands.Cog): ).format(playlist_name=playlist.name, id=playlist.id, scope=scope_name), ) - @playlist.command(name="save", usage=" [args]") - @commands.cooldown(1, 120, commands.BucketType.member) + @playlist.command(name="save", usage=" [args]", cooldown_after_parsing=True) + @commands.cooldown(1, 60, commands.BucketType.member) async def _playlist_save( self, ctx: commands.Context, @@ -5288,8 +5296,13 @@ class Audio(commands.Cog): else None, ) - @commands.cooldown(1, 60, commands.BucketType.member) - @playlist.command(name="start", aliases=["play"], usage=" [args]") + @commands.cooldown(1, 30, commands.BucketType.member) + @playlist.command( + name="start", + aliases=["play"], + usage=" [args]", + cooldown_after_parsing=True, + ) async def _playlist_start( self, ctx: commands.Context, @@ -5457,7 +5470,9 @@ class Audio(commands.Cog): return await ctx.invoke(self.play, query=playlist.url) @commands.cooldown(1, 60, commands.BucketType.member) - @playlist.command(name="update", usage=" [args]") + @playlist.command( + name="update", usage=" [args]", cooldown_after_parsing=True + ) async def _playlist_update( self, ctx: commands.Context, @@ -5734,7 +5749,9 @@ class Audio(commands.Cog): ) @commands.cooldown(1, 60, commands.BucketType.member) - @playlist.command(name="rename", usage=" [args]") + @playlist.command( + name="rename", usage=" [args]", cooldown_after_parsing=True + ) async def _playlist_rename( self, ctx: commands.Context, From a98497177474288f2a02f78795c3383ad258077e Mon Sep 17 00:00:00 2001 From: Draper <27962761+Drapersniper@users.noreply.github.com> Date: Sun, 12 Jan 2020 23:20:31 +0000 Subject: [PATCH 11/49] [3.2.3][Audio] Fixes some Playlists strings (#3347) * chore Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * chore Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> --- changelog.d/audio/3344.enhance.1.rst | 1 + redbot/cogs/audio/audio.py | 4 ++-- redbot/cogs/audio/utils.py | 6 +++--- 3 files changed, 6 insertions(+), 5 deletions(-) create mode 100644 changelog.d/audio/3344.enhance.1.rst diff --git a/changelog.d/audio/3344.enhance.1.rst b/changelog.d/audio/3344.enhance.1.rst new file mode 100644 index 000000000..124a7b894 --- /dev/null +++ b/changelog.d/audio/3344.enhance.1.rst @@ -0,0 +1 @@ +Fixes the messages for playlists. \ No newline at end of file diff --git a/redbot/cogs/audio/audio.py b/redbot/cogs/audio/audio.py index e47317403..67607f090 100644 --- a/redbot/cogs/audio/audio.py +++ b/redbot/cogs/audio/audio.py @@ -4290,8 +4290,8 @@ class Audio(commands.Cog): ).format( name=from_playlist.name, from_id=from_playlist.id, - from_scope=humanize_scope(from_scope, ctx=from_scope_name, the=True), - to_scope=humanize_scope(to_scope, ctx=to_scope_name, the=True), + from_scope=humanize_scope(from_scope, ctx=from_scope_name), + to_scope=humanize_scope(to_scope, ctx=to_scope_name), to_id=to_playlist.id, ), ) diff --git a/redbot/cogs/audio/utils.py b/redbot/cogs/audio/utils.py index e44abab2e..2886f4996 100644 --- a/redbot/cogs/audio/utils.py +++ b/redbot/cogs/audio/utils.py @@ -521,8 +521,8 @@ class PlaylistScope(Enum): def humanize_scope(scope, ctx=None, the=None): if scope == PlaylistScope.GLOBAL.value: - return _("the ") if the else "" + _("Global") + return (_("the ") if the else "") + _("Global") elif scope == PlaylistScope.GUILD.value: - return ctx.name if ctx else _("the ") if the else "" + _("Server") + return ctx.name if ctx else (_("the ") if the else "") + _("Server") elif scope == PlaylistScope.USER.value: - return str(ctx) if ctx else _("the ") if the else "" + _("User") + return str(ctx) if ctx else (_("the ") if the else "") + _("User") From cb49c5d4200d691c6a66256fb16066eb5fe95547 Mon Sep 17 00:00:00 2001 From: jack1142 <6032823+jack1142@users.noreply.github.com> Date: Mon, 13 Jan 2020 00:21:00 +0100 Subject: [PATCH 12/49] [Downloader] Improve partial-uninstall message in `[p]cog uninstall` (#3343) * Update downloader.py * Let's use more of Flame's suggestions. Co-authored-by: Flame442 <34169552+Flame442@users.noreply.github.com> Co-authored-by: Flame442 <34169552+Flame442@users.noreply.github.com> --- redbot/cogs/downloader/downloader.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/redbot/cogs/downloader/downloader.py b/redbot/cogs/downloader/downloader.py index d4da0d09e..f770f292f 100644 --- a/redbot/cogs/downloader/downloader.py +++ b/redbot/cogs/downloader/downloader.py @@ -748,11 +748,15 @@ class Downloader(commands.Cog): message += _("Successfully uninstalled cogs: ") + humanize_list(uninstalled_cogs) if failed_cogs: message += ( - _("\nThese cog were installed but can no longer be located: ") + _( + "\nDownloader has removed these cogs from the installed cogs list" + " but it wasn't able to find their files: " + ) + humanize_list(tuple(map(inline, failed_cogs))) + _( - "\nYou may need to remove their files manually if they are still usable." - " Also make sure you've unloaded those cogs with `{prefix}unload {cogs}`." + "\nThey were most likely removed without using `{prefix}cog uninstall`.\n" + "You may need to remove those files manually if the cogs are still usable." + " If so, ensure the cogs have been unloaded with `{prefix}unload {cogs}`." ).format(prefix=ctx.prefix, cogs=" ".join(failed_cogs)) ) await ctx.send(message) From 5eb4bda600badd5f6189588f620fefe34d2ec261 Mon Sep 17 00:00:00 2001 From: jack1142 <6032823+jack1142@users.noreply.github.com> Date: Mon, 13 Jan 2020 00:25:01 +0100 Subject: [PATCH 13/49] Update install_linux_mac.rst (#3336) --- docs/install_linux_mac.rst | 44 ++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/docs/install_linux_mac.rst b/docs/install_linux_mac.rst index b767c1592..04abf2bb0 100644 --- a/docs/install_linux_mac.rst +++ b/docs/install_linux_mac.rst @@ -188,28 +188,44 @@ with zypper: .. _install-ubuntu: -~~~~~~ -Ubuntu -~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Ubuntu LTS versions (18.04 and 16.04) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. note:: **Ubuntu Python Availability** - - We recommend using the deadsnakes ppa to ensure up to date python availability. - - .. code-block:: none - - sudo apt update - sudo apt install software-properties-common - sudo add-apt-repository ppa:deadsnakes/ppa - -Install the pre-requirements with apt: +We recommend adding the ``deadsnakes`` ppa to install Python 3.8.1 or greater: .. code-block:: none sudo apt update + sudo apt install software-properties-common + sudo add-apt-repository ppa:deadsnakes/ppa + +Now install the pre-requirements with apt: + +.. code-block:: none + sudo apt -y install python3.8 python3.8-dev python3.8-venv python3-pip git default-jre-headless \ build-essential +.. _install-ubuntu-non-lts: + +~~~~~~~~~~~~~~~~~~~~~~~ +Ubuntu non-LTS versions +~~~~~~~~~~~~~~~~~~~~~~~ + +We recommend installing pyenv as a method of installing non-native versions of python on +non-LTS versions of Ubuntu. This guide will tell you how. First, run the following commands: + +.. code-block:: none + + sudo apt update + sudo apt -y install make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev \ + libsqlite3-dev wget curl llvm libncurses5-dev xz-utils tk-dev libxml2-dev \ + libxmlsec1-dev libffi-dev liblzma-dev libgdbm-dev uuid-dev python3-openssl git openjdk-11-jre + CXX=/usr/bin/g++ + +Complete the rest of the installation by `installing Python 3.8 with pyenv `. + .. _install-python-pyenv: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 81b6d5bb93d8de611f80f117477fe0bc6d0d84ec Mon Sep 17 00:00:00 2001 From: Draper <27962761+Drapersniper@users.noreply.github.com> Date: Sun, 12 Jan 2020 23:35:23 +0000 Subject: [PATCH 14/49] why the hell is this here (#3357) Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> --- redbot/cogs/audio/databases.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/redbot/cogs/audio/databases.py b/redbot/cogs/audio/databases.py index ee136c222..6951a91b7 100644 --- a/redbot/cogs/audio/databases.py +++ b/redbot/cogs/audio/databases.py @@ -91,8 +91,6 @@ class CacheFetchResult: k in self.query for k in ["loadType", "playlistInfo", "isSeekable", "isStream"] ): self.query = json.loads(self.query) - else: - self.query = None @dataclass From 7bdd177713a6720cfa21e82a03d4cf8da4477343 Mon Sep 17 00:00:00 2001 From: Draper <27962761+Drapersniper@users.noreply.github.com> Date: Sun, 12 Jan 2020 23:35:49 +0000 Subject: [PATCH 15/49] [3.2.3][Audio] Correct an unsupported LoadType (#3337) * Limit Playlists Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * logging improvements Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * logging improvements Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * sigh Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * chore Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> --- changelog.d/audio/3337.misc.1.rst | 1 + changelog.d/audio/3337.misc.2.rst | 1 + redbot/cogs/audio/apis.py | 4 ++++ redbot/cogs/audio/audio.py | 4 ++-- redbot/cogs/audio/audio_dataclasses.py | 4 ++++ 5 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 changelog.d/audio/3337.misc.1.rst create mode 100644 changelog.d/audio/3337.misc.2.rst diff --git a/changelog.d/audio/3337.misc.1.rst b/changelog.d/audio/3337.misc.1.rst new file mode 100644 index 000000000..f39071842 --- /dev/null +++ b/changelog.d/audio/3337.misc.1.rst @@ -0,0 +1 @@ +Removed a duplication of track search prefixes. \ No newline at end of file diff --git a/changelog.d/audio/3337.misc.2.rst b/changelog.d/audio/3337.misc.2.rst new file mode 100644 index 000000000..25985f91d --- /dev/null +++ b/changelog.d/audio/3337.misc.2.rst @@ -0,0 +1 @@ +Changed and handled the `V2_COMPACT` LoadType to use the correct `V2_COMPAT` type. \ No newline at end of file diff --git a/redbot/cogs/audio/apis.py b/redbot/cogs/audio/apis.py index 5bc359efc..d949be375 100644 --- a/redbot/cogs/audio/apis.py +++ b/redbot/cogs/audio/apis.py @@ -755,6 +755,8 @@ class MusicCache: if val and not forced and isinstance(val, dict): data = val data["query"] = query + if data.get("loadType") == "V2_COMPACT": + data["loadType"] = "V2_COMPAT" results = LoadResult(data) called_api = False if results.has_error: @@ -863,6 +865,8 @@ class MusicCache: if recently_played: track = random.choice(recently_played) + if track.get("loadType") == "V2_COMPACT": + track["loadType"] = "V2_COMPAT" results = LoadResult(track) tracks = list(results.tracks) except Exception: diff --git a/redbot/cogs/audio/audio.py b/redbot/cogs/audio/audio.py index 67607f090..bbbf1852c 100644 --- a/redbot/cogs/audio/audio.py +++ b/redbot/cogs/audio/audio.py @@ -245,7 +245,7 @@ class Audio(commands.Cog): for t in tracks_in_playlist: uri = t.get("info", {}).get("uri") if uri: - t = {"loadType": "V2_COMPACT", "tracks": [t], "query": uri} + t = {"loadType": "V2_COMPAT", "tracks": [t], "query": uri} data = json.dumps(t) if all( k in data @@ -5905,7 +5905,7 @@ class Audio(commands.Cog): for t in track_list: uri = t.get("info", {}).get("uri") if uri: - t = {"loadType": "V2_COMPACT", "tracks": [t], "query": uri} + t = {"loadType": "V2_COMPAT", "tracks": [t], "query": uri} data = json.dumps(t) if all(k in data for k in ["loadType", "playlistInfo", "isSeekable", "isStream"]): database_entries.append( diff --git a/redbot/cogs/audio/audio_dataclasses.py b/redbot/cogs/audio/audio_dataclasses.py index 7b1ca769e..d31cbbc9b 100644 --- a/redbot/cogs/audio/audio_dataclasses.py +++ b/redbot/cogs/audio/audio_dataclasses.py @@ -378,6 +378,10 @@ class Query: if isinstance(query, str): query = query.strip("<>") + while "ytsearch:" in query: + query = query.replace("ytsearch:", "") + while "scsearch:" in query: + query = query.replace("scsearch:", "") elif isinstance(query, Query): for key, val in kwargs.items(): From 088360ec51f05f02fdb19d0ffcef42581c276c56 Mon Sep 17 00:00:00 2001 From: jack1142 <6032823+jack1142@users.noreply.github.com> Date: Mon, 13 Jan 2020 02:26:01 +0100 Subject: [PATCH 16/49] Make Red shutdown when resetting token (#3358) * Update __main__.py * Update __main__.py --- redbot/__main__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/redbot/__main__.py b/redbot/__main__.py index 0f0d2dacf..66b64cc57 100644 --- a/redbot/__main__.py +++ b/redbot/__main__.py @@ -16,6 +16,7 @@ import sys from argparse import Namespace from copy import deepcopy from pathlib import Path +from typing import NoReturn import discord @@ -287,7 +288,7 @@ def handle_edit(cli_flags: Namespace): sys.exit(0) -async def run_bot(red: Red, cli_flags: Namespace): +async def run_bot(red: Red, cli_flags: Namespace) -> NoReturn: driver_cls = drivers.get_driver_class() @@ -334,6 +335,7 @@ async def run_bot(red: Red, cli_flags: Namespace): sys.exit(0) try: await red.start(token, bot=True, cli_flags=cli_flags) + # This raises SystemExit in normal use at close except discord.LoginFailure: log.critical("This token doesn't seem to be valid.") db_token = await red._config.token() @@ -341,6 +343,8 @@ async def run_bot(red: Red, cli_flags: Namespace): if confirm("\nDo you want to reset the token?"): await red._config.token.set("") print("Token has been reset.") + sys.exit(0) + sys.exit(1) def handle_early_exit_flags(cli_flags: Namespace): From ab2e87a8fb44a901a0693c94c2aec6a4fdf1dc5d Mon Sep 17 00:00:00 2001 From: Michael H Date: Mon, 13 Jan 2020 09:46:05 -0500 Subject: [PATCH 17/49] Start making use of typehints for devs (#3335) * Start making use of typehints for devs * changelog --- changelog.d/3335.enhance.rst | 1 + redbot/py.typed | 0 setup.cfg | 1 + 3 files changed, 2 insertions(+) create mode 100644 changelog.d/3335.enhance.rst create mode 100644 redbot/py.typed diff --git a/changelog.d/3335.enhance.rst b/changelog.d/3335.enhance.rst new file mode 100644 index 000000000..316decb0f --- /dev/null +++ b/changelog.d/3335.enhance.rst @@ -0,0 +1 @@ +make typehints accessible to cog developers diff --git a/redbot/py.typed b/redbot/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/setup.cfg b/setup.cfg index 5040e8a1c..4fb7d934e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -127,5 +127,6 @@ include = data/* data/**/* *.export + py.typed redbot.core.drivers.postgres = *.sql From ef8b57a1d245b3f6ba6ef6ae454347d100625da5 Mon Sep 17 00:00:00 2001 From: Michael H Date: Mon, 13 Jan 2020 10:12:31 -0500 Subject: [PATCH 18/49] Add a command to set the bot description (#3340) * description-command * Cap the description length * mmk --- redbot/core/bot.py | 2 ++ redbot/core/commands/help.py | 6 +++--- redbot/core/core_commands.py | 26 ++++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/redbot/core/bot.py b/redbot/core/bot.py index 4264bd940..4b994a43d 100644 --- a/redbot/core/bot.py +++ b/redbot/core/bot.py @@ -76,6 +76,7 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin): # pylint: d help__verify_checks=True, help__verify_exists=False, help__tagline="", + description="Red V3", invite_public=False, invite_perm=0, disabled_commands=[], @@ -400,6 +401,7 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin): # pylint: d This should only be run once, prior to connecting to discord. """ await self._maybe_update_config() + self.description = await self._config.description() init_global_checks(self) init_events(self, cli_flags) diff --git a/redbot/core/commands/help.py b/redbot/core/commands/help.py index 5d1a5eb9a..13ce22d6e 100644 --- a/redbot/core/commands/help.py +++ b/redbot/core/commands/help.py @@ -198,7 +198,7 @@ class RedHelpFormatter: emb = {"embed": {"title": "", "description": ""}, "footer": {"text": ""}, "fields": []} if description: - emb["embed"]["title"] = f"*{description[:2044]}*" + emb["embed"]["title"] = f"*{description[:250]}*" emb["footer"]["text"] = tagline emb["embed"]["description"] = signature @@ -209,7 +209,7 @@ class RedHelpFormatter: value = "\n\n".join(splitted[1:]).replace("[p]", ctx.clean_prefix) if not value: value = EMPTY_STRING - field = EmbedField(name[:252], value[:1024], False) + field = EmbedField(name[:250], value[:1024], False) emb["fields"].append(field) if subcommands: @@ -442,7 +442,7 @@ class RedHelpFormatter: emb["footer"]["text"] = tagline if description: - emb["embed"]["title"] = f"*{description[:2044]}*" + emb["embed"]["title"] = f"*{description[:250]}*" for cog_name, data in coms: diff --git a/redbot/core/core_commands.py b/redbot/core/core_commands.py index f89b3473a..cca4af689 100644 --- a/redbot/core/core_commands.py +++ b/redbot/core/core_commands.py @@ -873,6 +873,32 @@ class Core(commands.Cog, CoreLogic): for page in pagify(settings): await ctx.send(box(page)) + @checks.is_owner() + @_set.command(name="description") + async def setdescription(self, ctx: commands.Context, *, description: str = ""): + """ + Sets the bot's description. + Use without a description to reset. + This is shown in a few locations, including the help menu. + + The default is "Red V3" + """ + if not description: + await ctx.bot._config.description.clear() + ctx.bot.description = "Red V3" + await ctx.send(_("Description reset.")) + elif len(description) > 250: # While the limit is 256, we bold it adding characters. + await ctx.send( + _( + "This description is too long to properly display. " + "Please try again with below 250 characters" + ) + ) + else: + await ctx.bot._config.description.set(description) + ctx.bot.description = description + await ctx.tick() + @_set.command() @checks.guildowner() @commands.guild_only() From a7987a83fde3d7f6d519f3802d1af07aed66a00b Mon Sep 17 00:00:00 2001 From: Michael H Date: Mon, 13 Jan 2020 11:37:49 -0500 Subject: [PATCH 19/49] Exit code handling (#3360) * Exit code handling * clear up a docstring --- redbot/__main__.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/redbot/__main__.py b/redbot/__main__.py index 66b64cc57..77c01ba52 100644 --- a/redbot/__main__.py +++ b/redbot/__main__.py @@ -288,7 +288,18 @@ def handle_edit(cli_flags: Namespace): sys.exit(0) -async def run_bot(red: Red, cli_flags: Namespace) -> NoReturn: +async def run_bot(red: Red, cli_flags: Namespace) -> None: + """ + This runs the bot. + + Any shutdown which is a result of not being able to log in needs to raise + a SystemExit exception. + + If the bot starts normally, the bot should be left to handle the exit case. + It will raise SystemExit in a task, which will reach the event loop and + interrupt running forever, then trigger our cleanup process, and does not + need additional handling in this function. + """ driver_cls = drivers.get_driver_class() @@ -335,7 +346,6 @@ async def run_bot(red: Red, cli_flags: Namespace) -> NoReturn: sys.exit(0) try: await red.start(token, bot=True, cli_flags=cli_flags) - # This raises SystemExit in normal use at close except discord.LoginFailure: log.critical("This token doesn't seem to be valid.") db_token = await red._config.token() @@ -344,7 +354,9 @@ async def run_bot(red: Red, cli_flags: Namespace) -> NoReturn: await red._config.token.set("") print("Token has been reset.") sys.exit(0) - sys.exit(1) + sys.exit(1) + + return None def handle_early_exit_flags(cli_flags: Namespace): From 3c53b890405ef7044be80db2975a8a0de93f3466 Mon Sep 17 00:00:00 2001 From: Michael H Date: Mon, 13 Jan 2020 11:50:45 -0500 Subject: [PATCH 20/49] [Help] formatting additions (#3339) * formatting additions * I really need to redo this module later * fix some casing --- redbot/core/commands/commands.py | 44 +++++++++++++++++++++ redbot/core/commands/help.py | 66 +++++++++++++++++++------------- 2 files changed, 84 insertions(+), 26 deletions(-) diff --git a/redbot/core/commands/commands.py b/redbot/core/commands/commands.py index 97649747c..fcd5c390e 100644 --- a/redbot/core/commands/commands.py +++ b/redbot/core/commands/commands.py @@ -4,6 +4,7 @@ This module contains extended classes and functions which are intended to replace those from the `discord.ext.commands` module. """ import inspect +import re import weakref from typing import Awaitable, Callable, Dict, List, Optional, Tuple, Union, TYPE_CHECKING @@ -57,6 +58,49 @@ class CogCommandMixin: checks=getattr(decorated, "__requires_checks__", []), ) + def format_help_for_context(self, ctx: "Context") -> str: + """ + This formats the help string based on values in context + + The steps are (currently, roughly) the following: + + - get the localized help + - substitute ``[p]`` with ``ctx.clean_prefix`` + - substitute ``[botname]`` with ``ctx.me.display_name`` + + More steps may be added at a later time. + + Cog creators may override this in their own command classes + as long as the method signature stays the same. + + Parameters + ---------- + ctx: Context + + Returns + ------- + str + Localized help with some formatting + """ + + help_str = self.help + if not help_str: + # Short circuit out on an empty help string + return help_str + + formatting_pattern = re.compile(r"\[p\]|\[botname\]") + + def replacement(m: re.Match) -> str: + s = m.group(0) + if s == "[p]": + return ctx.clean_prefix + if s == "[botname]": + return ctx.me.display_name + # We shouldnt get here: + return s + + return formatting_pattern.sub(replacement, help_str) + def allow_for(self, model_id: Union[int, str], guild_id: int) -> None: """Actively allow this command for the given model. diff --git a/redbot/core/commands/help.py b/redbot/core/commands/help.py index 13ce22d6e..783ef1dc5 100644 --- a/redbot/core/commands/help.py +++ b/redbot/core/commands/help.py @@ -162,10 +162,10 @@ class RedHelpFormatter: @staticmethod def get_default_tagline(ctx: Context): - return ( - f"Type {ctx.clean_prefix}help for more info on a command. " - f"You can also type {ctx.clean_prefix}help for more info on a category." - ) + return T_( + "Type {ctx.clean_prefix}help for more info on a command. " + "You can also type {ctx.clean_prefix}help for more info on a category." + ).format(ctx=ctx) async def format_command_help(self, ctx: Context, obj: commands.Command): @@ -187,7 +187,9 @@ class RedHelpFormatter: description = command.description or "" tagline = (await ctx.bot._config.help.tagline()) or self.get_default_tagline(ctx) - signature = f"`Syntax: {ctx.clean_prefix}{command.qualified_name} {command.signature}`" + signature = ( + f"`{T_('Syntax')}: {ctx.clean_prefix}{command.qualified_name} {command.signature}`" + ) subcommands = None if hasattr(command, "all_commands"): @@ -203,10 +205,11 @@ class RedHelpFormatter: emb["footer"]["text"] = tagline emb["embed"]["description"] = signature - if command.help: - splitted = command.help.split("\n\n") + command_help = command.format_help_for_context(ctx) + if command_help: + splitted = command_help.split("\n\n") name = splitted[0] - value = "\n\n".join(splitted[1:]).replace("[p]", ctx.clean_prefix) + value = "\n\n".join(splitted[1:]) if not value: value = EMPTY_STRING field = EmbedField(name[:250], value[:1024], False) @@ -225,9 +228,9 @@ class RedHelpFormatter: ) for i, page in enumerate(pagify(subtext, page_length=500, shorten_by=0)): if i == 0: - title = "**__Subcommands:__**" + title = T_("**__Subcommands:__**") else: - title = "**__Subcommands:__** (continued)" + title = T_("**__Subcommands:__** (continued)") field = EmbedField(title, page, False) emb["fields"].append(field) @@ -238,7 +241,7 @@ class RedHelpFormatter: subtext = None subtext_header = None if subcommands: - subtext_header = "Subcommands:" + subtext_header = T_("Subcommands:") max_width = max(discord.utils._string_width(name) for name in subcommands.keys()) def width_maker(cmds): @@ -261,7 +264,7 @@ class RedHelpFormatter: ( description, signature[1:-1], - command.help.replace("[p]", ctx.clean_prefix), + command.format_help_for_context(ctx), subtext_header, subtext, ), @@ -301,7 +304,10 @@ class RedHelpFormatter: page_char_limit = await ctx.bot._config.help.page_char_limit() page_char_limit = min(page_char_limit, 5500) # Just in case someone was manually... - author_info = {"name": f"{ctx.me.display_name} Help Menu", "icon_url": ctx.me.avatar_url} + author_info = { + "name": f"{ctx.me.display_name} {T_('Help Menu')}", + "icon_url": ctx.me.avatar_url, + } # Offset calculation here is for total embed size limit # 20 accounts for# *Page {i} of {page_count}* @@ -346,7 +352,9 @@ class RedHelpFormatter: embed = discord.Embed(color=color, **embed_dict["embed"]) if page_count > 1: - description = f"*Page {i} of {page_count}*\n{embed.description}" + description = T_( + "*Page {page_num} of {page_count}*\n{content_description}" + ).format(content_description=embed.description, page_num=i, page_count=page_count) embed.description = description embed.set_author(**author_info) @@ -366,7 +374,7 @@ class RedHelpFormatter: if not (coms or await ctx.bot._config.help.verify_exists()): return - description = obj.help + description = obj.format_help_for_context(ctx) tagline = (await ctx.bot._config.help.tagline()) or self.get_default_tagline(ctx) if await ctx.embed_requested(): @@ -376,7 +384,7 @@ class RedHelpFormatter: if description: splitted = description.split("\n\n") name = splitted[0] - value = "\n\n".join(splitted[1:]).replace("[p]", ctx.clean_prefix) + value = "\n\n".join(splitted[1:]) if not value: value = EMPTY_STRING field = EmbedField(name[:252], value[:1024], False) @@ -395,9 +403,9 @@ class RedHelpFormatter: ) for i, page in enumerate(pagify(command_text, page_length=500, shorten_by=0)): if i == 0: - title = "**__Commands:__**" + title = T_("**__Commands:__**") else: - title = "**__Commands:__** (continued)" + title = T_("**__Commands:__** (continued)") field = EmbedField(title, page, False) emb["fields"].append(field) @@ -407,7 +415,7 @@ class RedHelpFormatter: subtext = None subtext_header = None if coms: - subtext_header = "Commands:" + subtext_header = T_("Commands:") max_width = max(discord.utils._string_width(name) for name in coms.keys()) def width_maker(cmds): @@ -449,7 +457,7 @@ class RedHelpFormatter: if cog_name: title = f"**__{cog_name}:__**" else: - title = f"**__No Category:__**" + title = f"**__{T_('No Category')}:__**" def shorten_line(a_line: str) -> str: if len(a_line) < 70: # embed max width needs to be lower @@ -462,7 +470,7 @@ class RedHelpFormatter: ) for i, page in enumerate(pagify(cog_text, page_length=1000, shorten_by=0)): - title = title if i < 1 else f"{title} (continued)" + title = title if i < 1 else f"{title} {T_('(continued)')}" field = EmbedField(title, page, False) emb["fields"].append(field) @@ -478,7 +486,7 @@ class RedHelpFormatter: names.extend(list(v.name for v in v.values())) max_width = max( - discord.utils._string_width((name or "No Category:")) for name in names + discord.utils._string_width((name or T_("No Category:"))) for name in names ) def width_maker(cmds): @@ -492,7 +500,7 @@ class RedHelpFormatter: for cog_name, data in coms: - title = f"{cog_name}:" if cog_name else "No Category:" + title = f"{cog_name}:" if cog_name else T_("No Category:") to_join.append(title) for name, doc, width in width_maker(sorted(data.items())): @@ -543,7 +551,9 @@ class RedHelpFormatter: if fuzzy_commands: ret = await format_fuzzy_results(ctx, fuzzy_commands, embed=use_embeds) if use_embeds: - ret.set_author(name=f"{ctx.me.display_name} Help Menu", icon_url=ctx.me.avatar_url) + ret.set_author( + name=f"{ctx.me.display_name} {T_('Help Menu')}", icon_url=ctx.me.avatar_url + ) tagline = (await ctx.bot._config.help.tagline()) or self.get_default_tagline(ctx) ret.set_footer(text=tagline) await ctx.send(embed=ret) @@ -553,7 +563,9 @@ class RedHelpFormatter: ret = T_("Help topic for *{command_name}* not found.").format(command_name=help_for) if use_embeds: ret = discord.Embed(color=(await ctx.embed_color()), description=ret) - ret.set_author(name=f"{ctx.me.display_name} Help Menu", icon_url=ctx.me.avatar_url) + ret.set_author( + name=f"{ctx.me.display_name} {T_('Help Menu')}", icon_url=ctx.me.avatar_url + ) tagline = (await ctx.bot._config.help.tagline()) or self.get_default_tagline(ctx) ret.set_footer(text=tagline) await ctx.send(embed=ret) @@ -569,7 +581,9 @@ class RedHelpFormatter: ) if await ctx.embed_requested(): ret = discord.Embed(color=(await ctx.embed_color()), description=ret) - ret.set_author(name=f"{ctx.me.display_name} Help Menu", icon_url=ctx.me.avatar_url) + ret.set_author( + name=f"{ctx.me.display_name} {T_('Help Menu')}", icon_url=ctx.me.avatar_url + ) tagline = (await ctx.bot._config.help.tagline()) or self.get_default_tagline(ctx) ret.set_footer(text=tagline) await ctx.send(embed=ret) From 90c0f76ae4fced4f0feae02953171ef6ba8fc0ef Mon Sep 17 00:00:00 2001 From: Dav <57032623+Dav-Git@users.noreply.github.com> Date: Mon, 13 Jan 2020 23:57:39 +0100 Subject: [PATCH 21/49] [Warnings] Make [p]warnings usable on base of permissions (#3327) * new code Added the admin check to warnings and removed the part where the user could check themselves. Added new mywarns which replaces part of the old behaviour of warn * Update warnings.py * Create 2900.enhance.rst * Fixed command name Because appearently I can't remember a command for 10 seconds * Commands in backticks Put command names in changelog in double backticks after being advised to do so in discord * made user not optional, and the other thing sinbad requested * switched parts. magic resolves #2900 --- changelog.d/warnings/2900.enhance.rst | 2 + redbot/cogs/warnings/warnings.py | 62 ++++++++++++++++----------- 2 files changed, 40 insertions(+), 24 deletions(-) create mode 100644 changelog.d/warnings/2900.enhance.rst diff --git a/changelog.d/warnings/2900.enhance.rst b/changelog.d/warnings/2900.enhance.rst new file mode 100644 index 000000000..1c6f7599c --- /dev/null +++ b/changelog.d/warnings/2900.enhance.rst @@ -0,0 +1,2 @@ +``[p]warnings`` can now be used by users that have the permission to use it from the Permissions cog, in order to check another user's warnings. +``[p]mywarnings`` can now be used by any user (instead of ``[p]warnings`` previously) to check their own warnings. diff --git a/redbot/cogs/warnings/warnings.py b/redbot/cogs/warnings/warnings.py index 515c1c13c..458f43ff9 100644 --- a/redbot/cogs/warnings/warnings.py +++ b/redbot/cogs/warnings/warnings.py @@ -12,7 +12,6 @@ from redbot.cogs.warnings.helpers import ( from redbot.core import Config, checks, commands, modlog from redbot.core.bot import Red from redbot.core.i18n import Translator, cog_i18n -from redbot.core.utils.mod import is_admin_or_superior from redbot.core.utils.chat_formatting import warning, pagify from redbot.core.utils.menus import menu, DEFAULT_CONTROLS @@ -342,30 +341,16 @@ class Warnings(commands.Cog): @commands.command() @commands.guild_only() - async def warnings( - self, ctx: commands.Context, user: Optional[Union[discord.Member, int]] = None - ): - """List the warnings for the specified user. + @checks.admin() + async def warnings(self, ctx: commands.Context, user: Union[discord.Member, int]): + """List the warnings for the specified user.""" - Omit `` to see your own warnings. - - Note that showing warnings for users other than yourself requires - appropriate permissions. - """ - if user is None: - user = ctx.author - else: - if not await is_admin_or_superior(self.bot, ctx.author): - return await ctx.send( - warning(_("You are not allowed to check warnings for other users!")) - ) - - try: - userid: int = user.id - except AttributeError: - userid: int = user - user = ctx.guild.get_member(userid) - user = user or namedtuple("Member", "id guild")(userid, ctx.guild) + try: + userid: int = user.id + except AttributeError: + userid: int = user + user = ctx.guild.get_member(userid) + user = user or namedtuple("Member", "id guild")(userid, ctx.guild) msg = "" member_settings = self.config.member(user) @@ -389,6 +374,35 @@ class Warnings(commands.Cog): pagify(msg, shorten_by=58), box_lang=_("Warnings for {user}").format(user=user) ) + @commands.command() + @commands.guild_only() + async def mywarnings(self, ctx: commands.Context): + """List warnings for yourself.""" + + user = ctx.author + + msg = "" + member_settings = self.config.member(user) + async with member_settings.warnings() as user_warnings: + if not user_warnings.keys(): # no warnings for the user + await ctx.send(_("You have no warnings!")) + else: + for key in user_warnings.keys(): + mod_id = user_warnings[key]["mod"] + mod = ctx.bot.get_user(mod_id) or _("Unknown Moderator ({})").format(mod_id) + msg += _( + "{num_points} point warning {reason_name} issued by {user} for " + "{description}\n" + ).format( + num_points=user_warnings[key]["points"], + reason_name=key, + user=mod, + description=user_warnings[key]["description"], + ) + await ctx.send_interactive( + pagify(msg, shorten_by=58), box_lang=_("Warnings for {user}").format(user=user) + ) + @commands.command() @commands.guild_only() @checks.admin_or_permissions(ban_members=True) From b646c2fd98f80bfdd5a2bdf5abd60fe966bb5f3d Mon Sep 17 00:00:00 2001 From: jack1142 <6032823+jack1142@users.noreply.github.com> Date: Tue, 14 Jan 2020 17:54:44 +0100 Subject: [PATCH 22/49] [Docs] Add links to operating systems + minor readability improvements (#3365) * add operating systems links + some minor readability improvements * meh, let's add this too, draper --- docs/install_linux_mac.rst | 48 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/docs/install_linux_mac.rst b/docs/install_linux_mac.rst index 04abf2bb0..45c792f8d 100644 --- a/docs/install_linux_mac.rst +++ b/docs/install_linux_mac.rst @@ -25,6 +25,16 @@ The pre-requirements are: We also recommend installing some basic compiler tools, in case our dependencies don't provide pre-built "wheels" for your architecture. + +***************** +Operating systems +***************** + +.. contents:: + :local: + +---- + .. _install-arch: ~~~~~~~~~~ @@ -35,6 +45,10 @@ Arch Linux sudo pacman -Syu python python-pip git jre-openjdk-headless base-devel +Continue by `creating-venv-linux`. + +---- + .. _install-centos: .. _install-rhel: @@ -51,6 +65,8 @@ CentOS and RHEL 7 Complete the rest of the installation by `installing Python 3.8 with pyenv `. +---- + .. _install-debian-stretch: ~~~~~~~~~~~~~~ @@ -76,6 +92,8 @@ Debian Stretch. This guide will tell you how. First, run the following commands: Complete the rest of the installation by `installing Python 3.8 with pyenv `. +---- + .. _install-debian: .. _install-raspbian: @@ -96,6 +114,8 @@ Debian/Raspbian Buster. This guide will tell you how. First, run the following c Complete the rest of the installation by `installing Python 3.8 with pyenv `. +---- + .. _install-fedora: ~~~~~~~~~~~~ @@ -109,6 +129,10 @@ them with dnf: sudo dnf -y install python38 git java-latest-openjdk-headless @development-tools +Continue by `creating-venv-linux`. + +---- + .. _install-mac: ~~~ @@ -135,6 +159,10 @@ one-by-one: It's possible you will have network issues. If so, go in your Applications folder, inside it, go in the Python 3.8 folder then double click ``Install certificates.command``. +Continue by `creating-venv-linux`. + +---- + .. _install-opensuse: ~~~~~~~~ @@ -175,6 +203,8 @@ Now, install pip with easy_install: sudo /opt/python/bin/easy_install-3.8 pip +Continue by `creating-venv-linux`. + openSUSE Tumbleweed ******************* @@ -186,6 +216,10 @@ with zypper: sudo zypper install python3-base python3-pip git-core java-12-openjdk-headless sudo zypper install -t pattern devel_basis +Continue by `creating-venv-linux`. + +---- + .. _install-ubuntu: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -207,6 +241,10 @@ Now install the pre-requirements with apt: sudo apt -y install python3.8 python3.8-dev python3.8-venv python3-pip git default-jre-headless \ build-essential +Continue by `creating-venv-linux`. + +---- + .. _install-ubuntu-non-lts: ~~~~~~~~~~~~~~~~~~~~~~~ @@ -226,11 +264,13 @@ non-LTS versions of Ubuntu. This guide will tell you how. First, run the followi Complete the rest of the installation by `installing Python 3.8 with pyenv `. +---- + .. _install-python-pyenv: -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +**************************** Installing Python with pyenv -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +**************************** .. note:: @@ -268,6 +308,10 @@ After that is finished, run: Pyenv is now installed and your system should be configured to run Python 3.8. +Continue by `creating-venv-linux`. + +.. _creating-venv-linux: + ------------------------------ Creating a Virtual Environment ------------------------------ From 2be4080bc6c110d8aec6730af82e6acf80cdb73a Mon Sep 17 00:00:00 2001 From: jack1142 <6032823+jack1142@users.noreply.github.com> Date: Tue, 14 Jan 2020 18:52:18 +0100 Subject: [PATCH 23/49] stop messing with distutils's internal just to copy directory (#3364) --- redbot/cogs/downloader/installable.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/redbot/cogs/downloader/installable.py b/redbot/cogs/downloader/installable.py index 20997bb25..bfe3d916d 100644 --- a/redbot/cogs/downloader/installable.py +++ b/redbot/cogs/downloader/installable.py @@ -1,7 +1,7 @@ from __future__ import annotations import json -import distutils.dir_util +import functools import shutil from enum import IntEnum from pathlib import Path @@ -127,15 +127,13 @@ class Installable(RepoJSONMixin): if self._location.is_file(): copy_func = shutil.copy2 else: - # clear copy_tree's cache to make sure missing directories are created (GH-2690) - distutils.dir_util._path_created = {} - copy_func = distutils.dir_util.copy_tree + copy_func = functools.partial(shutil.copytree, dirs_exist_ok=True) # noinspection PyBroadException try: copy_func(src=str(self._location), dst=str(target_dir / self._location.stem)) except: # noqa: E722 - log.exception("Error occurred when copying path: {}".format(self._location)) + log.exception("Error occurred when copying path: %s", self._location) return False return True From 79dcd22ff644d124319d25e47b8f4a6c1be4493e Mon Sep 17 00:00:00 2001 From: jack1142 <6032823+jack1142@users.noreply.github.com> Date: Tue, 14 Jan 2020 18:53:28 +0100 Subject: [PATCH 24/49] Update bank.py (#3366) --- redbot/core/bank.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/redbot/core/bank.py b/redbot/core/bank.py index 4379199d8..03dc8b8f6 100644 --- a/redbot/core/bank.py +++ b/redbot/core/bank.py @@ -838,9 +838,9 @@ async def set_default_balance(amount: int, guild: discord.Guild = None) -> int: amount = int(amount) max_bal = await get_max_balance(guild) - if not (0 < amount <= max_bal): + if not (0 <= amount <= max_bal): raise ValueError( - "Amount must be greater than zero and less than {max}.".format( + "Amount must be greater than or equal zero and less than or equal {max}.".format( max=humanize_number(max_bal, override_locale="en_US") ) ) From a7f0e2b7c657a2368e096f90348c697d12a10e72 Mon Sep 17 00:00:00 2001 From: Michael H Date: Tue, 14 Jan 2020 17:42:40 -0500 Subject: [PATCH 25/49] Globally ensure send_messages for commands (#3361) * wew * typo fix, thanks Danny --- redbot/core/global_checks.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/redbot/core/global_checks.py b/redbot/core/global_checks.py index 1adbe76a4..47c264a04 100644 --- a/redbot/core/global_checks.py +++ b/redbot/core/global_checks.py @@ -4,7 +4,17 @@ from . import commands def init_global_checks(bot): @bot.check_once - def actually_up(ctx): + def minimum_bot_perms(ctx) -> bool: + """ + Too many 403, 401, and 429 Errors can cause bots to get global'd + + It's reasonable to assume the below as a minimum amount of perms for + commands. + """ + return ctx.channel.permissions_for(ctx.me).send_messages + + @bot.check_once + def actually_up(ctx) -> bool: """ Uptime is set during the initial startup process. If this hasn't been set, we should assume the bot isn't ready yet. @@ -12,10 +22,10 @@ def init_global_checks(bot): return ctx.bot.uptime is not None @bot.check_once - async def whiteblacklist_checks(ctx): + async def whiteblacklist_checks(ctx) -> bool: return await ctx.bot.allowed_by_whitelist_blacklist(ctx.author) @bot.check_once - def bots(ctx): + def bots(ctx) -> bool: """Check the user is not another bot.""" return not ctx.author.bot From d8199201a5acc9dca7174cf443dab0bfa44180dc Mon Sep 17 00:00:00 2001 From: jack1142 <6032823+jack1142@users.noreply.github.com> Date: Wed, 15 Jan 2020 04:14:20 +0100 Subject: [PATCH 26/49] [Changelog] Clarify breaking change related to `setup()` function (#3367) * Update changelog_3_2_0.rst * Update changelog_3_2_0.rst * Update changelog_3_2_0.rst --- docs/changelog_3_2_0.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/changelog_3_2_0.rst b/docs/changelog_3_2_0.rst index 04d69d6f6..43874ad1c 100644 --- a/docs/changelog_3_2_0.rst +++ b/docs/changelog_3_2_0.rst @@ -49,7 +49,8 @@ Breaking Changes - ``bot.get_mod_role_ids`` (`#2967 `_) - Reserved some command names for internal Red use. These are available programatically as ``redbot.core.commands.RESERVED_COMMAND_NAMES``. (`#2973 `_) - Removed ``bot._counter``, Made a few more attrs private (``cog_mgr``, ``main_dir``). (`#2976 `_) -- ``bot.wait_until_ready`` should no longer be used during extension setup. (`#3073 `_) +- Extension's ``setup()`` function should no longer assume that we are, or even will be connected to Discord. + This also means that cog creators should no longer use ``bot.wait_until_ready()`` inside it. (`#3073 `_) - Removed the mongo driver. (`#3099 `_) From 27e6f677e83304cb14447eab2a2048e07788367b Mon Sep 17 00:00:00 2001 From: flaree <31554168+flaree@users.noreply.github.com> Date: Wed, 15 Jan 2020 03:15:55 +0000 Subject: [PATCH 27/49] [Docs] Modlog Example: action -> action_type (#3368) * action -> action_type * Changelog. --- changelog.d/3368.docs.rst | 1 + docs/framework_modlog.rst | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog.d/3368.docs.rst diff --git a/changelog.d/3368.docs.rst b/changelog.d/3368.docs.rst new file mode 100644 index 000000000..d1f384e9b --- /dev/null +++ b/changelog.d/3368.docs.rst @@ -0,0 +1 @@ +Update modlog documentation example to show "action_type" instead of "action". \ No newline at end of file diff --git a/docs/framework_modlog.rst b/docs/framework_modlog.rst index 18a9cf453..39d1b7b48 100644 --- a/docs/framework_modlog.rst +++ b/docs/framework_modlog.rst @@ -25,7 +25,7 @@ Basic Usage async def ban(self, ctx, user: discord.Member, reason: str = None): await ctx.guild.ban(user) case = await modlog.create_case( - ctx.bot, ctx.guild, ctx.message.created_at, action="ban", + ctx.bot, ctx.guild, ctx.message.created_at, action_type="ban", user=user, moderator=ctx.author, reason=reason ) await ctx.send("Done. It was about time.") From 60dc54b081c0735773fdeb482c2b76ef1648effa Mon Sep 17 00:00:00 2001 From: Michael H Date: Wed, 15 Jan 2020 20:44:21 -0500 Subject: [PATCH 28/49] Allow pre_invoke to be used by 3rd party cogs safely. (#3369) * Okay, so there's a lot in this diff * fix docstrings * meh * fix misleading var name * meh... * useful typehints * Apply suggestions from code review Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com> * dep warn in locations suitable * Fix this... * Apply suggestions from code review Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com> Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com> --- redbot/core/bot.py | 75 +++++++++++++++++++++++++++++++++++- redbot/core/global_checks.py | 8 ---- 2 files changed, 74 insertions(+), 9 deletions(-) diff --git a/redbot/core/bot.py b/redbot/core/bot.py index 4b994a43d..32b69f27d 100644 --- a/redbot/core/bot.py +++ b/redbot/core/bot.py @@ -10,7 +10,19 @@ from datetime import datetime from enum import IntEnum from importlib.machinery import ModuleSpec from pathlib import Path -from typing import Optional, Union, List, Dict, NoReturn +from typing import ( + Optional, + Union, + List, + Dict, + NoReturn, + Set, + Coroutine, + TypeVar, + Callable, + Awaitable, + Any, +) from types import MappingProxyType import discord @@ -36,6 +48,9 @@ __all__ = ["RedBase", "Red", "ExitCodes"] NotMessage = namedtuple("NotMessage", "guild") +PreInvokeCoroutine = Callable[[commands.Context], Awaitable[Any]] +T_BIC = TypeVar("T_BIC", bound=PreInvokeCoroutine) + def _is_submodule(parent, child): return parent == child or child.startswith(parent + ".") @@ -150,6 +165,64 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin): # pylint: d self._permissions_hooks: List[commands.CheckPredicate] = [] self._red_ready = asyncio.Event() + self._red_before_invoke_objs: Set[PreInvokeCoroutine] = set() + + @property + def _before_invoke(self): # DEP-WARN + return self._red_before_invoke_method + + @_before_invoke.setter + def _before_invoke(self, val): # DEP-WARN + """Prevent this from being overwritten in super().__init__""" + pass + + async def _red_before_invoke_method(self, ctx): + await self.wait_until_red_ready() + return_exceptions = isinstance(ctx.command, commands.commands._AlwaysAvailableCommand) + if self._red_before_invoke_objs: + await asyncio.gather( + *(coro(ctx) for coro in self._red_before_invoke_objs), + return_exceptions=return_exceptions, + ) + + def remove_before_invoke_hook(self, coro: PreInvokeCoroutine) -> None: + """ + Functional method to remove a `before_invoke` hook. + """ + self._red_before_invoke_objs.discard(coro) + + def before_invoke(self, coro: T_BIC) -> T_BIC: + """ + Overridden decorator method for Red's ``before_invoke`` behavior. + + This can safely be used purely functionally as well. + + 3rd party cogs should remove any hooks which they register at unload + using `remove_before_invoke_hook` + + Below behavior shared with discord.py: + + .. note:: + The ``before_invoke`` hooks are + only called if all checks and argument parsing procedures pass + without error. If any check or argument parsing procedures fail + then the hooks are not called. + + Parameters + ---------- + coro: Callable[[commands.Context], Awaitable[Any]] + The coroutine to register as the pre-invoke hook. + + Raises + ------ + TypeError + The coroutine passed is not actually a coroutine. + """ + if not asyncio.iscoroutinefunction(coro): + raise TypeError("The pre-invoke hook must be a coroutine.") + + self._red_before_invoke_objs.add(coro) + return coro @property def cog_mgr(self) -> NoReturn: diff --git a/redbot/core/global_checks.py b/redbot/core/global_checks.py index 47c264a04..e88405df6 100644 --- a/redbot/core/global_checks.py +++ b/redbot/core/global_checks.py @@ -13,14 +13,6 @@ def init_global_checks(bot): """ return ctx.channel.permissions_for(ctx.me).send_messages - @bot.check_once - def actually_up(ctx) -> bool: - """ - Uptime is set during the initial startup process. - If this hasn't been set, we should assume the bot isn't ready yet. - """ - return ctx.bot.uptime is not None - @bot.check_once async def whiteblacklist_checks(ctx) -> bool: return await ctx.bot.allowed_by_whitelist_blacklist(ctx.author) From 29feab638a73ee5c6927e5745fb41c1b39d00bc5 Mon Sep 17 00:00:00 2001 From: jack1142 <6032823+jack1142@users.noreply.github.com> Date: Thu, 16 Jan 2020 02:45:35 +0100 Subject: [PATCH 29/49] Update install_linux_mac.rst (#3371) --- docs/install_linux_mac.rst | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/docs/install_linux_mac.rst b/docs/install_linux_mac.rst index 45c792f8d..1b7dccfea 100644 --- a/docs/install_linux_mac.rst +++ b/docs/install_linux_mac.rst @@ -19,7 +19,7 @@ Please install the pre-requirements using the commands listed for your operating The pre-requirements are: - Python 3.8.1 or greater - Pip 18.1 or greater - - Git + - Git 2.11+ - Java Runtime Environment 11 or later (for audio support) We also recommend installing some basic compiler tools, in case our dependencies don't provide @@ -226,12 +226,18 @@ Continue by `creating-venv-linux`. Ubuntu LTS versions (18.04 and 16.04) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -We recommend adding the ``deadsnakes`` ppa to install Python 3.8.1 or greater: +We recommend adding the ``git-core`` ppa to install Git 2.11 or greater: .. code-block:: none sudo apt update sudo apt install software-properties-common + sudo add-apt-repository ppa:git-core/ppa + +We recommend adding the ``deadsnakes`` ppa to install Python 3.8.1 or greater: + +.. code-block:: none + sudo add-apt-repository ppa:deadsnakes/ppa Now install the pre-requirements with apt: @@ -251,18 +257,25 @@ Continue by `creating-venv-linux`. Ubuntu non-LTS versions ~~~~~~~~~~~~~~~~~~~~~~~ -We recommend installing pyenv as a method of installing non-native versions of python on -non-LTS versions of Ubuntu. This guide will tell you how. First, run the following commands: +We recommend adding the ``git-core`` ppa to install Git 2.11 or greater: .. code-block:: none sudo apt update + sudo apt install software-properties-common + sudo add-apt-repository ppa:git-core/ppa + +Now, to install non-native version of python on non-LTS versions of Ubuntu, we recommend +installing pyenv. To do this, first run the following commands: + +.. code-block:: none + sudo apt -y install make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev \ libsqlite3-dev wget curl llvm libncurses5-dev xz-utils tk-dev libxml2-dev \ libxmlsec1-dev libffi-dev liblzma-dev libgdbm-dev uuid-dev python3-openssl git openjdk-11-jre CXX=/usr/bin/g++ -Complete the rest of the installation by `installing Python 3.8 with pyenv `. +And then complete the rest of the installation by `installing Python 3.8 with pyenv `. ---- From a1b95e5072a49745d1dc4f66d93fe50c1134c9a9 Mon Sep 17 00:00:00 2001 From: jack1142 <6032823+jack1142@users.noreply.github.com> Date: Thu, 16 Jan 2020 02:54:23 +0100 Subject: [PATCH 30/49] enhance(downloader): log git commands that failed (#3372) --- redbot/cogs/downloader/errors.py | 8 +- redbot/cogs/downloader/repo_manager.py | 162 ++++++++++++------------- 2 files changed, 81 insertions(+), 89 deletions(-) diff --git a/redbot/cogs/downloader/errors.py b/redbot/cogs/downloader/errors.py index efd31bedd..ee0c6ab2e 100644 --- a/redbot/cogs/downloader/errors.py +++ b/redbot/cogs/downloader/errors.py @@ -38,6 +38,10 @@ class GitException(DownloaderException): Generic class for git exceptions. """ + def __init__(self, message: str, git_command: str) -> None: + self.git_command = git_command + super().__init__(f"Git command failed: {git_command}\nError message: {message}") + class InvalidRepoName(DownloaderException): """ @@ -138,8 +142,8 @@ class AmbiguousRevision(GitException): Thrown when specified revision is ambiguous. """ - def __init__(self, message: str, candidates: List[Candidate]) -> None: - super().__init__(message) + def __init__(self, message: str, git_command: str, candidates: List[Candidate]) -> None: + super().__init__(message, git_command) self.candidates = candidates diff --git a/redbot/cogs/downloader/repo_manager.py b/redbot/cogs/downloader/repo_manager.py index 9c28a7a76..fe786ef98 100644 --- a/redbot/cogs/downloader/repo_manager.py +++ b/redbot/cogs/downloader/repo_manager.py @@ -203,21 +203,20 @@ class Repo(RepoJSONMixin): """ valid_exit_codes = (0, 1) - p = await self._run( - ProcessFormatter().format( - self.GIT_IS_ANCESTOR, - path=self.folder_path, - maybe_ancestor_rev=maybe_ancestor_rev, - descendant_rev=descendant_rev, - ), - valid_exit_codes=valid_exit_codes, + git_command = ProcessFormatter().format( + self.GIT_IS_ANCESTOR, + path=self.folder_path, + maybe_ancestor_rev=maybe_ancestor_rev, + descendant_rev=descendant_rev, ) + p = await self._run(git_command, valid_exit_codes=valid_exit_codes) if p.returncode in valid_exit_codes: return not bool(p.returncode) raise errors.GitException( f"Git failed to determine if commit {maybe_ancestor_rev}" - f" is ancestor of {descendant_rev} for repo at path: {self.folder_path}" + f" is ancestor of {descendant_rev} for repo at path: {self.folder_path}", + git_command, ) async def is_on_branch(self) -> bool: @@ -253,15 +252,14 @@ class Repo(RepoJSONMixin): """ if new_rev is None: new_rev = self.branch - p = await self._run( - ProcessFormatter().format( - self.GIT_DIFF_FILE_STATUS, path=self.folder_path, old_rev=old_rev, new_rev=new_rev - ) + git_command = ProcessFormatter().format( + self.GIT_DIFF_FILE_STATUS, path=self.folder_path, old_rev=old_rev, new_rev=new_rev ) + p = await self._run(git_command) if p.returncode != 0: raise errors.GitDiffError( - "Git diff failed for repo at path: {}".format(self.folder_path) + f"Git diff failed for repo at path: {self.folder_path}", git_command ) stdout = p.stdout.strip(b"\t\n\x00 ").decode().split("\x00\t") @@ -310,18 +308,17 @@ class Repo(RepoJSONMixin): async with self.checkout(descendant_rev): return discord.utils.get(self.available_modules, name=module_name) - p = await self._run( - ProcessFormatter().format( - self.GIT_GET_LAST_MODULE_OCCURRENCE_COMMIT, - path=self.folder_path, - descendant_rev=descendant_rev, - module_name=module_name, - ) + git_command = ProcessFormatter().format( + self.GIT_GET_LAST_MODULE_OCCURRENCE_COMMIT, + path=self.folder_path, + descendant_rev=descendant_rev, + module_name=module_name, ) + p = await self._run(git_command) if p.returncode != 0: raise errors.GitException( - "Git log failed for repo at path: {}".format(self.folder_path) + f"Git log failed for repo at path: {self.folder_path}", git_command ) commit = p.stdout.decode().strip() @@ -418,19 +415,18 @@ class Repo(RepoJSONMixin): to get messages for. :return: Git commit note log """ - p = await self._run( - ProcessFormatter().format( - self.GIT_LOG, - path=self.folder_path, - old_rev=old_rev, - relative_file_path=relative_file_path, - ) + git_command = ProcessFormatter().format( + self.GIT_LOG, + path=self.folder_path, + old_rev=old_rev, + relative_file_path=relative_file_path, ) + p = await self._run(git_command) if p.returncode != 0: raise errors.GitException( - "An exception occurred while executing git log on" - " this repo: {}".format(self.folder_path) + f"An exception occurred while executing git log on this repo: {self.folder_path}", + git_command, ) return p.stdout.decode().strip() @@ -457,21 +453,24 @@ class Repo(RepoJSONMixin): Full sha1 object name for provided revision. """ - p = await self._run( - ProcessFormatter().format(self.GIT_GET_FULL_SHA1, path=self.folder_path, rev=rev) + git_command = ProcessFormatter().format( + self.GIT_GET_FULL_SHA1, path=self.folder_path, rev=rev ) + p = await self._run(git_command) if p.returncode != 0: stderr = p.stderr.decode().strip() ambiguous_error = f"error: short SHA1 {rev} is ambiguous\nhint: The candidates are:\n" if not stderr.startswith(ambiguous_error): - raise errors.UnknownRevision(f"Revision {rev} cannot be found.") + raise errors.UnknownRevision(f"Revision {rev} cannot be found.", git_command) candidates = [] for match in self.AMBIGUOUS_ERROR_REGEX.finditer(stderr, len(ambiguous_error)): candidates.append(Candidate(match["rev"], match["type"], match["desc"])) if candidates: - raise errors.AmbiguousRevision(f"Short SHA1 {rev} is ambiguous.", candidates) - raise errors.UnknownRevision(f"Revision {rev} cannot be found.") + raise errors.AmbiguousRevision( + f"Short SHA1 {rev} is ambiguous.", git_command, candidates + ) + raise errors.UnknownRevision(f"Revision {rev} cannot be found.", git_command) return p.stdout.decode().strip() @@ -554,17 +553,14 @@ class Repo(RepoJSONMixin): return exists, __ = self._existing_git_repo() if not exists: - raise errors.MissingGitRepo( - "A git repo does not exist at path: {}".format(self.folder_path) - ) + raise errors.MissingGitRepo(f"A git repo does not exist at path: {self.folder_path}") - p = await self._run( - ProcessFormatter().format(self.GIT_CHECKOUT, path=self.folder_path, rev=rev) - ) + git_command = ProcessFormatter().format(self.GIT_CHECKOUT, path=self.folder_path, rev=rev) + p = await self._run(git_command) if p.returncode != 0: raise errors.UnknownRevision( - "Could not checkout to {}. This revision may not exist".format(rev) + f"Could not checkout to {rev}. This revision may not exist", git_command ) await self._setup_repo() @@ -619,25 +615,22 @@ class Repo(RepoJSONMixin): """ exists, path = self._existing_git_repo() if exists: - raise errors.ExistingGitRepo("A git repo already exists at path: {}".format(path)) + raise errors.ExistingGitRepo(f"A git repo already exists at path: {path}") if self.branch is not None: - p = await self._run( - ProcessFormatter().format( - self.GIT_CLONE, branch=self.branch, url=self.url, folder=self.folder_path - ) + git_command = ProcessFormatter().format( + self.GIT_CLONE, branch=self.branch, url=self.url, folder=self.folder_path ) else: - p = await self._run( - ProcessFormatter().format( - self.GIT_CLONE_NO_BRANCH, url=self.url, folder=self.folder_path - ) + git_command = ProcessFormatter().format( + self.GIT_CLONE_NO_BRANCH, url=self.url, folder=self.folder_path ) + p = await self._run(git_command) if p.returncode: # Try cleaning up folder shutil.rmtree(str(self.folder_path), ignore_errors=True) - raise errors.CloningError("Error when running git clone.") + raise errors.CloningError("Error when running git clone.", git_command) if self.branch is None: self.branch = await self.current_branch() @@ -657,17 +650,14 @@ class Repo(RepoJSONMixin): """ exists, __ = self._existing_git_repo() if not exists: - raise errors.MissingGitRepo( - "A git repo does not exist at path: {}".format(self.folder_path) - ) + raise errors.MissingGitRepo(f"A git repo does not exist at path: {self.folder_path}") - p = await self._run( - ProcessFormatter().format(self.GIT_CURRENT_BRANCH, path=self.folder_path) - ) + git_command = ProcessFormatter().format(self.GIT_CURRENT_BRANCH, path=self.folder_path) + p = await self._run(git_command) if p.returncode != 0: raise errors.GitException( - "Could not determine current branch at path: {}".format(self.folder_path) + f"Could not determine current branch at path: {self.folder_path}", git_command ) return p.stdout.decode().strip() @@ -683,16 +673,13 @@ class Repo(RepoJSONMixin): """ exists, __ = self._existing_git_repo() if not exists: - raise errors.MissingGitRepo( - "A git repo does not exist at path: {}".format(self.folder_path) - ) + raise errors.MissingGitRepo(f"A git repo does not exist at path: {self.folder_path}") - p = await self._run( - ProcessFormatter().format(self.GIT_CURRENT_COMMIT, path=self.folder_path) - ) + git_command = ProcessFormatter().format(self.GIT_CURRENT_COMMIT, path=self.folder_path) + p = await self._run(git_command) if p.returncode != 0: - raise errors.CurrentHashError("Unable to determine commit hash.") + raise errors.CurrentHashError("Unable to determine commit hash.", git_command) return p.stdout.decode().strip() @@ -715,16 +702,15 @@ class Repo(RepoJSONMixin): exists, __ = self._existing_git_repo() if not exists: - raise errors.MissingGitRepo( - "A git repo does not exist at path: {}".format(self.folder_path) - ) + raise errors.MissingGitRepo(f"A git repo does not exist at path: {self.folder_path}") - p = await self._run( - ProcessFormatter().format(self.GIT_LATEST_COMMIT, path=self.folder_path, branch=branch) + git_command = ProcessFormatter().format( + self.GIT_LATEST_COMMIT, path=self.folder_path, branch=branch ) + p = await self._run(git_command) if p.returncode != 0: - raise errors.CurrentHashError("Unable to determine latest commit hash.") + raise errors.CurrentHashError("Unable to determine latest commit hash.", git_command) return p.stdout.decode().strip() @@ -751,10 +737,11 @@ class Repo(RepoJSONMixin): if folder is None: folder = self.folder_path - p = await self._run(ProcessFormatter().format(Repo.GIT_DISCOVER_REMOTE_URL, path=folder)) + git_command = ProcessFormatter().format(Repo.GIT_DISCOVER_REMOTE_URL, path=folder) + p = await self._run(git_command) if p.returncode != 0: - raise errors.NoRemoteURL("Unable to discover a repo URL.") + raise errors.NoRemoteURL("Unable to discover a repo URL.", git_command) return p.stdout.decode().strip() @@ -773,19 +760,18 @@ class Repo(RepoJSONMixin): await self.checkout(branch) exists, __ = self._existing_git_repo() if not exists: - raise errors.MissingGitRepo( - "A git repo does not exist at path: {}".format(self.folder_path) - ) + raise errors.MissingGitRepo(f"A git repo does not exist at path: {self.folder_path}") - p = await self._run( - ProcessFormatter().format(self.GIT_HARD_RESET, path=self.folder_path, branch=branch) + git_command = ProcessFormatter().format( + self.GIT_HARD_RESET, path=self.folder_path, branch=branch ) + p = await self._run(git_command) if p.returncode != 0: raise errors.HardResetError( - "Some error occurred when trying to" - " execute a hard reset on the repo at" - " the following path: {}".format(self.folder_path) + "Some error occurred when trying to execute a hard reset on the repo at" + f" the following path: {self.folder_path}", + git_command, ) async def update(self) -> Tuple[str, str]: @@ -795,7 +781,7 @@ class Repo(RepoJSONMixin): ------- `tuple` of `str` :py:code`(old commit hash, new commit hash)` - + Raises ------- `UpdateError` - if git pull results with non-zero exit code @@ -804,12 +790,14 @@ class Repo(RepoJSONMixin): await self.hard_reset() - p = await self._run(ProcessFormatter().format(self.GIT_PULL, path=self.folder_path)) + git_command = ProcessFormatter().format(self.GIT_PULL, path=self.folder_path) + p = await self._run(git_command) if p.returncode != 0: raise errors.UpdateError( "Git pull returned a non zero exit code" - " for the repo located at path: {}".format(self.folder_path) + f" for the repo located at path: {self.folder_path}", + git_command, ) await self._setup_repo() @@ -1114,7 +1102,7 @@ class RepoManager: """ repo = self.get_repo(name) if repo is None: - raise errors.MissingGitRepo("There is no repo with the name {}".format(name)) + raise errors.MissingGitRepo(f"There is no repo with the name {name}") safe_delete(repo.folder_path) From d6d14617d21b5d24ef95bd2f4a9b23ba014de194 Mon Sep 17 00:00:00 2001 From: Redjumpman Date: Thu, 16 Jan 2020 13:18:20 -0500 Subject: [PATCH 31/49] Update __init__.py (#3381) Removed redundant check. --- redbot/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/redbot/__init__.py b/redbot/__init__.py index 13d14ff05..8b08c74f4 100644 --- a/redbot/__init__.py +++ b/redbot/__init__.py @@ -181,9 +181,7 @@ class VersionInfo: def _update_event_loop_policy(): - if _sys.platform == "win32": - _asyncio.set_event_loop_policy(_asyncio.WindowsProactorEventLoopPolicy()) - elif _sys.implementation.name == "cpython": + if _sys.implementation.name == "cpython": # Let's not force this dependency, uvloop is much faster on cpython try: import uvloop as _uvloop From 85438e7454c2d4b1331ec6efb7750b2a407704c8 Mon Sep 17 00:00:00 2001 From: Michael H Date: Thu, 16 Jan 2020 19:09:09 -0500 Subject: [PATCH 32/49] [Setup] Fix data deletion. (#3384) * I'm ready to :knife: some of these entrypoints * If we're gonna teardown here, may as well do it right --- redbot/setup.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/redbot/setup.py b/redbot/setup.py index c052cb64c..2be440acc 100644 --- a/redbot/setup.py +++ b/redbot/setup.py @@ -253,20 +253,23 @@ async def remove_instance( backend = get_current_backend(instance) driver_cls = drivers.get_driver_class(backend) + await driver_cls.initialize(**data_manager.storage_details()) + try: + if delete_data is True: + await driver_cls.delete_all_data(interactive=interactive, drop_db=drop_db) - if delete_data is True: - await driver_cls.delete_all_data(interactive=interactive, drop_db=drop_db) + if interactive is True and remove_datapath is None: + remove_datapath = click.confirm( + "Would you like to delete the instance's entire datapath?", default=False + ) - if interactive is True and remove_datapath is None: - remove_datapath = click.confirm( - "Would you like to delete the instance's entire datapath?", default=False - ) + if remove_datapath is True: + data_path = data_manager.core_data_path().parent + safe_delete(data_path) - if remove_datapath is True: - data_path = data_manager.core_data_path().parent - safe_delete(data_path) - - save_config(instance, {}, remove=True) + save_config(instance, {}, remove=True) + finally: + await driver_cls.teardown() print("The instance {} has been removed\n".format(instance)) From a203fe34cf01115533a07898e1636c2f542d318f Mon Sep 17 00:00:00 2001 From: Stonedestroyer <1307729+Stonedestroyer@users.noreply.github.com> Date: Fri, 17 Jan 2020 10:43:37 +0100 Subject: [PATCH 33/49] [Typo Fix] Permissions (#3390) * [Typo Fix] Permissions * Changelog file --- changelog.d/3390.misc.rst | 1 + redbot/core/commands/requires.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 changelog.d/3390.misc.rst diff --git a/changelog.d/3390.misc.rst b/changelog.d/3390.misc.rst new file mode 100644 index 000000000..9167a63c8 --- /dev/null +++ b/changelog.d/3390.misc.rst @@ -0,0 +1 @@ +Fixes a typo in redbot/core/commands/requires.py. \ No newline at end of file diff --git a/redbot/core/commands/requires.py b/redbot/core/commands/requires.py index b6f188de1..f3614e0fa 100644 --- a/redbot/core/commands/requires.py +++ b/redbot/core/commands/requires.py @@ -95,8 +95,8 @@ class PrivilegeLevel(enum.IntEnum): """Enumeration for special privileges.""" # Maintainer Note: do NOT re-order these. - # Each privelege level also implies access to the ones before it. - # Inserting new privelege levels at a later point is fine if that is considered. + # Each privilege level also implies access to the ones before it. + # Inserting new privilege levels at a later point is fine if that is considered. NONE = enum.auto() """No special privilege level.""" From 67fbcb1b4a2709474fc578509ae0e236b183b861 Mon Sep 17 00:00:00 2001 From: jack1142 <6032823+jack1142@users.noreply.github.com> Date: Fri, 17 Jan 2020 10:44:10 +0100 Subject: [PATCH 34/49] enhance(downloader): pagify any output that might be too long (#3388) --- redbot/cogs/downloader/downloader.py | 41 ++++++++++++++++------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/redbot/cogs/downloader/downloader.py b/redbot/cogs/downloader/downloader.py index f770f292f..eb063157c 100644 --- a/redbot/cogs/downloader/downloader.py +++ b/redbot/cogs/downloader/downloader.py @@ -418,6 +418,11 @@ class Downloader(commands.Cog): elif target.is_file(): os.remove(str(target)) + @staticmethod + async def send_pagified(target: discord.abc.Messageable, content: str) -> None: + for page in pagify(content): + await target.send(page) + @commands.command() @checks.is_owner() async def pipinstall(self, ctx: commands.Context, *deps: str) -> None: @@ -550,7 +555,7 @@ class Downloader(commands.Cog): if failed: message += "\n" + self.format_failed_repos(failed) - await ctx.send(message) + await self.send_pagified(ctx, message) @commands.group() @checks.is_owner() @@ -596,12 +601,13 @@ class Downloader(commands.Cog): tuple(map(inline, libnames)) ) if message: - await ctx.send( + await self.send_pagified( + ctx, _( "Cog requirements and shared libraries for all installed cogs" " have been reinstalled but there were some errors:\n" ) - + message + + message, ) else: await ctx.send( @@ -643,8 +649,7 @@ class Downloader(commands.Cog): f"**{candidate.object_type} {candidate.rev}**" f" - {candidate.description}\n" ) - for page in pagify(msg): - await ctx.send(msg) + await self.send_pagified(ctx, msg) return except errors.UnknownRevision: await ctx.send( @@ -658,14 +663,14 @@ class Downloader(commands.Cog): async with repo.checkout(commit, exit_to_rev=repo.branch): cogs, message = await self._filter_incorrect_cogs_by_names(repo, cog_names) if not cogs: - await ctx.send(message) + await self.send_pagified(ctx, message) return failed_reqs = await self._install_requirements(cogs) if failed_reqs: message += _("\nFailed to install requirements: ") + humanize_list( tuple(map(inline, failed_reqs)) ) - await ctx.send(message) + await self.send_pagified(ctx, message) return installed_cogs, failed_cogs = await self._install_cogs(cogs) @@ -711,7 +716,7 @@ class Downloader(commands.Cog): + message ) # "---" added to separate cog install messages from Downloader's message - await ctx.send(f"{message}{deprecation_notice}\n---") + await self.send_pagified(ctx, f"{message}{deprecation_notice}\n---") for cog in installed_cogs: if cog.install_msg: await ctx.send(cog.install_msg.replace("[p]", ctx.prefix)) @@ -759,7 +764,7 @@ class Downloader(commands.Cog): " If so, ensure the cogs have been unloaded with `{prefix}unload {cogs}`." ).format(prefix=ctx.prefix, cogs=" ".join(failed_cogs)) ) - await ctx.send(message) + await self.send_pagified(ctx, message) @cog.command(name="pin", usage="") async def _cog_pin(self, ctx: commands.Context, *cogs: InstalledCog) -> None: @@ -782,7 +787,7 @@ class Downloader(commands.Cog): message += _("Pinned cogs: ") + humanize_list(cognames) if already_pinned: message += _("\nThese cogs were already pinned: ") + humanize_list(already_pinned) - await ctx.send(message) + await self.send_pagified(ctx, message) @cog.command(name="unpin", usage="") async def _cog_unpin(self, ctx: commands.Context, *cogs: InstalledCog) -> None: @@ -805,7 +810,7 @@ class Downloader(commands.Cog): message += _("Unpinned cogs: ") + humanize_list(cognames) if not_pinned: message += _("\nThese cogs weren't pinned: ") + humanize_list(not_pinned) - await ctx.send(message) + await self.send_pagified(ctx, message) @cog.command(name="checkforupdates") async def _cog_checkforupdates(self, ctx: commands.Context) -> None: @@ -837,7 +842,7 @@ class Downloader(commands.Cog): if failed: message += "\n" + self.format_failed_repos(failed) - await ctx.send(message) + await self.send_pagified(ctx, message) @cog.command(name="update") async def _cog_update(self, ctx: commands.Context, *cogs: InstalledCog) -> None: @@ -873,7 +878,6 @@ class Downloader(commands.Cog): rev: Optional[str] = None, cogs: Optional[List[InstalledModule]] = None, ) -> None: - message = "" failed_repos = set() updates_available = set() @@ -886,7 +890,7 @@ class Downloader(commands.Cog): await repo.update() except errors.UpdateError: message = self.format_failed_repos([repo.name]) - await ctx.send(message) + await self.send_pagified(ctx, message) return try: @@ -900,11 +904,10 @@ class Downloader(commands.Cog): f"**{candidate.object_type} {candidate.rev}**" f" - {candidate.description}\n" ) - for page in pagify(msg): - await ctx.send(msg) + await self.send_pagified(ctx, msg) return except errors.UnknownRevision: - message += _( + message = _( "Error: there is no revision `{rev}` in repo `{repo.name}`" ).format(rev=rev, repo=repo) await ctx.send(message) @@ -921,6 +924,8 @@ class Downloader(commands.Cog): pinned_cogs = {cog for cog in cogs_to_check if cog.pinned} cogs_to_check -= pinned_cogs + + message = "" if not cogs_to_check: cogs_to_update = libs_to_update = () message += _("There were no cogs to check.") @@ -976,7 +981,7 @@ class Downloader(commands.Cog): if repos_with_libs: message += DEPRECATION_NOTICE.format(repo_list=humanize_list(list(repos_with_libs))) - await ctx.send(message) + await self.send_pagified(ctx, message) if updates_available and updated_cognames: await self._ask_for_cog_reload(ctx, updated_cognames) From 48ccd9070c85baecdf475f06facb9c663157b8cc Mon Sep 17 00:00:00 2001 From: Stonedestroyer <1307729+Stonedestroyer@users.noreply.github.com> Date: Fri, 17 Jan 2020 12:08:31 +0100 Subject: [PATCH 35/49] [Core] Adds server IDs to servers command. (#3393) * [Core] Adds server ID to servers command. * Changelog --- changelog.d/3224.enhance.rst | 1 + redbot/core/core_commands.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog.d/3224.enhance.rst diff --git a/changelog.d/3224.enhance.rst b/changelog.d/3224.enhance.rst new file mode 100644 index 000000000..5dcacb085 --- /dev/null +++ b/changelog.d/3224.enhance.rst @@ -0,0 +1 @@ +Adds server IDs to servers command. \ No newline at end of file diff --git a/redbot/core/core_commands.py b/redbot/core/core_commands.py index cca4af689..4a05cea70 100644 --- a/redbot/core/core_commands.py +++ b/redbot/core/core_commands.py @@ -563,7 +563,7 @@ class Core(commands.Cog, CoreLogic): msg = "" responses = [] for i, server in enumerate(guilds, 1): - msg += "{}: {}\n".format(i, server.name) + msg += "{}: {} (`{}`)\n".format(i, server.name, server.id) responses.append(str(i)) for page in pagify(msg, ["\n"]): From 3d1e6eab0051b9c425b94ce9f633b1e013f20316 Mon Sep 17 00:00:00 2001 From: Draper <27962761+Drapersniper@users.noreply.github.com> Date: Fri, 17 Jan 2020 21:30:29 +0000 Subject: [PATCH 36/49] [Audio] Add backticks to commands in docstrings, fix GH-3140 (#3374) * docstring change * remove backticks Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Seems like i cant read Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Rename 3140.enchance.1.rst to 3140.enhance.1.rst --- changelog.d/audio/3140.enhance.1.rst | 1 + redbot/cogs/audio/audio.py | 155 +++++++++++++-------------- 2 files changed, 76 insertions(+), 80 deletions(-) create mode 100644 changelog.d/audio/3140.enhance.1.rst diff --git a/changelog.d/audio/3140.enhance.1.rst b/changelog.d/audio/3140.enhance.1.rst new file mode 100644 index 000000000..81570e412 --- /dev/null +++ b/changelog.d/audio/3140.enhance.1.rst @@ -0,0 +1 @@ +Update the help strings for ``[p]audioset emptydisconnect``. diff --git a/redbot/cogs/audio/audio.py b/redbot/cogs/audio/audio.py index bbbf1852c..19ad5e744 100644 --- a/redbot/cogs/audio/audio.py +++ b/redbot/cogs/audio/audio.py @@ -696,7 +696,7 @@ class Audio(commands.Cog): async def dc(self, ctx: commands.Context): """Toggle the bot auto-disconnecting when done playing. - This setting takes precedence over [p]audioset emptydisconnect. + This setting takes precedence over `[p]audioset emptydisconnect`. """ disconnect = await self.config.guild(ctx.guild).disconnect() @@ -1123,7 +1123,7 @@ class Audio(commands.Cog): """Set a playlist to auto-play songs from. **Usage**: - ​ ​ ​ ​ [p]audioset autoplay playlist_name_OR_id args + ​ ​ ​ ​ `[p]audioset autoplay playlist_name_OR_id [args]` **Args**: ​ ​ ​ ​ The following are all optional: @@ -1146,9 +1146,9 @@ class Audio(commands.Cog): ​ ​ ​ ​ Exact guild name Example use: - ​ ​ ​ ​ [p]audioset autoplay MyGuildPlaylist - ​ ​ ​ ​ [p]audioset autoplay MyGlobalPlaylist --scope Global - ​ ​ ​ ​ [p]audioset autoplay PersonalPlaylist --scope User --author Draper + ​ ​ ​ ​ `[p]audioset autoplay MyGuildPlaylist` + ​ ​ ​ ​ `[p]audioset autoplay MyGlobalPlaylist --scope Global` + ​ ​ ​ ​ `[p]audioset autoplay PersonalPlaylist --scope User --author Draper` """ if scope_data is None: scope_data = [PlaylistScope.GUILD.value, ctx.author, ctx.guild, False] @@ -1259,7 +1259,10 @@ class Audio(commands.Cog): @audioset.command() @checks.mod_or_permissions(administrator=True) async def emptydisconnect(self, ctx: commands.Context, seconds: int): - """Auto-disconnect from channel when bot is alone in it for x seconds, 0 to disable.""" + """Auto-disconnect from channel when bot is alone in it for x seconds, 0 to disable. + + `[p]audioset dc` takes precedence over this setting. + """ if seconds < 0: return await self._embed_msg( ctx, title=_("Invalid Time"), description=_("Seconds can't be less than zero.") @@ -4002,7 +4005,7 @@ class Audio(commands.Cog): The track(s) will be appended to the end of the playlist. **Usage**: - ​ ​ ​ ​ [p]playlist append playlist_name_OR_id track_name_OR_url args + ​ ​ ​ ​ `[p]playlist append playlist_name_OR_id track_name_OR_url [args]` **Args**: ​ ​ ​ ​ The following are all optional: @@ -4025,10 +4028,9 @@ class Audio(commands.Cog): ​ ​ ​ ​ Exact guild name Example use: - ​ ​ ​ ​ [p]playlist append MyGuildPlaylist Hello by Adele - ​ ​ ​ ​ [p]playlist append MyGlobalPlaylist Hello by Adele --scope Global - ​ ​ ​ ​ [p]playlist append MyGlobalPlaylist Hello by Adele --scope Global - --Author Draper#6666 + ​ ​ ​ ​ `[p]playlist append MyGuildPlaylist Hello by Adele` + ​ ​ ​ ​ `[p]playlist append MyGlobalPlaylist Hello by Adele --scope Global` + ​ ​ ​ ​ `[p]playlist append MyGlobalPlaylist Hello by Adele --scope Global --Author Draper#6666` """ if scope_data is None: scope_data = [PlaylistScope.GUILD.value, ctx.author, ctx.guild, False] @@ -4163,7 +4165,7 @@ class Audio(commands.Cog): """Copy a playlist from one scope to another. **Usage**: - ​ ​ ​ ​ [p]playlist copy playlist_name_OR_id args + ​ ​ ​ ​ `[p]playlist copy playlist_name_OR_id [args]` **Args**: ​ ​ ​ ​ The following are all optional: @@ -4190,11 +4192,9 @@ class Audio(commands.Cog): ​ ​ ​ ​ Exact guild name Example use: - ​ ​ ​ ​ [p]playlist copy MyGuildPlaylist --from-scope Guild --to-scope Global - ​ ​ ​ ​ [p]playlist copy MyGlobalPlaylist --from-scope Global --to-author Draper#6666 - --to-scope User - ​ ​ ​ ​ [p]playlist copy MyPersonalPlaylist --from-scope user --to-author Draper#6666 - --to-scope Guild --to-guild Red - Discord Bot + ​ ​ ​ ​ `[p]playlist copy MyGuildPlaylist --from-scope Guild --to-scope Global` + ​ ​ ​ ​ `[p]playlist copy MyGlobalPlaylist --from-scope Global --to-author Draper#6666 --to-scope User` + ​ ​ ​ ​ `[p]playlist copy MyPersonalPlaylist --from-scope user --to-author Draper#6666 --to-scope Guild --to-guild Red - Discord Bot` """ if scope_data is None: @@ -4303,7 +4303,7 @@ class Audio(commands.Cog): """Create an empty playlist. **Usage**: - ​ ​ ​ ​ [p]playlist create playlist_name args + ​ ​ ​ ​ `[p]playlist create playlist_name [args]` **Args**: ​ ​ ​ ​ The following are all optional: @@ -4326,9 +4326,9 @@ class Audio(commands.Cog): ​ ​ ​ ​ Exact guild name Example use: - ​ ​ ​ ​ [p]playlist create MyGuildPlaylist - ​ ​ ​ ​ [p]playlist create MyGlobalPlaylist --scope Global - ​ ​ ​ ​ [p]playlist create MyPersonalPlaylist --scope User + ​ ​ ​ ​ `[p]playlist create MyGuildPlaylist` + ​ ​ ​ ​ `[p]playlist create MyGlobalPlaylist --scope Global` + ​ ​ ​ ​ `[p]playlist create MyPersonalPlaylist --scope User` """ if scope_data is None: scope_data = [PlaylistScope.GUILD.value, ctx.author, ctx.guild, False] @@ -4370,7 +4370,7 @@ class Audio(commands.Cog): """Delete a saved playlist. **Usage**: - ​ ​ ​ ​ [p]playlist delete playlist_name_OR_id args + ​ ​ ​ ​ `[p]playlist delete playlist_name_OR_id [args]` **Args**: ​ ​ ​ ​ The following are all optional: @@ -4393,9 +4393,9 @@ class Audio(commands.Cog): ​ ​ ​ ​ Exact guild name Example use: - ​ ​ ​ ​ [p]playlist delete MyGuildPlaylist - ​ ​ ​ ​ [p]playlist delete MyGlobalPlaylist --scope Global - ​ ​ ​ ​ [p]playlist delete MyPersonalPlaylist --scope User + ​ ​ ​ ​ `[p]playlist delete MyGuildPlaylist` + ​ ​ ​ ​ `[p]playlist delete MyGlobalPlaylist --scope Global` + ​ ​ ​ ​ `[p]playlist delete MyPersonalPlaylist --scope User` """ if scope_data is None: scope_data = [PlaylistScope.GUILD.value, ctx.author, ctx.guild, False] @@ -4457,7 +4457,7 @@ class Audio(commands.Cog): """Remove duplicate tracks from a saved playlist. **Usage**: - ​ ​ ​ ​ [p]playlist dedupe playlist_name_OR_id args + ​ ​ ​ ​ `[p]playlist dedupe playlist_name_OR_id [args]` **Args**: ​ ​ ​ ​ The following are all optional: @@ -4480,9 +4480,9 @@ class Audio(commands.Cog): ​ ​ ​ ​ Exact guild name Example use: - ​ ​ ​ ​ [p]playlist dedupe MyGuildPlaylist - ​ ​ ​ ​ [p]playlist dedupe MyGlobalPlaylist --scope Global - ​ ​ ​ ​ [p]playlist dedupe MyPersonalPlaylist --scope User + ​ ​ ​ ​ `[p]playlist dedupe MyGuildPlaylist` + ​ ​ ​ ​ `[p]playlist dedupe MyGlobalPlaylist --scope Global` + ​ ​ ​ ​ `[p]playlist dedupe MyPersonalPlaylist --scope User` """ async with ctx.typing(): if scope_data is None: @@ -4596,12 +4596,12 @@ class Audio(commands.Cog): ): """Download a copy of a playlist. - These files can be used with the [p]playlist upload command. + These files can be used with the `[p]playlist upload` command. Red v2-compatible playlists can be generated by passing True for the v2 variable. **Usage**: - ​ ​ ​ ​ [p]playlist download playlist_name_OR_id [v2=True_OR_False] args + ​ ​ ​ ​ `[p]playlist download playlist_name_OR_id [v2=True_OR_False] [args]` **Args**: ​ ​ ​ ​ The following are all optional: @@ -4624,9 +4624,9 @@ class Audio(commands.Cog): ​ ​ ​ ​ Exact guild name Example use: - ​ ​ ​ ​ [p]playlist download MyGuildPlaylist True - ​ ​ ​ ​ [p]playlist download MyGlobalPlaylist False --scope Global - ​ ​ ​ ​ [p]playlist download MyPersonalPlaylist --scope User + ​ ​ ​ ​ `[p]playlist download MyGuildPlaylist True` + ​ ​ ​ ​ `[p]playlist download MyGlobalPlaylist False --scope Global` + ​ ​ ​ ​ `[p]playlist download MyPersonalPlaylist --scope User` """ if scope_data is None: scope_data = [PlaylistScope.GUILD.value, ctx.author, ctx.guild, False] @@ -4741,7 +4741,7 @@ class Audio(commands.Cog): """Retrieve information from a saved playlist. **Usage**: - ​ ​ ​ ​ [p]playlist info playlist_name_OR_id args + ​ ​ ​ ​ `[p]playlist info playlist_name_OR_id [args]` **Args**: ​ ​ ​ ​ The following are all optional: @@ -4764,9 +4764,9 @@ class Audio(commands.Cog): ​ ​ ​ ​ Exact guild name Example use: - ​ ​ ​ ​ [p]playlist info MyGuildPlaylist - ​ ​ ​ ​ [p]playlist info MyGlobalPlaylist --scope Global - ​ ​ ​ ​ [p]playlist info MyPersonalPlaylist --scope User + ​ ​ ​ ​ `[p]playlist info MyGuildPlaylist` + ​ ​ ​ ​ `[p]playlist info MyGlobalPlaylist --scope Global` + ​ ​ ​ ​ `[p]playlist info MyPersonalPlaylist --scope User` """ if scope_data is None: scope_data = [PlaylistScope.GUILD.value, ctx.author, ctx.guild, False] @@ -4873,7 +4873,7 @@ class Audio(commands.Cog): """List saved playlists. **Usage**: - ​ ​ ​ ​ [p]playlist list args + ​ ​ ​ ​ `[p]playlist list [args]` **Args**: ​ ​ ​ ​ The following are all optional: @@ -4896,9 +4896,9 @@ class Audio(commands.Cog): ​ ​ ​ ​ Exact guild name Example use: - ​ ​ ​ ​ [p]playlist list - ​ ​ ​ ​ [p]playlist list --scope Global - ​ ​ ​ ​ [p]playlist list --scope User + ​ ​ ​ ​ `[p]playlist list` + ​ ​ ​ ​ `[p]playlist list --scope Global` + ​ ​ ​ ​ `[p]playlist list --scope User` """ if scope_data is None: scope_data = [PlaylistScope.GUILD.value, ctx.author, ctx.guild, False] @@ -4998,7 +4998,7 @@ class Audio(commands.Cog): """Save the queue to a playlist. **Usage**: - ​ ​ ​ ​ [p]playlist queue playlist_name + ​ ​ ​ ​ `[p]playlist queue playlist_name [args]` **Args**: ​ ​ ​ ​ The following are all optional: @@ -5021,9 +5021,9 @@ class Audio(commands.Cog): ​ ​ ​ ​ Exact guild name Example use: - ​ ​ ​ ​ [p]playlist queue MyGuildPlaylist - ​ ​ ​ ​ [p]playlist queue MyGlobalPlaylist --scope Global - ​ ​ ​ ​ [p]playlist queue MyPersonalPlaylist --scope User + ​ ​ ​ ​ `[p]playlist queue MyGuildPlaylist` + ​ ​ ​ ​ `[p]playlist queue MyGlobalPlaylist --scope Global` + ​ ​ ​ ​ `[p]playlist queue MyPersonalPlaylist --scope User` """ async with ctx.typing(): if scope_data is None: @@ -5101,7 +5101,7 @@ class Audio(commands.Cog): """Remove a track from a playlist by url. **Usage**: - ​ ​ ​ ​ [p]playlist remove playlist_name_OR_id url args + ​ ​ ​ ​ `[p]playlist remove playlist_name_OR_id url [args]` **Args**: ​ ​ ​ ​ The following are all optional: @@ -5124,11 +5124,9 @@ class Audio(commands.Cog): ​ ​ ​ ​ Exact guild name Example use: - ​ ​ ​ ​ [p]playlist remove MyGuildPlaylist https://www.youtube.com/watch?v=MN3x-kAbgFU - ​ ​ ​ ​ [p]playlist remove MyGlobalPlaylist https://www.youtube.com/watch?v=MN3x-kAbgFU - --scope Global - ​ ​ ​ ​ [p]playlist remove MyPersonalPlaylist https://www.youtube.com/watch?v=MN3x-kAbgFU - --scope User + ​ ​ ​ ​ `[p]playlist remove MyGuildPlaylist https://www.youtube.com/watch?v=MN3x-kAbgFU` + ​ ​ ​ ​ `[p]playlist remove MyGlobalPlaylist https://www.youtube.com/watch?v=MN3x-kAbgFU --scope Global` + ​ ​ ​ ​ `[p]playlist remove MyPersonalPlaylist https://www.youtube.com/watch?v=MN3x-kAbgFU --scope User` """ if scope_data is None: scope_data = [PlaylistScope.GUILD.value, ctx.author, ctx.guild, False] @@ -5215,7 +5213,7 @@ class Audio(commands.Cog): """Save a playlist from a url. **Usage**: - ​ ​ ​ ​ [p]playlist save name url args + ​ ​ ​ ​ `[p]playlist save name url [args]` **Args**: ​ ​ ​ ​ The following are all optional: @@ -5238,12 +5236,9 @@ class Audio(commands.Cog): ​ ​ ​ ​ Exact guild name Example use: - ​ ​ ​ ​ [p]playlist save MyGuildPlaylist - https://www.youtube.com/playlist?list=PLx0sYbCqOb8Q_CLZC2BdBSKEEB59BOPUM - ​ ​ ​ ​ [p]playlist save MyGlobalPlaylist - https://www.youtube.com/playlist?list=PLx0sYbCqOb8Q_CLZC2BdBSKEEB59BOPUM --scope Global - ​ ​ ​ ​ [p]playlist save MyPersonalPlaylist - https://open.spotify.com/playlist/1RyeIbyFeIJVnNzlGr5KkR --scope User + ​ ​ ​ ​ `[p]playlist save MyGuildPlaylist https://www.youtube.com/playlist?list=PLx0sYbCqOb8Q_CLZC2BdBSKEEB59BOPUM` + ​ ​ ​ ​ `[p]playlist save MyGlobalPlaylist https://www.youtube.com/playlist?list=PLx0sYbCqOb8Q_CLZC2BdBSKEEB59BOPUM --scope Global` + ​ ​ ​ ​ `[p]playlist save MyPersonalPlaylist https://open.spotify.com/playlist/1RyeIbyFeIJVnNzlGr5KkR --scope User` """ if scope_data is None: scope_data = [PlaylistScope.GUILD.value, ctx.author, ctx.guild, False] @@ -5313,7 +5308,7 @@ class Audio(commands.Cog): """Load a playlist into the queue. **Usage**: - ​ ​ ​ ​ [p]playlist start playlist_name_OR_id args + ​ ​ ​ ​` [p]playlist start playlist_name_OR_id [args]` **Args**: ​ ​ ​ ​ The following are all optional: @@ -5336,9 +5331,9 @@ class Audio(commands.Cog): ​ ​ ​ ​ Exact guild name Example use: - ​ ​ ​ ​ [p]playlist start MyGuildPlaylist - ​ ​ ​ ​ [p]playlist start MyGlobalPlaylist --scope Global - ​ ​ ​ ​ [p]playlist start MyPersonalPlaylist --scope User + ​ ​ ​ ​ `[p]playlist start MyGuildPlaylist` + ​ ​ ​ ​ `[p]playlist start MyGlobalPlaylist --scope Global` + ​ ​ ​ ​ `[p]playlist start MyPersonalPlaylist --scope User` """ if scope_data is None: scope_data = [PlaylistScope.GUILD.value, ctx.author, ctx.guild, False] @@ -5483,7 +5478,7 @@ class Audio(commands.Cog): """Updates all tracks in a playlist. **Usage**: - ​ ​ ​ ​ [p]playlist update playlist_name_OR_id args + ​ ​ ​ ​ `[p]playlist update playlist_name_OR_id [args]` **Args**: ​ ​ ​ ​ The following are all optional: @@ -5506,9 +5501,9 @@ class Audio(commands.Cog): ​ ​ ​ ​ Exact guild name Example use: - ​ ​ ​ ​ [p]playlist update MyGuildPlaylist - ​ ​ ​ ​ [p]playlist update MyGlobalPlaylist --scope Global - ​ ​ ​ ​ [p]playlist update MyPersonalPlaylist --scope User + ​ ​ ​ ​ `[p]playlist update MyGuildPlaylist` + ​ ​ ​ ​ `[p]playlist update MyGlobalPlaylist --scope Global` + ​ ​ ​ ​ `[p]playlist update MyPersonalPlaylist --scope User` """ if scope_data is None: @@ -5631,10 +5626,10 @@ class Audio(commands.Cog): """Uploads a playlist file as a playlist for the bot. V2 and old V3 playlist will be slow. - V3 Playlist made with [p]playlist download will load a lot faster. + V3 Playlist made with `[p]playlist download` will load a lot faster. **Usage**: - ​ ​ ​ ​ [p]playlist upload args + ​ ​ ​ ​ `[p]playlist upload [args]` **Args**: ​ ​ ​ ​ The following are all optional: @@ -5657,9 +5652,9 @@ class Audio(commands.Cog): ​ ​ ​ ​ Exact guild name Example use: - ​ ​ ​ ​ [p]playlist upload - ​ ​ ​ ​ [p]playlist upload --scope Global - ​ ​ ​ ​ [p]playlist upload --scope User + ​ ​ ​ ​ `[p]playlist upload` + ​ ​ ​ ​ `[p]playlist upload --scope Global` + ​ ​ ​ ​ `[p]playlist upload --scope User` """ if scope_data is None: scope_data = [PlaylistScope.GUILD.value, ctx.author, ctx.guild, False] @@ -5763,7 +5758,7 @@ class Audio(commands.Cog): """Rename an existing playlist. **Usage**: - ​ ​ ​ ​ [p]playlist rename playlist_name_OR_id new_name args + ​ ​ ​ ​ `[p]playlist rename playlist_name_OR_id new_name [args]` **Args**: ​ ​ ​ ​ The following are all optional: @@ -5786,9 +5781,9 @@ class Audio(commands.Cog): ​ ​ ​ ​ Exact guild name Example use: - ​ ​ ​ ​ [p]playlist rename MyGuildPlaylist RenamedGuildPlaylist - ​ ​ ​ ​ [p]playlist rename MyGlobalPlaylist RenamedGlobalPlaylist --scope Global - ​ ​ ​ ​ [p]playlist rename MyPersonalPlaylist RenamedPersonalPlaylist --scope User + ​ ​ ​ ​ `[p]playlist rename MyGuildPlaylist RenamedGuildPlaylist` + ​ ​ ​ ​ `[p]playlist rename MyGlobalPlaylist RenamedGlobalPlaylist --scope Global` + ​ ​ ​ ​ `[p]playlist rename MyPersonalPlaylist RenamedPersonalPlaylist --scope User` """ if scope_data is None: scope_data = [PlaylistScope.GUILD.value, ctx.author, ctx.guild, False] @@ -6818,8 +6813,8 @@ class Audio(commands.Cog): async def search(self, ctx: commands.Context, *, query: str): """Pick a track with a search. - Use `[p]search list ` to queue all tracks found on YouTube. `[p]search sc - ` will search SoundCloud instead of YouTube. + Use `[p]search list ` to queue all tracks found on YouTube. + `[p]search sc` will search SoundCloud instead of YouTube. """ async def _search_menu( @@ -7382,8 +7377,8 @@ class Audio(commands.Cog): async def _shuffle_bumpped(self, ctx: commands.Context): """Toggle bumped track shuffle. - Set this to disabled if you wish to avoid bumped songs being shuffled. This takes priority - over `[p]shuffle`. + Set this to disabled if you wish to avoid bumped songs being shuffled. + This takes priority over `[p]shuffle`. """ dj_enabled = self._dj_status_cache.setdefault( ctx.guild.id, await self.config.guild(ctx.guild).dj_enabled() From d1b7f836db4769790de9963c0c89a27167478b0f Mon Sep 17 00:00:00 2001 From: jack1142 <6032823+jack1142@users.noreply.github.com> Date: Fri, 17 Jan 2020 22:45:41 +0100 Subject: [PATCH 37/49] Update auto_labeler.yml (#3396) --- .github/workflows/auto_labeler.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/auto_labeler.yml b/.github/workflows/auto_labeler.yml index 5440e4e33..e4b3bc9f2 100644 --- a/.github/workflows/auto_labeler.yml +++ b/.github/workflows/auto_labeler.yml @@ -13,9 +13,14 @@ jobs: with: github-token: ${{secrets.GITHUB_TOKEN}} script: | + const is_status_label = (label) => label.name.startsWith('Status: '); + if (context.payload.issue.labels.some(is_status_label)) { + console.log('Issue already has Status label, skipping...'); + return; + } github.issues.addLabels({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, labels: ['Status: Needs Triage'] - }) + }); From cd7f4681a48c18a52bc825b232d2c1335bc384bb Mon Sep 17 00:00:00 2001 From: Michael H Date: Fri, 17 Jan 2020 16:49:25 -0500 Subject: [PATCH 38/49] Cache prefixes (#3150) * Cache prefixes - This works towards #3148 - Ends up centralizing some logic - Including that prefixes should be a reverse sorted list * handle global prefix attempts at none * fix prefix set for server * cache using guild id --- changelog.d/3148.misc.1.rst | 1 + redbot/core/bot.py | 36 +++++++++-------------- redbot/core/cli.py | 4 ++- redbot/core/core_commands.py | 15 ++++------ redbot/core/settings_caches.py | 53 ++++++++++++++++++++++++++++++++++ 5 files changed, 77 insertions(+), 32 deletions(-) create mode 100644 changelog.d/3148.misc.1.rst create mode 100644 redbot/core/settings_caches.py diff --git a/changelog.d/3148.misc.1.rst b/changelog.d/3148.misc.1.rst new file mode 100644 index 000000000..6e966e714 --- /dev/null +++ b/changelog.d/3148.misc.1.rst @@ -0,0 +1 @@ +Cache prefixes rather than lookup from config each time diff --git a/redbot/core/bot.py b/redbot/core/bot.py index 32b69f27d..9c7e56408 100644 --- a/redbot/core/bot.py +++ b/redbot/core/bot.py @@ -36,6 +36,8 @@ from .dev_commands import Dev from .events import init_events from .global_checks import init_global_checks +from .settings_caches import PrefixManager + from .rpc import RPCMixin from .utils import common_filters @@ -124,23 +126,13 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin): # pylint: d self._config.init_custom(SHARED_API_TOKENS, 2) self._config.register_custom(SHARED_API_TOKENS) + self._prefix_cache = PrefixManager(self._config, cli_flags) - async def prefix_manager(bot, message): - if not cli_flags.prefix: - global_prefix = await bot._config.prefix() - else: - global_prefix = cli_flags.prefix - if message.guild is None: - return global_prefix - server_prefix = await bot._config.guild(message.guild).prefix() + async def prefix_manager(bot, message) -> List[str]: + prefixes = await self._prefix_cache.get_prefixes(message.guild) if cli_flags.mentionable: - return ( - when_mentioned_or(*server_prefix)(bot, message) - if server_prefix - else when_mentioned_or(*global_prefix)(bot, message) - ) - else: - return server_prefix if server_prefix else global_prefix + return when_mentioned_or(*prefixes)(bot, message) + return prefixes if "command_prefix" not in kwargs: kwargs["command_prefix"] = prefix_manager @@ -273,15 +265,15 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin): # pylint: d """ This checks if a user or member is allowed to run things, as considered by Red's whitelist and blacklist. - + If given a user object, this function will check the global lists - + If given a member, this will additionally check guild lists - + If omiting a user or member, you must provide a value for ``who_id`` - + You may also provide a value for ``guild_id`` in this case - + If providing a member by guild and member ids, you should supply ``role_ids`` as well @@ -289,7 +281,7 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin): # pylint: d ---------- who : Optional[Union[discord.Member, discord.User]] The user or member object to check - + Other Parameters ---------------- who_id : Optional[int] @@ -906,7 +898,7 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin): # pylint: d This should realistically only be used for responding using user provided input. (unfortunately, including usernames) Manually crafted messages which dont take any user input have no need of this - + Returns ------- discord.Message diff --git a/redbot/core/cli.py b/redbot/core/cli.py index 4778bb642..9c2575795 100644 --- a/redbot/core/cli.py +++ b/redbot/core/cli.py @@ -135,7 +135,9 @@ def parse_cli_flags(args): "security implications if misused. Can be " "multiple.", ) - parser.add_argument("--prefix", "-p", action="append", help="Global prefix. Can be multiple") + parser.add_argument( + "--prefix", "-p", action="append", help="Global prefix. Can be multiple", default=[] + ) parser.add_argument( "--no-prompt", action="store_true", diff --git a/redbot/core/core_commands.py b/redbot/core/core_commands.py index 4a05cea70..02ed1b738 100644 --- a/redbot/core/core_commands.py +++ b/redbot/core/core_commands.py @@ -257,10 +257,9 @@ class CoreLogic: The current (or new) list of prefixes. """ if prefixes: - prefixes = sorted(prefixes, reverse=True) - await self.bot._config.prefix.set(prefixes) + await self.bot._prefix_cache.set_prefixes(guild=None, prefixes=prefixes) return prefixes - return await self.bot._config.prefix() + return await self.bot._prefix_cache.get_prefixes(guild=None) @classmethod async def _version_info(cls) -> Dict[str, str]: @@ -847,15 +846,13 @@ class Core(commands.Cog, CoreLogic): mod_role_ids = await ctx.bot._config.guild(ctx.guild).mod_role() mod_role_names = [r.name for r in guild.roles if r.id in mod_role_ids] mod_roles_str = humanize_list(mod_role_names) if mod_role_names else "Not Set." - prefixes = await ctx.bot._config.guild(ctx.guild).prefix() guild_settings = _("Admin roles: {admin}\nMod roles: {mod}\n").format( admin=admin_roles_str, mod=mod_roles_str ) else: guild_settings = "" - prefixes = None # This is correct. The below can happen in a guild. - if not prefixes: - prefixes = await ctx.bot._config.prefix() + + prefixes = await ctx.bot._prefix_cache.get_prefixes(ctx.guild) locale = await ctx.bot._config.locale() prefix_string = " ".join(prefixes) @@ -1182,11 +1179,11 @@ class Core(commands.Cog, CoreLogic): async def serverprefix(self, ctx: commands.Context, *prefixes: str): """Sets Red's server prefix(es)""" if not prefixes: - await ctx.bot._config.guild(ctx.guild).prefix.set([]) + await ctx.bot._prefix_cache.set_prefixes(guild=ctx.guild, prefixes=[]) await ctx.send(_("Guild prefixes have been reset.")) return prefixes = sorted(prefixes, reverse=True) - await ctx.bot._config.guild(ctx.guild).prefix.set(prefixes) + await ctx.bot._prefix_cache.set_prefixes(guild=ctx.guild, prefixes=prefixes) await ctx.send(_("Prefix set.")) @_set.command() diff --git a/redbot/core/settings_caches.py b/redbot/core/settings_caches.py new file mode 100644 index 000000000..32c4c5d52 --- /dev/null +++ b/redbot/core/settings_caches.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +from typing import Dict, List, Optional +from argparse import Namespace + +import discord + +from .config import Config + + +class PrefixManager: + def __init__(self, config: Config, cli_flags: Namespace): + self._config: Config = config + self._global_prefix_overide: Optional[List[str]] = sorted( + cli_flags.prefix, reverse=True + ) or None + self._cached: Dict[Optional[int], List[str]] = {} + + async def get_prefixes(self, guild: Optional[discord.Guild] = None) -> List[str]: + ret: List[str] + + gid: Optional[int] = guild.id if guild else None + + if gid in self._cached: + ret = self._cached[gid].copy() + else: + if gid is not None: + ret = await self._config.guild_from_id(gid).prefix() + if not ret: + ret = await self.get_prefixes(None) + else: + ret = self._global_prefix_overide or (await self._config.prefix()) + + self._cached[gid] = ret.copy() + + return ret + + async def set_prefixes( + self, guild: Optional[discord.Guild] = None, prefixes: Optional[List[str]] = None + ): + gid: Optional[int] = guild.id if guild else None + prefixes = prefixes or [] + if not isinstance(prefixes, list) and not all(isinstance(pfx, str) for pfx in prefixes): + raise TypeError("Prefixes must be a list of strings") + prefixes = sorted(prefixes, reverse=True) + if gid is None: + if not prefixes: + raise ValueError("You must have at least one prefix.") + self._cached.clear() + await self._config.prefix.set(prefixes) + else: + del self._cached[gid] + await self._config.guild_from_id(gid).prefix.set(prefixes) From 41b283ce5dfe6f515ea377e79971a14170e06cfa Mon Sep 17 00:00:00 2001 From: Draper <27962761+Drapersniper@users.noreply.github.com> Date: Fri, 17 Jan 2020 22:00:29 +0000 Subject: [PATCH 39/49] [Audio] Show correct time remaining for bumped tracks (#3375) * Fixes Bump play * *sigh* --- changelog.d/audio/3373.bugfix.1.rst | 1 + redbot/cogs/audio/audio.py | 3 +-- redbot/cogs/audio/audio_dataclasses.py | 2 +- redbot/cogs/audio/utils.py | 15 +++++++++++++++ 4 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 changelog.d/audio/3373.bugfix.1.rst diff --git a/changelog.d/audio/3373.bugfix.1.rst b/changelog.d/audio/3373.bugfix.1.rst new file mode 100644 index 000000000..3f421e0c7 --- /dev/null +++ b/changelog.d/audio/3373.bugfix.1.rst @@ -0,0 +1 @@ +``[p]bumpplay`` now shows the current remaining time until the bumped track is played. \ No newline at end of file diff --git a/redbot/cogs/audio/audio.py b/redbot/cogs/audio/audio.py index 19ad5e744..259d60e62 100644 --- a/redbot/cogs/audio/audio.py +++ b/redbot/cogs/audio/audio.py @@ -2957,8 +2957,7 @@ class Audio(commands.Cog): return await self._embed_msg(ctx, embed=embed) elif isinstance(tracks, discord.Message): return - queue_dur = await queue_duration(ctx) - lavalink.utils.format_time(queue_dur) + queue_dur = await track_remaining_duration(ctx) index = query.track_index seek = 0 if query.start_time: diff --git a/redbot/cogs/audio/audio_dataclasses.py b/redbot/cogs/audio/audio_dataclasses.py index d31cbbc9b..32eb3ef2b 100644 --- a/redbot/cogs/audio/audio_dataclasses.py +++ b/redbot/cogs/audio/audio_dataclasses.py @@ -211,7 +211,7 @@ class LocalPath: def tracks_in_tree(self): tracks = [] - for track in self.multirglob(*[f"*{ext}" for ext in self._all_music_ext]): + for track in self.multirglob(*[f"{ext}" for ext in self._all_music_ext]): if track.exists() and track.is_file() and track.parent != self.localtrack_folder: tracks.append(Query.process_input(LocalPath(str(track.absolute())))) return sorted(tracks, key=lambda x: x.to_string_user().lower()) diff --git a/redbot/cogs/audio/utils.py b/redbot/cogs/audio/utils.py index 2886f4996..79a0b00e7 100644 --- a/redbot/cogs/audio/utils.py +++ b/redbot/cogs/audio/utils.py @@ -43,6 +43,7 @@ __all__ = [ "CacheLevel", "format_playlist_picker_data", "get_track_description_unformatted", + "track_remaining_duration", "Notifier", "PlaylistScope", ] @@ -126,6 +127,20 @@ async def queue_duration(ctx) -> int: return queue_total_duration +async def track_remaining_duration(ctx) -> int: + player = lavalink.get_player(ctx.guild.id) + if not player.current: + return 0 + try: + if not player.current.is_stream: + remain = player.current.length - player.position + else: + remain = 0 + except AttributeError: + remain = 0 + return remain + + async def draw_time(ctx) -> str: player = lavalink.get_player(ctx.guild.id) paused = player.paused From b88bd5d44d327426b98fb63fa0888c34768fb352 Mon Sep 17 00:00:00 2001 From: Michael H Date: Fri, 17 Jan 2020 17:07:34 -0500 Subject: [PATCH 40/49] More exit tweaks (#3392) --- redbot/__main__.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/redbot/__main__.py b/redbot/__main__.py index 77c01ba52..2bf085f7d 100644 --- a/redbot/__main__.py +++ b/redbot/__main__.py @@ -490,14 +490,13 @@ def main(): # Allows transports to close properly, and prevent new ones from being opened. # Transports may still not be closed correcly on windows, see below loop.run_until_complete(loop.shutdown_asyncgens()) - if os.name == "nt": - # *we* aren't cleaning up more here, but it prevents - # a runtime error at the event loop on windows - # with resources which require longer to clean up. - # With other event loops, a failure to cleanup prior to here - # results in a resource warning instead and does not break us. - log.info("Please wait, cleaning up a bit more") - loop.run_until_complete(asyncio.sleep(1)) + # *we* aren't cleaning up more here, but it prevents + # a runtime error at the event loop on windows + # with resources which require longer to clean up. + # With other event loops, a failure to cleanup prior to here + # results in a resource warning instead + log.info("Please wait, cleaning up a bit more") + loop.run_until_complete(asyncio.sleep(2)) loop.stop() loop.close() exit_code = red._shutdown_mode if red is not None else 1 From 2c12e4f6bf9c384ad748680a4c77953f52900968 Mon Sep 17 00:00:00 2001 From: Draper <27962761+Drapersniper@users.noreply.github.com> Date: Fri, 17 Jan 2020 22:07:49 +0000 Subject: [PATCH 41/49] [Audio] Show symbolic link folders (#3376) * Fixes Bump play * Fixed #3332 * Revert "Fixed #3332" This reverts commit d76d3acb * Revert "Fixes Bump play" This reverts commit 3839bdaf * *sigh* * *sigh* * *sigh* * use iglob + async iterator Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * black Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * + fixes Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> --- redbot/cogs/audio/audio.py | 14 ++- redbot/cogs/audio/audio_dataclasses.py | 154 +++++++++++++++---------- 2 files changed, 105 insertions(+), 63 deletions(-) diff --git a/redbot/cogs/audio/audio.py b/redbot/cogs/audio/audio.py index 259d60e62..99714daf6 100644 --- a/redbot/cogs/audio/audio.py +++ b/redbot/cogs/audio/audio.py @@ -2452,7 +2452,11 @@ class Audio(commands.Cog): if not await self._localtracks_check(ctx): return - return audio_data.subfolders_in_tree() if search_subfolders else audio_data.subfolders() + return ( + await audio_data.subfolders_in_tree() + if search_subfolders + else await audio_data.subfolders() + ) async def _folder_list( self, ctx: commands.Context, query: audio_dataclasses.Query @@ -2463,9 +2467,9 @@ class Audio(commands.Cog): if not query.track.exists(): return return ( - query.track.tracks_in_tree() + await query.track.tracks_in_tree() if query.search_subfolders - else query.track.tracks_in_folder() + else await query.track.tracks_in_folder() ) async def _folder_tracks( @@ -2504,9 +2508,9 @@ class Audio(commands.Cog): return return ( - query.track.tracks_in_tree() + await query.track.tracks_in_tree() if query.search_subfolders - else query.track.tracks_in_folder() + else await query.track.tracks_in_folder() ) async def _localtracks_check(self, ctx: commands.Context) -> bool: diff --git a/redbot/cogs/audio/audio_dataclasses.py b/redbot/cogs/audio/audio_dataclasses.py index 32eb3ef2b..21c79e14f 100644 --- a/redbot/cogs/audio/audio_dataclasses.py +++ b/redbot/cogs/audio/audio_dataclasses.py @@ -1,9 +1,12 @@ +import asyncio +import contextlib +import glob import ntpath import os import posixpath import re from pathlib import Path, PosixPath, WindowsPath -from typing import List, Optional, Union, MutableMapping +from typing import List, Optional, Union, MutableMapping, Iterator, AsyncIterator from urllib.parse import urlparse import lavalink @@ -167,29 +170,48 @@ class LocalPath: modified.path = modified.path.joinpath(*args) return modified - def multiglob(self, *patterns): - paths = [] + def rglob(self, pattern, folder=False) -> Iterator[str]: + if folder: + return glob.iglob(f"{self.path}{os.sep}**{os.sep}", recursive=True) + else: + return glob.iglob(f"{self.path}{os.sep}**{os.sep}{pattern}", recursive=True) + + def glob(self, pattern, folder=False) -> Iterator[str]: + if folder: + return glob.iglob(f"{self.path}{os.sep}*{os.sep}", recursive=False) + else: + return glob.iglob(f"{self.path}{os.sep}*{pattern}", recursive=False) + + async def multiglob(self, *patterns, folder=False) -> AsyncIterator["LocalPath"]: for p in patterns: - paths.extend(list(self.path.glob(p))) - for p in self._filtered(paths): - yield p + for rp in self.glob(p): + rp = LocalPath(rp) + if folder and rp.is_dir() and rp.exists(): + yield rp + await asyncio.sleep(0) + else: + if rp.suffix in self._all_music_ext and rp.is_file() and rp.exists(): + yield rp + await asyncio.sleep(0) - def multirglob(self, *patterns): - paths = [] + async def multirglob(self, *patterns, folder=False) -> AsyncIterator["LocalPath"]: for p in patterns: - paths.extend(list(self.path.rglob(p))) - - for p in self._filtered(paths): - yield p - - def _filtered(self, paths: List[Path]): - for p in paths: - if p.suffix in self._all_music_ext: - yield p + for rp in self.rglob(p): + rp = LocalPath(rp) + if folder and rp.is_dir() and rp.exists(): + yield rp + await asyncio.sleep(0) + else: + if rp.suffix in self._all_music_ext and rp.is_file() and rp.exists(): + yield rp + await asyncio.sleep(0) def __str__(self): return self.to_string() + def __repr__(self): + return str(self) + def to_string(self): try: return str(self.path.absolute()) @@ -209,48 +231,56 @@ class LocalPath: string = f"...{os.sep}{string}" return string - def tracks_in_tree(self): + async def tracks_in_tree(self): tracks = [] - for track in self.multirglob(*[f"{ext}" for ext in self._all_music_ext]): - if track.exists() and track.is_file() and track.parent != self.localtrack_folder: - tracks.append(Query.process_input(LocalPath(str(track.absolute())))) + async for track in self.multirglob(*[f"{ext}" for ext in self._all_music_ext]): + with contextlib.suppress(ValueError): + if track.path.parent != self.localtrack_folder and track.path.relative_to( + self.path + ): + tracks.append(Query.process_input(track)) return sorted(tracks, key=lambda x: x.to_string_user().lower()) - def subfolders_in_tree(self): - files = list(self.multirglob(*[f"*{ext}" for ext in self._all_music_ext])) - folders = [] - for f in files: - if f.exists() and f.parent not in folders and f.parent != self.localtrack_folder: - folders.append(f.parent) + async def subfolders_in_tree(self): return_folders = [] - for folder in folders: - if folder.exists() and folder.is_dir(): - return_folders.append(LocalPath(str(folder.absolute()))) + async for f in self.multirglob("", folder=True): + with contextlib.suppress(ValueError): + if ( + f not in return_folders + and f.path != self.localtrack_folder + and f.path.relative_to(self.path) + ): + return_folders.append(f) return sorted(return_folders, key=lambda x: x.to_string_user().lower()) - def tracks_in_folder(self): + async def tracks_in_folder(self): tracks = [] - for track in self.multiglob(*[f"*{ext}" for ext in self._all_music_ext]): - if track.exists() and track.is_file() and track.parent != self.localtrack_folder: - tracks.append(Query.process_input(LocalPath(str(track.absolute())))) + async for track in self.multiglob(*[f"{ext}" for ext in self._all_music_ext]): + with contextlib.suppress(ValueError): + if track.path.parent != self.localtrack_folder and track.path.relative_to( + self.path + ): + tracks.append(Query.process_input(track)) return sorted(tracks, key=lambda x: x.to_string_user().lower()) - def subfolders(self): - files = list(self.multiglob(*[f"*{ext}" for ext in self._all_music_ext])) - folders = [] - for f in files: - if f.exists() and f.parent not in folders and f.parent != self.localtrack_folder: - folders.append(f.parent) + async def subfolders(self): return_folders = [] - for folder in folders: - if folder.exists() and folder.is_dir(): - return_folders.append(LocalPath(str(folder.absolute()))) + async for f in self.multiglob("", folder=True): + with contextlib.suppress(ValueError): + if ( + f not in return_folders + and f.path != self.localtrack_folder + and f.path.relative_to(self.path) + ): + return_folders.append(f) return sorted(return_folders, key=lambda x: x.to_string_user().lower()) def __eq__(self, other): - if not isinstance(other, LocalPath): - return NotImplemented - return self.path._cparts == other.path._cparts + if isinstance(other, LocalPath): + return self.path._cparts == other.path._cparts + elif isinstance(other, Path): + return self.path._cparts == other._cpart + return NotImplemented def __hash__(self): try: @@ -260,24 +290,32 @@ class LocalPath: return self._hash def __lt__(self, other): - if not isinstance(other, LocalPath): - return NotImplemented - return self.path._cparts < other.path._cparts + if isinstance(other, LocalPath): + return self.path._cparts < other.path._cparts + elif isinstance(other, Path): + return self.path._cparts < other._cpart + return NotImplemented def __le__(self, other): - if not isinstance(other, LocalPath): - return NotImplemented - return self.path._cparts <= other.path._cparts + if isinstance(other, LocalPath): + return self.path._cparts <= other.path._cparts + elif isinstance(other, Path): + return self.path._cparts <= other._cpart + return NotImplemented def __gt__(self, other): - if not isinstance(other, LocalPath): - return NotImplemented - return self.path._cparts > other.path._cparts + if isinstance(other, LocalPath): + return self.path._cparts > other.path._cparts + elif isinstance(other, Path): + return self.path._cparts > other._cpart + return NotImplemented def __ge__(self, other): - if not isinstance(other, LocalPath): - return NotImplemented - return self.path._cparts >= other.path._cparts + if isinstance(other, LocalPath): + return self.path._cparts >= other.path._cparts + elif isinstance(other, Path): + return self.path._cparts >= other._cpart + return NotImplemented class Query: From d52f8974fdf011100d9206537606651147fd9b0e Mon Sep 17 00:00:00 2001 From: Michael H Date: Fri, 17 Jan 2020 17:59:37 -0500 Subject: [PATCH 42/49] Stop special casing help in bot.embed_requested (#3382) - However, we are not changing the signature - This was previously special cased for reasons related to the older version of the help formatter we used and never re-evaluated for need. - We should leave the signature as is both for lack of breaking, and for potential future changes // actually this was already done once in GH-2966 but got accidentally overwritten --- redbot/core/bot.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/redbot/core/bot.py b/redbot/core/bot.py index 9c7e56408..5ad380f62 100644 --- a/redbot/core/bot.py +++ b/redbot/core/bot.py @@ -614,9 +614,7 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin): # pylint: d bool :code:`True` if an embed is requested """ - if isinstance(channel, discord.abc.PrivateChannel) or ( - command and command == self.get_command("help") - ): + if isinstance(channel, discord.abc.PrivateChannel): user_setting = await self._config.user(user).embeds() if user_setting is not None: return user_setting @@ -624,6 +622,7 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin): # pylint: d guild_setting = await self._config.guild(channel.guild).embeds() if guild_setting is not None: return guild_setting + global_setting = await self._config.embeds() return global_setting From 7f2e5a0b700bb4f90cfa9e5d6cb0ca1fed2c0287 Mon Sep 17 00:00:00 2001 From: Michael H Date: Fri, 17 Jan 2020 18:00:24 -0500 Subject: [PATCH 43/49] [Docs] Remaining doc improvements for 3.2.3 (#3400) * double the fun * double * pluralize this --- docs/framework_apikeys.rst | 13 +++++++++++++ docs/install_linux_mac.rst | 4 ++++ docs/install_windows.rst | 4 ++++ 3 files changed, 21 insertions(+) diff --git a/docs/framework_apikeys.rst b/docs/framework_apikeys.rst index b27d2e713..2a67d9c45 100644 --- a/docs/framework_apikeys.rst +++ b/docs/framework_apikeys.rst @@ -60,3 +60,16 @@ Event Reference :type service_name: :class:`str` :param api_tokens: New Mapping of token names to tokens. This contains api tokens that weren't changed too. :type api_tokens: Mapping[:class:`str`, :class:`str`] + + +********************* +Additional References +********************* + +.. py:currentmodule:: redbot.core.bot + +.. automethod:: Red.get_shared_api_tokens + +.. automethod:: Red.set_shared_api_tokens + +.. automethod:: Red.remove_shared_api_tokens diff --git a/docs/install_linux_mac.rst b/docs/install_linux_mac.rst index 1b7dccfea..c21e7bfe6 100644 --- a/docs/install_linux_mac.rst +++ b/docs/install_linux_mac.rst @@ -355,6 +355,10 @@ Or, to install with PostgreSQL support: python -m pip install -U Red-DiscordBot[postgres] +.. note:: + + These commands are also used for updating Red + -------------------------- Setting Up and Running Red -------------------------- diff --git a/docs/install_windows.rst b/docs/install_windows.rst index 36fcc11fa..05e8c60d9 100644 --- a/docs/install_windows.rst +++ b/docs/install_windows.rst @@ -96,6 +96,10 @@ Installing Red python -m pip install -U Red-DiscordBot[postgres] +.. note:: + + These commands are also used for updating Red + -------------------------- Setting Up and Running Red -------------------------- From 6219f0da67c74dd52401d93484605ee7328e66f5 Mon Sep 17 00:00:00 2001 From: Michael H Date: Fri, 17 Jan 2020 18:51:49 -0500 Subject: [PATCH 44/49] [Modlog API] Add resolution for people inpacted by bad casetypes (#3333) * add resolution for people inpacted by bad casetypes * *some* amount of notice on this * Fine. * clearer warnings * actually, unnneded --- redbot/cogs/modlog/modlog.py | 7 +++++++ redbot/core/modlog.py | 32 +++++++++++++++++++++----------- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/redbot/cogs/modlog/modlog.py b/redbot/cogs/modlog/modlog.py index 0ab2f85c6..3c9f1ba55 100644 --- a/redbot/cogs/modlog/modlog.py +++ b/redbot/cogs/modlog/modlog.py @@ -26,6 +26,13 @@ class ModLog(commands.Cog): """Manage modlog settings.""" pass + @checks.is_owner() + @modlogset.command(hidden=True, name="fixcasetypes") + async def reapply_audittype_migration(self, ctx: commands.Context): + """Command to fix misbehaving casetypes.""" + await modlog.handle_auditype_key() + await ctx.tick() + @modlogset.command() @commands.guild_only() async def modlog(self, ctx: commands.Context, channel: discord.TextChannel = None): diff --git a/redbot/core/modlog.py b/redbot/core/modlog.py index 5758b9b33..700203497 100644 --- a/redbot/core/modlog.py +++ b/redbot/core/modlog.py @@ -142,6 +142,18 @@ async def _init(bot: Red): bot.add_listener(on_member_unban) +async def handle_auditype_key(): + all_casetypes = { + casetype_name: { + inner_key: inner_value + for inner_key, inner_value in casetype_data.items() + if inner_key != "audit_type" + } + for casetype_name, casetype_data in (await _conf.custom(_CASETYPES).all()).items() + } + await _conf.custom(_CASETYPES).set(all_casetypes) + + async def _migrate_config(from_version: int, to_version: int): if from_version == to_version: return @@ -170,16 +182,7 @@ async def _migrate_config(from_version: int, to_version: int): await _conf.guild(cast(discord.Guild, discord.Object(id=guild_id))).clear_raw("cases") if from_version < 3 <= to_version: - all_casetypes = { - casetype_name: { - inner_key: inner_value - for inner_key, inner_value in casetype_data.items() - if inner_key != "audit_type" - } - for casetype_name, casetype_data in (await _conf.custom(_CASETYPES).all()).items() - } - - await _conf.custom(_CASETYPES).set(all_casetypes) + await handle_auditype_key() await _conf.schema_version.set(3) if from_version < 4 <= to_version: @@ -507,8 +510,15 @@ class CaseType: self.image = image self.case_str = case_str self.guild = guild + + if "audit_type" in kwargs: + kwargs.pop("audit_type", None) + log.warning( + "Fix this using the hidden command: `modlogset fixcasetypes` in Discord: " + "Got outdated key in casetype: audit_type" + ) if kwargs: - log.warning("Got unexpected keys in case %s", ",".join(kwargs.keys())) + log.warning("Got unexpected key(s) in casetype: %s", ",".join(kwargs.keys())) async def to_json(self): """Transforms the case type into a dict and saves it""" From 66cae71d90f64399ee3913ca0ac1cf96c83ffb2d Mon Sep 17 00:00:00 2001 From: Stonedestroyer <1307729+Stonedestroyer@users.noreply.github.com> Date: Sat, 18 Jan 2020 01:05:39 +0100 Subject: [PATCH 45/49] [Docs] Changed python version references on docs (#3402) * [Docs] Changes Python references to Python 3.8 * [Misc] Remove obsolete mention to Zenhub --- .github/CONTRIBUTING.md | 9 ++++----- README.md | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 292dcd86f..403bb1eb8 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -30,9 +30,8 @@ Red is an open source project. This means that each and every one of the develop We love receiving contributions from our community. Any assistance you can provide with regards to bug fixes, feature enhancements, and documentation is more than welcome. # 2. Ground Rules -We've made a point to use [ZenHub](https://www.zenhub.com/) (a plugin for GitHub) as our main source of collaboration and coordination. Your experience contributing to Red will be greatly improved if you go get that plugin. 1. Ensure cross compatibility for Windows, Mac OS and Linux. -2. Ensure all Python features used in contributions exist and work in Python 3.7 and above. +2. Ensure all Python features used in contributions exist and work in Python 3.8.1 and above. 3. Create new tests for code you add or bugs you fix. It helps us help you by making sure we don't accidentally break anything :grinning: 4. Create any issues for new features you'd like to implement and explain why this feature is useful to everyone and not just you personally. 5. Don't add new cogs unless specifically given approval in an issue discussing said cog idea. @@ -54,7 +53,7 @@ Red's repository is configured to follow a particular development workflow, usin ### 4.1 Setting up your development environment The following requirements must be installed prior to setting up: - - Python 3.7.0 or greater + - Python 3.8.1 or greater - git - pip @@ -83,7 +82,7 @@ If you're not on Windows, you should also have GNU make installed, and you can o We've recently started using [tox](https://github.com/tox-dev/tox) to run all of our tests. It's extremely simple to use, and if you followed the previous section correctly, it is already installed to your virtual environment. Currently, tox does the following, creating its own virtual environments for each stage: -- Runs all of our unit tests with [pytest](https://github.com/pytest-dev/pytest) on python 3.7 (test environment `py37`) +- Runs all of our unit tests with [pytest](https://github.com/pytest-dev/pytest) on python 3.8 (test environment `py38`) - Ensures documentation builds without warnings, and all hyperlinks have a valid destination (test environment `docs`) - Ensures that the code meets our style guide with [black](https://github.com/ambv/black) (test environment `style`) @@ -107,7 +106,7 @@ You may have noticed we have a `Makefile` and a `make.bat` in the top-level dire The other make recipes are most likely for project maintainers rather than contributors. -You can specify the Python executable used in the make recipes with the `PYTHON` environment variable, e.g. `make PYTHON=/usr/bin/python3.7 newenv`. +You can specify the Python executable used in the make recipes with the `PYTHON` environment variable, e.g. `make PYTHON=/usr/bin/python3.8 newenv`. ### 4.5 Keeping your dependencies up to date Whenever you pull from upstream (V3/develop on the main repository) and you notice either of the files `setup.cfg` or `tools/dev-requirements.txt` have been changed, it can often mean some package dependencies have been updated, added or removed. To make sure you're testing and formatting with the most up-to-date versions of our dependencies, run `make syncenv`. You could also simply do `make newenv` to install them to a clean new virtual environment. diff --git a/README.md b/README.md index 7d7fd9a6b..d65963747 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Support Red on Patreon! - Made with Python 3.7 + Made with Python 3.8 Localized with Crowdin From 33ea3a1419bb9d7a0dd6265add7a438ea514283c Mon Sep 17 00:00:00 2001 From: Michael H Date: Fri, 17 Jan 2020 19:47:22 -0500 Subject: [PATCH 46/49] version bump w/changelog (#3403) --- docs/changelog_3_2_0.rst | 63 ++++++++++++++++++++++++++++++++++++++++ redbot/__init__.py | 2 +- 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/docs/changelog_3_2_0.rst b/docs/changelog_3_2_0.rst index 43874ad1c..3ac5f6cf6 100644 --- a/docs/changelog_3_2_0.rst +++ b/docs/changelog_3_2_0.rst @@ -1,5 +1,68 @@ .. 3.2.x Changelogs +Redbot 3.2.3 (2020-01-17) +========================= + +Core Bot Changes +---------------- + +- Further improvements have been made to bot startup and shutdown. +- Prefixes are now cached for performance. +- Added a means for cog creators to use a global preinvoke hook. +- The bot now ensures it has at least the bare neccessary permissions before commands. +- Deleting instances works as intended again. +- Sinbad stopped fighting it and embraced the entrypoint madness. + +Core Commands +------------- + +- The servers command now also shows the ids. + +Admin Cog +--------- + +- The selfrole command now has reasonable expectations about hierarchy. + +Help Formatter +-------------- + +- ``[botname]`` is now replaced with the bot's display name in help text. +- New features added for cog creators to further customize help behavior. + + - Check out our command reference for details. +- Embed settings are now consistent. + +Downloader +---------- + +- Improved a few user facing messages. +- Added pagination of output on cog update. +- Added logging of failures. + +Docs +---- + +There's more detail to the below changes, so go read the docs. +For some reason, documenting documentation changes is hard. + +- Added instructions about git version +- Clarified instructions for installation and update. +- Added more detail to the API key reference. +- Fixed some typos and versioning mistakes. + + +Audio +----- + +Draper did things. + +- No seriously, Draper did things. +- Wait you wanted details? Ok, I guess we can share those. +- Audio properly disconnects with autodisconnect, even if notify is being used. +- Symbolic links now work as intended for local tracks. +- Bump play now shows the correct time till next track. +- Multiple user facing messages have been made more correct. + Redbot 3.2.2 (2020-01-10) ========================= diff --git a/redbot/__init__.py b/redbot/__init__.py index 8b08c74f4..0e625eb21 100644 --- a/redbot/__init__.py +++ b/redbot/__init__.py @@ -191,7 +191,7 @@ def _update_event_loop_policy(): _asyncio.set_event_loop_policy(_uvloop.EventLoopPolicy()) -__version__ = "3.2.3.dev1" +__version__ = "3.2.3" version_info = VersionInfo.from_str(__version__) # Filter fuzzywuzzy slow sequence matcher warning From b089be7b4975f0b0dc9ffa5f79a88ca9c01ff4cc Mon Sep 17 00:00:00 2001 From: Michael H Date: Fri, 17 Jan 2020 19:57:27 -0500 Subject: [PATCH 47/49] fixup docs (#3404) --- changelog.d/3148.misc.1.rst | 1 - changelog.d/3224.enhance.rst | 1 - changelog.d/3306.docs.rst | 1 - changelog.d/3335.enhance.rst | 1 - changelog.d/3368.docs.rst | 1 - changelog.d/3390.misc.rst | 1 - changelog.d/audio/3140.enhance.1.rst | 1 - changelog.d/audio/3328.hotfix.1.rst | 1 - changelog.d/audio/3328.hotfix.2.rst | 1 - changelog.d/audio/3337.misc.1.rst | 1 - changelog.d/audio/3337.misc.2.rst | 1 - changelog.d/audio/3342.enhance.1.rst | 2 -- changelog.d/audio/3344.enhance.1.rst | 1 - changelog.d/audio/3345.enhance.1.rst | 1 - changelog.d/audio/3349.bugfix.1.rst | 1 - changelog.d/audio/3373.bugfix.1.rst | 1 - changelog.d/warnings/2900.enhance.rst | 2 -- docs/framework_commands.rst | 1 + 18 files changed, 1 insertion(+), 19 deletions(-) delete mode 100644 changelog.d/3148.misc.1.rst delete mode 100644 changelog.d/3224.enhance.rst delete mode 100644 changelog.d/3306.docs.rst delete mode 100644 changelog.d/3335.enhance.rst delete mode 100644 changelog.d/3368.docs.rst delete mode 100644 changelog.d/3390.misc.rst delete mode 100644 changelog.d/audio/3140.enhance.1.rst delete mode 100644 changelog.d/audio/3328.hotfix.1.rst delete mode 100644 changelog.d/audio/3328.hotfix.2.rst delete mode 100644 changelog.d/audio/3337.misc.1.rst delete mode 100644 changelog.d/audio/3337.misc.2.rst delete mode 100644 changelog.d/audio/3342.enhance.1.rst delete mode 100644 changelog.d/audio/3344.enhance.1.rst delete mode 100644 changelog.d/audio/3345.enhance.1.rst delete mode 100644 changelog.d/audio/3349.bugfix.1.rst delete mode 100644 changelog.d/audio/3373.bugfix.1.rst delete mode 100644 changelog.d/warnings/2900.enhance.rst diff --git a/changelog.d/3148.misc.1.rst b/changelog.d/3148.misc.1.rst deleted file mode 100644 index 6e966e714..000000000 --- a/changelog.d/3148.misc.1.rst +++ /dev/null @@ -1 +0,0 @@ -Cache prefixes rather than lookup from config each time diff --git a/changelog.d/3224.enhance.rst b/changelog.d/3224.enhance.rst deleted file mode 100644 index 5dcacb085..000000000 --- a/changelog.d/3224.enhance.rst +++ /dev/null @@ -1 +0,0 @@ -Adds server IDs to servers command. \ No newline at end of file diff --git a/changelog.d/3306.docs.rst b/changelog.d/3306.docs.rst deleted file mode 100644 index e52352c5b..000000000 --- a/changelog.d/3306.docs.rst +++ /dev/null @@ -1 +0,0 @@ -Add "Fork me on GitHub" ribbon in top right corner of the docs page. \ No newline at end of file diff --git a/changelog.d/3335.enhance.rst b/changelog.d/3335.enhance.rst deleted file mode 100644 index 316decb0f..000000000 --- a/changelog.d/3335.enhance.rst +++ /dev/null @@ -1 +0,0 @@ -make typehints accessible to cog developers diff --git a/changelog.d/3368.docs.rst b/changelog.d/3368.docs.rst deleted file mode 100644 index d1f384e9b..000000000 --- a/changelog.d/3368.docs.rst +++ /dev/null @@ -1 +0,0 @@ -Update modlog documentation example to show "action_type" instead of "action". \ No newline at end of file diff --git a/changelog.d/3390.misc.rst b/changelog.d/3390.misc.rst deleted file mode 100644 index 9167a63c8..000000000 --- a/changelog.d/3390.misc.rst +++ /dev/null @@ -1 +0,0 @@ -Fixes a typo in redbot/core/commands/requires.py. \ No newline at end of file diff --git a/changelog.d/audio/3140.enhance.1.rst b/changelog.d/audio/3140.enhance.1.rst deleted file mode 100644 index 81570e412..000000000 --- a/changelog.d/audio/3140.enhance.1.rst +++ /dev/null @@ -1 +0,0 @@ -Update the help strings for ``[p]audioset emptydisconnect``. diff --git a/changelog.d/audio/3328.hotfix.1.rst b/changelog.d/audio/3328.hotfix.1.rst deleted file mode 100644 index 08e7ed7b9..000000000 --- a/changelog.d/audio/3328.hotfix.1.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed an attribute error that can be raised on play commands for spotify tracks. \ No newline at end of file diff --git a/changelog.d/audio/3328.hotfix.2.rst b/changelog.d/audio/3328.hotfix.2.rst deleted file mode 100644 index 039b506e0..000000000 --- a/changelog.d/audio/3328.hotfix.2.rst +++ /dev/null @@ -1 +0,0 @@ -Check data before it is inserted into the database to avoid corruption. \ No newline at end of file diff --git a/changelog.d/audio/3337.misc.1.rst b/changelog.d/audio/3337.misc.1.rst deleted file mode 100644 index f39071842..000000000 --- a/changelog.d/audio/3337.misc.1.rst +++ /dev/null @@ -1 +0,0 @@ -Removed a duplication of track search prefixes. \ No newline at end of file diff --git a/changelog.d/audio/3337.misc.2.rst b/changelog.d/audio/3337.misc.2.rst deleted file mode 100644 index 25985f91d..000000000 --- a/changelog.d/audio/3337.misc.2.rst +++ /dev/null @@ -1 +0,0 @@ -Changed and handled the `V2_COMPACT` LoadType to use the correct `V2_COMPAT` type. \ No newline at end of file diff --git a/changelog.d/audio/3342.enhance.1.rst b/changelog.d/audio/3342.enhance.1.rst deleted file mode 100644 index 38cab120a..000000000 --- a/changelog.d/audio/3342.enhance.1.rst +++ /dev/null @@ -1,2 +0,0 @@ -Reduce some cooldowns on playlist commands and stop them triggering before command parsing. - diff --git a/changelog.d/audio/3344.enhance.1.rst b/changelog.d/audio/3344.enhance.1.rst deleted file mode 100644 index 124a7b894..000000000 --- a/changelog.d/audio/3344.enhance.1.rst +++ /dev/null @@ -1 +0,0 @@ -Fixes the messages for playlists. \ No newline at end of file diff --git a/changelog.d/audio/3345.enhance.1.rst b/changelog.d/audio/3345.enhance.1.rst deleted file mode 100644 index 1a1767590..000000000 --- a/changelog.d/audio/3345.enhance.1.rst +++ /dev/null @@ -1 +0,0 @@ -Fix an issues with the formatting of non existing local tracks. \ No newline at end of file diff --git a/changelog.d/audio/3349.bugfix.1.rst b/changelog.d/audio/3349.bugfix.1.rst deleted file mode 100644 index 193d44958..000000000 --- a/changelog.d/audio/3349.bugfix.1.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed a bug where ``[p]audioset dc`` didn't disconnect the bot. \ No newline at end of file diff --git a/changelog.d/audio/3373.bugfix.1.rst b/changelog.d/audio/3373.bugfix.1.rst deleted file mode 100644 index 3f421e0c7..000000000 --- a/changelog.d/audio/3373.bugfix.1.rst +++ /dev/null @@ -1 +0,0 @@ -``[p]bumpplay`` now shows the current remaining time until the bumped track is played. \ No newline at end of file diff --git a/changelog.d/warnings/2900.enhance.rst b/changelog.d/warnings/2900.enhance.rst deleted file mode 100644 index 1c6f7599c..000000000 --- a/changelog.d/warnings/2900.enhance.rst +++ /dev/null @@ -1,2 +0,0 @@ -``[p]warnings`` can now be used by users that have the permission to use it from the Permissions cog, in order to check another user's warnings. -``[p]mywarnings`` can now be used by any user (instead of ``[p]warnings`` previously) to check their own warnings. diff --git a/docs/framework_commands.rst b/docs/framework_commands.rst index 5501cd03e..63e29f34a 100644 --- a/docs/framework_commands.rst +++ b/docs/framework_commands.rst @@ -15,6 +15,7 @@ extend functionlities used throughout the bot, as outlined below. .. autoclass:: redbot.core.commands.Command :members: + :inherited-members: format_help_for_context .. autoclass:: redbot.core.commands.Group :members: From 12da3bd89e8613e2364186365914352438aa12ae Mon Sep 17 00:00:00 2001 From: jack1142 <6032823+jack1142@users.noreply.github.com> Date: Sat, 18 Jan 2020 02:06:32 +0100 Subject: [PATCH 48/49] Update changelog_3_2_0.rst (#3405) --- docs/changelog_3_2_0.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/changelog_3_2_0.rst b/docs/changelog_3_2_0.rst index 3ac5f6cf6..4eabfb12e 100644 --- a/docs/changelog_3_2_0.rst +++ b/docs/changelog_3_2_0.rst @@ -8,8 +8,8 @@ Core Bot Changes - Further improvements have been made to bot startup and shutdown. - Prefixes are now cached for performance. -- Added a means for cog creators to use a global preinvoke hook. -- The bot now ensures it has at least the bare neccessary permissions before commands. +- Added the means for cog creators to use a global preinvoke hook. +- The bot now ensures it has at least the bare neccessary permissions before running commands. - Deleting instances works as intended again. - Sinbad stopped fighting it and embraced the entrypoint madness. @@ -29,7 +29,7 @@ Help Formatter - ``[botname]`` is now replaced with the bot's display name in help text. - New features added for cog creators to further customize help behavior. - - Check out our command reference for details. + - Check out our command reference for details on new ``format_help_for_context`` method. - Embed settings are now consistent. Downloader @@ -45,9 +45,9 @@ Docs There's more detail to the below changes, so go read the docs. For some reason, documenting documentation changes is hard. -- Added instructions about git version +- Added instructions about git version. - Clarified instructions for installation and update. -- Added more detail to the API key reference. +- Added more details to the API key reference. - Fixed some typos and versioning mistakes. From 826dae129e447b0bfbe5484831de33e9bafaefb8 Mon Sep 17 00:00:00 2001 From: Michael H Date: Fri, 17 Jan 2020 20:23:16 -0500 Subject: [PATCH 49/49] dev bump (#3406) --- redbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redbot/__init__.py b/redbot/__init__.py index 0e625eb21..a00782338 100644 --- a/redbot/__init__.py +++ b/redbot/__init__.py @@ -191,7 +191,7 @@ def _update_event_loop_policy(): _asyncio.set_event_loop_policy(_uvloop.EventLoopPolicy()) -__version__ = "3.2.3" +__version__ = "3.2.4.dev1" version_info = VersionInfo.from_str(__version__) # Filter fuzzywuzzy slow sequence matcher warning