diff --git a/redbot/cogs/mod/events.py b/redbot/cogs/mod/events.py index 9226d8650..a9803753a 100644 --- a/redbot/cogs/mod/events.py +++ b/redbot/cogs/mod/events.py @@ -42,18 +42,19 @@ class Events(MixinMeta): return False async def check_mention_spam(self, message): - guild = message.guild - author = message.author + guild, author = message.guild, message.author + mention_spam = await self.config.guild(guild).mention_spam.all() - max_mentions = await self.config.guild(guild).ban_mention_spam() - if max_mentions: - mentions = set(message.mentions) - if len(mentions) >= max_mentions: + mentions = set(message.mentions) + if mention_spam["ban"]: + if len(mentions) >= mention_spam["ban"]: try: await guild.ban(author, reason=_("Mention spam (Autoban)")) except discord.HTTPException: - log.info( - "Failed to ban member for mention spam in server {}.".format(guild.id) + log.warning( + "Failed to ban a member ({member}) for mention spam in server {guild}.".format( + member=author.id, guild=guild.id + ) ) else: await modlog.create_case( @@ -68,6 +69,62 @@ class Events(MixinMeta): channel=None, ) return True + + if mention_spam["kick"]: + if len(mentions) >= mention_spam["kick"]: + try: + await guild.kick(author, reason=_("Mention Spam (Autokick)")) + except discord.HTTPException: + log.warning( + "Failed to kick a member ({member}) for mention spam in server {guild}".format( + member=author.id, guild=guild.id + ) + ) + else: + await modlog.create_case( + self.bot, + guild, + message.created_at, + "kick", + author, + guild.me, + _("Mention spam (Autokick)"), + until=None, + channel=None, + ) + return True + + if mention_spam["warn"]: + if len(mentions) >= mention_spam["warn"]: + try: + await author.send(_("Please do not mass mention people!")) + except (discord.HTTPException, discord.Forbidden): + try: + await message.channel.send( + _("{member}, Please do not mass mention people!").format( + member=author.mention + ) + ) + except (discord.HTTPException, discord.Forbidden): + log.warning( + "Failed to warn a member ({member}) for mention spam in server {guild}".format( + member=author.id, guild=guild.id + ) + ) + return False + + await modlog.create_case( + self.bot, + guild, + message.created_at, + "warning", + author, + guild.me, + _("Mention spam (Autowarn)"), + until=None, + channel=None, + ) + return True return False @commands.Cog.listener() diff --git a/redbot/cogs/mod/mod.py b/redbot/cogs/mod/mod.py index 304e9d258..854e6944d 100644 --- a/redbot/cogs/mod/mod.py +++ b/redbot/cogs/mod/mod.py @@ -49,7 +49,7 @@ class Mod( default_global_settings = {"version": ""} default_guild_settings = { - "ban_mention_spam": False, + "mention_spam": {"ban": None, "kick": None, "warn": None}, "delete_repeats": -1, "ignored": False, "respect_hierarchy": True, @@ -130,7 +130,7 @@ class Mod( val = 3 else: val = -1 - await self.config.guild(discord.Object(id=guild_id)).delete_repeats.set(val) + await self.config.guild_from_id(guild_id).delete_repeats.set(val) await self.config.version.set("1.0.0") # set version of last update if await self.config.version() < "1.1.0": message_sent = False @@ -165,6 +165,16 @@ class Mod( self.bot.loop.create_task(send_to_owners_with_prefix_replaced(self.bot, msg)) break await self.config.version.set("1.2.0") + if await self.config.version() < "1.3.0": + guild_dict = await self.config.all_guilds() + async for guild_id in AsyncIter(guild_dict.keys(), steps=25): + async with self.config.guild_from_id(guild_id).all() as guild_data: + current_state = guild_data.pop("ban_mention_spam", False) + if current_state is not False: + if "mention_spam" not in guild_data: + guild_data["mention_spam"] = {} + guild_data["mention_spam"]["ban"] = current_state + await self.config.version.set("1.3.0") @commands.command() @commands.is_owner() diff --git a/redbot/cogs/mod/settings.py b/redbot/cogs/mod/settings.py index 2948c9a8e..373315729 100644 --- a/redbot/cogs/mod/settings.py +++ b/redbot/cogs/mod/settings.py @@ -25,7 +25,9 @@ class ModSettings(MixinMeta): guild = ctx.guild data = await self.config.guild(guild).all() delete_repeats = data["delete_repeats"] - ban_mention_spam = data["ban_mention_spam"] + warn_mention_spam = data["mention_spam"]["warn"] + kick_mention_spam = data["mention_spam"]["kick"] + ban_mention_spam = data["mention_spam"]["ban"] respect_hierarchy = data["respect_hierarchy"] delete_delay = data["delete_delay"] reinvite_on_unban = data["reinvite_on_unban"] @@ -37,6 +39,16 @@ class ModSettings(MixinMeta): if delete_repeats != -1 else _("No") ) + msg += _("Warn mention spam: {num_mentions}\n").format( + num_mentions=_("{num} mentions").format(num=warn_mention_spam) + if warn_mention_spam + else _("No") + ) + msg += _("Kick mention spam: {num_mentions}\n").format( + num_mentions=_("{num} mentions").format(num=kick_mention_spam) + if kick_mention_spam + else _("No") + ) msg += _("Ban mention spam: {num_mentions}\n").format( num_mentions=_("{num} mentions").format(num=ban_mention_spam) if ban_mention_spam @@ -87,35 +99,131 @@ class ModSettings(MixinMeta): _("Role hierarchy will be ignored when moderation commands are issued.") ) - @modset.command() + @modset.group() @commands.guild_only() - async def banmentionspam(self, ctx: commands.Context, max_mentions: int = 0): + async def mentionspam(self, ctx: commands.Context): + """ + Manage the automoderation settings for mentionspam. + """ + + @mentionspam.command(name="warn") + @commands.guild_only() + async def mentionspam_warn(self, ctx: commands.Context, max_mentions: int): + """ + Sets the autowarn conditions for mention spam. + + Users will be warned if they send any messages which contain more than + `` mentions. + + `` Must be 0 or greater. Set to 0 to disable this feature. + """ + mention_spam = await self.config.guild(ctx.guild).mention_spam.all() + if not max_mentions: + if not mention_spam["warn"]: + return await ctx.send(_("Autowarn for mention spam is already disabled.")) + await self.config.guild(ctx.guild).mention_spam.warn.set(False) + return await ctx.send(_("Autowarn for mention spam disabled.")) + + if max_mentions < 1: + return await ctx.send(_("`` must be 1 or higher to autowarn.")) + + mismatch_message = "" + + if mention_spam["kick"]: + if max_mentions >= mention_spam["kick"]: + mismatch_message += _("\nAutowarn is equal to or higher than autokick.") + + if mention_spam["ban"]: + if max_mentions >= mention_spam["ban"]: + mismatch_message += _("\nAutowarn is equal to or higher than autoban.") + + await self.config.guild(ctx.guild).mention_spam.warn.set(max_mentions) + await ctx.send( + _( + "Autowarn for mention spam enabled. " + "Anyone mentioning {max_mentions} or more different people " + "in a single message will be autowarned.\n{mismatch_message}" + ).format(max_mentions=max_mentions, mismatch_message=mismatch_message) + ) + + @mentionspam.command(name="kick") + @commands.guild_only() + async def mentionspam_kick(self, ctx: commands.Context, max_mentions: int): + """ + Sets the autokick conditions for mention spam. + + Users will be kicked if they send any messages which contain more than + `` mentions. + + `` Must be 0 or greater. Set to 0 to disable this feature. + """ + mention_spam = await self.config.guild(ctx.guild).mention_spam.all() + if not max_mentions: + if not mention_spam["kick"]: + return await ctx.send(_("Autokick for mention spam is already disabled.")) + await self.config.guild(ctx.guild).mention_spam.kick.set(False) + return await ctx.send(_("Autokick for mention spam disabled.")) + + if max_mentions < 1: + return await ctx.send(_("`` must be 1 or higher to autokick.")) + + mismatch_message = "" + + if mention_spam["warn"]: + if max_mentions <= mention_spam["warn"]: + mismatch_message += _("\nAutokick is equal to or lower than autowarn.") + + if mention_spam["ban"]: + if max_mentions >= mention_spam["ban"]: + mismatch_message += _("\nAutokick is equal to or higher than autoban.") + + await self.config.guild(ctx.guild).mention_spam.kick.set(max_mentions) + await ctx.send( + _( + "Autokick for mention spam enabled. " + "Anyone mentioning {max_mentions} or more different people " + "in a single message will be autokicked.\n{mismatch_message}" + ).format(max_mentions=max_mentions, mismatch_message=mismatch_message) + ) + + @mentionspam.command(name="ban") + @commands.guild_only() + async def mentionspam_ban(self, ctx: commands.Context, max_mentions: int): """Set the autoban conditions for mention spam. Users will be banned if they send any message which contains more than `` mentions. - `` must be at least 5. Set to 0 to disable. + `` Must be 0 or greater. Set to 0 to disable this feature. """ - guild = ctx.guild - if max_mentions: - if max_mentions < 5: - max_mentions = 5 - await self.config.guild(guild).ban_mention_spam.set(max_mentions) - await ctx.send( - _( - "Autoban for mention spam enabled. " - "Anyone mentioning {max_mentions} or more different people " - "in a single message will be autobanned." - ).format(max_mentions=max_mentions) - ) - else: - cur_setting = await self.config.guild(guild).ban_mention_spam() - if not cur_setting: - await ctx.send(_("Autoban for mention spam is already disabled.")) - return - await self.config.guild(guild).ban_mention_spam.set(False) - await ctx.send(_("Autoban for mention spam disabled.")) + mention_spam = await self.config.guild(ctx.guild).mention_spam.all() + if not max_mentions: + if not mention_spam["ban"]: + return await ctx.send(_("Autoban for mention spam is already disabled.")) + await self.config.guild(ctx.guild).mention_spam.ban.set(False) + return await ctx.send(_("Autoban for mention spam disabled.")) + + if max_mentions < 1: + return await ctx.send(_("`` must be 1 or higher to autoban.")) + + mismatch_message = "" + + if mention_spam["warn"]: + if max_mentions <= mention_spam["warn"]: + mismatch_message += _("\nAutoban is equal to or lower than autowarn.") + + if mention_spam["kick"]: + if max_mentions <= mention_spam["kick"]: + mismatch_message += _("\nAutoban is equal to or lower than autokick.") + + await self.config.guild(ctx.guild).mention_spam.ban.set(max_mentions) + await ctx.send( + _( + "Autoban for mention spam enabled. " + "Anyone mentioning {max_mentions} or more different people " + "in a single message will be autobanned.\n{mismatch_message}" + ).format(max_mentions=max_mentions, mismatch_message=mismatch_message) + ) @modset.command() @commands.guild_only() diff --git a/redbot/core/generic_casetypes.py b/redbot/core/generic_casetypes.py index 415728fe3..281479c36 100644 --- a/redbot/core/generic_casetypes.py +++ b/redbot/core/generic_casetypes.py @@ -92,6 +92,13 @@ voicekick = { "case_str": "Voice Kick", } +warning = { + "name": "warning", + "default_setting": True, + "image": "\N{WARNING SIGN}\N{VARIATION SELECTOR-16}", + "case_str": "Warning", +} + all_generics = ( ban, kick, @@ -108,4 +115,5 @@ all_generics = ( serverunmute, channelunmute, voicekick, + warning, )