diff --git a/commands/moments.py b/commands/moments.py index 7f3f509..3fbe433 100644 --- a/commands/moments.py +++ b/commands/moments.py @@ -8,6 +8,16 @@ from utils.database import Database db = Database() +import discord +from discord import app_commands +from typing import Optional, List +from datetime import datetime +import logging +import uuid +from utils.database import Database + +db = Database() + import discord from discord import app_commands from typing import Optional, List @@ -123,7 +133,7 @@ class IncidentModal(discord.ui.Modal): embed = discord.Embed( title="✅ Incident Logged", description=f"**ID:** `{incident_id}`\n**Mode:** {capture_mode.title()}", - color=0xff0000 + color=0x00ff00 ) embed.add_field(name="Reason", value=self.reason.value[:500], inline=False) @@ -145,6 +155,41 @@ class IncidentModal(discord.ui.Modal): 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 + ) async def setup(client): # Context menu command @@ -205,31 +250,30 @@ async def setup(client): name="review", description="Review a logged incident" ) - @app_commands.describe( - incident_id="The incident ID to review" - ) + @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 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 - ) + await interaction.response.send_message("❌ Incident not found", ephemeral=True) return + # Format messages messages = "\n\n".join( - f"**{msg['timestamp']}** <@{msg['author_id']}>:\n" - f"{msg['content']}" + f"**** <@{msg['author_id']}>:\n{msg['content']}" for msg in incident['messages'] ) - moderator = await interaction.guild.fetch_member(incident['details']['moderator_id']) + # Format follow-ups + followups = db.get_followups(incident_id) + followup_text = "\n\n".join( + 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=( @@ -239,39 +283,82 @@ async def setup(client): ), 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) - embed.add_field( - name="Messages", - value=messages[:1020] + "..." if len(messages) > 1024 else messages, - 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"Incident review error: {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", + 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 retrieve incident", + "⚠️ 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]]: - # Changed to get all incidents, not just current mod's incidents = db.get_recent_incidents(25) - return [ - app_commands.Choice( - name=f"{inc['id']} (by {await interaction.guild.fetch_member(inc['moderator_id'])})", - value=inc['id'] - ) - for inc in incidents if current.lower() in inc['id'].lower() - ][: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.add_command(moments_group) + client.tree.add_command(moments_group) \ No newline at end of file diff --git a/utils/database.py b/utils/database.py index b0958c0..08710a0 100644 --- a/utils/database.py +++ b/utils/database.py @@ -55,6 +55,17 @@ class Database: FOREIGN KEY (incident_id) REFERENCES incidents(id) ) """) + + conn.execute(""" + CREATE TABLE IF NOT EXISTS incident_followups ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + incident_id TEXT NOT NULL, + moderator_id INTEGER NOT NULL, + notes TEXT NOT NULL, + timestamp DATETIME NOT NULL, + FOREIGN KEY (incident_id) REFERENCES incidents(id) + ) + """) conn.commit() def _get_connection(self): @@ -120,27 +131,25 @@ class Database: conn.row_factory = sqlite3.Row cursor = conn.cursor() - # Get incident details - cursor.execute(""" - SELECT * FROM incidents - WHERE id = ? - """, (incident_id,)) + # Get incident details and parse timestamp + cursor.execute("SELECT * FROM incidents WHERE id = ?", (incident_id,)) incident = cursor.fetchone() - if not incident: return None - # Get related messages - cursor.execute(""" - SELECT * FROM incident_messages - WHERE incident_id = ? - ORDER BY timestamp ASC - """, (incident_id,)) - messages = cursor.fetchall() + incident_details = dict(incident) + incident_details['timestamp'] = datetime.fromisoformat(incident_details['timestamp']) # Convert string to datetime + + # Get messages with parsed timestamps + cursor.execute("SELECT * FROM incident_messages WHERE incident_id = ?", (incident_id,)) + messages = [ + {**dict(msg), 'timestamp': datetime.fromisoformat(msg['timestamp'])} + for msg in cursor.fetchall() + ] return { - "details": dict(incident), - "messages": [dict(msg) for msg in messages] + "details": incident_details, + "messages": messages } def get_recent_incidents(self, limit: int = 25): @@ -154,3 +163,29 @@ class Database: LIMIT ? """, (limit,)) return [dict(row) for row in cursor.fetchall()] + + def add_followup(self, incident_id: str, moderator_id: int, notes: str) -> bool: + """Add a follow-up report to an incident""" + try: + with self._get_connection() as conn: + conn.execute(""" + INSERT INTO incident_followups + (incident_id, moderator_id, notes, timestamp) + VALUES (?, ?, ?, ?) + """, (incident_id, moderator_id, notes, datetime.now())) + conn.commit() + return True + except Exception as e: + logging.error(f"Failed to add followup: {str(e)}") + return False + + def get_followups(self, incident_id: str) -> List[Dict]: + """Get follow-ups with proper timestamps""" + with self._get_connection() as conn: + conn.row_factory = sqlite3.Row + cursor = conn.cursor() + cursor.execute("SELECT * FROM incident_followups WHERE incident_id = ?", (incident_id,)) + return [ + {**dict(row), 'timestamp': datetime.fromisoformat(row['timestamp'])} + for row in cursor.fetchall() + ]