2022-07-21 20:50:23 +02:00
|
|
|
import logging
|
2022-07-22 02:27:17 +02:00
|
|
|
from typing import List, Optional
|
2022-07-21 20:50:23 +02:00
|
|
|
from naff import (
|
|
|
|
Extension,
|
|
|
|
slash_command,
|
|
|
|
slash_option,
|
|
|
|
OptionTypes,
|
|
|
|
InteractionContext,
|
|
|
|
Embed,
|
|
|
|
EmbedField,
|
|
|
|
Permissions,
|
|
|
|
Member,
|
|
|
|
Client,
|
|
|
|
ActionRow,
|
|
|
|
Button,
|
|
|
|
ButtonStyles,
|
|
|
|
)
|
2022-10-15 02:51:21 +02:00
|
|
|
from naff.ext.paginators import Paginator
|
2022-07-23 01:10:45 +02:00
|
|
|
from peewee import fn
|
2022-07-21 21:27:47 +02:00
|
|
|
from database import (
|
2022-07-22 02:27:17 +02:00
|
|
|
GuildSettings,
|
2022-07-21 21:27:47 +02:00
|
|
|
Infractions as InfractionsModel,
|
|
|
|
)
|
2022-07-21 20:50:23 +02:00
|
|
|
|
|
|
|
|
|
|
|
class Infractions(Extension):
|
|
|
|
def __init__(self, client):
|
|
|
|
self.client: Client = client
|
|
|
|
|
|
|
|
@slash_command(
|
|
|
|
name="infractions", description="View your infractions", dm_permission=False
|
|
|
|
)
|
|
|
|
async def infractions_command(self, ctx: InteractionContext):
|
|
|
|
await ctx.defer(ephemeral=True)
|
|
|
|
infractions: List[InfractionsModel] = InfractionsModel.select().where(
|
|
|
|
InfractionsModel.guild_id == int(ctx.guild_id),
|
|
|
|
InfractionsModel.user_id == int(ctx.author.id),
|
|
|
|
)
|
|
|
|
|
|
|
|
if len(infractions) == 0:
|
|
|
|
await ctx.send("You have no infractions.", ephemeral=True)
|
|
|
|
return
|
|
|
|
|
|
|
|
embeds: List[Embed] = []
|
2022-07-21 21:27:47 +02:00
|
|
|
for infraction in infractions: # pylint: disable=not-an-iterable
|
2022-07-21 20:50:23 +02:00
|
|
|
embed = Embed(
|
|
|
|
title="Infraction",
|
|
|
|
description=f"{infraction.reason}",
|
|
|
|
color=infraction_colour(infraction.weight),
|
|
|
|
fields=[
|
|
|
|
EmbedField(
|
|
|
|
name="**Received**",
|
|
|
|
value=f"<t:{int(infraction.at_time.timestamp())}:F>",
|
2022-07-23 01:10:45 +02:00
|
|
|
inline=True,
|
|
|
|
),
|
|
|
|
EmbedField(
|
|
|
|
name="**Severity**", value=f"{infraction.weight}", inline=True
|
2022-07-21 20:50:23 +02:00
|
|
|
),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
embeds.append(embed)
|
2022-07-21 21:27:47 +02:00
|
|
|
|
2022-10-15 02:51:21 +02:00
|
|
|
# await ctx.send(embed=embeds, ephemeral=True)
|
|
|
|
paginator = Paginator.create_from_embeds(self.client, *embeds)
|
|
|
|
await paginator.send(ctx)
|
|
|
|
paginator.send
|
2022-07-21 20:50:23 +02:00
|
|
|
|
2022-07-21 21:27:47 +02:00
|
|
|
@slash_command(
|
|
|
|
name="user-infractions",
|
|
|
|
description="View a user's infractions",
|
|
|
|
dm_permission=False,
|
|
|
|
default_member_permissions=Permissions.KICK_MEMBERS,
|
|
|
|
)
|
|
|
|
@slash_option(
|
|
|
|
name="user",
|
|
|
|
description="User to view",
|
|
|
|
required=True,
|
|
|
|
opt_type=OptionTypes.USER,
|
|
|
|
)
|
2022-07-21 20:50:23 +02:00
|
|
|
async def user_infractions(self, ctx: InteractionContext, user: Member):
|
|
|
|
await ctx.defer(ephemeral=False)
|
|
|
|
infractions: List[InfractionsModel] = InfractionsModel.select().where(
|
|
|
|
InfractionsModel.guild_id == int(ctx.guild_id),
|
2022-07-23 01:10:45 +02:00
|
|
|
InfractionsModel.user_id == int(user.id),
|
2022-07-21 20:50:23 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
if len(infractions) == 0:
|
|
|
|
await ctx.send(f"{user.mention} has no infractions.")
|
|
|
|
return
|
|
|
|
|
2022-07-23 01:10:45 +02:00
|
|
|
infractions_weight: List[InfractionsModel] = InfractionsModel.select(
|
|
|
|
InfractionsModel.user_id,
|
|
|
|
fn.SUM(InfractionsModel.weight).alias("total_weight"),
|
|
|
|
fn.COUNT(InfractionsModel.id).alias("total_infractions"),
|
|
|
|
).where(
|
|
|
|
InfractionsModel.guild_id == int(ctx.guild_id),
|
|
|
|
InfractionsModel.user_id == int(user.id),
|
|
|
|
)
|
|
|
|
|
2022-07-21 20:50:23 +02:00
|
|
|
embeds: List[Embed] = []
|
2022-07-21 21:27:47 +02:00
|
|
|
for infraction in infractions: # pylint: disable=not-an-iterable
|
|
|
|
issuer = await self.client.fetch_member(
|
|
|
|
guild_id=ctx.guild, user_id=infraction.given_by
|
|
|
|
)
|
2022-07-21 20:50:23 +02:00
|
|
|
embed = Embed(
|
2022-07-23 01:10:45 +02:00
|
|
|
title=f"Infraction for user {user.display_name} ({user.username}#{user.discriminator}, {user.id})", # pylint: disable=line-too-long
|
2022-07-21 20:50:23 +02:00
|
|
|
description=f"{infraction.reason}",
|
|
|
|
color=infraction_colour(infraction.weight),
|
|
|
|
fields=[
|
2022-07-23 01:10:45 +02:00
|
|
|
EmbedField(name="**ID**", value=f"{infraction.id}", inline=True),
|
2022-07-21 20:50:23 +02:00
|
|
|
EmbedField(
|
|
|
|
name="**Received**",
|
|
|
|
value=f"<t:{int(infraction.at_time.timestamp())}:F>",
|
2022-07-23 01:10:45 +02:00
|
|
|
inline=True,
|
|
|
|
),
|
|
|
|
EmbedField(
|
|
|
|
name="**Severity**", value=f"{infraction.weight}", inline=True
|
|
|
|
),
|
|
|
|
EmbedField(
|
|
|
|
name="**Issued by**",
|
|
|
|
value=f"{issuer.display_name}",
|
|
|
|
inline=True,
|
2022-07-21 20:50:23 +02:00
|
|
|
),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
embeds.append(embed)
|
2022-07-21 21:27:47 +02:00
|
|
|
|
2022-10-15 02:51:21 +02:00
|
|
|
if len(embeds) <= 10:
|
|
|
|
await ctx.send(
|
|
|
|
content=f"{user.mention} has {infractions_weight[0].total_infractions} infractions, for a total weight of {infractions_weight[0].total_weight:.2f}"
|
|
|
|
if infractions_weight.count() > 0
|
|
|
|
else None,
|
|
|
|
embed=embeds,
|
|
|
|
ephemeral=False,
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
await ctx.send(
|
|
|
|
content=f"{user.mention} has {infractions_weight[0].total_infractions} infractions, for a total weight of {infractions_weight[0].total_weight:.2f}"
|
|
|
|
if infractions_weight.count() > 0
|
|
|
|
else None,
|
|
|
|
embed=embeds[:10],
|
|
|
|
ephemeral=False,
|
|
|
|
)
|
|
|
|
await ctx.send(embed=embeds[10:])
|
2022-07-21 20:50:23 +02:00
|
|
|
|
2022-07-21 21:27:47 +02:00
|
|
|
@slash_command(
|
|
|
|
name="warn",
|
|
|
|
description="Warn a user",
|
|
|
|
dm_permission=False,
|
|
|
|
default_member_permissions=Permissions.KICK_MEMBERS,
|
|
|
|
)
|
|
|
|
@slash_option(
|
|
|
|
name="user",
|
|
|
|
description="User to warn",
|
|
|
|
required=True,
|
|
|
|
opt_type=OptionTypes.USER,
|
|
|
|
)
|
|
|
|
@slash_option(
|
|
|
|
name="reason",
|
|
|
|
description="Reason for warning",
|
|
|
|
required=False,
|
|
|
|
opt_type=OptionTypes.STRING,
|
|
|
|
)
|
|
|
|
@slash_option(
|
|
|
|
name="weight",
|
|
|
|
description="Severity of warning",
|
|
|
|
required=False,
|
|
|
|
opt_type=OptionTypes.NUMBER,
|
|
|
|
min_value=0.0,
|
|
|
|
max_value=10.0,
|
|
|
|
)
|
|
|
|
@slash_option(
|
|
|
|
name="silent",
|
|
|
|
description="Silent warning (will not notify user)",
|
|
|
|
required=False,
|
|
|
|
opt_type=OptionTypes.BOOLEAN,
|
|
|
|
)
|
|
|
|
async def warn_user(
|
|
|
|
self,
|
|
|
|
ctx: InteractionContext,
|
|
|
|
user: Member,
|
|
|
|
reason: str = None,
|
|
|
|
weight: float = None,
|
|
|
|
silent: bool = None,
|
|
|
|
):
|
2022-07-21 20:50:23 +02:00
|
|
|
await ctx.defer(ephemeral=False)
|
|
|
|
if weight is None:
|
|
|
|
weight = 1.0
|
|
|
|
if silent is None:
|
|
|
|
silent = False
|
|
|
|
if reason is None:
|
|
|
|
reason = ""
|
|
|
|
infraction = InfractionsModel.create(
|
|
|
|
guild_id=int(ctx.guild_id),
|
|
|
|
user_id=int(user.id),
|
|
|
|
given_by=int(ctx.author.id),
|
|
|
|
reason=reason,
|
|
|
|
weight=weight,
|
|
|
|
silent=silent,
|
|
|
|
)
|
|
|
|
warning_msg = ...
|
|
|
|
if not silent:
|
|
|
|
try:
|
|
|
|
warning_msg = await user.send(
|
|
|
|
embed=Embed(
|
|
|
|
title=f"You have received a warning in {ctx.guild.name}",
|
|
|
|
description=f"{reason}",
|
|
|
|
color=infraction_colour(weight),
|
|
|
|
fields=[
|
|
|
|
EmbedField(name="**Severity**", value=f"{weight}"),
|
2022-07-21 21:27:47 +02:00
|
|
|
EmbedField(
|
|
|
|
name="**Issued at**",
|
|
|
|
value=f"<t:{int(infraction.at_time.timestamp())}:F>", # pylint: disable=no-member
|
|
|
|
),
|
2022-07-21 20:50:23 +02:00
|
|
|
],
|
|
|
|
)
|
|
|
|
)
|
2022-07-21 21:27:47 +02:00
|
|
|
except Exception: # pylint: disable=broad-except
|
2022-07-21 20:50:23 +02:00
|
|
|
warning_msg = None
|
|
|
|
|
2022-07-21 21:27:47 +02:00
|
|
|
await ctx.send(
|
|
|
|
f'Warned {user.display_name} ({user.username}#{user.discriminator}, {user.id}) for "{reason}" with severity {weight}', # pylint: disable=line-too-long
|
|
|
|
ephemeral=True,
|
|
|
|
)
|
2022-07-22 02:27:17 +02:00
|
|
|
|
2022-07-23 01:10:45 +02:00
|
|
|
|
2022-08-11 01:39:15 +02:00
|
|
|
guild_settings: Optional[GuildSettings] = GuildSettings.get_or_none(GuildSettings.guild_id == int(ctx.guild_id))
|
|
|
|
if guild_settings is not None:
|
|
|
|
if guild_settings.admin_channel is not None:
|
2022-08-29 02:50:55 +02:00
|
|
|
admin_channel = await self.client.fetch_channel(int(guild_settings.admin_channel))
|
2022-08-11 01:39:15 +02:00
|
|
|
if admin_channel is not None:
|
|
|
|
await admin_channel.send(embed=Embed(
|
|
|
|
title=f"Warned {user.display_name} ({user.username}#{user.discriminator}, {user.id})",
|
|
|
|
description=f"{reason}",
|
|
|
|
color=infraction_colour(0x0000FF),
|
|
|
|
fields=[
|
|
|
|
],
|
|
|
|
))
|
2022-07-22 02:27:17 +02:00
|
|
|
|
2022-07-21 20:50:23 +02:00
|
|
|
if not silent and warning_msg is None:
|
2022-07-21 21:27:47 +02:00
|
|
|
await ctx.send(
|
|
|
|
f"{user.mention} has been warned, but I couldn't DM them.",
|
|
|
|
ephemeral=True,
|
|
|
|
)
|
2022-07-21 20:50:23 +02:00
|
|
|
|
2022-07-21 21:27:47 +02:00
|
|
|
@slash_command(
|
|
|
|
name="remove-infraction",
|
|
|
|
description="Remove an infraction",
|
|
|
|
dm_permission=False,
|
|
|
|
default_member_permissions=Permissions.KICK_MEMBERS,
|
|
|
|
)
|
|
|
|
@slash_option(
|
|
|
|
name="infraction-id",
|
|
|
|
description="ID of infraction to remove",
|
|
|
|
required=True,
|
|
|
|
opt_type=OptionTypes.INTEGER,
|
|
|
|
)
|
2022-07-21 20:50:23 +02:00
|
|
|
async def remove_infraction(self, ctx: InteractionContext, infraction_id: int):
|
|
|
|
await ctx.defer(ephemeral=True)
|
2022-07-21 21:27:47 +02:00
|
|
|
infraction: InfractionsModel = InfractionsModel.get_or_none(
|
|
|
|
InfractionsModel.id == infraction_id
|
|
|
|
)
|
2022-07-21 20:50:23 +02:00
|
|
|
if infraction is None:
|
|
|
|
await ctx.send("That infraction doesn't exist.", ephemeral=True)
|
|
|
|
return
|
|
|
|
|
2022-07-21 21:27:47 +02:00
|
|
|
if infraction.guild_id != int(ctx.guild_id):
|
2022-07-21 20:50:23 +02:00
|
|
|
await ctx.send("That infraction doesn't exist.", ephemeral=True)
|
|
|
|
return
|
|
|
|
|
2022-07-21 21:27:47 +02:00
|
|
|
# pylint: disable=unexpected-keyword-arg
|
|
|
|
components = [
|
|
|
|
ActionRow(
|
2022-07-21 20:50:23 +02:00
|
|
|
Button(
|
|
|
|
custom_id=f"remove-infraction:remove:{infraction.id}",
|
2022-07-21 21:27:47 +02:00
|
|
|
style=ButtonStyles.DANGER,
|
2022-07-21 20:50:23 +02:00
|
|
|
label="Remove infraction",
|
|
|
|
),
|
|
|
|
Button(
|
|
|
|
custom_id=f"remove-infraction:cancel:{infraction.id}",
|
2022-07-21 21:27:47 +02:00
|
|
|
style=ButtonStyles.SECONDARY,
|
2022-07-21 20:50:23 +02:00
|
|
|
label="Cancel",
|
|
|
|
),
|
2022-07-21 21:27:47 +02:00
|
|
|
)
|
|
|
|
]
|
2022-07-21 20:50:23 +02:00
|
|
|
|
2022-10-15 02:51:21 +02:00
|
|
|
given_by: Member = await self.client.fetch_member(f"{infraction.given_by}", ctx.guild_id)
|
2022-07-21 21:27:47 +02:00
|
|
|
await ctx.send(
|
2022-07-21 20:50:23 +02:00
|
|
|
content="Remove this infraction? (times out in 60 seconds)",
|
|
|
|
embed=Embed(
|
|
|
|
title="Infraction",
|
|
|
|
description=f"{infraction.reason}",
|
|
|
|
color=infraction_colour(infraction.weight),
|
|
|
|
fields=[
|
2022-07-21 21:27:47 +02:00
|
|
|
EmbedField(name="**ID**", value=f"{infraction.id}"),
|
2022-07-21 20:50:23 +02:00
|
|
|
EmbedField(
|
|
|
|
name="**Received**",
|
|
|
|
value=f"<t:{int(infraction.at_time.timestamp())}:F>",
|
|
|
|
),
|
|
|
|
EmbedField(name="**Severity**", value=f"{infraction.weight}"),
|
2022-10-15 02:51:21 +02:00
|
|
|
EmbedField(name="**Issued by**", value=f"{given_by.display_name} ({given_by.username}#{given_by.discriminator})"),
|
2022-07-21 20:50:23 +02:00
|
|
|
],
|
|
|
|
),
|
|
|
|
ephemeral=True,
|
2022-07-21 21:27:47 +02:00
|
|
|
components=components,
|
2022-07-21 20:50:23 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
try:
|
2022-07-21 21:27:47 +02:00
|
|
|
used_comp = await self.client.wait_for_component(
|
|
|
|
components=components, timeout=60
|
|
|
|
)
|
2022-07-21 20:50:23 +02:00
|
|
|
except TimeoutError:
|
|
|
|
await ctx.send("Timed out.", ephemeral=True)
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
(group, action, inf_id) = used_comp.context.custom_id.split(":")
|
|
|
|
if group != "remove-infraction" or action == "cancel":
|
|
|
|
await used_comp.context.send("Cancelled.", ephemeral=True)
|
|
|
|
return
|
2022-07-21 21:27:47 +02:00
|
|
|
|
2022-07-21 20:50:23 +02:00
|
|
|
if action == "remove" and inf_id == str(infraction.id):
|
|
|
|
infraction.delete_instance()
|
|
|
|
await used_comp.context.send("Removed.", ephemeral=True)
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
def infraction_colour(w: float) -> int:
|
|
|
|
if w < 0.5:
|
|
|
|
return 0xBCBCBC
|
2022-07-21 21:27:47 +02:00
|
|
|
if w < 1.0:
|
2022-07-21 20:50:23 +02:00
|
|
|
return 0xFF7711
|
2022-07-21 21:27:47 +02:00
|
|
|
|
|
|
|
return 0xFF2211
|
2022-07-21 20:50:23 +02:00
|
|
|
|
|
|
|
|
|
|
|
def setup(client):
|
|
|
|
InfractionsModel.create_table()
|
|
|
|
Infractions(client)
|
|
|
|
logging.info("Infractions extension loaded.")
|