[V3 Mod&Filter] add tempbans and filtering names/nicknames (#1123)

* [V3 Mod] add tempban command

* [V3 Filter] add name filtering

* [V3 Mod] Modify invite finding to have a max_age param

* [V3 Mod and Filter] regen messages.pot

* [V3 Mod] fill in formatting on tban invite

* [V3 Filter] add on_member_join + refactor logic on_member_update
This commit is contained in:
palmtree5 2017-12-03 17:27:48 -09:00 committed by GitHub
parent de09a8b7ca
commit 183572f312
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 487 additions and 14 deletions

View File

@ -19,7 +19,9 @@ class Filter:
default_guild_settings = { default_guild_settings = {
"filter": [], "filter": [],
"filterban_count": 0, "filterban_count": 0,
"filterban_time": 0 "filterban_time": 0,
"filter_names": False,
"filter_default_name": "John Doe"
} }
default_member_settings = { default_member_settings = {
"filter_count": 0, "filter_count": 0,
@ -27,7 +29,10 @@ class Filter:
} }
self.settings.register_guild(**default_guild_settings) self.settings.register_guild(**default_guild_settings)
self.settings.register_member(**default_member_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): async def register_filterban(self):
try: try:
@ -123,6 +128,37 @@ class Filter:
else: else:
await ctx.send(_("Those words weren't in the filter.")) 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") @_filter.command(name="ban")
async def filter_ban( async def filter_ban(
self, ctx: commands.Context, count: int, timeframe: int): self, ctx: commands.Context, count: int, timeframe: int):
@ -238,3 +274,54 @@ class Filter:
return return
await self.check_filter(message) 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

View File

@ -5,13 +5,61 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\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" "Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\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 ""

View File

@ -5,13 +5,266 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\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" "Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\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 ""

View File

@ -1,6 +1,6 @@
import asyncio import asyncio
from datetime import datetime from datetime import datetime, timedelta
from collections import deque, defaultdict from collections import deque, defaultdict, namedtuple
import discord import discord
from discord.ext import commands from discord.ext import commands
@ -27,7 +27,8 @@ class Mod:
"ignored": False, "ignored": False,
"respect_hierarchy": True, "respect_hierarchy": True,
"delete_delay": -1, "delete_delay": -1,
"reinvite_on_unban": False "reinvite_on_unban": False,
"current_tempbans": []
} }
default_channel_settings = { default_channel_settings = {
@ -56,10 +57,14 @@ class Mod:
self.unban_queue = [] self.unban_queue = []
self.cache = defaultdict(lambda: deque(maxlen=3)) 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) self.last_case = defaultdict(dict)
def __unload(self):
self.registration_task.cancel()
self.tban_expiry_task.cancel()
async def _casetype_registration(self): async def _casetype_registration(self):
casetypes_to_register = [ casetypes_to_register = [
{ {
@ -83,6 +88,13 @@ class Mod:
"case_str": "Hackban", "case_str": "Hackban",
"audit_type": "ban" "audit_type": "ban"
}, },
{
"name": "tempban",
"default_setting": True,
"image": "\N{ALARM CLOCK}\N{HAMMER}",
"case_str": "Tempban",
"audit_type": "ban"
},
{ {
"name": "softban", "name": "softban",
"default_setting": True, "default_setting": True,
@ -432,6 +444,54 @@ class Mod:
except RuntimeError as e: except RuntimeError as e:
await ctx.send(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.command()
@commands.guild_only() @commands.guild_only()
@checks.admin_or_permissions(ban_members=True) @checks.admin_or_permissions(ban_members=True)
@ -571,7 +631,7 @@ class Mod:
.format(invite.url)) .format(invite.url))
@staticmethod @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 """Handles the reinvite logic for getting an invite
to send the newly unbanned user to send the newly unbanned user
:returns: :class:`Invite`""" :returns: :class:`Invite`"""
@ -597,8 +657,8 @@ class Mod:
if channel is None: if channel is None:
return return
try: try:
# Create invite that expires after 1 day # Create invite that expires after max_age
return await channel.create_invite(max_age=86400) return await channel.create_invite(max_age=max_age)
except discord.HTTPException: except discord.HTTPException:
return return
@ -1085,6 +1145,31 @@ class Mod:
await ctx.send(_("That user doesn't have any recorded name or " await ctx.send(_("That user doesn't have any recorded name or "
"nickname change.")) "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): async def check_duplicates(self, message):
guild = message.guild guild = message.guild
author = message.author author = message.author