mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-12-06 09:22:31 -05:00
Compare commits
30 Commits
3.0.0rc1.p
...
3.0.0rc2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8bba860f85 | ||
|
|
d2d26835c3 | ||
|
|
aff62a8006 | ||
|
|
b5fd28ef7c | ||
|
|
c510ebe5e5 | ||
|
|
5ba95090d9 | ||
|
|
ad51fa830b | ||
|
|
1ba922eba2 | ||
|
|
9588a5740c | ||
|
|
7cd765d548 | ||
|
|
6022c0f7d7 | ||
|
|
0548744e94 | ||
|
|
8b2d115335 | ||
|
|
094735566d | ||
|
|
f7b1f9f0dc | ||
|
|
ce25011f0d | ||
|
|
f85034eb27 | ||
|
|
849755ecd2 | ||
|
|
9217275908 | ||
|
|
9e13ca45e6 | ||
|
|
46c38a28eb | ||
|
|
76bbcf2f8c | ||
|
|
ee7e8aa782 | ||
|
|
fd0abc250d | ||
|
|
847f9fc8d1 | ||
|
|
046e98565e | ||
|
|
71eddc89ea | ||
|
|
9730a424ec | ||
|
|
7b260cdafc | ||
|
|
4369095a51 |
@@ -374,6 +374,21 @@ API Reference
|
|||||||
inside the bot itself! Simply take a peek inside of the :code:`tests/core/test_config.py` file for examples of using
|
inside the bot itself! Simply take a peek inside of the :code:`tests/core/test_config.py` file for examples of using
|
||||||
Config in all kinds of ways.
|
Config in all kinds of ways.
|
||||||
|
|
||||||
|
.. important::
|
||||||
|
|
||||||
|
When getting, setting or clearing values in Config, all keys are casted to `str` for you. This
|
||||||
|
includes keys within a `dict` when one is being set, as well as keys in nested dictionaries
|
||||||
|
within that `dict`. For example::
|
||||||
|
|
||||||
|
>>> conf = Config.get_conf(self, identifier=999)
|
||||||
|
>>> conf.register_global(foo={})
|
||||||
|
>>> await conf.foo.set_raw(123, value=True)
|
||||||
|
>>> await conf.foo()
|
||||||
|
{'123': True}
|
||||||
|
>>> await conf.foo.set({123: True, 456: {789: False}}
|
||||||
|
>>> await conf.foo()
|
||||||
|
{'123': True, '456': {'789': False}}
|
||||||
|
|
||||||
.. automodule:: redbot.core.config
|
.. automodule:: redbot.core.config
|
||||||
|
|
||||||
Config
|
Config
|
||||||
|
|||||||
@@ -359,7 +359,7 @@ class Admin(commands.Cog):
|
|||||||
selfroles = await self._valid_selfroles(ctx.guild)
|
selfroles = await self._valid_selfroles(ctx.guild)
|
||||||
fmt_selfroles = "\n".join(["+ " + r.name for r in selfroles])
|
fmt_selfroles = "\n".join(["+ " + r.name for r in selfroles])
|
||||||
|
|
||||||
msg = _("Available Selfroles: {selfroles}").format(selfroles=fmt_selfroles)
|
msg = _("Available Selfroles:\n{selfroles}").format(selfroles=fmt_selfroles)
|
||||||
await ctx.send(box(msg, "diff"))
|
await ctx.send(box(msg, "diff"))
|
||||||
|
|
||||||
async def _serverlock_check(self, guild: discord.Guild) -> bool:
|
async def _serverlock_check(self, guild: discord.Guild) -> bool:
|
||||||
|
|||||||
@@ -288,7 +288,10 @@ class Alias(commands.Cog):
|
|||||||
"""Try to execute help for the base command of the alias."""
|
"""Try to execute help for the base command of the alias."""
|
||||||
is_alias, alias = await self.is_alias(ctx.guild, alias_name=alias_name)
|
is_alias, alias = await self.is_alias(ctx.guild, alias_name=alias_name)
|
||||||
if is_alias:
|
if is_alias:
|
||||||
base_cmd = alias.command[0]
|
if self.is_command(alias.command):
|
||||||
|
base_cmd = alias.command
|
||||||
|
else:
|
||||||
|
base_cmd = alias.command.rsplit(" ", 1)[0]
|
||||||
|
|
||||||
new_msg = copy(ctx.message)
|
new_msg = copy(ctx.message)
|
||||||
new_msg.content = _("{prefix}help {command}").format(
|
new_msg.content = _("{prefix}help {command}").format(
|
||||||
|
|||||||
@@ -34,14 +34,14 @@ async def download_lavalink(session):
|
|||||||
|
|
||||||
async def maybe_download_lavalink(loop, cog):
|
async def maybe_download_lavalink(loop, cog):
|
||||||
jar_exists = LAVALINK_JAR_FILE.exists()
|
jar_exists = LAVALINK_JAR_FILE.exists()
|
||||||
current_build = redbot.core.VersionInfo.from_json(await cog.config.current_build())
|
current_build = redbot.core.VersionInfo.from_json(await cog.config.current_version())
|
||||||
|
|
||||||
if not jar_exists or current_build < redbot.core.version_info:
|
if not jar_exists or current_build < redbot.core.version_info:
|
||||||
log.info("Downloading Lavalink.jar")
|
log.info("Downloading Lavalink.jar")
|
||||||
LAVALINK_DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True)
|
LAVALINK_DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
async with ClientSession(loop=loop) as session:
|
async with ClientSession(loop=loop) as session:
|
||||||
await download_lavalink(session)
|
await download_lavalink(session)
|
||||||
await cog.config.current_build.set(redbot.core.version_info.to_json())
|
await cog.config.current_version.set(redbot.core.version_info.to_json())
|
||||||
|
|
||||||
shutil.copyfile(str(BUNDLED_APP_YML_FILE), str(APP_YML_FILE))
|
shutil.copyfile(str(BUNDLED_APP_YML_FILE), str(APP_YML_FILE))
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class Audio(commands.Cog):
|
|||||||
"ws_port": "2332",
|
"ws_port": "2332",
|
||||||
"password": "youshallnotpass",
|
"password": "youshallnotpass",
|
||||||
"status": False,
|
"status": False,
|
||||||
"current_build": [3, 0, 0, "alpha", 0],
|
"current_version": redbot.core.VersionInfo.from_str("3.0.0a0").to_json(),
|
||||||
"use_external_lavalink": False,
|
"use_external_lavalink": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +253,9 @@ class Audio(commands.Cog):
|
|||||||
|
|
||||||
dj_enabled = await self.config.guild(ctx.guild).dj_enabled()
|
dj_enabled = await self.config.guild(ctx.guild).dj_enabled()
|
||||||
await self.config.guild(ctx.guild).dj_enabled.set(not dj_enabled)
|
await self.config.guild(ctx.guild).dj_enabled.set(not dj_enabled)
|
||||||
await self._embed_msg(ctx, "DJ role enabled: {}.".format(not dj_enabled))
|
await self._embed_msg(
|
||||||
|
ctx, _("DJ role enabled: {true_or_false}.".format(true_or_false=not dj_enabled))
|
||||||
|
)
|
||||||
|
|
||||||
@audioset.command()
|
@audioset.command()
|
||||||
@checks.mod_or_permissions(administrator=True)
|
@checks.mod_or_permissions(administrator=True)
|
||||||
@@ -332,7 +334,7 @@ class Audio(commands.Cog):
|
|||||||
jarbuild = redbot.core.__version__
|
jarbuild = redbot.core.__version__
|
||||||
|
|
||||||
vote_percent = data["vote_percent"]
|
vote_percent = data["vote_percent"]
|
||||||
msg = "----" + _("Server Settings") + "----"
|
msg = "----" + _("Server Settings") + "----\n"
|
||||||
if emptydc_enabled:
|
if emptydc_enabled:
|
||||||
msg += _("Disconnect timer: [{num_seconds}]\n").format(
|
msg += _("Disconnect timer: [{num_seconds}]\n").format(
|
||||||
num_seconds=self._dynamic_time(emptydc_timer)
|
num_seconds=self._dynamic_time(emptydc_timer)
|
||||||
@@ -370,7 +372,9 @@ class Audio(commands.Cog):
|
|||||||
"""Toggle displaying a thumbnail on audio messages."""
|
"""Toggle displaying a thumbnail on audio messages."""
|
||||||
thumbnail = await self.config.guild(ctx.guild).thumbnail()
|
thumbnail = await self.config.guild(ctx.guild).thumbnail()
|
||||||
await self.config.guild(ctx.guild).thumbnail.set(not thumbnail)
|
await self.config.guild(ctx.guild).thumbnail.set(not thumbnail)
|
||||||
await self._embed_msg(ctx, _("Thumbnail display: {}.").format(not thumbnail))
|
await self._embed_msg(
|
||||||
|
ctx, _("Thumbnail display: {true_or_false}.").format(true_or_false=not thumbnail)
|
||||||
|
)
|
||||||
|
|
||||||
@audioset.command()
|
@audioset.command()
|
||||||
@checks.mod_or_permissions(administrator=True)
|
@checks.mod_or_permissions(administrator=True)
|
||||||
@@ -567,6 +571,8 @@ class Audio(commands.Cog):
|
|||||||
return await menu(ctx, folder_page_list, DEFAULT_CONTROLS)
|
return await menu(ctx, folder_page_list, DEFAULT_CONTROLS)
|
||||||
else:
|
else:
|
||||||
await menu(ctx, folder_page_list, LOCAL_FOLDER_CONTROLS)
|
await menu(ctx, folder_page_list, LOCAL_FOLDER_CONTROLS)
|
||||||
|
else:
|
||||||
|
await menu(ctx, folder_page_list, LOCAL_FOLDER_CONTROLS)
|
||||||
|
|
||||||
@local.command(name="search")
|
@local.command(name="search")
|
||||||
async def local_search(self, ctx, *, search_words):
|
async def local_search(self, ctx, *, search_words):
|
||||||
@@ -1097,7 +1103,7 @@ class Audio(commands.Cog):
|
|||||||
(
|
(
|
||||||
bold(playlist_name),
|
bold(playlist_name),
|
||||||
_("Tracks: {num}").format(num=len(tracks)),
|
_("Tracks: {num}").format(num=len(tracks)),
|
||||||
_("Author: {name}").format(self.bot.get_user(author)),
|
_("Author: {name}\n").format(name=self.bot.get_user(author)),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -2360,7 +2366,7 @@ class Audio(commands.Cog):
|
|||||||
if await self._check_external():
|
if await self._check_external():
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
colour=await ctx.embed_colour(),
|
colour=await ctx.embed_colour(),
|
||||||
title=_("Websocket port set to {}.").format(ws_port),
|
title=_("Websocket port set to {port}.").format(port=ws_port),
|
||||||
)
|
)
|
||||||
embed.set_footer(text=_("External lavalink server set to True."))
|
embed.set_footer(text=_("External lavalink server set to True."))
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Union, List, Callable
|
from typing import Union, List, Callable, Set
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ class Cleanup(commands.Cog):
|
|||||||
):
|
):
|
||||||
if message.created_at < two_weeks_ago:
|
if message.created_at < two_weeks_ago:
|
||||||
break
|
break
|
||||||
if check(message):
|
if message_filter(message):
|
||||||
collected.append(message)
|
collected.append(message)
|
||||||
if number and number <= len(collected):
|
if number and number <= len(collected):
|
||||||
break
|
break
|
||||||
@@ -169,7 +169,7 @@ class Cleanup(commands.Cog):
|
|||||||
|
|
||||||
member = None
|
member = None
|
||||||
try:
|
try:
|
||||||
member = await commands.converter.MemberConverter().convert(ctx, user)
|
member = await commands.MemberConverter().convert(ctx, user)
|
||||||
except commands.BadArgument:
|
except commands.BadArgument:
|
||||||
try:
|
try:
|
||||||
_id = int(user)
|
_id = int(user)
|
||||||
@@ -323,15 +323,35 @@ class Cleanup(commands.Cog):
|
|||||||
if "" in prefixes:
|
if "" in prefixes:
|
||||||
prefixes.remove("")
|
prefixes.remove("")
|
||||||
|
|
||||||
|
cc_cog = self.bot.get_cog("CustomCommands")
|
||||||
|
if cc_cog is not None:
|
||||||
|
command_names: Set[str] = await cc_cog.get_command_names(ctx.guild)
|
||||||
|
is_cc = lambda name: name in command_names
|
||||||
|
else:
|
||||||
|
is_cc = lambda name: False
|
||||||
|
alias_cog = self.bot.get_cog("Alias")
|
||||||
|
if alias_cog is not None:
|
||||||
|
alias_names: Set[str] = (
|
||||||
|
set((a.name for a in await alias_cog.unloaded_global_aliases()))
|
||||||
|
| set(a.name for a in await alias_cog.unloaded_aliases(ctx.guild))
|
||||||
|
)
|
||||||
|
is_alias = lambda name: name in alias_names
|
||||||
|
else:
|
||||||
|
is_alias = lambda name: False
|
||||||
|
|
||||||
|
bot_id = self.bot.user.id
|
||||||
|
|
||||||
def check(m):
|
def check(m):
|
||||||
if m.author.id == self.bot.user.id:
|
if m.author.id == bot_id:
|
||||||
return True
|
return True
|
||||||
elif m == ctx.message:
|
elif m == ctx.message:
|
||||||
return True
|
return True
|
||||||
p = discord.utils.find(m.content.startswith, prefixes)
|
p = discord.utils.find(m.content.startswith, prefixes)
|
||||||
if p and len(p) > 0:
|
if p and len(p) > 0:
|
||||||
cmd_name = m.content[len(p) :].split(" ")[0]
|
cmd_name = m.content[len(p) :].split(" ")[0]
|
||||||
return bool(self.bot.get_command(cmd_name))
|
return (
|
||||||
|
bool(self.bot.get_command(cmd_name)) or is_alias(cmd_name) or is_cc(cmd_name)
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
to_delete = await self.get_messages_for_deletion(
|
to_delete = await self.get_messages_for_deletion(
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ import random
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from inspect import Parameter
|
from inspect import Parameter
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from typing import Mapping, Tuple, Dict
|
from typing import Mapping, Tuple, Dict, Set
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
|
||||||
from redbot.core import Config, checks, commands
|
from redbot.core import Config, checks, commands
|
||||||
from redbot.core.utils.chat_formatting import box, pagify
|
|
||||||
from redbot.core.i18n import Translator, cog_i18n
|
from redbot.core.i18n import Translator, cog_i18n
|
||||||
|
from redbot.core.utils import menus
|
||||||
|
from redbot.core.utils.chat_formatting import box, pagify, escape
|
||||||
from redbot.core.utils.predicates import MessagePredicate
|
from redbot.core.utils.predicates import MessagePredicate
|
||||||
|
|
||||||
_ = Translator("CustomCommands", __file__)
|
_ = Translator("CustomCommands", __file__)
|
||||||
@@ -43,11 +44,8 @@ class CommandObj:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def get_commands(config) -> dict:
|
async def get_commands(config) -> dict:
|
||||||
commands = await config.commands()
|
_commands = await config.commands()
|
||||||
customcommands = {k: v for k, v in commands.items() if commands[k]}
|
return {k: v for k, v in _commands.items() if _commands[k]}
|
||||||
if len(customcommands) == 0:
|
|
||||||
return None
|
|
||||||
return customcommands
|
|
||||||
|
|
||||||
async def get_responses(self, ctx):
|
async def get_responses(self, ctx):
|
||||||
intro = _(
|
intro = _(
|
||||||
@@ -79,7 +77,8 @@ class CommandObj:
|
|||||||
responses.append(msg.content)
|
responses.append(msg.content)
|
||||||
return responses
|
return responses
|
||||||
|
|
||||||
def get_now(self) -> str:
|
@staticmethod
|
||||||
|
def get_now() -> str:
|
||||||
# Get current time as a string, for 'created_at' and 'edited_at' fields
|
# Get current time as a string, for 'created_at' and 'edited_at' fields
|
||||||
# in the ccinfo dict
|
# in the ccinfo dict
|
||||||
return "{:%d/%m/%Y %H:%M:%S}".format(datetime.utcnow())
|
return "{:%d/%m/%Y %H:%M:%S}".format(datetime.utcnow())
|
||||||
@@ -116,7 +115,7 @@ class CommandObj:
|
|||||||
*,
|
*,
|
||||||
response=None,
|
response=None,
|
||||||
cooldowns: Mapping[str, int] = None,
|
cooldowns: Mapping[str, int] = None,
|
||||||
ask_for: bool = True
|
ask_for: bool = True,
|
||||||
):
|
):
|
||||||
"""Edit an already existing custom command"""
|
"""Edit an already existing custom command"""
|
||||||
ccinfo = await self.db(ctx.guild).commands.get_raw(command, default=None)
|
ccinfo = await self.db(ctx.guild).commands.get_raw(command, default=None)
|
||||||
@@ -312,8 +311,6 @@ class CustomCommands(commands.Cog):
|
|||||||
Example:
|
Example:
|
||||||
- `[p]customcom edit yourcommand Text you want`
|
- `[p]customcom edit yourcommand Text you want`
|
||||||
"""
|
"""
|
||||||
command = command.lower()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.commandobj.edit(ctx=ctx, command=command, response=text)
|
await self.commandobj.edit(ctx=ctx, command=command, response=text)
|
||||||
await ctx.send(_("Custom command successfully edited."))
|
await ctx.send(_("Custom command successfully edited."))
|
||||||
@@ -327,12 +324,16 @@ class CustomCommands(commands.Cog):
|
|||||||
await ctx.send(e.args[0])
|
await ctx.send(e.args[0])
|
||||||
|
|
||||||
@customcom.command(name="list")
|
@customcom.command(name="list")
|
||||||
async def cc_list(self, ctx):
|
@checks.bot_has_permissions(add_reactions=True)
|
||||||
"""List all available custom commands."""
|
async def cc_list(self, ctx: commands.Context):
|
||||||
|
"""List all available custom commands.
|
||||||
|
|
||||||
response = await CommandObj.get_commands(self.config.guild(ctx.guild))
|
The list displays a preview of each command's response, with
|
||||||
|
markdown escaped and newlines replaced with spaces.
|
||||||
|
"""
|
||||||
|
cc_dict = await CommandObj.get_commands(self.config.guild(ctx.guild))
|
||||||
|
|
||||||
if not response:
|
if not cc_dict:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_(
|
_(
|
||||||
"There are no custom commands in this server."
|
"There are no custom commands in this server."
|
||||||
@@ -342,8 +343,7 @@ class CustomCommands(commands.Cog):
|
|||||||
return
|
return
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
|
for command, body in sorted(cc_dict.items(), key=lambda t: t[0]):
|
||||||
for command, body in response.items():
|
|
||||||
responses = body["response"]
|
responses = body["response"]
|
||||||
if isinstance(responses, list):
|
if isinstance(responses, list):
|
||||||
result = ", ".join(responses)
|
result = ", ".join(responses)
|
||||||
@@ -351,15 +351,33 @@ class CustomCommands(commands.Cog):
|
|||||||
result = responses
|
result = responses
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
results.append("{command:<15} : {result}".format(command=command, result=result))
|
# Replace newlines with spaces
|
||||||
|
# Cut preview to 52 characters max
|
||||||
|
if len(result) > 52:
|
||||||
|
result = result[:49] + "..."
|
||||||
|
# Replace newlines with spaces
|
||||||
|
result = result.replace("\n", " ")
|
||||||
|
# Escape markdown and mass mentions
|
||||||
|
result = escape(result, formatting=True, mass_mentions=True)
|
||||||
|
results.append((f"{ctx.clean_prefix}{command}", result))
|
||||||
|
|
||||||
commands = "\n".join(results)
|
if await ctx.embed_requested():
|
||||||
|
content = "\n".join(map("**{0[0]}** {0[1]}".format, results))
|
||||||
if len(commands) < 1500:
|
pages = list(pagify(content, page_length=1024))
|
||||||
await ctx.send(box(commands))
|
embed_pages = []
|
||||||
|
for idx, page in enumerate(pages, start=1):
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=_("Custom Command List"),
|
||||||
|
description=page,
|
||||||
|
colour=await ctx.embed_colour(),
|
||||||
|
)
|
||||||
|
embed.set_footer(text=_("Page {num}/{total}").format(num=idx, total=len(pages)))
|
||||||
|
embed_pages.append(embed)
|
||||||
|
await menus.menu(ctx, embed_pages, menus.DEFAULT_CONTROLS)
|
||||||
else:
|
else:
|
||||||
for page in pagify(commands, delims=[" ", "\n"]):
|
content = "\n".join(map("{0[0]:<12} : {0[1]}".format, results))
|
||||||
await ctx.author.send(box(page))
|
pages = list(map(box, pagify(content, page_length=2000, shorten_by=10)))
|
||||||
|
await menus.menu(ctx, pages, menus.DEFAULT_CONTROLS)
|
||||||
|
|
||||||
async def on_message(self, message):
|
async def on_message(self, message):
|
||||||
is_private = isinstance(message.channel, discord.abc.PrivateChannel)
|
is_private = isinstance(message.channel, discord.abc.PrivateChannel)
|
||||||
@@ -411,11 +429,11 @@ class CustomCommands(commands.Cog):
|
|||||||
|
|
||||||
async def cc_command(self, ctx, *cc_args, raw_response, **cc_kwargs) -> None:
|
async def cc_command(self, ctx, *cc_args, raw_response, **cc_kwargs) -> None:
|
||||||
cc_args = (*cc_args, *cc_kwargs.values())
|
cc_args = (*cc_args, *cc_kwargs.values())
|
||||||
results = re.findall(r"\{([^}]+)\}", raw_response)
|
results = re.findall(r"{([^}]+)\}", raw_response)
|
||||||
for result in results:
|
for result in results:
|
||||||
param = self.transform_parameter(result, ctx.message)
|
param = self.transform_parameter(result, ctx.message)
|
||||||
raw_response = raw_response.replace("{" + result + "}", param)
|
raw_response = raw_response.replace("{" + result + "}", param)
|
||||||
results = re.findall(r"\{((\d+)[^\.}]*(\.[^:}]+)?[^}]*)\}", raw_response)
|
results = re.findall(r"{((\d+)[^.}]*(\.[^:}]+)?[^}]*)\}", raw_response)
|
||||||
if results:
|
if results:
|
||||||
low = min(int(result[1]) for result in results)
|
low = min(int(result[1]) for result in results)
|
||||||
for result in results:
|
for result in results:
|
||||||
@@ -424,9 +442,10 @@ class CustomCommands(commands.Cog):
|
|||||||
raw_response = raw_response.replace("{" + result[0] + "}", arg)
|
raw_response = raw_response.replace("{" + result[0] + "}", arg)
|
||||||
await ctx.send(raw_response)
|
await ctx.send(raw_response)
|
||||||
|
|
||||||
def prepare_args(self, raw_response) -> Mapping[str, Parameter]:
|
@staticmethod
|
||||||
args = re.findall(r"\{(\d+)[^:}]*(:[^\.}]*)?[^}]*\}", raw_response)
|
def prepare_args(raw_response) -> Mapping[str, Parameter]:
|
||||||
default = [["ctx", Parameter("ctx", Parameter.POSITIONAL_OR_KEYWORD)]]
|
args = re.findall(r"{(\d+)[^:}]*(:[^.}]*)?[^}]*\}", raw_response)
|
||||||
|
default = [("ctx", Parameter("ctx", Parameter.POSITIONAL_OR_KEYWORD))]
|
||||||
if not args:
|
if not args:
|
||||||
return OrderedDict(default)
|
return OrderedDict(default)
|
||||||
allowed_builtins = {
|
allowed_builtins = {
|
||||||
@@ -466,7 +485,7 @@ class CustomCommands(commands.Cog):
|
|||||||
try:
|
try:
|
||||||
anno = getattr(discord, anno)
|
anno = getattr(discord, anno)
|
||||||
# force an AttributeError if there's no discord.py converter
|
# force an AttributeError if there's no discord.py converter
|
||||||
getattr(commands.converter, anno.__name__ + "Converter")
|
getattr(commands, anno.__name__ + "Converter")
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
anno = allowed_builtins.get(anno.lower(), Parameter.empty)
|
anno = allowed_builtins.get(anno.lower(), Parameter.empty)
|
||||||
if (
|
if (
|
||||||
@@ -520,7 +539,8 @@ class CustomCommands(commands.Cog):
|
|||||||
# only update cooldowns if the command isn't on cooldown
|
# only update cooldowns if the command isn't on cooldown
|
||||||
self.cooldowns.update(new_cooldowns)
|
self.cooldowns.update(new_cooldowns)
|
||||||
|
|
||||||
def transform_arg(self, result, attr, obj) -> str:
|
@staticmethod
|
||||||
|
def transform_arg(result, attr, obj) -> str:
|
||||||
attr = attr[1:] # strip initial dot
|
attr = attr[1:] # strip initial dot
|
||||||
if not attr:
|
if not attr:
|
||||||
return str(obj)
|
return str(obj)
|
||||||
@@ -530,7 +550,8 @@ class CustomCommands(commands.Cog):
|
|||||||
return raw_result
|
return raw_result
|
||||||
return str(getattr(obj, attr, raw_result))
|
return str(getattr(obj, attr, raw_result))
|
||||||
|
|
||||||
def transform_parameter(self, result, message) -> str:
|
@staticmethod
|
||||||
|
def transform_parameter(result, message) -> str:
|
||||||
"""
|
"""
|
||||||
For security reasons only specific objects are allowed
|
For security reasons only specific objects are allowed
|
||||||
Internals are ignored
|
Internals are ignored
|
||||||
@@ -554,3 +575,14 @@ class CustomCommands(commands.Cog):
|
|||||||
else:
|
else:
|
||||||
return raw_result
|
return raw_result
|
||||||
return str(getattr(first, second, raw_result))
|
return str(getattr(first, second, raw_result))
|
||||||
|
|
||||||
|
async def get_command_names(self, guild: discord.Guild) -> Set[str]:
|
||||||
|
"""Get all custom command names in a guild.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
Set[str]
|
||||||
|
A set of all custom command names.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return set(await CommandObj.get_commands(self.config.guild(guild)))
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import discord
|
import discord
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
|
from redbot.core.i18n import Translator
|
||||||
from .installable import Installable
|
from .installable import Installable
|
||||||
|
|
||||||
|
_ = Translator("Koala", __file__)
|
||||||
|
|
||||||
|
|
||||||
class InstalledCog(Installable):
|
class InstalledCog(Installable):
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -325,13 +325,12 @@ class Downloader(commands.Cog):
|
|||||||
You may only uninstall cogs which were previously installed
|
You may only uninstall cogs which were previously installed
|
||||||
by Downloader.
|
by Downloader.
|
||||||
"""
|
"""
|
||||||
# noinspection PyUnresolvedReferences,PyProtectedMember
|
|
||||||
real_name = cog.name
|
real_name = cog.name
|
||||||
|
|
||||||
poss_installed_path = (await self.cog_install_path()) / real_name
|
poss_installed_path = (await self.cog_install_path()) / real_name
|
||||||
if poss_installed_path.exists():
|
if poss_installed_path.exists():
|
||||||
|
ctx.bot.unload_extension(real_name)
|
||||||
await self._delete_cog(poss_installed_path)
|
await self._delete_cog(poss_installed_path)
|
||||||
# noinspection PyTypeChecker
|
|
||||||
await self._remove_from_installed(cog)
|
await self._remove_from_installed(cog)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("Cog `{cog_name}` was successfully uninstalled.").format(cog_name=real_name)
|
_("Cog `{cog_name}` was successfully uninstalled.").format(cog_name=real_name)
|
||||||
@@ -344,7 +343,7 @@ class Downloader(commands.Cog):
|
|||||||
" files manually if it is still usable."
|
" files manually if it is still usable."
|
||||||
" Also make sure you've unloaded the cog"
|
" Also make sure you've unloaded the cog"
|
||||||
" with `{prefix}unload {cog_name}`."
|
" with `{prefix}unload {cog_name}`."
|
||||||
).format(cog_name=real_name)
|
).format(prefix=ctx.prefix, cog_name=real_name)
|
||||||
)
|
)
|
||||||
|
|
||||||
@cog.command(name="update")
|
@cog.command(name="update")
|
||||||
@@ -372,13 +371,18 @@ class Downloader(commands.Cog):
|
|||||||
await self._reinstall_libraries(installed_and_updated)
|
await self._reinstall_libraries(installed_and_updated)
|
||||||
message = _("Cog update completed successfully.")
|
message = _("Cog update completed successfully.")
|
||||||
|
|
||||||
cognames = [c.name for c in installed_and_updated]
|
cognames = {c.name for c in installed_and_updated}
|
||||||
message += _("\nUpdated: ") + humanize_list(tuple(map(inline, cognames)))
|
message += _("\nUpdated: ") + humanize_list(tuple(map(inline, cognames)))
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("All installed cogs are already up to date."))
|
await ctx.send(_("All installed cogs are already up to date."))
|
||||||
return
|
return
|
||||||
await ctx.send(message)
|
await ctx.send(message)
|
||||||
|
|
||||||
|
cognames &= set(ctx.bot.extensions.keys()) # only reload loaded cogs
|
||||||
|
if not cognames:
|
||||||
|
return await ctx.send(
|
||||||
|
_("None of the updated cogs were previously loaded. Update complete.")
|
||||||
|
)
|
||||||
message = _("Would you like to reload the updated cogs?")
|
message = _("Would you like to reload the updated cogs?")
|
||||||
can_react = ctx.channel.permissions_for(ctx.me).add_reactions
|
can_react = ctx.channel.permissions_for(ctx.me).add_reactions
|
||||||
if not can_react:
|
if not can_react:
|
||||||
@@ -402,7 +406,6 @@ class Downloader(commands.Cog):
|
|||||||
if can_react:
|
if can_react:
|
||||||
with contextlib.suppress(discord.Forbidden):
|
with contextlib.suppress(discord.Forbidden):
|
||||||
await query.clear_reactions()
|
await query.clear_reactions()
|
||||||
|
|
||||||
await ctx.invoke(ctx.bot.get_cog("Core").reload, *cognames)
|
await ctx.invoke(ctx.bot.get_cog("Core").reload, *cognames)
|
||||||
else:
|
else:
|
||||||
if can_react:
|
if can_react:
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from typing import cast, Iterable
|
|||||||
import discord
|
import discord
|
||||||
|
|
||||||
from redbot.cogs.bank import check_global_setting_guildowner, check_global_setting_admin
|
from redbot.cogs.bank import check_global_setting_guildowner, check_global_setting_admin
|
||||||
from redbot.core import Config, bank, commands
|
from redbot.core import Config, bank, commands, errors
|
||||||
from redbot.core.i18n import Translator, cog_i18n
|
from redbot.core.i18n import Translator, cog_i18n
|
||||||
from redbot.core.utils.chat_formatting import box
|
from redbot.core.utils.chat_formatting import box
|
||||||
from redbot.core.utils.menus import menu, DEFAULT_CONTROLS
|
from redbot.core.utils.menus import menu, DEFAULT_CONTROLS
|
||||||
@@ -171,7 +171,7 @@ class Economy(commands.Cog):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
await bank.transfer_credits(from_, to, amount)
|
await bank.transfer_credits(from_, to, amount)
|
||||||
except ValueError as e:
|
except (ValueError, errors.BalanceTooHigh) as e:
|
||||||
return await ctx.send(str(e))
|
return await ctx.send(str(e))
|
||||||
|
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
@@ -195,36 +195,35 @@ class Economy(commands.Cog):
|
|||||||
author = ctx.author
|
author = ctx.author
|
||||||
currency = await bank.get_currency_name(ctx.guild)
|
currency = await bank.get_currency_name(ctx.guild)
|
||||||
|
|
||||||
|
try:
|
||||||
if creds.operation == "deposit":
|
if creds.operation == "deposit":
|
||||||
await bank.deposit_credits(to, creds.sum)
|
await bank.deposit_credits(to, creds.sum)
|
||||||
await ctx.send(
|
msg = _("{author} added {num} {currency} to {user}'s account.").format(
|
||||||
_("{author} added {num} {currency} to {user}'s account.").format(
|
|
||||||
author=author.display_name,
|
author=author.display_name,
|
||||||
num=creds.sum,
|
num=creds.sum,
|
||||||
currency=currency,
|
currency=currency,
|
||||||
user=to.display_name,
|
user=to.display_name,
|
||||||
)
|
)
|
||||||
)
|
|
||||||
elif creds.operation == "withdraw":
|
elif creds.operation == "withdraw":
|
||||||
await bank.withdraw_credits(to, creds.sum)
|
await bank.withdraw_credits(to, creds.sum)
|
||||||
await ctx.send(
|
msg = _("{author} removed {num} {currency} from {user}'s account.").format(
|
||||||
_("{author} removed {num} {currency} from {user}'s account.").format(
|
|
||||||
author=author.display_name,
|
author=author.display_name,
|
||||||
num=creds.sum,
|
num=creds.sum,
|
||||||
currency=currency,
|
currency=currency,
|
||||||
user=to.display_name,
|
user=to.display_name,
|
||||||
)
|
)
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
await bank.set_balance(to, creds.sum)
|
await bank.set_balance(to, creds.sum)
|
||||||
await ctx.send(
|
msg = _("{author} set {user}'s account balance to {num} {currency}.").format(
|
||||||
_("{author} set {users}'s account balance to {num} {currency}.").format(
|
|
||||||
author=author.display_name,
|
author=author.display_name,
|
||||||
num=creds.sum,
|
num=creds.sum,
|
||||||
currency=currency,
|
currency=currency,
|
||||||
user=to.display_name,
|
user=to.display_name,
|
||||||
)
|
)
|
||||||
)
|
except (ValueError, errors.BalanceTooHigh) as e:
|
||||||
|
await ctx.send(str(e))
|
||||||
|
else:
|
||||||
|
await ctx.send(msg)
|
||||||
|
|
||||||
@_bank.command()
|
@_bank.command()
|
||||||
@check_global_setting_guildowner()
|
@check_global_setting_guildowner()
|
||||||
@@ -260,7 +259,18 @@ class Economy(commands.Cog):
|
|||||||
if await bank.is_global(): # Role payouts will not be used
|
if await bank.is_global(): # Role payouts will not be used
|
||||||
next_payday = await self.config.user(author).next_payday()
|
next_payday = await self.config.user(author).next_payday()
|
||||||
if cur_time >= next_payday:
|
if cur_time >= next_payday:
|
||||||
|
try:
|
||||||
await bank.deposit_credits(author, await self.config.PAYDAY_CREDITS())
|
await bank.deposit_credits(author, await self.config.PAYDAY_CREDITS())
|
||||||
|
except errors.BalanceTooHigh as exc:
|
||||||
|
await bank.set_balance(author, exc.max_balance)
|
||||||
|
await ctx.send(
|
||||||
|
_(
|
||||||
|
"You've reached the maximum amount of {currency}! (**{balance:,}**) "
|
||||||
|
"Please spend some more \N{GRIMACING FACE}\n\n"
|
||||||
|
"You currently have {new_balance} {currency}."
|
||||||
|
).format(currency=credits_name, new_balance=exc.max_balance)
|
||||||
|
)
|
||||||
|
return
|
||||||
next_payday = cur_time + await self.config.PAYDAY_TIME()
|
next_payday = cur_time + await self.config.PAYDAY_TIME()
|
||||||
await self.config.user(author).next_payday.set(next_payday)
|
await self.config.user(author).next_payday.set(next_payday)
|
||||||
|
|
||||||
@@ -268,7 +278,7 @@ class Economy(commands.Cog):
|
|||||||
await ctx.send(
|
await ctx.send(
|
||||||
_(
|
_(
|
||||||
"{author.mention} Here, take some {currency}. "
|
"{author.mention} Here, take some {currency}. "
|
||||||
"Enjoy! (+{amount} {new_balance}!)\n\n"
|
"Enjoy! (+{amount} {currency}!)\n\n"
|
||||||
"You currently have {new_balance} {currency}.\n\n"
|
"You currently have {new_balance} {currency}.\n\n"
|
||||||
"You are currently #{pos} on the global leaderboard!"
|
"You are currently #{pos} on the global leaderboard!"
|
||||||
).format(
|
).format(
|
||||||
@@ -297,14 +307,25 @@ class Economy(commands.Cog):
|
|||||||
).PAYDAY_CREDITS() # Nice variable name
|
).PAYDAY_CREDITS() # Nice variable name
|
||||||
if role_credits > credit_amount:
|
if role_credits > credit_amount:
|
||||||
credit_amount = role_credits
|
credit_amount = role_credits
|
||||||
|
try:
|
||||||
await bank.deposit_credits(author, credit_amount)
|
await bank.deposit_credits(author, credit_amount)
|
||||||
|
except errors.BalanceTooHigh as exc:
|
||||||
|
await bank.set_balance(author, exc.max_balance)
|
||||||
|
await ctx.send(
|
||||||
|
_(
|
||||||
|
"You've reached the maximum amount of {currency}! "
|
||||||
|
"Please spend some more \N{GRIMACING FACE}\n\n"
|
||||||
|
"You currently have {new_balance} {currency}."
|
||||||
|
).format(currency=credits_name, new_balance=exc.max_balance)
|
||||||
|
)
|
||||||
|
return
|
||||||
next_payday = cur_time + await self.config.guild(guild).PAYDAY_TIME()
|
next_payday = cur_time + await self.config.guild(guild).PAYDAY_TIME()
|
||||||
await self.config.member(author).next_payday.set(next_payday)
|
await self.config.member(author).next_payday.set(next_payday)
|
||||||
pos = await bank.get_leaderboard_position(author)
|
pos = await bank.get_leaderboard_position(author)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_(
|
_(
|
||||||
"{author.mention} Here, take some {currency}. "
|
"{author.mention} Here, take some {currency}. "
|
||||||
"Enjoy! (+{amount} {new_balance}!)\n\n"
|
"Enjoy! (+{amount} {currency}!)\n\n"
|
||||||
"You currently have {new_balance} {currency}.\n\n"
|
"You currently have {new_balance} {currency}.\n\n"
|
||||||
"You are currently #{pos} on the global leaderboard!"
|
"You are currently #{pos} on the global leaderboard!"
|
||||||
).format(
|
).format(
|
||||||
@@ -444,7 +465,21 @@ class Economy(commands.Cog):
|
|||||||
then = await bank.get_balance(author)
|
then = await bank.get_balance(author)
|
||||||
pay = payout["payout"](bid)
|
pay = payout["payout"](bid)
|
||||||
now = then - bid + pay
|
now = then - bid + pay
|
||||||
|
try:
|
||||||
await bank.set_balance(author, now)
|
await bank.set_balance(author, now)
|
||||||
|
except errors.BalanceTooHigh as exc:
|
||||||
|
await bank.set_balance(author, exc.max_balance)
|
||||||
|
await channel.send(
|
||||||
|
_(
|
||||||
|
"You've reached the maximum amount of {currency}! "
|
||||||
|
"Please spend some more \N{GRIMACING FACE}\n{old_balance} -> {new_balance}!"
|
||||||
|
).format(
|
||||||
|
currency=await bank.get_currency_name(getattr(channel, "guild", None)),
|
||||||
|
old_balance=then,
|
||||||
|
new_balance=exc.max_balance,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
phrase = T_(payout["phrase"])
|
phrase = T_(payout["phrase"])
|
||||||
else:
|
else:
|
||||||
then = await bank.get_balance(author)
|
then = await bank.get_balance(author)
|
||||||
@@ -561,10 +596,10 @@ class Economy(commands.Cog):
|
|||||||
async def paydayamount(self, ctx: commands.Context, creds: int):
|
async def paydayamount(self, ctx: commands.Context, creds: int):
|
||||||
"""Set the amount earned each payday."""
|
"""Set the amount earned each payday."""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
credits_name = await bank.get_currency_name(guild)
|
if creds <= 0 or creds > bank.MAX_BALANCE:
|
||||||
if creds <= 0:
|
|
||||||
await ctx.send(_("Har har so funny."))
|
await ctx.send(_("Har har so funny."))
|
||||||
return
|
return
|
||||||
|
credits_name = await bank.get_currency_name(guild)
|
||||||
if await bank.is_global():
|
if await bank.is_global():
|
||||||
await self.config.PAYDAY_CREDITS.set(creds)
|
await self.config.PAYDAY_CREDITS.set(creds)
|
||||||
else:
|
else:
|
||||||
@@ -579,6 +614,9 @@ class Economy(commands.Cog):
|
|||||||
async def rolepaydayamount(self, ctx: commands.Context, role: discord.Role, creds: int):
|
async def rolepaydayamount(self, ctx: commands.Context, role: discord.Role, creds: int):
|
||||||
"""Set the amount earned each payday for a role."""
|
"""Set the amount earned each payday for a role."""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
|
if creds <= 0 or creds > bank.MAX_BALANCE:
|
||||||
|
await ctx.send(_("Har har so funny."))
|
||||||
|
return
|
||||||
credits_name = await bank.get_currency_name(guild)
|
credits_name = await bank.get_currency_name(guild)
|
||||||
if await bank.is_global():
|
if await bank.is_global():
|
||||||
await ctx.send(_("The bank must be per-server for per-role paydays to work."))
|
await ctx.send(_("The bank must be per-server for per-role paydays to work."))
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ class Mod(commands.Cog):
|
|||||||
yes_or_no=_("Yes") if respect_hierarchy else _("No")
|
yes_or_no=_("Yes") if respect_hierarchy else _("No")
|
||||||
)
|
)
|
||||||
msg += _("Delete delay: {num_seconds}\n").format(
|
msg += _("Delete delay: {num_seconds}\n").format(
|
||||||
num_seconds=_("{num} seconds").format(delete_delay)
|
num_seconds=_("{num} seconds").format(num=delete_delay)
|
||||||
if delete_delay != -1
|
if delete_delay != -1
|
||||||
else _("None")
|
else _("None")
|
||||||
)
|
)
|
||||||
@@ -748,7 +748,8 @@ class Mod(commands.Cog):
|
|||||||
to send the newly unbanned user
|
to send the newly unbanned user
|
||||||
:returns: :class:`Invite`"""
|
:returns: :class:`Invite`"""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
if guild.me.permissions.manage_guild:
|
my_perms: discord.Permissions = guild.me.guild_permissions
|
||||||
|
if my_perms.manage_guild or my_perms.administrator:
|
||||||
if "VANITY_URL" in guild.features:
|
if "VANITY_URL" in guild.features:
|
||||||
# guild has a vanity url so use it as the one to send
|
# guild has a vanity url so use it as the one to send
|
||||||
return await guild.vanity_invite()
|
return await guild.vanity_invite()
|
||||||
@@ -824,7 +825,7 @@ class Mod(commands.Cog):
|
|||||||
@admin_or_voice_permissions(mute_members=True, deafen_members=True)
|
@admin_or_voice_permissions(mute_members=True, deafen_members=True)
|
||||||
@bot_has_voice_permissions(mute_members=True, deafen_members=True)
|
@bot_has_voice_permissions(mute_members=True, deafen_members=True)
|
||||||
async def voiceunban(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
|
async def voiceunban(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
|
||||||
"""Unban a the user from speaking and listening in the server's voice channels."""
|
"""Unban a user from speaking and listening in the server's voice channels."""
|
||||||
user_voice_state = user.voice
|
user_voice_state = user.voice
|
||||||
if user_voice_state is None:
|
if user_voice_state is None:
|
||||||
await ctx.send(_("No voice state for that user!"))
|
await ctx.send(_("No voice state for that user!"))
|
||||||
@@ -892,20 +893,23 @@ class Mod(commands.Cog):
|
|||||||
author = ctx.author
|
author = ctx.author
|
||||||
if user_voice_state:
|
if user_voice_state:
|
||||||
channel = user_voice_state.channel
|
channel = user_voice_state.channel
|
||||||
if channel and channel.permissions_for(user).speak:
|
if channel:
|
||||||
overwrites = channel.overwrites_for(user)
|
audit_reason = get_audit_reason(author, reason)
|
||||||
overwrites.speak = False
|
|
||||||
audit_reason = get_audit_reason(ctx.author, reason)
|
success, issue = await self.mute_user(guild, channel, author, user, audit_reason)
|
||||||
await channel.set_permissions(user, overwrite=overwrites, reason=audit_reason)
|
|
||||||
|
if success:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("Muted {user} in channel {channel.name}").format(user, channel=channel)
|
_("Muted {user} in channel {channel.name}").format(
|
||||||
|
user=user, channel=channel
|
||||||
|
)
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
await modlog.create_case(
|
await modlog.create_case(
|
||||||
self.bot,
|
self.bot,
|
||||||
guild,
|
guild,
|
||||||
ctx.message.created_at,
|
ctx.message.created_at,
|
||||||
"boicemute",
|
"vmute",
|
||||||
user,
|
user,
|
||||||
author,
|
author,
|
||||||
reason,
|
reason,
|
||||||
@@ -914,12 +918,8 @@ class Mod(commands.Cog):
|
|||||||
)
|
)
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
await ctx.send(e)
|
await ctx.send(e)
|
||||||
return
|
else:
|
||||||
elif channel.permissions_for(user).speak is False:
|
await channel.send(issue)
|
||||||
await ctx.send(
|
|
||||||
_("That user is already muted in {channel}!").format(channel=channel.name)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("That user is not in a voice channel right now!"))
|
await ctx.send(_("That user is not in a voice channel right now!"))
|
||||||
else:
|
else:
|
||||||
@@ -937,13 +937,7 @@ class Mod(commands.Cog):
|
|||||||
author = ctx.message.author
|
author = ctx.message.author
|
||||||
channel = ctx.message.channel
|
channel = ctx.message.channel
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
|
audit_reason = get_audit_reason(author, reason)
|
||||||
if reason is None:
|
|
||||||
audit_reason = "Channel mute requested by {a} (ID {a.id})".format(a=author)
|
|
||||||
else:
|
|
||||||
audit_reason = "Channel mute requested by {a} (ID {a.id}). Reason: {r}".format(
|
|
||||||
a=author, r=reason
|
|
||||||
)
|
|
||||||
|
|
||||||
success, issue = await self.mute_user(guild, channel, author, user, audit_reason)
|
success, issue = await self.mute_user(guild, channel, author, user, audit_reason)
|
||||||
|
|
||||||
@@ -974,24 +968,10 @@ class Mod(commands.Cog):
|
|||||||
"""Mutes user in the server"""
|
"""Mutes user in the server"""
|
||||||
author = ctx.message.author
|
author = ctx.message.author
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
if reason is None:
|
audit_reason = get_audit_reason(author, reason)
|
||||||
audit_reason = "server mute requested by {author} (ID {author.id})".format(
|
|
||||||
author=author
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
audit_reason = (
|
|
||||||
"server mute requested by {author} (ID {author.id}). Reason: {reason}"
|
|
||||||
).format(author=author, reason=reason)
|
|
||||||
|
|
||||||
mute_success = []
|
mute_success = []
|
||||||
for channel in guild.channels:
|
for channel in guild.channels:
|
||||||
if not isinstance(channel, discord.TextChannel):
|
|
||||||
if channel.permissions_for(user).speak:
|
|
||||||
overwrites = channel.overwrites_for(user)
|
|
||||||
overwrites.speak = False
|
|
||||||
audit_reason = get_audit_reason(ctx.author, reason)
|
|
||||||
await channel.set_permissions(user, overwrite=overwrites, reason=audit_reason)
|
|
||||||
else:
|
|
||||||
success, issue = await self.mute_user(guild, channel, author, user, audit_reason)
|
success, issue = await self.mute_user(guild, channel, author, user, audit_reason)
|
||||||
mute_success.append((success, issue))
|
mute_success.append((success, issue))
|
||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
@@ -1014,7 +994,7 @@ class Mod(commands.Cog):
|
|||||||
async def mute_user(
|
async def mute_user(
|
||||||
self,
|
self,
|
||||||
guild: discord.Guild,
|
guild: discord.Guild,
|
||||||
channel: discord.TextChannel,
|
channel: discord.abc.GuildChannel,
|
||||||
author: discord.Member,
|
author: discord.Member,
|
||||||
user: discord.Member,
|
user: discord.Member,
|
||||||
reason: str,
|
reason: str,
|
||||||
@@ -1022,25 +1002,32 @@ class Mod(commands.Cog):
|
|||||||
"""Mutes the specified user in the specified channel"""
|
"""Mutes the specified user in the specified channel"""
|
||||||
overwrites = channel.overwrites_for(user)
|
overwrites = channel.overwrites_for(user)
|
||||||
permissions = channel.permissions_for(user)
|
permissions = channel.permissions_for(user)
|
||||||
perms_cache = await self.settings.member(user).perms_cache()
|
|
||||||
|
|
||||||
if overwrites.send_messages is False or permissions.send_messages is False:
|
if permissions.administrator:
|
||||||
|
return False, T_(mute_unmute_issues["is_admin"])
|
||||||
|
|
||||||
|
new_overs = {}
|
||||||
|
if not isinstance(channel, discord.TextChannel):
|
||||||
|
new_overs.update(speak=False)
|
||||||
|
if not isinstance(channel, discord.VoiceChannel):
|
||||||
|
new_overs.update(send_messages=False, add_reactions=False)
|
||||||
|
|
||||||
|
if all(getattr(permissions, p) is False for p in new_overs.keys()):
|
||||||
return False, T_(mute_unmute_issues["already_muted"])
|
return False, T_(mute_unmute_issues["already_muted"])
|
||||||
|
|
||||||
elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user):
|
elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user):
|
||||||
return False, T_(mute_unmute_issues["hierarchy_problem"])
|
return False, T_(mute_unmute_issues["hierarchy_problem"])
|
||||||
|
|
||||||
perms_cache[str(channel.id)] = {
|
old_overs = {k: getattr(overwrites, k) for k in new_overs}
|
||||||
"send_messages": overwrites.send_messages,
|
overwrites.update(**new_overs)
|
||||||
"add_reactions": overwrites.add_reactions,
|
|
||||||
}
|
|
||||||
overwrites.update(send_messages=False, add_reactions=False)
|
|
||||||
try:
|
try:
|
||||||
await channel.set_permissions(user, overwrite=overwrites, reason=reason)
|
await channel.set_permissions(user, overwrite=overwrites, reason=reason)
|
||||||
except discord.Forbidden:
|
except discord.Forbidden:
|
||||||
return False, T_(mute_unmute_issues["permissions_issue"])
|
return False, T_(mute_unmute_issues["permissions_issue"])
|
||||||
else:
|
else:
|
||||||
await self.settings.member(user).perms_cache.set(perms_cache)
|
await self.settings.member(user).set_raw(
|
||||||
|
"perms_cache", str(channel.id), value=old_overs
|
||||||
|
)
|
||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
@commands.group()
|
@commands.group()
|
||||||
@@ -1060,18 +1047,21 @@ class Mod(commands.Cog):
|
|||||||
):
|
):
|
||||||
"""Unmute a user in their current voice channel."""
|
"""Unmute a user in their current voice channel."""
|
||||||
user_voice_state = user.voice
|
user_voice_state = user.voice
|
||||||
|
guild = ctx.guild
|
||||||
|
author = ctx.author
|
||||||
if user_voice_state:
|
if user_voice_state:
|
||||||
channel = user_voice_state.channel
|
channel = user_voice_state.channel
|
||||||
if channel and channel.permissions_for(user).speak is False:
|
if channel:
|
||||||
overwrites = channel.overwrites_for(user)
|
audit_reason = get_audit_reason(author, reason)
|
||||||
overwrites.speak = None
|
|
||||||
audit_reason = get_audit_reason(ctx.author, reason)
|
success, message = await self.unmute_user(
|
||||||
await channel.set_permissions(user, overwrite=overwrites, reason=audit_reason)
|
guild, channel, author, user, audit_reason
|
||||||
author = ctx.author
|
)
|
||||||
guild = ctx.guild
|
|
||||||
|
if success:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("Unmuted {}#{} in channel {}").format(
|
_("Unmuted {user} in channel {channel.name}").format(
|
||||||
user.name, user.discriminator, channel.name
|
user=user, channel=channel
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
@@ -1079,7 +1069,7 @@ class Mod(commands.Cog):
|
|||||||
self.bot,
|
self.bot,
|
||||||
guild,
|
guild,
|
||||||
ctx.message.created_at,
|
ctx.message.created_at,
|
||||||
"voiceunmute",
|
"vunmute",
|
||||||
user,
|
user,
|
||||||
author,
|
author,
|
||||||
reason,
|
reason,
|
||||||
@@ -1088,9 +1078,8 @@ class Mod(commands.Cog):
|
|||||||
)
|
)
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
await ctx.send(e)
|
await ctx.send(e)
|
||||||
elif channel.permissions_for(user).speak:
|
else:
|
||||||
await ctx.send(_("That user is already unmuted in {}!").format(channel.name))
|
await ctx.send(_("Unmute failed. Reason: {}").format(message))
|
||||||
return
|
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("That user is not in a voice channel right now!"))
|
await ctx.send(_("That user is not in a voice channel right now!"))
|
||||||
else:
|
else:
|
||||||
@@ -1108,8 +1097,9 @@ class Mod(commands.Cog):
|
|||||||
channel = ctx.channel
|
channel = ctx.channel
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
|
audit_reason = get_audit_reason(author, reason)
|
||||||
|
|
||||||
success, message = await self.unmute_user(guild, channel, author, user)
|
success, message = await self.unmute_user(guild, channel, author, user, audit_reason)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
await ctx.send(_("User unmuted in this channel."))
|
await ctx.send(_("User unmuted in this channel."))
|
||||||
@@ -1140,16 +1130,11 @@ class Mod(commands.Cog):
|
|||||||
"""Unmute a user in this server."""
|
"""Unmute a user in this server."""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
|
audit_reason = get_audit_reason(author, reason)
|
||||||
|
|
||||||
unmute_success = []
|
unmute_success = []
|
||||||
for channel in guild.channels:
|
for channel in guild.channels:
|
||||||
if not isinstance(channel, discord.TextChannel):
|
success, message = await self.unmute_user(guild, channel, author, user, audit_reason)
|
||||||
if channel.permissions_for(user).speak is False:
|
|
||||||
overwrites = channel.overwrites_for(user)
|
|
||||||
overwrites.speak = None
|
|
||||||
audit_reason = get_audit_reason(author, reason)
|
|
||||||
await channel.set_permissions(user, overwrite=overwrites, reason=audit_reason)
|
|
||||||
success, message = await self.unmute_user(guild, channel, author, user)
|
|
||||||
unmute_success.append((success, message))
|
unmute_success.append((success, message))
|
||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
await ctx.send(_("User has been unmuted in this server."))
|
await ctx.send(_("User has been unmuted in this server."))
|
||||||
@@ -1170,45 +1155,37 @@ class Mod(commands.Cog):
|
|||||||
async def unmute_user(
|
async def unmute_user(
|
||||||
self,
|
self,
|
||||||
guild: discord.Guild,
|
guild: discord.Guild,
|
||||||
channel: discord.TextChannel,
|
channel: discord.abc.GuildChannel,
|
||||||
author: discord.Member,
|
author: discord.Member,
|
||||||
user: discord.Member,
|
user: discord.Member,
|
||||||
|
reason: str,
|
||||||
) -> (bool, str):
|
) -> (bool, str):
|
||||||
overwrites = channel.overwrites_for(user)
|
overwrites = channel.overwrites_for(user)
|
||||||
permissions = channel.permissions_for(user)
|
|
||||||
perms_cache = await self.settings.member(user).perms_cache()
|
perms_cache = await self.settings.member(user).perms_cache()
|
||||||
|
|
||||||
if overwrites.send_messages or permissions.send_messages:
|
if channel.id in perms_cache:
|
||||||
|
old_values = perms_cache[channel.id]
|
||||||
|
else:
|
||||||
|
old_values = {"send_messages": None, "add_reactions": None, "speak": None}
|
||||||
|
|
||||||
|
if all(getattr(overwrites, k) == v for k, v in old_values.items()):
|
||||||
return False, T_(mute_unmute_issues["already_unmuted"])
|
return False, T_(mute_unmute_issues["already_unmuted"])
|
||||||
|
|
||||||
elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user):
|
elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user):
|
||||||
return False, T_(mute_unmute_issues["hierarchy_problem"])
|
return False, T_(mute_unmute_issues["hierarchy_problem"])
|
||||||
|
|
||||||
if channel.id in perms_cache:
|
overwrites.update(**old_values)
|
||||||
old_values = perms_cache[channel.id]
|
|
||||||
else:
|
|
||||||
old_values = {"send_messages": None, "add_reactions": None}
|
|
||||||
overwrites.update(
|
|
||||||
send_messages=old_values["send_messages"], add_reactions=old_values["add_reactions"]
|
|
||||||
)
|
|
||||||
is_empty = self.are_overwrites_empty(overwrites)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not is_empty:
|
if overwrites.is_empty():
|
||||||
await channel.set_permissions(user, overwrite=overwrites)
|
|
||||||
else:
|
|
||||||
await channel.set_permissions(
|
await channel.set_permissions(
|
||||||
user, overwrite=cast(discord.PermissionOverwrite, None)
|
user, overwrite=cast(discord.PermissionOverwrite, None), reason=reason
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
await channel.set_permissions(user, overwrite=overwrites, reason=reason)
|
||||||
except discord.Forbidden:
|
except discord.Forbidden:
|
||||||
return False, T_(mute_unmute_issues["permissions_issue"])
|
return False, T_(mute_unmute_issues["permissions_issue"])
|
||||||
else:
|
else:
|
||||||
try:
|
await self.settings.member(user).clear_raw("perms_cache", str(channel.id))
|
||||||
del perms_cache[channel.id]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
await self.settings.member(user).perms_cache.set(perms_cache)
|
|
||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
@commands.group()
|
@commands.group()
|
||||||
@@ -1694,20 +1671,15 @@ class Mod(commands.Cog):
|
|||||||
while len(nick_list) > 20:
|
while len(nick_list) > 20:
|
||||||
nick_list.pop(0)
|
nick_list.pop(0)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def are_overwrites_empty(overwrites):
|
|
||||||
"""There is currently no cleaner way to check if a
|
|
||||||
PermissionOverwrite object is empty"""
|
|
||||||
return [p for p in iter(overwrites)] == [p for p in iter(discord.PermissionOverwrite())]
|
|
||||||
|
|
||||||
|
|
||||||
_ = lambda s: s
|
_ = lambda s: s
|
||||||
mute_unmute_issues = {
|
mute_unmute_issues = {
|
||||||
"already_muted": _("That user can't send messages in this channel."),
|
"already_muted": _("That user can't send messages in this channel."),
|
||||||
"already_unmuted": _("That user isn't muted in this channel!"),
|
"already_unmuted": _("That user isn't muted in this channel."),
|
||||||
"hierarchy_problem": _(
|
"hierarchy_problem": _(
|
||||||
"I cannot let you do that. You are not higher than " "the user in the role hierarchy."
|
"I cannot let you do that. You are not higher than the user in the role hierarchy."
|
||||||
),
|
),
|
||||||
|
"is_admin": _("That user cannot be muted, as they have the Administrator permission."),
|
||||||
"permissions_issue": _(
|
"permissions_issue": _(
|
||||||
"Failed to mute user. I need the manage roles "
|
"Failed to mute user. I need the manage roles "
|
||||||
"permission and the user I'm muting must be "
|
"permission and the user I'm muting must be "
|
||||||
|
|||||||
@@ -542,7 +542,8 @@ class Permissions(commands.Cog):
|
|||||||
continue
|
continue
|
||||||
conf = self.config.custom(category)
|
conf = self.config.custom(category)
|
||||||
for cmd_name, cmd_rules in rules_dict.items():
|
for cmd_name, cmd_rules in rules_dict.items():
|
||||||
await conf.set_raw(cmd_name, guild_id, value=cmd_rules)
|
cmd_rules = {str(model_id): rule for model_id, rule in cmd_rules.items()}
|
||||||
|
await conf.set_raw(cmd_name, str(guild_id), value=cmd_rules)
|
||||||
cmd_obj = getter(cmd_name)
|
cmd_obj = getter(cmd_name)
|
||||||
if cmd_obj is not None:
|
if cmd_obj is not None:
|
||||||
self._load_rules_for(cmd_obj, {guild_id: cmd_rules})
|
self._load_rules_for(cmd_obj, {guild_id: cmd_rules})
|
||||||
@@ -651,14 +652,14 @@ class Permissions(commands.Cog):
|
|||||||
if category in old_rules:
|
if category in old_rules:
|
||||||
for name, rules in old_rules[category].items():
|
for name, rules in old_rules[category].items():
|
||||||
these_rules = new_rules.setdefault(name, {})
|
these_rules = new_rules.setdefault(name, {})
|
||||||
guild_rules = these_rules.setdefault(guild_id, {})
|
guild_rules = these_rules.setdefault(str(guild_id), {})
|
||||||
# Since allow rules would take precedence if the same model ID
|
# Since allow rules would take precedence if the same model ID
|
||||||
# sat in both the allow and deny list, we add the deny entries
|
# sat in both the allow and deny list, we add the deny entries
|
||||||
# first and let any conflicting allow entries overwrite.
|
# first and let any conflicting allow entries overwrite.
|
||||||
for model_id in rules.get("deny", []):
|
for model_id in rules.get("deny", []):
|
||||||
guild_rules[model_id] = False
|
guild_rules[str(model_id)] = False
|
||||||
for model_id in rules.get("allow", []):
|
for model_id in rules.get("allow", []):
|
||||||
guild_rules[model_id] = True
|
guild_rules[str(model_id)] = True
|
||||||
if "default" in rules:
|
if "default" in rules:
|
||||||
default = rules["default"]
|
default = rules["default"]
|
||||||
if default == "allow":
|
if default == "allow":
|
||||||
@@ -689,7 +690,9 @@ class Permissions(commands.Cog):
|
|||||||
"""
|
"""
|
||||||
for guild_id, guild_dict in _int_key_map(rule_dict.items()):
|
for guild_id, guild_dict in _int_key_map(rule_dict.items()):
|
||||||
for model_id, rule in _int_key_map(guild_dict.items()):
|
for model_id, rule in _int_key_map(guild_dict.items()):
|
||||||
if rule is True:
|
if model_id == "default":
|
||||||
|
cog_or_command.set_default_rule(rule, guild_id=guild_id)
|
||||||
|
elif rule is True:
|
||||||
cog_or_command.allow_for(model_id, guild_id=guild_id)
|
cog_or_command.allow_for(model_id, guild_id=guild_id)
|
||||||
elif rule is False:
|
elif rule is False:
|
||||||
cog_or_command.deny_to(model_id, guild_id=guild_id)
|
cog_or_command.deny_to(model_id, guild_id=guild_id)
|
||||||
@@ -724,9 +727,16 @@ class Permissions(commands.Cog):
|
|||||||
rules.
|
rules.
|
||||||
"""
|
"""
|
||||||
for guild_id, guild_dict in _int_key_map(rule_dict.items()):
|
for guild_id, guild_dict in _int_key_map(rule_dict.items()):
|
||||||
for model_id in map(int, guild_dict.keys()):
|
for model_id in guild_dict.keys():
|
||||||
cog_or_command.clear_rule_for(model_id, guild_id)
|
if model_id == "default":
|
||||||
|
cog_or_command.set_default_rule(None, guild_id=guild_id)
|
||||||
|
else:
|
||||||
|
cog_or_command.clear_rule_for(int(model_id), guild_id=guild_id)
|
||||||
|
|
||||||
|
|
||||||
def _int_key_map(items_view: ItemsView[str, Any]) -> Iterator[Tuple[int, Any]]:
|
def _int_key_map(items_view: ItemsView[str, Any]) -> Iterator[Tuple[Union[str, int], Any]]:
|
||||||
return map(lambda tup: (int(tup[0]), tup[1]), items_view)
|
for k, v in items_view:
|
||||||
|
if k == "default":
|
||||||
|
yield k, v
|
||||||
|
else:
|
||||||
|
yield int(k), v
|
||||||
|
|||||||
@@ -626,7 +626,12 @@ class Streams(commands.Cog):
|
|||||||
raw_stream["_messages_cache"] = []
|
raw_stream["_messages_cache"] = []
|
||||||
for raw_msg in raw_msg_cache:
|
for raw_msg in raw_msg_cache:
|
||||||
chn = self.bot.get_channel(raw_msg["channel"])
|
chn = self.bot.get_channel(raw_msg["channel"])
|
||||||
|
if chn is not None:
|
||||||
|
try:
|
||||||
msg = await chn.get_message(raw_msg["message"])
|
msg = await chn.get_message(raw_msg["message"])
|
||||||
|
except discord.HTTPException:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
raw_stream["_messages_cache"].append(msg)
|
raw_stream["_messages_cache"].append(msg)
|
||||||
token = await self.db.tokens.get_raw(_class.__name__, default=None)
|
token = await self.db.tokens.get_raw(_class.__name__, default=None)
|
||||||
if token is not None:
|
if token is not None:
|
||||||
@@ -646,7 +651,12 @@ class Streams(commands.Cog):
|
|||||||
raw_community["_messages_cache"] = []
|
raw_community["_messages_cache"] = []
|
||||||
for raw_msg in raw_msg_cache:
|
for raw_msg in raw_msg_cache:
|
||||||
chn = self.bot.get_channel(raw_msg["channel"])
|
chn = self.bot.get_channel(raw_msg["channel"])
|
||||||
|
if chn is not None:
|
||||||
|
try:
|
||||||
msg = await chn.get_message(raw_msg["message"])
|
msg = await chn.get_message(raw_msg["message"])
|
||||||
|
except discord.HTTPException:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
raw_community["_messages_cache"].append(msg)
|
raw_community["_messages_cache"].append(msg)
|
||||||
token = await self.db.tokens.get_raw(_class.__name__, default=None)
|
token = await self.db.tokens.get_raw(_class.__name__, default=None)
|
||||||
communities.append(_class(token=token, **raw_community))
|
communities.append(_class(token=token, **raw_community))
|
||||||
|
|||||||
@@ -322,9 +322,9 @@ def _parse_answers(answers):
|
|||||||
for answer in answers:
|
for answer in answers:
|
||||||
if isinstance(answer, bool):
|
if isinstance(answer, bool):
|
||||||
if answer is True:
|
if answer is True:
|
||||||
ret.extend(["True", "Yes", _("Yes")])
|
ret.extend(["True", "Yes", "On"])
|
||||||
else:
|
else:
|
||||||
ret.extend(["False", "No", _("No")])
|
ret.extend(["False", "No", "Off"])
|
||||||
else:
|
else:
|
||||||
ret.append(str(answer))
|
ret.append(str(answer))
|
||||||
# Uniquify list
|
# Uniquify list
|
||||||
|
|||||||
@@ -19,9 +19,11 @@ async def warning_points_add_check(
|
|||||||
act = {}
|
act = {}
|
||||||
async with guild_settings.actions() as registered_actions:
|
async with guild_settings.actions() as registered_actions:
|
||||||
for a in registered_actions:
|
for a in registered_actions:
|
||||||
|
# Actions are sorted in decreasing order of points.
|
||||||
|
# The first action we find where the user is above the threshold will be the
|
||||||
|
# highest action we can take.
|
||||||
if points >= a["points"]:
|
if points >= a["points"]:
|
||||||
act = a
|
act = a
|
||||||
else:
|
|
||||||
break
|
break
|
||||||
if act and act["exceed_command"] is not None: # some action needs to be taken
|
if act and act["exceed_command"] is not None: # some action needs to be taken
|
||||||
await create_and_invoke_context(ctx, act["exceed_command"], user)
|
await create_and_invoke_context(ctx, act["exceed_command"], user)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from redbot.cogs.warnings.helpers import (
|
|||||||
get_command_for_dropping_points,
|
get_command_for_dropping_points,
|
||||||
warning_points_remove_check,
|
warning_points_remove_check,
|
||||||
)
|
)
|
||||||
from redbot.core import Config, modlog, checks, commands
|
from redbot.core import Config, checks, commands
|
||||||
from redbot.core.bot import Red
|
from redbot.core.bot import Red
|
||||||
from redbot.core.i18n import Translator, cog_i18n
|
from redbot.core.i18n import Translator, cog_i18n
|
||||||
from redbot.core.utils.mod import is_admin_or_superior
|
from redbot.core.utils.mod import is_admin_or_superior
|
||||||
@@ -34,15 +34,14 @@ class Warnings(commands.Cog):
|
|||||||
self.config.register_guild(**self.default_guild)
|
self.config.register_guild(**self.default_guild)
|
||||||
self.config.register_member(**self.default_member)
|
self.config.register_member(**self.default_member)
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
loop.create_task(self.register_warningtype())
|
|
||||||
|
|
||||||
@staticmethod
|
# We're not utilising modlog yet - no need to register a casetype
|
||||||
async def register_warningtype():
|
# @staticmethod
|
||||||
try:
|
# async def register_warningtype():
|
||||||
await modlog.register_casetype("warning", True, "\N{WARNING SIGN}", "Warning", None)
|
# try:
|
||||||
except RuntimeError:
|
# await modlog.register_casetype("warning", True, "\N{WARNING SIGN}", "Warning", None)
|
||||||
pass
|
# except RuntimeError:
|
||||||
|
# pass
|
||||||
|
|
||||||
@commands.group()
|
@commands.group()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
|
|||||||
@@ -148,5 +148,5 @@ class VersionInfo:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
__version__ = "3.0.0rc1.post1"
|
__version__ = "3.0.0rc2"
|
||||||
version_info = VersionInfo.from_str(__version__)
|
version_info = VersionInfo.from_str(__version__)
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ from typing import Union, List, Optional
|
|||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
|
||||||
from redbot.core import Config
|
from . import Config, errors
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
"MAX_BALANCE",
|
||||||
"Account",
|
"Account",
|
||||||
"get_balance",
|
"get_balance",
|
||||||
"set_balance",
|
"set_balance",
|
||||||
@@ -26,6 +27,8 @@ __all__ = [
|
|||||||
"set_default_balance",
|
"set_default_balance",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
MAX_BALANCE = 2 ** 63 - 1
|
||||||
|
|
||||||
_DEFAULT_GLOBAL = {
|
_DEFAULT_GLOBAL = {
|
||||||
"is_global": False,
|
"is_global": False,
|
||||||
"bank_name": "Twentysix bank",
|
"bank_name": "Twentysix bank",
|
||||||
@@ -170,10 +173,22 @@ async def set_balance(member: discord.Member, amount: int) -> int:
|
|||||||
------
|
------
|
||||||
ValueError
|
ValueError
|
||||||
If attempting to set the balance to a negative number.
|
If attempting to set the balance to a negative number.
|
||||||
|
BalanceTooHigh
|
||||||
|
If attempting to set the balance to a value greater than
|
||||||
|
``bank.MAX_BALANCE``
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if amount < 0:
|
if amount < 0:
|
||||||
raise ValueError("Not allowed to have negative balance.")
|
raise ValueError("Not allowed to have negative balance.")
|
||||||
|
if amount > MAX_BALANCE:
|
||||||
|
currency = (
|
||||||
|
await get_currency_name()
|
||||||
|
if await is_global()
|
||||||
|
else await get_currency_name(member.guild)
|
||||||
|
)
|
||||||
|
raise errors.BalanceTooHigh(
|
||||||
|
user=member.display_name, max_balance=MAX_BALANCE, currency_name=currency
|
||||||
|
)
|
||||||
if await is_global():
|
if await is_global():
|
||||||
group = _conf.user(member)
|
group = _conf.user(member)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
import inspect
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
@@ -11,10 +12,10 @@ import discord
|
|||||||
import sys
|
import sys
|
||||||
from discord.ext.commands import when_mentioned_or
|
from discord.ext.commands import when_mentioned_or
|
||||||
|
|
||||||
|
from . import Config, i18n, commands, errors
|
||||||
from .cog_manager import CogManager
|
from .cog_manager import CogManager
|
||||||
from . import Config, i18n, commands
|
|
||||||
from .rpc import RPCMixin
|
|
||||||
from .help_formatter import Help, help as help_
|
from .help_formatter import Help, help as help_
|
||||||
|
from .rpc import RPCMixin
|
||||||
from .sentry import SentryManager
|
from .sentry import SentryManager
|
||||||
from .utils import common_filters
|
from .utils import common_filters
|
||||||
|
|
||||||
@@ -217,7 +218,7 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
|||||||
async def load_extension(self, spec: ModuleSpec):
|
async def load_extension(self, spec: ModuleSpec):
|
||||||
name = spec.name.split(".")[-1]
|
name = spec.name.split(".")[-1]
|
||||||
if name in self.extensions:
|
if name in self.extensions:
|
||||||
raise discord.ClientException(f"there is already a package named {name} loaded")
|
raise errors.PackageAlreadyLoaded(spec)
|
||||||
|
|
||||||
lib = spec.loader.load_module()
|
lib = spec.loader.load_module()
|
||||||
if not hasattr(lib, "setup"):
|
if not hasattr(lib, "setup"):
|
||||||
@@ -236,16 +237,9 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
|||||||
if cog is None:
|
if cog is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
for when in ("before", "after"):
|
for cls in inspect.getmro(cog.__class__):
|
||||||
try:
|
try:
|
||||||
hook = getattr(cog, f"_{cog.__class__.__name__}__red_permissions_{when}")
|
hook = getattr(cog, f"_{cls.__name__}__permissions_hook")
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
self.remove_permissions_hook(hook, when)
|
|
||||||
|
|
||||||
try:
|
|
||||||
hook = getattr(cog, f"_{cog.__class__.__name__}__red_permissions_before")
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
@@ -390,10 +384,17 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
|||||||
)
|
)
|
||||||
if not hasattr(cog, "requires"):
|
if not hasattr(cog, "requires"):
|
||||||
commands.Cog.__init__(cog)
|
commands.Cog.__init__(cog)
|
||||||
|
|
||||||
|
for cls in inspect.getmro(cog.__class__):
|
||||||
|
try:
|
||||||
|
hook = getattr(cog, f"_{cls.__name__}__permissions_hook")
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.add_permissions_hook(hook)
|
||||||
|
|
||||||
for attr in dir(cog):
|
for attr in dir(cog):
|
||||||
_attr = getattr(cog, attr)
|
_attr = getattr(cog, attr)
|
||||||
if attr == f"_{cog.__class__.__name__}__permissions_hook":
|
|
||||||
self.add_permissions_hook(_attr)
|
|
||||||
if isinstance(_attr, discord.ext.commands.Command) and not isinstance(
|
if isinstance(_attr, discord.ext.commands.Command) and not isinstance(
|
||||||
_attr, commands.Command
|
_attr, commands.Command
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -39,17 +39,21 @@ class _ValueCtxManager(Awaitable[_T], AsyncContextManager[_T]):
|
|||||||
|
|
||||||
async def __aenter__(self):
|
async def __aenter__(self):
|
||||||
self.raw_value = await self
|
self.raw_value = await self
|
||||||
self.__original_value = deepcopy(self.raw_value)
|
|
||||||
if not isinstance(self.raw_value, (list, dict)):
|
if not isinstance(self.raw_value, (list, dict)):
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
"Type of retrieved value must be mutable (i.e. "
|
"Type of retrieved value must be mutable (i.e. "
|
||||||
"list or dict) in order to use a config value as "
|
"list or dict) in order to use a config value as "
|
||||||
"a context manager."
|
"a context manager."
|
||||||
)
|
)
|
||||||
|
self.__original_value = deepcopy(self.raw_value)
|
||||||
return self.raw_value
|
return self.raw_value
|
||||||
|
|
||||||
async def __aexit__(self, exc_type, exc, tb):
|
async def __aexit__(self, exc_type, exc, tb):
|
||||||
if self.raw_value != self.__original_value:
|
if isinstance(self.raw_value, dict):
|
||||||
|
raw_value = _str_key_dict(self.raw_value)
|
||||||
|
else:
|
||||||
|
raw_value = self.raw_value
|
||||||
|
if raw_value != self.__original_value:
|
||||||
await self.value_obj.set(self.raw_value)
|
await self.value_obj.set(self.raw_value)
|
||||||
|
|
||||||
|
|
||||||
@@ -58,7 +62,7 @@ class Value:
|
|||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
----------
|
----------
|
||||||
identifiers : `tuple` of `str`
|
identifiers : Tuple[str]
|
||||||
This attribute provides all the keys necessary to get a specific data
|
This attribute provides all the keys necessary to get a specific data
|
||||||
element from a json document.
|
element from a json document.
|
||||||
default
|
default
|
||||||
@@ -69,15 +73,10 @@ class Value:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, identifiers: Tuple[str], default_value, driver):
|
def __init__(self, identifiers: Tuple[str], default_value, driver):
|
||||||
self._identifiers = identifiers
|
self.identifiers = identifiers
|
||||||
self.default = default_value
|
self.default = default_value
|
||||||
|
|
||||||
self.driver = driver
|
self.driver = driver
|
||||||
|
|
||||||
@property
|
|
||||||
def identifiers(self):
|
|
||||||
return tuple(str(i) for i in self._identifiers)
|
|
||||||
|
|
||||||
async def _get(self, default=...):
|
async def _get(self, default=...):
|
||||||
try:
|
try:
|
||||||
ret = await self.driver.get(*self.identifiers)
|
ret = await self.driver.get(*self.identifiers)
|
||||||
@@ -149,6 +148,8 @@ class Value:
|
|||||||
The new literal value of this attribute.
|
The new literal value of this attribute.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if isinstance(value, dict):
|
||||||
|
value = _str_key_dict(value)
|
||||||
await self.driver.set(*self.identifiers, value=value)
|
await self.driver.set(*self.identifiers, value=value)
|
||||||
|
|
||||||
async def clear(self):
|
async def clear(self):
|
||||||
@@ -192,7 +193,10 @@ class Group(Value):
|
|||||||
async def _get(self, default: Dict[str, Any] = ...) -> Dict[str, Any]:
|
async def _get(self, default: Dict[str, Any] = ...) -> Dict[str, Any]:
|
||||||
default = default if default is not ... else self.defaults
|
default = default if default is not ... else self.defaults
|
||||||
raw = await super()._get(default)
|
raw = await super()._get(default)
|
||||||
|
if isinstance(raw, dict):
|
||||||
return self.nested_update(raw, default)
|
return self.nested_update(raw, default)
|
||||||
|
else:
|
||||||
|
return raw
|
||||||
|
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
def __getattr__(self, item: str) -> Union["Group", Value]:
|
def __getattr__(self, item: str) -> Union["Group", Value]:
|
||||||
@@ -238,7 +242,7 @@ class Group(Value):
|
|||||||
else:
|
else:
|
||||||
return Value(identifiers=new_identifiers, default_value=None, driver=self.driver)
|
return Value(identifiers=new_identifiers, default_value=None, driver=self.driver)
|
||||||
|
|
||||||
async def clear_raw(self, *nested_path: str):
|
async def clear_raw(self, *nested_path: Any):
|
||||||
"""
|
"""
|
||||||
Allows a developer to clear data as if it was stored in a standard
|
Allows a developer to clear data as if it was stored in a standard
|
||||||
Python dictionary.
|
Python dictionary.
|
||||||
@@ -254,44 +258,44 @@ class Group(Value):
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
nested_path : str
|
nested_path : Any
|
||||||
Multiple arguments that mirror the arguments passed in for nested
|
Multiple arguments that mirror the arguments passed in for nested
|
||||||
dict access.
|
dict access. These are casted to `str` for you.
|
||||||
"""
|
"""
|
||||||
path = [str(p) for p in nested_path]
|
path = [str(p) for p in nested_path]
|
||||||
await self.driver.clear(*self.identifiers, *path)
|
await self.driver.clear(*self.identifiers, *path)
|
||||||
|
|
||||||
def is_group(self, item: str) -> bool:
|
def is_group(self, item: Any) -> bool:
|
||||||
"""A helper method for `__getattr__`. Most developers will have no need
|
"""A helper method for `__getattr__`. Most developers will have no need
|
||||||
to use this.
|
to use this.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
item : str
|
item : Any
|
||||||
See `__getattr__`.
|
See `__getattr__`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
default = self._defaults.get(item)
|
default = self._defaults.get(str(item))
|
||||||
return isinstance(default, dict)
|
return isinstance(default, dict)
|
||||||
|
|
||||||
def is_value(self, item: str) -> bool:
|
def is_value(self, item: Any) -> bool:
|
||||||
"""A helper method for `__getattr__`. Most developers will have no need
|
"""A helper method for `__getattr__`. Most developers will have no need
|
||||||
to use this.
|
to use this.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
item : str
|
item : Any
|
||||||
See `__getattr__`.
|
See `__getattr__`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
default = self._defaults[item]
|
default = self._defaults[str(item)]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return not isinstance(default, dict)
|
return not isinstance(default, dict)
|
||||||
|
|
||||||
def get_attr(self, item: str):
|
def get_attr(self, item: Union[int, str]):
|
||||||
"""Manually get an attribute of this Group.
|
"""Manually get an attribute of this Group.
|
||||||
|
|
||||||
This is available to use as an alternative to using normal Python
|
This is available to use as an alternative to using normal Python
|
||||||
@@ -312,7 +316,8 @@ class Group(Value):
|
|||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
item : str
|
item : str
|
||||||
The name of the data field in `Config`.
|
The name of the data field in `Config`. This is casted to
|
||||||
|
`str` for you.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@@ -320,9 +325,11 @@ class Group(Value):
|
|||||||
The attribute which was requested.
|
The attribute which was requested.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if isinstance(item, int):
|
||||||
|
item = str(item)
|
||||||
return self.__getattr__(item)
|
return self.__getattr__(item)
|
||||||
|
|
||||||
async def get_raw(self, *nested_path: str, default=...):
|
async def get_raw(self, *nested_path: Any, default=...):
|
||||||
"""
|
"""
|
||||||
Allows a developer to access data as if it was stored in a standard
|
Allows a developer to access data as if it was stored in a standard
|
||||||
Python dictionary.
|
Python dictionary.
|
||||||
@@ -345,7 +352,7 @@ class Group(Value):
|
|||||||
----------
|
----------
|
||||||
nested_path : str
|
nested_path : str
|
||||||
Multiple arguments that mirror the arguments passed in for nested
|
Multiple arguments that mirror the arguments passed in for nested
|
||||||
dict access.
|
dict access. These are casted to `str` for you.
|
||||||
default
|
default
|
||||||
Default argument for the value attempting to be accessed. If the
|
Default argument for the value attempting to be accessed. If the
|
||||||
value does not exist the default will be returned.
|
value does not exist the default will be returned.
|
||||||
@@ -410,7 +417,6 @@ class Group(Value):
|
|||||||
|
|
||||||
If no defaults are passed, then the instance attribute 'defaults'
|
If no defaults are passed, then the instance attribute 'defaults'
|
||||||
will be used.
|
will be used.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if defaults is ...:
|
if defaults is ...:
|
||||||
defaults = self.defaults
|
defaults = self.defaults
|
||||||
@@ -428,7 +434,7 @@ class Group(Value):
|
|||||||
raise ValueError("You may only set the value of a group to be a dict.")
|
raise ValueError("You may only set the value of a group to be a dict.")
|
||||||
await super().set(value)
|
await super().set(value)
|
||||||
|
|
||||||
async def set_raw(self, *nested_path: str, value):
|
async def set_raw(self, *nested_path: Any, value):
|
||||||
"""
|
"""
|
||||||
Allows a developer to set data as if it was stored in a standard
|
Allows a developer to set data as if it was stored in a standard
|
||||||
Python dictionary.
|
Python dictionary.
|
||||||
@@ -444,13 +450,15 @@ class Group(Value):
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
nested_path : str
|
nested_path : Any
|
||||||
Multiple arguments that mirror the arguments passed in for nested
|
Multiple arguments that mirror the arguments passed in for nested
|
||||||
dict access.
|
`dict` access. These are casted to `str` for you.
|
||||||
value
|
value
|
||||||
The value to store.
|
The value to store.
|
||||||
"""
|
"""
|
||||||
path = [str(p) for p in nested_path]
|
path = [str(p) for p in nested_path]
|
||||||
|
if isinstance(value, dict):
|
||||||
|
value = _str_key_dict(value)
|
||||||
await self.driver.set(*self.identifiers, *path, value=value)
|
await self.driver.set(*self.identifiers, *path, value=value)
|
||||||
|
|
||||||
|
|
||||||
@@ -461,9 +469,11 @@ class Config:
|
|||||||
`get_core_conf` for Config used in the core package.
|
`get_core_conf` for Config used in the core package.
|
||||||
|
|
||||||
.. important::
|
.. important::
|
||||||
Most config data should be accessed through its respective group method (e.g. :py:meth:`guild`)
|
Most config data should be accessed through its respective
|
||||||
however the process for accessing global data is a bit different. There is no :python:`global` method
|
group method (e.g. :py:meth:`guild`) however the process for
|
||||||
because global data is accessed by normal attribute access::
|
accessing global data is a bit different. There is no
|
||||||
|
:python:`global` method because global data is accessed by
|
||||||
|
normal attribute access::
|
||||||
|
|
||||||
await conf.foo()
|
await conf.foo()
|
||||||
|
|
||||||
@@ -548,7 +558,7 @@ class Config:
|
|||||||
A new Config object.
|
A new Config object.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if cog_instance is None and not cog_name is None:
|
if cog_instance is None and cog_name is not None:
|
||||||
cog_path_override = cog_data_path(raw_name=cog_name)
|
cog_path_override = cog_data_path(raw_name=cog_name)
|
||||||
else:
|
else:
|
||||||
cog_path_override = cog_data_path(cog_instance=cog_instance)
|
cog_path_override = cog_data_path(cog_instance=cog_instance)
|
||||||
@@ -635,11 +645,8 @@ class Config:
|
|||||||
def _get_defaults_dict(key: str, value) -> dict:
|
def _get_defaults_dict(key: str, value) -> dict:
|
||||||
"""
|
"""
|
||||||
Since we're allowing nested config stuff now, not storing the
|
Since we're allowing nested config stuff now, not storing the
|
||||||
_defaults as a flat dict sounds like a good idea. May turn
|
_defaults as a flat dict sounds like a good idea. May turn out
|
||||||
out to be an awful one but we'll see.
|
to be an awful one but we'll see.
|
||||||
:param key:
|
|
||||||
:param value:
|
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
ret = {}
|
ret = {}
|
||||||
partial = ret
|
partial = ret
|
||||||
@@ -655,15 +662,12 @@ class Config:
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _update_defaults(to_add: dict, _partial: dict):
|
def _update_defaults(to_add: Dict[str, Any], _partial: Dict[str, Any]):
|
||||||
"""
|
"""
|
||||||
This tries to update the _defaults dictionary with the nested
|
This tries to update the _defaults dictionary with the nested
|
||||||
partial dict generated by _get_defaults_dict. This WILL
|
partial dict generated by _get_defaults_dict. This WILL
|
||||||
throw an error if you try to have both a value and a group
|
throw an error if you try to have both a value and a group
|
||||||
registered under the same name.
|
registered under the same name.
|
||||||
:param to_add:
|
|
||||||
:param _partial:
|
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
for k, v in to_add.items():
|
for k, v in to_add.items():
|
||||||
val_is_dict = isinstance(v, dict)
|
val_is_dict = isinstance(v, dict)
|
||||||
@@ -679,7 +683,7 @@ class Config:
|
|||||||
else:
|
else:
|
||||||
_partial[k] = v
|
_partial[k] = v
|
||||||
|
|
||||||
def _register_default(self, key: str, **kwargs):
|
def _register_default(self, key: str, **kwargs: Any):
|
||||||
if key not in self._defaults:
|
if key not in self._defaults:
|
||||||
self._defaults[key] = {}
|
self._defaults[key] = {}
|
||||||
|
|
||||||
@@ -720,8 +724,8 @@ class Config:
|
|||||||
**_defaults
|
**_defaults
|
||||||
)
|
)
|
||||||
|
|
||||||
You can do the same thing without a :python:`_defaults` dict by using double underscore as a variable
|
You can do the same thing without a :python:`_defaults` dict by
|
||||||
name separator::
|
using double underscore as a variable name separator::
|
||||||
|
|
||||||
# This is equivalent to the previous example
|
# This is equivalent to the previous example
|
||||||
conf.register_global(
|
conf.register_global(
|
||||||
@@ -802,7 +806,7 @@ class Config:
|
|||||||
The guild's Group object.
|
The guild's Group object.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self._get_base_group(self.GUILD, guild.id)
|
return self._get_base_group(self.GUILD, str(guild.id))
|
||||||
|
|
||||||
def channel(self, channel: discord.TextChannel) -> Group:
|
def channel(self, channel: discord.TextChannel) -> Group:
|
||||||
"""Returns a `Group` for the given channel.
|
"""Returns a `Group` for the given channel.
|
||||||
@@ -820,7 +824,7 @@ class Config:
|
|||||||
The channel's Group object.
|
The channel's Group object.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self._get_base_group(self.CHANNEL, channel.id)
|
return self._get_base_group(self.CHANNEL, str(channel.id))
|
||||||
|
|
||||||
def role(self, role: discord.Role) -> Group:
|
def role(self, role: discord.Role) -> Group:
|
||||||
"""Returns a `Group` for the given role.
|
"""Returns a `Group` for the given role.
|
||||||
@@ -836,7 +840,7 @@ class Config:
|
|||||||
The role's Group object.
|
The role's Group object.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self._get_base_group(self.ROLE, role.id)
|
return self._get_base_group(self.ROLE, str(role.id))
|
||||||
|
|
||||||
def user(self, user: discord.abc.User) -> Group:
|
def user(self, user: discord.abc.User) -> Group:
|
||||||
"""Returns a `Group` for the given user.
|
"""Returns a `Group` for the given user.
|
||||||
@@ -852,7 +856,7 @@ class Config:
|
|||||||
The user's Group object.
|
The user's Group object.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self._get_base_group(self.USER, user.id)
|
return self._get_base_group(self.USER, str(user.id))
|
||||||
|
|
||||||
def member(self, member: discord.Member) -> Group:
|
def member(self, member: discord.Member) -> Group:
|
||||||
"""Returns a `Group` for the given member.
|
"""Returns a `Group` for the given member.
|
||||||
@@ -866,8 +870,9 @@ class Config:
|
|||||||
-------
|
-------
|
||||||
`Group <redbot.core.config.Group>`
|
`Group <redbot.core.config.Group>`
|
||||||
The member's Group object.
|
The member's Group object.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self._get_base_group(self.MEMBER, member.guild.id, member.id)
|
return self._get_base_group(self.MEMBER, str(member.guild.id), str(member.id))
|
||||||
|
|
||||||
def custom(self, group_identifier: str, *identifiers: str):
|
def custom(self, group_identifier: str, *identifiers: str):
|
||||||
"""Returns a `Group` for the given custom group.
|
"""Returns a `Group` for the given custom group.
|
||||||
@@ -876,17 +881,17 @@ class Config:
|
|||||||
----------
|
----------
|
||||||
group_identifier : str
|
group_identifier : str
|
||||||
Used to identify the custom group.
|
Used to identify the custom group.
|
||||||
|
|
||||||
identifiers : str
|
identifiers : str
|
||||||
The attributes necessary to uniquely identify an entry in the
|
The attributes necessary to uniquely identify an entry in the
|
||||||
custom group.
|
custom group. These are casted to `str` for you.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
`Group <redbot.core.config.Group>`
|
`Group <redbot.core.config.Group>`
|
||||||
The custom group's Group object.
|
The custom group's Group object.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self._get_base_group(group_identifier, *identifiers)
|
return self._get_base_group(str(group_identifier), *map(str, identifiers))
|
||||||
|
|
||||||
async def _all_from_scope(self, scope: str) -> Dict[int, Dict[Any, Any]]:
|
async def _all_from_scope(self, scope: str) -> Dict[int, Dict[Any, Any]]:
|
||||||
"""Get a dict of all values from a particular scope of data.
|
"""Get a dict of all values from a particular scope of data.
|
||||||
@@ -982,7 +987,8 @@ class Config:
|
|||||||
"""
|
"""
|
||||||
return await self._all_from_scope(self.USER)
|
return await self._all_from_scope(self.USER)
|
||||||
|
|
||||||
def _all_members_from_guild(self, group: Group, guild_data: dict) -> dict:
|
@staticmethod
|
||||||
|
def _all_members_from_guild(group: Group, guild_data: dict) -> dict:
|
||||||
ret = {}
|
ret = {}
|
||||||
for member_id, member_data in guild_data.items():
|
for member_id, member_data in guild_data.items():
|
||||||
new_member_data = group.defaults
|
new_member_data = group.defaults
|
||||||
@@ -1026,7 +1032,7 @@ class Config:
|
|||||||
for guild_id, guild_data in dict_.items():
|
for guild_id, guild_data in dict_.items():
|
||||||
ret[int(guild_id)] = self._all_members_from_guild(group, guild_data)
|
ret[int(guild_id)] = self._all_members_from_guild(group, guild_data)
|
||||||
else:
|
else:
|
||||||
group = self._get_base_group(self.MEMBER, guild.id)
|
group = self._get_base_group(self.MEMBER, str(guild.id))
|
||||||
try:
|
try:
|
||||||
guild_data = await self.driver.get(*group.identifiers)
|
guild_data = await self.driver.get(*group.identifiers)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -1054,7 +1060,8 @@ class Config:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
if not scopes:
|
if not scopes:
|
||||||
group = Group(identifiers=[], defaults={}, driver=self.driver)
|
# noinspection PyTypeChecker
|
||||||
|
group = Group(identifiers=(), defaults={}, driver=self.driver)
|
||||||
else:
|
else:
|
||||||
group = self._get_base_group(*scopes)
|
group = self._get_base_group(*scopes)
|
||||||
await group.clear()
|
await group.clear()
|
||||||
@@ -1119,7 +1126,7 @@ class Config:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
if guild is not None:
|
if guild is not None:
|
||||||
await self._clear_scope(self.MEMBER, guild.id)
|
await self._clear_scope(self.MEMBER, str(guild.id))
|
||||||
return
|
return
|
||||||
await self._clear_scope(self.MEMBER)
|
await self._clear_scope(self.MEMBER)
|
||||||
|
|
||||||
@@ -1127,5 +1134,34 @@ class Config:
|
|||||||
"""Clear all custom group data.
|
"""Clear all custom group data.
|
||||||
|
|
||||||
This resets all custom group data to its registered defaults.
|
This resets all custom group data to its registered defaults.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
group_identifier : str
|
||||||
|
The identifier for the custom group. This is casted to
|
||||||
|
`str` for you.
|
||||||
"""
|
"""
|
||||||
await self._clear_scope(group_identifier)
|
await self._clear_scope(str(group_identifier))
|
||||||
|
|
||||||
|
|
||||||
|
def _str_key_dict(value: Dict[Any, _T]) -> Dict[str, _T]:
|
||||||
|
"""
|
||||||
|
Recursively casts all keys in the given `dict` to `str`.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
value : Dict[Any, Any]
|
||||||
|
The `dict` to cast keys to `str`.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Dict[str, Any]
|
||||||
|
The `dict` with keys (and nested keys) casted to `str`.
|
||||||
|
|
||||||
|
"""
|
||||||
|
ret = {}
|
||||||
|
for k, v in value.items():
|
||||||
|
if isinstance(v, dict):
|
||||||
|
v = _str_key_dict(v)
|
||||||
|
ret[str(k)] = v
|
||||||
|
return ret
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from collections import namedtuple
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from random import SystemRandom
|
from random import SystemRandom
|
||||||
from string import ascii_letters, digits
|
from string import ascii_letters, digits
|
||||||
from typing import TYPE_CHECKING, Union
|
from typing import TYPE_CHECKING, Union, Tuple, List, Optional, Iterable, Sequence, Dict
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import discord
|
import discord
|
||||||
@@ -25,6 +25,7 @@ from redbot.core import (
|
|||||||
VersionInfo,
|
VersionInfo,
|
||||||
checks,
|
checks,
|
||||||
commands,
|
commands,
|
||||||
|
errors,
|
||||||
i18n,
|
i18n,
|
||||||
)
|
)
|
||||||
from .utils.predicates import MessagePredicate
|
from .utils.predicates import MessagePredicate
|
||||||
@@ -59,7 +60,9 @@ class CoreLogic:
|
|||||||
self.bot.register_rpc_handler(self._version_info)
|
self.bot.register_rpc_handler(self._version_info)
|
||||||
self.bot.register_rpc_handler(self._invite_url)
|
self.bot.register_rpc_handler(self._invite_url)
|
||||||
|
|
||||||
async def _load(self, cog_names: list):
|
async def _load(
|
||||||
|
self, cog_names: Iterable[str]
|
||||||
|
) -> Tuple[List[str], List[str], List[str], List[str]]:
|
||||||
"""
|
"""
|
||||||
Loads cogs by name.
|
Loads cogs by name.
|
||||||
Parameters
|
Parameters
|
||||||
@@ -69,11 +72,12 @@ class CoreLogic:
|
|||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
tuple
|
tuple
|
||||||
3 element tuple of loaded, failed, and not found cogs.
|
4-tuple of loaded, failed, not found and already loaded cogs.
|
||||||
"""
|
"""
|
||||||
failed_packages = []
|
failed_packages = []
|
||||||
loaded_packages = []
|
loaded_packages = []
|
||||||
notfound_packages = []
|
notfound_packages = []
|
||||||
|
alreadyloaded_packages = []
|
||||||
|
|
||||||
bot = self.bot
|
bot = self.bot
|
||||||
|
|
||||||
@@ -98,6 +102,8 @@ class CoreLogic:
|
|||||||
try:
|
try:
|
||||||
self._cleanup_and_refresh_modules(spec.name)
|
self._cleanup_and_refresh_modules(spec.name)
|
||||||
await bot.load_extension(spec)
|
await bot.load_extension(spec)
|
||||||
|
except errors.PackageAlreadyLoaded:
|
||||||
|
alreadyloaded_packages.append(name)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.exception("Package loading failed", exc_info=e)
|
log.exception("Package loading failed", exc_info=e)
|
||||||
|
|
||||||
@@ -109,9 +115,10 @@ class CoreLogic:
|
|||||||
await bot.add_loaded_package(name)
|
await bot.add_loaded_package(name)
|
||||||
loaded_packages.append(name)
|
loaded_packages.append(name)
|
||||||
|
|
||||||
return loaded_packages, failed_packages, notfound_packages
|
return loaded_packages, failed_packages, notfound_packages, alreadyloaded_packages
|
||||||
|
|
||||||
def _cleanup_and_refresh_modules(self, module_name: str):
|
@staticmethod
|
||||||
|
def _cleanup_and_refresh_modules(module_name: str) -> None:
|
||||||
"""Interally reloads modules so that changes are detected"""
|
"""Interally reloads modules so that changes are detected"""
|
||||||
splitted = module_name.split(".")
|
splitted = module_name.split(".")
|
||||||
|
|
||||||
@@ -123,6 +130,7 @@ class CoreLogic:
|
|||||||
else:
|
else:
|
||||||
importlib._bootstrap._exec(lib.__spec__, lib)
|
importlib._bootstrap._exec(lib.__spec__, lib)
|
||||||
|
|
||||||
|
# noinspection PyTypeChecker
|
||||||
modules = itertools.accumulate(splitted, "{}.{}".format)
|
modules = itertools.accumulate(splitted, "{}.{}".format)
|
||||||
for m in modules:
|
for m in modules:
|
||||||
maybe_reload(m)
|
maybe_reload(m)
|
||||||
@@ -131,7 +139,10 @@ class CoreLogic:
|
|||||||
for child_name, lib in children.items():
|
for child_name, lib in children.items():
|
||||||
importlib._bootstrap._exec(lib.__spec__, lib)
|
importlib._bootstrap._exec(lib.__spec__, lib)
|
||||||
|
|
||||||
def _get_package_strings(self, packages: list, fmt: str, other: tuple = None):
|
@staticmethod
|
||||||
|
def _get_package_strings(
|
||||||
|
packages: List[str], fmt: str, other: Optional[Tuple[str, ...]] = None
|
||||||
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Gets the strings needed for the load, unload and reload commands
|
Gets the strings needed for the load, unload and reload commands
|
||||||
"""
|
"""
|
||||||
@@ -147,7 +158,7 @@ class CoreLogic:
|
|||||||
final_string = fmt.format(**form)
|
final_string = fmt.format(**form)
|
||||||
return final_string
|
return final_string
|
||||||
|
|
||||||
async def _unload(self, cog_names: list):
|
async def _unload(self, cog_names: Iterable[str]) -> Tuple[List[str], List[str]]:
|
||||||
"""
|
"""
|
||||||
Unloads cogs with the given names.
|
Unloads cogs with the given names.
|
||||||
|
|
||||||
@@ -175,14 +186,16 @@ class CoreLogic:
|
|||||||
|
|
||||||
return unloaded_packages, failed_packages
|
return unloaded_packages, failed_packages
|
||||||
|
|
||||||
async def _reload(self, cog_names):
|
async def _reload(
|
||||||
|
self, cog_names: Sequence[str]
|
||||||
|
) -> Tuple[List[str], List[str], List[str], List[str]]:
|
||||||
await self._unload(cog_names)
|
await self._unload(cog_names)
|
||||||
|
|
||||||
loaded, load_failed, not_found = await self._load(cog_names)
|
loaded, load_failed, not_found, already_loaded = await self._load(cog_names)
|
||||||
|
|
||||||
return loaded, load_failed, not_found
|
return loaded, load_failed, not_found, already_loaded
|
||||||
|
|
||||||
async def _name(self, name: str = None):
|
async def _name(self, name: Optional[str] = None) -> str:
|
||||||
"""
|
"""
|
||||||
Gets or sets the bot's username.
|
Gets or sets the bot's username.
|
||||||
|
|
||||||
@@ -201,7 +214,7 @@ class CoreLogic:
|
|||||||
|
|
||||||
return self.bot.user.name
|
return self.bot.user.name
|
||||||
|
|
||||||
async def _prefixes(self, prefixes: list = None):
|
async def _prefixes(self, prefixes: Optional[Sequence[str]] = None) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Gets or sets the bot's global prefixes.
|
Gets or sets the bot's global prefixes.
|
||||||
|
|
||||||
@@ -220,7 +233,8 @@ class CoreLogic:
|
|||||||
await self.bot.db.prefix.set(prefixes)
|
await self.bot.db.prefix.set(prefixes)
|
||||||
return await self.bot.db.prefix()
|
return await self.bot.db.prefix()
|
||||||
|
|
||||||
async def _version_info(self):
|
@classmethod
|
||||||
|
async def _version_info(cls) -> Dict[str, str]:
|
||||||
"""
|
"""
|
||||||
Version information for Red and discord.py
|
Version information for Red and discord.py
|
||||||
|
|
||||||
@@ -231,7 +245,7 @@ class CoreLogic:
|
|||||||
"""
|
"""
|
||||||
return {"redbot": __version__, "discordpy": discord.__version__}
|
return {"redbot": __version__, "discordpy": discord.__version__}
|
||||||
|
|
||||||
async def _invite_url(self):
|
async def _invite_url(self) -> str:
|
||||||
"""
|
"""
|
||||||
Generates the invite URL for the bot.
|
Generates the invite URL for the bot.
|
||||||
|
|
||||||
@@ -248,11 +262,8 @@ class CoreLogic:
|
|||||||
class Core(commands.Cog, CoreLogic):
|
class Core(commands.Cog, CoreLogic):
|
||||||
"""Commands related to core functions"""
|
"""Commands related to core functions"""
|
||||||
|
|
||||||
def __init__(self, bot):
|
|
||||||
super().__init__(bot)
|
|
||||||
|
|
||||||
@commands.command(hidden=True)
|
@commands.command(hidden=True)
|
||||||
async def ping(self, ctx):
|
async def ping(self, ctx: commands.Context):
|
||||||
"""Pong."""
|
"""Pong."""
|
||||||
await ctx.send("Pong.")
|
await ctx.send("Pong.")
|
||||||
|
|
||||||
@@ -313,7 +324,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
passed = self.get_bot_uptime()
|
passed = self.get_bot_uptime()
|
||||||
await ctx.send("Been up for: **{}** (since {} UTC)".format(passed, since))
|
await ctx.send("Been up for: **{}** (since {} UTC)".format(passed, since))
|
||||||
|
|
||||||
def get_bot_uptime(self, *, brief=False):
|
def get_bot_uptime(self, *, brief: bool = False):
|
||||||
# Courtesy of Danny
|
# Courtesy of Danny
|
||||||
now = datetime.datetime.utcnow()
|
now = datetime.datetime.utcnow()
|
||||||
delta = now - self.bot.uptime
|
delta = now - self.bot.uptime
|
||||||
@@ -416,7 +427,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def traceback(self, ctx, public: bool = False):
|
async def traceback(self, ctx: commands.Context, public: bool = False):
|
||||||
"""Sends to the owner the last command exception that has occurred
|
"""Sends to the owner the last command exception that has occurred
|
||||||
|
|
||||||
If public (yes is specified), it will be sent to the chat instead"""
|
If public (yes is specified), it will be sent to the chat instead"""
|
||||||
@@ -433,14 +444,14 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def invite(self, ctx):
|
async def invite(self, ctx: commands.Context):
|
||||||
"""Show's Red's invite url"""
|
"""Show's Red's invite url"""
|
||||||
await ctx.author.send(await self._invite_url())
|
await ctx.author.send(await self._invite_url())
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def leave(self, ctx):
|
async def leave(self, ctx: commands.Context):
|
||||||
"""Leaves server"""
|
"""Leaves server"""
|
||||||
await ctx.send("Are you sure you want me to leave this server? (y/n)")
|
await ctx.send("Are you sure you want me to leave this server? (y/n)")
|
||||||
|
|
||||||
@@ -460,7 +471,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def servers(self, ctx):
|
async def servers(self, ctx: commands.Context):
|
||||||
"""Lists and allows to leave servers"""
|
"""Lists and allows to leave servers"""
|
||||||
guilds = sorted(list(self.bot.guilds), key=lambda s: s.name.lower())
|
guilds = sorted(list(self.bot.guilds), key=lambda s: s.name.lower())
|
||||||
msg = ""
|
msg = ""
|
||||||
@@ -502,18 +513,21 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def load(self, ctx, *, cog_name: str):
|
async def load(self, ctx: commands.Context, *cogs: str):
|
||||||
"""Loads packages"""
|
"""Loads packages"""
|
||||||
|
|
||||||
cog_names = [c.strip() for c in cog_name.split(" ")]
|
|
||||||
async with ctx.typing():
|
async with ctx.typing():
|
||||||
loaded, failed, not_found = await self._load(cog_names)
|
loaded, failed, not_found, already_loaded = await self._load(cogs)
|
||||||
|
|
||||||
if loaded:
|
if loaded:
|
||||||
fmt = "Loaded {packs}."
|
fmt = "Loaded {packs}."
|
||||||
formed = self._get_package_strings(loaded, fmt)
|
formed = self._get_package_strings(loaded, fmt)
|
||||||
await ctx.send(formed)
|
await ctx.send(formed)
|
||||||
|
|
||||||
|
if already_loaded:
|
||||||
|
fmt = "The package{plural} {packs} {other} already loaded."
|
||||||
|
formed = self._get_package_strings(already_loaded, fmt, ("is", "are"))
|
||||||
|
await ctx.send(formed)
|
||||||
|
|
||||||
if failed:
|
if failed:
|
||||||
fmt = (
|
fmt = (
|
||||||
"Failed to load package{plural} {packs}. Check your console or "
|
"Failed to load package{plural} {packs}. Check your console or "
|
||||||
@@ -529,12 +543,9 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def unload(self, ctx, *, cog_name: str):
|
async def unload(self, ctx: commands.Context, *cogs: str):
|
||||||
"""Unloads packages"""
|
"""Unloads packages"""
|
||||||
|
unloaded, failed = await self._unload(cogs)
|
||||||
cog_names = [c.strip() for c in cog_name.split(" ")]
|
|
||||||
|
|
||||||
unloaded, failed = await self._unload(cog_names)
|
|
||||||
|
|
||||||
if unloaded:
|
if unloaded:
|
||||||
fmt = "Package{plural} {packs} {other} unloaded."
|
fmt = "Package{plural} {packs} {other} unloaded."
|
||||||
@@ -548,10 +559,10 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@commands.command(name="reload")
|
@commands.command(name="reload")
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def reload(self, ctx, *cogs: str):
|
async def reload(self, ctx: commands.Context, *cogs: str):
|
||||||
"""Reloads packages"""
|
"""Reloads packages"""
|
||||||
async with ctx.typing():
|
async with ctx.typing():
|
||||||
loaded, failed, not_found = await self._reload(cogs)
|
loaded, failed, not_found, already_loaded = await self._reload(cogs)
|
||||||
|
|
||||||
if loaded:
|
if loaded:
|
||||||
fmt = "Package{plural} {packs} {other} reloaded."
|
fmt = "Package{plural} {packs} {other} reloaded."
|
||||||
@@ -570,34 +581,30 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@commands.command(name="shutdown")
|
@commands.command(name="shutdown")
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def _shutdown(self, ctx, silently: bool = False):
|
async def _shutdown(self, ctx: commands.Context, silently: bool = False):
|
||||||
"""Shuts down the bot"""
|
"""Shuts down the bot"""
|
||||||
wave = "\N{WAVING HAND SIGN}"
|
wave = "\N{WAVING HAND SIGN}"
|
||||||
skin = "\N{EMOJI MODIFIER FITZPATRICK TYPE-3}"
|
skin = "\N{EMOJI MODIFIER FITZPATRICK TYPE-3}"
|
||||||
try: # We don't want missing perms to stop our shutdown
|
with contextlib.suppress(discord.HTTPException):
|
||||||
if not silently:
|
if not silently:
|
||||||
await ctx.send(_("Shutting down... ") + wave + skin)
|
await ctx.send(_("Shutting down... ") + wave + skin)
|
||||||
except:
|
|
||||||
pass
|
|
||||||
await ctx.bot.shutdown()
|
await ctx.bot.shutdown()
|
||||||
|
|
||||||
@commands.command(name="restart")
|
@commands.command(name="restart")
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def _restart(self, ctx, silently: bool = False):
|
async def _restart(self, ctx: commands.Context, silently: bool = False):
|
||||||
"""Attempts to restart Red
|
"""Attempts to restart Red
|
||||||
|
|
||||||
Makes Red quit with exit code 26
|
Makes Red quit with exit code 26
|
||||||
The restart is not guaranteed: it must be dealt
|
The restart is not guaranteed: it must be dealt
|
||||||
with by the process manager in use"""
|
with by the process manager in use"""
|
||||||
try:
|
with contextlib.suppress(discord.HTTPException):
|
||||||
if not silently:
|
if not silently:
|
||||||
await ctx.send(_("Restarting..."))
|
await ctx.send(_("Restarting..."))
|
||||||
except:
|
|
||||||
pass
|
|
||||||
await ctx.bot.shutdown(restart=True)
|
await ctx.bot.shutdown(restart=True)
|
||||||
|
|
||||||
@commands.group(name="set")
|
@commands.group(name="set")
|
||||||
async def _set(self, ctx):
|
async def _set(self, ctx: commands.Context):
|
||||||
"""Changes Red's settings"""
|
"""Changes Red's settings"""
|
||||||
if ctx.invoked_subcommand is None:
|
if ctx.invoked_subcommand is None:
|
||||||
if ctx.guild:
|
if ctx.guild:
|
||||||
@@ -629,7 +636,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@_set.command()
|
@_set.command()
|
||||||
@checks.guildowner()
|
@checks.guildowner()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def adminrole(self, ctx, *, role: discord.Role):
|
async def adminrole(self, ctx: commands.Context, *, role: discord.Role):
|
||||||
"""Sets the admin role for this server"""
|
"""Sets the admin role for this server"""
|
||||||
await ctx.bot.db.guild(ctx.guild).admin_role.set(role.id)
|
await ctx.bot.db.guild(ctx.guild).admin_role.set(role.id)
|
||||||
await ctx.send(_("The admin role for this guild has been set."))
|
await ctx.send(_("The admin role for this guild has been set."))
|
||||||
@@ -637,7 +644,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@_set.command()
|
@_set.command()
|
||||||
@checks.guildowner()
|
@checks.guildowner()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def modrole(self, ctx, *, role: discord.Role):
|
async def modrole(self, ctx: commands.Context, *, role: discord.Role):
|
||||||
"""Sets the mod role for this server"""
|
"""Sets the mod role for this server"""
|
||||||
await ctx.bot.db.guild(ctx.guild).mod_role.set(role.id)
|
await ctx.bot.db.guild(ctx.guild).mod_role.set(role.id)
|
||||||
await ctx.send(_("The mod role for this guild has been set."))
|
await ctx.send(_("The mod role for this guild has been set."))
|
||||||
@@ -645,7 +652,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@_set.command(aliases=["usebotcolor"])
|
@_set.command(aliases=["usebotcolor"])
|
||||||
@checks.guildowner()
|
@checks.guildowner()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def usebotcolour(self, ctx):
|
async def usebotcolour(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
Toggle whether to use the bot owner-configured colour for embeds.
|
Toggle whether to use the bot owner-configured colour for embeds.
|
||||||
|
|
||||||
@@ -663,7 +670,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@_set.command()
|
@_set.command()
|
||||||
@checks.guildowner()
|
@checks.guildowner()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def serverfuzzy(self, ctx):
|
async def serverfuzzy(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
Toggle whether to enable fuzzy command search for the server.
|
Toggle whether to enable fuzzy command search for the server.
|
||||||
|
|
||||||
@@ -679,7 +686,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@_set.command()
|
@_set.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def fuzzy(self, ctx):
|
async def fuzzy(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
Toggle whether to enable fuzzy command search in DMs.
|
Toggle whether to enable fuzzy command search in DMs.
|
||||||
|
|
||||||
@@ -695,7 +702,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@_set.command(aliases=["color"])
|
@_set.command(aliases=["color"])
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def colour(self, ctx, *, colour: discord.Colour = None):
|
async def colour(self, ctx: commands.Context, *, colour: discord.Colour = None):
|
||||||
"""
|
"""
|
||||||
Sets a default colour to be used for the bot's embeds.
|
Sets a default colour to be used for the bot's embeds.
|
||||||
|
|
||||||
@@ -713,7 +720,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@_set.command()
|
@_set.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def avatar(self, ctx, url: str):
|
async def avatar(self, ctx: commands.Context, url: str):
|
||||||
"""Sets Red's avatar"""
|
"""Sets Red's avatar"""
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get(url) as r:
|
async with session.get(url) as r:
|
||||||
@@ -737,7 +744,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@_set.command(name="game")
|
@_set.command(name="game")
|
||||||
@checks.bot_in_a_guild()
|
@checks.bot_in_a_guild()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def _game(self, ctx, *, game: str = None):
|
async def _game(self, ctx: commands.Context, *, game: str = None):
|
||||||
"""Sets Red's playing status"""
|
"""Sets Red's playing status"""
|
||||||
|
|
||||||
if game:
|
if game:
|
||||||
@@ -751,7 +758,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@_set.command(name="listening")
|
@_set.command(name="listening")
|
||||||
@checks.bot_in_a_guild()
|
@checks.bot_in_a_guild()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def _listening(self, ctx, *, listening: str = None):
|
async def _listening(self, ctx: commands.Context, *, listening: str = None):
|
||||||
"""Sets Red's listening status"""
|
"""Sets Red's listening status"""
|
||||||
|
|
||||||
status = ctx.bot.guilds[0].me.status if len(ctx.bot.guilds) > 0 else discord.Status.online
|
status = ctx.bot.guilds[0].me.status if len(ctx.bot.guilds) > 0 else discord.Status.online
|
||||||
@@ -765,7 +772,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@_set.command(name="watching")
|
@_set.command(name="watching")
|
||||||
@checks.bot_in_a_guild()
|
@checks.bot_in_a_guild()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def _watching(self, ctx, *, watching: str = None):
|
async def _watching(self, ctx: commands.Context, *, watching: str = None):
|
||||||
"""Sets Red's watching status"""
|
"""Sets Red's watching status"""
|
||||||
|
|
||||||
status = ctx.bot.guilds[0].me.status if len(ctx.bot.guilds) > 0 else discord.Status.online
|
status = ctx.bot.guilds[0].me.status if len(ctx.bot.guilds) > 0 else discord.Status.online
|
||||||
@@ -779,7 +786,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@_set.command()
|
@_set.command()
|
||||||
@checks.bot_in_a_guild()
|
@checks.bot_in_a_guild()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def status(self, ctx, *, status: str):
|
async def status(self, ctx: commands.Context, *, status: str):
|
||||||
"""Sets Red's status
|
"""Sets Red's status
|
||||||
|
|
||||||
Available statuses:
|
Available statuses:
|
||||||
@@ -808,7 +815,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@_set.command()
|
@_set.command()
|
||||||
@checks.bot_in_a_guild()
|
@checks.bot_in_a_guild()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def stream(self, ctx, streamer=None, *, stream_title=None):
|
async def stream(self, ctx: commands.Context, streamer=None, *, stream_title=None):
|
||||||
"""Sets Red's streaming status
|
"""Sets Red's streaming status
|
||||||
Leaving both streamer and stream_title empty will clear it."""
|
Leaving both streamer and stream_title empty will clear it."""
|
||||||
|
|
||||||
@@ -829,7 +836,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@_set.command(name="username", aliases=["name"])
|
@_set.command(name="username", aliases=["name"])
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def _username(self, ctx, *, username: str):
|
async def _username(self, ctx: commands.Context, *, username: str):
|
||||||
"""Sets Red's username"""
|
"""Sets Red's username"""
|
||||||
try:
|
try:
|
||||||
await self._name(name=username)
|
await self._name(name=username)
|
||||||
@@ -848,7 +855,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@_set.command(name="nickname")
|
@_set.command(name="nickname")
|
||||||
@checks.admin()
|
@checks.admin()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def _nickname(self, ctx, *, nickname: str = None):
|
async def _nickname(self, ctx: commands.Context, *, nickname: str = None):
|
||||||
"""Sets Red's nickname"""
|
"""Sets Red's nickname"""
|
||||||
try:
|
try:
|
||||||
await ctx.guild.me.edit(nick=nickname)
|
await ctx.guild.me.edit(nick=nickname)
|
||||||
@@ -859,7 +866,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@_set.command(aliases=["prefixes"])
|
@_set.command(aliases=["prefixes"])
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def prefix(self, ctx, *prefixes):
|
async def prefix(self, ctx: commands.Context, *prefixes: str):
|
||||||
"""Sets Red's global prefix(es)"""
|
"""Sets Red's global prefix(es)"""
|
||||||
if not prefixes:
|
if not prefixes:
|
||||||
await ctx.send_help()
|
await ctx.send_help()
|
||||||
@@ -870,7 +877,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@_set.command(aliases=["serverprefixes"])
|
@_set.command(aliases=["serverprefixes"])
|
||||||
@checks.admin()
|
@checks.admin()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def serverprefix(self, ctx, *prefixes):
|
async def serverprefix(self, ctx: commands.Context, *prefixes: str):
|
||||||
"""Sets Red's server prefix(es)"""
|
"""Sets Red's server prefix(es)"""
|
||||||
if not prefixes:
|
if not prefixes:
|
||||||
await ctx.bot.db.guild(ctx.guild).prefix.set([])
|
await ctx.bot.db.guild(ctx.guild).prefix.set([])
|
||||||
@@ -882,7 +889,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@_set.command()
|
@_set.command()
|
||||||
@commands.cooldown(1, 60 * 10, commands.BucketType.default)
|
@commands.cooldown(1, 60 * 10, commands.BucketType.default)
|
||||||
async def owner(self, ctx):
|
async def owner(self, ctx: commands.Context):
|
||||||
"""Sets Red's main owner"""
|
"""Sets Red's main owner"""
|
||||||
# According to the Python docs this is suitable for cryptographic use
|
# According to the Python docs this is suitable for cryptographic use
|
||||||
random = SystemRandom()
|
random = SystemRandom()
|
||||||
@@ -926,7 +933,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@_set.command()
|
@_set.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def token(self, ctx, token: str):
|
async def token(self, ctx: commands.Context, token: str):
|
||||||
"""Change bot token."""
|
"""Change bot token."""
|
||||||
|
|
||||||
if not isinstance(ctx.channel, discord.DMChannel):
|
if not isinstance(ctx.channel, discord.DMChannel):
|
||||||
@@ -1071,7 +1078,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def backup(self, ctx, backup_path: str = None):
|
async def backup(self, ctx: commands.Context, backup_path: str = None):
|
||||||
"""Creates a backup of all data for the instance."""
|
"""Creates a backup of all data for the instance."""
|
||||||
from redbot.core.data_manager import basic_config, instance_name
|
from redbot.core.data_manager import basic_config, instance_name
|
||||||
from redbot.core.drivers.red_json import JSON
|
from redbot.core.drivers.red_json import JSON
|
||||||
@@ -1080,20 +1087,19 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
if basic_config["STORAGE_TYPE"] == "MongoDB":
|
if basic_config["STORAGE_TYPE"] == "MongoDB":
|
||||||
from redbot.core.drivers.red_mongo import Mongo
|
from redbot.core.drivers.red_mongo import Mongo
|
||||||
|
|
||||||
m = Mongo("Core", **basic_config["STORAGE_DETAILS"])
|
m = Mongo("Core", "0", **basic_config["STORAGE_DETAILS"])
|
||||||
db = m.db
|
db = m.db
|
||||||
collection_names = await db.collection_names(include_system_collections=False)
|
collection_names = await db.list_collection_names()
|
||||||
for c_name in collection_names:
|
for c_name in collection_names:
|
||||||
if c_name == "Core":
|
if c_name == "Core":
|
||||||
c_data_path = data_dir / basic_config["CORE_PATH_APPEND"]
|
c_data_path = data_dir / basic_config["CORE_PATH_APPEND"]
|
||||||
else:
|
else:
|
||||||
c_data_path = data_dir / basic_config["COG_PATH_APPEND"]
|
c_data_path = data_dir / basic_config["COG_PATH_APPEND"] / c_name
|
||||||
output = {}
|
|
||||||
docs = await db[c_name].find().to_list(None)
|
docs = await db[c_name].find().to_list(None)
|
||||||
for item in docs:
|
for item in docs:
|
||||||
item_id = str(item.pop("_id"))
|
item_id = str(item.pop("_id"))
|
||||||
output[item_id] = item
|
output = item
|
||||||
target = JSON(c_name, data_path_override=c_data_path)
|
target = JSON(c_name, item_id, data_path_override=c_data_path)
|
||||||
await target.jsonIO._threadsafe_save_json(output)
|
await target.jsonIO._threadsafe_save_json(output)
|
||||||
backup_filename = "redv3-{}-{}.tar.gz".format(
|
backup_filename = "redv3-{}-{}.tar.gz".format(
|
||||||
instance_name, ctx.message.created_at.strftime("%Y-%m-%d %H-%M-%S")
|
instance_name, ctx.message.created_at.strftime("%Y-%m-%d %H-%M-%S")
|
||||||
@@ -1134,7 +1140,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
tar.add(str(f), recursive=False)
|
tar.add(str(f), recursive=False)
|
||||||
print(str(backup_file))
|
print(str(backup_file))
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("A backup has been made of this instance. It is at {}.").format((backup_file))
|
_("A backup has been made of this instance. It is at {}.").format(backup_file)
|
||||||
)
|
)
|
||||||
await ctx.send(_("Would you like to receive a copy via DM? (y/n)"))
|
await ctx.send(_("Would you like to receive a copy via DM? (y/n)"))
|
||||||
|
|
||||||
@@ -1157,7 +1163,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.cooldown(1, 60, commands.BucketType.user)
|
@commands.cooldown(1, 60, commands.BucketType.user)
|
||||||
async def contact(self, ctx, *, message: str):
|
async def contact(self, ctx: commands.Context, *, message: str):
|
||||||
"""Sends a message to the owner"""
|
"""Sends a message to the owner"""
|
||||||
guild = ctx.message.guild
|
guild = ctx.message.guild
|
||||||
owner = discord.utils.get(ctx.bot.get_all_members(), id=ctx.bot.owner_id)
|
owner = discord.utils.get(ctx.bot.get_all_members(), id=ctx.bot.owner_id)
|
||||||
@@ -1200,7 +1206,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("I cannot send your message, I'm unable to find my owner... *sigh*")
|
_("I cannot send your message, I'm unable to find my owner... *sigh*")
|
||||||
)
|
)
|
||||||
except:
|
except discord.HTTPException:
|
||||||
await ctx.send(_("I'm unable to deliver your message. Sorry."))
|
await ctx.send(_("I'm unable to deliver your message. Sorry."))
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("Your message has been sent."))
|
await ctx.send(_("Your message has been sent."))
|
||||||
@@ -1212,14 +1218,14 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("I cannot send your message, I'm unable to find my owner... *sigh*")
|
_("I cannot send your message, I'm unable to find my owner... *sigh*")
|
||||||
)
|
)
|
||||||
except:
|
except discord.HTTPException:
|
||||||
await ctx.send(_("I'm unable to deliver your message. Sorry."))
|
await ctx.send(_("I'm unable to deliver your message. Sorry."))
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("Your message has been sent."))
|
await ctx.send(_("Your message has been sent."))
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def dm(self, ctx, user_id: int, *, message: str):
|
async def dm(self, ctx: commands.Context, user_id: int, *, message: str):
|
||||||
"""Sends a DM to a user
|
"""Sends a DM to a user
|
||||||
|
|
||||||
This command needs a user id to work.
|
This command needs a user id to work.
|
||||||
@@ -1253,7 +1259,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
await destination.send(embed=e)
|
await destination.send(embed=e)
|
||||||
except:
|
except discord.HTTPException:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("Sorry, I couldn't deliver your message to {}").format(destination)
|
_("Sorry, I couldn't deliver your message to {}").format(destination)
|
||||||
)
|
)
|
||||||
@@ -1263,7 +1269,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
response = "{}\nMessage:\n\n{}".format(description, message)
|
response = "{}\nMessage:\n\n{}".format(description, message)
|
||||||
try:
|
try:
|
||||||
await destination.send("{}\n{}".format(box(response), content))
|
await destination.send("{}\n{}".format(box(response), content))
|
||||||
except:
|
except discord.HTTPException:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("Sorry, I couldn't deliver your message to {}").format(destination)
|
_("Sorry, I couldn't deliver your message to {}").format(destination)
|
||||||
)
|
)
|
||||||
@@ -1272,7 +1278,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@commands.group()
|
@commands.group()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def whitelist(self, ctx):
|
async def whitelist(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
Whitelist management commands.
|
Whitelist management commands.
|
||||||
"""
|
"""
|
||||||
@@ -1290,7 +1296,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
await ctx.send(_("User added to whitelist."))
|
await ctx.send(_("User added to whitelist."))
|
||||||
|
|
||||||
@whitelist.command(name="list")
|
@whitelist.command(name="list")
|
||||||
async def whitelist_list(self, ctx):
|
async def whitelist_list(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
Lists whitelisted users.
|
Lists whitelisted users.
|
||||||
"""
|
"""
|
||||||
@@ -1304,7 +1310,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
await ctx.send(box(page))
|
await ctx.send(box(page))
|
||||||
|
|
||||||
@whitelist.command(name="remove")
|
@whitelist.command(name="remove")
|
||||||
async def whitelist_remove(self, ctx, user: discord.User):
|
async def whitelist_remove(self, ctx: commands.Context, user: discord.User):
|
||||||
"""
|
"""
|
||||||
Removes user from whitelist.
|
Removes user from whitelist.
|
||||||
"""
|
"""
|
||||||
@@ -1321,7 +1327,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
await ctx.send(_("User was not in the whitelist."))
|
await ctx.send(_("User was not in the whitelist."))
|
||||||
|
|
||||||
@whitelist.command(name="clear")
|
@whitelist.command(name="clear")
|
||||||
async def whitelist_clear(self, ctx):
|
async def whitelist_clear(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
Clears the whitelist.
|
Clears the whitelist.
|
||||||
"""
|
"""
|
||||||
@@ -1330,19 +1336,19 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@commands.group()
|
@commands.group()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def blacklist(self, ctx):
|
async def blacklist(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
blacklist management commands.
|
blacklist management commands.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@blacklist.command(name="add")
|
@blacklist.command(name="add")
|
||||||
async def blacklist_add(self, ctx, user: discord.User):
|
async def blacklist_add(self, ctx: commands.Context, user: discord.User):
|
||||||
"""
|
"""
|
||||||
Adds a user to the blacklist.
|
Adds a user to the blacklist.
|
||||||
"""
|
"""
|
||||||
if await ctx.bot.is_owner(user):
|
if await ctx.bot.is_owner(user):
|
||||||
ctx.send(_("You cannot blacklist an owner!"))
|
await ctx.send(_("You cannot blacklist an owner!"))
|
||||||
return
|
return
|
||||||
|
|
||||||
async with ctx.bot.db.blacklist() as curr_list:
|
async with ctx.bot.db.blacklist() as curr_list:
|
||||||
@@ -1352,7 +1358,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
await ctx.send(_("User added to blacklist."))
|
await ctx.send(_("User added to blacklist."))
|
||||||
|
|
||||||
@blacklist.command(name="list")
|
@blacklist.command(name="list")
|
||||||
async def blacklist_list(self, ctx):
|
async def blacklist_list(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
Lists blacklisted users.
|
Lists blacklisted users.
|
||||||
"""
|
"""
|
||||||
@@ -1366,7 +1372,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
await ctx.send(box(page))
|
await ctx.send(box(page))
|
||||||
|
|
||||||
@blacklist.command(name="remove")
|
@blacklist.command(name="remove")
|
||||||
async def blacklist_remove(self, ctx, user: discord.User):
|
async def blacklist_remove(self, ctx: commands.Context, user: discord.User):
|
||||||
"""
|
"""
|
||||||
Removes user from blacklist.
|
Removes user from blacklist.
|
||||||
"""
|
"""
|
||||||
@@ -1383,7 +1389,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
await ctx.send(_("User was not in the blacklist."))
|
await ctx.send(_("User was not in the blacklist."))
|
||||||
|
|
||||||
@blacklist.command(name="clear")
|
@blacklist.command(name="clear")
|
||||||
async def blacklist_clear(self, ctx):
|
async def blacklist_clear(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
Clears the blacklist.
|
Clears the blacklist.
|
||||||
"""
|
"""
|
||||||
@@ -1393,14 +1399,14 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@commands.group()
|
@commands.group()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.admin_or_permissions(administrator=True)
|
@checks.admin_or_permissions(administrator=True)
|
||||||
async def localwhitelist(self, ctx):
|
async def localwhitelist(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
Whitelist management commands.
|
Whitelist management commands.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@localwhitelist.command(name="add")
|
@localwhitelist.command(name="add")
|
||||||
async def localwhitelist_add(self, ctx, *, user_or_role: str):
|
async def localwhitelist_add(self, ctx: commands.Context, *, user_or_role: str):
|
||||||
"""
|
"""
|
||||||
Adds a user or role to the whitelist.
|
Adds a user or role to the whitelist.
|
||||||
"""
|
"""
|
||||||
@@ -1421,7 +1427,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
await ctx.send(_("Role added to whitelist."))
|
await ctx.send(_("Role added to whitelist."))
|
||||||
|
|
||||||
@localwhitelist.command(name="list")
|
@localwhitelist.command(name="list")
|
||||||
async def localwhitelist_list(self, ctx):
|
async def localwhitelist_list(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
Lists whitelisted users and roles.
|
Lists whitelisted users and roles.
|
||||||
"""
|
"""
|
||||||
@@ -1435,7 +1441,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
await ctx.send(box(page))
|
await ctx.send(box(page))
|
||||||
|
|
||||||
@localwhitelist.command(name="remove")
|
@localwhitelist.command(name="remove")
|
||||||
async def localwhitelist_remove(self, ctx, *, user_or_role: str):
|
async def localwhitelist_remove(self, ctx: commands.Context, *, user_or_role: str):
|
||||||
"""
|
"""
|
||||||
Removes user or role from whitelist.
|
Removes user or role from whitelist.
|
||||||
"""
|
"""
|
||||||
@@ -1465,7 +1471,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
await ctx.send(_("Role was not in the whitelist."))
|
await ctx.send(_("Role was not in the whitelist."))
|
||||||
|
|
||||||
@localwhitelist.command(name="clear")
|
@localwhitelist.command(name="clear")
|
||||||
async def localwhitelist_clear(self, ctx):
|
async def localwhitelist_clear(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
Clears the whitelist.
|
Clears the whitelist.
|
||||||
"""
|
"""
|
||||||
@@ -1475,14 +1481,14 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@commands.group()
|
@commands.group()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.admin_or_permissions(administrator=True)
|
@checks.admin_or_permissions(administrator=True)
|
||||||
async def localblacklist(self, ctx):
|
async def localblacklist(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
blacklist management commands.
|
blacklist management commands.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@localblacklist.command(name="add")
|
@localblacklist.command(name="add")
|
||||||
async def localblacklist_add(self, ctx, *, user_or_role: str):
|
async def localblacklist_add(self, ctx: commands.Context, *, user_or_role: str):
|
||||||
"""
|
"""
|
||||||
Adds a user or role to the blacklist.
|
Adds a user or role to the blacklist.
|
||||||
"""
|
"""
|
||||||
@@ -1495,7 +1501,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
user = True
|
user = True
|
||||||
|
|
||||||
if user and await ctx.bot.is_owner(obj):
|
if user and await ctx.bot.is_owner(obj):
|
||||||
ctx.send(_("You cannot blacklist an owner!"))
|
await ctx.send(_("You cannot blacklist an owner!"))
|
||||||
return
|
return
|
||||||
|
|
||||||
async with ctx.bot.db.guild(ctx.guild).blacklist() as curr_list:
|
async with ctx.bot.db.guild(ctx.guild).blacklist() as curr_list:
|
||||||
@@ -1508,7 +1514,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
await ctx.send(_("Role added to blacklist."))
|
await ctx.send(_("Role added to blacklist."))
|
||||||
|
|
||||||
@localblacklist.command(name="list")
|
@localblacklist.command(name="list")
|
||||||
async def localblacklist_list(self, ctx):
|
async def localblacklist_list(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
Lists blacklisted users and roles.
|
Lists blacklisted users and roles.
|
||||||
"""
|
"""
|
||||||
@@ -1522,7 +1528,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
await ctx.send(box(page))
|
await ctx.send(box(page))
|
||||||
|
|
||||||
@localblacklist.command(name="remove")
|
@localblacklist.command(name="remove")
|
||||||
async def localblacklist_remove(self, ctx, *, user_or_role: str):
|
async def localblacklist_remove(self, ctx: commands.Context, *, user_or_role: str):
|
||||||
"""
|
"""
|
||||||
Removes user or role from blacklist.
|
Removes user or role from blacklist.
|
||||||
"""
|
"""
|
||||||
@@ -1552,7 +1558,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
await ctx.send(_("Role was not in the blacklist."))
|
await ctx.send(_("Role was not in the blacklist."))
|
||||||
|
|
||||||
@localblacklist.command(name="clear")
|
@localblacklist.command(name="clear")
|
||||||
async def localblacklist_clear(self, ctx):
|
async def localblacklist_clear(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
Clears the blacklist.
|
Clears the blacklist.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import motor.motor_asyncio
|
import re
|
||||||
from .red_base import BaseDriver
|
from typing import Match, Pattern
|
||||||
from urllib.parse import quote_plus
|
from urllib.parse import quote_plus
|
||||||
|
|
||||||
|
import motor.core
|
||||||
|
import motor.motor_asyncio
|
||||||
|
|
||||||
|
from .red_base import BaseDriver
|
||||||
|
|
||||||
__all__ = ["Mongo"]
|
__all__ = ["Mongo"]
|
||||||
|
|
||||||
|
|
||||||
@@ -80,6 +85,7 @@ class Mongo(BaseDriver):
|
|||||||
async def get(self, *identifiers: str):
|
async def get(self, *identifiers: str):
|
||||||
mongo_collection = self.get_collection()
|
mongo_collection = self.get_collection()
|
||||||
|
|
||||||
|
identifiers = (*map(self._escape_key, identifiers),)
|
||||||
dot_identifiers = ".".join(identifiers)
|
dot_identifiers = ".".join(identifiers)
|
||||||
|
|
||||||
partial = await mongo_collection.find_one(
|
partial = await mongo_collection.find_one(
|
||||||
@@ -91,10 +97,14 @@ class Mongo(BaseDriver):
|
|||||||
|
|
||||||
for i in identifiers:
|
for i in identifiers:
|
||||||
partial = partial[i]
|
partial = partial[i]
|
||||||
|
if isinstance(partial, dict):
|
||||||
|
return self._unescape_dict_keys(partial)
|
||||||
return partial
|
return partial
|
||||||
|
|
||||||
async def set(self, *identifiers: str, value=None):
|
async def set(self, *identifiers: str, value=None):
|
||||||
dot_identifiers = ".".join(identifiers)
|
dot_identifiers = ".".join(map(self._escape_key, identifiers))
|
||||||
|
if isinstance(value, dict):
|
||||||
|
value = self._escape_dict_keys(value)
|
||||||
|
|
||||||
mongo_collection = self.get_collection()
|
mongo_collection = self.get_collection()
|
||||||
|
|
||||||
@@ -105,7 +115,7 @@ class Mongo(BaseDriver):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def clear(self, *identifiers: str):
|
async def clear(self, *identifiers: str):
|
||||||
dot_identifiers = ".".join(identifiers)
|
dot_identifiers = ".".join(map(self._escape_key, identifiers))
|
||||||
mongo_collection = self.get_collection()
|
mongo_collection = self.get_collection()
|
||||||
|
|
||||||
if len(identifiers) > 0:
|
if len(identifiers) > 0:
|
||||||
@@ -115,6 +125,62 @@ class Mongo(BaseDriver):
|
|||||||
else:
|
else:
|
||||||
await mongo_collection.delete_one({"_id": self.unique_cog_identifier})
|
await mongo_collection.delete_one({"_id": self.unique_cog_identifier})
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _escape_key(key: str) -> str:
|
||||||
|
return _SPECIAL_CHAR_PATTERN.sub(_replace_with_escaped, key)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _unescape_key(key: str) -> str:
|
||||||
|
return _CHAR_ESCAPE_PATTERN.sub(_replace_with_unescaped, key)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _escape_dict_keys(cls, data: dict) -> dict:
|
||||||
|
"""Recursively escape all keys in a dict."""
|
||||||
|
ret = {}
|
||||||
|
for key, value in data.items():
|
||||||
|
key = cls._escape_key(key)
|
||||||
|
if isinstance(value, dict):
|
||||||
|
value = cls._escape_dict_keys(value)
|
||||||
|
ret[key] = value
|
||||||
|
return ret
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _unescape_dict_keys(cls, data: dict) -> dict:
|
||||||
|
"""Recursively unescape all keys in a dict."""
|
||||||
|
ret = {}
|
||||||
|
for key, value in data.items():
|
||||||
|
key = cls._unescape_key(key)
|
||||||
|
if isinstance(value, dict):
|
||||||
|
value = cls._unescape_dict_keys(value)
|
||||||
|
ret[key] = value
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
_SPECIAL_CHAR_PATTERN: Pattern[str] = re.compile(r"([.$]|\\U0000002E|\\U00000024)")
|
||||||
|
_SPECIAL_CHARS = {
|
||||||
|
".": "\\U0000002E",
|
||||||
|
"$": "\\U00000024",
|
||||||
|
"\\U0000002E": "\\U&0000002E",
|
||||||
|
"\\U00000024": "\\U&00000024",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _replace_with_escaped(match: Match[str]) -> str:
|
||||||
|
return _SPECIAL_CHARS[match[0]]
|
||||||
|
|
||||||
|
|
||||||
|
_CHAR_ESCAPE_PATTERN: Pattern[str] = re.compile(r"(\\U0000002E|\\U00000024)")
|
||||||
|
_CHAR_ESCAPES = {
|
||||||
|
"\\U0000002E": ".",
|
||||||
|
"\\U00000024": "$",
|
||||||
|
"\\U&0000002E": "\\U0000002E",
|
||||||
|
"\\U&00000024": "\\U00000024",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _replace_with_unescaped(match: Match[str]) -> str:
|
||||||
|
return _CHAR_ESCAPES[match[0]]
|
||||||
|
|
||||||
|
|
||||||
def get_config_details():
|
def get_config_details():
|
||||||
uri = None
|
uri = None
|
||||||
|
|||||||
44
redbot/core/errors.py
Normal file
44
redbot/core/errors.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import importlib.machinery
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import discord
|
||||||
|
|
||||||
|
from .i18n import Translator
|
||||||
|
|
||||||
|
_ = Translator(__name__, __file__)
|
||||||
|
|
||||||
|
|
||||||
|
class RedError(Exception):
|
||||||
|
"""Base error class for Red-related errors."""
|
||||||
|
|
||||||
|
|
||||||
|
class PackageAlreadyLoaded(RedError):
|
||||||
|
"""Raised when trying to load an already-loaded package."""
|
||||||
|
|
||||||
|
def __init__(self, spec: importlib.machinery.ModuleSpec, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.spec: importlib.machinery.ModuleSpec = spec
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"There is already a package named {self.spec.name.split('.')[-1]} loaded"
|
||||||
|
|
||||||
|
|
||||||
|
class BankError(RedError):
|
||||||
|
"""Base error class for bank-related errors."""
|
||||||
|
|
||||||
|
|
||||||
|
class BalanceTooHigh(BankError, OverflowError):
|
||||||
|
"""Raised when trying to set a user's balance to higher than the maximum."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, user: discord.abc.User, max_balance: int, currency_name: str, *args, **kwargs
|
||||||
|
):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.user = user
|
||||||
|
self.max_balance = max_balance
|
||||||
|
self.currency_name = currency_name
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return _("{user}'s balance cannot rise above {max:,} {currency}.").format(
|
||||||
|
user=self.user, max=self.max_balance, currency=self.currency_name
|
||||||
|
)
|
||||||
@@ -3,8 +3,6 @@ import re
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Callable, Union
|
from typing import Callable, Union
|
||||||
|
|
||||||
from . import commands
|
|
||||||
|
|
||||||
__all__ = ["get_locale", "set_locale", "reload_locales", "cog_i18n", "Translator"]
|
__all__ = ["get_locale", "set_locale", "reload_locales", "cog_i18n", "Translator"]
|
||||||
|
|
||||||
_current_locale = "en_us"
|
_current_locale = "en_us"
|
||||||
@@ -219,6 +217,12 @@ class Translator(Callable[[str], str]):
|
|||||||
self.translations.update({untranslated: translated})
|
self.translations.update({untranslated: translated})
|
||||||
|
|
||||||
|
|
||||||
|
# This import to be down here to avoid circular import issues.
|
||||||
|
# This will be cleaned up at a later date
|
||||||
|
# noinspection PyPep8
|
||||||
|
from . import commands
|
||||||
|
|
||||||
|
|
||||||
def cog_i18n(translator: Translator):
|
def cog_i18n(translator: Translator):
|
||||||
"""Get a class decorator to link the translator to this cog."""
|
"""Get a class decorator to link the translator to this cog."""
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from redbot.cogs.permissions.permissions import Permissions, GLOBAL
|
|||||||
|
|
||||||
def test_schema_update():
|
def test_schema_update():
|
||||||
old = {
|
old = {
|
||||||
GLOBAL: {
|
str(GLOBAL): {
|
||||||
"owner_models": {
|
"owner_models": {
|
||||||
"cogs": {
|
"cogs": {
|
||||||
"Admin": {"allow": [78631113035100160], "deny": [96733288462286848]},
|
"Admin": {"allow": [78631113035100160], "deny": [96733288462286848]},
|
||||||
@@ -19,7 +19,7 @@ def test_schema_update():
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
43733288462286848: {
|
"43733288462286848": {
|
||||||
"owner_models": {
|
"owner_models": {
|
||||||
"cogs": {
|
"cogs": {
|
||||||
"Admin": {
|
"Admin": {
|
||||||
@@ -43,22 +43,22 @@ def test_schema_update():
|
|||||||
assert new == (
|
assert new == (
|
||||||
{
|
{
|
||||||
"Admin": {
|
"Admin": {
|
||||||
GLOBAL: {78631113035100160: True, 96733288462286848: False},
|
str(GLOBAL): {"78631113035100160": True, "96733288462286848": False},
|
||||||
43733288462286848: {24231113035100160: True, 35533288462286848: False},
|
"43733288462286848": {"24231113035100160": True, "35533288462286848": False},
|
||||||
},
|
},
|
||||||
"Audio": {GLOBAL: {133049272517001216: True, "default": False}},
|
"Audio": {str(GLOBAL): {"133049272517001216": True, "default": False}},
|
||||||
"General": {43733288462286848: {133049272517001216: True, "default": False}},
|
"General": {"43733288462286848": {"133049272517001216": True, "default": False}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cleanup bot": {
|
"cleanup bot": {
|
||||||
GLOBAL: {78631113035100160: True, "default": False},
|
str(GLOBAL): {"78631113035100160": True, "default": False},
|
||||||
43733288462286848: {17831113035100160: True, "default": True},
|
"43733288462286848": {"17831113035100160": True, "default": True},
|
||||||
},
|
},
|
||||||
"ping": {GLOBAL: {96733288462286848: True, "default": True}},
|
"ping": {str(GLOBAL): {"96733288462286848": True, "default": True}},
|
||||||
"set adminrole": {
|
"set adminrole": {
|
||||||
43733288462286848: {
|
"43733288462286848": {
|
||||||
87733288462286848: True,
|
"87733288462286848": True,
|
||||||
95433288462286848: False,
|
"95433288462286848": False,
|
||||||
"default": True,
|
"default": True,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -475,3 +475,18 @@ async def test_get_raw_mixes_defaults(config):
|
|||||||
|
|
||||||
subgroup = await config.get_raw("subgroup")
|
subgroup = await config.get_raw("subgroup")
|
||||||
assert subgroup == {"foo": True, "bar": False}
|
assert subgroup == {"foo": True, "bar": False}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_cast_str_raw(config):
|
||||||
|
await config.set_raw(123, 456, value=True)
|
||||||
|
assert await config.get_raw(123, 456) is True
|
||||||
|
assert await config.get_raw("123", "456") is True
|
||||||
|
await config.clear_raw("123", 456)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_cast_str_nested(config):
|
||||||
|
config.register_global(foo={})
|
||||||
|
await config.foo.set({123: True, 456: {789: False}})
|
||||||
|
assert await config.foo() == {"123": True, "456": {"789": False}}
|
||||||
|
|||||||
Reference in New Issue
Block a user