- Deleted .env.example file as it is no longer needed. - Added .gitignore to manage ignored files and directories. - Introduced CLAUDE.md for AI provider integration documentation. - Created dev.sh for development setup and scripts. - Updated Dockerfile and Dockerfile.production for improved build processes. - Added multiple test files and directories for comprehensive testing. - Introduced new utility and service files for enhanced functionality. - Organized codebase with new directories and files for better maintainability.
719 lines
28 KiB
Python
719 lines
28 KiB
Python
"""
|
|
Consent Cog for Discord Voice Chat Quote Bot
|
|
|
|
Handles all consent-related slash commands, privacy controls, and GDPR compliance
|
|
including consent management, data export, deletion, and user rights.
|
|
"""
|
|
|
|
import io
|
|
import json
|
|
import logging
|
|
from datetime import datetime, timezone
|
|
from typing import TYPE_CHECKING, Optional
|
|
|
|
import discord
|
|
from discord import app_commands
|
|
from discord.ext import commands
|
|
|
|
from config.consent_templates import ConsentMessages, ConsentTemplates
|
|
from core.consent_manager import ConsentManager
|
|
from ui.components import DataDeletionView, EmbedBuilder
|
|
|
|
if TYPE_CHECKING:
|
|
from main import QuoteBot
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ConsentCog(commands.Cog):
|
|
"""
|
|
Comprehensive consent and privacy management for the Discord Quote Bot
|
|
|
|
Commands:
|
|
- /give_consent - Grant recording consent
|
|
- /revoke_consent - Revoke consent for current server
|
|
- /opt_out - Global opt-out from all recording
|
|
- /opt_in - Re-enable recording after opt-out
|
|
- /privacy_info - Show detailed privacy information
|
|
- /consent_status - Check your consent status
|
|
- /delete_my_quotes - Delete your quote data
|
|
- /export_my_data - Export your data (GDPR)
|
|
- /gdpr_info - GDPR compliance information
|
|
"""
|
|
|
|
def __init__(self, bot: "QuoteBot") -> None:
|
|
self.bot = bot
|
|
self.consent_manager: ConsentManager = bot.consent_manager # type: ignore[assignment]
|
|
self.db_manager = bot.db_manager
|
|
|
|
@app_commands.command(
|
|
name="give_consent",
|
|
description="Give consent for voice recording in this server",
|
|
)
|
|
@app_commands.describe(
|
|
first_name="Optional: Your preferred first name for quotes (instead of username)"
|
|
)
|
|
async def give_consent(
|
|
self, interaction: discord.Interaction, first_name: Optional[str] = None
|
|
):
|
|
"""Grant recording consent for the current server"""
|
|
try:
|
|
if interaction.guild is None:
|
|
embed = EmbedBuilder.error_embed(
|
|
"Guild Error", "This command can only be used in a server."
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
return
|
|
|
|
user_id = interaction.user.id
|
|
guild_id = interaction.guild.id
|
|
|
|
# Check if user has global opt-out
|
|
if user_id in self.consent_manager.global_opt_outs:
|
|
embed = EmbedBuilder.error_embed(
|
|
"Global Opt-Out Active", ConsentMessages.GLOBAL_OPT_OUT, "warning"
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
return
|
|
|
|
# Check current consent status
|
|
current_consent = await self.consent_manager.check_consent(
|
|
user_id, guild_id
|
|
)
|
|
|
|
if current_consent:
|
|
embed = EmbedBuilder.error_embed(
|
|
"Already Consented", ConsentMessages.ALREADY_CONSENTED, "info"
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
return
|
|
|
|
# Grant consent
|
|
success = await self.consent_manager.grant_consent(
|
|
user_id, guild_id, first_name
|
|
)
|
|
|
|
if success:
|
|
embed = EmbedBuilder.success_embed(
|
|
"Consent Granted", ConsentMessages.CONSENT_GRANTED
|
|
)
|
|
|
|
if first_name:
|
|
embed.add_field(
|
|
name="Preferred Name",
|
|
value=f"Your quotes will be attributed to: **{first_name}**",
|
|
inline=False,
|
|
)
|
|
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
|
|
# Log consent action
|
|
if self.bot.metrics:
|
|
self.bot.metrics.increment(
|
|
"consent_actions",
|
|
{"action": "granted", "guild_id": str(guild_id)},
|
|
)
|
|
|
|
logger.info(f"Consent granted by user {user_id} in guild {guild_id}")
|
|
else:
|
|
embed = EmbedBuilder.error_embed(
|
|
"Consent Failed",
|
|
"Failed to grant consent. Please try again or contact an administrator.",
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error in give_consent command: {e}")
|
|
embed = EmbedBuilder.error_embed(
|
|
"Command Error", "An error occurred while processing your consent."
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
|
|
@app_commands.command(
|
|
name="revoke_consent", description="Revoke recording consent for this server"
|
|
)
|
|
async def revoke_consent(self, interaction: discord.Interaction):
|
|
"""Revoke recording consent for the current server"""
|
|
try:
|
|
if interaction.guild is None:
|
|
embed = EmbedBuilder.error_embed(
|
|
"Guild Error", "This command can only be used in a server."
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
return
|
|
|
|
user_id = interaction.user.id
|
|
guild_id = interaction.guild.id
|
|
|
|
# Check current consent status
|
|
current_consent = await self.consent_manager.check_consent(
|
|
user_id, guild_id
|
|
)
|
|
|
|
if not current_consent:
|
|
embed = EmbedBuilder.error_embed(
|
|
"No Consent to Revoke", ConsentMessages.NOT_CONSENTED, "info"
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
return
|
|
|
|
# Revoke consent
|
|
success = await self.consent_manager.revoke_consent(user_id, guild_id)
|
|
|
|
if success:
|
|
embed = EmbedBuilder.success_embed(
|
|
"Consent Revoked", ConsentMessages.CONSENT_REVOKED
|
|
)
|
|
|
|
embed.add_field(
|
|
name="What's Next?",
|
|
value=(
|
|
"• Your voice will no longer be recorded\n"
|
|
"• Existing quotes remain (use `/delete_my_quotes` to remove)\n"
|
|
"• You can re-consent anytime with `/give_consent`"
|
|
),
|
|
inline=False,
|
|
)
|
|
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
|
|
# Log consent action
|
|
if self.bot.metrics:
|
|
self.bot.metrics.increment(
|
|
"consent_actions",
|
|
{"action": "revoked", "guild_id": str(guild_id)},
|
|
)
|
|
|
|
logger.info(f"Consent revoked by user {user_id} in guild {guild_id}")
|
|
else:
|
|
embed = EmbedBuilder.error_embed(
|
|
"Revocation Failed",
|
|
"Failed to revoke consent. Please try again or contact an administrator.",
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error in revoke_consent command: {e}")
|
|
embed = EmbedBuilder.error_embed(
|
|
"Command Error", "An error occurred while revoking your consent."
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
|
|
@app_commands.command(
|
|
name="opt_out", description="Globally opt out from all voice recording"
|
|
)
|
|
@app_commands.describe(
|
|
global_opt_out="True for global opt-out across all servers, False for this server only"
|
|
)
|
|
async def opt_out(
|
|
self, interaction: discord.Interaction, global_opt_out: bool = True
|
|
):
|
|
"""Global opt-out from all voice recording"""
|
|
try:
|
|
user_id = interaction.user.id
|
|
|
|
if global_opt_out:
|
|
# Global opt-out
|
|
success = await self.consent_manager.set_global_opt_out(user_id, True)
|
|
|
|
if success:
|
|
embed = EmbedBuilder.success_embed(
|
|
"Global Opt-Out Enabled", ConsentMessages.OPT_OUT_MESSAGE
|
|
)
|
|
|
|
embed.add_field(
|
|
name="📊 Data Management",
|
|
value=(
|
|
"Use these commands to manage your data:\n"
|
|
"• `/delete_my_quotes` - Remove quotes from specific servers\n"
|
|
"• `/export_my_data` - Download your data\n"
|
|
"• `/opt_in` - Re-enable recording in the future"
|
|
),
|
|
inline=False,
|
|
)
|
|
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
|
|
# Log opt-out action
|
|
if interaction.guild is not None and self.bot.metrics:
|
|
self.bot.metrics.increment(
|
|
"consent_actions",
|
|
{
|
|
"action": "global_opt_out",
|
|
"guild_id": str(interaction.guild.id),
|
|
},
|
|
)
|
|
|
|
logger.info(f"Global opt-out by user {user_id}")
|
|
else:
|
|
embed = EmbedBuilder.error_embed(
|
|
"Opt-Out Failed",
|
|
"Failed to set global opt-out. Please try again.",
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
else:
|
|
# Server-specific opt-out (same as revoke consent)
|
|
await self._handle_server_consent_revoke(interaction)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error in opt_out command: {e}")
|
|
embed = EmbedBuilder.error_embed(
|
|
"Command Error", "An error occurred while processing your opt-out."
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
|
|
@app_commands.command(
|
|
name="opt_in", description="Re-enable voice recording after global opt-out"
|
|
)
|
|
async def opt_in(self, interaction: discord.Interaction):
|
|
"""Re-enable recording after global opt-out"""
|
|
try:
|
|
user_id = interaction.user.id
|
|
|
|
# Check if user has global opt-out
|
|
if user_id not in self.consent_manager.global_opt_outs:
|
|
embed = EmbedBuilder.error_embed(
|
|
"Not Opted Out",
|
|
"You haven't globally opted out. Use `/give_consent` to enable recording in this server.",
|
|
"info",
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
return
|
|
|
|
# Remove global opt-out
|
|
success = await self.consent_manager.set_global_opt_out(user_id, False)
|
|
|
|
if success:
|
|
embed = EmbedBuilder.success_embed(
|
|
"Global Opt-Out Disabled",
|
|
"✅ **You've opted back into voice recording!**\n\n"
|
|
"You can now give consent in individual servers using `/give_consent`.",
|
|
)
|
|
|
|
embed.add_field(
|
|
name="Next Steps",
|
|
value=(
|
|
"• Use `/give_consent` to enable recording in this server\n"
|
|
"• Your previous consent settings may need to be renewed\n"
|
|
"• Use `/consent_status` to check your current status"
|
|
),
|
|
inline=False,
|
|
)
|
|
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
|
|
# Log opt-in action
|
|
if interaction.guild is not None and self.bot.metrics:
|
|
self.bot.metrics.increment(
|
|
"consent_actions",
|
|
{
|
|
"action": "global_opt_in",
|
|
"guild_id": str(interaction.guild.id),
|
|
},
|
|
)
|
|
|
|
logger.info(f"Global opt-in by user {user_id}")
|
|
else:
|
|
embed = EmbedBuilder.error_embed(
|
|
"Opt-In Failed", "Failed to re-enable recording. Please try again."
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error in opt_in command: {e}")
|
|
embed = EmbedBuilder.error_embed(
|
|
"Command Error", "An error occurred while processing your opt-in."
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
|
|
@app_commands.command(
|
|
name="privacy_info",
|
|
description="View detailed privacy and data handling information",
|
|
)
|
|
async def privacy_info(self, interaction: discord.Interaction):
|
|
"""Show detailed privacy information"""
|
|
try:
|
|
embed = ConsentTemplates.get_privacy_info_embed()
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error in privacy_info command: {e}")
|
|
embed = EmbedBuilder.error_embed(
|
|
"Command Error", "Failed to load privacy information."
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
|
|
@app_commands.command(
|
|
name="consent_status",
|
|
description="Check your current consent and privacy status",
|
|
)
|
|
async def consent_status(self, interaction: discord.Interaction):
|
|
"""Show user's current consent status"""
|
|
try:
|
|
if interaction.guild is None:
|
|
embed = EmbedBuilder.error_embed(
|
|
"Guild Error", "This command can only be used in a server."
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
return
|
|
|
|
user_id = interaction.user.id
|
|
guild_id = interaction.guild.id
|
|
|
|
# Get detailed consent status
|
|
status = await self.consent_manager.get_consent_status(user_id, guild_id)
|
|
|
|
# Build status embed
|
|
embed = discord.Embed(
|
|
title="🔒 Your Privacy Status",
|
|
description=f"Consent and privacy settings for {interaction.user.display_name}",
|
|
color=0x0099FF,
|
|
)
|
|
|
|
# Current consent status
|
|
if status["consent_given"]:
|
|
consent_status = "✅ **Consented** - Voice recording enabled"
|
|
consent_color = "🟢"
|
|
else:
|
|
consent_status = "❌ **Not Consented** - Voice recording disabled"
|
|
consent_color = "🔴"
|
|
|
|
embed.add_field(
|
|
name=f"{consent_color} Recording Consent",
|
|
value=consent_status,
|
|
inline=False,
|
|
)
|
|
|
|
# Global opt-out status
|
|
if status["global_opt_out"]:
|
|
global_status = (
|
|
"🔴 **Global Opt-Out Active** - Recording disabled on all servers"
|
|
)
|
|
else:
|
|
global_status = "🟢 **Global Recording Enabled** - Can consent on individual servers"
|
|
|
|
embed.add_field(name="🌐 Global Status", value=global_status, inline=False)
|
|
|
|
# Consent details
|
|
if status["has_record"]:
|
|
details = []
|
|
|
|
if status["consent_timestamp"]:
|
|
consent_date = status["consent_timestamp"].strftime(
|
|
"%Y-%m-%d %H:%M UTC"
|
|
)
|
|
details.append(f"**Consent Given:** {consent_date}")
|
|
|
|
if status["first_name"]:
|
|
details.append(f"**Preferred Name:** {status['first_name']}")
|
|
|
|
if status["created_at"]:
|
|
created_date = status["created_at"].strftime("%Y-%m-%d")
|
|
details.append(f"**First Interaction:** {created_date}")
|
|
|
|
if details:
|
|
embed.add_field(
|
|
name="📊 Account Details",
|
|
value="\n".join(details),
|
|
inline=False,
|
|
)
|
|
|
|
# Quick actions
|
|
actions = []
|
|
if not status["global_opt_out"]:
|
|
if status["consent_given"]:
|
|
actions.extend(
|
|
[
|
|
"`/revoke_consent` - Stop recording in this server",
|
|
"`/opt_out` - Stop recording globally",
|
|
]
|
|
)
|
|
else:
|
|
actions.append("`/give_consent` - Enable recording in this server")
|
|
else:
|
|
actions.append("`/opt_in` - Re-enable recording globally")
|
|
|
|
actions.extend(
|
|
[
|
|
"`/delete_my_quotes` - Remove your quote data",
|
|
"`/export_my_data` - Download your data",
|
|
]
|
|
)
|
|
|
|
embed.add_field(
|
|
name="⚡ Quick Actions", value="\n".join(actions), inline=False
|
|
)
|
|
|
|
embed.set_footer(
|
|
text="Your privacy matters • Use /privacy_info for more details"
|
|
)
|
|
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error in consent_status command: {e}")
|
|
embed = EmbedBuilder.error_embed(
|
|
"Command Error", "Failed to retrieve consent status."
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
|
|
@app_commands.command(
|
|
name="delete_my_quotes", description="Delete your quote data from this server"
|
|
)
|
|
@app_commands.describe(confirm="Type 'CONFIRM' to proceed with data deletion")
|
|
async def delete_my_quotes(
|
|
self, interaction: discord.Interaction, confirm: Optional[str] = None
|
|
):
|
|
"""Delete user's quote data with confirmation"""
|
|
try:
|
|
if interaction.guild is None:
|
|
embed = EmbedBuilder.error_embed(
|
|
"Guild Error", "This command can only be used in a server."
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
return
|
|
|
|
user_id = interaction.user.id
|
|
guild_id = interaction.guild.id
|
|
|
|
# Get user's quote count
|
|
quotes = await self.db_manager.get_user_quotes(
|
|
user_id, guild_id, limit=1000
|
|
)
|
|
quote_count = len(quotes)
|
|
|
|
if quote_count == 0:
|
|
embed = EmbedBuilder.error_embed(
|
|
"No Data to Delete",
|
|
"You don't have any quotes stored in this server.",
|
|
"info",
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
return
|
|
|
|
# If no confirmation provided, show confirmation dialog
|
|
if not confirm or confirm.upper() != "CONFIRM":
|
|
embed = ConsentTemplates.get_data_deletion_confirmation(quote_count)
|
|
view = DataDeletionView(
|
|
user_id, guild_id, quote_count, self.consent_manager
|
|
)
|
|
await interaction.response.send_message(
|
|
embed=embed, view=view, ephemeral=True
|
|
)
|
|
return
|
|
|
|
# Execute deletion
|
|
deletion_counts = await self.consent_manager.delete_user_data(
|
|
user_id, guild_id
|
|
)
|
|
|
|
if "error" not in deletion_counts:
|
|
embed = EmbedBuilder.success_embed(
|
|
"Data Deleted Successfully",
|
|
f"✅ **{deletion_counts.get('quotes', 0)} quotes** and related data have been permanently removed.",
|
|
)
|
|
|
|
embed.add_field(
|
|
name="What was deleted",
|
|
value=f"• **{deletion_counts.get('quotes', 0)}** quotes\n"
|
|
f"• **{deletion_counts.get('feedback_records', 0)}** feedback records\n"
|
|
f"• Associated metadata and timestamps",
|
|
inline=False,
|
|
)
|
|
|
|
embed.add_field(
|
|
name="What's Next?",
|
|
value="You can continue using the bot normally. Give consent again anytime with `/give_consent`.",
|
|
inline=False,
|
|
)
|
|
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
|
|
# Log deletion action
|
|
if self.bot.metrics:
|
|
self.bot.metrics.increment(
|
|
"consent_actions",
|
|
{"action": "data_deleted", "guild_id": str(guild_id)},
|
|
)
|
|
|
|
logger.info(
|
|
f"Data deleted for user {user_id} in guild {guild_id}: {deletion_counts}"
|
|
)
|
|
else:
|
|
embed = EmbedBuilder.error_embed(
|
|
"Deletion Failed", f"An error occurred: {deletion_counts['error']}"
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error in delete_my_quotes command: {e}")
|
|
embed = EmbedBuilder.error_embed(
|
|
"Command Error", "An error occurred during data deletion."
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
|
|
@app_commands.command(
|
|
name="export_my_data",
|
|
description="Export your data for download (GDPR compliance)",
|
|
)
|
|
async def export_my_data(self, interaction: discord.Interaction):
|
|
"""Export user data for GDPR compliance"""
|
|
try:
|
|
if interaction.guild is None:
|
|
embed = EmbedBuilder.error_embed(
|
|
"Guild Error", "This command can only be used in a server."
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
return
|
|
|
|
user_id = interaction.user.id
|
|
guild_id = interaction.guild.id
|
|
|
|
# Initial response
|
|
embed = EmbedBuilder.success_embed(
|
|
"Data Export Started", ConsentMessages.DATA_EXPORT_STARTED
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
|
|
# Export data
|
|
export_data = await self.consent_manager.export_user_data(user_id, guild_id)
|
|
|
|
if "error" in export_data:
|
|
error_embed = EmbedBuilder.error_embed(
|
|
"Export Failed", f"Failed to export data: {export_data['error']}"
|
|
)
|
|
await interaction.followup.send(embed=error_embed, ephemeral=True)
|
|
return
|
|
|
|
# Create JSON file
|
|
json_data = json.dumps(export_data, indent=2, ensure_ascii=False)
|
|
json_bytes = json_data.encode("utf-8")
|
|
|
|
# Create file
|
|
filename = f"discord_quote_data_{user_id}_{guild_id}_{datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S')}.json"
|
|
file = discord.File(io.BytesIO(json_bytes), filename=filename)
|
|
|
|
# Send file via DM
|
|
try:
|
|
dm_embed = discord.Embed(
|
|
title="📤 Your Data Export",
|
|
description=(
|
|
f"Here's your exported data from **{interaction.guild.name if interaction.guild else 'Unknown Server'}**.\n\n"
|
|
f"**Contents:**\n"
|
|
f"• {len(export_data.get('quotes', []))} quotes\n"
|
|
f"• {len(export_data.get('consent_records', []))} consent records\n"
|
|
f"• {len(export_data.get('feedback_records', []))} feedback records\n"
|
|
f"• Speaker profile data (if available)\n\n"
|
|
f"This data is provided in JSON format for GDPR compliance."
|
|
),
|
|
color=0x00FF00,
|
|
)
|
|
|
|
dm_embed.add_field(
|
|
name="🔒 Privacy Note",
|
|
value="This file contains your personal data. Please store it securely and delete it when no longer needed.",
|
|
inline=False,
|
|
)
|
|
|
|
dm_embed.set_footer(
|
|
text=f"Exported on {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S')} UTC"
|
|
)
|
|
|
|
await interaction.user.send(embed=dm_embed, file=file)
|
|
|
|
# Confirm successful DM
|
|
success_embed = EmbedBuilder.success_embed(
|
|
"Export Complete",
|
|
"✅ Your data has been sent to your DMs! Check your direct messages for the download file.",
|
|
)
|
|
await interaction.followup.send(embed=success_embed, ephemeral=True)
|
|
|
|
except discord.Forbidden:
|
|
# Can't send DM, offer alternative
|
|
dm_error_embed = EmbedBuilder.error_embed(
|
|
"DM Failed",
|
|
"❌ Couldn't send the file via DM (DMs might be disabled).\n\n"
|
|
"Please enable DMs from server members temporarily and try again, "
|
|
"or contact a server administrator for assistance.",
|
|
"warning",
|
|
)
|
|
await interaction.followup.send(embed=dm_error_embed, ephemeral=True)
|
|
|
|
# Log export action
|
|
if self.bot.metrics:
|
|
self.bot.metrics.increment(
|
|
"consent_actions",
|
|
{"action": "data_exported", "guild_id": str(guild_id)},
|
|
)
|
|
|
|
logger.info(f"Data exported for user {user_id} in guild {guild_id}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error in export_my_data command: {e}")
|
|
embed = EmbedBuilder.error_embed(
|
|
"Export Error",
|
|
"An error occurred during data export. Please try again or contact an administrator.",
|
|
)
|
|
await interaction.followup.send(embed=embed, ephemeral=True)
|
|
|
|
@app_commands.command(
|
|
name="gdpr_info",
|
|
description="View GDPR compliance and data protection information",
|
|
)
|
|
async def gdpr_info(self, interaction: discord.Interaction):
|
|
"""Show GDPR compliance information"""
|
|
try:
|
|
embed = ConsentTemplates.get_gdpr_compliance_embed()
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error in gdpr_info command: {e}")
|
|
embed = EmbedBuilder.error_embed(
|
|
"Command Error", "Failed to load GDPR information."
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
|
|
async def _handle_server_consent_revoke(
|
|
self, interaction: discord.Interaction
|
|
) -> None:
|
|
"""Helper method to handle server-specific consent revocation."""
|
|
if interaction.guild is None:
|
|
embed = EmbedBuilder.error_embed(
|
|
"Guild Error", "This command can only be used in a server."
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
return
|
|
|
|
user_id = interaction.user.id
|
|
guild_id = interaction.guild.id
|
|
|
|
# Check current consent status
|
|
current_consent = await self.consent_manager.check_consent(user_id, guild_id)
|
|
|
|
if not current_consent:
|
|
embed = EmbedBuilder.error_embed(
|
|
"No Consent to Revoke", ConsentMessages.NOT_CONSENTED, "info"
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
return
|
|
|
|
# Revoke consent
|
|
success = await self.consent_manager.revoke_consent(user_id, guild_id)
|
|
|
|
if success:
|
|
embed = EmbedBuilder.success_embed(
|
|
"Consent Revoked", ConsentMessages.CONSENT_REVOKED
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
else:
|
|
embed = EmbedBuilder.error_embed(
|
|
"Revoke Failed",
|
|
"Failed to revoke consent. Please try again or contact an administrator.",
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
|
|
|
|
async def setup(bot: "QuoteBot") -> None:
|
|
"""Setup function for the cog"""
|
|
await bot.add_cog(ConsentCog(bot))
|