mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-12-05 08:52:31 -05:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8d3e9fceb | ||
|
|
b3281385e9 | ||
|
|
e6947bdbf6 | ||
|
|
a6d924221d | ||
|
|
cf7db1e891 | ||
|
|
bc544d476b | ||
|
|
10976f218b | ||
|
|
a589838a41 | ||
|
|
e36d1f143d | ||
|
|
9a6f78e62e | ||
|
|
649db87a8a | ||
|
|
c6f9a78d57 | ||
|
|
bd89e4386d | ||
|
|
a962f94a58 | ||
|
|
3471011f85 | ||
|
|
186dbe6118 |
@@ -1,5 +1,47 @@
|
||||
.. 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)
|
||||
===================================
|
||||
|
||||
|
||||
@@ -397,7 +397,7 @@ Then run the following command:
|
||||
|
||||
.. 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
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -29,13 +29,14 @@ Using PowerShell and Chocolatey (recommended)
|
||||
*********************************************
|
||||
|
||||
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:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
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'))
|
||||
choco upgrade git --params "/GitOnlyOnPath /WindowsTerminal" -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>`_
|
||||
|
||||
* `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
|
||||
you may run into issues when trying to run Red.
|
||||
@@ -86,7 +87,7 @@ Creating a Virtual Environment
|
||||
|
||||
.. 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
|
||||
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
|
||||
``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::
|
||||
|
||||
@@ -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 Red-DiscordBot[postgres]
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
These commands are also used for updating Red
|
||||
|
||||
--------------------------
|
||||
Setting Up and Running Red
|
||||
--------------------------
|
||||
|
||||
@@ -191,7 +191,7 @@ def _update_event_loop_policy():
|
||||
_asyncio.set_event_loop_policy(_uvloop.EventLoopPolicy())
|
||||
|
||||
|
||||
__version__ = "3.3.11.dev1"
|
||||
__version__ = "3.3.11"
|
||||
version_info = VersionInfo.from_str(__version__)
|
||||
|
||||
# Filter fuzzywuzzy slow sequence matcher warning
|
||||
|
||||
@@ -576,6 +576,16 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
|
||||
notify_channel = self.bot.get_channel(notify_channel)
|
||||
await self.send_embed_msg(notify_channel, title=_("Couldn't get a valid track."))
|
||||
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"]:
|
||||
await ctx.invoke(self.command_audioset_autoplay_toggle)
|
||||
|
||||
@@ -22,7 +22,7 @@ from ...apis.playlist_interface import create_playlist, delete_playlist, get_all
|
||||
from ...audio_dataclasses import LocalPath, Query
|
||||
from ...audio_logging import IS_DEBUG, debug_exc_log
|
||||
from ...converters import ComplexScopeParser, ScopeParser
|
||||
from ...errors import MissingGuild, TooManyMatches
|
||||
from ...errors import MissingGuild, TooManyMatches, TrackEnqueueError
|
||||
from ...utils import PlaylistScope
|
||||
from ..abc import MixinMeta
|
||||
from ..cog_utils import CompositeMetaClass, LazyGreedyConverter, PlaylistConverter, _
|
||||
@@ -1817,43 +1817,54 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
|
||||
uploaded_playlist_name = uploaded_playlist.get(
|
||||
"name", (file_url.split("/")[6]).split(".")[0]
|
||||
)
|
||||
if self.api_interface is not None and (
|
||||
not uploaded_playlist_url
|
||||
or not self.match_yt_playlist(uploaded_playlist_url)
|
||||
or not (
|
||||
await self.api_interface.fetch_track(
|
||||
try:
|
||||
if self.api_interface is not None and (
|
||||
not uploaded_playlist_url
|
||||
or not self.match_yt_playlist(uploaded_playlist_url)
|
||||
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,
|
||||
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,
|
||||
player,
|
||||
uploaded_playlist_url,
|
||||
uploaded_playlist_name,
|
||||
scope,
|
||||
author,
|
||||
guild,
|
||||
)
|
||||
return await self._load_v2_playlist(
|
||||
ctx,
|
||||
track_list,
|
||||
player,
|
||||
uploaded_playlist_url,
|
||||
uploaded_playlist_name,
|
||||
scope,
|
||||
author,
|
||||
guild,
|
||||
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),
|
||||
)
|
||||
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."
|
||||
),
|
||||
)
|
||||
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)
|
||||
@command_playlist.command(
|
||||
|
||||
@@ -5,7 +5,7 @@ import logging
|
||||
import discord
|
||||
import lavalink
|
||||
|
||||
from ...errors import DatabaseError
|
||||
from ...errors import DatabaseError, TrackEnqueueError
|
||||
from ..abc import MixinMeta
|
||||
from ..cog_utils import CompositeMetaClass, _
|
||||
|
||||
@@ -62,12 +62,25 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
|
||||
await self.api_interface.autoplay(player, self.playlist_api)
|
||||
except DatabaseError:
|
||||
notify_channel = player.fetch("channel")
|
||||
notify_channel = self.bot.get_channel(notify_channel)
|
||||
if notify_channel:
|
||||
notify_channel = self.bot.get_channel(notify_channel)
|
||||
await self.send_embed_msg(
|
||||
notify_channel, title=_("Couldn't get a valid track.")
|
||||
)
|
||||
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:
|
||||
notify_channel = player.fetch("channel")
|
||||
if notify_channel:
|
||||
|
||||
@@ -20,7 +20,7 @@ from .errors import LavalinkDownloadFailed
|
||||
|
||||
log = logging.getLogger("red.audio.manager")
|
||||
JAR_VERSION: Final[str] = "3.3.1"
|
||||
JAR_BUILD: Final[int] = 1068
|
||||
JAR_BUILD: Final[int] = 1069
|
||||
LAVALINK_DOWNLOAD_URL: Final[str] = (
|
||||
"https://github.com/Cog-Creators/Lavalink-Jars/releases/download/"
|
||||
f"{JAR_VERSION}_{JAR_BUILD}/"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Union, List, Callable, Set
|
||||
from typing import Callable, List, Optional, Set, Union
|
||||
|
||||
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.mod import slow_deletion, mass_purge
|
||||
from redbot.core.utils.predicates import MessagePredicate
|
||||
from .converters import RawMessageIds
|
||||
from .converters import PositiveInt, RawMessageIds, positive_int
|
||||
|
||||
_ = Translator("Cleanup", __file__)
|
||||
|
||||
@@ -60,9 +60,9 @@ class Cleanup(commands.Cog):
|
||||
async def get_messages_for_deletion(
|
||||
*,
|
||||
channel: discord.TextChannel,
|
||||
number: int = None,
|
||||
number: Optional[PositiveInt] = None,
|
||||
check: Callable[[discord.Message], bool] = lambda x: True,
|
||||
limit: int = None,
|
||||
limit: Optional[PositiveInt] = None,
|
||||
before: Union[discord.Message, datetime] = None,
|
||||
after: Union[discord.Message, datetime] = None,
|
||||
delete_pinned: bool = False,
|
||||
@@ -105,7 +105,7 @@ class Cleanup(commands.Cog):
|
||||
break
|
||||
if message_filter(message):
|
||||
collected.append(message)
|
||||
if number and number <= len(collected):
|
||||
if number is not None and number <= len(collected):
|
||||
break
|
||||
|
||||
return collected
|
||||
@@ -120,7 +120,7 @@ class Cleanup(commands.Cog):
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(manage_messages=True)
|
||||
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.
|
||||
|
||||
@@ -169,7 +169,7 @@ class Cleanup(commands.Cog):
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(manage_messages=True)
|
||||
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.
|
||||
|
||||
@@ -270,7 +270,7 @@ class Cleanup(commands.Cog):
|
||||
self,
|
||||
ctx: commands.Context,
|
||||
message_id: RawMessageIds,
|
||||
number: int,
|
||||
number: positive_int,
|
||||
delete_pinned: bool = False,
|
||||
):
|
||||
"""Deletes X messages before specified message.
|
||||
@@ -351,7 +351,9 @@ class Cleanup(commands.Cog):
|
||||
@cleanup.command()
|
||||
@commands.guild_only()
|
||||
@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.
|
||||
|
||||
Example:
|
||||
@@ -381,7 +383,9 @@ class Cleanup(commands.Cog):
|
||||
@cleanup.command(name="bot")
|
||||
@commands.guild_only()
|
||||
@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."""
|
||||
|
||||
channel = ctx.channel
|
||||
@@ -458,7 +462,7 @@ class Cleanup(commands.Cog):
|
||||
async def cleanup_self(
|
||||
self,
|
||||
ctx: commands.Context,
|
||||
number: int,
|
||||
number: positive_int,
|
||||
match_pattern: str = None,
|
||||
delete_pinned: bool = False,
|
||||
):
|
||||
@@ -531,7 +535,7 @@ class Cleanup(commands.Cog):
|
||||
@cleanup.command(name="spam")
|
||||
@commands.guild_only()
|
||||
@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.
|
||||
|
||||
Defaults to 50.
|
||||
|
||||
@@ -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.utils.chat_formatting import inline
|
||||
|
||||
_ = Translator("Cleanup", __file__)
|
||||
|
||||
|
||||
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:
|
||||
return int(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
|
||||
|
||||
@@ -464,7 +464,7 @@ class CustomCommands(commands.Cog):
|
||||
try:
|
||||
cmd = await self.commandobj.get_full(ctx.message, command_name)
|
||||
except NotFound:
|
||||
ctx.send(_("I could not not find that custom command."))
|
||||
await ctx.send(_("I could not not find that custom command."))
|
||||
return
|
||||
|
||||
responses = cmd["response"]
|
||||
|
||||
@@ -303,10 +303,22 @@ class Downloader(commands.Cog):
|
||||
hashes: Dict[Tuple[Repo, str], Set[InstalledModule]] = defaultdict(set)
|
||||
for module in modules:
|
||||
module.repo = cast(Repo, module.repo)
|
||||
if module.repo.commit != module.commit and await module.repo.is_ancestor(
|
||||
module.commit, module.repo.commit
|
||||
):
|
||||
hashes[(module.repo, module.commit)].add(module)
|
||||
if module.repo.commit != module.commit:
|
||||
try:
|
||||
should_add = await module.repo.is_ancestor(module.commit, module.repo.commit)
|
||||
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 = []
|
||||
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)
|
||||
installed, cog_installable = await self.is_installed(cog_name)
|
||||
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 = (
|
||||
_("Missing from installed repos")
|
||||
if cog_installable.repo is None
|
||||
|
||||
@@ -198,6 +198,11 @@ class Repo(RepoJSONMixin):
|
||||
descendant_rev : `str`
|
||||
Descendant revision
|
||||
|
||||
Raises
|
||||
------
|
||||
.UnknownRevision
|
||||
When git cannot find one of the provided revisions.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
@@ -212,10 +217,17 @@ class Repo(RepoJSONMixin):
|
||||
maybe_ancestor_rev=maybe_ancestor_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:
|
||||
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(
|
||||
f"Git failed to determine if commit {maybe_ancestor_rev}"
|
||||
f" is ancestor of {descendant_rev} for repo at path: {self.folder_path}",
|
||||
|
||||
@@ -532,7 +532,10 @@ class Economy(commands.Cog):
|
||||
@guild_only_check()
|
||||
async def payouts(self, ctx: commands.Context):
|
||||
"""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()
|
||||
@guild_only_check()
|
||||
|
||||
@@ -599,8 +599,8 @@ class Permissions(commands.Cog):
|
||||
cog_or_cmd.obj.clear_rule_for(model_id, guild_id=guild_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:
|
||||
if guild_id in rules and rules[guild_id]:
|
||||
del rules[guild_id][model_id]
|
||||
if (guild_rules := rules.get(guild_id)) is not None:
|
||||
guild_rules.pop(model_id, None)
|
||||
|
||||
async def _set_default_rule(
|
||||
self, rule: Optional[bool], cog_or_cmd: CogOrCommand, guild_id: int
|
||||
|
||||
@@ -4,7 +4,7 @@ import time
|
||||
import random
|
||||
from collections import Counter
|
||||
import discord
|
||||
from redbot.core import bank
|
||||
from redbot.core import bank, errors
|
||||
from redbot.core.i18n import Translator
|
||||
from redbot.core.utils.chat_formatting import box, bold, humanize_list, humanize_number
|
||||
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))
|
||||
try:
|
||||
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 self.ctx.send(
|
||||
_(
|
||||
|
||||
@@ -523,7 +523,7 @@ class Trivia(commands.Cog):
|
||||
)
|
||||
padding = [" " * (len(h) - len(f)) for h, f in zip(headers, 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:
|
||||
break
|
||||
return "\n".join(lines)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import contextlib
|
||||
from collections import namedtuple
|
||||
from copy import copy
|
||||
from typing import Union, Optional
|
||||
|
||||
import discord
|
||||
@@ -350,22 +351,27 @@ class Warnings(commands.Cog):
|
||||
|
||||
reason_type = None
|
||||
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!")
|
||||
if custom_allowed:
|
||||
reason_type = {"description": reason, "points": points}
|
||||
elif (
|
||||
ctx.guild.owner == ctx.author
|
||||
or ctx.channel.permissions_for(ctx.author).administrator
|
||||
or await ctx.bot.is_owner(ctx.author)
|
||||
):
|
||||
msg += " " + _(
|
||||
"Do `{prefix}warningset allowcustomreasons true` to enable custom "
|
||||
"reasons."
|
||||
).format(prefix=ctx.clean_prefix)
|
||||
else:
|
||||
# logic taken from `[p]permissions canrun`
|
||||
fake_message = copy(ctx.message)
|
||||
fake_message.content = f"{ctx.prefix}warningset allowcustomreasons"
|
||||
fake_context = await ctx.bot.get_context(fake_message)
|
||||
try:
|
||||
can = await self.allowcustomreasons.can_run(
|
||||
fake_context, check_all_parents=True, change_permission_state=False
|
||||
)
|
||||
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)
|
||||
else:
|
||||
reason_type = registered_reasons[reason.lower()]
|
||||
if reason_type is None:
|
||||
return
|
||||
member_settings = self.config.member(user)
|
||||
|
||||
@@ -81,14 +81,15 @@ async def test_is_ancestor(mocker, repo, maybe_ancestor_rev, descendant_rev, ret
|
||||
descendant_rev=descendant_rev,
|
||||
),
|
||||
valid_exit_codes=(0, 1),
|
||||
debug_only=True,
|
||||
)
|
||||
assert ret is expected
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_is_ancestor_raise(mocker, repo):
|
||||
m = _mock_run(mocker, repo, 128)
|
||||
with pytest.raises(GitException):
|
||||
async def test_is_ancestor_object_raise(mocker, repo):
|
||||
m = _mock_run(mocker, repo, 128, b"", b"fatal: Not a valid object name invalid1")
|
||||
with pytest.raises(UnknownRevision):
|
||||
await repo.is_ancestor("invalid1", "invalid2")
|
||||
|
||||
m.assert_called_once_with(
|
||||
@@ -99,6 +100,33 @@ async def test_is_ancestor_raise(mocker, repo):
|
||||
descendant_rev="invalid2",
|
||||
),
|
||||
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,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -381,7 +381,7 @@ async def test_git_is_ancestor_false(git_repo):
|
||||
|
||||
|
||||
@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(
|
||||
ProcessFormatter().format(
|
||||
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"
|
||||
|
||||
|
||||
@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
|
||||
async def test_git_check_if_module_exists_true(git_repo):
|
||||
p = await git_repo._run(
|
||||
|
||||
Reference in New Issue
Block a user