Compare commits
	
		
			9 Commits
		
	
	
		
			main
			...
			Dev-Modula
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					43baa58834 | ||
| 
						 | 
					33f0ce3a45 | ||
| 
						 | 
					87fa9c6aee | ||
| 
						 | 
					02155f3e0f | ||
| 
						 | 
					979e7c74d5 | ||
| 
						 | 
					31913db64f | ||
| 
						 | 
					6f2b13f055 | ||
| 
						 | 
					c4962f2d09 | ||
| 
						 | 
					03f6337e27 | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -172,3 +172,4 @@ cython_debug/
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# PyPI configuration file
 | 
					# PyPI configuration file
 | 
				
			||||||
.pypirc
 | 
					.pypirc
 | 
				
			||||||
 | 
					/data
 | 
				
			||||||
							
								
								
									
										294
									
								
								commands/incidents.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										294
									
								
								commands/incidents.py
									
									
									
									
									
										Normal 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)
 | 
				
			||||||
							
								
								
									
										166
									
								
								commands/moments.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								commands/moments.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,166 @@
 | 
				
			|||||||
 | 
					import discord
 | 
				
			||||||
 | 
					from discord import app_commands
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					db = Database()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FunnyMoments:
 | 
				
			||||||
 | 
					    def __init__(self, client):
 | 
				
			||||||
 | 
					        self.client = client
 | 
				
			||||||
 | 
					        self.retention_days = 30
 | 
				
			||||||
 | 
					        self.highlight_channel = None  # Will be set in on_ready
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Start scheduled tasks
 | 
				
			||||||
 | 
					        self.weekly_highlight.start()
 | 
				
			||||||
 | 
					        self.daily_purge.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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:
 | 
				
			||||||
 | 
					            # Only run on Mondays (0 = Monday)
 | 
				
			||||||
 | 
					            if datetime.now(timezone.utc).weekday() != 0:
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            start_date = datetime.now(timezone.utc) - timedelta(days=7)
 | 
				
			||||||
 | 
					            moments = db.get_funny_moments_since(start_date)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if not moments or not self.highlight_channel:
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            embed = discord.Embed(
 | 
				
			||||||
 | 
					                title="😂 Weekly Funny Moments Highlight",
 | 
				
			||||||
 | 
					                color=0x00ff00
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            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"Weekly highlight error: {str(e)}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @tasks.loop(hours=24)
 | 
				
			||||||
 | 
					    async def daily_purge(self):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            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"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(
 | 
				
			||||||
 | 
					                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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Context menu command
 | 
				
			||||||
 | 
					    @client.tree.context_menu(name="Mark as Funny Moment")
 | 
				
			||||||
 | 
					    async def mark_funny(interaction: discord.Interaction, message: discord.Message):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            await message.add_reaction("😂")
 | 
				
			||||||
 | 
					            await message.add_reaction("🎉")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            message_link = f"https://discord.com/channels/{interaction.guild_id}/{message.channel.id}/{message.id}"
 | 
				
			||||||
 | 
					            record_id = db.add_funny_moment(
 | 
				
			||||||
 | 
					                message_link=message_link,
 | 
				
			||||||
 | 
					                author_id=message.author.id
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            embed = discord.Embed(
 | 
				
			||||||
 | 
					                title="😂 Funny Moment Saved",
 | 
				
			||||||
 | 
					                description=f"[Jump to Message]({message_link})",
 | 
				
			||||||
 | 
					                color=0x00ff00
 | 
				
			||||||
 | 
					            ).set_author(
 | 
				
			||||||
 | 
					                name=message.author.display_name,
 | 
				
			||||||
 | 
					                icon_url=message.author.avatar.url
 | 
				
			||||||
 | 
					            ).add_field(
 | 
				
			||||||
 | 
					                name="Message Preview",
 | 
				
			||||||
 | 
					                value=message.content[:100] + "..." if len(message.content) > 100 else message.content,
 | 
				
			||||||
 | 
					                inline=False
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await interaction.response.send_message(embed=embed, ephemeral=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            logging.error(f"Context menu error: {e}")
 | 
				
			||||||
 | 
					            await interaction.response.send_message(
 | 
				
			||||||
 | 
					                "❌ Couldn't mark this message. Is it too old?",
 | 
				
			||||||
 | 
					                ephemeral=True
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # 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 purge_funny(interaction: discord.Interaction, days: int = 30):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            if days < 0:
 | 
				
			||||||
 | 
					                raise ValueError("Days must be >= 0")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            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
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            await interaction.response.send_message(
 | 
				
			||||||
 | 
					                f"❌ Error: {str(e)}",
 | 
				
			||||||
 | 
					                ephemeral=True
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    client.tree.add_command(moments_group)
 | 
				
			||||||
							
								
								
									
										31
									
								
								main.py
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								main.py
									
									
									
									
									
								
							@@ -1,4 +1,3 @@
 | 
				
			|||||||
import os
 | 
					 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
import discord
 | 
					import discord
 | 
				
			||||||
from discord import app_commands
 | 
					from discord import app_commands
 | 
				
			||||||
@@ -6,9 +5,11 @@ from dotenv import load_dotenv
 | 
				
			|||||||
from pydantic_settings import BaseSettings
 | 
					from pydantic_settings import BaseSettings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Configuration
 | 
				
			||||||
class Settings(BaseSettings):
 | 
					class Settings(BaseSettings):
 | 
				
			||||||
    DISCORD_TOKEN: str
 | 
					    DISCORD_TOKEN: str
 | 
				
			||||||
    DISCORD_GUILD: int
 | 
					    DISCORD_GUILD: int
 | 
				
			||||||
 | 
					    HIGHLIGHT_CHANNEL_ID: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class Config:
 | 
					    class Config:
 | 
				
			||||||
        env_file = ".env"
 | 
					        env_file = ".env"
 | 
				
			||||||
@@ -17,7 +18,8 @@ class Settings(BaseSettings):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
config = Settings()
 | 
					config = Settings()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Initialize logging
 | 
					
 | 
				
			||||||
 | 
					# Logging setup
 | 
				
			||||||
logging.basicConfig(
 | 
					logging.basicConfig(
 | 
				
			||||||
    level=logging.INFO,
 | 
					    level=logging.INFO,
 | 
				
			||||||
    format="%(asctime)s | %(levelname)s | %(message)s",
 | 
					    format="%(asctime)s | %(levelname)s | %(message)s",
 | 
				
			||||||
@@ -25,29 +27,40 @@ logging.basicConfig(
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class EmeraldClient(discord.Client):
 | 
					class EsmeraldaClient(discord.Client):
 | 
				
			||||||
    def __init__(self):
 | 
					    def __init__(self):
 | 
				
			||||||
        intents = discord.Intents.default()
 | 
					        intents = discord.Intents.default()
 | 
				
			||||||
        intents.message_content = True
 | 
					        intents.message_content = True
 | 
				
			||||||
 | 
					        intents.messages = True
 | 
				
			||||||
 | 
					        intents.reactions = True
 | 
				
			||||||
        super().__init__(intents=intents)
 | 
					        super().__init__(intents=intents)
 | 
				
			||||||
        self.tree = app_commands.CommandTree(self)
 | 
					        self.tree = app_commands.CommandTree(self)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async def setup_hook(self):
 | 
					    async def setup_hook(self):
 | 
				
			||||||
        """Sync commands with test guild"""
 | 
					        # Load command modules
 | 
				
			||||||
 | 
					        from commands.moments import setup as moments_setup
 | 
				
			||||||
 | 
					        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)
 | 
					        guild = discord.Object(id=config.DISCORD_GUILD)
 | 
				
			||||||
        self.tree.copy_global_to(guild=guild)
 | 
					        self.tree.copy_global_to(guild=guild)
 | 
				
			||||||
        await self.tree.sync(guild=guild)
 | 
					        await self.tree.sync(guild=guild)
 | 
				
			||||||
        logging.info("Commands synced to guild")
 | 
					        logging.info("Commands synced")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async def on_ready(self):
 | 
					    async def on_ready(self):
 | 
				
			||||||
        logging.info(f"Logged in as {self.user} (ID: {self.user.id})")
 | 
					        logging.info(f"Logged in as {self.user}")
 | 
				
			||||||
        await self.change_presence(activity=discord.Activity(
 | 
					        await self.change_presence(activity=discord.Activity(
 | 
				
			||||||
            type=discord.ActivityType.listening,
 | 
					            type=discord.ActivityType.listening,
 | 
				
			||||||
            name="your requests"
 | 
					            name="/moments"
 | 
				
			||||||
        ))
 | 
					        ))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if __name__ == "__main__":
 | 
					if __name__ == "__main__":
 | 
				
			||||||
    load_dotenv()
 | 
					    load_dotenv()
 | 
				
			||||||
    client = EmeraldClient()
 | 
					    client = EsmeraldaClient()
 | 
				
			||||||
    client.run(config.DISCORD_TOKEN)
 | 
					
 | 
				
			||||||
 | 
					    client.run(config.DISCORD_TOKEN)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										3
									
								
								sample.env
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								sample.env
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					DISCORD_TOKEN="YOUR_TOKEN_HERE"
 | 
				
			||||||
 | 
					DISCORD_GUILD="YOUR_GUILD_ID"
 | 
				
			||||||
 | 
					HIGHLIGHT_CHANNEL_ID="YOUR_CHANNEL_ID"
 | 
				
			||||||
							
								
								
									
										235
									
								
								utils/database.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										235
									
								
								utils/database.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,235 @@
 | 
				
			|||||||
 | 
					import sqlite3
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					from datetime import datetime
 | 
				
			||||||
 | 
					from typing import List, Dict, Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Database:
 | 
				
			||||||
 | 
					    def __init__(self, db_path: str = "data/moments.db"):
 | 
				
			||||||
 | 
					        self.db_path = db_path
 | 
				
			||||||
 | 
					        self._init_db()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _init_db(self):
 | 
				
			||||||
 | 
					        """Initialize database tables"""
 | 
				
			||||||
 | 
					        with self._get_connection() as conn:
 | 
				
			||||||
 | 
					            # Drop tables if they exist (for development)
 | 
				
			||||||
 | 
					            conn.execute("DROP TABLE IF EXISTS incidents")
 | 
				
			||||||
 | 
					            conn.execute("DROP TABLE IF EXISTS incident_messages")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            conn.execute("""
 | 
				
			||||||
 | 
					                CREATE TABLE IF NOT EXISTS unauthorized_access (
 | 
				
			||||||
 | 
					                    id INTEGER PRIMARY KEY AUTOINCREMENT,
 | 
				
			||||||
 | 
					                    user_id INTEGER NOT NULL,
 | 
				
			||||||
 | 
					                    command_used TEXT NOT NULL,
 | 
				
			||||||
 | 
					                    timestamp DATETIME NOT NULL,
 | 
				
			||||||
 | 
					                    details TEXT
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            """)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            conn.execute("""
 | 
				
			||||||
 | 
					                CREATE TABLE IF NOT EXISTS funny_moments (
 | 
				
			||||||
 | 
					                    id INTEGER PRIMARY KEY AUTOINCREMENT,
 | 
				
			||||||
 | 
					                    message_link TEXT NOT NULL,
 | 
				
			||||||
 | 
					                    description TEXT,
 | 
				
			||||||
 | 
					                    author_id INTEGER NOT NULL,
 | 
				
			||||||
 | 
					                    timestamp DATETIME NOT NULL
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            """)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            conn.execute("""
 | 
				
			||||||
 | 
					                CREATE TABLE IF NOT EXISTS incidents (
 | 
				
			||||||
 | 
					                    id TEXT PRIMARY KEY,
 | 
				
			||||||
 | 
					                    reason TEXT NOT NULL,
 | 
				
			||||||
 | 
					                    moderator_id INTEGER NOT NULL,
 | 
				
			||||||
 | 
					                    timestamp DATETIME NOT NULL,
 | 
				
			||||||
 | 
					                    capture_mode TEXT NOT NULL,
 | 
				
			||||||
 | 
					                    capture_param TEXT,
 | 
				
			||||||
 | 
					                    start_time DATETIME,
 | 
				
			||||||
 | 
					                    end_time DATETIME
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            """)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            conn.execute("""
 | 
				
			||||||
 | 
					                CREATE TABLE IF NOT EXISTS incident_messages (
 | 
				
			||||||
 | 
					                    incident_id TEXT,
 | 
				
			||||||
 | 
					                    message_id INTEGER,
 | 
				
			||||||
 | 
					                    author_id INTEGER,
 | 
				
			||||||
 | 
					                    content TEXT,
 | 
				
			||||||
 | 
					                    timestamp DATETIME,
 | 
				
			||||||
 | 
					                    PRIMARY KEY (incident_id, message_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()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _get_connection(self):
 | 
				
			||||||
 | 
					        return sqlite3.connect(self.db_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def add_incident(self, incident_id: str, reason: str, moderator_id: int, 
 | 
				
			||||||
 | 
					                    messages: List[Dict], capture_mode: str, capture_param: str,
 | 
				
			||||||
 | 
					                    start_time: datetime = None, end_time: datetime = None) -> bool:
 | 
				
			||||||
 | 
					        """Store an incident with related messages"""
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            with self._get_connection() as conn:
 | 
				
			||||||
 | 
					                # Add incident record
 | 
				
			||||||
 | 
					                conn.execute("""
 | 
				
			||||||
 | 
					                    INSERT INTO incidents 
 | 
				
			||||||
 | 
					                    (id, reason, moderator_id, timestamp, capture_mode, capture_param, start_time, end_time)
 | 
				
			||||||
 | 
					                    VALUES (?, ?, ?, ?, ?, ?, ?, ?)
 | 
				
			||||||
 | 
					                """, (
 | 
				
			||||||
 | 
					                    incident_id,
 | 
				
			||||||
 | 
					                    reason,
 | 
				
			||||||
 | 
					                    moderator_id,
 | 
				
			||||||
 | 
					                    datetime.now(),
 | 
				
			||||||
 | 
					                    capture_mode,
 | 
				
			||||||
 | 
					                    capture_param,
 | 
				
			||||||
 | 
					                    start_time,
 | 
				
			||||||
 | 
					                    end_time
 | 
				
			||||||
 | 
					                ))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # Add incident messages
 | 
				
			||||||
 | 
					                for msg in messages:
 | 
				
			||||||
 | 
					                    conn.execute("""
 | 
				
			||||||
 | 
					                        INSERT INTO incident_messages
 | 
				
			||||||
 | 
					                        (incident_id, message_id, author_id, content, timestamp)
 | 
				
			||||||
 | 
					                        VALUES (?, ?, ?, ?, ?)
 | 
				
			||||||
 | 
					                    """, (
 | 
				
			||||||
 | 
					                        incident_id,
 | 
				
			||||||
 | 
					                        msg['id'],
 | 
				
			||||||
 | 
					                        msg['author_id'],
 | 
				
			||||||
 | 
					                        msg['content'],
 | 
				
			||||||
 | 
					                        msg['timestamp']
 | 
				
			||||||
 | 
					                    ))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                conn.commit()
 | 
				
			||||||
 | 
					                return True
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            logging.error(f"Failed to save incident: {str(e)}")
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def add_funny_moment(self, message_link: str, author_id: int, description: str = None) -> int:
 | 
				
			||||||
 | 
					        """Store a funny moment in database"""
 | 
				
			||||||
 | 
					        with self._get_connection() as conn:
 | 
				
			||||||
 | 
					            cursor = conn.cursor()
 | 
				
			||||||
 | 
					            cursor.execute("""
 | 
				
			||||||
 | 
					                INSERT INTO funny_moments 
 | 
				
			||||||
 | 
					                (message_link, description, author_id, timestamp)
 | 
				
			||||||
 | 
					                VALUES (?, ?, ?, ?)
 | 
				
			||||||
 | 
					            """, (message_link, description, author_id, datetime.now()))
 | 
				
			||||||
 | 
					            conn.commit()
 | 
				
			||||||
 | 
					            return cursor.lastrowid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_incident(self, incident_id: str) -> Optional[Dict]:
 | 
				
			||||||
 | 
					        """Retrieve an incident with its messages"""
 | 
				
			||||||
 | 
					        with self._get_connection() as conn:
 | 
				
			||||||
 | 
					            conn.row_factory = sqlite3.Row
 | 
				
			||||||
 | 
					            cursor = conn.cursor()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Get incident details
 | 
				
			||||||
 | 
					            cursor.execute("SELECT * FROM incidents WHERE id = ?", (incident_id,))
 | 
				
			||||||
 | 
					            incident = cursor.fetchone()
 | 
				
			||||||
 | 
					            if not incident:
 | 
				
			||||||
 | 
					                return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Convert timestamp string to datetime object
 | 
				
			||||||
 | 
					            incident_details = dict(incident)
 | 
				
			||||||
 | 
					            incident_details['timestamp'] = datetime.fromisoformat(incident_details['timestamp'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Get related messages
 | 
				
			||||||
 | 
					            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": incident_details,
 | 
				
			||||||
 | 
					                "messages": messages
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_recent_incidents(self, limit: int = 25):
 | 
				
			||||||
 | 
					        """Get all recent incidents (not limited to current moderator)"""
 | 
				
			||||||
 | 
					        with self._get_connection() as conn:
 | 
				
			||||||
 | 
					            conn.row_factory = sqlite3.Row
 | 
				
			||||||
 | 
					            cursor = conn.cursor()
 | 
				
			||||||
 | 
					            cursor.execute("""
 | 
				
			||||||
 | 
					                SELECT id, moderator_id FROM incidents
 | 
				
			||||||
 | 
					                ORDER BY timestamp DESC
 | 
				
			||||||
 | 
					                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]:
 | 
				
			||||||
 | 
					        """Retrieve 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()
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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:
 | 
				
			||||||
 | 
					            with self._get_connection() as conn:
 | 
				
			||||||
 | 
					                conn.execute("""
 | 
				
			||||||
 | 
					                    INSERT INTO unauthorized_access 
 | 
				
			||||||
 | 
					                    (user_id, command_used, timestamp, details)
 | 
				
			||||||
 | 
					                    VALUES (?, ?, ?, ?)
 | 
				
			||||||
 | 
					                """, (user_id, command_used, datetime.now(), details))
 | 
				
			||||||
 | 
					                conn.commit()
 | 
				
			||||||
 | 
					                return True
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            logging.error(f"Failed to log unauthorized access: {e}")
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
		Reference in New Issue
	
	Block a user