Added a sample.env

Moved incidents code to its own code
Fixed errors that occur with that move.
This commit is contained in:
Dan 2025-01-29 22:01:59 -05:00
parent 87fa9c6aee
commit 33f0ce3a45
5 changed files with 419 additions and 364 deletions

294
commands/incidents.py Normal file
View File

@ -0,0 +1,294 @@
import discord
from discord import app_commands
from typing import Optional, List
from datetime import datetime, timedelta
import sqlite3
import logging
import uuid
from utils.database import Database
from discord.app_commands import MissingPermissions
db = Database()
class IncidentModal(discord.ui.Modal):
def __init__(self):
super().__init__(title="Log Incident")
self.reason = discord.ui.TextInput(
label="Reason for logging",
style=discord.TextStyle.long,
required=True
)
self.message_count = discord.ui.TextInput(
label="Recent messages to capture (1-50)",
placeholder="Leave blank to use timeframe",
default="",
required=False
)
self.start_time = discord.ui.TextInput(
label="Start time (YYYY-MM-DD HH:MM)",
placeholder="Optional - Example: 2024-05-10 14:30",
required=False
)
self.end_time = discord.ui.TextInput(
label="End time (YYYY-MM-DD HH:MM)",
placeholder="Optional - Example: 2024-05-10 15:00",
required=False
)
self.add_item(self.reason)
self.add_item(self.message_count)
self.add_item(self.start_time)
self.add_item(self.end_time)
async def on_submit(self, interaction: discord.Interaction):
try:
messages = []
capture_mode = "count"
capture_param = ""
start_time = None
end_time = None
if self.start_time.value or self.end_time.value:
if not all([self.start_time.value, self.end_time.value]):
raise ValueError("Both start and end times required for timeframe")
start_time = datetime.strptime(self.start_time.value, "%Y-%m-%d %H:%M")
end_time = datetime.strptime(self.end_time.value, "%Y-%m-%d %H:%M")
if start_time >= end_time:
raise ValueError("End time must be after start time")
if (end_time - start_time).total_seconds() > 86400:
raise ValueError("Maximum timeframe duration is 24 hours")
async for msg in interaction.channel.history(
limit=None,
after=start_time,
before=end_time
):
messages.append(msg)
messages = messages[::-1]
capture_mode = "timeframe"
capture_param = f"{start_time.strftime('%Y-%m-%d %H:%M')} to {end_time.strftime('%Y-%m-%d %H:%M')}"
else:
if not self.message_count.value:
raise ValueError("Please provide either message count or timeframe")
count = int(self.message_count.value)
if not 1 <= count <= 50:
raise ValueError("Message count must be between 1-50")
messages = [
msg async for msg in interaction.channel.history(limit=count)
][::-1]
capture_mode = "count"
capture_param = str(count)
formatted_messages = [{
"id": msg.id,
"author_id": msg.author.id,
"content": msg.content,
"timestamp": msg.created_at
} for msg in messages]
incident_id = f"incident_{uuid.uuid4().hex[:8]}"
success = db.add_incident(
incident_id=incident_id,
reason=self.reason.value,
moderator_id=interaction.user.id,
messages=formatted_messages,
capture_mode=capture_mode,
capture_param=capture_param,
start_time=start_time,
end_time=end_time
)
if not success:
raise Exception("Database storage failed - check server logs")
embed = discord.Embed(
title="✅ Incident Logged",
description=f"**ID:** `{incident_id}`\n**Mode:** {capture_mode.title()}",
color=0x00ff00
)
embed.add_field(name="Reason", value=self.reason.value[:500], inline=False)
if messages:
preview = f"{messages[0].content[:100]}..." if len(messages[0].content) > 100 else messages[0].content
embed.add_field(name="First Message", value=preview, inline=False)
await interaction.response.send_message(embed=embed, ephemeral=True)
except ValueError as e:
await interaction.response.send_message(f"❌ Validation Error: {str(e)}", ephemeral=True)
except Exception as e:
logging.error(f"Incident submission error: {str(e)}", exc_info=True)
await interaction.response.send_message("⚠️ Failed to log incident - please check input format", ephemeral=True)
class FollowupModal(discord.ui.Modal):
def __init__(self, incident_id: str):
super().__init__(title=f"Follow-up: {incident_id}")
self.incident_id = incident_id
self.notes = discord.ui.TextInput(
label="Additional notes/actions",
style=discord.TextStyle.long,
required=True
)
self.add_item(self.notes)
async def on_submit(self, interaction: discord.Interaction):
try:
success = db.add_followup(
incident_id=self.incident_id,
moderator_id=interaction.user.id,
notes=self.notes.value
)
if success:
await interaction.response.send_message(f"✅ Follow-up added to **{self.incident_id}**", ephemeral=True)
else:
await interaction.response.send_message("❌ Failed to save follow-up", ephemeral=True)
except Exception as e:
logging.error(f"Followup error: {str(e)}")
await interaction.response.send_message("⚠️ Failed to add follow-up", ephemeral=True)
def setup(client):
incidents_group = app_commands.Group(name="incidents", description="Manage server incidents")
# Add all commands to the group
@incidents_group.command(name="log", description="Log a new incident")
@app_commands.checks.has_permissions(manage_messages=True)
async def incident_log(interaction: discord.Interaction):
await interaction.response.send_modal(IncidentModal())
@incidents_group.command(name="review", description="Review a logged incident")
@app_commands.describe(incident_id="The incident ID to review")
@app_commands.checks.has_permissions(manage_messages=True)
async def review_incident(interaction: discord.Interaction, incident_id: str):
try:
incident = db.get_incident(incident_id)
if not incident:
await interaction.response.send_message("❌ Incident not found", ephemeral=True)
return
messages = "\n\n".join(
f"**<t:{int(msg['timestamp'].timestamp())}:F>** <@{msg['author_id']}>:\n{msg['content']}"
for msg in incident['messages']
)
followups = db.get_followups(incident_id)
followup_text = "\n\n".join(
f"**<t:{int(f['timestamp'].timestamp())}:f>** <@{f['moderator_id']}>:\n{f['notes'][:200]}"
for f in followups
) if followups else "No follow-up reports yet"
moderator = await interaction.guild.fetch_member(incident['details']['moderator_id'])
embed = discord.Embed(
title=f"Incident {incident_id}",
description=(
f"**Reason:** {incident['details']['reason']}\n"
f"**Logged by:** {moderator.mention}\n"
f"**When:** <t:{int(incident['details']['timestamp'].timestamp())}:F>"
),
color=0xff0000
)
embed.add_field(name="Messages", value=messages[:1020] + "..." if len(messages) > 1024 else messages, inline=False)
embed.add_field(name=f"Follow-ups ({len(followups)})", value=followup_text[:1020] + "..." if len(followup_text) > 1024 else followup_text, inline=False)
await interaction.response.send_message(embed=embed, ephemeral=True)
except Exception as e:
logging.error(f"Incident review error: {str(e)}", exc_info=True)
await interaction.response.send_message("❌ Failed to retrieve incident", ephemeral=True)
@incidents_group.command(name="followup", description="Add follow-up to an incident")
@app_commands.describe(incident_id="The incident ID to follow up on", notes="Quick note (optional)")
@app_commands.checks.has_permissions(manage_messages=True)
async def add_followup(interaction: discord.Interaction, incident_id: str, notes: Optional[str] = None):
try:
if not db.get_incident(incident_id):
await interaction.response.send_message("❌ Incident not found", ephemeral=True)
return
if notes:
success = db.add_followup(incident_id, interaction.user.id, notes)
if success:
await interaction.response.send_message(f"✅ Added quick follow-up to **{incident_id}**", ephemeral=True)
else:
await interaction.response.send_message("❌ Failed to add follow-up", ephemeral=True)
else:
await interaction.response.send_modal(FollowupModal(incident_id))
except Exception as e:
logging.error(f"Followup error: {e}")
await interaction.response.send_message("⚠️ Failed to add follow-up", ephemeral=True)
@incidents_group.command(name="audit", description="View unauthorized access attempts (Admin only)")
@app_commands.describe(days="Lookback period in days (max 30)")
@app_commands.checks.has_permissions(administrator=True)
async def view_audit_log(interaction: discord.Interaction, days: int = 7):
try:
if days > 30 or days < 1:
raise ValueError("Lookback period must be 1-30 days")
cutoff = datetime.now() - timedelta(days=days)
with db._get_connection() as conn:
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
cursor.execute("SELECT * FROM unauthorized_access WHERE timestamp > ? ORDER BY timestamp DESC", (cutoff.isoformat(),))
results = [dict(row) for row in cursor.fetchall()]
if not results:
await interaction.response.send_message("✅ No unauthorized access attempts found", ephemeral=True)
return
log_text = "\n".join(
f"<t:{int(datetime.fromisoformat(row['timestamp']).timestamp())}:f> | "
f"<@{row['user_id']}> tried `{row['command_used']}`"
for row in results
)
embed = discord.Embed(
title=f"Unauthorized Access Logs ({days} days)",
description=log_text[:4000],
color=0xff0000
)
await interaction.response.send_message(embed=embed, ephemeral=True)
except Exception as e:
await interaction.response.send_message(f"❌ Error: {str(e)}", ephemeral=True)
@review_incident.autocomplete("incident_id")
@add_followup.autocomplete("incident_id")
async def incident_autocomplete(interaction: discord.Interaction, current: str) -> List[app_commands.Choice[str]]:
incidents = db.get_recent_incidents(25)
choices = []
for inc in incidents:
try:
mod = await interaction.guild.fetch_member(inc['moderator_id'])
name = f"{inc['id']} (by {mod.display_name})"
except:
name = inc['id']
if current.lower() in name.lower():
choices.append(app_commands.Choice(name=name, value=inc['id']))
return choices[:25]
@client.tree.error
async def handle_incident_errors(interaction: discord.Interaction, error):
if isinstance(error, MissingPermissions):
db.log_unauthorized_access(
user_id=interaction.user.id,
command_used=interaction.command.name if interaction.command else "unknown",
details=f"Attempted params: {interaction.data}"
)
await interaction.response.send_message("⛔ You don't have permission to use this command.", ephemeral=True)
else:
logging.error(f"Command error: {error}", exc_info=True)
await interaction.response.send_message("⚠️ An unexpected error occurred. This has been logged.", ephemeral=True)
client.tree.add_command(incidents_group)

View File

@ -1,181 +1,110 @@
import discord
from discord import app_commands
from typing import Optional, List
from datetime import datetime, timedelta
import sqlite3
from discord.ext import tasks
from typing import Optional
from datetime import datetime, timedelta, time, timezone
import logging
import os
from utils.database import Database
from discord.app_commands import MissingPermissions
db = Database()
class IncidentModal(discord.ui.Modal):
def __init__(self):
super().__init__(title="Log Incident")
self.reason = discord.ui.TextInput(
label="Reason for logging",
style=discord.TextStyle.long,
required=True
)
self.message_count = discord.ui.TextInput(
label="Recent messages to capture (1-50)",
placeholder="Leave blank to use timeframe",
default="",
required=False
)
self.start_time = discord.ui.TextInput(
label="Start time (YYYY-MM-DD HH:MM)",
placeholder="Optional - Example: 2024-05-10 14:30",
required=False
)
self.end_time = discord.ui.TextInput(
label="End time (YYYY-MM-DD HH:MM)",
placeholder="Optional - Example: 2024-05-10 15:00",
required=False
)
class FunnyMoments:
def __init__(self, client):
self.client = client
self.retention_days = 30
self.highlight_channel = None # Will be set in on_ready
self.add_item(self.reason)
self.add_item(self.message_count)
self.add_item(self.start_time)
self.add_item(self.end_time)
# Start scheduled tasks
self.weekly_highlight.start()
self.daily_purge.start()
async def on_submit(self, interaction: discord.Interaction):
async def on_ready(self):
"""Initialize channel after bot is ready"""
channel_id = int(os.getenv("HIGHLIGHT_CHANNEL_ID"))
self.highlight_channel = self.client.get_channel(channel_id)
if not self.highlight_channel:
logging.error("Invalid highlight channel ID in .env")
@tasks.loop(time=time(hour=9, minute=0, tzinfo=timezone.utc))
async def weekly_highlight(self):
try:
messages = []
capture_mode = "count"
capture_param = ""
start_time = None
end_time = None
# Only run on Mondays (0 = Monday)
if datetime.now(timezone.utc).weekday() != 0:
return
# Determine capture mode
if self.start_time.value or self.end_time.value:
if not all([self.start_time.value, self.end_time.value]):
raise ValueError("Both start and end times required for timeframe")
start_date = datetime.now(timezone.utc) - timedelta(days=7)
moments = db.get_funny_moments_since(start_date)
start_time = datetime.strptime(self.start_time.value, "%Y-%m-%d %H:%M")
end_time = datetime.strptime(self.end_time.value, "%Y-%m-%d %H:%M")
if start_time >= end_time:
raise ValueError("End time must be after start time")
if (end_time - start_time).total_seconds() > 86400:
raise ValueError("Maximum timeframe duration is 24 hours")
async for msg in interaction.channel.history(
limit=None,
after=start_time,
before=end_time
):
messages.append(msg)
messages = messages[::-1] # Oldest first
capture_mode = "timeframe"
capture_param = f"{start_time.strftime('%Y-%m-%d %H:%M')} to {end_time.strftime('%Y-%m-%d %H:%M')}"
else:
if not self.message_count.value:
raise ValueError("Please provide either message count or timeframe")
count = int(self.message_count.value)
if not 1 <= count <= 50:
raise ValueError("Message count must be between 1-50")
messages = [
msg async for msg in interaction.channel.history(limit=count)
][::-1]
capture_mode = "count"
capture_param = str(count)
# Format messages
formatted_messages = [{
"id": msg.id,
"author_id": msg.author.id,
"content": msg.content,
"timestamp": msg.created_at
} for msg in messages]
# Generate unique ID
incident_id = f"incident_{uuid.uuid4().hex[:8]}"
success = db.add_incident(
incident_id=incident_id,
reason=self.reason.value,
moderator_id=interaction.user.id,
messages=formatted_messages,
capture_mode=capture_mode,
capture_param=capture_param,
start_time=start_time,
end_time=end_time
)
if not success:
raise Exception("Database storage failed - check server logs")
if not moments or not self.highlight_channel:
return
embed = discord.Embed(
title="✅ Incident Logged",
description=f"**ID:** `{incident_id}`\n**Mode:** {capture_mode.title()}",
title="😂 Weekly Funny Moments Highlight",
color=0x00ff00
)
embed.add_field(name="Reason", value=self.reason.value[:500], inline=False)
if messages:
preview = f"{messages[0].content[:100]}..." if len(messages[0].content) > 100 else messages[0].content
embed.add_field(name="First Message", value=preview, inline=False)
await interaction.response.send_message(embed=embed, ephemeral=True)
except ValueError as e:
await interaction.response.send_message(
f"❌ Validation Error: {str(e)}",
ephemeral=True
for idx, moment in enumerate(moments[:5], 1):
embed.add_field(
name=f"Moment #{idx}",
value=f"[Jump to Message]({moment['message_link']})",
inline=False
)
await self.highlight_channel.send(embed=embed)
except Exception as e:
logging.error(f"Incident submission error: {str(e)}", exc_info=True)
await interaction.response.send_message(
"⚠️ Failed to log incident - please check input format",
ephemeral=True
)
logging.error(f"Weekly highlight error: {str(e)}")
class FollowupModal(discord.ui.Modal):
def __init__(self, incident_id: str):
super().__init__(title=f"Follow-up: {incident_id}")
self.incident_id = incident_id
self.notes = discord.ui.TextInput(
label="Additional notes/actions",
style=discord.TextStyle.long,
required=True
)
self.add_item(self.notes)
async def on_submit(self, interaction: discord.Interaction):
@tasks.loop(hours=24)
async def daily_purge(self):
try:
success = db.add_followup(
incident_id=self.incident_id,
moderator_id=interaction.user.id,
notes=self.notes.value
)
if success:
await interaction.response.send_message(
f"✅ Follow-up added to **{self.incident_id}**",
ephemeral=True
)
else:
await interaction.response.send_message(
"❌ Failed to save follow-up",
ephemeral=True
)
cutoff = datetime.utcnow() - timedelta(days=self.retention_days)
deleted_count = db.purge_old_funny_moments(cutoff)
logging.info(f"Purged {deleted_count} old funny moments")
except Exception as e:
logging.error(f"Followup error: {str(e)}")
logging.error(f"Purge error: {str(e)}")
@weekly_highlight.before_loop
@daily_purge.before_loop
async def before_tasks(self):
await self.client.wait_until_ready()
def setup(client):
# Create command group
moments_group = app_commands.Group(name="moments", description="Manage funny moments")
# Add purge command to the group
@moments_group.command(
name="purge",
description="Purge old funny moments"
)
@app_commands.describe(days="Purge moments older than X days (0 to disable)")
@app_commands.checks.has_permissions(manage_messages=True)
async def purge_funny(interaction: discord.Interaction, days: int = 30):
try:
if days < 0:
raise ValueError("Days must be >= 0")
cutoff = datetime.now(timezone.utc) - timedelta(days=days)
deleted_count = db.purge_old_funny_moments(cutoff)
await interaction.response.send_message(
"⚠️ Failed to add follow-up",
f"✅ Purged {deleted_count} moments older than {days} days",
ephemeral=True
)
except Exception as e:
await interaction.response.send_message(
f"❌ Error: {str(e)}",
ephemeral=True
)
# Create FunnyMoments instance
funny_moments = FunnyMoments(client)
async def setup(client):
# Context menu command
@client.tree.context_menu(name="Mark as Funny Moment")
async def mark_funny(interaction: discord.Interaction, message: discord.Message):
@ -202,10 +131,7 @@ async def setup(client):
inline=False
)
await interaction.response.send_message(
embed=embed,
ephemeral=True
)
await interaction.response.send_message(embed=embed, ephemeral=True)
except Exception as e:
logging.error(f"Context menu error: {e}")
@ -214,213 +140,27 @@ async def setup(client):
ephemeral=True
)
# Command group
moments_group = app_commands.Group(
name="moments",
description="Manage memorable moments"
)
# Incident command
@moments_group.command(
name="incident",
description="Log an incident with recent messages"
)
# Purge command
@app_commands.command(name="purge_funny")
@app_commands.describe(days="Purge moments older than X days (0 to disable)")
@app_commands.checks.has_permissions(manage_messages=True)
async def incident_log(interaction: discord.Interaction):
await interaction.response.send_modal(IncidentModal())
# Review command
@moments_group.command(
name="review",
description="Review a logged incident"
)
@app_commands.describe(incident_id="The incident ID to review")
@app_commands.checks.has_permissions(manage_messages=True)
async def review_incident(interaction: discord.Interaction, incident_id: str):
async def purge_funny(interaction: discord.Interaction, days: int = 30):
try:
incident = db.get_incident(incident_id)
if not incident:
await interaction.response.send_message("❌ Incident not found", ephemeral=True)
return
if days < 0:
raise ValueError("Days must be >= 0")
# Format messages with proper timestamps
messages = "\n\n".join(
f"**<t:{int(msg['timestamp'].timestamp())}:F>** <@{msg['author_id']}>:\n"
f"{msg['content']}"
for msg in incident['messages']
cutoff = datetime.utcnow() - timedelta(days=days)
deleted_count = db.purge_old_funny_moments(cutoff)
await interaction.response.send_message(
f"✅ Purged {deleted_count} moments older than {days} days",
ephemeral=True
)
# Get follow-ups with proper timestamps
followups = db.get_followups(incident_id)
followup_text = "\n\n".join(
f"**<t:{int(f['timestamp'].timestamp())}:f>** <@{f['moderator_id']}>:\n{f['notes'][:200]}"
for f in followups
) if followups else "No follow-up reports yet"
# Create embed
moderator = await interaction.guild.fetch_member(incident['details']['moderator_id'])
embed = discord.Embed(
title=f"Incident {incident_id}",
description=(
f"**Reason:** {incident['details']['reason']}\n"
f"**Logged by:** {moderator.mention}\n"
f"**When:** <t:{int(incident['details']['timestamp'].timestamp())}:F>"
),
color=0xff0000
)
embed.add_field(
name="Messages",
value=messages[:1020] + "..." if len(messages) > 1024 else messages,
inline=False
)
embed.add_field(
name=f"Follow-ups ({len(followups)})",
value=followup_text[:1020] + "..." if len(followup_text) > 1024 else followup_text,
inline=False
)
await interaction.response.send_message(embed=embed, ephemeral=True)
except Exception as e:
logging.error(f"Incident review error: {str(e)}", exc_info=True)
await interaction.response.send_message("❌ Failed to retrieve incident", ephemeral=True)
# Followup commands
@moments_group.command(
name="followup",
description="Add follow-up to an incident"
)
@app_commands.describe(
incident_id="The incident ID to follow up on",
notes="Quick note (optional)"
)
@app_commands.checks.has_permissions(manage_messages=True)
async def add_followup(
interaction: discord.Interaction,
incident_id: str,
notes: Optional[str] = None
):
try:
if not db.get_incident(incident_id):
await interaction.response.send_message(
"❌ Incident not found",
f"❌ Error: {str(e)}",
ephemeral=True
)
return
if notes:
success = db.add_followup(
incident_id=incident_id,
moderator_id=interaction.user.id,
notes=notes
)
if success:
await interaction.response.send_message(
f"✅ Added quick follow-up to **{incident_id}**",
ephemeral=True
)
else:
await interaction.response.send_message(
"❌ Failed to add follow-up",
ephemeral=True
)
else:
await interaction.response.send_modal(FollowupModal(incident_id))
except Exception as e:
logging.error(f"Followup error: {e}")
await interaction.response.send_message(
"⚠️ Failed to add follow-up",
ephemeral=True
)
# Autocomplete
@review_incident.autocomplete("incident_id")
@add_followup.autocomplete("incident_id")
async def incident_autocomplete(
interaction: discord.Interaction,
current: str
) -> List[app_commands.Choice[str]]:
incidents = db.get_recent_incidents(25)
choices = []
for inc in incidents:
try:
mod = await interaction.guild.fetch_member(inc['moderator_id'])
name = f"{inc['id']} (by {mod.display_name})"
except:
name = inc['id']
if current.lower() in name.lower():
choices.append(app_commands.Choice(name=name, value=inc['id']))
return choices[:25]
# Global error handler for commands
@client.tree.error
async def on_command_error(interaction: discord.Interaction, error):
"""Handle unauthorized access attempts"""
if isinstance(error, MissingPermissions):
# Log unauthorized access
db.log_unauthorized_access(
user_id=interaction.user.id,
command_used=interaction.command.name if interaction.command else "unknown",
details=f"Attempted params: {interaction.data}"
)
await interaction.response.send_message(
"⛔ You don't have permission to use this command.",
ephemeral=True
)
else:
logging.error(f"Command error: {error}", exc_info=True)
await interaction.response.send_message(
"⚠️ An unexpected error occurred. This has been logged.",
ephemeral=True
)
@moments_group.command(
name="audit",
description="View unauthorized access attempts (Admin only)"
)
@app_commands.describe(days="Lookback period in days (max 30)")
@app_commands.checks.has_permissions(administrator=True)
async def view_audit_log(interaction: discord.Interaction, days: int = 7):
try:
if days > 30 or days < 1:
raise ValueError("Lookback period must be 1-30 days")
cutoff = datetime.now() - timedelta(days=days)
with db._get_connection() as conn:
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
cursor.execute("""
SELECT * FROM unauthorized_access
WHERE timestamp > ?
ORDER BY timestamp DESC
""", (cutoff.isoformat(),)) # Store and compare ISO format
results = [dict(row) for row in cursor.fetchall()]
if not results:
await interaction.response.send_message("✅ No unauthorized access attempts found", ephemeral=True)
return
log_text = "\n".join(
f"<t:{int(datetime.fromisoformat(row['timestamp']).timestamp())}:f> | "
f"<@{row['user_id']}> tried `{row['command_used']}`"
for row in results
)
embed = discord.Embed(
title=f"Unauthorized Access Logs ({days} days)",
description=log_text[:4000],
color=0xff0000
)
await interaction.response.send_message(embed=embed, ephemeral=True)
except Exception as e:
await interaction.response.send_message(f"❌ Error: {str(e)}", ephemeral=True)
client.tree.add_command(moments_group)

19
main.py
View File

@ -1,4 +1,3 @@
import os
import logging
import discord
from discord import app_commands
@ -10,6 +9,7 @@ from pydantic_settings import BaseSettings
class Settings(BaseSettings):
DISCORD_TOKEN: str
DISCORD_GUILD: int
HIGHLIGHT_CHANNEL_ID: int
class Config:
env_file = ".env"
@ -37,9 +37,13 @@ class EmeraldClient(discord.Client):
self.tree = app_commands.CommandTree(self)
async def setup_hook(self):
# Load commands
# Load command modules
from commands.moments import setup as moments_setup
await moments_setup(self)
from commands.incidents import setup as incidents_setup
# Initialize command groups
moments_setup(self)
incidents_setup(self)
# Sync commands
guild = discord.Object(id=config.DISCORD_GUILD)
@ -59,13 +63,4 @@ if __name__ == "__main__":
load_dotenv()
client = EmeraldClient()
# Global error handler
@client.tree.error
async def on_error(interaction: discord.Interaction, error):
logging.error(f"Error: {error}")
await interaction.response.send_message(
"⚠️ Something went wrong. Please try again.",
ephemeral=True
)
client.run(config.DISCORD_TOKEN)

3
sample.env Normal file
View File

@ -0,0 +1,3 @@
DISCORD_TOKEN="YOUR_TOKEN_HERE"
DISCORD_GUILD="YOUR_GUILD_ID"
HIGHLIGHT_CHANNEL_ID="YOUR_CHANNEL_ID"

View File

@ -196,6 +196,29 @@ class Database:
for row in cursor.fetchall()
]
def get_funny_moments_since(self, start_date: datetime) -> List[Dict]:
"""Get funny moments since specified date"""
with self._get_connection() as conn:
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
cursor.execute("""
SELECT * FROM funny_moments
WHERE timestamp > ?
ORDER BY timestamp DESC
""", (start_date,))
return [dict(row) for row in cursor.fetchall()]
def purge_old_funny_moments(self, cutoff_date: datetime) -> int:
"""Delete funny moments older than specified date"""
with self._get_connection() as conn:
cursor = conn.cursor()
cursor.execute("""
DELETE FROM funny_moments
WHERE timestamp < ?
""", (cutoff_date,))
conn.commit()
return cursor.rowcount
def log_unauthorized_access(self, user_id: int, command_used: str, details: str = ""):
"""Log unauthorized command attempts"""
try: