From 7959e0c916cc1622bf8fb156e5c76b210a5ccc70 Mon Sep 17 00:00:00 2001 From: Draper <27962761+Drapersniper@users.noreply.github.com> Date: Fri, 30 Aug 2019 22:35:25 +0100 Subject: [PATCH] [Bank] Allow Bot Owner/Guild Owners to remove invalid users from the bank (#2845) * Add command to remove dead members from bank * Add a global check * Added a FIXME so `bank_local_clean` is updated once bulk-update is implemented Added a brief warning to warn devs not to use the `_get_base_group` as it can mess up their config files Removed a redundant existence check * Updated commit to reflect changes requested in review * Updated commit to reflect changes requested in review * :facepalm: * Return command to run with user id so we don't worry about safeguarding the command agaisn't invalid formats * Braaaainnn Removed aliases that used old naming scheme * TL:DR Added global bank support, and rework permissions Renamed `bank_local_clean` to `bank_prune` Added support for global banks to `bank_prune` `bank_prune` will now raise `BankPruneError` if trying to prune a local bank and `guild` is not supplied Renamed `cleanup` subgroup to `prune` `prune` subgroup will have 3 commands: `user` : Deletes the bank account for the specified member : Accepts `Union[discord.Member, discord.User, int]` `global` : Prune global bank accounts for all users who no longer share a server with the bot `local` : Prune local bank accounts for all users who are no longer in the guild Changed check for `prune` subgroup to be `@check_global_setting_admin()` [p]bank prune local : Can be run by Guild owners only [p]bank prune global : Can be run by Bot Owner only [p]bank prune user : Can be run by Admins, Guild owners and Bot Owner * Yikes ... Updated kwarg name * Fixed unexpected unindent: docstring of redbot.core.bank.bank_prune:14:Field list ends without a blank line * ... * 3rd time lucky? * 4th time lucky? * Fix Docstring * Initial commit to address review by Flame * Updated code to reflect Flame's comments * Skip pruning of unavailable guilds * Fixed typo in string * *sigh* black is the bane of my existence * addressed Flames commends Fixed [p]bank prune user, When run via DM it will now return an error message to the user (Thanks jack1142) * Time to get some sleep * 'DM' > 'DMs' in string * Add towncrier entries Signed-off-by: Draper * Update to reflect Flame's review Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com> --- changelog.d/2845.feature.1.rst | 5 ++ changelog.d/2845.feature.2.rst | 3 ++ redbot/cogs/economy/economy.py | 86 +++++++++++++++++++++++++++++++++- redbot/core/bank.py | 56 ++++++++++++++++++++++ redbot/core/config.py | 6 +++ redbot/core/errors.py | 4 ++ 6 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 changelog.d/2845.feature.1.rst create mode 100644 changelog.d/2845.feature.2.rst diff --git a/changelog.d/2845.feature.1.rst b/changelog.d/2845.feature.1.rst new file mode 100644 index 000000000..5a1cdcf30 --- /dev/null +++ b/changelog.d/2845.feature.1.rst @@ -0,0 +1,5 @@ +Added New commands to Economy + +- ``[p]bank prune user`` - This will delete a user's bank account. +- ``[p]bank prune local`` - This will prune the bank of accounts from users no longer in the server. +- ``[p]bank prune global`` - This will prune the global bank of accounts from users who do not share any servers with the bot. \ No newline at end of file diff --git a/changelog.d/2845.feature.2.rst b/changelog.d/2845.feature.2.rst new file mode 100644 index 000000000..63ac4a83a --- /dev/null +++ b/changelog.d/2845.feature.2.rst @@ -0,0 +1,3 @@ +Added :func:`bank_prune` to :module:`redbot.core.bank` + +- :func:`bank_prune` can be used to delete a specific user's bank account or remove all invalid bank accounts (For users who are not in the guild if bank is local or share servers with the bot if bank is global). \ No newline at end of file diff --git a/redbot/cogs/economy/economy.py b/redbot/cogs/economy/economy.py index f0c27a8f6..7029cedae 100644 --- a/redbot/cogs/economy/economy.py +++ b/redbot/cogs/economy/economy.py @@ -3,12 +3,13 @@ import logging import random from collections import defaultdict, deque, namedtuple from enum import Enum -from typing import cast, Iterable +from typing import cast, Iterable, Union import discord from redbot.cogs.bank import check_global_setting_guildowner, check_global_setting_admin -from redbot.core import Config, bank, commands, errors +from redbot.cogs.mod.converters import RawUserIds +from redbot.core import Config, bank, commands, errors, checks from redbot.core.i18n import Translator, cog_i18n from redbot.core.utils.chat_formatting import box, humanize_number from redbot.core.utils.menus import menu, DEFAULT_CONTROLS @@ -257,6 +258,87 @@ class Economy(commands.Cog): ) ) + @_bank.group(name="prune") + @check_global_setting_admin() + async def _prune(self, ctx): + """Prune bank accounts.""" + pass + + @_prune.command(name="local") + @commands.guild_only() + @checks.guildowner() + async def _local(self, ctx, confirmation: bool = False): + """Prune bank accounts for users no longer in the server.""" + global_bank = await bank.is_global() + if global_bank is True: + return await ctx.send(_("This command cannot be used with a global bank.")) + + if confirmation is False: + await ctx.send( + _( + "This will delete all bank accounts for users no longer in this server." + "\nIf you're sure, type " + "`{prefix}bank prune local yes`" + ).format(prefix=ctx.prefix) + ) + else: + await bank.bank_prune(self.bot, guild=ctx.guild) + await ctx.send( + _("Bank accounts for users no longer in this server have been deleted.") + ) + + @_prune.command(name="global") + @checks.is_owner() + async def _global(self, ctx, confirmation: bool = False): + """Prune bank accounts for users who no longer share a server with the bot.""" + global_bank = await bank.is_global() + if global_bank is False: + return await ctx.send(_("This command cannot be used with a local bank.")) + + if confirmation is False: + await ctx.send( + _( + "This will delete all bank accounts for users " + "who no longer share a server with the bot." + "\nIf you're sure, type `{prefix}bank prune global yes`" + ).format(prefix=ctx.prefix) + ) + else: + await bank.bank_prune(self.bot) + await ctx.send( + _( + "Bank accounts for users who " + "no longer share a server with the bot have been pruned." + ) + ) + + @_prune.command(usage=" [confirmation=False]") + async def user( + self, ctx, member_or_id: Union[discord.Member, RawUserIds], confirmation: bool = False + ): + """Delete the bank account of a specified user.""" + global_bank = await bank.is_global() + if global_bank is False and ctx.guild is None: + return await ctx.send(_("This command cannot be used in DMs with a local bank.")) + try: + name = member_or_id.display_name + uid = member_or_id.id + except AttributeError: + name = member_or_id + uid = member_or_id + + if confirmation is False: + await ctx.send( + _( + "This will delete {name}'s bank account." + "\nIf you're sure, type " + "`{prefix}bank prune user {id} yes`" + ).format(prefix=ctx.prefix, id=uid, name=name) + ) + else: + await bank.bank_prune(self.bot, guild=ctx.guild, user_id=uid) + await ctx.send(_("The bank account for {name} has been pruned.").format(name=name)) + @guild_only_check() @commands.command() async def payday(self, ctx: commands.Context): diff --git a/redbot/core/bank.py b/redbot/core/bank.py index 599956a29..a0b2d9399 100644 --- a/redbot/core/bank.py +++ b/redbot/core/bank.py @@ -8,6 +8,8 @@ import discord from redbot.core.utils.chat_formatting import humanize_number from . import Config, errors, commands from .i18n import Translator +from .bot import Red +from .errors import BankPruneError _ = Translator("Bank API", __file__) @@ -33,6 +35,7 @@ __all__ = [ "set_max_balance", "cost", "AbortPurchase", + "bank_prune", ] _MAX_BALANCE = 2 ** 63 - 1 @@ -365,6 +368,59 @@ async def wipe_bank(guild: Optional[discord.Guild] = None) -> None: await _conf.clear_all_members(guild) +async def bank_prune(bot: Red, guild: discord.Guild = None, user_id: int = None) -> None: + """Prune bank accounts from the bank. + + Parameters + ---------- + bot : Red + The bot. + guild : discord.Guild + The guild to prune. This is required if the bank is set to local. + user_id : int + The id of the user whose account will be pruned. + If supplied this will prune only this user's bank account + otherwise it will prune all invalid users from the bank. + + Raises + ------ + BankPruneError + If guild is :code:`None` and the bank is Local. + + """ + + global_bank = await is_global() + + if global_bank: + _guilds = [g for g in bot.guilds if not g.unavailable and g.large and not g.chunked] + _uguilds = [g for g in bot.guilds if g.unavailable] + group = _conf._get_base_group(_conf.USER) + + else: + if guild is None: + raise BankPruneError("'guild' can't be None when pruning a local bank") + _guilds = [guild] if not guild.unavailable and guild.large else [] + _uguilds = [guild] if guild.unavailable else [] + group = _conf._get_base_group(_conf.MEMBER, str(guild.id)) + + if user_id is None: + await bot.request_offline_members(*_guilds) + accounts = await group.all() + tmp = accounts.copy() + members = bot.get_all_members() if global_bank else guild.members + user_list = {str(m.id) for m in members if m.guild not in _uguilds} + + async with group.all() as bank_data: # FIXME: use-config-bulk-update + if user_id is None: + for acc in tmp: + if acc not in user_list: + del bank_data[acc] + else: + user_id = str(user_id) + if user_id in bank_data: + del bank_data[user_id] + + async def get_leaderboard(positions: int = None, guild: discord.Guild = None) -> List[tuple]: """ Gets the bank's leaderboard diff --git a/redbot/core/config.py b/redbot/core/config.py index 1fed17bba..2f4ba2ba7 100644 --- a/redbot/core/config.py +++ b/redbot/core/config.py @@ -883,6 +883,12 @@ class Config: self.custom_groups[group_identifier] = identifier_count def _get_base_group(self, category: str, *primary_keys: str) -> Group: + """ + .. warning:: + :code:`Config._get_base_group()` should not be used to get config groups as + this is not a safe operation. Using this could end up corrupting your config file. + """ + # noinspection PyTypeChecker pkey_len, is_custom = ConfigCategory.get_pkey_info(category, self.custom_groups) identifier_data = IdentifierData( cog_name=self.cog_name, diff --git a/redbot/core/errors.py b/redbot/core/errors.py index 5396ef240..1fec3d4eb 100644 --- a/redbot/core/errors.py +++ b/redbot/core/errors.py @@ -51,6 +51,10 @@ class BalanceTooHigh(BankError, OverflowError): ) +class BankPruneError(BankError): + """Raised when trying to prune a local bank and no server is specified.""" + + class MissingExtraRequirements(RedError): """Raised when an extra requirement is missing but required."""