Changing the project to being called Nessa now

This commit is contained in:
Dan
2024-05-05 16:38:32 -04:00
parent e5a189d9cb
commit ce1d4626a8
12 changed files with 105 additions and 51 deletions

1
.gitignore vendored
View File

@ -159,3 +159,4 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
/dolly.db
/nessa.db

BIN
Dolly.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 570 KiB

BIN
Nessa.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

BIN
Nessa.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 KiB

6
README.MD Normal file
View File

@ -0,0 +1,6 @@
# Nessa
![Nessa](/Nessa.png)
# About Me:
Hey there! I am Mnemosyne, better known as Nessa! I am here to help you track your projects and task in Discord. Thanks for checking out my services!

View File

@ -1,5 +1,5 @@
from dolly.dolly import Dolly
from dolly.database import init_db
from nessa.nessa import Nessa
from nessa.database import init_db
import asyncio
from dotenv import load_dotenv
import os
@ -10,7 +10,7 @@ TOKEN = os.getenv("DISCORD_BOT_TOKEN")
async def main():
await init_db()
client = Dolly()
client = Nessa()
await client.start(TOKEN)
asyncio.run(main())

View File

@ -2,6 +2,7 @@ import discord
from discord import app_commands
from .database import add_project, get_project_id, get_project_name, add_task_to_project, update_task, list_projects, list_tasks_for_project, remove_task, remove_project
from .consent import check_user_consent, store_user_consent, revoke_user_consent
from .confirm import ConfirmView, ConfirmTaskDeletionView
from datetime import datetime
import logging
from dotenv import load_dotenv
@ -11,7 +12,7 @@ load_dotenv()
contact_user_id = int(os.getenv("AUTHORIZED_USER_ID"))
logger = logging.getLogger('Dolly')
logger = logging.getLogger('Nessa')
class ProjectCommands(app_commands.Group):
def __init__(self):
@ -21,9 +22,9 @@ class ProjectCommands(app_commands.Group):
async def create_project(self, interaction: discord.Interaction, name: str, description: str):
try:
project_id = await add_project(name, description)
await interaction.response.send_message(f"Project '{name}' created successfully with ID: {project_id}.")
await interaction.response.send_message(f"Noted. I've created a new project called '{name}'. You can look it up using the `/project list` command. Your project ID is {project_id}.")
except Exception as e:
await interaction.response.send_message("An error occurred while creating the project.", ephemeral=True)
await interaction.response.send_message("Whoops! Looks like we hit a snag! Try creating the project again! :)", ephemeral=True)
logger.error(f"Error in create_project: {e}")
@app_commands.command(name="list", description="List all projects.")
@ -32,22 +33,23 @@ class ProjectCommands(app_commands.Group):
projects = await list_projects()
if projects:
message = "\n".join([f"ID: {id}, Name: {name}, Description: {description}" for id, name, description in projects])
await interaction.response.send_message(f"Projects:\n{message}")
await interaction.response.send_message(f"Here's the list of Projects:\n{message} \nIf you'd like to add a new project, use the `/project create` command.")
else:
await interaction.response.send_message("No projects found.")
await interaction.response.send_message("Mmmm, I couldn't find any projects. If you'd like to add one, use the `/project create` command.")
except Exception as e:
await interaction.response.send_message("Failed to retrieve projects.", ephemeral=True)
await interaction.response.send_message("Whoops! I failed to retrieve the list of projects. Did I misplace that database somewhere? Try again later!.", ephemeral=True)
logger.error(f"Error in list_projects: {e}")
@app_commands.command(name="remove", description="Remove a specific project and all its tasks.")
async def remove_project_command(self, interaction: discord.Interaction, project_id: int):
# Send a message with the confirmation view
try:
await remove_project(project_id)
await interaction.response.send_message(f"Project ID {project_id} and all associated tasks have been successfully removed.")
logger.info(f"Project ID {project_id} and all associated tasks removed successfully.")
view = ConfirmView(project_id)
await interaction.response.send_message(f"Hey there! Are you sure you wanna me to forgot the project with ID {project_id}? All associated tasks will be removed as well. Once I remove the project, I won't be able to retrieve it again. You have 180 seconds to confirm.", view=view, ephemeral=True)
except Exception as e:
await interaction.response.send_message("Failed to remove the project.", ephemeral=True)
logger.error(f"Error in remove_project_command: {e}")
await interaction.response.send_message(f"Whoops! I failed to remove the project. Error: {e}", ephemeral=True)
logger.error(f"Preparation error in remove_project_command for Project ID {project_id}: {e}")
class TaskCommands(app_commands.Group):
def __init__(self):
@ -60,16 +62,16 @@ class TaskCommands(app_commands.Group):
project_id = await get_project_id(project_name)
if project_id:
await add_task_to_project(project_id, description, assignee, deadline, status, priority)
await interaction.response.send_message(f"Task '{description}' added to project '{project_name}'.")
await interaction.response.send_message(f"Okay so we are adding the task called '{description}' to project '{project_name}'. got it!. Make sure you filled out your project and task details correctly. \nIf you need to update the task, use the `/task update` command.")
logger.info(f"Task added to project {project_name}: {description}")
else:
await interaction.response.send_message(f"Project '{project_name}' not found.", ephemeral=True)
await interaction.response.send_message(f"Whoops! I don't have that project named '{project_name}'. Did you mean to use the `/project create` command? or did you enter the name wrong?", ephemeral=True)
logger.warning(f"Attempted to add task to non-existent project: {project_name}")
except ValueError:
await interaction.response.send_message("Invalid date format. Please use MM/DD/YYYY format.", ephemeral=True)
await interaction.response.send_message("Silly! I know that's not a date! Please use the right format, i.e. MM/DD/YYYY format!", ephemeral=True)
logger.error(f"Invalid date format provided by user: {deadline}")
except Exception as e:
await interaction.response.send_message("An error occurred while adding the task.", ephemeral=True)
await interaction.response.send_message("Huh, that's never happened before... Try again!", ephemeral=True)
logger.error(f"Unexpected error in add_task: {e}")
@app_commands.command(name="update", description="Update an existing task.")
@ -77,13 +79,13 @@ class TaskCommands(app_commands.Group):
try:
datetime.strptime(deadline, "%m/%d/%Y") # Validate date format
await update_task(task_id, description, assignee, deadline, status, priority)
await interaction.response.send_message(f"Task ID {task_id} updated successfully.")
await interaction.response.send_message(f"Okay hun, so I updated the task with ID {task_id} with the changes you wanted made!")
logger.info(f"Task ID {task_id} updated: {description}")
except ValueError:
await interaction.response.send_message("Invalid date format. Please use MM/DD/YYYY format.", ephemeral=True)
await interaction.response.send_message("Silly! I know that's not a date! Please use the right format, i.e. MM/DD/YYYY format!", ephemeral=True)
logger.error(f"Invalid date format provided by user for task update: {deadline}")
except Exception as e:
await interaction.response.send_message("An error occurred while updating the task.", ephemeral=True)
await interaction.response.send_message("Huh, that's never happened before... Try again!", ephemeral=True)
logger.error(f"Unexpected error in update_task_command: {e}")
@app_commands.command(name="list", description="List tasks for a project.")
@ -91,53 +93,50 @@ class TaskCommands(app_commands.Group):
try:
project_name = await get_project_name(project_id)
if not project_name:
await interaction.response.send_message(f"No project found with ID {project_id}.", ephemeral=True)
await interaction.response.send_message(f"I may be the godesss of memory, but I don't have a project with that ID {project_id} in my memory. are you sure you entered the right ID?", ephemeral=True)
return
tasks = await list_tasks_for_project(project_id)
if tasks:
message = "\n".join([f"ID: {id}, Description: {description}, Assignee: {assignee}, Deadline: {deadline}, Status: {status}, Priority: {priority}"
for id, description, assignee, deadline, status, priority in tasks])
await interaction.response.send_message(f"Tasks for Project '{project_name}':\n{message}")
await interaction.response.send_message(f"So, Here are all the tasks I found for that project '{project_name}':\n{message}")
else:
await interaction.response.send_message(f"No tasks found for project '{project_name}'.")
await interaction.response.send_message(f"Whoops! I couldn't find any tasks for that project '{project_name}' Is it brand new?.")
except Exception as e:
await interaction.response.send_message(f"Failed to retrieve tasks for project ID {project_id}.", ephemeral=True)
await interaction.response.send_message(f"My memory must be slipping, I think I couldn't find any tasks for that project {project_id}.", ephemeral=True)
logger.error(f"Error in list_tasks: {e}")
@app_commands.command(name="remove", description="Remove a specific task.")
async def remove_task_command(self, interaction: discord.Interaction, task_id: int):
try:
await remove_task(task_id)
await interaction.response.send_message(f"Task ID {task_id} has been successfully removed.")
logger.info(f"Task ID {task_id} removed successfully.")
except Exception as e:
await interaction.response.send_message("Failed to remove the task.", ephemeral=True)
logger.error(f"Error in remove_task_command: {e}")
# Send a message with the confirmation view
view = ConfirmTaskDeletionView(task_id)
await interaction.response.send_message(f"So, are you sure you want to remove Task ID {task_id}? Once I forget, that's it! You'll have 180 seconds to confirm.", view=view, ephemeral=True)
class ConsentCommands(app_commands.Group):
def __init__(self):
super().__init__(name="consent", description="Manage data consent settings.")
@app_commands.command(name="opt-in", description="Opt-in to data storage and use the bot.")
@app_commands.command(name="opt-in", description="Opt-in to data storage and use Nessa's Services.")
async def opt_in(self, interaction: discord.Interaction):
await store_user_consent(interaction.user.id)
await interaction.response.send_message("You have opted in to data storage. You can now use the bot.", ephemeral=True)
await interaction.response.send_message("Hey there! Thanks for opt-ing in to allowing me to store the data for your projects.", ephemeral=True)
@app_commands.command(name="opt-out", description="Opt-out of data storage and stop using the bot.")
@app_commands.command(name="opt-out", description="Opt-out of data storage and stop using Nessa's Services.")
async def opt_out(self, interaction: discord.Interaction):
await revoke_user_consent(interaction.user.id)
await interaction.response.send_message("You have opted out of data storage. You will no longer be able to use the bot.", ephemeral=True)
await interaction.response.send_message("Awe. It's ok. I won't store your data but this also means you won't be able to use my services. You can always opt-in again. Bye!", ephemeral=True)
@app_commands.command(name="privacy-policy", description="View the bot's data handling policy.")
@app_commands.command(name="privacy-policy", description="View Nessa's data handling policy.")
async def privacy_policy(self, interaction: discord.Interaction):
# Replace 'YOUR_USER_ID' with the actual numeric ID of the user 'advtech'
policy_details = f"""Your data, including your Discord ID, task entries, and other related data, is collected for the purpose of providing task management functionalities. All data is stored securely and is not shared with third parties. You can withdraw your consent at any time by using the `/opt-out` command. For any data requests or inquiries, please contact <@{contact_user_id}> on Discord."""
policy_details = f"""Hi there! So you're looking to hire Nessa, the godess of memory! Let me tell you what you need to know. You need to consent to me storing what you send me in my 'memory' (better known as a database). What I am storing is: Your Disocrd ID, Projects, Tasks, and other data related to your projects. Why I need it? Well, I need your ID so as track your consent to store your data. If I don't have your consent, You can't use my services and your data isn't stored. As far as project and task management is concerned, I store just what you send me. I do not share it with any third-parties. I do not use any external services. I store it in my local 'memory'. Your data is not used for anything else, including but not limited to machine learning. Your data can be removed at any time using the commands. If you wish to opt-out, please use the `/consent opt-out` command. You need anything else, talk to my boss: <@{contact_user_id}> on Discord."""
await interaction.response.send_message(policy_details, ephemeral=True)
class DollyTracker(app_commands.Group, name="dolly", description="Dolly the Project Tracker."):
class NessaTracker(app_commands.Group, name="nessa", description="Nessa the Project Tracker."):
def __init__(self):
super().__init__()
self.add_command(ProjectCommands())

48
nessa/confirm.py Normal file
View File

@ -0,0 +1,48 @@
from discord.ui import View, Button
class ConfirmView(View):
def __init__(self, project_id, timeout=180): # 180 seconds until the view expires
super().__init__(timeout=timeout)
self.project_id = project_id
self.value = None
@discord.ui.button(label="Confirm", style=discord.ButtonStyle.danger)
async def confirm(self, interaction: discord.Interaction, button: Button):
# Attempt to remove the project
try:
await remove_project(self.project_id)
await interaction.response.edit_message(content=f"Project ID {self.project_id} and all associated tasks have been successfully removed.", view=None)
logger.info(f"Project ID {self.project_id} and all associated tasks removed successfully.")
except Exception as e:
error_message = f"Failed to remove the project. Error: {e}"
await interaction.response.edit_message(content=error_message, view=None)
logger.error(f"Error in remove_project_command: {e}")
self.stop() # Stopping the view after an error is usually a good practice to clean up the UI.
@discord.ui.button(label="Cancel", style=discord.ButtonStyle.secondary)
async def cancel(self, interaction: discord.Interaction, button: Button):
await interaction.response.edit_message(content="Project removal cancelled.", view=None)
self.stop()
class ConfirmTaskDeletionView(View):
def __init__(self, task_id, timeout=180): # 180 seconds until the view expires
super().__init__(timeout=timeout)
self.task_id = task_id
self.value = None
@discord.ui.button(label="Confirm", style=discord.ButtonStyle.danger)
async def confirm(self, interaction: discord.Interaction, button: Button):
# Attempt to remove the task
try:
await remove_task(self.task_id)
await interaction.response.edit_message(content=f"Task ID {self.task_id} has been successfully removed.", view=None)
logger.info(f"Task ID {self.task_id} removed successfully.")
except Exception as e:
error_message = f"Failed to remove the task. Error: {e}"
await interaction.response.edit_message(content=error_message, view=None)
logger.error(f"Error in remove_task_command: {e}")
@discord.ui.button(label="Cancel", style=discord.ButtonStyle.secondary)
async def cancel(self, interaction: discord.Interaction, button: Button):
await interaction.response.edit_message(content="Task removal cancelled.", view=None)
self.stop()

View File

@ -19,7 +19,7 @@ class ConsentView(View):
@discord.ui.button(label="Decline Data Storage", style=discord.ButtonStyle.grey)
async def cancel(self, interaction: discord.Interaction, button: Button):
self.value = False
await interaction.response.edit_message(content="You have declined data storage. You cannot use this bot without consenting.", view=None)
await interaction.response.edit_message(content="You have declined data storage. Since you have, You can't use my services.", view=None)
self.stop()
async def check_user_consent(user_id):

View File

@ -1,7 +1,7 @@
import aiosqlite
from datetime import datetime
DATABASE = "dolly.db"
DATABASE = "nessa.db"
async def init_db():
async with aiosqlite.connect(DATABASE) as db:

View File

@ -1,6 +1,6 @@
import discord
from discord import app_commands
from .commands import DollyTracker
from .commands import NessaTracker
from .consent import ConsentView, check_user_consent, store_user_consent
from dotenv import load_dotenv
import os
@ -9,23 +9,23 @@ load_dotenv()
GUILD_ID = int(os.getenv("DISCORD_GUILD_ID"))
OWNER_ID = int(os.getenv("AUTHORIZED_USER_ID"))
class Dolly(discord.Client):
class Nessa(discord.Client):
def __init__(self):
super().__init__(intents=discord.Intents.default())
self.tree = app_commands.CommandTree(self)
async def setup_hook(self):
self.tree.add_command(DollyTracker())
self.tree.add_command(NessaTracker())
self.tree.copy_global_to(guild=discord.Object(id=GUILD_ID))
await self.tree.sync(guild=discord.Object(id=GUILD_ID))
@self.tree.command(name="shutdown", description="Shut down the bot", guild=discord.Object(id=GUILD_ID))
@self.tree.command(name="shutdown", description="Shut down Nessa", guild=discord.Object(id=GUILD_ID))
async def shutdown(interaction: discord.Interaction):
if interaction.user.id == OWNER_ID:
await interaction.response.send_message("Shutting down...")
await interaction.response.send_message("Night, Night. Shutting down...")
await self.close()
else:
await interaction.response.send_message("You do not have permission to use this command.", ephemeral=True)
await interaction.response.send_message("Hey! I don't know you! You don't have permission to use this command. Only the owner can do that.", ephemeral=True)
async def on_ready(self):
print(f"Logged on as {self.user}!")
@ -45,7 +45,7 @@ class Dolly(discord.Client):
return
view = ConsentView()
await interaction.response.send_message(
"By using this bot, you consent to the storage of your data necessary for functionality. Please confirm your consent. See /dolly consent privacy-policy for more details.",
"By using the services I, Nessa, provide, you consent to the storage of your data necessary for functionality. Please confirm your consent. See /nessa consent privacy-policy for more details.",
view=view,
ephemeral=True
)
@ -53,11 +53,11 @@ class Dolly(discord.Client):
if view.value:
await store_user_consent(interaction.user.id)
else:
await interaction.followup.send("You must consent to data storage to use this bot.", ephemeral=True)
await interaction.followup.send("Whoops! You have to give me your okay to do store your data before you can use my services!", ephemeral=True)
return # Stop processing if they do not consent
# For opt-in command, check if they're trying to opt-in after opting out.
if interaction.command.name == 'opt-in':
if consented:
await interaction.response.send_message("You have already consented.", ephemeral=True)
await interaction.response.send_message("Hey! Thanks, but you've already opted in.", ephemeral=True)
return