feat(aurora): starting work on the config rewrite
Some checks failed
Actions / Lint Code (Pylint) (pull_request) Failing after 15s
Actions / Build Documentation (MkDocs) (pull_request) Successful in 12s

This commit is contained in:
Seaswimmer 2024-01-13 15:44:08 +00:00
parent a1b038057e
commit c42b4eca2d
Signed by: cswimr
GPG key ID: D74DDDDF420E13DF
9 changed files with 192 additions and 325 deletions

28
aurora/abc.py Normal file
View file

@ -0,0 +1,28 @@
from abc import ABC
from redbot.core import commands
from redbot.core.bot import Red
from .utilities.config import Config
class CompositeMetaClass(type(commands.Cog), type(ABC)):
"""
This allows the metaclass used for proper type detection to
coexist with discord.py's metaclass
"""
pass
class Mixin(ABC):
"""
Base class for well behaved type hint detection with composite class.
Basically, to keep developers sane when not all attributes are defined in each mixin.
"""
def __init__(self, *_args):
super().__init__()
self.config: Config
self.bot: Red

View file

@ -20,6 +20,8 @@ from redbot.core import app_commands, checks, commands, data_manager
from redbot.core.app_commands import Choice
from redbot.core.utils.chat_formatting import box, error, warning
from .abc import CompositeMetaClass
from .configuration.commands import Configuration
from .importers.galacticbot import ImportGalacticBotView
from .importers.aurora import ImportAuroraView
from .utilities.config import config, register_config
@ -29,7 +31,7 @@ from .utilities.logger import logger
from .utilities.utils import convert_timedelta_to_str, check_moddable, check_permissions, fetch_channel_dict, fetch_user_dict, generate_dict, log, send_evidenceformat
class Aurora(commands.Cog):
class Aurora(Configuration, commands.Cog, metaclass=CompositeMetaClass):
"""Aurora is a fully-featured moderation system.
It is heavily inspired by GalacticBot, and is designed to be a more user-friendly alternative to Red's core Mod cogs.
This cog stores all of its data in an SQLite database."""
@ -1016,330 +1018,6 @@ class Aurora(commands.Cog):
completion_time = (time.time() - current_time) * 1000
logger.debug("Completed expiry loop in %sms with %s users unbanned", f"{completion_time:.6f}", global_num)
#######################################################################################################################
### CONFIGURATION COMMANDS
#######################################################################################################################
@commands.group(autohelp=True, aliases=['moderationset', 'modset', 'moderationsettings', 'aurorasettings', 'auroraconfig'])
async def auroraset(self, ctx: commands.Context):
"""Manage moderation commands."""
@auroraset.command(name='list', aliases=['view', 'show'])
async def auroraset_list(self, ctx: commands.Context):
"""List all moderation settings."""
if ctx.guild:
guild_settings = await config.guild(ctx.guild).all()
guild_settings_string = ""
for setting in guild_settings:
if 'roles' in setting:
continue
if setting == 'log_channel':
channel = ctx.guild.get_channel(guild_settings[setting])
guild_settings_string += f"**{setting}**: {channel.mention}\n" if channel else f"**{setting}**: {guild_settings[setting]}\n"
else:
guild_settings_string += f"**{setting}**: {guild_settings[setting]}\n"
user_settings = await config.user(ctx.author).all()
user_settings_string = ""
for setting in user_settings:
user_settings_string += f"**{setting}**: {user_settings[setting]}\n"
embed = discord.Embed(color=await self.bot.get_embed_color(ctx.channel))
embed.set_author(icon_url=ctx.guild.icon.url, name=f"{ctx.guild.name} Moderation Settings")
if ctx.guild:
embed.add_field(name="Guild Settings", value=guild_settings_string)
embed.add_field(name="User Settings", value=user_settings_string)
await ctx.send(embed=embed)
@auroraset.group(autohelp=True, name='user')
async def auroraset_user(self, ctx: commands.Context):
"""Manage configurations for user configuration options."""
@auroraset_user.command(name='autoevidence')
async def auroraset_user_autoevidence(self, ctx: commands.Context, enabled: bool):
"""Toggle if the evidenceformat codeblock should be sent automatically."""
await config.user(ctx.author).auto_evidenceformat.set(enabled)
await ctx.send(f"Auto evidenceformat setting set to {enabled}")
@auroraset_user.group(autohelp=True, name='history')
async def auroraset_user_history(self, ctx: commands.Context):
"""Manage configuration for the /history command."""
@auroraset_user_history.command(name='ephemeral', aliases=['hidden', 'hide'])
async def auroraset_user_history_ephemeral(self, ctx: commands.Context, enabled: bool):
"""Toggle if the /history command should be ephemeral."""
await config.user(ctx.author).history_ephemeral.set(enabled)
await ctx.send(f"Ephemeral setting set to {enabled}")
@auroraset_user_history.command(name='pagesize')
async def auroraset_user_history_pagesize(self, ctx: commands.Context, pagesize: int):
"""Set the amount of cases to display per page."""
if pagesize > 20:
await ctx.send("Pagesize cannot be greater than 20!")
return
if pagesize < 1:
await ctx.send("Pagesize cannot be less than 1!")
return
await config.user(ctx.author).history_pagesize.set(pagesize)
await ctx.send(f"Pagesize set to {await config.user(ctx.author).history_pagesize()}")
@auroraset_user_history.group(name='inline')
async def auroraset_user_history_inline(self, ctx: commands.Context):
"""Manage configuration for the /history command's inline argument."""
@auroraset_user_history_inline.command(name='toggle')
async def auroraset_user_history_inline_toggle(self, ctx: commands.Context, enabled: bool):
"""Enable the /history command's inline argument by default."""
await config.user(ctx.author).history_inline.set(enabled)
await ctx.send(f"Inline setting set to {enabled}")
@auroraset_user_history_inline.command(name='pagesize')
async def auroraset_user_history_inline_pagesize(self, ctx: commands.Context, pagesize: int):
"""Set the amount of cases to display per page."""
if pagesize > 20:
await ctx.send(error("Pagesize cannot be greater than 20!"))
return
if pagesize < 1:
await ctx.send(error("Pagesize cannot be less than 1!"))
return
await config.user(ctx.author).history_inline_pagesize.set(pagesize)
await ctx.send(f"Inline pagesize set to {await config.user(ctx.author).history_inline_pagesize()}")
@auroraset.group(autohelp=True, name='guild')
@checks.admin()
async def auroraset_guild(self, ctx: commands.Context):
"""Manage default configurations for user configuration options, per guild."""
@auroraset_guild.command(name='autoevidence')
async def auroraset_guild_autoevidence(self, ctx: commands.Context, enabled: bool):
"""Toggle if the evidenceformat codeblock should be sent automatically."""
await config.guild(ctx.guild).auto_evidenceformat.set(enabled)
await ctx.send(f"Auto evidenceformat setting set to {enabled}")
@auroraset_guild.group(autohelp=True, name='history')
@checks.admin()
async def auroraset_guild_history(self, ctx: commands.Context):
"""Manage configuration for the /history command."""
@auroraset_guild_history.command(name='ephemeral', aliases=['hidden', 'hide'])
@checks.admin()
async def auroraset_guild_history_ephemeral(self, ctx: commands.Context, enabled: bool):
"""Toggle if the /history command should be ephemeral."""
await config.guild(ctx.guild).history_ephemeral.set(enabled)
await ctx.send(f"Ephemeral setting set to {enabled}")
@auroraset_guild_history.command(name='pagesize')
@checks.admin()
async def auroraset_guild_history_pagesize(self, ctx: commands.Context, pagesize: int):
"""Set the amount of cases to display per page."""
if pagesize > 20:
await ctx.send("Pagesize cannot be greater than 20!")
return
if pagesize < 1:
await ctx.send("Pagesize cannot be less than 1!")
return
await config.guild(ctx.guild).history_pagesize.set(pagesize)
await ctx.send(f"Pagesize set to {await config.guild(ctx.guild).history_pagesize()}")
@auroraset_guild_history.group(name='inline')
@checks.admin()
async def auroraset_guild_history_inline(self, ctx: commands.Context):
"""Manage configuration for the /history command's inline argument."""
@auroraset_guild_history_inline.command(name='toggle')
@checks.admin()
async def auroraset_guild_history_inline_toggle(self, ctx: commands.Context, enabled: bool):
"""Enable the /history command's inline argument by default."""
await config.guild(ctx.guild).history_inline.set(enabled)
await ctx.send(f"Inline setting set to {enabled}")
@auroraset_guild_history_inline.command(name='pagesize')
@checks.admin()
async def auroraset_guild_history_inline_pagesize(self, ctx: commands.Context, pagesize: int):
"""Set the amount of cases to display per page."""
if pagesize > 20:
await ctx.send("Pagesize cannot be greater than 20!")
return
if pagesize < 1:
await ctx.send("Pagesize cannot be less than 1!")
return
await config.guild(ctx.guild).history_inline_pagesize.set(pagesize)
await ctx.send(f"Inline pagesize set to {await config.guild(ctx.guild).history_inline_pagesize()}")
@auroraset.group(autohelp=True, name='immunity')
@checks.admin()
async def auroraset_immunity(self, ctx: commands.Context):
"""Manage configuration for immune roles."""
@auroraset_immunity.command(name='add')
@checks.admin()
async def auroraset_immunity_add(self, ctx: commands.Context, role: discord.Role):
"""Add a role to the immune roles list."""
immune_roles: list = await config.guild(ctx.guild).immune_roles()
if role.id in immune_roles:
await ctx.send(error("Role is already immune!"))
return
immune_roles.append(role.id)
await config.guild(ctx.guild).immune_roles.set(immune_roles)
await ctx.send(f"Role {role.name} added to immune roles.")
@auroraset_immunity.command(name='remove')
@checks.admin()
async def auroraset_immunity_remove(self, ctx: commands.Context, role: discord.Role):
"""Remove a role from the immune roles list."""
immune_roles: list = await config.guild(ctx.guild).immune_roles()
if role.id not in immune_roles:
await ctx.send(error("Role is not immune!"))
return
immune_roles.remove(role.id)
await config.guild(ctx.guild).immune_roles.set(immune_roles)
await ctx.send(f"Role {role.name} removed from immune roles.")
@auroraset_immunity.command(name='list')
@checks.admin()
async def auroraset_immunity_list(self, ctx: commands.Context):
"""List all immune roles."""
immune_roles: list = await config.guild(ctx.guild).immune_roles()
if not immune_roles:
await ctx.send("No immune roles set!")
return
role_list = ""
for role_id in immune_roles:
role = ctx.guild.get_role(role_id)
if role:
role_list += f"{role.mention}\n"
if role_list:
embed = discord.Embed(title="Immune Roles", description=role_list, color=await self.bot.get_embed_color(ctx.channel))
await ctx.send(embed=embed)
@auroraset.group(autohelp=True, name='blacklist')
@checks.admin()
async def auroraset_blacklist(self, ctx: commands.Context):
"""Manage configuration for the /blacklist command."""
@auroraset_blacklist.command(name='add')
@checks.admin()
async def auroraset_blacklist_add(self, ctx: commands.Context, role: discord.Role, duration: str):
"""Add a role to the blacklist."""
blacklist_roles: list = await config.guild(ctx.guild).blacklist_roles()
for blacklist_role in blacklist_roles:
if role.id == blacklist_role['role']:
await ctx.send(error("Role already has an associated blacklist type!"))
return
try:
parsed_time = parse(sval=duration, as_timedelta=True, raise_exception=True)
except ValueError:
await ctx.send(error("Please provide a valid duration!"))
return
blacklist_roles.append(
{
'role': role.id,
'duration': str(parsed_time)
}
)
await config.guild(ctx.guild).blacklist_roles.set(blacklist_roles)
await ctx.send(f"Role {role.mention} added as a blacklist type.", allowed_mentions=discord.AllowedMentions.none())
@auroraset_blacklist.command(name='remove')
@checks.admin()
async def auroraset_blacklist_remove(self, ctx: commands.Context, role: discord.Role):
"""Remove a role's blacklist type."""
blacklist_roles: list = await config.guild(ctx.guild).blacklist_roles()
for blacklist_role in blacklist_roles:
if role.id == blacklist_role['role']:
blacklist_roles.remove(blacklist_role)
await config.guild(ctx.guild).blacklist_roles.set(blacklist_roles)
await ctx.send(f"Role {role.mention} removed from blacklist types.", allowed_mentions=discord.AllowedMentions.none())
return
await ctx.send(error("Role does not have an associated blacklist type!"))
@auroraset_blacklist.command(name='list')
@checks.admin()
async def auroraset_blacklist_list(self, ctx: commands.Context):
"""List all blacklist types."""
blacklist_roles: list = await config.guild(ctx.guild).blacklist_roles()
if not blacklist_roles:
await ctx.send("No blacklist types set!")
return
blacklist_list = ""
for blacklist_role in blacklist_roles:
role = ctx.guild.get_role(blacklist_role['role'])
if role:
blacklist_list += f"{role.mention} - {blacklist_role['duration']}\n"
if blacklist_list:
embed = discord.Embed(title="Blacklist Types", description=blacklist_list, color=await self.bot.get_embed_color(ctx.channel))
await ctx.send(embed=embed)
@auroraset.command(name="ignorebots")
@checks.admin()
async def auroraset_ignorebots(self, ctx: commands.Context):
"""Toggle if the cog should ignore other bots' moderations."""
await config.guild(ctx.guild).ignore_other_bots.set(not await config.guild(ctx.guild).ignore_other_bots())
await ctx.send(f"Ignore bots setting set to {await config.guild(ctx.guild).ignore_other_bots()}")
@auroraset.command(name="dm")
@checks.admin()
async def auroraset_dm(self, ctx: commands.Context):
"""Toggle automatically messaging moderated users.
This option can be overridden by specifying the `silent` argument in any moderation command."""
await config.guild(ctx.guild).dm_users.set(not await config.guild(ctx.guild).dm_users())
await ctx.send(f"DM users setting set to {await config.guild(ctx.guild).dm_users()}")
@auroraset.command(name="permissions")
@checks.admin()
async def auroraset_permissions(self, ctx: commands.Context):
"""Toggle whether the bot will check for discord permissions."""
await config.guild(ctx.guild).use_discord_permissions.set(not await config.guild(ctx.guild).use_discord_permissions())
await ctx.send(f"Use Discord Permissions setting set to {await config.guild(ctx.guild).use_discord_permissions()}")
@auroraset.command(name="logchannel")
@checks.admin()
async def auroraset_logchannel(self, ctx: commands.Context, channel: discord.TextChannel = None):
"""Set a channel to log infractions to."""
if channel:
await config.guild(ctx.guild).log_channel.set(channel.id)
await ctx.send(f"Logging channel set to {channel.mention}.")
else:
await config.guild(ctx.guild).log_channel.set(" ")
await ctx.send(warning("Logging channel disabled."))
@auroraset.command(name="showmoderator")
@checks.admin()
async def auroraset_showmoderator(self, ctx: commands.Context):
"""Toggle if the cog should show the moderator in the case embed when dming a user."""
await config.guild(ctx.guild).show_moderator.set(not await config.guild(ctx.guild).show_moderator())
await ctx.send(f"Show moderator setting set to {await config.guild(ctx.guild).show_moderator()}")
@auroraset.group(autohelp=True, name='import')
@checks.admin()
async def auroraset_import(self, ctx: commands.Context):
"""Import moderations from other bots."""
@auroraset_import.command(name="aurora")
@checks.admin()
async def auroraset_import_aurora(self, ctx: commands.Context):
"""Import moderations from another bot using Aurora."""
if ctx.message.attachments and ctx.message.attachments[0].content_type == 'application/json; charset=utf-8':
message = await ctx.send(warning("Are you sure you want to import moderations from another bot?\n**This will overwrite any moderations that already exist in this guild's moderation table.**\n*The import process will block the rest of your bot until it is complete.*"))
await message.edit(view=ImportAuroraView(60, ctx, message))
else:
await ctx.send(error("Please provide a valid Aurora export file."))
@auroraset_import.command(name="galacticbot")
@checks.admin()
async def auroraset_import_galacticbot(self, ctx: commands.Context):
"""Import moderations from GalacticBot."""
if ctx.message.attachments and ctx.message.attachments[0].content_type == 'application/json; charset=utf-8':
message = await ctx.send(warning("Are you sure you want to import GalacticBot moderations?\n**This will overwrite any moderations that already exist in this guild's moderation table.**\n*The import process will block the rest of your bot until it is complete.*"))
await message.edit(view=ImportGalacticBotView(60, ctx, message))
else:
await ctx.send(error("Please provide a valid GalacticBot moderation export file."))
@commands.command(aliases=["tdc"])
async def timedeltaconvert(self, ctx: commands.Context, *, duration: str):
"""This command converts a duration to a [`timedelta`](https://docs.python.org/3/library/datetime.html#datetime.timedelta) Python object.

View file

View file

@ -0,0 +1,42 @@
from redbot.core import commands
from redbot.core.utils.chat_formatting import error, warning
from .embed import embed
from ..abc import Mixin
from ..importers.aurora import ImportAuroraView
from ..importers.galacticbot import ImportGalacticBotView
class Configuration(Mixin):
"""Configuration commands for Aurora."""
@commands.guild_only()
@commands.group(autohelp=True, aliases=['moderationset', 'modset', 'moderationsettings', 'aurorasettings', 'auroraconfig'])
async def auroraset(self, ctx: commands.Context):
"""Set Aurora configuration options."""
await ctx.reply(embed=embed(ctx))
@auroraset.group(autohelp=True, name='import')
@commands.admin()
@commands.guild_only()
async def auroraset_import(self, ctx: commands.Context):
"""Import moderations from other bots."""
@auroraset_import.command(name="aurora")
@commands.admin()
async def auroraset_import_aurora(self, ctx: commands.Context):
"""Import moderations from another bot using Aurora."""
if ctx.message.attachments and ctx.message.attachments[0].content_type == 'application/json; charset=utf-8':
message = await ctx.send(warning("Are you sure you want to import moderations from another bot?\n**This will overwrite any moderations that already exist in this guild's moderation table.**\n*The import process will block the rest of your bot until it is complete.*"))
await message.edit(view=ImportAuroraView(60, ctx, message))
else:
await ctx.send(error("Please provide a valid Aurora export file."))
@auroraset_import.command(name="galacticbot")
@commands.admin()
async def auroraset_import_galacticbot(self, ctx: commands.Context):
"""Import moderations from GalacticBot."""
if ctx.message.attachments and ctx.message.attachments[0].content_type == 'application/json; charset=utf-8':
message = await ctx.send(warning("Are you sure you want to import GalacticBot moderations?\n**This will overwrite any moderations that already exist in this guild's moderation table.**\n*The import process will block the rest of your bot until it is complete.*"))
await message.edit(view=ImportGalacticBotView(60, ctx, message))
else:
await ctx.send(error("Please provide a valid GalacticBot moderation export file."))

View file

@ -0,0 +1,111 @@
from typing import Union
from discord import Embed, Guild, Member, User
from redbot.core import commands
from redbot.core.utils.chat_formatting import bold, error, warning
from .utils import get_bool_emoji
from ..utilities.config import config
async def _core(ctx: commands.Context) -> Embed:
"""Generates the core embed for configuration menus to use."""
embed = Embed(
title="Aurora Configuration Menu",
description="Use the buttons below to configure Aurora.",
color=await ctx.embed_color()
)
embed.set_thumbnail(url=ctx.bot.user.avatar_url)
return embed
async def _overrides(user: Union[Member, User]) -> str:
"""Generates a configuration menu field value for a user's overrides."""
override_settings = {
"ephemeral": await config.user(user).history_ephemeral(),
"inline": await config.user(user).history_inline(),
"inline_pagesize": await config.user(user).history_inline_pagesize(),
"pagesize": await config.user(user).history_pagesize(),
"auto_evidenceformat": await config.user(user).auto_evidenceformat()
}
overrides = [
"These settings will override the relevant guild settings.\n", # Add an extra line between the subtitle and the settings
bold("Auto Evidence Format: ") + get_bool_emoji(override_settings['auto_evidenceformat']),
bold("Ephemeral: ") + get_bool_emoji(override_settings['ephemeral']),
bold("Inline: ") + get_bool_emoji(override_settings['inline']),
bold("Inline Pagesize: ") + override_settings['inline_pagesize'] + " cases per page",
bold("Pagesize: ") + override_settings['pagesize'] + " cases per page",
]
overrides = '\n'.join(overrides)
return overrides
async def _guild(guild: Guild) -> str:
"""Generates a configuration menu field value for a guild's settings."""
guild_settings = {
"show_moderator": await config.guild(guild).show_moderator(),
"use_discord_permissions": await config.guild(guild).use_discord_permissions(),
"ignore_modlog": await config.guild(guild).ignore_modlog(),
"ignore_other_bots": await config.guild(guild).ignore_other_bots(),
"dm_users": await config.guild(guild).dm_users(),
"log_channel": await config.guild(guild).log_channel(),
"history_ephemeral": await config.guild(guild).history_ephemeral(),
"history_inline": await config.guild(guild).history_inline(),
"history_pagesize": await config.guild(guild).history_pagesize(),
"history_inline_pagesize": await config.guild(guild).history_inline_pagesize(),
"auto_evidenceformat": await config.guild(guild).auto_evidenceformat(),
}
channel = 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("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("Log Channel: ") + channel,
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: ") + guild_settings['history_pagesize'] + " cases per page",
bold("History Inline Pagesize: ") + guild_settings['history_inline_pagesize'] + " cases per page"
]
guild_str = '\n'.join(guild_str)
return guild_str
async def _blacklist(guild: Guild) -> str:
"""Generates a configuration menu field value for a guild's blacklist."""
blacklist = await config.guild(guild).blacklist_roles()
if blacklist:
blacklist = [guild.get_role(role).mention or error(f"`{role}` (Not Found)") for role in blacklist]
blacklist = '\n'.join(blacklist)
else:
blacklist = warning("No roles are set as blacklist roles!")
return blacklist
async def _immune(guild: Guild) -> str:
"""Generates a configuration menu field value for a guild's immune roles."""
immune = await config.guild(guild).immune_roles()
if immune:
immune = [guild.get_role(role).mention or error(f"`{role}` (Not Found)") for role in immune]
immune = '\n'.join(immune)
else:
immune = warning("No roles are set as immune roles!")
return immune
async def embed(ctx: commands.Context) -> Embed:
"""Generates the configuration embed for a guild."""
embed = await _core(ctx)
embed.add_field(name="User Overrides", value=await _overrides(ctx.author))
if ctx.guild is not None and (ctx.author.guild_permissions.administrator or ctx.author.guild_permissions.manage_guild):
embed.add_field(name="Guild Settings", value=await _guild(ctx.guild))
embed.add_field(name="Blacklist Roles", value=await _blacklist(ctx.guild))
embed.add_field(name="Immune Roles", value=await _immune(ctx.guild))
return embed

View file

View file

View file

View file

@ -0,0 +1,8 @@
def get_bool_emoji(value: bool) -> str:
"""Returns a unicode emoji based on a boolean value."""
if value is True:
return "\N{WHITE HEAVY CHECK MARK}"
if value is False:
return "\N{NO ENTRY SIGN}"
if value is None:
return "\N{BLACK QUESTION MARK ORNAMENT}\N{VARIATION SELECTOR-16}"