Seaswimmer
9c345ed96b
none of the options do anything yet, this is just creating the configuration keys and the menu to modify them
582 lines
22 KiB
Python
582 lines
22 KiB
Python
# pylint: disable=cyclic-import
|
|
from datetime import datetime, timedelta
|
|
from typing import Union
|
|
|
|
from discord import Color, Embed, Guild, Interaction, Member, Message, Role, User
|
|
from redbot.core import commands
|
|
from redbot.core.bot import Red
|
|
from redbot.core.utils.chat_formatting import bold, box, error, humanize_timedelta, warning
|
|
|
|
from ..models.moderation import Moderation
|
|
from ..models.partials import PartialUser
|
|
from ..models.type import Type
|
|
from .config import config
|
|
from .utils import get_bool_emoji, get_pagesize_str
|
|
|
|
|
|
async def message_factory(
|
|
bot: Red,
|
|
color: Color,
|
|
guild: Guild,
|
|
reason: str,
|
|
moderation_type: Type,
|
|
moderator: Union[Member, User] | None = None,
|
|
duration: timedelta | None = None,
|
|
response: Message | None = None,
|
|
case: bool = True,
|
|
) -> Embed:
|
|
"""This function creates a message from set parameters, meant for contacting the moderated user.
|
|
|
|
Args:
|
|
bot (Red): The bot instance.
|
|
color (Color): The color of the embed.
|
|
guild (Guild): The guild the moderation occurred in.
|
|
reason (str): The reason for the moderation.
|
|
moderation_type (Type): The type of moderation.
|
|
moderator (Union[Member, User], optional): The moderator who performed the moderation. Defaults to None.
|
|
duration (timedelta, optional): The duration of the moderation. Defaults to None.
|
|
response (Message, optional): The response message. Defaults to None.
|
|
case (bool, optional): Whether the message is for a moderation case. Defaults to True.
|
|
|
|
|
|
Returns:
|
|
embed: The message embed.
|
|
"""
|
|
if response is not None and moderation_type.key not in [
|
|
"kick",
|
|
"ban",
|
|
"tempban",
|
|
"unban",
|
|
]:
|
|
guild_name = f"[{guild.name}]({response.jump_url})"
|
|
else:
|
|
guild_name = guild.name
|
|
|
|
if duration:
|
|
embed_duration = f" for {humanize_timedelta(timedelta=duration)}"
|
|
else:
|
|
embed_duration = ""
|
|
|
|
embed = Embed(
|
|
title=str.title(moderation_type.verb),
|
|
description=f"You have {moderation_type.embed_desc}{moderation_type.verb}{embed_duration} in {guild_name}.",
|
|
color=color,
|
|
timestamp=datetime.now(),
|
|
)
|
|
|
|
if await config.guild(guild).show_moderator() and moderator is not None:
|
|
embed.add_field(
|
|
name="Moderator", value=f"`{moderator.name} ({moderator.id})`", inline=False
|
|
)
|
|
|
|
embed.add_field(name="Reason", value=f"`{reason}`", inline=False)
|
|
|
|
if guild.icon.url is not None:
|
|
embed.set_author(name=guild.name, icon_url=guild.icon.url)
|
|
else:
|
|
embed.set_author(name=guild.name)
|
|
|
|
if case:
|
|
embed.set_footer(
|
|
text=f"Case #{await Moderation.get_next_case_number(bot=bot, guild_id=guild.id):,}",
|
|
icon_url="attachment://arrow.png",
|
|
)
|
|
|
|
return embed
|
|
|
|
async def resolve_factory(moderation: Moderation, reason: str) -> Embed:
|
|
"""This function creates a resolved embed from set parameters, meant for contacting the moderated user.
|
|
|
|
Args:
|
|
moderation (aurora.models.Moderation): The moderation object.
|
|
reason (str): The reason for resolving the moderation.
|
|
Returns: `discord.Embed`
|
|
"""
|
|
|
|
embed = Embed(
|
|
title=str.title(moderation.type.name) + " Resolved",
|
|
description=f"Your {moderation.type.name} in {moderation.guild.name} has been resolved.",
|
|
color=await moderation.bot.get_embed_color(moderation.guild.channels[0]),
|
|
timestamp=datetime.now(),
|
|
)
|
|
|
|
embed.add_field(name="Reason", value=f"`{reason}`", inline=False)
|
|
|
|
if moderation.guild.icon.url is not None:
|
|
embed.set_author(name=moderation.guild.name, icon_url=moderation.guild.icon.url)
|
|
else:
|
|
embed.set_author(name=moderation.guild.name)
|
|
embed.set_footer(
|
|
text=f"Case #{moderation.id:,}",
|
|
icon_url="attachment://arrow.png",
|
|
)
|
|
|
|
return embed
|
|
|
|
async def log_factory(
|
|
ctx: commands.Context, moderation: Moderation, resolved: bool = False
|
|
) -> Embed:
|
|
"""This function creates a log embed from set parameters, meant for moderation logging.
|
|
|
|
Args:
|
|
ctx (commands.Context): The ctx object.
|
|
moderation (aurora.models.Moderation): The moderation object.
|
|
resolved (bool, optional): Whether the case is resolved or not. Defaults to False.
|
|
"""
|
|
target = await moderation.get_target()
|
|
moderator = await moderation.get_moderator()
|
|
if resolved:
|
|
embed = Embed(
|
|
title=f"📕 Case #{moderation.id:,} Resolved",
|
|
color=await ctx.bot.get_embed_color(ctx.channel),
|
|
)
|
|
|
|
resolved_by = await moderation.get_resolved_by()
|
|
embed.description = f"**Type:** {str.title(moderation.type.string)}\n**Target:** {target.name} ({target.id})\n**Moderator:** {moderator.name} ({moderator.id})\n**Timestamp:** <t:{moderation.unix_timestamp}> | <t:{moderation.unix_timestamp}:R>"
|
|
|
|
if moderation.duration is not None:
|
|
duration_embed = (
|
|
f"{humanize_timedelta(timedelta=moderation.duration)} | <t:{moderation.end_timestamp}:R>"
|
|
if not moderation.expired
|
|
else str(humanize_timedelta(timedelta=moderation.duration))
|
|
)
|
|
embed.description = (
|
|
embed.description
|
|
+ f"\n**Duration:** {duration_embed}\n**Expired:** {moderation.expired}"
|
|
)
|
|
|
|
if moderation.metadata.items():
|
|
for key, value in moderation.metadata.items():
|
|
embed.description += f"\n**{key.title()}:** {value}"
|
|
|
|
embed.add_field(name="Reason", value=box(moderation.reason), inline=False)
|
|
|
|
embed.add_field(
|
|
name="Resolve Reason",
|
|
value=f"Resolved by `{resolved_by.name}` ({resolved_by.id}) for:\n"
|
|
+ box(moderation.resolve_reason),
|
|
inline=False,
|
|
)
|
|
else:
|
|
embed = Embed(
|
|
title=f"📕 Case #{moderation.id:,}",
|
|
color=await ctx.bot.get_embed_color(ctx.channel),
|
|
)
|
|
embed.description = f"**Type:** {str.title(moderation.type.string)}\n**Target:** {target.name} ({target.id})\n**Moderator:** {moderator.name} ({moderator.id})\n**Timestamp:** <t:{moderation.unix_timestamp}> | <t:{moderation.unix_timestamp}:R>"
|
|
|
|
if moderation.duration:
|
|
embed.description = (
|
|
embed.description
|
|
+ f"\n**Duration:** {humanize_timedelta(timedelta=moderation.duration)} | <t:{moderation.unix_timestamp}:R>"
|
|
)
|
|
|
|
if moderation.metadata.items():
|
|
for key, value in moderation.metadata.items():
|
|
embed.description += f"\n**{key.title()}:** {value}"
|
|
|
|
embed.add_field(name="Reason", value=box(moderation.reason), inline=False)
|
|
return embed
|
|
|
|
|
|
async def case_factory(interaction: Interaction, moderation: Moderation) -> Embed:
|
|
"""This function creates a case embed from set parameters.
|
|
|
|
Args:
|
|
interaction (discord.Interaction): The interaction object.
|
|
moderation (aurora.models.Moderation): The moderation object.
|
|
"""
|
|
target = await moderation.get_target()
|
|
moderator = await moderation.get_moderator()
|
|
|
|
embed = Embed(
|
|
title=f"📕 Case #{moderation.id:,}",
|
|
color=await interaction.client.get_embed_color(interaction.channel),
|
|
)
|
|
embed.description = f"**Type:** {str.title(moderation.type.string)}\n**Target:** `{target.name}` ({target.id})\n**Moderator:** `{moderator.name}` ({moderator.id})\n**Resolved:** {moderation.resolved}\n**Timestamp:** <t:{moderation.unix_timestamp}> | <t:{moderation.unix_timestamp}:R>"
|
|
|
|
if moderation.duration:
|
|
duration_embed = (
|
|
f"{humanize_timedelta(timedelta=moderation.duration)} | <t:{moderation.unix_timestamp}:R>"
|
|
if moderation.expired is False
|
|
else str(humanize_timedelta(timedelta=moderation.duration))
|
|
)
|
|
embed.description += f"\n**Duration:** {duration_embed}\n**Expired:** {moderation.expired}"
|
|
|
|
embed.description += (
|
|
f"\n**Changes:** {len(moderation.changes) - 1}"
|
|
if moderation.changes
|
|
else "\n**Changes:** 0"
|
|
)
|
|
|
|
if moderation.role_id:
|
|
role = await moderation.get_role()
|
|
embed.description += f"\n**Role:** {role.name}"
|
|
|
|
if moderation.metadata:
|
|
if moderation.metadata.get("imported_from"):
|
|
embed.description += (
|
|
f"\n**Imported From:** {moderation.metadata['imported_from']}"
|
|
)
|
|
moderation.metadata.pop("imported_from")
|
|
if moderation.metadata.get("imported_timestamp"):
|
|
embed.description += (
|
|
f"\n**Imported Timestamp:** <t:{moderation.metadata['imported_timestamp']}> | <t:{moderation.metadata['imported_timestamp']}:R>"
|
|
)
|
|
moderation.metadata.pop("imported_timestamp")
|
|
if moderation.metadata.items():
|
|
for key, value in moderation.metadata.items():
|
|
embed.description += f"\n**{key.title()}:** {value}"
|
|
|
|
embed.add_field(name="Reason", value=box(moderation.reason), inline=False)
|
|
|
|
if moderation.resolved:
|
|
resolved_user = await moderation.get_resolved_by()
|
|
embed.add_field(
|
|
name="Resolve Reason",
|
|
value=f"Resolved by `{resolved_user.name}` ({resolved_user.id}) for:\n{box(moderation.resolve_reason)}",
|
|
inline=False,
|
|
)
|
|
|
|
return embed
|
|
|
|
|
|
async def changes_factory(interaction: Interaction, moderation: Moderation) -> Embed:
|
|
"""This function creates a changes embed from set parameters.
|
|
|
|
Args:
|
|
interaction (discord.Interaction): The interaction object.
|
|
moderation (aurora.models.Moderation): The moderation object.
|
|
"""
|
|
embed = Embed(
|
|
title=f"📕 Case #{moderation.id:,} Changes",
|
|
color=await interaction.client.get_embed_color(interaction.channel),
|
|
)
|
|
|
|
memory_dict = {}
|
|
|
|
if moderation.changes:
|
|
for change in moderation.changes:
|
|
if change.user_id not in memory_dict:
|
|
memory_dict[str(change.user_id)] = await change.get_user()
|
|
|
|
user: PartialUser = memory_dict[str(change.user_id)]
|
|
|
|
timestamp = f"<t:{change.unix_timestamp}> | <t:{change.unix_timestamp}:R>"
|
|
|
|
if change.type == "ORIGINAL":
|
|
embed.add_field(
|
|
name="Original",
|
|
value=f"**User:** `{user.name}` ({user.id})\n**Reason:** {change.reason}\n**Timestamp:** {timestamp}",
|
|
inline=False,
|
|
)
|
|
|
|
elif change.type == "EDIT":
|
|
embed.add_field(
|
|
name="Edit",
|
|
value=f"**User:** `{user.name}` ({user.id})\n**Reason:** {change.reason}\n**Timestamp:** {timestamp}",
|
|
inline=False,
|
|
)
|
|
|
|
elif change.type == "RESOLVE":
|
|
embed.add_field(
|
|
name="Resolve",
|
|
value=f"**User:** `{user.name}` ({user.id})\n**Reason:** {change.reason}\n**Timestamp:** {timestamp}",
|
|
inline=False,
|
|
)
|
|
|
|
else:
|
|
embed.description = "*No changes have been made to this case.* 🙁"
|
|
|
|
return embed
|
|
|
|
|
|
async def evidenceformat_factory(moderation: Moderation) -> str:
|
|
"""This function creates a codeblock in evidence format from set parameters.
|
|
|
|
Args:
|
|
interaction (discord.Interaction): The interaction object.
|
|
moderation (aurora.models.Moderation): The moderation object.
|
|
"""
|
|
target = await moderation.get_target()
|
|
moderator = await moderation.get_moderator()
|
|
|
|
content = f"Case: {moderation.id:,} ({str.title(moderation.type.string)})\nTarget: {target.name} ({target.id})\nModerator: {moderator.name} ({moderator.id})"
|
|
|
|
if moderation.duration is not None:
|
|
content += f"\nDuration: {humanize_timedelta(timedelta=moderation.duration)}"
|
|
|
|
if moderation.role_id:
|
|
role = await moderation.get_role()
|
|
content += "\nRole: " + (role.name if role is not None else moderation.role_id)
|
|
|
|
content += f"\nReason: {moderation.reason}"
|
|
|
|
for key, value in moderation.metadata.items():
|
|
content += f"\n{key.title()}: {value}"
|
|
|
|
return box(content, "prolog")
|
|
|
|
|
|
########################################################################################################################
|
|
### Configuration Embeds #
|
|
########################################################################################################################
|
|
|
|
|
|
async def _config(ctx: commands.Context) -> Embed:
|
|
"""Generates the core embed for configuration menus to use."""
|
|
e = Embed(title="Aurora Configuration Menu", color=await ctx.embed_color())
|
|
e.set_thumbnail(url=ctx.bot.user.display_avatar.url)
|
|
return e
|
|
|
|
|
|
async def overrides_embed(ctx: commands.Context) -> Embed:
|
|
"""Generates a configuration menu embed for a user's overrides."""
|
|
|
|
override_settings = {
|
|
"ephemeral": await config.user(ctx.author).history_ephemeral(),
|
|
"inline": await config.user(ctx.author).history_inline(),
|
|
"inline_pagesize": await config.user(ctx.author).history_inline_pagesize(),
|
|
"pagesize": await config.user(ctx.author).history_pagesize(),
|
|
"auto_evidenceformat": await config.user(ctx.author).auto_evidenceformat(),
|
|
}
|
|
|
|
override_str = [
|
|
"- "
|
|
+ bold("Auto Evidence Format: ")
|
|
+ get_bool_emoji(override_settings["auto_evidenceformat"]),
|
|
"- " + bold("Ephemeral: ") + get_bool_emoji(override_settings["ephemeral"]),
|
|
"- " + bold("History Inline: ") + get_bool_emoji(override_settings["inline"]),
|
|
"- "
|
|
+ bold("History Inline Pagesize: ")
|
|
+ get_pagesize_str(override_settings["inline_pagesize"]),
|
|
"- "
|
|
+ bold("History Pagesize: ")
|
|
+ get_pagesize_str(override_settings["pagesize"]),
|
|
]
|
|
override_str = "\n".join(override_str)
|
|
|
|
e = await _config(ctx)
|
|
e.title += ": User Overrides"
|
|
e.description = (
|
|
"""
|
|
Use the buttons below to manage your user overrides.
|
|
These settings will override the relevant guild settings.\n
|
|
"""
|
|
+ override_str
|
|
)
|
|
return e
|
|
|
|
|
|
async def guild_embed(ctx: commands.Context) -> Embed:
|
|
"""Generates a configuration menu field value for a guild's settings."""
|
|
|
|
guild_settings = {
|
|
"show_moderator": await config.guild(ctx.guild).show_moderator(),
|
|
"use_discord_permissions": await config.guild(
|
|
ctx.guild
|
|
).use_discord_permissions(),
|
|
"ignore_modlog": await config.guild(ctx.guild).ignore_modlog(),
|
|
"ignore_other_bots": await config.guild(ctx.guild).ignore_other_bots(),
|
|
"dm_users": await config.guild(ctx.guild).dm_users(),
|
|
"log_channel": await config.guild(ctx.guild).log_channel(),
|
|
"history_ephemeral": await config.guild(ctx.guild).history_ephemeral(),
|
|
"history_inline": await config.guild(ctx.guild).history_inline(),
|
|
"history_pagesize": await config.guild(ctx.guild).history_pagesize(),
|
|
"history_inline_pagesize": await config.guild(
|
|
ctx.guild
|
|
).history_inline_pagesize(),
|
|
"auto_evidenceformat": await config.guild(ctx.guild).auto_evidenceformat(),
|
|
"respect_hierarchy": await config.guild(ctx.guild).respect_hierarchy(),
|
|
}
|
|
|
|
channel = ctx.guild.get_channel(guild_settings["log_channel"])
|
|
if channel is None:
|
|
channel = warning("Not Set")
|
|
else:
|
|
channel = channel.mention
|
|
|
|
guild_str = [
|
|
"- "
|
|
+ bold("Show Moderator: ")
|
|
+ get_bool_emoji(guild_settings["show_moderator"]),
|
|
"- "
|
|
+ bold("Use Discord Permissions: ")
|
|
+ get_bool_emoji(guild_settings["use_discord_permissions"]),
|
|
"- "
|
|
+ bold("Respect Hierarchy: ")
|
|
+ get_bool_emoji(guild_settings["respect_hierarchy"]),
|
|
"- "
|
|
+ bold("Ignore Modlog: ")
|
|
+ get_bool_emoji(guild_settings["ignore_modlog"]),
|
|
"- "
|
|
+ bold("Ignore Other Bots: ")
|
|
+ get_bool_emoji(guild_settings["ignore_other_bots"]),
|
|
"- " + bold("DM Users: ") + get_bool_emoji(guild_settings["dm_users"]),
|
|
"- "
|
|
+ bold("Auto Evidence Format: ")
|
|
+ get_bool_emoji(guild_settings["auto_evidenceformat"]),
|
|
"- "
|
|
+ bold("Ephemeral: ")
|
|
+ get_bool_emoji(guild_settings["history_ephemeral"]),
|
|
"- "
|
|
+ bold("History Inline: ")
|
|
+ get_bool_emoji(guild_settings["history_inline"]),
|
|
"- "
|
|
+ bold("History Pagesize: ")
|
|
+ get_pagesize_str(guild_settings["history_pagesize"]),
|
|
"- "
|
|
+ bold("History Inline Pagesize: ")
|
|
+ get_pagesize_str(guild_settings["history_inline_pagesize"]),
|
|
"- " + bold("Log Channel: ") + channel,
|
|
]
|
|
guild_str = "\n".join(guild_str)
|
|
|
|
e = await _config(ctx)
|
|
e.title += ": Server Configuration"
|
|
e.description = (
|
|
"""
|
|
Use the buttons below to manage Aurora's server configuration.\n
|
|
"""
|
|
+ guild_str
|
|
)
|
|
return e
|
|
|
|
|
|
async def addrole_embed(ctx: commands.Context) -> Embed:
|
|
"""Generates a configuration menu field value for a guild's addrole whitelist."""
|
|
|
|
roles = []
|
|
async with config.guild(ctx.guild).addrole_whitelist() as whitelist:
|
|
for role in whitelist:
|
|
evalulated_role = ctx.guild.get_role(role) or error(f"`{role}` (Not Found)")
|
|
if isinstance(evalulated_role, Role):
|
|
roles.append({
|
|
"id": evalulated_role.id,
|
|
"mention": evalulated_role.mention,
|
|
"position": evalulated_role.position
|
|
})
|
|
else:
|
|
roles.append({
|
|
"id": role,
|
|
"mention": error(f"`{role}` (Not Found)"),
|
|
"position": 0
|
|
})
|
|
|
|
if roles:
|
|
roles = sorted(roles, key=lambda x: x["position"], reverse=True)
|
|
roles = [role["mention"] for role in roles]
|
|
whitelist_str = "\n".join(roles)
|
|
else:
|
|
whitelist_str = warning("No roles are on the addrole whitelist!")
|
|
|
|
e = await _config(ctx)
|
|
e.title += ": Addrole Whitelist"
|
|
e.description = (
|
|
"Use the select menu below to manage this guild's addrole whitelist."
|
|
)
|
|
|
|
if len(whitelist_str) > 4000 and len(whitelist_str) < 5000:
|
|
lines = whitelist_str.split("\n")
|
|
chunks = []
|
|
chunk = ""
|
|
for line in lines:
|
|
if len(chunk) + len(line) > 1024:
|
|
chunks.append(chunk)
|
|
chunk = line
|
|
else:
|
|
chunk += "\n" + line if chunk else line
|
|
chunks.append(chunk)
|
|
|
|
for chunk in chunks:
|
|
e.add_field(name="", value=chunk)
|
|
else:
|
|
e.description += "\n\n" + whitelist_str
|
|
|
|
return e
|
|
|
|
|
|
async def immune_embed(ctx: commands.Context) -> Embed:
|
|
"""Generates a configuration menu embed for a guild's immune roles."""
|
|
|
|
roles = []
|
|
async with config.guild(ctx.guild).immune_roles() as immune_roles:
|
|
for role in immune_roles:
|
|
evalulated_role = ctx.guild.get_role(role) or error(f"`{role}` (Not Found)")
|
|
if isinstance(evalulated_role, Role):
|
|
roles.append({
|
|
"id": evalulated_role.id,
|
|
"mention": evalulated_role.mention,
|
|
"position": evalulated_role.position
|
|
})
|
|
else:
|
|
roles.append({
|
|
"id": role,
|
|
"mention": error(f"`{role}` (Not Found)"),
|
|
"position": 0
|
|
})
|
|
|
|
if roles:
|
|
roles = sorted(roles, key=lambda x: x["position"], reverse=True)
|
|
roles = [role["mention"] for role in roles]
|
|
immune_str = "\n".join(roles)
|
|
else:
|
|
immune_str = warning("No roles are set as immune roles!")
|
|
|
|
e = await _config(ctx)
|
|
e.title += ": Immune Roles"
|
|
e.description = "Use the select menu below to manage this guild's immune roles."
|
|
|
|
if len(immune_str) > 4000 and len(immune_str) < 5000:
|
|
lines = immune_str.split("\n")
|
|
chunks = []
|
|
chunk = ""
|
|
for line in lines:
|
|
if len(chunk) + len(line) > 1024:
|
|
chunks.append(chunk)
|
|
chunk = line
|
|
else:
|
|
chunk += "\n" + line if chunk else line
|
|
chunks.append(chunk)
|
|
|
|
for chunk in chunks:
|
|
e.add_field(name="", value=chunk)
|
|
else:
|
|
e.description += "\n\n" + immune_str
|
|
|
|
return e
|
|
|
|
async def type_embed(ctx: commands.Context, moderation_type = Type) -> Embed:
|
|
"""Generates a configuration menu field value for a guild's settings."""
|
|
|
|
type_settings = {
|
|
"show_in_history": await config.custom("type", ctx.guild.id, moderation_type.key).show_in_history(),
|
|
"show_moderator": await config.custom("type", ctx.guild.id, moderation_type.key).show_moderator(),
|
|
"use_discord_permissions": await config.custom("type", ctx.guild.id, moderation_type.key).use_discord_permissions(),
|
|
"dm_users": await config.custom("type", ctx.guild.id, moderation_type.key).dm_users(),
|
|
}
|
|
|
|
guild_str = [
|
|
"- "
|
|
+ bold("Show in History: ")
|
|
+ get_bool_emoji(type_settings["show_in_history"]),
|
|
"- "
|
|
+ bold("Show Moderator: ")
|
|
+ get_bool_emoji(type_settings["show_moderator"]),
|
|
"- "
|
|
+ bold("Use Discord Permissions: ")
|
|
+ get_bool_emoji(type_settings["use_discord_permissions"]),
|
|
"- "
|
|
+ bold("DM Users: ")
|
|
+ get_bool_emoji(type_settings["dm_users"]),
|
|
]
|
|
guild_str = "\n".join(guild_str)
|
|
|
|
e = await _config(ctx)
|
|
e.title += ": Server Configuration"
|
|
e.description = (
|
|
"""
|
|
Use the buttons below to manage Aurora's server configuration.\n
|
|
"""
|
|
+ guild_str
|
|
)
|
|
return e
|