import logging from datetime import datetime from typing import Optional import discord from discord.ext import commands from tuxbot.cogs.Mod.functions.converters import ( RuleConverter, RuleIDConverter, BotMessageConverter, ReasonConverter, ) from tuxbot.cogs.Mod.functions.exceptions import ( RuleTooLongException, UnknownRuleException, NonMessageException, NonBotMessageException, ReasonTooLongException, ) from tuxbot.cogs.Mod.functions.utils import ( save_lang, get_server_rules, format_rule, get_most_recent_server_rules, paginate_server_rules, get_mute_role, create_mute_role, ) from tuxbot.cogs.Mod.models.rules import Rule from tuxbot.core.utils import checks from tuxbot.core.bot import Tux from tuxbot.core.i18n import ( Translator, find_locale, get_locale_name, list_locales, ) from tuxbot.core.utils.functions.extra import ( group_extra, ContextPlus, command_extra, ) log = logging.getLogger("tuxbot.cogs.Mod") _ = Translator("Mod", __file__) class Mod(commands.Cog): def __init__(self, bot: Tux): self.bot = bot async def cog_command_error(self, ctx: ContextPlus, error): if isinstance( error, ( RuleTooLongException, UnknownRuleException, NonMessageException, NonBotMessageException, ReasonTooLongException, ), ): return await ctx.send(_(str(error), ctx, self.bot.config)) # ========================================================================= # ========================================================================= @group_extra(name="lang", aliases=["locale", "langue"], deletable=True) @commands.guild_only() @checks.is_admin() async def _lang(self, ctx: ContextPlus): """Manage lang settings.""" @_lang.command(name="set", aliases=["define", "choice"]) async def _lang_set(self, ctx: ContextPlus, lang: str): try: await save_lang(self.bot, ctx, find_locale(lang.lower())) await ctx.send( _( "Locale changed to {lang} successfully", ctx, self.bot.config, ).format(lang=f"`{get_locale_name(lang).lower()}`") ) except NotImplementedError: await self._lang_list(ctx) @_lang.command(name="list", aliases=["liste", "all", "view"]) async def _lang_list(self, ctx: ContextPlus): e = discord.Embed( title=_("List of available locales: ", ctx, self.bot.config), description=list_locales(), color=0x36393E, ) await ctx.send(embed=e) # ========================================================================= @group_extra( name="rule", aliases=["rules", "regle", "regles"], deletable=False, invoke_without_command=True, ) @commands.guild_only() async def _rule( self, ctx: ContextPlus, rule: RuleIDConverter, members: commands.Greedy[discord.Member], ): rule_row = await Rule.get(server_id=ctx.guild.id, rule_id=rule) message = _( "{}please read the following rule: \n{}", ctx, self.bot.config ) authors = "" for member in members: if member in ctx.message.mentions: authors += f"{member.name}#{member.discriminator}, " else: authors += f"{member.mention}, " await ctx.send(message.format(authors, format_rule(rule_row))) @_rule.command(name="list", aliases=["show", "all"]) async def _rule_list(self, ctx: ContextPlus): rules = await get_server_rules(ctx.guild.id) if not rules: return await ctx.send( _("No rules found for this server", ctx, self.bot.config) ) embed = discord.Embed( title=_("Rules for {}", ctx, self.bot.config).format( ctx.guild.name ), color=discord.Color.blue(), ) embed.set_footer( text=_("Latest change: {}", ctx, self.bot.config).format( get_most_recent_server_rules(rules).updated_at.ctime() ) ) pages = paginate_server_rules(rules) if len(pages) == 1: embed.description = pages[0] await ctx.send(embed=embed) else: for i, page in enumerate(pages): embed.title = _( "Rules for {} ({}/{})", ctx, self.bot.config ).format(ctx.guild.name, str(i + 1), str(len(pages))) embed.description = page await ctx.send(embed=embed) @checks.is_admin() @_rule.command(name="add") async def _rule_add(self, ctx: ContextPlus, *, rule: RuleConverter): rule_row = await Rule() rule_row.server_id = ctx.guild.id rule_row.author_id = ctx.message.author.id rule_row.rule_id = ( len(await get_server_rules(ctx.guild.id)) + 1 # type: ignore ) rule_row.content = str(rule) # type: ignore await rule_row.save() await ctx.send( _("Following rule added: \n{}", ctx, self.bot.config).format( format_rule(rule_row) ) ) @checks.is_admin() @_rule.command(name="edit") async def _rule_edit( self, ctx: ContextPlus, rule: RuleIDConverter, *, content: RuleConverter, ): # noinspection PyTypeChecker rule_row = await Rule.get(server_id=ctx.guild.id, rule_id=rule) rule_row.content = str(content) # type: ignore rule_row.updated_at = datetime.now() # type: ignore await rule_row.save() await ctx.send( _("Following rule updated: \n{}", ctx, self.bot.config).format( format_rule(rule_row) ) ) @checks.is_admin() @_rule.command(name="delete") async def _rule_delete( self, ctx: ContextPlus, rule: RuleIDConverter, ): # noinspection PyTypeChecker rule_row = await Rule.get(server_id=ctx.guild.id, rule_id=rule) await rule_row.delete() await ctx.send( _("Following rule deleted: \n{}", ctx, self.bot.config).format( format_rule(rule_row) ) ) @checks.is_admin() @_rule.command(name="update") async def _rule_update( self, ctx: ContextPlus, message: BotMessageConverter, ): rules = await get_server_rules(ctx.guild.id) if not rules: return await ctx.send( _("No rules found for this server", ctx, self.bot.config) ) embed = discord.Embed( title=_("Rules for {}", ctx, self.bot.config).format( ctx.guild.name ), color=discord.Color.blue(), ) embed.set_footer( text=_("Latest change: {}", ctx, self.bot.config).format( get_most_recent_server_rules(rules).updated_at.ctime() ) ) pages = paginate_server_rules(rules) # noinspection PyTypeChecker to_edit: discord.Message = message if len(pages) == 1: embed.description = pages[0] await to_edit.edit(content="", embed=embed) else: for i, page in enumerate(pages): embed.title = _( "Rules for {} ({}/{})", ctx, self.bot.config ).format(ctx.guild.name, str(i + 1), str(len(pages))) embed.description = page await to_edit.edit(content="", embed=embed) # ========================================================================= @group_extra( name="mute", deletable=True, invoke_without_command=True, ) @commands.guild_only() @checks.is_admin() async def _mute( self, ctx: ContextPlus, members: commands.Greedy[discord.Member], *, reason: Optional[ReasonConverter], ): if not members: return await ctx.send(_("Missing members", ctx, self.bot.config)) role_row = await get_mute_role(ctx.guild.id) if role_row is None: return await ctx.send( _( "No mute role has been specified for this guild", ctx, self.bot.config, ) ) for member in members: await member.add_roles( discord.Object(id=int(role_row.role_id)), reason=reason ) await ctx.send("\N{THUMBS UP SIGN}") @_mute.command(name="show", aliases=["role"]) async def _mute_show( self, ctx: ContextPlus, ): role_row = await get_mute_role(ctx.guild.id) if ( role_row is None or (role := ctx.guild.get_role(int(role_row.role_id))) is None ): return await ctx.send( _( "No mute role has been specified for this guild", ctx, self.bot.config, ) ) muted_members = [m for m in ctx.guild.members if role in m.roles] e = discord.Embed( title=f"Role: {role.name} (ID: {role.id})", color=role.color ) e.add_field(name="Total mute:", value=len(muted_members)) await ctx.send(embed=e) @_mute.command(name="set", aliases=["define"]) async def _mute_set(self, ctx: ContextPlus, role: discord.Role): role_row = await get_mute_role(ctx.guild.id) if role_row is None: await create_mute_role(ctx.guild.id, role.id) else: role_row.role_id = role.id # type: ignore await role_row.save() await ctx.send( _("Mute role successfully defined", ctx, self.bot.config) ) # ========================================================================= @command_extra( name="tempmute", deletable=True, ) @commands.guild_only() @checks.is_admin() async def _tempmute( self, ctx: ContextPlus, time, members: discord.Member, *, reason: Optional[ReasonConverter], ): _, _, _, _ = ctx, time, members, reason # ========================================================================= @command_extra( name="unmute", deletable=True, ) @commands.guild_only() @checks.is_admin() async def _unmute( self, ctx: ContextPlus, members: commands.Greedy[discord.Member], *, reason: Optional[ReasonConverter], ): if not members: return await ctx.send(_("Missing members", ctx, self.bot.config)) role_row = await get_mute_role(ctx.guild.id) if role_row is None: return await ctx.send( _( "No mute role has been specified for this guild", ctx, self.bot.config, ) ) for member in members: await member.remove_roles( discord.Object(id=int(role_row.role_id)), reason=reason ) await ctx.send("\N{THUMBS UP SIGN}")