mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-09 04:38:55 -05:00
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:
commit
8b4e12da81
@ -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(
|
||||
|
||||
@ -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)))
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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)
|
||||
|
||||
try:
|
||||
if creds.operation == "deposit":
|
||||
await bank.deposit_credits(to, creds.sum)
|
||||
await ctx.send(
|
||||
_("{author} added {num} {currency} to {user}'s account.").format(
|
||||
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(
|
||||
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)
|
||||
await ctx.send(
|
||||
_("{author} set {user}'s account balance to {num} {currency}.").format(
|
||||
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 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:
|
||||
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
|
||||
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
|
||||
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."))
|
||||
|
||||
@ -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,20 +893,23 @@ 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)
|
||||
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, channel=channel)
|
||||
_("Muted {user} in channel {channel.name}").format(
|
||||
user=user, channel=channel
|
||||
)
|
||||
)
|
||||
try:
|
||||
await modlog.create_case(
|
||||
self.bot,
|
||||
guild,
|
||||
ctx.message.created_at,
|
||||
"boicemute",
|
||||
"vmute",
|
||||
user,
|
||||
author,
|
||||
reason,
|
||||
@ -915,12 +918,8 @@ class Mod(commands.Cog):
|
||||
)
|
||||
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
|
||||
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,24 +968,10 @@ 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))
|
||||
await asyncio.sleep(0.1)
|
||||
@ -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,18 +1047,21 @@ 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
|
||||
if channel:
|
||||
audit_reason = get_audit_reason(author, reason)
|
||||
|
||||
success, message = await self.unmute_user(
|
||||
guild, channel, author, user, audit_reason
|
||||
)
|
||||
|
||||
if success:
|
||||
await ctx.send(
|
||||
_("Unmuted {}#{} in channel {}").format(
|
||||
user.name, user.discriminator, channel.name
|
||||
_("Unmuted {user} in channel {channel.name}").format(
|
||||
user=user, channel=channel
|
||||
)
|
||||
)
|
||||
try:
|
||||
@ -1080,7 +1069,7 @@ class Mod(commands.Cog):
|
||||
self.bot,
|
||||
guild,
|
||||
ctx.message.created_at,
|
||||
"voiceunmute",
|
||||
"vunmute",
|
||||
user,
|
||||
author,
|
||||
reason,
|
||||
@ -1089,9 +1078,8 @@ class Mod(commands.Cog):
|
||||
)
|
||||
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
|
||||
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 "
|
||||
|
||||
@ -626,7 +626,12 @@ class Streams(commands.Cog):
|
||||
raw_stream["_messages_cache"] = []
|
||||
for raw_msg in raw_msg_cache:
|
||||
chn = self.bot.get_channel(raw_msg["channel"])
|
||||
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:
|
||||
@ -646,7 +651,12 @@ class Streams(commands.Cog):
|
||||
raw_community["_messages_cache"] = []
|
||||
for raw_msg in raw_msg_cache:
|
||||
chn = self.bot.get_channel(raw_msg["channel"])
|
||||
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))
|
||||
|
||||
@ -148,5 +148,5 @@ class VersionInfo:
|
||||
)
|
||||
|
||||
|
||||
__version__ = "3.0.0rc1.post1"
|
||||
__version__ = "3.0.0rc2"
|
||||
version_info = VersionInfo.from_str(__version__)
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import asyncio
|
||||
import inspect
|
||||
import os
|
||||
import logging
|
||||
from collections import Counter
|
||||
@ -236,16 +237,9 @@ 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}")
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
self.remove_permissions_hook(hook, when)
|
||||
|
||||
try:
|
||||
hook = getattr(cog, f"_{cog.__class__.__name__}__red_permissions_before")
|
||||
hook = getattr(cog, f"_{cls.__name__}__permissions_hook")
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
@ -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
|
||||
):
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user