Warn code

Pasted Oct 4, 2026, 12:45 PM1 file1,205 lines

Scan to open on your phone

Point a camera at the QR code - it opens this exact paste, no app needed.

https://codepastes.com/zzgt4399qv09x8mt
untitled
import os
import sqlite3
import asyncio
from datetime import datetime, timedelta, timezone

import discord
from discord.ext import commands
from discord import ui
from dotenv import load_dotenv


# ============================================================
# CONFIG
# ============================================================

load_dotenv()

TOKEN = os.getenv("TOKEN")
MOD_LOG_CHANNEL_ID = int(os.getenv("MOD_LOG_CHANNEL_ID", "0"))

WARN_TIMEOUT_AT = int(os.getenv("WARN_TIMEOUT_AT", "3"))
WARN_KICK_AT = int(os.getenv("WARN_KICK_AT", "5"))
WARN_BAN_AT = int(os.getenv("WARN_BAN_AT", "8"))

PREFIX = "."

if not TOKEN:
    raise RuntimeError("TOKEN is missing from .env")


# ============================================================
# INTENTS
# ============================================================

intents = discord.Intents.default()
intents.members = True
intents.message_content = True


bot = commands.Bot(
    command_prefix=PREFIX,
    intents=intents,
    help_command=None
)


# ============================================================
# DATABASE
# ============================================================

db = sqlite3.connect(
    "moderation.db",
    check_same_thread=False
)

db.row_factory = sqlite3.Row

cursor = db.cursor()

cursor.execute("""
CREATE TABLE IF NOT EXISTS warnings (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    guild_id INTEGER NOT NULL,
    user_id INTEGER NOT NULL,
    moderator_id INTEGER NOT NULL,
    reason TEXT NOT NULL,
    evidence TEXT,
    created_at TEXT NOT NULL,
    active INTEGER DEFAULT 1
)
""")

cursor.execute("""
CREATE TABLE IF NOT EXISTS appeals (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    warning_id INTEGER NOT NULL,
    user_id INTEGER NOT NULL,
    reason TEXT NOT NULL,
    created_at TEXT NOT NULL,
    status TEXT DEFAULT 'pending'
)
""")

db.commit()


# ============================================================
# DATABASE HELPERS
# ============================================================

def create_warning(
    guild_id: int,
    user_id: int,
    moderator_id: int,
    reason: str,
    evidence: str | None
):
    now = datetime.now(timezone.utc).isoformat()

    cursor.execute(
        """
        INSERT INTO warnings
        (guild_id, user_id, moderator_id, reason, evidence, created_at)
        VALUES (?, ?, ?, ?, ?, ?)
        """,
        (
            guild_id,
            user_id,
            moderator_id,
            reason,
            evidence,
            now
        )
    )

    db.commit()

    return cursor.lastrowid


def get_warning(warning_id: int):
    cursor.execute(
        """
        SELECT *
        FROM warnings
        WHERE id = ?
        """,
        (warning_id,)
    )

    return cursor.fetchone()


def get_user_warnings(guild_id: int, user_id: int):
    cursor.execute(
        """
        SELECT *
        FROM warnings
        WHERE guild_id = ?
        AND user_id = ?
        AND active = 1
        ORDER BY id DESC
        """,
        (guild_id, user_id)
    )

    return cursor.fetchall()


def get_warning_count(guild_id: int, user_id: int):
    cursor.execute(
        """
        SELECT COUNT(*)
        FROM warnings
        WHERE guild_id = ?
        AND user_id = ?
        AND active = 1
        """,
        (guild_id, user_id)
    )

    return cursor.fetchone()[0]


def remove_warning(warning_id: int):
    cursor.execute(
        """
        UPDATE warnings
        SET active = 0
        WHERE id = ?
        """,
        (warning_id,)
    )

    db.commit()


def clear_warnings(guild_id: int, user_id: int):
    cursor.execute(
        """
        UPDATE warnings
        SET active = 0
        WHERE guild_id = ?
        AND user_id = ?
        """,
        (guild_id, user_id)
    )

    db.commit()


def create_appeal(
    warning_id: int,
    user_id: int,
    reason: str
):
    now = datetime.now(timezone.utc).isoformat()

    cursor.execute(
        """
        INSERT INTO appeals
        (warning_id, user_id, reason, created_at)
        VALUES (?, ?, ?, ?)
        """,
        (
            warning_id,
            user_id,
            reason,
            now
        )
    )

    db.commit()

    return cursor.lastrowid


# ============================================================
# UTILS
# ============================================================

def utc_timestamp():
    return datetime.now(timezone.utc).strftime(
        "%Y-%m-%d %H:%M UTC"
    )


def get_log_channel(guild: discord.Guild):
    if not MOD_LOG_CHANNEL_ID:
        return None

    channel = guild.get_channel(MOD_LOG_CHANNEL_ID)

    if isinstance(channel, discord.TextChannel):
        return channel

    return None


def can_moderate(
    moderator: discord.Member,
    target: discord.Member
):
    if moderator.id == target.id:
        return False, "You cannot moderate yourself."

    if target.id == moderator.guild.owner_id:
        return False, "You cannot moderate the server owner."

    if moderator.id != moderator.guild.owner_id:
        if target.top_role >= moderator.top_role:
            return False, "That member has an equal or higher role than you."

    if target.top_role >= moderator.guild.me.top_role:
        return False, "I cannot moderate that member because of role hierarchy."

    return True, None


async def send_log(
    guild: discord.Guild,
    view: discord.ui.LayoutView
):
    channel = get_log_channel(guild)

    if not channel:
        return

    try:
        await channel.send(view=view)
    except discord.HTTPException:
        pass


# ============================================================
# COMPONENTS V2 - WARNING PANEL
# ============================================================

class WarningPanel(discord.ui.LayoutView):

    def __init__(
        self,
        warning_id: int,
        member: discord.Member,
        moderator: discord.Member,
        reason: str,
        total_warns: int
    ):
        super().__init__(timeout=None)

        self.add_item(
            discord.ui.Container(
                discord.ui.TextDisplay(
                    "# ⚠️ Member Warned\n"
                    f"**User**\n"
                    f"{member.mention} · `{member.id}`\n\n"
                    f"**Reason**\n"
                    f"{reason}\n\n"
                    f"**Case**\n"
                    f"`#{warning_id:04d}`\n\n"
                    f"**Total Warnings**\n"
                    f"`{total_warns}`\n\n"
                    f"**Moderator**\n"
                    f"{moderator.mention}\n\n"
                    f"-# {utc_timestamp()}"
                ),

                discord.ui.Separator(
                    visible=True,
                    spacing=discord.SeparatorSpacing.small
                ),

                discord.ui.ActionRow(
                    discord.ui.Button(
                        label="View Warnings",
                        style=discord.ButtonStyle.secondary,
                        custom_id=f"warn_view:{warning_id}:{member.id}"
                    ),
                    discord.ui.Button(
                        label="Appeal",
                        style=discord.ButtonStyle.primary,
                        custom_id=f"warn_appeal:{warning_id}:{member.id}"
                    )
                ),

                accent_colour=0xFF7A00
            )
        )


# ============================================================
# COMPONENTS V2 - WARNING LIST
# ============================================================

def build_warning_list(
    member: discord.Member,
    warnings
):

    text = (
        f"# Warning History\n"
        f"**{member}** · `{member.id}`\n\n"
    )

    if not warnings:
        text += "No active warnings."

    else:
        for warning in warnings[:15]:

            moderator = member.guild.get_member(
                warning["moderator_id"]
            )

            moderator_text = (
                moderator.mention
                if moderator
                else f"`{warning['moderator_id']}`"
            )

            text += (
                f"**Case #{warning['id']:04d}**\n"
                f"> {warning['reason']}\n"
                f"> Moderator: {moderator_text}\n"
                f"> `{warning['created_at']}`\n\n"
            )

        if len(warnings) > 15:
            text += (
                f"-# Showing 15 of {len(warnings)} active warnings."
            )

    return discord.ui.LayoutView(
        timeout=120
    ), text


# ============================================================
# COMPONENTS V2 - APPEAL RESULT
# ============================================================

class AppealPanel(discord.ui.LayoutView):

    def __init__(self, appeal_id: int):
        super().__init__(timeout=None)

        self.add_item(
            discord.ui.Container(
                discord.ui.TextDisplay(
                    "# Appeal Submitted\n"
                    f"Your appeal has been submitted successfully.\n\n"
                    f"**Appeal ID**\n"
                    f"`#{appeal_id:04d}`\n\n"
                    "-# Staff will review your appeal."
                ),
                accent_colour=0x5865F2
            )
        )


# ============================================================
# WARN COMMAND
# ============================================================

@bot.command(
    name="warn",
    aliases=["w"]
)
@commands.guild_only()
@commands.has_permissions(moderate_members=True)
async def warn(
    ctx: commands.Context,
    member: discord.Member,
    *,
    reason: str = None
):

    if not reason:
        await ctx.send(
            "Usage: `.warn @user <reason>`"
        )
        return

    if len(reason) > 1000:
        await ctx.send(
            "The reason cannot be longer than 1000 characters."
        )
        return

    if member.bot:
        await ctx.send(
            "You cannot warn bots."
        )
        return

    moderator = ctx.author

    valid, error = can_moderate(
        moderator,
        member
    )

    if not valid:
        await ctx.send(error)
        return

    evidence = None

    if ctx.message.attachments:
        evidence = ctx.message.attachments[0].url

    warning_id = create_warning(
        guild_id=ctx.guild.id,
        user_id=member.id,
        moderator_id=moderator.id,
        reason=reason,
        evidence=evidence
    )

    total_warns = get_warning_count(
        ctx.guild.id,
        member.id
    )

    # ========================================================
    # DM USER
    # ========================================================

    try:

        dm_view = discord.ui.LayoutView(
            timeout=120
        )

        dm_view.add_item(
            discord.ui.Container(
                discord.ui.TextDisplay(
                    f"# ⚠️ You Have Been Warned\n"
                    f"**Server**\n"
                    f"{ctx.guild.name}\n\n"
                    f"**Reason**\n"
                    f"{reason}\n\n"
                    f"**Case**\n"
                    f"`#{warning_id:04d}`\n\n"
                    f"**Total Warnings**\n"
                    f"`{total_warns}`\n\n"
                    f"-# If you believe this warning was issued incorrectly, "
                    f"use the appeal option on the warning message."
                ),
                accent_colour=0xFF7A00
            )
        )

        await member.send(
            view=dm_view
        )

    except (
        discord.Forbidden,
        discord.HTTPException
    ):
        pass

    # ========================================================
    # MAIN RESPONSE
    # ========================================================

    panel = WarningPanel(
        warning_id,
        member,
        moderator,
        reason,
        total_warns
    )

    await ctx.send(
        view=panel
    )

    # ========================================================
    # MOD LOG
    # ========================================================

    log_view = discord.ui.LayoutView(
        timeout=None
    )

    log_view.add_item(
        discord.ui.Container(
            discord.ui.TextDisplay(
                "# Moderation Log\n"
                f"**Action**\n"
                `Warning`\n\n"
                f"**User**\n"
                f"{member.mention} · `{member.id}`\n\n"
                f"**Moderator**\n"
                f"{moderator.mention}\n\n"
                f"**Reason**\n"
                f"{reason}\n\n"
                f"**Case**\n"
                f"`#{warning_id:04d}`\n\n"
                f"**Total Warnings**\n"
                f"`{total_warns}`"
            ),
            accent_colour=0xFF7A00
        )
    )

    await send_log(
        ctx.guild,
        log_view
    )

    # ========================================================
    # AUTOMATIC PUNISHMENTS
    # ========================================================

    try:

        if total_warns >= WARN_BAN_AT:

            await member.ban(
                reason=f"Automatic punishment: {total_warns} warnings"
            )

        elif total_warns >= WARN_KICK_AT:

            await member.kick(
                reason=f"Automatic punishment: {total_warns} warnings"
            )

        elif total_warns >= WARN_TIMEOUT_AT:

            await member.timeout(
                timedelta(minutes=10),
                reason=f"Automatic punishment: {total_warns} warnings"
            )

    except discord.Forbidden:
        pass

    except discord.HTTPException:
        pass


# ============================================================
# WARNINGS COMMAND
# ============================================================

@bot.command(
    name="warnings",
    aliases=["warns", "infractions"]
)
@commands.guild_only()
async def warnings(
    ctx: commands.Context,
    member: discord.Member = None
):

    member = member or ctx.author

    if (
        member != ctx.author
        and not ctx.author.guild_permissions.moderate_members
    ):
        await ctx.send(
            "You need `Moderate Members` to view another member's warnings."
        )
        return

    data = get_user_warnings(
        ctx.guild.id,
        member.id
    )

    view = discord.ui.LayoutView(
        timeout=120
    )

    text = (
        f"# Warning History\n"
        f"**User:** {member.mention}\n"
        f"**Active Warnings:** `{len(data)}`\n\n"
    )

    if not data:
        text += "No active warnings."

    else:

        for warning in data[:15]:

            moderator = ctx.guild.get_member(
                warning["moderator_id"]
            )

            mod_text = (
                moderator.mention
                if moderator
                else f"`{warning['moderator_id']}`"
            )

            text += (
                f"**Case #{warning['id']:04d}**\n"
                f"> {warning['reason']}\n"
                f"> Moderator: {mod_text}\n"
                f"> `{warning['created_at']}`\n\n"
            )

    view.add_item(
        discord.ui.Container(
            discord.ui.TextDisplay(text),
            accent_colour=0xFF7A00
        )
    )

    await ctx.send(
        view=view
    )


# ============================================================
# WARN INFO
# ============================================================

@bot.command(
    name="warninfo"
)
@commands.guild_only()
@commands.has_permissions(moderate_members=True)
async def warninfo(
    ctx: commands.Context,
    warning_id: int
):

    warning = get_warning(
        warning_id
    )

    if not warning:
        await ctx.send(
            "That warning case does not exist."
        )
        return

    member = ctx.guild.get_member(
        warning["user_id"]
    )

    moderator = ctx.guild.get_member(
        warning["moderator_id"]
    )

    member_text = (
        member.mention
        if member
        else f"`{warning['user_id']}`"
    )

    moderator_text = (
        moderator.mention
        if moderator
        else f"`{warning['moderator_id']}`"
    )

    status = (
        "Active"
        if warning["active"]
        else "Removed"
    )

    view = discord.ui.LayoutView(
        timeout=120
    )

    view.add_item(
        discord.ui.Container(
            discord.ui.TextDisplay(
                f"# Warning Case #{warning_id:04d}\n\n"
                f"**User**\n"
                f"{member_text}\n\n"
                f"**Moderator**\n"
                f"{moderator_text}\n\n"
                f"**Reason**\n"
                f"{warning['reason']}\n\n"
                f"**Status**\n"
                f"`{status}`\n\n"
                f"**Created**\n"
                f"`{warning['created_at']}`\n\n"
                f"**Evidence**\n"
                f"{warning['evidence'] or 'None'}"
            ),
            accent_colour=0xFF7A00
        )
    )

    await ctx.send(
        view=view
    )


# ============================================================
# REMOVE WARNING
# ============================================================

@bot.command(
    name="unwarn",
    aliases=["removewarn"]
)
@commands.guild_only()
@commands.has_permissions(moderate_members=True)
async def unwarn(
    ctx: commands.Context,
    warning_id: int
):

    warning = get_warning(
        warning_id
    )

    if not warning:
        await ctx.send(
            "That warning case does not exist."
        )
        return

    if not warning["active"]:
        await ctx.send(
            "That warning has already been removed."
        )
        return

    remove_warning(
        warning_id
    )

    view = discord.ui.LayoutView(
        timeout=120
    )

    view.add_item(
        discord.ui.Container(
            discord.ui.TextDisplay(
                "# Warning Removed\n"
                f"Case `#{warning_id:04d}` has been removed.\n\n"
                f"**Reason**\n"
                f"{warning['reason']}\n\n"
                f"**Removed by**\n"
                f"{ctx.author.mention}"
            ),
            accent_colour=0x57F287
        )
    )

    await ctx.send(
        view=view
    )


# ============================================================
# CLEAR WARNINGS
# ============================================================

@bot.command(
    name="clearwarns",
    aliases=["clearwarnings"]
)
@commands.guild_only()
@commands.has_permissions(administrator=True)
async def clearwarns(
    ctx: commands.Context,
    member: discord.Member
):

    count = get_warning_count(
        ctx.guild.id,
        member.id
    )

    if count == 0:
        await ctx.send(
            f"{member.mention} has no active warnings."
        )
        return

    clear_warnings(
        ctx.guild.id,
        member.id
    )

    view = discord.ui.LayoutView(
        timeout=120
    )

    view.add_item(
        discord.ui.Container(
            discord.ui.TextDisplay(
                "# Warnings Cleared\n"
                f"All active warnings for {member.mention} "
                f"have been removed.\n\n"
                f"**Removed**\n"
                f"`{count}` warnings\n\n"
                f"**Moderator**\n"
                f"{ctx.author.mention}"
            ),
            accent_colour=0x57F287
        )
    )

    await ctx.send(
        view=view
    )


# ============================================================
# WARN HELP
# ============================================================

@bot.command(
    name="warnhelp"
)
async def warnhelp(
    ctx: commands.Context
):

    view = discord.ui.LayoutView(
        timeout=120
    )

    view.add_item(
        discord.ui.Container(
            discord.ui.TextDisplay(
                "# Moderation Commands\n\n"
                "**`.warn @user <reason>`**\n"
                "Issue a warning.\n\n"
                "**`.warnings [@user]`**\n"
                "View active warnings.\n\n"
                "**`.warninfo <case>`**\n"
                "View a specific warning case.\n\n"
                "**`.unwarn <case>`**\n"
                "Remove a warning.\n\n"
                "**`.clearwarns @user`**\n"
                "Clear all active warnings.\n\n"
                "-# Staff permissions are required for moderation commands."
            ),
            accent_colour=0x5865F2
        )
    )

    await ctx.send(
        view=view
    )


# ============================================================
# COMPONENT INTERACTIONS
# ============================================================

@bot.event
async def on_interaction(
    interaction: discord.Interaction
):

    if not interaction.data:
        return

    custom_id = interaction.data.get(
        "custom_id"
    )

    if not custom_id:
        return

    # ========================================================
    # VIEW WARNINGS
    # ========================================================

    if custom_id.startswith("warn_view:"):

        parts = custom_id.split(":")

        warning_id = int(parts[1])
        user_id = int(parts[2])

        if not interaction.guild:
            return

        member = interaction.guild.get_member(
            user_id
        )

        if not member:
            return await interaction.response.send_message(
                "That member is no longer in the server.",
                ephemeral=True
            )

        if (
            interaction.user.id != user_id
            and not isinstance(
                interaction.user,
                discord.Member
            )
        ):
            return await interaction.response.send_message(
                "You cannot view these warnings.",
                ephemeral=True
            )

        if (
            interaction.user.id != user_id
            and not interaction.user.guild_permissions.moderate_members
        ):
            return await interaction.response.send_message(
                "You need `Moderate Members` to view another member's warnings.",
                ephemeral=True
            )

        data = get_user_warnings(
            interaction.guild.id,
            user_id
        )

        view = discord.ui.LayoutView(
            timeout=120
        )

        text = (
            f"# Warning History\n"
            f"**User:** {member.mention}\n"
            f"**Active:** `{len(data)}`\n\n"
        )

        if not data:
            text += "No active warnings."

        else:

            for warning in data[:15]:

                moderator = interaction.guild.get_member(
                    warning["moderator_id"]
                )

                moderator_text = (
                    moderator.mention
                    if moderator
                    else f"`{warning['moderator_id']}`"
                )

                text += (
                    f"**Case #{warning['id']:04d}**\n"
                    f"> {warning['reason']}\n"
                    f"> Moderator: {moderator_text}\n"
                    f"> `{warning['created_at']}`\n\n"
                )

        view.add_item(
            discord.ui.Container(
                discord.ui.TextDisplay(text),
                accent_colour=0xFF7A00
            )
        )

        await interaction.response.send_message(
            view=view,
            ephemeral=True
        )

        return

    # ========================================================
    # APPEAL
    # ========================================================

    if custom_id.startswith("warn_appeal:"):

        parts = custom_id.split(":")

        warning_id = int(parts[1])
        user_id = int(parts[2])

        if interaction.user.id != user_id:
            return await interaction.response.send_message(
                "Only the warned member can appeal this warning.",
                ephemeral=True
            )

        warning = get_warning(
            warning_id
        )

        if not warning:
            return await interaction.response.send_message(
                "That warning no longer exists.",
                ephemeral=True
            )

        modal = AppealModal(
            warning_id=warning_id
        )

        await interaction.response.send_modal(
            modal
        )

        return


# ============================================================
# APPEAL MODAL
# ============================================================

class AppealModal(discord.ui.Modal):

    def __init__(self, warning_id: int):
        super().__init__(
            title=f"Appeal Warning #{warning_id:04d}"
        )

        self.warning_id = warning_id

        self.reason = discord.ui.TextInput(
            label="Why should this warning be removed?",
            placeholder="Explain your appeal...",
            style=discord.TextStyle.paragraph,
            min_length=10,
            max_length=1000,
            required=True
        )

        self.add_item(
            self.reason
        )

    async def on_submit(
        self,
        interaction: discord.Interaction
    ):

        appeal_id = create_appeal(
            warning_id=self.warning_id,
            user_id=interaction.user.id,
            reason=str(self.reason.value)
        )

        # ----------------------------------------------------
        # SEND APPEAL TO MOD LOG
        # ----------------------------------------------------

        if interaction.guild:

            log_view = discord.ui.LayoutView(
                timeout=None
            )

            log_view.add_item(
                discord.ui.Container(
                    discord.ui.TextDisplay(
                        "# New Warning Appeal\n"
                        f"**Appeal**\n"
                        f"`#{appeal_id:04d}`\n\n"
                        f"**Warning**\n"
                        f"`#{self.warning_id:04d}`\n\n"
                        f"**User**\n"
                        f"{interaction.user.mention}\n\n"
                        f"**Reason**\n"
                        f"{self.reason.value}\n\n"
                        f"-# Status: Pending"
                    ),
                    accent_colour=0x5865F2
                )
            )

            await send_log(
                interaction.guild,
                log_view
            )

        # ----------------------------------------------------
        # USER RESPONSE
        # ----------------------------------------------------

        await interaction.response.send_message(
            view=AppealPanel(
                appeal_id
            ),
            ephemeral=True
        )


# ============================================================
# COMMAND ERROR HANDLER
# ============================================================

@bot.event
async def on_command_error(
    ctx: commands.Context,
    error: commands.CommandError
):

    if isinstance(
        error,
        commands.CommandNotFound
    ):
        return

    if isinstance(
        error,
        commands.MissingPermissions
    ):
        await ctx.send(
            "You do not have permission to use that command."
        )
        return

    if isinstance(
        error,
        commands.MissingRequiredArgument
    ):
        await ctx.send(
            f"Missing argument.\n"
            f"Use `.warnhelp` for command usage."
        )
        return

    if isinstance(
        error,
        commands.BadArgument
    ):
        await ctx.send(
            "I couldn't find that member or case."
        )
        return

    print(
        f"[COMMAND ERROR] {type(error).__name__}: {error}"
    )


# ============================================================
# READY
# ============================================================

@bot.event
async def on_ready():

    print("----------------------------------------")
    print(f"Logged in as: {bot.user}")
    print(f"Bot ID: {bot.user.id}")
    print(f"Discord.py: {discord.__version__}")
    print("----------------------------------------")
    print("Moderation system loaded.")
    print(f"Prefix: {PREFIX}")
    print(f"Timeout threshold: {WARN_TIMEOUT_AT}")
    print(f"Kick threshold: {WARN_KICK_AT}")
    print(f"Ban threshold: {WARN_BAN_AT}")
    print("----------------------------------------")


# ============================================================
# START
# ============================================================

bot.run(TOKEN)

28,375 chars · codepastes.com