mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-06 11:18:54 -05:00
[V3 Mod/ModLog] Fix duplicate cases and allow case creation without audit log perms (#1102)
* Use ban/unban queue * Refactor unban's cmd help * Better support for no audit log perms
This commit is contained in:
parent
5cef3e13e1
commit
7322f0c676
@ -1,4 +1,5 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
from datetime import datetime
|
||||||
from collections import deque, defaultdict
|
from collections import deque, defaultdict
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
@ -51,8 +52,8 @@ class Mod:
|
|||||||
self.settings.register_channel(**self.default_channel_settings)
|
self.settings.register_channel(**self.default_channel_settings)
|
||||||
self.settings.register_member(**self.default_member_settings)
|
self.settings.register_member(**self.default_member_settings)
|
||||||
self.settings.register_user(**self.default_user_settings)
|
self.settings.register_user(**self.default_user_settings)
|
||||||
self.current_softban = {}
|
self.ban_queue = []
|
||||||
self.ban_type = None
|
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.bot.loop.create_task(self._casetype_registration())
|
||||||
@ -355,14 +356,17 @@ class Mod:
|
|||||||
if days < 0 or days > 7:
|
if days < 0 or days > 7:
|
||||||
await ctx.send(_("Invalid days. Must be between 0 and 7."))
|
await ctx.send(_("Invalid days. Must be between 0 and 7."))
|
||||||
return
|
return
|
||||||
|
queue_entry = (guild.id, user.id)
|
||||||
|
self.ban_queue.append(queue_entry)
|
||||||
try:
|
try:
|
||||||
await guild.ban(user, reason=audit_reason, delete_message_days=days)
|
await guild.ban(user, reason=audit_reason, delete_message_days=days)
|
||||||
log.info("{}({}) banned {}({}), deleting {} days worth of messages".format(
|
log.info("{}({}) banned {}({}), deleting {} days worth of messages".format(
|
||||||
author.name, author.id, user.name, user.id, str(days)))
|
author.name, author.id, user.name, user.id, str(days)))
|
||||||
except discord.Forbidden:
|
except discord.Forbidden:
|
||||||
|
self.ban_queue.remove(queue_entry)
|
||||||
await ctx.send(_("I'm not allowed to do that."))
|
await ctx.send(_("I'm not allowed to do that."))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
self.ban_queue.remove(queue_entry)
|
||||||
print(e)
|
print(e)
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("Done. It was about time."))
|
await ctx.send(_("Done. It was about time."))
|
||||||
@ -402,15 +406,18 @@ class Mod:
|
|||||||
user = discord.Object(id=user_id) # User not in the guild, but
|
user = discord.Object(id=user_id) # User not in the guild, but
|
||||||
|
|
||||||
audit_reason = get_audit_reason(author, reason)
|
audit_reason = get_audit_reason(author, reason)
|
||||||
|
queue_entry = (guild.id, user_id)
|
||||||
|
self.ban_queue.append(queue_entry)
|
||||||
try:
|
try:
|
||||||
await guild.ban(user, reason=audit_reason)
|
await guild.ban(user, reason=audit_reason)
|
||||||
log.info("{}({}) hackbanned {}"
|
log.info("{}({}) hackbanned {}"
|
||||||
"".format(author.name, author.id, user_id))
|
"".format(author.name, author.id, user_id))
|
||||||
except discord.NotFound:
|
except discord.NotFound:
|
||||||
|
self.ban_queue.remove(queue_entry)
|
||||||
await ctx.send(_("User not found. Have you provided the "
|
await ctx.send(_("User not found. Have you provided the "
|
||||||
"correct user ID?"))
|
"correct user ID?"))
|
||||||
except discord.Forbidden:
|
except discord.Forbidden:
|
||||||
|
self.ban_queue.remove(queue_entry)
|
||||||
await ctx.send(_("I lack the permissions to do this."))
|
await ctx.send(_("I lack the permissions to do this."))
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("Done. The user will not be able to join this "
|
await ctx.send(_("Done. The user will not be able to join this "
|
||||||
@ -452,7 +459,7 @@ class Mod:
|
|||||||
invite = ""
|
invite = ""
|
||||||
|
|
||||||
if can_ban:
|
if can_ban:
|
||||||
self.current_softban[str(guild.id)] = user
|
queue_entry = (guild.id, user.id)
|
||||||
try: # We don't want blocked DMs preventing us from banning
|
try: # We don't want blocked DMs preventing us from banning
|
||||||
msg = await user.send(
|
msg = await user.send(
|
||||||
_("You have been banned and "
|
_("You have been banned and "
|
||||||
@ -460,17 +467,26 @@ class Mod:
|
|||||||
"You can now join the guild again. {}").format(invite))
|
"You can now join the guild again. {}").format(invite))
|
||||||
except discord.HTTPException:
|
except discord.HTTPException:
|
||||||
msg = None
|
msg = None
|
||||||
|
self.ban_queue.append(queue_entry)
|
||||||
try:
|
try:
|
||||||
await guild.ban(
|
await guild.ban(
|
||||||
user, reason=audit_reason, delete_message_days=1)
|
user, reason=audit_reason, delete_message_days=1)
|
||||||
await guild.unban(user)
|
|
||||||
except discord.errors.Forbidden:
|
except discord.errors.Forbidden:
|
||||||
|
self.ban_queue.remove(queue_entry)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("My role is not high enough to softban that user."))
|
_("My role is not high enough to softban that user."))
|
||||||
if msg is not None:
|
if msg is not None:
|
||||||
await msg.delete()
|
await msg.delete()
|
||||||
return
|
return
|
||||||
except discord.HTTPException as e:
|
except discord.HTTPException as e:
|
||||||
|
self.ban_queue.remove(queue_entry)
|
||||||
|
print(e)
|
||||||
|
return
|
||||||
|
self.unban_queue.append(queue_entry)
|
||||||
|
try:
|
||||||
|
await guild.unban(user)
|
||||||
|
except discord.HTTPException as e:
|
||||||
|
self.unban_queue.remove(queue_entry)
|
||||||
print(e)
|
print(e)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
@ -490,9 +506,6 @@ class Mod:
|
|||||||
channel=None)
|
channel=None)
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
await ctx.send(e)
|
await ctx.send(e)
|
||||||
finally:
|
|
||||||
await asyncio.sleep(5)
|
|
||||||
self.current_softban = None
|
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("I'm not allowed to do that."))
|
await ctx.send(_("I'm not allowed to do that."))
|
||||||
|
|
||||||
@ -501,11 +514,12 @@ class Mod:
|
|||||||
@checks.admin_or_permissions(ban_members=True)
|
@checks.admin_or_permissions(ban_members=True)
|
||||||
@commands.bot_has_permissions(ban_members=True)
|
@commands.bot_has_permissions(ban_members=True)
|
||||||
async def unban(self, ctx: RedContext, user_id: int, *, reason: str = None):
|
async def unban(self, ctx: RedContext, user_id: int, *, reason: str = None):
|
||||||
"""Unbans the target user. Requires specifying the target user's ID
|
"""Unbans the target user.
|
||||||
(which can be found in the mod log channel (if logging was enabled for
|
|
||||||
the casetype associated with the command used to ban the user) or (if
|
Requires specifying the target user's ID. To find this, you may either:
|
||||||
developer mode is enabled) by looking in Bans in guild settings,
|
1. Copy it from the mod log case (if one was created), or
|
||||||
finding the user, right-clicking, and selecting 'Copy ID'"""
|
2. enable developer mode, go to Bans in this server's settings, right-
|
||||||
|
click the user and select 'Copy ID'."""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
user = self.bot.get_user_info(user_id)
|
user = self.bot.get_user_info(user_id)
|
||||||
@ -518,10 +532,12 @@ class Mod:
|
|||||||
if user not in bans:
|
if user not in bans:
|
||||||
await ctx.send(_("It seems that user isn't banned!"))
|
await ctx.send(_("It seems that user isn't banned!"))
|
||||||
return
|
return
|
||||||
|
queue_entry = (guild.id, user.id)
|
||||||
|
self.unban_queue.append(queue_entry)
|
||||||
try:
|
try:
|
||||||
await guild.unban(user, reason=reason)
|
await guild.unban(user, reason=reason)
|
||||||
except discord.HTTPException:
|
except discord.HTTPException:
|
||||||
|
self.unban_queue.remove(queue_entry)
|
||||||
await ctx.send(_("Something went wrong while attempting to unban that user"))
|
await ctx.send(_("Something went wrong while attempting to unban that user"))
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
@ -1149,130 +1165,100 @@ class Mod:
|
|||||||
deleted = await self.check_mention_spam(message)
|
deleted = await self.check_mention_spam(message)
|
||||||
|
|
||||||
async def on_member_ban(self, guild: discord.Guild, member: discord.Member):
|
async def on_member_ban(self, guild: discord.Guild, member: discord.Member):
|
||||||
if str(guild.id) in self.current_softban and \
|
if (guild.id, member.id) in self.ban_queue:
|
||||||
self.current_softban[str(guild.id)] == member:
|
self.ban_queue.remove((guild.id, member.id))
|
||||||
return # softban in progress, so a case will be created
|
return
|
||||||
try:
|
try:
|
||||||
mod_ch = await modlog.get_modlog_channel(guild)
|
await modlog.get_modlog_channel(guild)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
return # No modlog channel so no point in continuing
|
return # No modlog channel so no point in continuing
|
||||||
audit_case = None
|
mod, reason, date = await self.get_audit_entry_info(
|
||||||
permissions = guild.me.guild_permissions
|
guild, discord.AuditLogAction.ban, member)
|
||||||
modlog_cases = await modlog.get_all_cases(guild, self.bot)
|
if date is None:
|
||||||
if permissions.view_audit_log:
|
date = datetime.now()
|
||||||
async for entry in guild.audit_logs(action=discord.AuditLogAction.ban):
|
|
||||||
if entry.target == member:
|
|
||||||
audit_case = entry
|
|
||||||
break
|
|
||||||
|
|
||||||
if audit_case:
|
|
||||||
mod = audit_case.user
|
|
||||||
reason = audit_case.reason
|
|
||||||
for case in sorted(modlog_cases, key=lambda x: x.case_number, reverse=True):
|
|
||||||
if mod == guild.me and case.user == member\
|
|
||||||
and case.action_type in ["ban", "hackban"]:
|
|
||||||
log.info("Case already exists for ban of {}".format(member.name))
|
|
||||||
break
|
|
||||||
else: # no ban, softban, or hackban case with the mod and user combo
|
|
||||||
try:
|
try:
|
||||||
await modlog.create_case(guild, audit_case.created_at, "ban",
|
await modlog.create_case(guild, date,
|
||||||
member, mod, reason if reason else None)
|
"ban", member, mod,
|
||||||
|
reason if reason else None)
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
print(e)
|
print(e)
|
||||||
else:
|
|
||||||
return
|
|
||||||
else: # No permissions to view audit logs, so message the guild owner
|
|
||||||
owner = guild.owner
|
|
||||||
try:
|
|
||||||
await owner.send(
|
|
||||||
_("Hi, I noticed that someone in your server "
|
|
||||||
"(the server named {}) banned {}#{} (user ID {}). "
|
|
||||||
"However, I don't have permissions to view audit logs, "
|
|
||||||
"so I could not determine if a mod log case was created "
|
|
||||||
"for that ban, meaning I could not create a case in "
|
|
||||||
"the mod log. If you want me to be able to add cases "
|
|
||||||
"to the mod log for bans done manually, I need the "
|
|
||||||
"`View Audit Logs` permission.").format(
|
|
||||||
guild.name,
|
|
||||||
member.name,
|
|
||||||
member.discriminator,
|
|
||||||
member.id
|
|
||||||
)
|
|
||||||
)
|
|
||||||
except discord.Forbidden:
|
|
||||||
log.warning(
|
|
||||||
"I attempted to inform a guild owner of a lack of the "
|
|
||||||
"'View Audit Log' permission but I am unable to send "
|
|
||||||
"the guild owner the message!"
|
|
||||||
)
|
|
||||||
except discord.HTTPException:
|
|
||||||
log.warning(
|
|
||||||
"Something else went wrong while attempting to "
|
|
||||||
"message a guild owner."
|
|
||||||
)
|
|
||||||
|
|
||||||
async def on_member_unban(self, guild: discord.Guild, user: discord.User):
|
async def on_member_unban(self, guild: discord.Guild, user: discord.User):
|
||||||
if str(guild.id) in self.current_softban and \
|
if (guild.id, user.id) in self.unban_queue:
|
||||||
self.current_softban[str(guild.id)] == user:
|
self.unban_queue.remove((guild.id, user.id))
|
||||||
return # softban in progress, so a case will be created
|
return
|
||||||
try:
|
try:
|
||||||
mod_ch = await modlog.get_modlog_channel(guild)
|
await modlog.get_modlog_channel(guild)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
return # No modlog channel so no point in continuing
|
return # No modlog channel so no point in continuing
|
||||||
audit_case = None
|
mod, reason, date = await self.get_audit_entry_info(
|
||||||
permissions = guild.me.guild_permissions
|
guild, discord.AuditLogAction.unban, user)
|
||||||
if permissions.view_audit_log:
|
if date is None:
|
||||||
async for entry in guild.audit_logs(action=discord.AuditLogAction.unban):
|
date = datetime.now()
|
||||||
if entry.target == user:
|
|
||||||
audit_case = entry
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
if audit_case:
|
|
||||||
mod = audit_case.user
|
|
||||||
reason = audit_case.reason
|
|
||||||
|
|
||||||
cases = await modlog.get_all_cases(guild, self.bot)
|
|
||||||
for case in sorted(cases, key=lambda x: x.case_number, reverse=True):
|
|
||||||
if mod == guild.me and case.user == user\
|
|
||||||
and case.action_type == "unban":
|
|
||||||
log.info("Case already exists for unban of {}".format(user.name))
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
try:
|
try:
|
||||||
await modlog.create_case(guild, audit_case.created_at, "unban",
|
await modlog.create_case(guild, date, "unban",
|
||||||
user, mod, reason if reason else None)
|
user, mod, reason)
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
print(e)
|
print(e)
|
||||||
else: # No permissions to view audit logs, so message the guild owner
|
|
||||||
owner = guild.owner
|
async def get_audit_entry_info(self,
|
||||||
|
guild: discord.Guild,
|
||||||
|
action: int,
|
||||||
|
target):
|
||||||
|
"""Get info about an audit log entry.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
guild : discord.Guild
|
||||||
|
Same as ``guild`` in `get_audit_log_entry`.
|
||||||
|
action : int
|
||||||
|
Same as ``action`` in `get_audit_log_entry`.
|
||||||
|
target : `discord.User` or `discord.Member`
|
||||||
|
Same as ``target`` in `get_audit_log_entry`.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
tuple
|
||||||
|
A tuple in the form``(mod: discord.Member, reason: str,
|
||||||
|
date_created: datetime.datetime)``. Returns ``(None, None, None)``
|
||||||
|
if the audit log entry could not be found.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
await owner.send(
|
entry = await self.get_audit_log_entry(
|
||||||
_("Hi, I noticed that someone in your server "
|
guild, action=action, target=target)
|
||||||
"(the server named {}) unbanned {}#{} (user ID {}). "
|
|
||||||
"However, I don't have permissions to view audit logs, "
|
|
||||||
"so I could not determine if a mod log case was created "
|
|
||||||
"for that unban, meaning I could not create a case in "
|
|
||||||
"the mod log. If you want me to be able to add cases "
|
|
||||||
"to the mod log for unbans done manually, I need the "
|
|
||||||
"`View Audit Logs` permission.").format(
|
|
||||||
guild.name,
|
|
||||||
user.name,
|
|
||||||
user.discriminator,
|
|
||||||
user.id
|
|
||||||
)
|
|
||||||
)
|
|
||||||
except discord.Forbidden:
|
|
||||||
log.warning(
|
|
||||||
"I attempted to inform a guild owner of a lack of the "
|
|
||||||
"'View Audit Log' permission but I am unable to send "
|
|
||||||
"the guild owner the message!"
|
|
||||||
)
|
|
||||||
except discord.HTTPException:
|
except discord.HTTPException:
|
||||||
log.warning(
|
entry = None
|
||||||
"Something else went wrong while attempting to "
|
if entry is None:
|
||||||
"message a guild owner."
|
return None, None, None
|
||||||
)
|
return entry.user, entry.reason, entry.created_at
|
||||||
|
|
||||||
|
async def get_audit_log_entry(self,
|
||||||
|
guild: discord.Guild,
|
||||||
|
action: int,
|
||||||
|
target):
|
||||||
|
"""Get an audit log entry.
|
||||||
|
|
||||||
|
Any exceptions encountered when looking through the audit log will be
|
||||||
|
propogated out of this function.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
guild : discord.Guild
|
||||||
|
The guild for the audit log.
|
||||||
|
action : int
|
||||||
|
The audit log action (see `discord.AuditLogAction`).
|
||||||
|
target : `discord.Member` or `discord.User`
|
||||||
|
The target of the audit log action.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
discord.AuditLogEntry
|
||||||
|
The audit log entry. Returns ``None`` if not found.
|
||||||
|
|
||||||
|
"""
|
||||||
|
async for entry in guild.audit_logs(action=action):
|
||||||
|
if entry.target == target:
|
||||||
|
return entry
|
||||||
|
|
||||||
async def on_member_update(self, before, after):
|
async def on_member_update(self, before, after):
|
||||||
if before.name != after.name:
|
if before.name != after.name:
|
||||||
|
|||||||
@ -107,7 +107,7 @@ class Case:
|
|||||||
user = "{}#{} ({})\n".format(
|
user = "{}#{} ({})\n".format(
|
||||||
self.user.name, self.user.discriminator, self.user.id)
|
self.user.name, self.user.discriminator, self.user.id)
|
||||||
emb.set_author(name=user, icon_url=self.user.avatar_url)
|
emb.set_author(name=user, icon_url=self.user.avatar_url)
|
||||||
|
if self.moderator is not None:
|
||||||
moderator = "{}#{} ({})\n".format(
|
moderator = "{}#{} ({})\n".format(
|
||||||
self.moderator.name,
|
self.moderator.name,
|
||||||
self.moderator.discriminator,
|
self.moderator.discriminator,
|
||||||
@ -153,13 +153,17 @@ class Case:
|
|||||||
The case in the form of a dict
|
The case in the form of a dict
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if self.moderator is not None:
|
||||||
|
mod = self.moderator.id
|
||||||
|
else:
|
||||||
|
mod = None
|
||||||
data = {
|
data = {
|
||||||
"case_number": self.case_number,
|
"case_number": self.case_number,
|
||||||
"action_type": self.action_type,
|
"action_type": self.action_type,
|
||||||
"guild": self.guild.id,
|
"guild": self.guild.id,
|
||||||
"created_at": self.created_at,
|
"created_at": self.created_at,
|
||||||
"user": self.user.id,
|
"user": self.user.id,
|
||||||
"moderator": self.moderator.id,
|
"moderator": mod,
|
||||||
"reason": self.reason,
|
"reason": self.reason,
|
||||||
"until": self.until,
|
"until": self.until,
|
||||||
"channel": self.channel.id if hasattr(self.channel, "id") else None,
|
"channel": self.channel.id if hasattr(self.channel, "id") else None,
|
||||||
@ -373,7 +377,7 @@ async def get_all_cases(guild: discord.Guild, bot: Red) -> List[Case]:
|
|||||||
|
|
||||||
async def create_case(guild: discord.Guild, created_at: datetime, action_type: str,
|
async def create_case(guild: discord.Guild, created_at: datetime, action_type: str,
|
||||||
user: Union[discord.User, discord.Member],
|
user: Union[discord.User, discord.Member],
|
||||||
moderator: discord.Member, reason: str=None,
|
moderator: discord.Member=None, reason: str=None,
|
||||||
until: datetime=None, channel: discord.TextChannel=None
|
until: datetime=None, channel: discord.TextChannel=None
|
||||||
) -> Union[Case, None]:
|
) -> Union[Case, None]:
|
||||||
"""
|
"""
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user