Compare commits

...

16 Commits

Author SHA1 Message Date
Flame442
d8d3e9fceb [Trivia] Remove an unnecessary .format (#4175) 2020-08-10 01:17:44 +02:00
jack1142
b3281385e9 3.3.11 changelog (#4173)
* 3.3.11 changelog

* Update docs/changelog_3_3_0.rst

Co-authored-by: Flame442 <34169552+Flame442@users.noreply.github.com>

* Update docs/changelog_3_3_0.rst

Co-authored-by: Flame442 <34169552+Flame442@users.noreply.github.com>

* Update docs/changelog_3_3_0.rst

Co-authored-by: Flame442 <34169552+Flame442@users.noreply.github.com>

* Update docs/changelog_3_3_0.rst

Co-authored-by: Flame442 <34169552+Flame442@users.noreply.github.com>

Co-authored-by: Flame442 <34169552+Flame442@users.noreply.github.com>
2020-08-10 01:17:37 +02:00
jack1142
e6947bdbf6 Bump to 3.3.11 2020-08-10 01:10:35 +02:00
Douglas
a6d924221d [Trivia] Fix unresolved reference to bank.BalanceTooHigh (#4170)
Co-authored-by: douglas-cpp <douglasc.dev@gmail.com>
2020-08-10 00:50:22 +02:00
Vexed
cf7db1e891 Docs improvements after watching two self-proclaimed incompetent people install it (#4119)
* the thing

* right

* hmm

* review
2020-08-10 00:50:03 +02:00
jack1142
bc544d476b Fix the errors related to installed module having invalid commit data (#4086) 2020-08-10 00:49:17 +02:00
MeatyChunks
10976f218b [Economy] Prevent forbidden error when blocked by user (#4120)
Stop `[p]payouts` throwing a console error if the user has blocked the bot. Probably too spammy to put the payout message in chat.
2020-08-10 00:48:34 +02:00
jack1142
a589838a41 Only accept positive integers in [p]cleanup commands (#4115) 2020-08-10 00:48:26 +02:00
Ryan
e36d1f143d [CustomCom] Add missing await (#4108) 2020-08-10 00:47:36 +02:00
jack1142
9a6f78e62e Update chocolatey install commands per chocolatey.org (#4098) 2020-08-10 00:47:28 +02:00
Draper
649db87a8a [Audio] Ensure TrackEnqueueError is always handled (#3879)
Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com>
2020-08-10 00:43:20 +02:00
jack1142
c6f9a78d57 Update pyenv instructions to install Python 3.8.5 (#4094) 2020-08-10 00:35:30 +02:00
jack1142
bd89e4386d Fix no message when unregistered reason is used in [p]warn (#3840) 2020-08-10 00:34:26 +02:00
jack1142
a962f94a58 Ignore that the rule for model doesn't exist when trying to remove it (#4036) 2020-08-10 00:34:10 +02:00
jack1142
3471011f85 Fix the error for empty author list in [p]findcog (#4042) 2020-08-10 00:34:01 +02:00
Draper
186dbe6118 Lavalink.jar bump for internal manager (#4168) 2020-08-10 00:27:45 +02:00
20 changed files with 270 additions and 90 deletions

View File

@@ -1,5 +1,47 @@
.. 3.3.x Changelogs .. 3.3.x Changelogs
Redbot 3.3.11 (2020-08-10)
==========================
| Thanks to all these amazing people that contributed to this release:
| :ghuser:`douglas-cpp`, :ghuser:`Drapersniper`, :ghuser:`jack1142`, :ghuser:`MeatyChunks`, :ghuser:`Vexed01`, :ghuser:`yamikaitou`
End-user changelog
------------------
Audio
*****
- Audio should now work again on all voice regions (:issue:`4162`, :issue:`4168`)
- Removed an edge case where an unfriendly error message was sent in Audio cog (:issue:`3879`)
Cleanup
*******
- Fixed a bug causing ``[p]cleanup`` commands to clear all messages within last 2 weeks when ``0`` is passed as the amount of messages to delete (:issue:`4114`, :issue:`4115`)
CustomCommands
**************
- ``[p]cc show`` now sends an error message when command with the provided name couldn't be found (:issue:`4108`)
Downloader
**********
- ``[p]findcog`` no longer fails for 3rd-party cogs without any author (:issue:`4032`, :issue:`4042`)
- Update commands no longer crash when a different repo is added under a repo name that was once used (:issue:`4086`)
Permissions
***********
- ``[p]permissions removeserverrule`` and ``[p]permissions removeglobalrule`` no longer error when trying to remove a rule that doesn't exist (:issue:`4028`, :issue:`4036`)
Warnings
********
- ``[p]warn`` now sends an error message (instead of no feedback) when an unregistered reason is used by someone who doesn't have Administrator permission (:issue:`3839`, :issue:`3840`)
Redbot 3.3.10 (2020-07-09) Redbot 3.3.10 (2020-07-09)
=================================== ===================================

View File

@@ -397,7 +397,7 @@ Then run the following command:
.. code-block:: none .. code-block:: none
CONFIGURE_OPTS=--enable-optimizations pyenv install 3.8.3 -v CONFIGURE_OPTS=--enable-optimizations pyenv install 3.8.5 -v
This may take a long time to complete, depending on your hardware. For some machines (such as This may take a long time to complete, depending on your hardware. For some machines (such as
Raspberry Pis and micro-tier VPSes), it may take over an hour; in this case, you may wish to remove Raspberry Pis and micro-tier VPSes), it may take over an hour; in this case, you may wish to remove
@@ -409,7 +409,7 @@ After that is finished, run:
.. code-block:: none .. code-block:: none
pyenv global 3.8.3 pyenv global 3.8.5
Pyenv is now installed and your system should be configured to run Python 3.8. Pyenv is now installed and your system should be configured to run Python 3.8.

View File

@@ -29,13 +29,14 @@ Using PowerShell and Chocolatey (recommended)
********************************************* *********************************************
To install via PowerShell, search "powershell" in the Windows start menu, To install via PowerShell, search "powershell" in the Windows start menu,
right-click on it and then click "Run as administrator" right-click on it and then click "Run as administrator".
Then run each of the following commands: Then run each of the following commands:
.. code-block:: none .. code-block:: none
Set-ExecutionPolicy Bypass -Scope Process -Force Set-ExecutionPolicy Bypass -Scope Process -Force
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072
iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
choco upgrade git --params "/GitOnlyOnPath /WindowsTerminal" -y choco upgrade git --params "/GitOnlyOnPath /WindowsTerminal" -y
choco upgrade visualstudio2019-workload-vctools -y choco upgrade visualstudio2019-workload-vctools -y
@@ -63,7 +64,7 @@ Manually installing dependencies
* `MSVC Build tools <https://www.visualstudio.com/downloads/#build-tools-for-visual-studio-2019>`_ * `MSVC Build tools <https://www.visualstudio.com/downloads/#build-tools-for-visual-studio-2019>`_
* `Python 3.8.1 <https://www.python.org/downloads/>`_ - Red needs Python 3.8.1 or greater * `Python 3.8.1 or greater <https://www.python.org/downloads/>`_
.. attention:: Please make sure that the box to add Python to PATH is CHECKED, otherwise .. attention:: Please make sure that the box to add Python to PATH is CHECKED, otherwise
you may run into issues when trying to run Red. you may run into issues when trying to run Red.
@@ -86,7 +87,7 @@ Creating a Virtual Environment
.. tip:: .. tip::
If you want to learn more about virtual environments, see page: `about-venvs` If you want to learn more about virtual environments, see page: `about-venvs`.
We require 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. straightforward.
@@ -95,7 +96,12 @@ First, choose a directory where you would like to create your virtual environmen
to keep it in a location which is easy to type out the path to. From now, we'll call it to keep it in a location which is easy to type out the path to. From now, we'll call it
``redenv`` and it will be located in your home directory. ``redenv`` and it will be located in your home directory.
Start with opening a command prompt (open Start, search for "command prompt", then click it) Start with opening a command prompt (open Start, search for "command prompt", then click it).
.. note::
You shouldn't run command prompt as administrator when creating your virtual environment, or
running Red.
.. warning:: .. warning::
@@ -144,11 +150,6 @@ Run **one** of the following set of commands, depending on what extras you want
python -m pip install -U pip setuptools wheel python -m pip install -U pip setuptools wheel
python -m pip install -U Red-DiscordBot[postgres] python -m pip install -U Red-DiscordBot[postgres]
.. note::
These commands are also used for updating Red
-------------------------- --------------------------
Setting Up and Running Red Setting Up and Running Red
-------------------------- --------------------------

View File

@@ -191,7 +191,7 @@ def _update_event_loop_policy():
_asyncio.set_event_loop_policy(_uvloop.EventLoopPolicy()) _asyncio.set_event_loop_policy(_uvloop.EventLoopPolicy())
__version__ = "3.3.11.dev1" __version__ = "3.3.11"
version_info = VersionInfo.from_str(__version__) version_info = VersionInfo.from_str(__version__)
# Filter fuzzywuzzy slow sequence matcher warning # Filter fuzzywuzzy slow sequence matcher warning

View File

@@ -576,6 +576,16 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
notify_channel = self.bot.get_channel(notify_channel) notify_channel = self.bot.get_channel(notify_channel)
await self.send_embed_msg(notify_channel, title=_("Couldn't get a valid track.")) await self.send_embed_msg(notify_channel, title=_("Couldn't get a valid track."))
return return
except TrackEnqueueError:
self.update_player_lock(ctx, False)
return await self.send_embed_msg(
ctx,
title=_("Unable to Get Track"),
description=_(
"I'm unable get a track from Lavalink at the moment, try again in a few "
"minutes."
),
)
if not guild_data["auto_play"]: if not guild_data["auto_play"]:
await ctx.invoke(self.command_audioset_autoplay_toggle) await ctx.invoke(self.command_audioset_autoplay_toggle)

View File

@@ -22,7 +22,7 @@ from ...apis.playlist_interface import create_playlist, delete_playlist, get_all
from ...audio_dataclasses import LocalPath, Query from ...audio_dataclasses import LocalPath, Query
from ...audio_logging import IS_DEBUG, debug_exc_log from ...audio_logging import IS_DEBUG, debug_exc_log
from ...converters import ComplexScopeParser, ScopeParser from ...converters import ComplexScopeParser, ScopeParser
from ...errors import MissingGuild, TooManyMatches from ...errors import MissingGuild, TooManyMatches, TrackEnqueueError
from ...utils import PlaylistScope from ...utils import PlaylistScope
from ..abc import MixinMeta from ..abc import MixinMeta
from ..cog_utils import CompositeMetaClass, LazyGreedyConverter, PlaylistConverter, _ from ..cog_utils import CompositeMetaClass, LazyGreedyConverter, PlaylistConverter, _
@@ -1817,43 +1817,54 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
uploaded_playlist_name = uploaded_playlist.get( uploaded_playlist_name = uploaded_playlist.get(
"name", (file_url.split("/")[6]).split(".")[0] "name", (file_url.split("/")[6]).split(".")[0]
) )
if self.api_interface is not None and ( try:
not uploaded_playlist_url if self.api_interface is not None and (
or not self.match_yt_playlist(uploaded_playlist_url) not uploaded_playlist_url
or not ( or not self.match_yt_playlist(uploaded_playlist_url)
await self.api_interface.fetch_track( or not (
await self.api_interface.fetch_track(
ctx,
player,
Query.process_input(uploaded_playlist_url, self.local_folder_current_path),
)
)[0].tracks
):
if version == "v3":
return await self._load_v3_playlist(
ctx,
scope,
uploaded_playlist_name,
uploaded_playlist_url,
track_list,
author,
guild,
)
return await self._load_v2_playlist(
ctx, ctx,
player,
Query.process_input(uploaded_playlist_url, self.local_folder_current_path),
)
)[0].tracks
):
if version == "v3":
return await self._load_v3_playlist(
ctx,
scope,
uploaded_playlist_name,
uploaded_playlist_url,
track_list, track_list,
player,
uploaded_playlist_url,
uploaded_playlist_name,
scope,
author, author,
guild, guild,
) )
return await self._load_v2_playlist( return await ctx.invoke(
ctx, self.command_playlist_save,
track_list, playlist_name=uploaded_playlist_name,
player, playlist_url=uploaded_playlist_url,
uploaded_playlist_url, scope_data=(scope, author, guild, specified_user),
uploaded_playlist_name, )
scope, except TrackEnqueueError:
author, self.update_player_lock(ctx, False)
guild, return await self.send_embed_msg(
ctx,
title=_("Unable to Get Track"),
description=_(
"I'm unable get a track from Lavalink at the moment, try again in a few "
"minutes."
),
) )
return await ctx.invoke(
self.command_playlist_save,
playlist_name=uploaded_playlist_name,
playlist_url=uploaded_playlist_url,
scope_data=(scope, author, guild, specified_user),
)
@commands.cooldown(1, 60, commands.BucketType.member) @commands.cooldown(1, 60, commands.BucketType.member)
@command_playlist.command( @command_playlist.command(

View File

@@ -5,7 +5,7 @@ import logging
import discord import discord
import lavalink import lavalink
from ...errors import DatabaseError from ...errors import DatabaseError, TrackEnqueueError
from ..abc import MixinMeta from ..abc import MixinMeta
from ..cog_utils import CompositeMetaClass, _ from ..cog_utils import CompositeMetaClass, _
@@ -62,12 +62,25 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
await self.api_interface.autoplay(player, self.playlist_api) await self.api_interface.autoplay(player, self.playlist_api)
except DatabaseError: except DatabaseError:
notify_channel = player.fetch("channel") notify_channel = player.fetch("channel")
notify_channel = self.bot.get_channel(notify_channel)
if notify_channel: if notify_channel:
notify_channel = self.bot.get_channel(notify_channel)
await self.send_embed_msg( await self.send_embed_msg(
notify_channel, title=_("Couldn't get a valid track.") notify_channel, title=_("Couldn't get a valid track.")
) )
return return
except TrackEnqueueError:
notify_channel = player.fetch("channel")
notify_channel = self.bot.get_channel(notify_channel)
if notify_channel:
await self.send_embed_msg(
notify_channel,
title=_("Unable to Get Track"),
description=_(
"I'm unable get a track from Lavalink at the moment, try again in a few "
"minutes."
),
)
return
if event_type == lavalink.LavalinkEvents.TRACK_START and notify: if event_type == lavalink.LavalinkEvents.TRACK_START and notify:
notify_channel = player.fetch("channel") notify_channel = player.fetch("channel")
if notify_channel: if notify_channel:

View File

@@ -20,7 +20,7 @@ from .errors import LavalinkDownloadFailed
log = logging.getLogger("red.audio.manager") log = logging.getLogger("red.audio.manager")
JAR_VERSION: Final[str] = "3.3.1" JAR_VERSION: Final[str] = "3.3.1"
JAR_BUILD: Final[int] = 1068 JAR_BUILD: Final[int] = 1069
LAVALINK_DOWNLOAD_URL: Final[str] = ( LAVALINK_DOWNLOAD_URL: Final[str] = (
"https://github.com/Cog-Creators/Lavalink-Jars/releases/download/" "https://github.com/Cog-Creators/Lavalink-Jars/releases/download/"
f"{JAR_VERSION}_{JAR_BUILD}/" f"{JAR_VERSION}_{JAR_BUILD}/"

View File

@@ -1,6 +1,6 @@
import logging import logging
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Union, List, Callable, Set from typing import Callable, List, Optional, Set, Union
import discord import discord
@@ -10,7 +10,7 @@ from redbot.core.i18n import Translator, cog_i18n
from redbot.core.utils.chat_formatting import humanize_number from redbot.core.utils.chat_formatting import humanize_number
from redbot.core.utils.mod import slow_deletion, mass_purge from redbot.core.utils.mod import slow_deletion, mass_purge
from redbot.core.utils.predicates import MessagePredicate from redbot.core.utils.predicates import MessagePredicate
from .converters import RawMessageIds from .converters import PositiveInt, RawMessageIds, positive_int
_ = Translator("Cleanup", __file__) _ = Translator("Cleanup", __file__)
@@ -60,9 +60,9 @@ class Cleanup(commands.Cog):
async def get_messages_for_deletion( async def get_messages_for_deletion(
*, *,
channel: discord.TextChannel, channel: discord.TextChannel,
number: int = None, number: Optional[PositiveInt] = None,
check: Callable[[discord.Message], bool] = lambda x: True, check: Callable[[discord.Message], bool] = lambda x: True,
limit: int = None, limit: Optional[PositiveInt] = None,
before: Union[discord.Message, datetime] = None, before: Union[discord.Message, datetime] = None,
after: Union[discord.Message, datetime] = None, after: Union[discord.Message, datetime] = None,
delete_pinned: bool = False, delete_pinned: bool = False,
@@ -105,7 +105,7 @@ class Cleanup(commands.Cog):
break break
if message_filter(message): if message_filter(message):
collected.append(message) collected.append(message)
if number and number <= len(collected): if number is not None and number <= len(collected):
break break
return collected return collected
@@ -120,7 +120,7 @@ class Cleanup(commands.Cog):
@commands.guild_only() @commands.guild_only()
@commands.bot_has_permissions(manage_messages=True) @commands.bot_has_permissions(manage_messages=True)
async def text( async def text(
self, ctx: commands.Context, text: str, number: int, delete_pinned: bool = False self, ctx: commands.Context, text: str, number: positive_int, delete_pinned: bool = False
): ):
"""Delete the last X messages matching the specified text. """Delete the last X messages matching the specified text.
@@ -169,7 +169,7 @@ class Cleanup(commands.Cog):
@commands.guild_only() @commands.guild_only()
@commands.bot_has_permissions(manage_messages=True) @commands.bot_has_permissions(manage_messages=True)
async def user( async def user(
self, ctx: commands.Context, user: str, number: int, delete_pinned: bool = False self, ctx: commands.Context, user: str, number: positive_int, delete_pinned: bool = False
): ):
"""Delete the last X messages from a specified user. """Delete the last X messages from a specified user.
@@ -270,7 +270,7 @@ class Cleanup(commands.Cog):
self, self,
ctx: commands.Context, ctx: commands.Context,
message_id: RawMessageIds, message_id: RawMessageIds,
number: int, number: positive_int,
delete_pinned: bool = False, delete_pinned: bool = False,
): ):
"""Deletes X messages before specified message. """Deletes X messages before specified message.
@@ -351,7 +351,9 @@ class Cleanup(commands.Cog):
@cleanup.command() @cleanup.command()
@commands.guild_only() @commands.guild_only()
@commands.bot_has_permissions(manage_messages=True) @commands.bot_has_permissions(manage_messages=True)
async def messages(self, ctx: commands.Context, number: int, delete_pinned: bool = False): async def messages(
self, ctx: commands.Context, number: positive_int, delete_pinned: bool = False
):
"""Delete the last X messages. """Delete the last X messages.
Example: Example:
@@ -381,7 +383,9 @@ class Cleanup(commands.Cog):
@cleanup.command(name="bot") @cleanup.command(name="bot")
@commands.guild_only() @commands.guild_only()
@commands.bot_has_permissions(manage_messages=True) @commands.bot_has_permissions(manage_messages=True)
async def cleanup_bot(self, ctx: commands.Context, number: int, delete_pinned: bool = False): async def cleanup_bot(
self, ctx: commands.Context, number: positive_int, delete_pinned: bool = False
):
"""Clean up command messages and messages from the bot.""" """Clean up command messages and messages from the bot."""
channel = ctx.channel channel = ctx.channel
@@ -458,7 +462,7 @@ class Cleanup(commands.Cog):
async def cleanup_self( async def cleanup_self(
self, self,
ctx: commands.Context, ctx: commands.Context,
number: int, number: positive_int,
match_pattern: str = None, match_pattern: str = None,
delete_pinned: bool = False, delete_pinned: bool = False,
): ):
@@ -531,7 +535,7 @@ class Cleanup(commands.Cog):
@cleanup.command(name="spam") @cleanup.command(name="spam")
@commands.guild_only() @commands.guild_only()
@commands.bot_has_permissions(manage_messages=True) @commands.bot_has_permissions(manage_messages=True)
async def cleanup_spam(self, ctx: commands.Context, number: int = 50): async def cleanup_spam(self, ctx: commands.Context, number: positive_int = PositiveInt(50)):
"""Deletes duplicate messages in the channel from the last X messages and keeps only one copy. """Deletes duplicate messages in the channel from the last X messages and keeps only one copy.
Defaults to 50. Defaults to 50.

View File

@@ -1,12 +1,30 @@
from redbot.core.commands import Converter, BadArgument from typing import NewType, TYPE_CHECKING
from redbot.core.commands import BadArgument, Context, Converter
from redbot.core.i18n import Translator from redbot.core.i18n import Translator
from redbot.core.utils.chat_formatting import inline
_ = Translator("Cleanup", __file__) _ = Translator("Cleanup", __file__)
class RawMessageIds(Converter): class RawMessageIds(Converter):
async def convert(self, ctx, argument): async def convert(self, ctx: Context, argument: str) -> int:
if argument.isnumeric() and len(argument) >= 17: if argument.isnumeric() and len(argument) >= 17:
return int(argument) return int(argument)
raise BadArgument(_("{} doesn't look like a valid message ID.").format(argument)) raise BadArgument(_("{} doesn't look like a valid message ID.").format(argument))
PositiveInt = NewType("PositiveInt", int)
if TYPE_CHECKING:
positive_int = PositiveInt
else:
def positive_int(arg: str) -> int:
try:
ret = int(arg)
except ValueError:
raise BadArgument(_("{arg} is not an integer.").format(arg=inline(arg)))
if ret <= 0:
raise BadArgument(_("{arg} is not a positive integer.").format(arg=inline(arg)))
return ret

View File

@@ -464,7 +464,7 @@ class CustomCommands(commands.Cog):
try: try:
cmd = await self.commandobj.get_full(ctx.message, command_name) cmd = await self.commandobj.get_full(ctx.message, command_name)
except NotFound: except NotFound:
ctx.send(_("I could not not find that custom command.")) await ctx.send(_("I could not not find that custom command."))
return return
responses = cmd["response"] responses = cmd["response"]

View File

@@ -303,10 +303,22 @@ class Downloader(commands.Cog):
hashes: Dict[Tuple[Repo, str], Set[InstalledModule]] = defaultdict(set) hashes: Dict[Tuple[Repo, str], Set[InstalledModule]] = defaultdict(set)
for module in modules: for module in modules:
module.repo = cast(Repo, module.repo) module.repo = cast(Repo, module.repo)
if module.repo.commit != module.commit and await module.repo.is_ancestor( if module.repo.commit != module.commit:
module.commit, module.repo.commit try:
): should_add = await module.repo.is_ancestor(module.commit, module.repo.commit)
hashes[(module.repo, module.commit)].add(module) except errors.UnknownRevision:
# marking module for update if the saved commit data is invalid
last_module_occurrence = await module.repo.get_last_module_occurrence(
module.name
)
if last_module_occurrence is not None and not last_module_occurrence.disabled:
if last_module_occurrence.type == InstallableType.COG:
cogs_to_update.add(last_module_occurrence)
elif last_module_occurrence.type == InstallableType.SHARED_LIBRARY:
libraries_to_update.add(last_module_occurrence)
else:
if should_add:
hashes[(module.repo, module.commit)].add(module)
update_commits = [] update_commits = []
for (repo, old_hash), modules_to_check in hashes.items(): for (repo, old_hash), modules_to_check in hashes.items():
@@ -1387,7 +1399,11 @@ class Downloader(commands.Cog):
cog_name = self.cog_name_from_instance(cog) cog_name = self.cog_name_from_instance(cog)
installed, cog_installable = await self.is_installed(cog_name) installed, cog_installable = await self.is_installed(cog_name)
if installed: if installed:
made_by = humanize_list(cog_installable.author) or _("Missing from info.json") made_by = (
humanize_list(cog_installable.author)
if cog_installable.author
else _("Missing from info.json")
)
repo_url = ( repo_url = (
_("Missing from installed repos") _("Missing from installed repos")
if cog_installable.repo is None if cog_installable.repo is None

View File

@@ -198,6 +198,11 @@ class Repo(RepoJSONMixin):
descendant_rev : `str` descendant_rev : `str`
Descendant revision Descendant revision
Raises
------
.UnknownRevision
When git cannot find one of the provided revisions.
Returns Returns
------- -------
bool bool
@@ -212,10 +217,17 @@ class Repo(RepoJSONMixin):
maybe_ancestor_rev=maybe_ancestor_rev, maybe_ancestor_rev=maybe_ancestor_rev,
descendant_rev=descendant_rev, descendant_rev=descendant_rev,
) )
p = await self._run(git_command, valid_exit_codes=valid_exit_codes) p = await self._run(git_command, valid_exit_codes=valid_exit_codes, debug_only=True)
if p.returncode in valid_exit_codes: if p.returncode in valid_exit_codes:
return not bool(p.returncode) return not bool(p.returncode)
# this is a plumbing command so we're safe here
stderr = p.stderr.decode(**DECODE_PARAMS).strip()
if stderr.startswith(("fatal: Not a valid object name", "fatal: Not a valid commit name")):
rev, *__ = stderr[31:].split(maxsplit=1)
raise errors.UnknownRevision(f"Revision {rev} cannot be found.", git_command)
raise errors.GitException( raise errors.GitException(
f"Git failed to determine if commit {maybe_ancestor_rev}" 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}",

View File

@@ -532,7 +532,10 @@ class Economy(commands.Cog):
@guild_only_check() @guild_only_check()
async def payouts(self, ctx: commands.Context): async def payouts(self, ctx: commands.Context):
"""Show the payouts for the slot machine.""" """Show the payouts for the slot machine."""
await ctx.author.send(SLOT_PAYOUTS_MSG) try:
await ctx.author.send(SLOT_PAYOUTS_MSG)
except discord.Forbidden:
await ctx.send(_("I can't send direct messages to you."))
@commands.command() @commands.command()
@guild_only_check() @guild_only_check()

View File

@@ -599,8 +599,8 @@ class Permissions(commands.Cog):
cog_or_cmd.obj.clear_rule_for(model_id, guild_id=guild_id) cog_or_cmd.obj.clear_rule_for(model_id, guild_id=guild_id)
guild_id, model_id = str(guild_id), str(model_id) guild_id, model_id = str(guild_id), str(model_id)
async with self.config.custom(cog_or_cmd.type, cog_or_cmd.name).all() as rules: async with self.config.custom(cog_or_cmd.type, cog_or_cmd.name).all() as rules:
if guild_id in rules and rules[guild_id]: if (guild_rules := rules.get(guild_id)) is not None:
del rules[guild_id][model_id] guild_rules.pop(model_id, None)
async def _set_default_rule( async def _set_default_rule(
self, rule: Optional[bool], cog_or_cmd: CogOrCommand, guild_id: int self, rule: Optional[bool], cog_or_cmd: CogOrCommand, guild_id: int

View File

@@ -4,7 +4,7 @@ import time
import random import random
from collections import Counter from collections import Counter
import discord import discord
from redbot.core import bank from redbot.core import bank, errors
from redbot.core.i18n import Translator from redbot.core.i18n import Translator
from redbot.core.utils.chat_formatting import box, bold, humanize_list, humanize_number from redbot.core.utils.chat_formatting import box, bold, humanize_list, humanize_number
from redbot.core.utils.common_filters import normalize_smartquotes from redbot.core.utils.common_filters import normalize_smartquotes
@@ -305,7 +305,7 @@ class TriviaSession:
LOG.debug("Paying trivia winner: %d credits --> %s", amount, str(winner)) LOG.debug("Paying trivia winner: %d credits --> %s", amount, str(winner))
try: try:
await bank.deposit_credits(winner, int(multiplier * score)) await bank.deposit_credits(winner, int(multiplier * score))
except bank.BalanceTooHigh as e: except errors.BalanceTooHigh as e:
await bank.set_balance(winner, e.max_balance) await bank.set_balance(winner, e.max_balance)
await self.ctx.send( await self.ctx.send(
_( _(

View File

@@ -523,7 +523,7 @@ class Trivia(commands.Cog):
) )
padding = [" " * (len(h) - len(f)) for h, f in zip(headers, fields)] padding = [" " * (len(h) - len(f)) for h, f in zip(headers, fields)]
fields = tuple(f + padding[i] for i, f in enumerate(fields)) fields = tuple(f + padding[i] for i, f in enumerate(fields))
lines.append(" | ".join(fields).format(member=member, **m_data)) lines.append(" | ".join(fields))
if rank == top: if rank == top:
break break
return "\n".join(lines) return "\n".join(lines)

View File

@@ -1,5 +1,6 @@
import contextlib import contextlib
from collections import namedtuple from collections import namedtuple
from copy import copy
from typing import Union, Optional from typing import Union, Optional
import discord import discord
@@ -350,22 +351,27 @@ class Warnings(commands.Cog):
reason_type = None reason_type = None
async with self.config.guild(ctx.guild).reasons() as registered_reasons: async with self.config.guild(ctx.guild).reasons() as registered_reasons:
if reason.lower() not in registered_reasons: if (reason_type := registered_reasons.get(reason.lower())) is None:
msg = _("That is not a registered reason!") msg = _("That is not a registered reason!")
if custom_allowed: if custom_allowed:
reason_type = {"description": reason, "points": points} reason_type = {"description": reason, "points": points}
elif ( else:
ctx.guild.owner == ctx.author # logic taken from `[p]permissions canrun`
or ctx.channel.permissions_for(ctx.author).administrator fake_message = copy(ctx.message)
or await ctx.bot.is_owner(ctx.author) fake_message.content = f"{ctx.prefix}warningset allowcustomreasons"
): fake_context = await ctx.bot.get_context(fake_message)
msg += " " + _( try:
"Do `{prefix}warningset allowcustomreasons true` to enable custom " can = await self.allowcustomreasons.can_run(
"reasons." fake_context, check_all_parents=True, change_permission_state=False
).format(prefix=ctx.clean_prefix) )
except commands.CommandError:
can = False
if can:
msg += " " + _(
"Do `{prefix}warningset allowcustomreasons true` to enable custom "
"reasons."
).format(prefix=ctx.clean_prefix)
return await ctx.send(msg) return await ctx.send(msg)
else:
reason_type = registered_reasons[reason.lower()]
if reason_type is None: if reason_type is None:
return return
member_settings = self.config.member(user) member_settings = self.config.member(user)

View File

@@ -81,14 +81,15 @@ async def test_is_ancestor(mocker, repo, maybe_ancestor_rev, descendant_rev, ret
descendant_rev=descendant_rev, descendant_rev=descendant_rev,
), ),
valid_exit_codes=(0, 1), valid_exit_codes=(0, 1),
debug_only=True,
) )
assert ret is expected assert ret is expected
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_is_ancestor_raise(mocker, repo): async def test_is_ancestor_object_raise(mocker, repo):
m = _mock_run(mocker, repo, 128) m = _mock_run(mocker, repo, 128, b"", b"fatal: Not a valid object name invalid1")
with pytest.raises(GitException): with pytest.raises(UnknownRevision):
await repo.is_ancestor("invalid1", "invalid2") await repo.is_ancestor("invalid1", "invalid2")
m.assert_called_once_with( m.assert_called_once_with(
@@ -99,6 +100,33 @@ async def test_is_ancestor_raise(mocker, repo):
descendant_rev="invalid2", descendant_rev="invalid2",
), ),
valid_exit_codes=(0, 1), valid_exit_codes=(0, 1),
debug_only=True,
)
@pytest.mark.asyncio
async def test_is_ancestor_commit_raise(mocker, repo):
m = _mock_run(
mocker,
repo,
128,
b"",
b"fatal: Not a valid commit name 0123456789abcde0123456789abcde0123456789",
)
with pytest.raises(UnknownRevision):
await repo.is_ancestor(
"0123456789abcde0123456789abcde0123456789", "c950fc05a540dd76b944719c2a3302da2e2f3090"
)
m.assert_called_once_with(
ProcessFormatter().format(
repo.GIT_IS_ANCESTOR,
path=repo.folder_path,
maybe_ancestor_rev="0123456789abcde0123456789abcde0123456789",
descendant_rev="c950fc05a540dd76b944719c2a3302da2e2f3090",
),
valid_exit_codes=(0, 1),
debug_only=True,
) )

View File

@@ -381,7 +381,7 @@ async def test_git_is_ancestor_false(git_repo):
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_git_is_ancestor_invalid_ref(git_repo): async def test_git_is_ancestor_invalid_object(git_repo):
p = await git_repo._run( p = await git_repo._run(
ProcessFormatter().format( ProcessFormatter().format(
git_repo.GIT_IS_ANCESTOR, git_repo.GIT_IS_ANCESTOR,
@@ -394,6 +394,22 @@ async def test_git_is_ancestor_invalid_ref(git_repo):
assert p.stderr.decode().strip() == "fatal: Not a valid object name invalid1" assert p.stderr.decode().strip() == "fatal: Not a valid object name invalid1"
@pytest.mark.asyncio
async def test_git_is_ancestor_invalid_commit(git_repo):
p = await git_repo._run(
ProcessFormatter().format(
git_repo.GIT_IS_ANCESTOR,
path=git_repo.folder_path,
maybe_ancestor_rev="0123456789abcde0123456789abcde0123456789",
descendant_rev="c950fc05a540dd76b944719c2a3302da2e2f3090",
)
)
assert p.returncode == 128
assert p.stderr.decode().strip() == (
"fatal: Not a valid commit name 0123456789abcde0123456789abcde0123456789"
)
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_git_check_if_module_exists_true(git_repo): async def test_git_check_if_module_exists_true(git_repo):
p = await git_repo._run( p = await git_repo._run(