diff --git a/redbot/cogs/filter/filter.py b/redbot/cogs/filter/filter.py index ee80467cd..4c9c21075 100644 --- a/redbot/cogs/filter/filter.py +++ b/redbot/cogs/filter/filter.py @@ -19,7 +19,9 @@ class Filter: default_guild_settings = { "filter": [], "filterban_count": 0, - "filterban_time": 0 + "filterban_time": 0, + "filter_names": False, + "filter_default_name": "John Doe" } default_member_settings = { "filter_count": 0, @@ -27,7 +29,10 @@ class Filter: } self.settings.register_guild(**default_guild_settings) self.settings.register_member(**default_member_settings) - self.bot.loop.create_task(self.register_filterban()) + self.register_task = self.bot.loop.create_task(self.register_filterban()) + + def __unload(self): + self.register_task.cancel() async def register_filterban(self): try: @@ -123,6 +128,37 @@ class Filter: else: await ctx.send(_("Those words weren't in the filter.")) + @_filter.command(name="names") + async def filter_names(self, ctx: RedContext): + """ + Toggles whether or not to check names and nicknames against the filter + This is disabled by default + """ + guild = ctx.guild + current_setting = await self.settings.guild(guild).filter_names() + await self.settings.guild(guild).filter_names.set(not current_setting) + if current_setting: + await ctx.send( + _("Names and nicknames will no longer be " + "checked against the filter") + ) + else: + await ctx.send( + _("Names and nicknames will now be checked against " + "the filter") + ) + + @_filter.command(name="defaultname") + async def filter_default_name(self, ctx: RedContext, name: str): + """ + Sets the default name to use if filtering names is enabled + Note that this has no effect if filtering names is disabled + The default name used is John Doe + """ + guild = ctx.guild + await self.settings.guild(guild).filter_default_name.set(name) + await ctx.send(_("The name to use on filtered names has been set")) + @_filter.command(name="ban") async def filter_ban( self, ctx: commands.Context, count: int, timeframe: int): @@ -238,3 +274,54 @@ class Filter: return await self.check_filter(message) + + async def on_member_update(self, before: discord.Member, after: discord.Member): + if not after.guild.me.guild_permissions.manage_nicknames: + return # No permissions to manage nicknames, so can't do anything + word_list = await self.settings.guild(after.guild).filter() + filter_names = await self.settings.guild(after.guild).filter_names() + name_to_use = await self.settings.guild(after.guild).filter_default_name() + if not filter_names: + return + + name_filtered = False + nick_filtered = False + + for w in word_list: + if w in after.name: + name_filtered = True + if after.nick and w in after.nick: # since Member.nick can be None + nick_filtered = True + if name_filtered and nick_filtered: # Both true, so break from loop + break + + if name_filtered and after.nick is None: + try: + await after.edit(nick=name_to_use, reason="Filtered name") + except: + pass + elif nick_filtered: + try: + await after.edit(nick=None, reason="Filtered nickname") + except: + pass + + async def on_member_join(self, member: discord.Member): + guild = member.guild + if not guild.me.guild_permissions.manage_nicknames: + return + word_list = await self.settings.guild(guild).filter() + filter_names = await self.settings.guild(guild).filter_names() + name_to_use = await self.settings.guild(guild).filter_default_name + + if not filter_names: + return + + for w in word_list: + if w in member.name: + try: + await member.edit(nick=name_to_use, reason="Filtered name") + except: + pass + break + diff --git a/redbot/cogs/filter/locales/messages.pot b/redbot/cogs/filter/locales/messages.pot index b4c3e47c7..70fd41d8b 100644 --- a/redbot/cogs/filter/locales/messages.pot +++ b/redbot/cogs/filter/locales/messages.pot @@ -5,13 +5,61 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2017-10-22 16:33-0800\n" +"POT-Creation-Date: 2017-11-28 13:25-0900\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=cp1252\n" +"Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" +#: filter.py:62 +msgid "Filtered in this server:" +msgstr "" + +#: filter.py:67 +msgid "I can't send direct messages to you." +msgstr "" + +#: filter.py:96 +msgid "Words added to filter." +msgstr "" + +#: filter.py:98 +msgid "Words already in the filter." +msgstr "" + +#: filter.py:127 +msgid "Words removed from filter." +msgstr "" + +#: filter.py:129 +msgid "Those words weren't in the filter." +msgstr "" + +#: filter.py:142 +msgid "Names and nicknames will no longer be checked against the filter" +msgstr "" + +#: filter.py:147 +msgid "Names and nicknames will now be checked against the filter" +msgstr "" + +#: filter.py:160 +msgid "The name to use on filtered names has been set" +msgstr "" + +#: filter.py:171 +msgid "Count and timeframe either both need to be 0 or both need to be greater than 0!" +msgstr "" + +#: filter.py:179 +msgid "Autoban disabled." +msgstr "" + +#: filter.py:183 +msgid "Count and time have been set." +msgstr "" + diff --git a/redbot/cogs/mod/locales/messages.pot b/redbot/cogs/mod/locales/messages.pot index b4c3e47c7..4b285493c 100644 --- a/redbot/cogs/mod/locales/messages.pot +++ b/redbot/cogs/mod/locales/messages.pot @@ -5,13 +5,266 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2017-10-22 16:33-0800\n" +"POT-Creation-Date: 2017-11-28 13:26-0900\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=cp1252\n" +"Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" +#: mod.py:209 +msgid "Role hierarchy will be checked when moderation commands are issued." +msgstr "" + +#: mod.py:213 +msgid "Role hierarchy will be ignored when moderation commands are issued." +msgstr "" + +#: mod.py:228 +msgid "Autoban for mention spam enabled. Anyone mentioning {} or more different people in a single message will be autobanned." +msgstr "" + +#: mod.py:239 +msgid "Autoban for mention spam disabled." +msgstr "" + +#: mod.py:249 +msgid "Messages repeated up to 3 times will be deleted." +msgstr "" + +#: mod.py:253 +msgid "Repeated messages will be ignored." +msgstr "" + +#: mod.py:267 +msgid "Command deleting disabled." +msgstr "" + +#: mod.py:270 +msgid "Delete delay set to {} seconds." +msgstr "" + +#: mod.py:275 +msgid "Bot will delete command messages after {} seconds. Set this value to -1 to stop deleting messages" +msgstr "" + +#: mod.py:279 +msgid "I will not delete command messages." +msgstr "" + +#: mod.py:292 +msgid "Users unbanned with {} will be reinvited." +msgstr "" + +#: mod.py:295 +msgid "Users unbanned with {} will not be reinvited." +msgstr "" + +#: mod.py:309 mod.py:349 mod.py:505 +msgid "I cannot let you do that. Self-harm is bad {}" +msgstr "" + +#: mod.py:313 mod.py:353 mod.py:509 +msgid "I cannot let you do that. You are not higher than the user in the role hierarchy." +msgstr "" + +#: mod.py:323 mod.py:379 mod.py:569 +msgid "I'm not allowed to do that." +msgstr "" + +#: mod.py:327 +msgid "Done. That felt good." +msgstr "" + +#: mod.py:369 +msgid "Invalid days. Must be between 0 and 7." +msgstr "" + +#: mod.py:384 +msgid "Done. It was about time." +msgstr "" + +#: mod.py:413 +msgid "User is already banned." +msgstr "" + +#: mod.py:429 +msgid "User not found. Have you provided the correct user ID?" +msgstr "" + +#: mod.py:433 +msgid "I lack the permissions to do this." +msgstr "" + +#: mod.py:435 +msgid "Done. The user will not be able to join this guild." +msgstr "" + +#: mod.py:471 mod.py:524 +msgid "" +"You have been banned and then unbanned as a quick way to delete your messages.\n" +"You can now join the guild again. {}" +msgstr "" + +#: mod.py:536 +msgid "My role is not high enough to softban that user." +msgstr "" + +#: mod.py:552 +msgid "Done. Enough chaos." +msgstr "" + +#: mod.py:586 +msgid "Couldn't find a user with that ID!" +msgstr "" + +#: mod.py:592 +msgid "It seems that user isn't banned!" +msgstr "" + +#: mod.py:600 +msgid "Something went wrong while attempting to unban that user" +msgstr "" + +#: mod.py:603 +msgid "Unbanned that user from this guild" +msgstr "" + +#: mod.py:618 +msgid "" +"You've been unbanned from {}.\n" +"Here is an invite for that guild: {}" +msgstr "" + +#: mod.py:622 +msgid "" +"I failed to send an invite to that user. Perhaps you may be able to send it for me?\n" +"Here's the invite link: {}" +msgstr "" + +#: mod.py:628 +msgid "Something went wrong when attempting to send that useran invite. Here's the link so you can try: {}" +msgstr "" + +#: mod.py:672 mod.py:709 +msgid "No voice state for that user!" +msgstr "" + +#: mod.py:686 +msgid "That user is already muted and deafened guild-wide!" +msgstr "" + +#: mod.py:689 +msgid "User has been banned from speaking or listening in voice channels" +msgstr "" + +#: mod.py:721 +msgid "That user isn't muted or deafened by the guild!" +msgstr "" + +#: mod.py:724 +msgid "User is now allowed to speak and listen in voice channels" +msgstr "" + +#: mod.py:753 +msgid "I cannot do that, I lack the '{}' permission." +msgstr "" + +#: mod.py:782 +msgid "Muted {}#{} in channel {}" +msgstr "" + +#: mod.py:796 +msgid "That user is already muted in {}!" +msgstr "" + +#: mod.py:799 mod.py:931 +msgid "That user is not in a voice channel right now!" +msgstr "" + +#: mod.py:801 mod.py:933 +msgid "No voice state for the target!" +msgstr "" + +#: mod.py:821 +msgid "User has been muted in this channel." +msgstr "" + +#: mod.py:857 +msgid "User has been muted in this guild." +msgstr "" + +#: mod.py:918 +msgid "Unmuted {}#{} in channel {}" +msgstr "" + +#: mod.py:928 +msgid "That user is already unmuted in {}!" +msgstr "" + +#: mod.py:948 +msgid "User unmuted in this channel." +msgstr "" + +#: mod.py:957 +msgid "Unmute failed. Reason: {}" +msgstr "" + +#: mod.py:980 +msgid "User has been unmuted in this guild." +msgstr "" + +#: mod.py:1044 +msgid "Channel added to ignore list." +msgstr "" + +#: mod.py:1046 +msgid "Channel already in ignore list." +msgstr "" + +#: mod.py:1055 +msgid "This guild has been added to the ignore list." +msgstr "" + +#: mod.py:1057 +msgid "This guild is already being ignored." +msgstr "" + +#: mod.py:1078 +msgid "Channel removed from ignore list." +msgstr "" + +#: mod.py:1080 +msgid "That channel is not in the ignore list." +msgstr "" + +#: mod.py:1089 +msgid "This guild has been removed from the ignore list." +msgstr "" + +#: mod.py:1091 +msgid "This guild is not in the ignore list." +msgstr "" + +#: mod.py:1103 +msgid "" +"Currently ignoring:\n" +"{} channels\n" +"{} guilds\n" +msgstr "" + +#: mod.py:1131 +msgid "**Past 20 names**:" +msgstr "" + +#: mod.py:1138 +msgid "**Past 20 nicknames**:" +msgstr "" + +#: mod.py:1144 +msgid "That user doesn't have any recorded name or nickname change." +msgstr "" + diff --git a/redbot/cogs/mod/mod.py b/redbot/cogs/mod/mod.py index 49d2f962b..23790748c 100644 --- a/redbot/cogs/mod/mod.py +++ b/redbot/cogs/mod/mod.py @@ -1,6 +1,6 @@ import asyncio -from datetime import datetime -from collections import deque, defaultdict +from datetime import datetime, timedelta +from collections import deque, defaultdict, namedtuple import discord from discord.ext import commands @@ -27,7 +27,8 @@ class Mod: "ignored": False, "respect_hierarchy": True, "delete_delay": -1, - "reinvite_on_unban": False + "reinvite_on_unban": False, + "current_tempbans": [] } default_channel_settings = { @@ -56,10 +57,14 @@ class Mod: self.unban_queue = [] self.cache = defaultdict(lambda: deque(maxlen=3)) - self.bot.loop.create_task(self._casetype_registration()) - + self.registration_task = self.bot.loop.create_task(self._casetype_registration()) + self.tban_expiry_task = self.bot.loop.create_task(self.check_tempban_expirations()) self.last_case = defaultdict(dict) + def __unload(self): + self.registration_task.cancel() + self.tban_expiry_task.cancel() + async def _casetype_registration(self): casetypes_to_register = [ { @@ -83,6 +88,13 @@ class Mod: "case_str": "Hackban", "audit_type": "ban" }, + { + "name": "tempban", + "default_setting": True, + "image": "\N{ALARM CLOCK}\N{HAMMER}", + "case_str": "Tempban", + "audit_type": "ban" + }, { "name": "softban", "default_setting": True, @@ -432,6 +444,54 @@ class Mod: except RuntimeError as e: await ctx.send(e) + @commands.command() + @commands.guild_only() + @checks.admin_or_permissions(ban_members=True) + async def tempban(self, ctx: RedContext, user: discord.Member, days: int=1, *, reason: str=None): + """Tempbans the user for the specified number of days""" + guild = ctx.guild + author = ctx.author + days_delta = timedelta(days=int(days)) + unban_time = datetime.utcnow() + days_delta + channel = ctx.channel + can_ban = channel.permissions_for(guild.me).ban_members + + invite = await self.get_invite_for_reinvite(ctx, int(days_delta.total_seconds() + 86400)) + if invite is None: + invite = "" + + if can_ban: + queue_entry = (guild.id, user.id) + await self.settings.member(user).banned_until.set(unban_time.timestamp()) + cur_tbans = await self.settings.guild(guild).current_tempbans() + cur_tbans.append(user.id) + await self.settings.guild(guild).current_tempbans.set(cur_tbans) + + try: # We don't want blocked DMs preventing us from banning + msg = await user.send( + _("You have been temporarily banned from {} until {}. " + "Here is an invite for when your ban expires: {}").format( + guild.name, unban_time.strftime("%m-%d-%Y %H:%M:%S"), invite)) + except discord.HTTPException: + msg = None + self.ban_queue.append(queue_entry) + try: + await guild.ban(user) + except discord.Forbidden: + await ctx.send(_("I can't do that for some reason.")) + except discord.HTTPException: + await ctx.send(_("Something went wrong while banning")) + else: + await ctx.send(_("Done. Enough chaos for now")) + + try: + await modlog.create_case( + guild, ctx.message.created_at, "tempban", + user, author, reason, unban_time + ) + except RuntimeError as e: + await ctx.send(e) + @commands.command() @commands.guild_only() @checks.admin_or_permissions(ban_members=True) @@ -571,7 +631,7 @@ class Mod: .format(invite.url)) @staticmethod - async def get_invite_for_reinvite(ctx: RedContext): + async def get_invite_for_reinvite(ctx: RedContext, max_age: int=86400): """Handles the reinvite logic for getting an invite to send the newly unbanned user :returns: :class:`Invite`""" @@ -597,8 +657,8 @@ class Mod: if channel is None: return try: - # Create invite that expires after 1 day - return await channel.create_invite(max_age=86400) + # Create invite that expires after max_age + return await channel.create_invite(max_age=max_age) except discord.HTTPException: return @@ -1085,6 +1145,31 @@ class Mod: await ctx.send(_("That user doesn't have any recorded name or " "nickname change.")) + async def check_tempban_expirations(self): + member = namedtuple("Member", "id guild") + while self == self.bot.get_cog("Mod"): + for guild in self.bot.guilds: + guild_tempbans = await self.settings.guild(guild).current_tempbans() + for uid in guild_tempbans: + unban_time = datetime.utcfromtimestamp( + await self.settings.member( + member(uid, guild) + ).banned_until() + ) + now = datetime.utcnow() + if now > unban_time: # Time to unban the user + user = await self.bot.get_user_info(uid) + queue_entry = (guild.id, user.id) + self.unban_queue.append(queue_entry) + try: + await guild.unban(user, reason="Tempban finished") + except discord.Forbidden: + self.unban_queue.remove(queue_entry) + log.info("Failed to unban member due to permissions") + except discord.HTTPException: + self.unban_queue.remove(queue_entry) + await asyncio.sleep(60) + async def check_duplicates(self, message): guild = message.guild author = message.author