jarvis-bot/jarvis/cogs/admin/mute.py

206 lines
7.3 KiB
Python

"""JARVIS MuteCog."""
import asyncio
from datetime import datetime, timedelta, timezone
from dateparser import parse
from dateparser_data.settings import default_parsers
from jarvis_core.db.models import Mute
from naff import InteractionContext, Permissions
from naff.client.errors import Forbidden
from naff.models.discord.modal import InputText, Modal, TextStyles
from naff.models.discord.user import Member
from naff.models.naff.application_commands import (
CommandTypes,
OptionTypes,
SlashCommandChoice,
context_menu,
slash_command,
slash_option,
)
from naff.models.naff.command import check
from jarvis.embeds.admin import mute_embed, unmute_embed
from jarvis.utils.cogs import ModcaseCog
from jarvis.utils.permissions import admin_or_permissions
class MuteCog(ModcaseCog):
"""JARVIS MuteCog."""
async def _apply_timeout(
self, ctx: InteractionContext, user: Member, reason: str, until: datetime
) -> None:
await user.timeout(communication_disabled_until=until, reason=reason)
duration = int((until - datetime.now(tz=timezone.utc)).seconds / 60)
await Mute(
user=user.id,
reason=reason,
admin=ctx.author.id,
guild=ctx.guild.id,
duration=duration,
active=True,
).commit()
return mute_embed(user=user, admin=ctx.author, reason=reason, guild=ctx.guild)
@context_menu(name="Mute User", context_type=CommandTypes.USER)
@check(
admin_or_permissions(
Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS
)
)
async def _timeout_cm(self, ctx: InteractionContext) -> None:
modal = Modal(
title=f"Muting {ctx.target.mention}",
components=[
InputText(
label="Reason?",
placeholder="Spamming, harrassment, etc",
style=TextStyles.SHORT,
custom_id="reason",
max_length=100,
),
InputText(
label="Duration",
placeholder="1h 30m | in 5 minutes | in 4 weeks",
style=TextStyles.SHORT,
custom_id="until",
max_length=100,
),
],
)
await ctx.send_modal(modal)
try:
response = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5)
reason = response.responses.get("reason")
until = response.responses.get("until")
except asyncio.TimeoutError:
return
base_settings = {
"PREFER_DATES_FROM": "future",
"TIMEZONE": "UTC",
"RETURN_AS_TIMEZONE_AWARE": True,
}
rt_settings = base_settings.copy()
rt_settings["PARSERS"] = [
x for x in default_parsers if x not in ["absolute-time", "timestamp"]
]
rt_until = parse(until, settings=rt_settings)
at_settings = base_settings.copy()
at_settings["PARSERS"] = [x for x in default_parsers if x != "relative-time"]
at_until = parse(until, settings=at_settings)
old_until = until
if rt_until:
until = rt_until
elif at_until:
until = at_until
else:
self.logger.debug(f"Failed to parse delay: {until}")
await response.send(
f"`{until}` is not a parsable date, please try again", ephemeral=True
)
return
if until < datetime.now(tz=timezone.utc):
await response.send(
f"`{old_until}` is in the past, which isn't allowed", ephemeral=True
)
return
try:
embed = await self._apply_timeout(ctx, ctx.target, reason, until)
await response.send(embeds=embed)
except Forbidden:
await response.send("Unable to mute this user", ephemeral=True)
@slash_command(name="mute", description="Mute a user")
@slash_option(name="user", description="User to mute", opt_type=OptionTypes.USER, required=True)
@slash_option(
name="reason",
description="Reason for mute",
opt_type=OptionTypes.STRING,
required=True,
)
@slash_option(
name="time",
description="Duration of mute, default 1",
opt_type=OptionTypes.INTEGER,
required=False,
)
@slash_option(
name="scale",
description="Time scale, default Hour(s)",
opt_type=OptionTypes.INTEGER,
required=False,
choices=[
SlashCommandChoice(name="Minute(s)", value=1),
SlashCommandChoice(name="Hour(s)", value=60),
SlashCommandChoice(name="Day(s)", value=1440),
SlashCommandChoice(name="Week(s)", value=10080),
],
)
@check(
admin_or_permissions(
Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS
)
)
async def _timeout(
self, ctx: InteractionContext, user: Member, reason: str, time: int = 1, scale: int = 60
) -> None:
if user == ctx.author:
await ctx.send("You cannot mute yourself.", ephemeral=True)
return
if user == self.bot.user:
await ctx.send("I'm afraid I can't let you do that", ephemeral=True)
return
if len(reason) > 100:
await ctx.send("Reason must be < 100 characters", ephemeral=True)
return
if not await ctx.guild.fetch_member(user.id):
await ctx.send("User must be in guild", ephemeral=True)
return
# Max 4 weeks (2419200 seconds) per API
duration = time * scale
if duration > 40320:
await ctx.send("Mute must be less than 4 weeks (40,320 minutes)", ephemeral=True)
return
until = datetime.now(tz=timezone.utc) + timedelta(minutes=duration)
try:
embed = await self._apply_timeout(ctx, user, reason, until)
await ctx.send(embeds=embed)
except Forbidden:
await ctx.send("Unable to mute this user", ephemeral=True)
@slash_command(name="unmute", description="Unmute a user")
@slash_option(
name="user", description="User to unmute", opt_type=OptionTypes.USER, required=True
)
@slash_option(
name="reason", description="Reason for unmute", opt_type=OptionTypes.STRING, required=True
)
@check(
admin_or_permissions(
Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS
)
)
async def _unmute(self, ctx: InteractionContext, user: Member, reason: str) -> None:
if (
not user.communication_disabled_until
or user.communication_disabled_until.timestamp()
< datetime.now(tz=timezone.utc).timestamp() # noqa: W503
):
await ctx.send("User is not muted", ephemeral=True)
return
if not await ctx.guild.fetch_member(user.id):
await ctx.send("User must be in guild", ephemeral=True)
return
await user.timeout(communication_disabled_until=datetime.now(tz=timezone.utc))
embed = unmute_embed(user=user, admin=ctx.author, reason=reason, guild=ctx.guild)
await ctx.send(embeds=embed)