diff --git a/.gitignore b/.gitignore index 5d381cc..73a0dd9 100644 --- a/.gitignore +++ b/.gitignore @@ -160,3 +160,4 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ +*.db \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..3560b35 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Ariella: Main", + "type": "debugpy", + "request": "launch", + "program": "E:\\Development\\AI Development\\Ariella\\main.py", + "console": "integratedTerminal" + } + ] +} \ No newline at end of file diff --git a/commands.py b/commands.py new file mode 100644 index 0000000..cc8c5c9 --- /dev/null +++ b/commands.py @@ -0,0 +1,171 @@ +import discord +from discord import app_commands +from discord.ext import commands +import aiosqlite +import os +import sys +import subprocess +from gdpr import check_consent + + +class ModCommands(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @app_commands.command(name="addnote", + description="Add a note to a user and optionally " + "add strikes") + async def add_note(self, interaction: discord.Interaction, + user: discord.User, note: str, strikes: int = 0): + if not await check_consent(user.id): + await interaction.response.send_message( + f"{user.name} has not given consent to store data." + ) + return + + async with aiosqlite.connect("ariella.db") as db: + cursor = await db.execute( + "SELECT notes, strikes FROM user_notes WHERE user_id = ?", + (user.id,) + ) + row = await cursor.fetchone() + if row: + notes = row[0] + "\n" + note + current_strikes = row[1] + strikes + await db.execute( + "UPDATE user_notes SET notes = ?, strikes = ? " + "WHERE user_id = ?", + (notes, current_strikes, user.id) + ) + else: + await db.execute( + "INSERT INTO user_notes (user_id, notes, strikes) " + "VALUES (?, ?, ?)", + (user.id, note, strikes) + ) + await db.commit() + await interaction.response.send_message( + f"Note added for {user.name}: {note}. Strikes: {strikes}" + ) + + @app_commands.command(name="warn", + description="Warn a user") + async def warn_user(self, interaction: discord.Interaction, + user: discord.User, reason: str): + if not await check_consent(user.id): + await interaction.response.send_message( + f"{user.name} has not given consent to store data." + ) + return + + async with aiosqlite.connect("ariella.db") as db: + cursor = await db.execute( + "SELECT strikes FROM user_notes WHERE user_id = ?", + (user.id,) + ) + row = await cursor.fetchone() + if row: + strikes = row[0] + 1 + await db.execute( + "UPDATE user_notes SET strikes = ? WHERE user_id = ?", + (strikes, user.id) + ) + else: + strikes = 1 + await db.execute( + "INSERT INTO user_notes (user_id, notes, strikes) " + "VALUES (?, ?, ?)", + (user.id, "", strikes) + ) + await db.commit() + await interaction.response.send_message( + f"User {user.name} warned for: {reason}. They now have {strikes} " + f"strikes." + ) + await user.send( + f"You have been warned for: {reason}. You now have {strikes} " + f"strikes." + ) + + @app_commands.command(name="removestrikes", + description="Remove strikes from a user") + async def remove_strikes(self, interaction: discord.Interaction, + user: discord.User, strikes: int): + if not await check_consent(user.id): + await interaction.response.send_message( + f"{user.name} has not given consent to store data." + ) + return + + async with aiosqlite.connect("ariella.db") as db: + cursor = await db.execute( + "SELECT strikes FROM user_notes WHERE user_id = ?", + (user.id,) + ) + row = await cursor.fetchone() + if row: + current_strikes = max(row[0] - strikes, 0) + await db.execute( + "UPDATE user_notes SET strikes = ? WHERE user_id = ?", + (current_strikes, user.id) + ) + await db.commit() + await interaction.response.send_message( + f"Removed {strikes} strikes from {user.name}. " + f"They now have {current_strikes} strikes." + ) + else: + await interaction.response.send_message( + f"No strikes found for {user.name}." + ) + + @app_commands.command(name="checknotes", + description="Check notes and strikes of a user") + async def check_notes(self, interaction: discord.Interaction, + user: discord.User): + if not await check_consent(user.id): + await interaction.response.send_message( + f"{user.name} has not given consent to store data." + ) + return + + async with aiosqlite.connect("ariella.db") as db: + cursor = await db.execute( + "SELECT notes, strikes FROM user_notes WHERE user_id = ?", + (user.id,) + ) + row = await cursor.fetchone() + if row: + notes, strikes = row + await interaction.response.send_message( + f"Notes for {user.name}: {notes}\nStrikes: {strikes}" + ) + else: + await interaction.response.send_message( + f"No notes found for {user.name}." + ) + + @app_commands.command(name="update", + description="Update Ariellia to the latest version") + @commands.is_owner() + async def update(self, interaction: discord.Interaction): + await interaction.response.send_message("Updating the bot...") + # Pull latest changes from the repository + repo_dir = os.path.dirname(os.path.abspath(__file__)) + subprocess.run(["git", "-C", repo_dir, "pull"]) + # Restart the bot + await interaction.followup.send("Restarting the bot...") + os.execv(sys.executable, ['python'] + sys.argv) + + @app_commands.command(name="help", + description="List all commands") + async def help_command(self, interaction: discord.Interaction): + commands = self.bot.tree.walk_commands() + help_text = "Here are the available commands:\n" + for command in commands: + help_text += f"/{command.name} - {command.description}\n" + await interaction.response.send_message(help_text) + + +async def setup(bot): + await bot.add_cog(ModCommands(bot)) diff --git a/gdpr.py b/gdpr.py new file mode 100644 index 0000000..5cb3005 --- /dev/null +++ b/gdpr.py @@ -0,0 +1,33 @@ +import aiosqlite + + +async def check_consent(user_id: int) -> bool: + async with aiosqlite.connect("ariella.db") as db: + cursor = await db.execute( + "SELECT consent FROM user_consent WHERE user_id = ?", + (user_id,) + ) + row = await cursor.fetchone() + return row and row[0] == 1 + + +async def give_consent(user_id: int): + async with aiosqlite.connect("ariella.db") as db: + await db.execute( + "INSERT OR REPLACE INTO user_consent (user_id, consent) VALUES (?, ?)", # noqa: E501 + (user_id, 1) + ) + await db.commit() + + +async def revoke_consent(user_id: int): + async with aiosqlite.connect("ariella.db") as db: + await db.execute( + "DELETE FROM user_consent WHERE user_id = ?", + (user_id,) + ) + await db.execute( + "DELETE FROM user_notes WHERE user_id = ?", + (user_id,) + ) + await db.commit() diff --git a/gdpr_commands.py b/gdpr_commands.py new file mode 100644 index 0000000..6c7ea1f --- /dev/null +++ b/gdpr_commands.py @@ -0,0 +1,74 @@ +import discord +from discord import app_commands +from discord.ext import commands +import aiosqlite +import json +from gdpr import check_consent, give_consent, revoke_consent + + +class GDPRCommands(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @app_commands.command(name="consent", + description="Give consent to store data") + async def consent(self, interaction: discord.Interaction): + await give_consent(interaction.user.id) + await interaction.response.send_message( + "Consent given to store your data." + ) + + @app_commands.command(name="revoke_consent", + description="Revoke consent to store data") + async def revoke_consent(self, interaction: discord.Interaction): + await revoke_consent(interaction.user.id) + await interaction.response.send_message( + "Consent revoked and your data has been deleted." + ) + + @app_commands.command(name="privacy_policy", + description="View the privacy policy") + async def privacy_policy(self, interaction: discord.Interaction): + privacy_text = ( + "Privacy Policy:\n" + "We collect and store data to provide better services. The data " + "includes:\n" + "- User ID\n" + "- Notes and Strikes added by moderators\n" + "Data is stored securely and only accessible by authorized " + "personnel.\n" + "You can revoke consent at any time by using the /revoke_consent " + "command." + ) + await interaction.response.send_message(privacy_text) + + @app_commands.command(name="get_my_data", + description="Get a copy of your data") + async def get_my_data(self, interaction: discord.Interaction): + user_id = interaction.user.id + async with aiosqlite.connect("ariella.db") as db: + cursor = await db.execute( + "SELECT notes, strikes FROM user_notes WHERE user_id = ?", + (user_id,) + ) + row = await cursor.fetchone() + if row: + notes, strikes = row + data = { + "user_id": user_id, + "notes": notes, + "strikes": strikes + } + data_text = json.dumps(data, indent=4) + await interaction.user.send + (f"Your data:\n```json\n{data_text}\n```") + await interaction.response.send_message( + "Your data has been sent to you privately." + ) + else: + await interaction.response.send_message + ("No data found for you.") + + +async def setup(bot): + await bot.add_cog(GDPRCommands(bot)) diff --git a/main.py b/main.py new file mode 100644 index 0000000..68d8f0a --- /dev/null +++ b/main.py @@ -0,0 +1,64 @@ +import discord +from discord.ext import commands +import aiosqlite +import os +import sys +import subprocess +import asyncio +from dotenv import load_dotenv + +load_dotenv() + +GUILD_ID = os.getenv('GUILD_ID') +TOKEN = os.getenv('DISCORD_TOKEN') + +intents = discord.Intents.default() +intents.message_content = True +intents.members = True + + +class Ariella(commands.Bot): + def __init__(self): + super().__init__(command_prefix='!', intents=intents) + + async def setup_hook(self): + await self.load_extension('commands') + await self.load_extension('gdpr_commands') + self.tree.copy_global_to(guild=discord.Object(id=GUILD_ID)) + await self.tree.sync() + + +bot = Ariella() + + +# Database setup +async def init_db(): + async with aiosqlite.connect("ariella.db") as db: + await db.execute(""" + CREATE TABLE IF NOT EXISTS user_notes ( + user_id INTEGER PRIMARY KEY, + notes TEXT, + strikes INTEGER + ) + """) + await db.execute(""" + CREATE TABLE IF NOT EXISTS user_consent ( + user_id INTEGER PRIMARY KEY, + consent INTEGER + ) + """) + await db.commit() + + +@bot.event +async def on_ready(): + print(f'Logged in as {bot.user} (ID: {bot.user.id})') + print('------') + await init_db() + + +@bot.event +async def on_guild_join(guild): + await bot.tree.sync(guild=guild) + +bot.run(TOKEN) diff --git a/sample.env b/sample.env new file mode 100644 index 0000000..325ed88 --- /dev/null +++ b/sample.env @@ -0,0 +1,2 @@ +DISCORD_TOKEN = "ADD YOUR TOKEN HERE" +GUILD_ID = "ADD YOUR GUILD ID HERE" \ No newline at end of file