mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-06 11:18:54 -05:00
[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:
parent
de09a8b7ca
commit
183572f312
@ -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
|
||||
|
||||
|
||||
@ -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 <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\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 ""
|
||||
|
||||
|
||||
@ -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 <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\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 ""
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user