202 lines
7.1 KiB
Python
202 lines
7.1 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
|
|
|
|
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)
|