feat(aurora): boilerplate for all currently added moderation types
This commit is contained in:
parent
a05e957dde
commit
8f0425456c
4 changed files with 201 additions and 21 deletions
|
@ -9,7 +9,9 @@ from redbot.core import commands, data_manager
|
||||||
from redbot.core.utils.chat_formatting import warning
|
from redbot.core.utils.chat_formatting import warning
|
||||||
|
|
||||||
from ..models.moderation import Moderation
|
from ..models.moderation import Moderation
|
||||||
|
from ..models.type import Type
|
||||||
from ..utilities.json import dump
|
from ..utilities.json import dump
|
||||||
|
from ..utilities.registry import type_registry
|
||||||
from ..utilities.utils import create_guild_table, timedelta_from_string
|
from ..utilities.utils import create_guild_table, timedelta_from_string
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,23 +42,18 @@ class ImportAuroraView(ui.View):
|
||||||
file = await self.ctx.message.attachments[0].read()
|
file = await self.ctx.message.attachments[0].read()
|
||||||
data: list[dict] = sorted(json.loads(file), key=lambda x: x["moderation_id"])
|
data: list[dict] = sorted(json.loads(file), key=lambda x: x["moderation_id"])
|
||||||
|
|
||||||
user_mod_types = ["NOTE", "WARN", "ADDROLE", "REMOVEROLE", "MUTE", "UNMUTE", "KICK", "TEMPBAN", "BAN", "UNBAN"]
|
|
||||||
|
|
||||||
channel_mod_types = ["SLOWMODE", "LOCKDOWN"]
|
|
||||||
|
|
||||||
failed_cases = []
|
failed_cases = []
|
||||||
|
|
||||||
for case in data:
|
for case in data:
|
||||||
if case["moderation_id"] == 0:
|
if case["moderation_id"] == 0:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
moderation_type: Type = type_registry[str.lower(case["moderation_type"])]
|
||||||
if "target_type" not in case or not case["target_type"]:
|
if "target_type" not in case or not case["target_type"]:
|
||||||
if case["moderation_type"] in user_mod_types:
|
if moderation_type.channel:
|
||||||
case["target_type"] = "USER"
|
case["target_type"] = "channel"
|
||||||
elif case["moderation_type"] in channel_mod_types:
|
|
||||||
case["target_type"] = "CHANNEL"
|
|
||||||
else:
|
else:
|
||||||
case["target_type"] = "USER"
|
case["target_type"] = "user"
|
||||||
|
|
||||||
if "role_id" not in case or not case["role_id"]:
|
if "role_id" not in case or not case["role_id"]:
|
||||||
case["role_id"] = None
|
case["role_id"] = None
|
||||||
|
@ -95,8 +92,8 @@ class ImportAuroraView(ui.View):
|
||||||
bot=interaction.client,
|
bot=interaction.client,
|
||||||
guild_id=self.ctx.guild.id,
|
guild_id=self.ctx.guild.id,
|
||||||
moderator_id=case["moderator_id"],
|
moderator_id=case["moderator_id"],
|
||||||
moderation_type=case["moderation_type"],
|
moderation_type=moderation_type.key,
|
||||||
target_type=case["target_type"],
|
target_type=case["target_type"].lower(),
|
||||||
target_id=case["target_id"],
|
target_id=case["target_id"],
|
||||||
role_id=case["role_id"],
|
role_id=case["role_id"],
|
||||||
duration=duration,
|
duration=duration,
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
|
|
||||||
from discord import File, Guild, Member, User
|
from math import ceil
|
||||||
|
|
||||||
|
from discord import File, Guild, Member, TextChannel, User
|
||||||
|
from discord.abc import Messageable
|
||||||
from discord.errors import HTTPException, NotFound
|
from discord.errors import HTTPException, NotFound
|
||||||
from redbot.core import app_commands, commands
|
from redbot.core import app_commands, commands
|
||||||
from redbot.core.bot import Red
|
from redbot.core.bot import Red
|
||||||
|
@ -19,6 +22,86 @@ def get_icon(bot: Red) -> File:
|
||||||
return get_footer_image(cog)
|
return get_footer_image(cog)
|
||||||
raise ValueError("Aurora cog not found. How was this managed?")
|
raise ValueError("Aurora cog not found. How was this managed?")
|
||||||
|
|
||||||
|
@type_registry.register(key="note")
|
||||||
|
class Note(Type):
|
||||||
|
key="note"
|
||||||
|
string="note"
|
||||||
|
verb="noted"
|
||||||
|
|
||||||
|
@type_registry.register(key="warn")
|
||||||
|
class Warn(Type):
|
||||||
|
key="warn"
|
||||||
|
string="warn"
|
||||||
|
verb="warned"
|
||||||
|
|
||||||
|
@type_registry.register(key="addrole")
|
||||||
|
class AddRole(Type):
|
||||||
|
key="addrole"
|
||||||
|
string="addrole"
|
||||||
|
verb="added a role to"
|
||||||
|
|
||||||
|
@type_registry.register(key="removerole")
|
||||||
|
class RemoveRole(Type):
|
||||||
|
key="removerole"
|
||||||
|
string="removerole"
|
||||||
|
verb="removed a role from"
|
||||||
|
|
||||||
|
@type_registry.register(key="mute")
|
||||||
|
class Mute(Type):
|
||||||
|
key="mute"
|
||||||
|
string="mute"
|
||||||
|
verb="muted"
|
||||||
|
|
||||||
|
@type_registry.register(key="unmute")
|
||||||
|
class Unmute(Type):
|
||||||
|
key="unmute"
|
||||||
|
string="unmute"
|
||||||
|
verb="unmuted"
|
||||||
|
|
||||||
|
@type_registry.register(key="kick")
|
||||||
|
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,
|
||||||
|
await bot.get_embed_color(ctx.channel),
|
||||||
|
ctx.guild,
|
||||||
|
reason,
|
||||||
|
cls(),
|
||||||
|
ctx.author,
|
||||||
|
None,
|
||||||
|
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,
|
||||||
|
ctx.guild.id,
|
||||||
|
ctx.author.id,
|
||||||
|
cls(),
|
||||||
|
'user',
|
||||||
|
target.id,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
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
|
||||||
@type_registry.register(key="ban")
|
@type_registry.register(key="ban")
|
||||||
class Ban(Type):
|
class Ban(Type):
|
||||||
key="ban"
|
key="ban"
|
||||||
|
@ -31,7 +114,7 @@ class Ban(Type):
|
||||||
bot = ctx.bot
|
bot = ctx.bot
|
||||||
try:
|
try:
|
||||||
await ctx.guild.fetch_ban(target)
|
await ctx.guild.fetch_ban(target)
|
||||||
await ctx.send(content=error(f"{target.mention} is already banned!"), ephemeral=True)
|
await ctx.send(content=error(f"{target.mention} is already {cls.verb}!"), ephemeral=True)
|
||||||
except NotFound:
|
except NotFound:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -58,7 +141,7 @@ class Ban(Type):
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
await ctx.guild.ban(target, reason=f"Banned by {ctx.author.id} for: {reason}", delete_message_seconds=delete_messages_seconds)
|
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(
|
moderation = await Moderation.log(
|
||||||
bot,
|
bot,
|
||||||
ctx.guild.id,
|
ctx.guild.id,
|
||||||
|
@ -110,7 +193,7 @@ class Tempban(Ban):
|
||||||
bot = ctx.bot
|
bot = ctx.bot
|
||||||
try:
|
try:
|
||||||
await ctx.guild.fetch_ban(target)
|
await ctx.guild.fetch_ban(target)
|
||||||
await ctx.send(content=error(f"{target.mention} is already banned!"), ephemeral=True)
|
await ctx.send(content=error(f"{target.mention} is already {Ban.verb}!"), ephemeral=True)
|
||||||
except NotFound:
|
except NotFound:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -145,7 +228,7 @@ class Tempban(Ban):
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
await ctx.guild.ban(target, reason=f"Tempbanned by {ctx.author.id} for: {reason} (Duration: {parsed_time})", delete_message_seconds=delete_messages_seconds)
|
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(
|
moderation = await Moderation.log(
|
||||||
bot,
|
bot,
|
||||||
ctx.guild.id,
|
ctx.guild.id,
|
||||||
|
@ -161,3 +244,101 @@ class Tempban(Ban):
|
||||||
await log(ctx, moderation.id)
|
await log(ctx, moderation.id)
|
||||||
await send_evidenceformat(ctx, moderation.id)
|
await send_evidenceformat(ctx, moderation.id)
|
||||||
return cls
|
return cls
|
||||||
|
|
||||||
|
@type_registry.register(key="unban")
|
||||||
|
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,
|
||||||
|
await bot.get_embed_color(ctx.channel),
|
||||||
|
ctx.guild,
|
||||||
|
reason,
|
||||||
|
cls(),
|
||||||
|
ctx.author,
|
||||||
|
None,
|
||||||
|
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,
|
||||||
|
ctx.guild.id,
|
||||||
|
ctx.author.id,
|
||||||
|
cls(),
|
||||||
|
'user',
|
||||||
|
target.id,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
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
|
||||||
|
|
||||||
|
@type_registry.register(key="slowmode")
|
||||||
|
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,
|
||||||
|
ctx.guild.id,
|
||||||
|
ctx.author.id,
|
||||||
|
cls(),
|
||||||
|
'channel',
|
||||||
|
target.id,
|
||||||
|
None,
|
||||||
|
parsed_time,
|
||||||
|
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
|
||||||
|
|
||||||
|
@type_registry.register(key="lockdown")
|
||||||
|
class Lockdown(Type):
|
||||||
|
key="lockdown"
|
||||||
|
string="lockdown"
|
||||||
|
verb="locked down"
|
||||||
|
channel=True
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from discord import Guild, Member, User
|
from discord import Guild, Member, User
|
||||||
|
from discord.abc import Messageable
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
from redbot.core.bot import Red
|
from redbot.core.bot import Red
|
||||||
|
|
||||||
|
@ -12,23 +13,24 @@ class Type(object):
|
||||||
string = "type"
|
string = "type"
|
||||||
verb = "typed"
|
verb = "typed"
|
||||||
embed_desc = "been"
|
embed_desc = "been"
|
||||||
|
channel = False # if this is True, the overridden handler methods should be typed with `discord.abc.Messageable` instead of `discord.Member | discord.User`
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.string
|
return self.string
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def handler(cls, ctx: commands.Context, target: Member | User, silent: bool, **kwargs) -> 'Type': # pylint: disable=unused-argument
|
async def handler(cls, ctx: commands.Context, target: Member | User | Messageable, silent: bool, **kwargs) -> 'Type': # pylint: disable=unused-argument
|
||||||
"""This method should be overridden by any child classes, but should retain the same starting keyword arguments."""
|
"""This method should be overridden by any child classes, but should retain the same starting keyword arguments."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def resolve_handler(cls, bot: Red, guild: Guild, target: Member | User, reason: str | None = None) -> Any: # pylint: disable=unused-argument
|
async def resolve_handler(cls, bot: Red, guild: Guild, target: Member | User | Messageable, reason: str | None = None, **kwargs) -> Any: # pylint: disable=unused-argument
|
||||||
"""This method should be overridden by any resolvable child classes, but should retain the same keyword arguments.
|
"""This method should be overridden by any resolvable child classes, but should retain the same keyword arguments.
|
||||||
If your moderation type should not be resolvable, do not override this."""
|
If your moderation type should not be resolvable, do not override this."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def expiry_handler(cls, bot: Red, guild: Guild, target: Member | User) -> Any: # pylint: disable=unused-argument
|
async def expiry_handler(cls, bot: Red, guild: Guild, target: Member | User | Messageable, **kwargs) -> Any: # pylint: disable=unused-argument
|
||||||
"""This method should be overridden by any expirable child classes, but should retain the same keyword arguments.
|
"""This method should be overridden by any expirable child classes, but should retain the same keyword arguments.
|
||||||
If your moderation type should not expire, do not override this."""
|
If your moderation type should not expire, do not override this."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
|
@ -9,14 +9,14 @@ from .registry import type_registry
|
||||||
from .utils import check_moddable
|
from .utils import check_moddable
|
||||||
|
|
||||||
|
|
||||||
async def moderate(ctx: Union[commands.Context, discord.Interaction], target: discord.Member, silent: bool | None, permissions: List[str], moderation_type: Type | str, **kwargs) -> None | Type:
|
async def moderate(ctx: Union[commands.Context, discord.Interaction], target: discord.Member | discord.User | discord.abc.Messageable, silent: bool | None, permissions: List[str], moderation_type: Type | str, **kwargs) -> None | Type:
|
||||||
"""This function is used to moderate users.
|
"""This function is used to moderate users.
|
||||||
It checks if the target can be moderated, then calls the handler method of the moderation type specified.
|
It checks if the target can be moderated, then calls the handler method of the moderation type specified.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
bot (Red): The bot instance.
|
bot (Red): The bot instance.
|
||||||
ctx (Union[commands.Context, discord.Interaction]): The context of the command. If this is a `discord.Interaction` object, it will be converted to a `commands.Context` object. Additionally, if the interaction orignated from a context menu, the `ctx.author` attribute will be overriden to `interaction.user`.
|
ctx (Union[commands.Context, discord.Interaction]): The context of the command. If this is a `discord.Interaction` object, it will be converted to a `commands.Context` object. Additionally, if the interaction orignated from a context menu, the `ctx.author` attribute will be overriden to `interaction.user`.
|
||||||
target (discord.Member): The target user to moderate.
|
target (discord.Member, discord.User, discord.abc.Messageable): The target user or channel to moderate.
|
||||||
silent (bool | None): Whether to send the moderation action to the target.
|
silent (bool | None): Whether to send the moderation action to the target.
|
||||||
permissions (List[str]): The permissions required to moderate the target.
|
permissions (List[str]): The permissions required to moderate the target.
|
||||||
moderation_type (Type): The moderation type (handler) to use. See `aurora.models.moderation_types` for some examples.
|
moderation_type (Type): The moderation type (handler) to use. See `aurora.models.moderation_types` for some examples.
|
||||||
|
|
Loading…
Reference in a new issue