fuck my stupid chungus life

This commit is contained in:
Lilac-Rose 2026-02-02 17:30:24 +01:00
parent 78544dee2c
commit 4aefafc404
7 changed files with 684 additions and 11 deletions

3
.gitignore vendored
View File

@ -7,4 +7,5 @@ venv/
*/fonts/
.txt
*/databases/
*/old_backups/
*/old_backups/
logs/

1
bot.py
View File

@ -7,6 +7,7 @@ import glob
import traceback
from xp.database import get_db as get_xp_db
from moderation.loader import ModerationBase
from aiohttp import web
# Import sparkle DB to ensure it exists
from sparkle.database import get_db as get_sparkle_db

View File

@ -15,7 +15,7 @@ COLOR_ROLE_NAMES = [
"Turquoise", "Green Sea", "Emerald", "Nephritis", "River", "Belize",
"Amethyst", "Wisteria", "Linen", "Alizarin", "Pomegranate", "Tangerine",
"Rose", "Carrot", "Orange", "Sun Flower", "Pumpkin", "Light Gray",
"Dark Air", "White"
"Dark Air", "White-ish"
]
FONTS_PATH = os.path.join(os.path.dirname(__file__), "..", "fonts")

233
events/status_alerts.py Normal file
View File

@ -0,0 +1,233 @@
"""
Status Monitor Cog
Monitors the status_monitor database and sends DMs when services go down/recover
"""
import discord
from discord.ext import commands, tasks
import sqlite3
import os
from pathlib import Path
from datetime import datetime
from zoneinfo import ZoneInfo
class StatusMonitor(commands.Cog):
def __init__(self, bot):
self.bot = bot
# Path to the status monitor database
self.db_path = Path("/home/lilacrose/lilacrose.dev2.0/monitor.db")
# Your Discord user ID
self.admin_user_id = 252130669919076352
# Track last known status to detect changes
self.last_status = {}
# Start the monitoring loop
self.check_status.start()
def cog_unload(self):
"""Stop the monitoring loop when cog is unloaded"""
self.check_status.cancel()
@tasks.loop(minutes=1)
async def check_status(self):
"""Check service status every minute"""
try:
# Check if database exists
if not self.db_path.exists():
return
# Connect to the status monitor database
conn = sqlite3.connect(self.db_path)
conn.row_factory = sqlite3.Row
cur = conn.cursor()
# Get latest status for each service
cur.execute("""
SELECT service_name, status, error_message, timestamp
FROM service_checks
WHERE id IN (
SELECT MAX(id)
FROM service_checks
GROUP BY service_name
)
""")
current_statuses = {row['service_name']: row for row in cur.fetchall()}
conn.close()
# Get the admin user
admin = await self.bot.fetch_user(self.admin_user_id)
if not admin:
return
# Check each service for status changes
for service_name, row in current_statuses.items():
current_status = row['status']
error_msg = row['error_message']
# Get the service's display name
service_display_names = {
"website": "TERMINAL//FKLR-F23",
"game_tracker": "GAME_TRACKER//FKLR-F23",
"forms_server": "Lacie Bot Website",
"file_server": "File Server",
"discord_bot": "Lacie Bot"
}
display_name = service_display_names.get(service_name, service_name)
# If we haven't seen this service before, just store it
if service_name not in self.last_status:
self.last_status[service_name] = current_status
continue
previous_status = self.last_status[service_name]
# Service went DOWN
if current_status == "down" and previous_status != "down":
embed = discord.Embed(
title="🔴 Service Down Alert",
description=f"**{display_name}** has gone offline",
color=discord.Color.red(),
timestamp=datetime.now(ZoneInfo("America/New_York"))
)
if error_msg:
embed.add_field(name="Error", value=f"```{error_msg}```", inline=False)
embed.add_field(name="Service ID", value=f"`{service_name}`", inline=True)
embed.set_footer(text="Status Monitor Alert")
try:
await admin.send(embed=embed)
except Exception as e:
print(f"Failed to send down alert for {service_name}: {e}")
# Service went DEGRADED
elif current_status == "degraded" and previous_status == "up":
embed = discord.Embed(
title="⚠️ Service Degraded",
description=f"**{display_name}** is experiencing issues",
color=discord.Color.orange(),
timestamp=datetime.now(ZoneInfo("America/New_York"))
)
if error_msg:
embed.add_field(name="Error", value=f"```{error_msg}```", inline=False)
embed.add_field(name="Service ID", value=f"`{service_name}`", inline=True)
embed.set_footer(text="Status Monitor Alert")
try:
await admin.send(embed=embed)
except Exception as e:
print(f"Failed to send degraded alert for {service_name}: {e}")
# Service RECOVERED
elif current_status == "up" and previous_status in ["down", "degraded"]:
# Calculate downtime
conn = sqlite3.connect(self.db_path)
conn.row_factory = sqlite3.Row
cur = conn.cursor()
cur.execute("""
SELECT started_at, ended_at, duration_seconds
FROM downtime_incidents
WHERE service_name = ? AND ended_at IS NOT NULL
ORDER BY id DESC LIMIT 1
""", (service_name,))
incident = cur.fetchone()
conn.close()
embed = discord.Embed(
title="✅ Service Recovered",
description=f"**{display_name}** is back online",
color=discord.Color.green(),
timestamp=datetime.now(ZoneInfo("America/New_York"))
)
if incident and incident['duration_seconds']:
duration = incident['duration_seconds']
if duration < 60:
duration_str = f"{int(duration)} seconds"
elif duration < 3600:
duration_str = f"{int(duration / 60)} minutes"
else:
duration_str = f"{duration / 3600:.1f} hours"
embed.add_field(name="Downtime", value=duration_str, inline=True)
embed.add_field(name="Service ID", value=f"`{service_name}`", inline=True)
embed.set_footer(text="Status Monitor Alert")
try:
await admin.send(embed=embed)
except Exception as e:
print(f"Failed to send recovery alert for {service_name}: {e}")
# Update last known status
self.last_status[service_name] = current_status
except Exception as e:
print(f"[StatusMonitor] Error checking status: {e}")
@check_status.before_loop
async def before_check_status(self):
"""Wait for bot to be ready before starting the loop"""
await self.bot.wait_until_ready()
print("[StatusMonitor] Starting status monitoring loop...")
# Initialize last_status with current state on startup
try:
if self.db_path.exists():
conn = sqlite3.connect(self.db_path)
conn.row_factory = sqlite3.Row
cur = conn.cursor()
cur.execute("""
SELECT service_name, status
FROM service_checks
WHERE id IN (
SELECT MAX(id)
FROM service_checks
GROUP BY service_name
)
""")
for row in cur.fetchall():
self.last_status[row['service_name']] = row['status']
conn.close()
print(f"[StatusMonitor] Initialized with {len(self.last_status)} services")
except Exception as e:
print(f"[StatusMonitor] Failed to initialize: {e}")
@commands.command(name="statustest")
@commands.is_owner()
async def status_test(self, ctx):
"""Test the status monitoring alerts (Owner only)"""
embed = discord.Embed(
title="🔔 Status Monitor Test",
description="This is a test alert from the status monitoring system",
color=discord.Color.blue(),
timestamp=datetime.now(ZoneInfo("America/New_York"))
)
embed.add_field(name="Database Path", value=f"`{self.db_path}`", inline=False)
embed.add_field(name="Database Exists", value="✅ Yes" if self.db_path.exists() else "❌ No", inline=True)
embed.add_field(name="Monitoring", value=f"{len(self.last_status)} services", inline=True)
embed.add_field(name="Admin User ID", value=f"`{self.admin_user_id}`", inline=True)
if self.last_status:
status_list = "\n".join([f"• `{name}`: {status}" for name, status in self.last_status.items()])
embed.add_field(name="Current Status", value=status_list, inline=False)
await ctx.send(embed=embed)
# Try to DM the admin
try:
admin = await self.bot.fetch_user(self.admin_user_id)
test_embed = discord.Embed(
title="✅ DM Test Successful",
description="If you're seeing this, status alerts will work!",
color=discord.Color.green()
)
await admin.send(embed=test_embed)
await ctx.send("✅ Test DM sent successfully!")
except Exception as e:
await ctx.send(f"❌ Failed to send test DM: {e}")
async def setup(bot):
await bot.add_cog(StatusMonitor(bot))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 528 KiB

After

Width:  |  Height:  |  Size: 533 KiB

View File

@ -1,10 +1,202 @@
import discord
from discord.ext import commands
from discord.ext import commands, tasks
from .loader import ModerationBase
from datetime import datetime
from datetime import datetime, timedelta
class InfractionCommand(ModerationBase):
def __init__(self, bot: commands.Bot):
super().__init__(bot)
self.approval_channel_id = 1467901790333960377
self.migrate_existing_infractions()
self.check_auto_removals.start()
def cog_unload(self):
"""Stop the background task and close DB when cog unloads."""
self.check_auto_removals.cancel()
super().cog_unload()
def migrate_existing_infractions(self):
"""Add new columns to existing infractions table for auto-removal system."""
try:
# Add removed column (0 = active, 1 = removed)
self.c.execute("ALTER TABLE infractions ADD COLUMN removed INTEGER DEFAULT 0")
except Exception:
pass # Column already exists
try:
# Add removed_date column
self.c.execute("ALTER TABLE infractions ADD COLUMN removed_date TEXT")
except Exception:
pass
try:
# Add removed_by column (moderator who approved removal)
self.c.execute("ALTER TABLE infractions ADD COLUMN removed_by INTEGER")
except Exception:
pass
try:
# Add skip_auto_removal column (1 = staff chose to keep it, skip future checks)
self.c.execute("ALTER TABLE infractions ADD COLUMN skip_auto_removal INTEGER DEFAULT 0")
except Exception:
pass
try:
# Add pending_approval column (1 = approval request already sent, waiting for staff decision)
self.c.execute("ALTER TABLE infractions ADD COLUMN pending_approval INTEGER DEFAULT 0")
except Exception:
pass
self.conn.commit()
@tasks.loop(hours=24)
async def check_auto_removals(self):
"""Background task that runs every 24 hours to check for eligible infraction removals."""
try:
# Get all guilds with active infractions
self.c.execute("SELECT DISTINCT guild_id FROM infractions WHERE removed=0 AND skip_auto_removal=0 AND pending_approval=0")
guilds = [row[0] for row in self.c.fetchall()]
for guild_id in guilds:
# Get all users with active, non-skipped, non-pending infractions in this guild
self.c.execute("""
SELECT DISTINCT user_id
FROM infractions
WHERE guild_id=? AND removed=0 AND skip_auto_removal=0 AND pending_approval=0
""", (guild_id,))
users = [row[0] for row in self.c.fetchall()]
for user_id in users:
await self.check_user_eligibility(guild_id, user_id)
except Exception as e:
print(f"Error in auto-removal check: {e}")
@check_auto_removals.before_loop
async def before_check_auto_removals(self):
"""Wait until the bot is ready before starting the loop."""
await self.bot.wait_until_ready()
async def check_user_eligibility(self, guild_id: int, user_id: int):
"""Check if a user's most recent infraction is eligible for removal."""
try:
# Get guild
guild = self.bot.get_guild(guild_id)
if not guild:
return
# Check if user is banned
try:
ban = await guild.fetch_ban(discord.Object(id=user_id))
if ban:
return # User is banned, skip
except discord.NotFound:
pass # User is not banned, continue
except Exception:
pass # Error checking ban, continue anyway
# Check if user is in the server
member = guild.get_member(user_id)
if not member:
return # User not in server, skip
# Get the most recent ACTIVE infraction for this user (not removed, not skipped, not pending)
self.c.execute("""
SELECT id, timestamp, type, reason, moderator_id
FROM infractions
WHERE user_id=? AND guild_id=? AND removed=0 AND skip_auto_removal=0 AND pending_approval=0
ORDER BY timestamp DESC
LIMIT 1
""", (user_id, guild_id))
most_recent = self.c.fetchone()
if not most_recent:
return # No active infractions
inf_id, timestamp_str, inf_type, reason, mod_id = most_recent
infraction_date = datetime.fromisoformat(timestamp_str)
# Check if 4 months have passed
four_months_ago = datetime.utcnow() - timedelta(days=120)
if infraction_date > four_months_ago:
return # Not eligible yet
# Check if user got ANY infractions after this one
self.c.execute("""
SELECT COUNT(*)
FROM infractions
WHERE user_id=? AND guild_id=? AND timestamp > ?
""", (user_id, guild_id, timestamp_str))
newer_infractions = self.c.fetchone()[0]
if newer_infractions > 0:
return # User got infractions after this one, not eligible
# User is eligible! Send to staff for approval
await self.send_removal_approval(guild_id, user_id, inf_id, inf_type, reason, timestamp_str, mod_id)
except Exception as e:
print(f"Error checking eligibility for user {user_id} in guild {guild_id}: {e}")
async def send_removal_approval(self, guild_id: int, user_id: int, inf_id: int,
inf_type: str, reason: str, timestamp: str, mod_id: int):
"""Send an infraction removal request to the approval channel."""
try:
approval_channel = self.bot.get_channel(self.approval_channel_id)
if not approval_channel:
print(f"Approval channel {self.approval_channel_id} not found")
return
# Get guild, user, and moderator info
guild = self.bot.get_guild(guild_id)
if not guild:
return
try:
user = await self.bot.fetch_user(user_id)
user_tag = f"{user.name}#{user.discriminator}"
except:
user_tag = f"Unknown User ({user_id})"
try:
moderator = await self.bot.fetch_user(mod_id)
mod_tag = f"{moderator.name}#{moderator.discriminator}"
except:
mod_tag = f"Unknown Mod ({mod_id})"
# Create approval embed
embed = discord.Embed(
title="🔔 Infraction Eligible for Auto-Removal",
description=f"This user has stayed clean for 4 months. Should this infraction be removed?",
color=discord.Color.blue()
)
embed.add_field(name="Server", value=guild.name, inline=False)
embed.add_field(name="User", value=user_tag, inline=True)
embed.add_field(name="Infraction ID", value=str(inf_id), inline=True)
embed.add_field(name="Type", value=inf_type, inline=True)
embed.add_field(name="Reason", value=reason or "None", inline=False)
embed.add_field(name="Original Date", value=timestamp.replace("T", " ")[:19], inline=True)
embed.add_field(name="Original Moderator", value=mod_tag, inline=True)
embed.set_footer(text=f"User ID: {user_id} | Guild ID: {guild_id}")
# Create approval view
view = InfractionRemovalView(self, inf_id, user_id, guild_id, user_tag, inf_type, reason, timestamp)
await approval_channel.send(embed=embed, view=view)
# Mark infraction as pending approval so we don't send duplicate requests
self.c.execute("""
UPDATE infractions
SET pending_approval=1
WHERE id=?
""", (inf_id,))
self.conn.commit()
except Exception as e:
print(f"Error sending removal approval: {e}")
@commands.command(name="inf")
@ModerationBase.is_admin()
async def inf(self, ctx, action: str, *args):
@ -25,6 +217,33 @@ class InfractionCommand(ModerationBase):
self.c.execute("""
SELECT id, user_id, type, reason, moderator_id, timestamp
FROM infractions
WHERE user_id=? AND guild_id=? AND removed=0
ORDER BY timestamp DESC
""", (user_id, ctx.guild.id))
results = self.c.fetchall()
except Exception as e:
await ctx.send(f"Database error: {e}")
return
if not results:
await ctx.send("This user has no active infractions.")
return
elif action == "search_full":
if not args:
await ctx.send("You must provide a user ID to search.")
return
try:
user_id = int(args[0])
except ValueError:
await ctx.send("Invalid user ID.")
return
try:
self.c.execute("""
SELECT id, user_id, type, reason, moderator_id, timestamp, removed, removed_date
FROM infractions
WHERE user_id=? AND guild_id=?
ORDER BY timestamp DESC
""", (user_id, ctx.guild.id))
@ -37,13 +256,98 @@ class InfractionCommand(ModerationBase):
await ctx.send("This user has no infractions.")
return
# Build full history rows with status
ids_to_cache = set(row[1] for row in results) | set(row[4] for row in results)
user_cache = {u.id: u for u in self.bot.users if u.id in ids_to_cache}
rows = []
for row in results:
try:
user = user_cache.get(row[1]) or await self.bot.fetch_user(int(row[1]))
moderator = user_cache.get(row[4]) or await self.bot.fetch_user(int(row[4]))
user_cache[user.id] = user
user_cache[moderator.id] = moderator
except Exception as e:
await ctx.send(f"User fetch error: {e}")
return
user_tag = f"{user.name}#{user.discriminator}"
mod_tag = f"{moderator.name}#{moderator.discriminator}"
timestamp = row[5].replace("T", " ")[:19]
reason = row[3] or "None"
# Check if removed
is_removed = row[6] if len(row) > 6 else 0
removed_date = row[7] if len(row) > 7 and row[7] else ""
if is_removed:
status = f"Removed ({removed_date.replace('T', ' ')[:19]})" if removed_date else "Removed"
else:
status = "Active"
rows.append({
"id": str(row[0]),
"user": user_tag,
"moderator": mod_tag,
"timestamp": timestamp,
"type": row[2],
"reason": reason,
"status": status
})
# Prepare table
widths = {key: max(len(key), *(len(r[key]) for r in rows)) for key in rows[0].keys()}
header = " | ".join(f"{key.capitalize():{widths[key]}}" for key in rows[0].keys())
separator = "-" * len(header)
# Build pages
chunk_size = 1800
pages = []
current_chunk = [header, separator]
char_count = len("```md\n") + len(header) + len(separator) + 2
for r in rows:
line = " | ".join(f"{r[key]:{widths[key]}}" for key in r.keys())
line_len = len(line) + 1
if char_count + line_len > chunk_size:
pages.append("```md\n" + "\n".join(current_chunk) + "\n```")
current_chunk = [header, separator]
char_count = len("```md\n") + len(header) + len(separator) + 2
current_chunk.append(line)
char_count += line_len
if current_chunk:
pages.append("```md\n" + "\n".join(current_chunk) + "\n```")
# Pagination with buttons
class PageView(discord.ui.View):
def __init__(self, pages):
super().__init__(timeout=180)
self.pages = pages
self.current = 0
async def update_message(self, interaction):
await interaction.response.edit_message(content=self.pages[self.current], view=self)
@discord.ui.button(label="Previous", style=discord.ButtonStyle.blurple)
async def previous(self, button: discord.ui.Button, interaction: discord.Interaction):
self.current = (self.current - 1) % len(self.pages)
await self.update_message(interaction)
@discord.ui.button(label="Next", style=discord.ButtonStyle.blurple)
async def next(self, button: discord.ui.Button, interaction: discord.Interaction):
self.current = (self.current + 1) % len(self.pages)
await self.update_message(interaction)
await ctx.send(content=pages[0], view=PageView(pages))
return
elif action == "list":
try:
self.c.execute("""
SELECT id, user_id, type, reason, moderator_id, timestamp
FROM infractions
WHERE guild_id=?
WHERE guild_id=? AND removed=0
ORDER BY timestamp DESC
""", (ctx.guild.id,))
results = self.c.fetchall()
@ -52,7 +356,7 @@ class InfractionCommand(ModerationBase):
return
if not results:
await ctx.send("No infractions found in this server.")
await ctx.send("No active infractions found in this server.")
return
elif action == "delete":
@ -66,16 +370,54 @@ class InfractionCommand(ModerationBase):
await ctx.send("Invalid infraction ID.")
return
# Fetch infraction details before deleting
self.c.execute("""
SELECT user_id, type, reason, timestamp
FROM infractions
WHERE id=? AND guild_id=?
""", (inf_id, ctx.guild.id))
infraction = self.c.fetchone()
if not infraction:
await ctx.send(f"Infraction {inf_id} not found.")
return
user_id, inf_type, reason, timestamp = infraction
# Delete the infraction (actually delete it, not just mark as removed)
self.c.execute("DELETE FROM infractions WHERE id=? AND guild_id=?", (inf_id, ctx.guild.id))
self.conn.commit()
await ctx.send(f"Infraction {inf_id} deleted.")
# Send DM to the user
try:
user = await self.bot.fetch_user(user_id)
embed = discord.Embed(
title="Infraction Removed",
description=f"An infraction has been removed from your record in **{ctx.guild.name}**.",
color=discord.Color.green(),
timestamp=datetime.utcnow()
)
embed.add_field(name="Infraction ID", value=str(inf_id), inline=True)
embed.add_field(name="Type", value=inf_type, inline=True)
embed.add_field(name="Original Reason", value=reason or "None", inline=False)
embed.add_field(name="Original Date", value=timestamp.replace("T", " ")[:19], inline=True)
embed.add_field(name="Removed By", value=f"{ctx.author.name}#{ctx.author.discriminator}", inline=True)
embed.set_footer(text=f"Server: {ctx.guild.name}")
await user.send(embed=embed)
await ctx.send(f"Infraction {inf_id} deleted and user notified.")
except discord.Forbidden:
await ctx.send(f"Infraction {inf_id} deleted, but couldn't DM the user (DMs disabled or blocked).")
except Exception as e:
await ctx.send(f"Infraction {inf_id} deleted, but failed to notify user: {e}")
return
else:
await ctx.send("Unknown action. Use search, list, or delete.")
await ctx.send("Unknown action. Use search, search_full, list, or delete.")
return
# Cache users to avoid repeated API calls
# Cache users to avoid repeated API calls (for search and list)
ids_to_cache = set(row[1] for row in results) | set(row[4] for row in results)
user_cache = {u.id: u for u in self.bot.users if u.id in ids_to_cache}
@ -152,5 +494,101 @@ class InfractionCommand(ModerationBase):
await ctx.send(content=pages[0], view=PageView(pages))
class InfractionRemovalView(discord.ui.View):
"""View for approving or denying infraction auto-removals."""
def __init__(self, cog, inf_id, user_id, guild_id, user_tag, inf_type, reason, timestamp):
super().__init__(timeout=None)
self.cog = cog
self.inf_id = inf_id
self.user_id = user_id
self.guild_id = guild_id
self.user_tag = user_tag
self.inf_type = inf_type
self.reason = reason
self.timestamp = timestamp
@discord.ui.button(label="Remove Infraction", style=discord.ButtonStyle.green, custom_id="approve_removal")
async def approve_button(self, interaction: discord.Interaction, button: discord.ui.Button):
"""Approve the removal - mark infraction as removed."""
try:
# Mark infraction as removed and clear pending flag
self.cog.c.execute("""
UPDATE infractions
SET removed=1, removed_date=?, removed_by=?, pending_approval=0
WHERE id=?
""", (datetime.utcnow().isoformat(), interaction.user.id, self.inf_id))
self.cog.conn.commit()
# Update embed
embed = discord.Embed(
title="✅ Infraction Removed",
description="This infraction has been removed from the user's active record.",
color=discord.Color.green()
)
embed.add_field(name="User", value=self.user_tag, inline=True)
embed.add_field(name="Infraction ID", value=str(self.inf_id), inline=True)
embed.add_field(name="Type", value=self.inf_type, inline=True)
embed.add_field(name="Original Date", value=self.timestamp.replace("T", " ")[:19], inline=True)
embed.add_field(name="Removed By", value=interaction.user.mention, inline=True)
embed.add_field(name="Removed At", value=datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S"), inline=True)
await interaction.response.edit_message(embed=embed, view=None)
# Notify user
try:
user = await self.cog.bot.fetch_user(self.user_id)
guild = self.cog.bot.get_guild(self.guild_id)
notify_embed = discord.Embed(
title="✅ Infraction Removed",
description=f"Great news! An infraction has been removed from your record in **{guild.name}** for good behavior.",
color=discord.Color.green(),
timestamp=datetime.utcnow()
)
notify_embed.add_field(name="Infraction ID", value=str(self.inf_id), inline=True)
notify_embed.add_field(name="Type", value=self.inf_type, inline=True)
notify_embed.add_field(name="Original Reason", value=self.reason or "None", inline=False)
notify_embed.add_field(name="Original Date", value=self.timestamp.replace("T", " ")[:19], inline=True)
notify_embed.set_footer(text=f"You stayed clean for 4 months! Keep up the good behavior.")
await user.send(embed=notify_embed)
except:
pass # User has DMs disabled or bot can't reach them
except Exception as e:
await interaction.response.send_message(f"Error removing infraction: {e}", ephemeral=True)
@discord.ui.button(label="Keep Infraction", style=discord.ButtonStyle.red, custom_id="deny_removal")
async def deny_button(self, interaction: discord.Interaction, button: discord.ui.Button):
"""Deny the removal - mark to skip future auto-removal checks."""
try:
# Mark infraction to skip future checks and clear pending flag
self.cog.c.execute("""
UPDATE infractions
SET skip_auto_removal=1, pending_approval=0
WHERE id=?
""", (self.inf_id,))
self.cog.conn.commit()
# Update embed
embed = discord.Embed(
title="❌ Removal Denied",
description="This infraction will remain active and will not be checked for auto-removal again.",
color=discord.Color.red()
)
embed.add_field(name="User", value=self.user_tag, inline=True)
embed.add_field(name="Infraction ID", value=str(self.inf_id), inline=True)
embed.add_field(name="Type", value=self.inf_type, inline=True)
embed.add_field(name="Original Date", value=self.timestamp.replace("T", " ")[:19], inline=True)
embed.add_field(name="Decision By", value=interaction.user.mention, inline=True)
embed.add_field(name="Decision At", value=datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S"), inline=True)
await interaction.response.edit_message(embed=embed, view=None)
except Exception as e:
await interaction.response.send_message(f"Error denying removal: {e}", ephemeral=True)
async def setup(bot: commands.Bot):
await bot.add_cog(InfractionCommand(bot))
await bot.add_cog(InfractionCommand(bot))

View File

@ -23,7 +23,7 @@ class XPSync(commands.Cog):
async def sync_roles_for_user(self, member: discord.Member) -> tuple[int, list[str]]:
"""Sync roles for a member based on their lifetime XP level."""
config = load_config()
ROLE_REWARDS = {int(k): int(v) for k, v in config["ROLE_REWARDS"].items()}
ROLE_REWARDS = {int(k): int(v) for k, v in config["role_rewards"].items()}
# Run database operation in executor to avoid blocking
def get_level():