Ejemplo n.º 1
0
class Mute(commands.Cog):
    """Auto Delete Messages in a specified channel"""
    def __init__(self, bot):
        self.bot: dataclass.Bot = bot

        self.emoji = bot.emoji_list

        self.guild_data = {}

        self.bot.add_listener(self.on_member_join)

        # i know i could use tasks, however i dont want to use interval scheduling, due to the
        # chance it can fail if not checked second by second
        self.scheduler: AsyncIOScheduler = AsyncIOScheduler(
        )  # the task scheduler

    async def setup(self):
        log.debug("Starting scheduler...")
        self.scheduler.start()
        await self.cache_and_schedule()
        log.debug(
            f"Started scheduler with {len(self.scheduler.get_jobs())} jobs")

    def cog_unload(self):
        log.debug("Shutting down scheduler")
        self.scheduler.shutdown(wait=False)

    async def cache_and_schedule(self):
        """Caches guild data and schedules auto-un-mutes"""
        keys = await self.bot.redis.keys("member||*")
        for key in keys:
            data = await self.bot.redis.get(key)
            raw_data = json.loads(data.decode())
            await self._schedule_job(raw_data)

    async def _schedule_job(self, user_data: dict):
        """Schedules a job based on user_data passed"""
        try:
            job_id = f"{user_data.get('guild_id')}|{user_data.get('user_id')}"
            run_time: datetime = user_data.get("unmute_time")

            if isinstance(run_time, str):
                run_time = datetime.strptime(run_time, "%Y-%m-%d %H:%M:%S.%f")

            if run_time is not None and run_time <= datetime.utcnow():
                # bumps events in the past a few seconds into the future
                # to handle events that should have occurred while the bot
                # was offline
                run_time = datetime.utcnow() + timedelta(seconds=10)

            job = self.scheduler.get_job(job_id)

            if run_time is not None:
                # the trigger for an job
                trigger = cron.CronTrigger(
                    year=run_time.year,
                    month=run_time.month,
                    day=run_time.day,
                    hour=run_time.hour,
                    minute=run_time.minute,
                    second=run_time.second,
                    timezone=pytz.utc,
                )

                if job:
                    job.reschedule(trigger=trigger)
                    log.debug(f"Unmute job rescheduled for {run_time.ctime()}")
                else:
                    self.scheduler.add_job(
                        func=self.auto_unmute,
                        kwargs={
                            "user_id": user_data.get("user_id"),
                            "guild_id": user_data.get("guild_id")
                        },
                        trigger=trigger,
                        id=job_id,
                        name=f"Auto-unmute job for {job_id}",
                    )
                    log.debug(f"Unmute job scheduled for {run_time.ctime()}")
            else:
                # runtime is none, delete the job
                if job:
                    self.scheduler.remove_job(job_id)
                    log.debug(f"Job deleted due to empty run_time {job_id}")
        except Exception as e:
            log.error("Error scheduling job:\n" + "".join(
                traceback.format_exception(type(e), e, e.__traceback__)))

    async def auto_unmute(self, user_id, guild_id):
        """Called at a set time to automatically unmute a user"""
        try:
            log.debug(f"Running unmute task for {guild_id}/{user_id}")

            # grab db data to check the user is *actually* due for un-muting
            guild: discord.Guild = self.bot.get_guild(int(guild_id))
            user: discord.Member = guild.get_member(int(user_id))

            if not guild or not user:
                return

            user_data = await self.bot.get_member_data(guild_id, user.id)

            # check if user is still muted
            if not user_data.muted:
                # user has been un-muted already
                return

            unmute_time = user_data.unmute_time
            if not (unmute_time.hour <= datetime.utcnow().hour
                    and unmute_time.minute <= datetime.utcnow().minute):
                # the job has run at the wrong time
                # this can occur if the user was re-muted for a new time, but for some reason _schedule_job didnt reschedule it
                job = self.scheduler.get_job(
                    f"{user_data.guild_id}|{user_data.user_id}")
                if job:
                    run_time = user_data.unmute_time
                    if isinstance(run_time, str):
                        run_time = datetime.strptime(user_data.unmute_time,
                                                     "%Y-%m-%d %H:%M:%S.%f")
                    trigger = cron.CronTrigger(
                        year=run_time.year,
                        month=run_time.month,
                        day=run_time.day,
                        hour=run_time.hour,
                        minute=run_time.minute,
                        second=run_time.second,
                        timezone=pytz.utc,
                    )
                    job.reschedule(trigger)
                    return log.debug(
                        f"Unmute job rescheduled for {run_time.ctime()}")
                return

            # actually unmute
            await user.remove_roles(await self.get_mute_role(guild))

            # remove from db
            user_data.unmute_time = None
            user_data.muted = False
            await self.bot.redis.set(user_data.key, user_data.to_json())

            me = guild.get_member(self.bot.user.id)
            await self.bot.paladinEvents.add_item(
                Action(
                    actionType=ModActions.unmute,
                    moderator=me,
                    guild=guild,
                    user=user,
                    reason=
                    f"AUTOMATIC ACTION: \nMute scheduled to be removed at `{unmute_time.ctime()}` (UTC)",
                ))
        except Exception as e:
            log.error("Error un-muting:\n" + "".join(
                traceback.format_exception(type(e), e, e.__traceback__)))

    async def write_user_to_db(self,
                               user: discord.Member,
                               muted: bool,
                               mute_time: typing.Optional[datetime] = None):
        """Write a users mute status to the database"""

        user_data = await self.bot.get_member_data(guild_id=user.guild.id,
                                                   user_id=user.id)
        user_data.muted = muted
        user_data.unmute_time = mute_time

        await self.bot.redis.set(user_data.key, user_data.to_json())

        await self._schedule_job(user_data.__dict__)

    async def get_mute_role(
            self, guild: discord.Guild) -> typing.Optional[discord.Role]:
        """Gets the mute role for a specified guild"""
        guild_data = await self.bot.get_guild_data(guild.id)
        if guild_data is None or guild_data.role_mute_id is None:
            return None

        role = guild.get_role(int(guild_data.role_mute_id))
        return role

    # region: commands

    @cog_ext.cog_subcommand(**jsonManager.getDecorator("setrole.mute.user"))
    # @commands.max_concurrency(1, BucketType.guild, wait=False)
    async def set_mute_role(self, ctx: SlashContext, role: discord.Role):
        await ctx.defer()

        try:
            guild_data = await self.bot.get_guild_data(ctx.guild.id)
            guild_data.role_mute_id = role.id
            await self.bot.redis.set(guild_data.key, guild_data.to_json())
        except Exception as e:
            log.error(f"Error setting mute role: {e}")
            return await ctx.send(
                "Failed to set mute role... please try again later")
        await ctx.send(
            f"Your server's mute role has been set to {role.mention}",
            allowed_mentions=discord.AllowedMentions.none())

    @cog_ext.cog_subcommand(**jsonManager.getDecorator("add.mute.user"))
    async def mute(self,
                   ctx: SlashContext,
                   user: discord.Member,
                   time: int,
                   unit: int,
                   reason: str = None):
        # scale up time value to match unit (ie minutes/hours/days
        await ctx.defer(hidden=True)
        # search database for muteRole
        role = await self.get_mute_role(ctx.guild)
        if not role:
            return await ctx.send("Sorry, you haven't set a mute role to use",
                                  hidden=True)

        # handle time
        if time >= 1:
            if unit == 2:
                time *= 60
            elif unit == 3:
                time *= 1440
            mute_time = datetime.utcnow() + timedelta(minutes=time)
        else:
            # mute forever
            mute_time = None
            time = None

        time_fmt = f"for {self.bot.strf_delta(timedelta(minutes=time), show_seconds=False)}" if time else "forever"

        await user.add_roles(
            role,
            reason=
            f"Mute requested by {ctx.author.name}#{ctx.author.discriminator}")
        await self.write_user_to_db(user, muted=True, mute_time=mute_time)

        await ctx.send(f"Muted {user.mention} {time_fmt}",
                       hidden=True,
                       allowed_mentions=discord.AllowedMentions.none())
        await self.bot.paladinEvents.add_item(
            Action(
                actionType=ModActions.mute,
                moderator=ctx.author,
                guild=ctx.guild,
                user=user,
                reason=reason,
            ))

    @cog_ext.cog_subcommand(**jsonManager.getDecorator("clear.mute.user"))
    async def unmute(self,
                     ctx: SlashContext,
                     user: discord.Member,
                     reason: str = None):
        # search database for muteRole id
        role = await self.get_mute_role(ctx.guild)
        if not role:
            return await ctx.send("Sorry, you haven't set a mute role to use",
                                  hidden=True)

        await user.remove_roles(role)

        await ctx.send(f"{user.mention} is no longer muted",
                       hidden=True,
                       allowed_mentions=discord.AllowedMentions.none())
        await self.write_user_to_db(user, muted=False, mute_time=None)

        await self.bot.paladinEvents.add_item(
            Action(
                actionType=ModActions.unmute,
                moderator=ctx.author,
                guild=ctx.guild,
                user=user,
                reason=reason,
            ))

    @unmute.error
    @mute.error
    async def mute_error(self, ctx: SlashContext, error):
        me: discord.Member = ctx.guild.get_member(self.bot.user.id)
        perms: discord.Permissions = me.guild_permissions
        if isinstance(error, discord.errors.Forbidden) and perms.manage_roles:
            await ctx.send(
                "I'm too low in the role hierarchy to add that role,\n"
                "please move my role above any roles you want me to add",
                hidden=True,
            )
        elif isinstance(error, commands.CheckFailure):
            await ctx.send(
                "Sorry you are missing permissions. You need one of the following:\n"
                "- `manage_messages`\n- `manage_roles`\n- `mute_members`")
        else:
            log.error("".join(
                traceback.format_exception(type(error), error,
                                           error.__traceback__)))
            await ctx.send(
                "An error occurred executing that command... please try again later",
                hidden=True)

    @set_mute_role.error
    async def role_error(self, ctx: SlashContext, error):
        if isinstance(error, commands.MaxConcurrencyReached):
            await ctx.send(
                "Hang on, another user in your server is updating your server's settings",
                hidden=True)
        elif isinstance(error, commands.CheckFailure):
            await ctx.send(
                "Sorry you are missing permissions. You need one of the following:\n"
                "- `manage_messages`\n- `manage_roles`\n- `mute_members`")
        else:
            log.error(error)
            await ctx.send(
                "An error occurred executing that command... please try again later",
                hidden=True)

    # endregion

    async def on_member_join(self, member: typing.Union[discord.Member,
                                                        discord.User]):
        """Automatically mute a user if they try and bypass the mute"""
        user_data = await self.bot.get_member_data(member.guild.id, member.id)
        if user_data:
            if user_data.muted:
                # user should be muted, check if mute has expired
                if user_data.unmute_time <= datetime.utcnow():
                    # mute has expired, dont re-add it
                    user_data.muted = False
                    return await self.bot.redis.set(user_data.key,
                                                    user_data.to_json())
                else:
                    reason = "AUTOMATIC ACTION: \nRe-applying mute role - user rejoined"
                    role = await self.get_mute_role(member.guild)
                    await member.add_roles(role, reason=reason)

                    await self.bot.paladinEvents.add_item(
                        Action(
                            actionType=ModActions.mute,
                            moderator=member.guild.get_member(
                                self.bot.user.id),
                            guild=member.guild,
                            user=member,
                            reason=reason,
                        ))
Ejemplo n.º 2
0
class UserInfo(commands.Cog):
    """Gets information about a user"""
    def __init__(self, bot):
        self.bot: dataclass.Bot = bot

        self.emoji = bot.emoji_list

    # todo: add additional user info commands

    @cog_ext.cog_subcommand(**jsonManager.getDecorator("info.user"))
    async def userInfo(self, ctx: SlashContext,
                       user: typing.Union[discord.Member, discord.User]):
        emb = discord.Embed(colour=shared.new_blurple)
        emb.set_thumbnail(url=user.avatar_url)
        emb.description = ""

        # get db data on user
        user_data = await self.bot.get_member_data(ctx.guild.id, user.id)
        user_perms: discord.Permissions = ctx.author.permissions_in(
            ctx.channel)
        if shared.is_user_moderator(user_perms):
            if user_data:
                if user_data.warnings != 0:
                    warnings = user_data.warnings
                    emb.description += f"{self.emoji['rules']} {warnings} warning{'s' if warnings > 1 else ''}\n"
                if user_data.muted == 1:
                    emb.description += f"{self.emoji['voiceLocked']} Muted\n"

        # names
        emb.add_field(name="ID", value=user.id, inline=False)
        emb.add_field(name="Username",
                      value=f"{user.name} #{user.discriminator}",
                      inline=False)
        if user.display_name != user.name:
            emb.add_field(name="Display name",
                          value=user.display_name,
                          inline=False)
        emb.add_field(
            name="Account Creation Date",
            value=f"{self.bot.formatDate(user.created_at)}\n"
            f"{self.emoji['time']}*{self.bot.strf_delta(datetime.utcnow() - user.created_at)}*",
            inline=False,
        )
        emb.add_field(
            name="Join Date",
            value=f"{self.bot.formatDate(user.joined_at)}\n"
            f"{self.emoji['time']}*{self.bot.strf_delta(datetime.utcnow() - user.joined_at)}*",
            inline=False,
        )
        emb.add_field(name="Highest Role",
                      value=f"{user.top_role.name}",
                      inline=False)

        # user flags
        flags: discord.UserFlags = user.public_flags
        if user.bot:
            emb.description += f"{self.emoji['bot']}{'Verified ' if flags.verified_bot else ''}Bot Account\n"
        if user.id == ctx.guild.owner_id:
            emb.description += f"{self.emoji['settings']}Server Owner\n"
        elif user.guild_permissions.administrator:
            emb.description += f"{self.emoji['settings']}Server Admin\n"
        elif (user.guild_permissions.manage_channels
              or user.guild_permissions.manage_guild
              or user.guild_permissions.manage_roles):
            emb.description += f"{self.emoji['settings']}Server Staff\n"
        elif user.guild_permissions.kick_members or user.guild_permissions.ban_members:
            emb.description += f"{self.emoji['settings']}Server Moderator\n"
        if user.pending:
            emb.description += "⚠ User is pending verification\n"
        if user.premium_since is not None:
            emb.description += f"{self.emoji['spaceship']}Server Booster\n"
        if flags.verified_bot_developer:
            emb.description += f"{self.emoji['bot']} Verified Bot Developer\n"
        if flags.staff:
            emb.description += f"{self.emoji['settings']}Discord Staff\n"
        if flags.partner:
            emb.description += f"{self.emoji['members']}Discord Partner\n"
        if flags.bug_hunter or flags.bug_hunter_level_2:
            emb.description += f"{self.emoji['settingsOverride']}Bug Hunter\n"

        await ctx.send(embed=emb)
Ejemplo n.º 3
0
class AutoDelete(commands.Cog):
    """Auto Delete Messages in a specified channel"""
    def __init__(self, bot):
        self.bot: dataclass.Bot = bot

        self.emoji = bot.emoji_list

        self.events = self.bot.paladinEvents

    async def setup(self):
        # await self.cache_guild_data()
        self.task.start()

    @tasks.loop(minutes=1)
    async def task(self):
        try:
            log.spam("Running delete task...")

            for guild in self.bot.guilds:
                # get guild data and determine if this server is using auto delete
                guild_data = await self.bot.get_guild_data(guild.id)
                if guild_data:
                    auto_del_data = guild_data.auto_delete_data
                    if not auto_del_data:
                        continue

                    for channel_data in auto_del_data:
                        channel: discord.TextChannel = self.bot.get_channel(
                            int(channel_data["channel_id"]))
                        if channel:
                            messages_to_delete = {}
                            async for message in channel.history(
                                    limit=None,
                                    after=datetime.utcnow() -
                                    timedelta(days=14)):
                                message_age = datetime.utcnow(
                                ) - snowflake_time(message.id)
                                # message is younger than 14 days, but older than specified time
                                if message_age.total_seconds() >= int(
                                        channel_data["delete_after"]) * 60:
                                    messages_to_delete[message.id] = message
                                    if len(messages_to_delete) >= 200:
                                        # to avoid api spamming, i only want to delete in batches of 200 at most
                                        break
                            if len(messages_to_delete) != 0:
                                messages_to_delete = list(
                                    messages_to_delete.values())
                                # bulk delete can only take 100 messages at a time,
                                # so we chunk the deletions into batches of 100 or less
                                message_chunk = []
                                for i, msg in enumerate(messages_to_delete):
                                    message_chunk.append(msg)
                                    if len(message_chunk) == 100 or i == len(
                                            messages_to_delete) - 1:
                                        await channel.delete_messages(
                                            message_chunk)
                                        message_chunk = []
                                log.spam(
                                    f"Deleted {len(messages_to_delete)} messages"
                                )

                await asyncio.sleep(0)
        except Exception as e:
            log.error("".join(
                traceback.format_exception(type(e), e, e.__traceback__)))

    @cog_ext.cog_subcommand(**jsonManager.getDecorator("disable.autodelete"))
    async def disable_cmd(self,
                          ctx: SlashContext,
                          channel: discord.TextChannel = None):
        await ctx.defer(hidden=True)

        if channel is None:
            channel = ctx.channel
        guild_data = await self.bot.get_guild_data(ctx.guild.id)

        if guild_data is None:
            return await ctx.send(
                f"I'm not auto-deleting messages in {channel.mention}",
                hidden=True)

        auto_del_data: list = guild_data.auto_delete_data

        if guild_data is None or str(channel.id) not in str(auto_del_data):
            return await ctx.send(
                f"I'm not auto-deleting messages in {channel.mention}",
                hidden=True)

        index = next((index for (index, d) in enumerate(auto_del_data)
                      if str(d["channel_id"]) == str(channel.id)), None)

        del auto_del_data[index]

        guild_data.auto_delete_data = auto_del_data
        await self.bot.redis.set(guild_data.key, guild_data.to_json())

        await ctx.send(
            f"Got it, auto-deletion has been disabled in {channel.mention}",
            hidden=True)

    @cog_ext.cog_subcommand(**jsonManager.getDecorator("setup.autodelete"))
    async def setup_cmd(self,
                        ctx: SlashContext,
                        time: int,
                        unit: int,
                        channel: discord.TextChannel = None):
        await ctx.defer(hidden=True)

        # sanity check

        if channel is None:
            channel = ctx.channel
        if not isinstance(channel, discord.TextChannel):
            return await ctx.send("Only text channels are supported")

        if time < 1:
            return await ctx.send("Time must be at least 1 minute",
                                  hidden=True)
        if time >= 1:
            if unit == 2:
                time *= 60
            elif unit == 3:
                time *= 1440

        if time > 20160:
            return await ctx.send(
                "Bots cannot bulk delete messages older than 14 days")

        # get current guild data
        guild_data = await self.bot.get_guild_data(ctx.guild_id)
        if guild_data is not None:
            auto_del_data = guild_data.auto_delete_data
        else:
            auto_del_data = []

        if str(channel.id) in str(auto_del_data):
            # channel already in db, update data instead
            index = next((index for (index, d) in enumerate(auto_del_data)
                          if str(d["channel_id"]) == str(channel.id)), None)
            data: dict = auto_del_data[index]
            data["delete_after"] = time
            auto_del_data[index] = data
        else:
            data: dict = del_channel_template[0].copy()
            data["channel_id"] = str(channel.id)
            data["delete_after"] = time
            auto_del_data.append(data)

        try:
            guild_data.auto_delete_data = auto_del_data
            await self.bot.redis.set(guild_data.key, guild_data.to_json())

            await ctx.send(
                f"New Messages sent in `{channel.name}` will now be deleted after `{time}` minute{'s' if time > 1 else ''}\n"
                f"**Note:** This channel will be checked for messages that match that rule within the a minute",
                hidden=True,
            )
        except Exception as e:
            log.error("Error saving autoDelete data:\n{}".format("".join(
                traceback.format_exception(type(e), e, e.__traceback__))))
            await ctx.send(
                "An error occurred saving that data... please try again later")
        await self.events.add_item("autoDelCache")

    @setup_cmd.error
    @disable_cmd.error
    async def cmd_error(self, ctx, error):
        if isinstance(error, commands.MaxConcurrencyReached):
            await ctx.send(
                "Hang on, another user in your server is updating your server's settings",
                hidden=True)
        elif isinstance(error, commands.CheckFailure):
            await ctx.send(
                "Sorry you need `manage_messages` to use that command",
                hidden=True)
        else:
            log.error("".join(
                traceback.format_exception(type(error), error,
                                           error.__traceback__)))
            await ctx.send(
                "An error occurred executing that command... please try again later",
                hidden=True)
Ejemplo n.º 4
0
class LogAction(commands.Cog):
    """Configuration commands"""
    def __init__(self, bot):
        self.bot: dataclass.Bot = bot

        self.emoji = bot.emoji_list

        self.bot.paladinEvents.subscribe_to_event(self.log_mod_action,
                                                  "modAction")

    async def _get_new_action_id(self, guild: discord.Guild) -> int:
        """Gets an action ID for a new action"""
        keys = await self.bot.redis.keys(f"action||{guild.id}*")
        return len(keys) + 1

    async def _writeActionToDb(
        self,
        guild: discord.Guild,
        modAction: ModActions,
        moderator: discord.Member,
        reason: str,
        message: discord.Message,
        actionID: typing.Optional[int] = None,
        user: typing.Optional[discord.Member] = None,
        role: typing.Optional[discord.Role] = None,
    ):
        """Writes an action to the database"""
        try:
            if actionID is None:
                actionID = await self._get_new_action_id(guild)

            reason = json.dumps(reason)
            reason = base64.b64encode(reason.encode()).decode("utf-8")

            obj = dataclass.ModAction(guild.id, actionID, modAction,
                                      moderator.id)
            obj.reason = reason
            obj.message_id = message.id
            obj.channel_id = message.channel.id
            obj.user_id = user.id if user else None
            obj.role_id = role.id if role else None

            await self.bot.redis.set(obj.key, obj.to_json())
        except Exception as e:
            log.error(e)

    async def log_mod_action(self, action: Action):
        """Logs a moderation action"""
        guild_data = await self.bot.get_guild_data(guild_id=action.guild.id)

        if guild_data:
            if guild_data.channel_action_log_id is None:
                return
            channel: discord.TextChannel = self.bot.get_channel(
                int(guild_data.channel_action_log_id))
            if not channel:
                return
        else:
            return

        emb = discord.Embed(colour=new_blurple)

        token = await self._get_new_action_id(action.guild)

        # todo: replace this ugliness with a match statement when 3.10 is fully released
        if action.action_type == ModActions.kick or action.action_type == ModActions.ban:
            emb = self.fmt_kick(action, emb)

        elif action.action_type == ModActions.roleGive or action.action_type == ModActions.roleRem:
            emb = self.fmt_role(action, emb)

        elif action.action_type == ModActions.purge:
            emb = self.fmt_purge(action, emb)

        elif action.action_type == ModActions.warn:
            emb = self.fmt_warn(action, emb)

        elif action.action_type == ModActions.mute:
            emb = self.fmt_mute(action, emb)

        elif action.action_type == ModActions.unmute:
            emb = self.fmt_unmute(action, emb)

        else:
            log.warning(
                f"Unhandled action: {ModActions(action.action_type).name}")
            emb.title = f"Uncaught event: {action.action_type}"
            emb.description = action.__repr__()

        # Add generic data to embed
        emb.add_field(
            name="Moderator",
            value=
            f"{action.moderator.name} #{action.moderator.discriminator} ({action.moderator.mention})",
            inline=False,
        )
        reason = action.reason if action.reason is not None else f"**Moderator:** Please use `/reason {token}`"
        emb.add_field(
            name="Reason",
            value=reason,
            inline=False,
        )

        msg = await channel.send(
            embed=emb, allowed_mentions=discord.AllowedMentions.none())
        await self._writeActionToDb(
            guild=action.guild,
            actionID=token,
            modAction=action.action_type,
            moderator=action.moderator,
            reason=reason,
            message=msg,
            user=action.user,
            role=action.extra
            if isinstance(action.extra, discord.Role) else None,
        )

    # region: formatters

    def fmt_kick(self, action, emb):
        emb.title = (f"{self.emoji['banned']} User Banned"
                     if action == ModActions.ban else
                     f"{self.emoji['MemberRemove']} User Kicked")
        emb.add_field(
            name="User",
            value=
            f"{action.user.name} #{action.user.discriminator} ({action.user.mention})",
            inline=False,
        )
        return emb

    def fmt_role(self, action, emb):
        emb.title = f"{self.emoji['members']} Role {'Given' if action == ModActions.roleGive else 'Removed'}"
        emb.add_field(
            name="User",
            value=
            f"{action.user.name} #{action.user.discriminator} ({action.user.mention})",
            inline=False,
        )
        emb.add_field(name="Role", value=action.extra.name, inline=False)
        return emb

    def fmt_warn(self, action, emb):
        if "Cleared" in str(action.extra):
            emb.title = f"{self.emoji['rules']} User Warnings Cleared"
        else:
            emb.title = f"{self.emoji['rules']} User Warned"

        warnings = action.extra
        emb.add_field(
            name="User",
            value=
            f"{action.user.name} #{action.user.discriminator} ({action.user.mention})",
            inline=False,
        )
        emb.add_field(name="Warnings", value=warnings, inline=False)
        return emb

    def fmt_purge(self, action, emb):
        actChannel: discord.TextChannel = action.extra
        emb.title = f"{self.emoji['deleted']} Channel Purged"
        emb.add_field(name="Channel", value=actChannel.mention, inline=False)
        return emb

    def fmt_mute(self, action, emb):
        emb.title = f"{self.emoji['voiceLocked']} User Muted"
        emb.add_field(
            name="User",
            value=
            f"{action.user.name} #{action.user.discriminator} ({action.user.mention})",
            inline=False,
        )
        return emb

    def fmt_unmute(self, action, emb):
        emb.title = f"{self.emoji['voice']} User Un-Muted"
        emb.add_field(
            name="User",
            value=
            f"{action.user.name} #{action.user.discriminator} ({action.user.mention})",
            inline=False,
        )
        return emb

    # endregion: formattersguildID

    @cog_ext.cog_slash(**jsonManager.getDecorator("reason"))
    async def reason_cmd(self, ctx: SlashContext, id, reason):
        await ctx.defer(hidden=True)
        action_data = await self.bot.get_action_data(ctx.guild_id, id)
        if action_data is None:
            return await ctx.send("No action exists with that ID")

        if str(ctx.author.id) != str(action_data.moderator_id):
            return await ctx.send(
                "You are not the person who performed that action",
                hidden=True)

        chnl = ctx.guild.get_channel(int(action_data.channel_id))

        # update value in db
        db_reason = json.dumps(reason)
        db_reason = base64.b64encode(db_reason.encode()).decode("utf-8")
        action_data.reason = db_reason
        await self.bot.redis.set(action_data.key, action_data.to_json())

        # try to update message in discord
        message: discord.Message = await self.bot.getMessage(
            channel=chnl, messageID=int(action_data.message_id))

        if message:
            original_embed = message.embeds[0]
            for i in range(len(original_embed.fields)):
                field = original_embed.fields[i]
                if field.name.startswith("Reason"):
                    original_embed.remove_field(i)
            original_embed.add_field(name="Action ID",
                                     value=str(id),
                                     inline=False)
            original_embed.add_field(name="Reason", value=reason, inline=False)
            await message.edit(embed=original_embed)
        await ctx.send(f"Your reason has been stored for action #{id}")

    @cog_ext.cog_subcommand(
        base="set-channel",
        name="action",
        description="Set the channel for action logging",
        options=[
            manage_commands.create_option(name="channel",
                                          option_type=7,
                                          description="The channel to send to",
                                          required=True)
        ],
        base_default_permission=False,
    )
    async def _set_channel(self, ctx: SlashContext,
                           channel: discord.TextChannel):
        if not isinstance(channel, discord.TextChannel):
            return await ctx.send(
                "Sorry, logs can only be sent to a text channel")

        await ctx.defer(hidden=True)

        guild_data = await self.bot.get_guild_data(ctx.guild_id)
        guild_data.channel_action_log_id = channel.id

        await self.bot.redis.set(guild_data.key, guild_data.to_json())

        await ctx.send(f"Set action log channel to {channel.mention}",
                       hidden=True)

    @cog_ext.cog_subcommand(
        base="clear-channel",
        name="action",
        description="Clear the set channel for action logs (disables it)",
        base_default_permission=False,
    )
    async def _clear_channel(self, ctx):
        await ctx.defer()
        guild_data = await self.bot.get_guild_data(ctx.guild_id)
        guild_data.channel_action_log_id = None

        await self.bot.redis.set(guild_data.key, guild_data.to_json())

        await ctx.send(f"Disabled action logging")
Ejemplo n.º 5
0
class UserWarnings(commands.Cog):
    """Configuration commands"""
    def __init__(self, bot: dataclass.Bot):
        self.bot: dataclass.Bot = bot

        self.slash = bot.slash

        self.emoji = bot.emoji_list

    @cog_ext.cog_subcommand(**jsonManager.getDecorator("add.warn.user"))
    async def warnCMD(
        self,
        ctx: SlashContext,
        user: typing.Union[discord.User, discord.Member],
        reason: str = None,
    ):
        """Warns a user, 3 warnings and the user will be kicked"""
        await ctx.defer()
        user_data = await self.bot.get_member_data(ctx.guild_id, user.id)
        warning_num = int(user_data.warnings) + 1

        embed = discord.Embed(
            title=f"Warning for {user.name} #{user.discriminator}",
            color=0xE7C30D)
        embed.add_field(
            name="Warning LVL.1",
            value=":white_check_mark:" if warning_num >= 1 else ":x:",
            inline=False,
        )
        embed.add_field(
            name="Warning LVL.2",
            value=":white_check_mark:" if warning_num >= 2 else ":x:",
            inline=False,
        )
        embed.add_field(
            name="Warning LVL.3",
            value=":white_check_mark:" if warning_num >= 3 else ":x:",
            inline=False,
        )
        embed.set_footer(
            text=f"Warned by {ctx.author.name}#{ctx.author.discriminator}",
            icon_url=ctx.author.avatar_url,
        )
        if warning_num >= 3:
            embed.colour = discord.Colour.red()
            action = None
            autoReason = None
            if warning_num == 4:
                action = ModActions.kick
                autoReason = "Auto: 4th warning"
                embed.description = "User has 4 warnings, they have been kicked"
                await user.kick(reason=autoReason)
            elif warning_num >= 5:
                action = ModActions.ban
                autoReason = f"Auto: {warning_num}th warning"
                embed.description = "User has 5 warnings, they have been banned"
                await ctx.guild.ban(user,
                                    reason=autoReason,
                                    delete_message_days=0)
            if action and autoReason:
                await self.bot.paladinEvents.add_item(
                    Action(
                        actionType=action,
                        moderator=ctx.author,
                        guild=ctx.guild,
                        user=user,
                        reason=autoReason,
                    ))
        await ctx.send(embed=embed)

        await self.bot.paladinEvents.add_item(
            Action(
                actionType=ModActions.warn,
                moderator=ctx.author,
                guild=ctx.guild,
                user=user,
                extra=warning_num,
                reason=reason,
            ))

        user_data.warnings = warning_num
        await self.bot.redis.set(user_data.key, user_data.to_json())

    @cog_ext.cog_subcommand(**jsonManager.getDecorator("clear.warn.user"))
    async def warnClearCMD(
        self,
        ctx: SlashContext,
        user: typing.Union[discord.User, discord.Member],
        reason: str = None,
    ):
        await ctx.defer()

        try:
            user_data = await self.bot.get_member_data(ctx.guild_id, user.id)
            user_data.warnings = 0
            await self.bot.redis.set(user_data.key, user_data.to_json())

            await ctx.send(
                f"Cleared warnings for {user.name} #{user.discriminator}")
        except Exception as e:
            log.error(f"Error clearing warnings: {e}")
            await ctx.send("Unable to clear warnings... please try again later"
                           )
        else:
            await self.bot.paladinEvents.add_item(
                Action(
                    actionType=ModActions.warn,
                    moderator=ctx.author,
                    guild=ctx.guild,
                    user=user,
                    extra="Cleared to 0",
                    reason=reason,
                ))