1032 lines
38 KiB
Python
1032 lines
38 KiB
Python
|
|
from datetime import timedelta
|
|
from math import ceil
|
|
from time import time
|
|
from typing import Tuple
|
|
|
|
from discord import AllowedMentions, File, Interaction, Member, Object, Role, TextChannel, User
|
|
from discord.abc import Messageable
|
|
from discord.errors import Forbidden, HTTPException, NotFound
|
|
from redbot.core import app_commands, commands
|
|
from redbot.core.bot import Red
|
|
from redbot.core.commands.converter import parse_relativedelta, parse_timedelta
|
|
from redbot.core.utils.chat_formatting import bold, error, humanize_timedelta, inline
|
|
|
|
from ..utilities.config import config
|
|
from ..utilities.factory import message_factory, resolve_factory
|
|
from ..utilities.logger import logger
|
|
from ..utilities.utils import get_footer_image, log, send_evidenceformat, timedelta_from_relativedelta
|
|
from .moderation import Moderation
|
|
from .type import Type, type_registry
|
|
|
|
|
|
def get_icon(bot: Red) -> File:
|
|
cog = bot.get_cog("Aurora")
|
|
if cog:
|
|
return get_footer_image(cog)
|
|
raise ValueError("Aurora cog not found. How was this managed?")
|
|
|
|
class Note(Type):
|
|
key="note"
|
|
string="note"
|
|
verb="noted"
|
|
embed_desc="received a "
|
|
|
|
@classmethod
|
|
async def handler(cls, ctx: commands.Context, target: Member | User, silent: bool, reason: str) -> 'Note':
|
|
response = await ctx.send(
|
|
content=f"{target.mention} has {cls.embed_desc}{cls.string}!\n**Reason** - `{reason}`"
|
|
)
|
|
|
|
if silent is False:
|
|
try:
|
|
embed = await message_factory(
|
|
bot=ctx.bot,
|
|
color=await ctx.embed_color,
|
|
guild=ctx.guild,
|
|
moderator=ctx.author,
|
|
reason=reason,
|
|
moderation_type=cls(),
|
|
response=response,
|
|
)
|
|
await target.send(embed=embed, file=get_icon(ctx.bot))
|
|
except HTTPException:
|
|
pass
|
|
|
|
moderation = await Moderation.log(
|
|
bot=ctx.bot,
|
|
guild_id=ctx.guild.id,
|
|
moderator_id=ctx.author.id,
|
|
moderation_type=cls(),
|
|
target_type="user",
|
|
target_id=target.id,
|
|
role_id=None,
|
|
duration=None,
|
|
reason=reason,
|
|
)
|
|
await response.edit(
|
|
content=f"{target.mention} has {cls.embed_desc}{cls.string}! (Case `#{moderation.id:,}`)\n**Reason** - `{reason}`"
|
|
)
|
|
await log(ctx=ctx, moderation_id=moderation.id)
|
|
await send_evidenceformat(ctx=ctx, moderation_id=moderation.id)
|
|
return cls()
|
|
|
|
@classmethod
|
|
async def resolve_handler(cls, moderation: Moderation, reason: str) -> Tuple[bool, str]:
|
|
if await config.guild(moderation.guild).dm_users() is True:
|
|
try:
|
|
target = await moderation.bot.fetch_user(moderation.target_id)
|
|
embed = await resolve_factory(
|
|
moderation=moderation,
|
|
reason=reason
|
|
)
|
|
await target.send(embed=embed, file=get_icon(bot=moderation.bot))
|
|
except (Forbidden, HTTPException, NotFound):
|
|
pass
|
|
return True, ""
|
|
|
|
class Warn(Type):
|
|
key="warn"
|
|
string="warn"
|
|
verb="warned"
|
|
|
|
@classmethod
|
|
async def handler(cls, ctx: commands.Context, target: Member | User, silent: bool, reason: str) -> 'Warn':
|
|
response = await ctx.send(
|
|
content=f"{target.mention} has {cls.embed_desc}{cls.verb}!\n**Reason** - `{reason}`"
|
|
)
|
|
|
|
if silent is False:
|
|
try:
|
|
embed = await message_factory(
|
|
bot=ctx.bot,
|
|
color=await ctx.embed_color,
|
|
guild=ctx.guild,
|
|
moderator=ctx.author,
|
|
reason=reason,
|
|
moderation_type=cls(),
|
|
response=response,
|
|
)
|
|
await target.send(embed=embed, file=get_icon(ctx.bot))
|
|
except HTTPException:
|
|
pass
|
|
|
|
moderation = await Moderation.log(
|
|
bot=ctx.bot,
|
|
guild_id=ctx.guild.id,
|
|
moderator_id=ctx.author.id,
|
|
moderation_type=cls(),
|
|
target_type="user",
|
|
target_id=target.id,
|
|
role_id=None,
|
|
duration=None,
|
|
reason=reason,
|
|
)
|
|
await response.edit(
|
|
content=f"{target.mention} has {cls.embed_desc}{cls.verb}! (Case `#{moderation.id:,}`)\n**Reason** - `{reason}`"
|
|
)
|
|
await log(ctx=ctx, moderation_id=moderation.id)
|
|
await send_evidenceformat(ctx=ctx, moderation_id=moderation.id)
|
|
return cls()
|
|
|
|
@classmethod
|
|
async def resolve_handler(cls, moderation: Moderation, reason: str) -> Tuple[bool, str]:
|
|
if await config.guild(moderation.guild).dm_users() is True:
|
|
try:
|
|
target = await moderation.bot.fetch_user(moderation.target_id)
|
|
embed = await resolve_factory(
|
|
moderation=moderation,
|
|
reason=reason
|
|
)
|
|
await target.send(embed=embed, file=get_icon(bot=moderation.bot))
|
|
except (Forbidden, HTTPException, NotFound):
|
|
pass
|
|
return True, ""
|
|
|
|
class AddRole(Type):
|
|
key="addrole"
|
|
string="addrole"
|
|
verb="added a role to"
|
|
embed_desc="been given the "
|
|
|
|
@classmethod
|
|
async def handler(cls, ctx: commands.Context, target: Member, role: Role, silent: bool, duration: str = None, reason: str = None):
|
|
addrole_whitelist = await config.guild(ctx.guild).addrole_whitelist()
|
|
|
|
if not addrole_whitelist:
|
|
await ctx.send(
|
|
content=error("There are no whitelisted roles set for this server!"),
|
|
ephemeral=True,
|
|
)
|
|
return
|
|
|
|
if duration is not None:
|
|
parsed_time = parse_timedelta(duration)
|
|
if parsed_time is None:
|
|
await ctx.send(
|
|
content=error("Please provide a valid duration!"), ephemeral=True
|
|
)
|
|
return
|
|
else:
|
|
parsed_time = None
|
|
|
|
if role.id not in addrole_whitelist:
|
|
await ctx.send(
|
|
content=error("That role isn't whitelisted!"), ephemeral=True
|
|
)
|
|
return
|
|
|
|
if role.id in [user_role.id for user_role in target.roles]:
|
|
await ctx.send(
|
|
content=error(f"{target.mention} already has this role!"),
|
|
ephemeral=True,
|
|
)
|
|
return
|
|
|
|
response = await ctx.send(
|
|
content=f"{target.mention} has {cls.embed_desc}{role.mention} role{' for ' + humanize_timedelta(timedelta=parsed_time) if parsed_time != 'NULL' else ''}!\n**Reason** - `{reason}`"
|
|
)
|
|
|
|
if silent is False:
|
|
try:
|
|
embed = await message_factory(
|
|
bot=ctx.bot,
|
|
color=await ctx.embed_color(),
|
|
guild=ctx.guild,
|
|
moderator=ctx.author,
|
|
reason=reason,
|
|
moderation_type=cls(),
|
|
response=response,
|
|
duration=parsed_time,
|
|
role=role,
|
|
)
|
|
await target.send(embed=embed, file=get_icon(ctx.bot))
|
|
except HTTPException:
|
|
pass
|
|
|
|
await target.add_roles(
|
|
role,
|
|
reason=f"Role added by {ctx.author.id}{' for ' + humanize_timedelta(timedelta=parsed_time) if parsed_time != 'NULL' else ''} for: {reason}",
|
|
)
|
|
|
|
moderation = await Moderation.log(
|
|
bot=ctx.bot,
|
|
guild_id=ctx.guild.id,
|
|
moderator_id=ctx.author.id,
|
|
moderation_type=cls(),
|
|
target_type="user",
|
|
target_id=target.id,
|
|
role_id=role.id,
|
|
duration=parsed_time,
|
|
reason=reason,
|
|
)
|
|
await response.edit(
|
|
content=f"{target.mention} has {cls.embed_desc}{role.mention} role{' for ' + humanize_timedelta(timedelta=parsed_time) if parsed_time != 'NULL' else ''}! (Case `#{moderation.id:,}`)\n**Reason** - `{reason}`",
|
|
)
|
|
await log(ctx=ctx, moderation_id=moderation.id)
|
|
await send_evidenceformat(ctx=ctx, moderation_id=moderation.id)
|
|
return cls()
|
|
|
|
@classmethod
|
|
async def duration_edit_handler(cls, interaction: Interaction, old_moderation: Moderation, new_moderation: Moderation) -> bool: # pylint: disable=unused-argument
|
|
return True
|
|
|
|
@classmethod
|
|
async def expiry_handler(cls, moderation: Moderation) -> int:
|
|
try:
|
|
target = await moderation.get_target()
|
|
|
|
await target.remove_roles(
|
|
Object(moderation.role_id), reason=f"Automatic role removal from case #{moderation.id}"
|
|
)
|
|
|
|
if await config.guild(moderation.guild).dm_users() is True:
|
|
try:
|
|
embed = await message_factory(
|
|
bot=moderation.bot,
|
|
color=await moderation.bot.get_embed_color(moderation.guild.channels[0]),
|
|
guild=moderation.guild,
|
|
reason=f"Automatic role removal from case #{moderation.id}",
|
|
moderation_type=type_registry["removerole"],
|
|
moderator=None,
|
|
duration=None,
|
|
response=None,
|
|
case=False,
|
|
)
|
|
await target.send(embed=embed, file=get_icon(bot=moderation.bot))
|
|
except HTTPException:
|
|
pass
|
|
logger.trace(
|
|
"Removed role %s from %s (%s)",
|
|
moderation.role_id,
|
|
target.name,
|
|
target.id,
|
|
)
|
|
return 1
|
|
except (
|
|
NotFound,
|
|
Forbidden,
|
|
HTTPException,
|
|
) as e:
|
|
logger.error(
|
|
"Removing the role %s from user %s failed due to: \n%s",
|
|
moderation.role_id,
|
|
moderation.target_id,
|
|
e,
|
|
)
|
|
return 0
|
|
|
|
class RemoveRole(Type):
|
|
key="removerole"
|
|
string="removerole"
|
|
verb="removed a role from"
|
|
embed_desc="had the "
|
|
|
|
@classmethod
|
|
async def handler(cls, ctx: commands.Context, target: Member, role: Role, silent: bool, duration: str = None, reason: str = None):
|
|
addrole_whitelist = await config.guild(ctx.guild).addrole_whitelist()
|
|
|
|
if not addrole_whitelist:
|
|
await ctx.send(
|
|
content=error("There are no whitelisted roles set for this server!"),
|
|
ephemeral=True,
|
|
)
|
|
return
|
|
|
|
if duration is not None:
|
|
parsed_time = parse_timedelta(duration)
|
|
if parsed_time is None:
|
|
await ctx.send(
|
|
content=error("Please provide a valid duration!"), ephemeral=True
|
|
)
|
|
return
|
|
else:
|
|
parsed_time = None
|
|
|
|
if role.id not in addrole_whitelist:
|
|
await ctx.send(
|
|
content=error("That role isn't whitelisted!"), ephemeral=True
|
|
)
|
|
return
|
|
|
|
if role.id not in [user_role.id for user_role in target.roles]:
|
|
await ctx.send(
|
|
content=error(f"{target.mention} does not have this role!"),
|
|
ephemeral=True,
|
|
)
|
|
return
|
|
|
|
response = await ctx.send(
|
|
content=f"{target.mention} has {cls.embed_desc}{role.mention} role removed{' for ' + humanize_timedelta(timedelta=parsed_time) if parsed_time != 'NULL' else ''}!\n**Reason** - `{reason}`"
|
|
)
|
|
|
|
if silent is False:
|
|
try:
|
|
embed = await message_factory(
|
|
bot=ctx.bot,
|
|
color=await ctx.embed_color(),
|
|
guild=ctx.guild,
|
|
moderator=ctx.author,
|
|
reason=reason,
|
|
moderation_type="removerole",
|
|
response=response,
|
|
duration=parsed_time,
|
|
role=role,
|
|
)
|
|
await target.send(embed=embed, file=get_icon(ctx.bot))
|
|
except HTTPException:
|
|
pass
|
|
|
|
await target.remove_roles(
|
|
role,
|
|
reason=f"Role removed by {ctx.author.id}{' for ' + humanize_timedelta(timedelta=parsed_time) if parsed_time != 'NULL' else ''} for: {reason}",
|
|
)
|
|
|
|
moderation = await Moderation.log(
|
|
bot=ctx.bot,
|
|
guild_id=ctx.guild.id,
|
|
moderator_id=ctx.author.id,
|
|
moderation_type=cls(),
|
|
target_type="user",
|
|
target_id=target.id,
|
|
role_id=role.id,
|
|
duration=parsed_time,
|
|
reason=reason,
|
|
)
|
|
await response.edit(
|
|
content=f"{target.mention} has {cls.embed_desc}{role.mention} role removed{' for ' + humanize_timedelta(timedelta=parsed_time) if parsed_time != 'NULL' else ''}! (Case `#{moderation.id:,}`)\n**Reason** - `{reason}`",
|
|
)
|
|
await log(ctx=ctx, moderation_id=moderation.id)
|
|
await send_evidenceformat(ctx=ctx, moderation_id=moderation.id)
|
|
return cls()
|
|
|
|
@classmethod
|
|
async def duration_edit_handler(cls, interaction: Interaction, old_moderation: Moderation, new_moderation: Moderation) -> bool: # pylint: disable=unused-argument
|
|
return True
|
|
|
|
@classmethod
|
|
async def expiry_handler(cls, moderation: Moderation) -> int:
|
|
try:
|
|
target = await moderation.get_target()
|
|
|
|
await target.add_roles(
|
|
Object(moderation.role_id), reason=f"Automatic role addition from case #{moderation.id}"
|
|
)
|
|
if await config.guild(moderation.guild).dm_users() is True:
|
|
try:
|
|
embed = await message_factory(
|
|
bot=moderation.bot,
|
|
color=await moderation.bot.get_embed_color(moderation.guild.channels[0]),
|
|
guild=moderation.guild,
|
|
reason=f"Automatic role addition from case #{moderation.id}",
|
|
moderation_type=type_registry["addrole"],
|
|
moderator=None,
|
|
duration=None,
|
|
response=None,
|
|
case=False,
|
|
)
|
|
await target.send(embed=embed, file=get_icon(bot=moderation.bot))
|
|
except HTTPException:
|
|
pass
|
|
logger.trace(
|
|
"Added role %s to %s (%s)",
|
|
moderation.role_id,
|
|
target.name,
|
|
target.id,
|
|
)
|
|
return 1
|
|
except (
|
|
NotFound,
|
|
Forbidden,
|
|
HTTPException,
|
|
) as e:
|
|
logger.error(
|
|
"Adding the role %s to user %s failed due to: \n%s",
|
|
moderation.role_id,
|
|
moderation.target_id,
|
|
e,
|
|
)
|
|
return 0
|
|
|
|
@classmethod
|
|
async def resolve_handler(cls, moderation: Moderation, reason: str) -> Tuple[bool, str]:
|
|
try:
|
|
target = await moderation.get_target()
|
|
await target.add_roles(
|
|
Object(moderation.role_id), reason=reason
|
|
)
|
|
if await config.guild(moderation.guild).dm_users() is True:
|
|
try:
|
|
embed = await resolve_factory(
|
|
moderation=moderation,
|
|
reason=reason
|
|
)
|
|
await target.send(embed=embed, file=get_icon(bot=moderation.bot))
|
|
except HTTPException:
|
|
pass
|
|
logger.trace(
|
|
"Added role %s to %s (%s)",
|
|
moderation.role_id,
|
|
target.name,
|
|
target.id,
|
|
)
|
|
return True, ""
|
|
except (NotFound, Forbidden, HTTPException) as e:
|
|
logger.error(
|
|
"Failed to add role %s to user %s (%s)\n%s",
|
|
moderation.role_id,
|
|
target.name,
|
|
target.id,
|
|
e,
|
|
)
|
|
return False, "Failed to add role to user."
|
|
|
|
class Mute(Type):
|
|
key="mute"
|
|
string="mute"
|
|
verb="muted"
|
|
|
|
@classmethod
|
|
async def handler(cls, ctx: commands.Context, target: Member, silent: bool, duration: str, reason: str = None):
|
|
if target.is_timed_out() is True:
|
|
await ctx.send(
|
|
error(f"{target.mention} is already muted!"),
|
|
allowed_mentions=AllowedMentions(users=False),
|
|
ephemeral=True,
|
|
)
|
|
return
|
|
|
|
try:
|
|
parsed_time = parse_timedelta(duration, maximum=timedelta(days=28))
|
|
if parsed_time is None:
|
|
await ctx.send(
|
|
error("Please provide a valid duration!"), ephemeral=True
|
|
)
|
|
return
|
|
except commands.BadArgument:
|
|
await ctx.send(
|
|
error("Please provide a duration that is less than 28 days."), ephemeral=True
|
|
)
|
|
return
|
|
|
|
await target.timeout(
|
|
parsed_time, reason=f"Muted by {ctx.author.id} for: {reason}"
|
|
)
|
|
|
|
response = await ctx.send(
|
|
content=f"{target.mention} has been muted for {humanize_timedelta(timedelta=parsed_time)}!\n**Reason** - `{reason}`"
|
|
)
|
|
|
|
if silent is False:
|
|
try:
|
|
embed = await message_factory(
|
|
bot=ctx.bot,
|
|
color=await ctx.embed_color(),
|
|
guild=ctx.guild,
|
|
moderator=ctx.author,
|
|
reason=reason,
|
|
moderation_type=cls(),
|
|
response=response,
|
|
duration=parsed_time,
|
|
)
|
|
await target.send(embed=embed, file=get_icon(ctx.bot))
|
|
except HTTPException:
|
|
pass
|
|
|
|
moderation = await Moderation.log(
|
|
bot=ctx.bot,
|
|
guild_id=ctx.guild.id,
|
|
moderator_id=ctx.author.id,
|
|
moderation_type=cls(),
|
|
target_type="user",
|
|
target_id=target.id,
|
|
role_id=None,
|
|
duration=parsed_time,
|
|
reason=reason,
|
|
)
|
|
await response.edit(
|
|
content=f"{target.mention} has been muted for {humanize_timedelta(timedelta=parsed_time)}! (Case `#{moderation.id:,}`)\n**Reason** - `{reason}`"
|
|
)
|
|
await log(ctx=ctx, moderation_id=moderation.id)
|
|
await send_evidenceformat(ctx=ctx, moderation_id=moderation.id)
|
|
return cls()
|
|
|
|
@classmethod
|
|
async def resolve_handler(cls, moderation: Moderation, reason: str) -> Tuple[bool, str]:
|
|
try:
|
|
target = await moderation.guild.fetch_member(moderation.target_id)
|
|
except (Forbidden, HTTPException, NotFound):
|
|
return False, "User is not in the server, so I cannot unmute them."
|
|
if target.is_timed_out() is False:
|
|
return True, ""
|
|
await target.timeout(None, reason=reason)
|
|
|
|
if await config.guild(moderation.guild).dm_users() is True:
|
|
try:
|
|
embed = await resolve_factory(
|
|
moderation=moderation,
|
|
reason=reason
|
|
)
|
|
await target.send(embed=embed, file=get_icon(bot=moderation.bot))
|
|
except (Forbidden, HTTPException):
|
|
pass
|
|
return True, ""
|
|
|
|
@classmethod
|
|
async def duration_edit_handler(cls, interaction: Interaction, old_moderation: Moderation, new_moderation: Moderation) -> bool: # pylint: disable=unused-argument
|
|
if (
|
|
time() - new_moderation.unix_timestamp
|
|
) + new_moderation.duration.total_seconds() > 2419200:
|
|
await interaction.response.send_message(
|
|
content=error(
|
|
"Please provide a duration that is less than 28 days from the initial moderation."
|
|
),
|
|
ephemeral=True
|
|
)
|
|
return False
|
|
|
|
try:
|
|
member = await interaction.guild.fetch_member(
|
|
new_moderation.target_id
|
|
)
|
|
|
|
await member.timeout(
|
|
new_moderation.duration,
|
|
reason=f"Case #{new_moderation.id:,} edited by {interaction.user.id}",
|
|
)
|
|
except NotFound:
|
|
pass
|
|
return True
|
|
|
|
class Unmute(Type):
|
|
key="unmute"
|
|
string="unmute"
|
|
verb="unmuted"
|
|
|
|
@classmethod
|
|
async def handler(cls, ctx: commands.Context, target: Member, silent: bool, reason: str = None):
|
|
if target.is_timed_out() is False:
|
|
await ctx.send(
|
|
content=error(f"{target.mention} is not muted!"),
|
|
allowed_mentions=AllowedMentions(users=False),
|
|
ephemeral=True,
|
|
)
|
|
return
|
|
|
|
if reason:
|
|
await target.timeout(
|
|
None, reason=f"{cls.verb.title()} by {ctx.author.id} for: {reason}"
|
|
)
|
|
else:
|
|
await target.timeout(None, reason=f"{cls.verb.title()} by {ctx.author.id}")
|
|
reason = "No reason given."
|
|
|
|
response_message = await ctx.send(
|
|
content=f"{target.mention} has been {cls.verb}!\n**Reason** - `{reason}`"
|
|
)
|
|
|
|
if silent is False:
|
|
try:
|
|
embed = await message_factory(
|
|
bot=ctx.bot,
|
|
color=await ctx.embed_color(),
|
|
guild=ctx.guild,
|
|
moderator=ctx.author,
|
|
reason=reason,
|
|
moderation_type="unmuted",
|
|
response=response_message,
|
|
)
|
|
await target.send(embed=embed, file=get_icon(ctx.bot))
|
|
except HTTPException:
|
|
pass
|
|
|
|
moderation = await Moderation.log(
|
|
bot=ctx.bot,
|
|
guild_id=ctx.guild.id,
|
|
moderator_id=ctx.author.id,
|
|
moderation_type=cls(),
|
|
target_type="user",
|
|
target_id=target.id,
|
|
role_id=None,
|
|
duration=None,
|
|
reason=reason,
|
|
)
|
|
await response_message.edit(
|
|
content=f"{target.mention} has been {cls.verb}! (Case `#{moderation.id:,}`)\n**Reason** - `{reason}`"
|
|
)
|
|
await log(ctx=ctx, moderation_id=moderation.id)
|
|
await send_evidenceformat(ctx=ctx, moderation_id=moderation.id)
|
|
|
|
class Kick(Type):
|
|
key="kick"
|
|
string="kick"
|
|
verb="kicked"
|
|
|
|
@classmethod
|
|
async def handler(cls, ctx: commands.Context, target: Member | User, silent: bool, reason: str = None) -> 'Kick':
|
|
"""Kick a user."""
|
|
bot = ctx.bot
|
|
response_message = await ctx.send(f"{target.mention} has been {cls.verb}!\n{bold('Reason:')} {inline(reason)}")
|
|
|
|
if silent is False:
|
|
try:
|
|
embed = await message_factory(
|
|
bot=bot,
|
|
color=await ctx.embed_color(),
|
|
guild=ctx.guild,
|
|
reason=reason,
|
|
moderation_type=cls(),
|
|
moderator=ctx.author,
|
|
duration=None,
|
|
response=response_message
|
|
)
|
|
await target.send(embed=embed, file=get_icon(bot))
|
|
except HTTPException:
|
|
pass
|
|
|
|
await target.kick(reason=f"{str.title(cls.verb)} by {ctx.author.id} for: {reason}")
|
|
moderation = await Moderation.log(
|
|
bot=bot,
|
|
guild_id=ctx.guild.id,
|
|
moderator_id=ctx.author.id,
|
|
moderation_type=cls(),
|
|
target_type='user',
|
|
target_id=target.id,
|
|
role_id=None,
|
|
duration=None,
|
|
reason=reason
|
|
)
|
|
await response_message.edit(content=f"{target.mention} has been {cls.verb}! (Case {inline(f'#{moderation.id}')})\n{bold('Reason:')} {inline(reason)}")
|
|
await log(ctx=ctx, moderation_id=moderation.id)
|
|
await send_evidenceformat(ctx=ctx, moderation_id=moderation.id)
|
|
return cls
|
|
|
|
@classmethod
|
|
async def resolve_handler(cls, moderation: Moderation, reason: str) -> Tuple[bool, str]:
|
|
if await config.guild(moderation.guild).dm_users() is True:
|
|
try:
|
|
target = await moderation.bot.fetch_user(moderation.target_id)
|
|
embed = await resolve_factory(
|
|
moderation=moderation,
|
|
reason=reason
|
|
)
|
|
await target.send(embed=embed, file=get_icon(bot=moderation.bot))
|
|
except (Forbidden, HTTPException, NotFound):
|
|
pass
|
|
return True, ""
|
|
|
|
class Ban(Type):
|
|
key="ban"
|
|
string="ban"
|
|
verb="banned"
|
|
|
|
@classmethod
|
|
async def handler(cls, ctx: commands.Context, target: Member | User, silent: bool, reason: str = None, delete_messages: app_commands.Choice | None = None) -> 'Ban':
|
|
"""Ban a user."""
|
|
bot = ctx.bot
|
|
try:
|
|
await ctx.guild.fetch_ban(target)
|
|
await ctx.send(content=error(f"{target.mention} is already {cls.verb}!"), ephemeral=True)
|
|
except NotFound:
|
|
pass
|
|
|
|
if delete_messages is None:
|
|
delete_messages_seconds = 0
|
|
else:
|
|
delete_messages_seconds = delete_messages.value
|
|
|
|
response_message = await ctx.send(f"{target.mention} has been {cls.verb}!\n{bold('Reason:')} {inline(reason)}")
|
|
|
|
if silent is False:
|
|
try:
|
|
embed = await message_factory(
|
|
bot=bot,
|
|
color=await ctx.embed_color(),
|
|
guild=ctx.guild,
|
|
reason=reason,
|
|
moderation_type=cls(),
|
|
moderator=ctx.author,
|
|
duration=None,
|
|
response=response_message
|
|
)
|
|
await target.send(embed=embed, file=get_icon(bot))
|
|
except HTTPException:
|
|
pass
|
|
|
|
await ctx.guild.ban(target, reason=f"{str.title(cls.verb)} by {ctx.author.id} for: {reason}", delete_message_seconds=delete_messages_seconds)
|
|
moderation = await Moderation.log(
|
|
bot=bot,
|
|
guild_id=ctx.guild.id,
|
|
moderator_id=ctx.author.id,
|
|
moderation_type=cls(),
|
|
target_type='user',
|
|
target_id=target.id,
|
|
role_id=None,
|
|
duration=None,
|
|
reason=reason
|
|
)
|
|
await response_message.edit(content=f"{target.mention} has been {cls.verb}! (Case {inline(f'#{moderation.id}')})\n{bold('Reason:')} {inline(reason)}")
|
|
await log(ctx=ctx, moderation_id=moderation.id)
|
|
await send_evidenceformat(ctx=ctx, moderation_id=moderation.id)
|
|
return cls
|
|
|
|
@classmethod
|
|
async def resolve_handler(cls, moderation: Moderation, reason: str) -> Tuple[bool, str]:
|
|
try:
|
|
target = await moderation.bot.fetch_user(moderation.target_id)
|
|
except (HTTPException, NotFound):
|
|
return False, "Fetching the target failed, so I cannot unban them."
|
|
|
|
try:
|
|
await moderation.guild.unban(user=target, reason=reason)
|
|
except (NotFound, Forbidden, HTTPException) as e:
|
|
if e == NotFound:
|
|
return True, ""
|
|
return False, "I do not have permission to unban this user."
|
|
|
|
if await config.guild(moderation.guild).dm_users() is True:
|
|
try:
|
|
embed = await resolve_factory(
|
|
moderation=moderation,
|
|
reason=reason
|
|
)
|
|
await target.send(embed=embed, file=get_icon(bot=moderation.bot))
|
|
except (Forbidden, HTTPException):
|
|
pass
|
|
return True, ""
|
|
|
|
class Tempban(Ban):
|
|
key="tempban"
|
|
string="tempban"
|
|
verb="tempbanned"
|
|
|
|
@classmethod
|
|
async def handler(cls, ctx: commands.Context, target: Member | User, silent: bool, duration: str, reason: str = None, delete_messages: app_commands.Choice | None = None) -> 'Ban':
|
|
"""Ban a user."""
|
|
bot = ctx.bot
|
|
try:
|
|
await ctx.guild.fetch_ban(target)
|
|
await ctx.send(content=error(f"{target.mention} is already {Ban.verb}!"), ephemeral=True)
|
|
except NotFound:
|
|
pass
|
|
|
|
if delete_messages is None:
|
|
delete_messages_seconds = 0
|
|
else:
|
|
delete_messages_seconds = delete_messages.value
|
|
|
|
parsed_time = parse_relativedelta(duration)
|
|
if not parsed_time:
|
|
await ctx.send(content=error("Please provide a valid duration!"), ephemeral=True)
|
|
try:
|
|
parsed_time = timedelta_from_relativedelta(parsed_time)
|
|
except ValueError:
|
|
await ctx.send(content=error("Please provide a valid duration!"), ephemeral=True)
|
|
|
|
response_message = await ctx.send(f"{target.mention} has been {cls.verb} for {humanize_timedelta(parsed_time)}!\n{bold('Reason:')} {inline(reason)}")
|
|
|
|
if silent is False:
|
|
try:
|
|
embed = await message_factory(
|
|
bot=bot,
|
|
color=await ctx.embed_color(),
|
|
guild=ctx.guild,
|
|
reason=reason,
|
|
moderation_type=cls(),
|
|
moderator=ctx.author,
|
|
duration=parsed_time,
|
|
response=response_message
|
|
)
|
|
await target.send(embed=embed, file=get_icon(bot))
|
|
except HTTPException:
|
|
pass
|
|
|
|
await ctx.guild.ban(target, reason=f"{str.title(cls.verb)} by {ctx.author.id} for: {reason} (Duration: {parsed_time})", delete_message_seconds=delete_messages_seconds)
|
|
moderation = await Moderation.log(
|
|
bot=bot,
|
|
guild_id=ctx.guild.id,
|
|
moderator_id=ctx.author.id,
|
|
moderation_type=cls(),
|
|
target_type='user',
|
|
target_id=target.id,
|
|
role_id=None,
|
|
duration=parsed_time,
|
|
reason=reason
|
|
)
|
|
await response_message.edit(content=f"{target.mention} has been {cls.verb} for {humanize_timedelta(parsed_time)}! (Case {inline(f'#{moderation.id}')})\n{bold('Reason:')} {inline(reason)}")
|
|
await log(ctx, moderation.id)
|
|
await send_evidenceformat(ctx, moderation.id)
|
|
return cls
|
|
|
|
@classmethod
|
|
async def expiry_handler(cls, moderation: Moderation) -> int:
|
|
reason = f"Automatic {Unban.string} from case #{moderation.id}"
|
|
try:
|
|
target = await moderation.get_target()
|
|
await moderation.guild.unban(user=target, reason=reason)
|
|
|
|
if await config.guild(moderation.guild).dm_users() is True:
|
|
try:
|
|
embed = await message_factory(
|
|
bot=moderation.bot,
|
|
color=await moderation.bot.get_embed_color(moderation.guild.channels[0]),
|
|
guild=moderation.guild,
|
|
reason=reason,
|
|
moderation_type=type_registry["unban"],
|
|
moderator=None,
|
|
duration=None,
|
|
response=None,
|
|
case=False,
|
|
)
|
|
await target.send(embed=embed, file=get_icon(bot=moderation.bot))
|
|
except HTTPException:
|
|
pass
|
|
logger.trace(
|
|
"%s %s (%s) from %s (%s)",
|
|
Unban.verb.title(),
|
|
target.name,
|
|
target.id,
|
|
moderation.guild.name,
|
|
moderation.guild.id,
|
|
)
|
|
return 1
|
|
except (NotFound, Forbidden, HTTPException) as e:
|
|
logger.error(
|
|
"Failed to %s %s (%s) from %s (%s)\n%s",
|
|
Unban.string,
|
|
target.name,
|
|
target.id,
|
|
moderation.guild.name,
|
|
moderation.guild.id,
|
|
e,
|
|
)
|
|
return 0
|
|
|
|
@classmethod
|
|
async def duration_edit_handler(cls, interaction: Interaction, old_moderation: Moderation, new_moderation: Moderation) -> bool: # pylint: disable=unused-argument
|
|
return True
|
|
|
|
class Softban(Type):
|
|
key="softban"
|
|
string="softban"
|
|
verb="softbanned"
|
|
|
|
@classmethod
|
|
async def handler(cls, ctx: commands.Context, target: Member | User, silent: bool, reason: str = None, delete_messages: app_commands.Choice | None = None) -> 'Softban':
|
|
"""Softban a user."""
|
|
bot = ctx.bot
|
|
try:
|
|
await ctx.guild.fetch_ban(target)
|
|
await ctx.send(content=error(f"{target.mention} is already {Ban.verb}!"), ephemeral=True)
|
|
except NotFound:
|
|
pass
|
|
|
|
if delete_messages is None:
|
|
delete_messages_seconds = 0
|
|
else:
|
|
delete_messages_seconds = delete_messages.value
|
|
|
|
response_message = await ctx.send(f"{target.mention} has been {cls.verb}!\n{bold('Reason:')} {inline(reason)}")
|
|
|
|
if silent is False:
|
|
try:
|
|
embed = await message_factory(
|
|
bot=bot,
|
|
color=await ctx.embed_color(),
|
|
guild=ctx.guild,
|
|
reason=reason,
|
|
moderation_type=cls(),
|
|
moderator=ctx.author,
|
|
duration=None,
|
|
response=response_message
|
|
)
|
|
await target.send(embed=embed, file=get_icon(bot))
|
|
except HTTPException:
|
|
pass
|
|
|
|
await ctx.guild.ban(target, reason=f"{str.title(cls.verb)} by {ctx.author.id} for: {reason}", delete_message_seconds=delete_messages_seconds)
|
|
await ctx.guild.unban(target, reason=f"{str.title(cls.verb)} by {ctx.author.id} for: {reason}")
|
|
moderation = await Moderation.log(
|
|
bot=bot,
|
|
guild_id=ctx.guild.id,
|
|
moderator_id=ctx.author.id,
|
|
moderation_type=cls(),
|
|
target_type='user',
|
|
target_id=target.id,
|
|
role_id=None,
|
|
duration=None,
|
|
reason=reason
|
|
)
|
|
await response_message.edit(content=f"{target.mention} has been {cls.verb}! (Case {inline(f'#{moderation.id}')})\n{bold('Reason:')} {inline(reason)}")
|
|
await log(ctx, moderation.id)
|
|
await send_evidenceformat(ctx, moderation.id)
|
|
return cls
|
|
|
|
@classmethod
|
|
async def resolve_handler(cls, moderation: Moderation, reason: str) -> Tuple[bool, str]:
|
|
if await config.guild(moderation.guild).dm_users() is True:
|
|
try:
|
|
target = await moderation.bot.fetch_user(moderation.target_id)
|
|
embed = await resolve_factory(
|
|
moderation=moderation,
|
|
reason=reason
|
|
)
|
|
await target.send(embed=embed, file=get_icon(bot=moderation.bot))
|
|
except (Forbidden, HTTPException, NotFound):
|
|
pass
|
|
return True, ""
|
|
|
|
class Unban(Type):
|
|
key="unban"
|
|
string="unban"
|
|
verb="unbanned"
|
|
|
|
@classmethod
|
|
async def handler(cls, ctx: commands.Context, target: Member | User, silent: bool, reason: str = None) -> 'Unban':
|
|
"""Unban a user."""
|
|
bot = ctx.bot
|
|
try:
|
|
await ctx.guild.fetch_ban(target)
|
|
except NotFound:
|
|
await ctx.send(content=error(f"{target.mention} is not {Ban.verb}!"), ephemeral=True)
|
|
return
|
|
|
|
response_message = await ctx.send(f"{target.mention} has been {cls.verb}!\n{bold('Reason:')} {inline(reason)}")
|
|
|
|
if silent is False:
|
|
try:
|
|
embed = await message_factory(
|
|
bot=bot,
|
|
color=await ctx.embed_color(),
|
|
guild=ctx.guild,
|
|
reason=reason,
|
|
moderation_type=cls(),
|
|
moderator=ctx.author,
|
|
duration=None,
|
|
response=response_message
|
|
)
|
|
await target.send(embed=embed, file=get_icon(bot))
|
|
except HTTPException:
|
|
pass
|
|
|
|
await ctx.guild.unban(target, reason=f"{str.title(cls.verb)} by {ctx.author.id} for: {reason}")
|
|
moderation = await Moderation.log(
|
|
bot=bot,
|
|
guild_id=ctx.guild.id,
|
|
moderator_id=ctx.author.id,
|
|
moderation_type=cls(),
|
|
target_type='user',
|
|
target_id=target.id,
|
|
role_id=None,
|
|
duration=None,
|
|
reason=reason
|
|
)
|
|
await response_message.edit(content=f"{target.mention} has been {cls.verb}! (Case {inline(f'#{moderation.id}')})\n{bold('Reason:')} {inline(reason)}")
|
|
await log(ctx, moderation.id)
|
|
await send_evidenceformat(ctx, moderation.id)
|
|
return cls
|
|
|
|
class Slowmode(Type):
|
|
key="slowmode"
|
|
string="slowmode"
|
|
verb="set the slowmode in"
|
|
channel=True
|
|
|
|
@classmethod
|
|
async def handler(cls, ctx: commands.Context, target: Messageable, silent: bool, duration: str, reason: str) -> 'Slowmode': # pylint: disable=unused-argument
|
|
"""Set the slowmode in a channel."""
|
|
bot = ctx.bot
|
|
parsed_time = parse_relativedelta(duration)
|
|
if not parsed_time:
|
|
await ctx.send(content=error("Please provide a valid duration!"), ephemeral=True)
|
|
try:
|
|
parsed_time = timedelta_from_relativedelta(parsed_time)
|
|
except ValueError:
|
|
await ctx.send(content=error("Please provide a valid duration!"), ephemeral=True)
|
|
|
|
if ceil(parsed_time.total_seconds()) > 21600:
|
|
await ctx.send(content=error("The slowmode duration cannot exceed 6 hours!"), ephemeral=True)
|
|
return
|
|
|
|
if isinstance(target, TextChannel):
|
|
await target.edit(slowmode_delay=ceil(parsed_time.total_seconds()))
|
|
moderation = await Moderation.log(
|
|
bot=bot,
|
|
guild_id=ctx.guild.id,
|
|
moderator_id=ctx.author.id,
|
|
moderation_type=cls(),
|
|
target_type='channel',
|
|
target_id=target.id,
|
|
role_id=None,
|
|
duration=parsed_time,
|
|
reason=None
|
|
)
|
|
await ctx.send(f"{ctx.author.mention} has {cls.verb} {target.mention} to {humanize_timedelta(parsed_time)}!\n{bold('Reason:')} {inline(reason)}")
|
|
await log(ctx, moderation.id)
|
|
return cls
|
|
|
|
class Lockdown(Type):
|
|
key="lockdown"
|
|
string="lockdown"
|
|
verb="locked down"
|
|
channel=True
|