Merge branch 'V3/release/3.0.0' into V3/develop

# Conflicts:
#	redbot/cogs/customcom/customcom.py
#	redbot/core/errors.py
This commit is contained in:
Toby Harradine 2018-10-16 09:42:38 +11:00
commit 8b4e12da81
11 changed files with 317 additions and 195 deletions

View File

@ -1,6 +1,6 @@
import re
from datetime import datetime, timedelta
from typing import Union, List, Callable
from typing import Union, List, Callable, Set
import discord
@ -323,15 +323,35 @@ class Cleanup(commands.Cog):
if "" in prefixes:
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):
if m.author.id == self.bot.user.id:
if m.author.id == bot_id:
return True
elif m == ctx.message:
return True
p = discord.utils.find(m.content.startswith, prefixes)
if p and len(p) > 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
to_delete = await self.get_messages_for_deletion(

View File

@ -3,13 +3,14 @@ import random
from datetime import datetime, timedelta
from inspect import Parameter
from collections import OrderedDict
from typing import Mapping, Tuple, Dict
from typing import Mapping, Tuple, Dict, Set
import discord
from redbot.core import Config, checks, commands
from redbot.core.utils.chat_formatting import box, pagify, escape
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
_ = Translator("CustomCommands", __file__)
@ -121,7 +122,7 @@ class CommandObj:
*,
response=None,
cooldowns: Mapping[str, int] = None,
ask_for: bool = True
ask_for: bool = True,
):
"""Edit an already existing custom command"""
ccinfo = await self.db(ctx.guild).commands.get_raw(command, default=None)
@ -330,12 +331,16 @@ class CustomCommands(commands.Cog):
await ctx.send(e.args[0])
@customcom.command(name="list")
async def cc_list(self, ctx):
"""List all available custom commands."""
@checks.bot_has_permissions(add_reactions=True)
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(
_(
"There are no custom commands in this server."
@ -345,8 +350,7 @@ class CustomCommands(commands.Cog):
return
results = []
for command, body in response.items():
for command, body in sorted(cc_dict.items(), key=lambda t: t[0]):
responses = body["response"]
if isinstance(responses, list):
result = ", ".join(responses)
@ -354,15 +358,33 @@ class CustomCommands(commands.Cog):
result = responses
else:
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 len(_commands) < 1500:
await ctx.send(box(_commands))
if await ctx.embed_requested():
content = "\n".join(map("**{0[0]}** {0[1]}".format, results))
pages = list(pagify(content, page_length=1024))
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:
for page in pagify(_commands, delims=[" ", "\n"]):
await ctx.author.send(box(page))
content = "\n".join(map("{0[0]:<12} : {0[1]}".format, results))
pages = list(map(box, pagify(content, page_length=2000, shorten_by=10)))
await menus.menu(ctx, pages, menus.DEFAULT_CONTROLS)
@customcom.command(name="show")
async def cc_show(self, ctx, command_name: str):
@ -606,3 +628,14 @@ class CustomCommands(commands.Cog):
else:
return 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)))

View File

@ -1,7 +1,10 @@
import discord
from redbot.core import commands
from redbot.core.i18n import Translator
from .installable import Installable
_ = Translator("Koala", __file__)
class InstalledCog(Installable):
@classmethod

View File

@ -325,13 +325,12 @@ class Downloader(commands.Cog):
You may only uninstall cogs which were previously installed
by Downloader.
"""
# noinspection PyUnresolvedReferences,PyProtectedMember
real_name = cog.name
poss_installed_path = (await self.cog_install_path()) / real_name
if poss_installed_path.exists():
ctx.bot.unload_extension(real_name)
await self._delete_cog(poss_installed_path)
# noinspection PyTypeChecker
await self._remove_from_installed(cog)
await ctx.send(
_("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."
" Also make sure you've unloaded the cog"
" with `{prefix}unload {cog_name}`."
).format(cog_name=real_name)
).format(prefix=ctx.prefix, cog_name=real_name)
)
@cog.command(name="update")
@ -372,13 +371,18 @@ class Downloader(commands.Cog):
await self._reinstall_libraries(installed_and_updated)
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)))
else:
await ctx.send(_("All installed cogs are already up to date."))
return
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?")
can_react = ctx.channel.permissions_for(ctx.me).add_reactions
if not can_react:
@ -402,7 +406,6 @@ class Downloader(commands.Cog):
if can_react:
with contextlib.suppress(discord.Forbidden):
await query.clear_reactions()
await ctx.invoke(ctx.bot.get_cog("Core").reload, *cognames)
else:
if can_react:

View File

@ -8,7 +8,7 @@ from typing import cast, Iterable
import discord
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.utils.chat_formatting import box
from redbot.core.utils.menus import menu, DEFAULT_CONTROLS
@ -171,7 +171,7 @@ class Economy(commands.Cog):
try:
await bank.transfer_credits(from_, to, amount)
except ValueError as e:
except (ValueError, errors.BalanceTooHigh) as e:
return await ctx.send(str(e))
await ctx.send(
@ -195,36 +195,35 @@ class Economy(commands.Cog):
author = ctx.author
currency = await bank.get_currency_name(ctx.guild)
if creds.operation == "deposit":
await bank.deposit_credits(to, creds.sum)
await ctx.send(
_("{author} added {num} {currency} to {user}'s account.").format(
try:
if creds.operation == "deposit":
await bank.deposit_credits(to, creds.sum)
msg = _("{author} added {num} {currency} to {user}'s account.").format(
author=author.display_name,
num=creds.sum,
currency=currency,
user=to.display_name,
)
)
elif creds.operation == "withdraw":
await bank.withdraw_credits(to, creds.sum)
await ctx.send(
_("{author} removed {num} {currency} from {user}'s account.").format(
elif creds.operation == "withdraw":
await bank.withdraw_credits(to, creds.sum)
msg = _("{author} removed {num} {currency} from {user}'s account.").format(
author=author.display_name,
num=creds.sum,
currency=currency,
user=to.display_name,
)
)
else:
await bank.set_balance(to, creds.sum)
msg = _("{author} set {user}'s account balance to {num} {currency}.").format(
author=author.display_name,
num=creds.sum,
currency=currency,
user=to.display_name,
)
except (ValueError, errors.BalanceTooHigh) as e:
await ctx.send(str(e))
else:
await bank.set_balance(to, creds.sum)
await ctx.send(
_("{author} set {user}'s account balance to {num} {currency}.").format(
author=author.display_name,
num=creds.sum,
currency=currency,
user=to.display_name,
)
)
await ctx.send(msg)
@_bank.command()
@check_global_setting_guildowner()
@ -260,7 +259,18 @@ class Economy(commands.Cog):
if await bank.is_global(): # Role payouts will not be used
next_payday = await self.config.user(author).next_payday()
if cur_time >= next_payday:
await bank.deposit_credits(author, await self.config.PAYDAY_CREDITS())
try:
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()
await self.config.user(author).next_payday.set(next_payday)
@ -297,14 +307,25 @@ class Economy(commands.Cog):
).PAYDAY_CREDITS() # Nice variable name
if role_credits > credit_amount:
credit_amount = role_credits
await bank.deposit_credits(author, credit_amount)
try:
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()
await self.config.member(author).next_payday.set(next_payday)
pos = await bank.get_leaderboard_position(author)
await ctx.send(
_(
"{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 are currently #{pos} on the global leaderboard!"
).format(
@ -444,7 +465,21 @@ class Economy(commands.Cog):
then = await bank.get_balance(author)
pay = payout["payout"](bid)
now = then - bid + pay
await bank.set_balance(author, now)
try:
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"])
else:
then = await bank.get_balance(author)
@ -561,10 +596,10 @@ class Economy(commands.Cog):
async def paydayamount(self, ctx: commands.Context, creds: int):
"""Set the amount earned each payday."""
guild = ctx.guild
credits_name = await bank.get_currency_name(guild)
if creds <= 0:
if creds <= 0 or creds > bank.MAX_BALANCE:
await ctx.send(_("Har har so funny."))
return
credits_name = await bank.get_currency_name(guild)
if await bank.is_global():
await self.config.PAYDAY_CREDITS.set(creds)
else:
@ -579,6 +614,9 @@ class Economy(commands.Cog):
async def rolepaydayamount(self, ctx: commands.Context, role: discord.Role, creds: int):
"""Set the amount earned each payday for a role."""
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)
if await bank.is_global():
await ctx.send(_("The bank must be per-server for per-role paydays to work."))

View File

@ -825,7 +825,7 @@ class Mod(commands.Cog):
@admin_or_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):
"""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
if user_voice_state is None:
await ctx.send(_("No voice state for that user!"))
@ -893,34 +893,33 @@ class Mod(commands.Cog):
author = ctx.author
if user_voice_state:
channel = user_voice_state.channel
if channel and 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)
await ctx.send(
_("Muted {user} in channel {channel.name}").format(user, channel=channel)
)
try:
await modlog.create_case(
self.bot,
guild,
ctx.message.created_at,
"boicemute",
user,
author,
reason,
until=None,
channel=channel,
if channel:
audit_reason = get_audit_reason(author, reason)
success, issue = await self.mute_user(guild, channel, author, user, audit_reason)
if success:
await ctx.send(
_("Muted {user} in channel {channel.name}").format(
user=user, channel=channel
)
)
except RuntimeError as e:
await ctx.send(e)
return
elif channel.permissions_for(user).speak is False:
await ctx.send(
_("That user is already muted in {channel}!").format(channel=channel.name)
)
return
try:
await modlog.create_case(
self.bot,
guild,
ctx.message.created_at,
"vmute",
user,
author,
reason,
until=None,
channel=channel,
)
except RuntimeError as e:
await ctx.send(e)
else:
await channel.send(issue)
else:
await ctx.send(_("That user is not in a voice channel right now!"))
else:
@ -938,13 +937,7 @@ class Mod(commands.Cog):
author = ctx.message.author
channel = ctx.message.channel
guild = ctx.guild
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
)
audit_reason = get_audit_reason(author, reason)
success, issue = await self.mute_user(guild, channel, author, user, audit_reason)
@ -975,26 +968,12 @@ class Mod(commands.Cog):
"""Mutes user in the server"""
author = ctx.message.author
guild = ctx.guild
if reason is None:
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)
audit_reason = get_audit_reason(author, reason)
mute_success = []
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)
mute_success.append((success, issue))
success, issue = await self.mute_user(guild, channel, author, user, audit_reason)
mute_success.append((success, issue))
await asyncio.sleep(0.1)
await ctx.send(_("User has been muted in this server."))
try:
@ -1015,7 +994,7 @@ class Mod(commands.Cog):
async def mute_user(
self,
guild: discord.Guild,
channel: discord.TextChannel,
channel: discord.abc.GuildChannel,
author: discord.Member,
user: discord.Member,
reason: str,
@ -1023,25 +1002,32 @@ class Mod(commands.Cog):
"""Mutes the specified user in the specified channel"""
overwrites = channel.overwrites_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"])
elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user):
return False, T_(mute_unmute_issues["hierarchy_problem"])
perms_cache[str(channel.id)] = {
"send_messages": overwrites.send_messages,
"add_reactions": overwrites.add_reactions,
}
overwrites.update(send_messages=False, add_reactions=False)
old_overs = {k: getattr(overwrites, k) for k in new_overs}
overwrites.update(**new_overs)
try:
await channel.set_permissions(user, overwrite=overwrites, reason=reason)
except discord.Forbidden:
return False, T_(mute_unmute_issues["permissions_issue"])
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
@commands.group()
@ -1061,37 +1047,39 @@ class Mod(commands.Cog):
):
"""Unmute a user in their current voice channel."""
user_voice_state = user.voice
guild = ctx.guild
author = ctx.author
if user_voice_state:
channel = user_voice_state.channel
if channel and channel.permissions_for(user).speak is False:
overwrites = channel.overwrites_for(user)
overwrites.speak = None
audit_reason = get_audit_reason(ctx.author, reason)
await channel.set_permissions(user, overwrite=overwrites, reason=audit_reason)
author = ctx.author
guild = ctx.guild
await ctx.send(
_("Unmuted {}#{} in channel {}").format(
user.name, user.discriminator, channel.name
)
if channel:
audit_reason = get_audit_reason(author, reason)
success, message = await self.unmute_user(
guild, channel, author, user, audit_reason
)
try:
await modlog.create_case(
self.bot,
guild,
ctx.message.created_at,
"voiceunmute",
user,
author,
reason,
until=None,
channel=channel,
if success:
await ctx.send(
_("Unmuted {user} in channel {channel.name}").format(
user=user, channel=channel
)
)
except RuntimeError as e:
await ctx.send(e)
elif channel.permissions_for(user).speak:
await ctx.send(_("That user is already unmuted in {}!").format(channel.name))
return
try:
await modlog.create_case(
self.bot,
guild,
ctx.message.created_at,
"vunmute",
user,
author,
reason,
until=None,
channel=channel,
)
except RuntimeError as e:
await ctx.send(e)
else:
await ctx.send(_("Unmute failed. Reason: {}").format(message))
else:
await ctx.send(_("That user is not in a voice channel right now!"))
else:
@ -1109,8 +1097,9 @@ class Mod(commands.Cog):
channel = ctx.channel
author = ctx.author
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:
await ctx.send(_("User unmuted in this channel."))
@ -1141,16 +1130,11 @@ class Mod(commands.Cog):
"""Unmute a user in this server."""
guild = ctx.guild
author = ctx.author
audit_reason = get_audit_reason(author, reason)
unmute_success = []
for channel in guild.channels:
if not isinstance(channel, discord.TextChannel):
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)
success, message = await self.unmute_user(guild, channel, author, user, audit_reason)
unmute_success.append((success, message))
await asyncio.sleep(0.1)
await ctx.send(_("User has been unmuted in this server."))
@ -1171,45 +1155,37 @@ class Mod(commands.Cog):
async def unmute_user(
self,
guild: discord.Guild,
channel: discord.TextChannel,
channel: discord.abc.GuildChannel,
author: discord.Member,
user: discord.Member,
reason: str,
) -> (bool, str):
overwrites = channel.overwrites_for(user)
permissions = channel.permissions_for(user)
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"])
elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user):
return False, T_(mute_unmute_issues["hierarchy_problem"])
if channel.id in perms_cache:
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)
overwrites.update(**old_values)
try:
if not is_empty:
await channel.set_permissions(user, overwrite=overwrites)
else:
if overwrites.is_empty():
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:
return False, T_(mute_unmute_issues["permissions_issue"])
else:
try:
del perms_cache[channel.id]
except KeyError:
pass
else:
await self.settings.member(user).perms_cache.set(perms_cache)
await self.settings.member(user).clear_raw("perms_cache", str(channel.id))
return True, None
@commands.group()
@ -1695,20 +1671,15 @@ class Mod(commands.Cog):
while len(nick_list) > 20:
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
mute_unmute_issues = {
"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": _(
"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": _(
"Failed to mute user. I need the manage roles "
"permission and the user I'm muting must be "

View File

@ -626,8 +626,13 @@ class Streams(commands.Cog):
raw_stream["_messages_cache"] = []
for raw_msg in raw_msg_cache:
chn = self.bot.get_channel(raw_msg["channel"])
msg = await chn.get_message(raw_msg["message"])
raw_stream["_messages_cache"].append(msg)
if chn is not None:
try:
msg = await chn.get_message(raw_msg["message"])
except discord.HTTPException:
pass
else:
raw_stream["_messages_cache"].append(msg)
token = await self.db.tokens.get_raw(_class.__name__, default=None)
if token is not None:
raw_stream["token"] = token
@ -646,8 +651,13 @@ class Streams(commands.Cog):
raw_community["_messages_cache"] = []
for raw_msg in raw_msg_cache:
chn = self.bot.get_channel(raw_msg["channel"])
msg = await chn.get_message(raw_msg["message"])
raw_community["_messages_cache"].append(msg)
if chn is not None:
try:
msg = await chn.get_message(raw_msg["message"])
except discord.HTTPException:
pass
else:
raw_community["_messages_cache"].append(msg)
token = await self.db.tokens.get_raw(_class.__name__, default=None)
communities.append(_class(token=token, **raw_community))

View File

@ -148,5 +148,5 @@ class VersionInfo:
)
__version__ = "3.0.0rc1.post1"
__version__ = "3.0.0rc2"
version_info = VersionInfo.from_str(__version__)

View File

@ -4,9 +4,10 @@ from typing import Union, List, Optional
import discord
from redbot.core import Config
from . import Config, errors
__all__ = [
"MAX_BALANCE",
"Account",
"get_balance",
"set_balance",
@ -26,6 +27,8 @@ __all__ = [
"set_default_balance",
]
MAX_BALANCE = 2 ** 63 - 1
_DEFAULT_GLOBAL = {
"is_global": False,
"bank_name": "Twentysix bank",
@ -170,10 +173,22 @@ async def set_balance(member: discord.Member, amount: int) -> int:
------
ValueError
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:
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():
group = _conf.user(member)
else:

View File

@ -1,4 +1,5 @@
import asyncio
import inspect
import os
import logging
from collections import Counter
@ -236,20 +237,13 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
if cog is None:
return
for when in ("before", "after"):
for cls in inspect.getmro(cog.__class__):
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:
pass
else:
self.remove_permissions_hook(hook)
self.remove_permissions_hook(hook)
super().remove_cog(cogname)
@ -390,10 +384,17 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
)
if not hasattr(cog, "requires"):
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):
_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(
_attr, commands.Command
):

View File

@ -1,4 +1,11 @@
import importlib.machinery
from typing import Optional
import discord
from .i18n import Translator
_ = Translator(__name__, __file__)
class RedError(Exception):
@ -21,3 +28,24 @@ class CogLoadError(RedError):
The message will be send to the user."""
pass
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
)