Fixed it so follow-up notes can be added to any incidents

This commit is contained in:
advtech 2025-01-29 17:56:15 -05:00
parent 979e7c74d5
commit 02155f3e0f
2 changed files with 173 additions and 51 deletions

View File

@ -8,6 +8,16 @@ from utils.database import Database
db = 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 import discord
from discord import app_commands from discord import app_commands
from typing import Optional, List from typing import Optional, List
@ -123,7 +133,7 @@ class IncidentModal(discord.ui.Modal):
embed = discord.Embed( embed = discord.Embed(
title="✅ Incident Logged", title="✅ Incident Logged",
description=f"**ID:** `{incident_id}`\n**Mode:** {capture_mode.title()}", 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) embed.add_field(name="Reason", value=self.reason.value[:500], inline=False)
@ -145,6 +155,41 @@ class IncidentModal(discord.ui.Modal):
ephemeral=True 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): async def setup(client):
# Context menu command # Context menu command
@ -205,31 +250,30 @@ async def setup(client):
name="review", name="review",
description="Review a logged incident" description="Review a logged incident"
) )
@app_commands.describe( @app_commands.describe(incident_id="The incident ID to review")
incident_id="The incident ID to review"
)
@app_commands.checks.has_permissions(manage_messages=True) @app_commands.checks.has_permissions(manage_messages=True)
async def review_incident( async def review_incident(interaction: discord.Interaction, incident_id: str):
interaction: discord.Interaction,
incident_id: str
):
try: try:
incident = db.get_incident(incident_id) incident = db.get_incident(incident_id)
if not incident: if not incident:
await interaction.response.send_message( await interaction.response.send_message("❌ Incident not found", ephemeral=True)
"❌ Incident not found",
ephemeral=True
)
return return
# Format messages
messages = "\n\n".join( messages = "\n\n".join(
f"**{msg['timestamp']}** <@{msg['author_id']}>:\n" f"**<t:{int(msg['timestamp'].timestamp())}:F>** <@{msg['author_id']}>:\n{msg['content']}"
f"{msg['content']}"
for msg in incident['messages'] 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"**<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( embed = discord.Embed(
title=f"Incident {incident_id}", title=f"Incident {incident_id}",
description=( description=(
@ -239,39 +283,82 @@ async def setup(client):
), ),
color=0xff0000 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( await interaction.response.send_message(embed=embed, ephemeral=True)
name="Messages",
value=messages[:1020] + "..." if len(messages) > 1024 else messages,
inline=False
)
await interaction.response.send_message(
embed=embed,
ephemeral=True
)
except Exception as e: 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( await interaction.response.send_message(
"❌ Failed to retrieve incident", "⚠️ Failed to add follow-up",
ephemeral=True ephemeral=True
) )
# Autocomplete # Autocomplete
@review_incident.autocomplete("incident_id") @review_incident.autocomplete("incident_id")
@add_followup.autocomplete("incident_id")
async def incident_autocomplete( async def incident_autocomplete(
interaction: discord.Interaction, interaction: discord.Interaction,
current: str current: str
) -> List[app_commands.Choice[str]]: ) -> List[app_commands.Choice[str]]:
# Changed to get all incidents, not just current mod's
incidents = db.get_recent_incidents(25) incidents = db.get_recent_incidents(25)
return [ choices = []
app_commands.Choice( for inc in incidents:
name=f"{inc['id']} (by {await interaction.guild.fetch_member(inc['moderator_id'])})", try:
value=inc['id'] mod = await interaction.guild.fetch_member(inc['moderator_id'])
) name = f"{inc['id']} (by {mod.display_name})"
for inc in incidents if current.lower() in inc['id'].lower() except:
][:25] 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)

View File

@ -55,6 +55,17 @@ class Database:
FOREIGN KEY (incident_id) REFERENCES incidents(id) 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() conn.commit()
def _get_connection(self): def _get_connection(self):
@ -120,27 +131,25 @@ class Database:
conn.row_factory = sqlite3.Row conn.row_factory = sqlite3.Row
cursor = conn.cursor() cursor = conn.cursor()
# Get incident details # Get incident details and parse timestamp
cursor.execute(""" cursor.execute("SELECT * FROM incidents WHERE id = ?", (incident_id,))
SELECT * FROM incidents
WHERE id = ?
""", (incident_id,))
incident = cursor.fetchone() incident = cursor.fetchone()
if not incident: if not incident:
return None return None
# Get related messages incident_details = dict(incident)
cursor.execute(""" incident_details['timestamp'] = datetime.fromisoformat(incident_details['timestamp']) # Convert string to datetime
SELECT * FROM incident_messages
WHERE incident_id = ? # Get messages with parsed timestamps
ORDER BY timestamp ASC cursor.execute("SELECT * FROM incident_messages WHERE incident_id = ?", (incident_id,))
""", (incident_id,)) messages = [
messages = cursor.fetchall() {**dict(msg), 'timestamp': datetime.fromisoformat(msg['timestamp'])}
for msg in cursor.fetchall()
]
return { return {
"details": dict(incident), "details": incident_details,
"messages": [dict(msg) for msg in messages] "messages": messages
} }
def get_recent_incidents(self, limit: int = 25): def get_recent_incidents(self, limit: int = 25):
@ -154,3 +163,29 @@ class Database:
LIMIT ? LIMIT ?
""", (limit,)) """, (limit,))
return [dict(row) for row in cursor.fetchall()] 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()
]