gotta archive this lmao
This commit is contained in:
10
.dockerignore
Normal file → Executable file
10
.dockerignore
Normal file → Executable file
@@ -1,6 +1,6 @@
|
|||||||
.gitignore
|
.gitignore
|
||||||
/logs
|
/logs
|
||||||
/.venv
|
/.venv
|
||||||
/__pycache__
|
/__pycache__
|
||||||
*.pyc
|
*.pyc
|
||||||
/data
|
/data
|
||||||
0
.gitignore
vendored
Normal file → Executable file
0
.gitignore
vendored
Normal file → Executable file
30
.vscode/launch.json
vendored
Normal file → Executable file
30
.vscode/launch.json
vendored
Normal file → Executable file
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
// Use IntelliSense to learn about possible attributes.
|
// Use IntelliSense to learn about possible attributes.
|
||||||
// Hover to view descriptions of existing attributes.
|
// Hover to view descriptions of existing attributes.
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "Python Debugger: Current File",
|
"name": "Python Debugger: Current File",
|
||||||
"type": "debugpy",
|
"type": "debugpy",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "${file}",
|
"program": "${file}",
|
||||||
"console": "integratedTerminal"
|
"console": "integratedTerminal"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
32
Dockerfile
Normal file → Executable file
32
Dockerfile
Normal file → Executable file
@@ -1,17 +1,17 @@
|
|||||||
# Use the official Python image from the Docker Hub
|
# Use the official Python image from the Docker Hub
|
||||||
FROM python:3.12-slim
|
FROM python:3.12-slim
|
||||||
|
|
||||||
# Set working directory in the container
|
# Set working directory in the container
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Copy requirements.txt
|
# Copy requirements.txt
|
||||||
COPY requirements.txt .
|
COPY requirements.txt .
|
||||||
|
|
||||||
# Install the required packages
|
# Install the required packages
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
# Copy the rest of the application files
|
# Copy the rest of the application files
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Command to run the application
|
# Command to run the application
|
||||||
CMD ["python", "app.py"]
|
CMD ["python", "app.py"]
|
||||||
378
ai_logic.py
Normal file → Executable file
378
ai_logic.py
Normal file → Executable file
@@ -1,182 +1,196 @@
|
|||||||
import logging
|
# ----- Configuration & Setup -----
|
||||||
import os
|
import logging
|
||||||
import asyncio
|
import os
|
||||||
import discord # For type hintings
|
import asyncio
|
||||||
from json import loads as load_json
|
import discord # For type hintings
|
||||||
|
from json import loads as load_json
|
||||||
from openai import OpenAI
|
|
||||||
from openai.types.beta import Assistant, Thread
|
from openai import OpenAI
|
||||||
from openai.types.beta.threads import Run, Message, RequiredActionFunctionToolCall
|
from openai.types.beta import Assistant, Thread
|
||||||
from quotes import QUOTES
|
from openai.types.beta.threads import Run, Message, RequiredActionFunctionToolCall
|
||||||
from students import *
|
from quotes import QUOTES
|
||||||
|
from students import *
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
OPENAI_KEY = os.getenv('OPENAI_KEY')
|
|
||||||
|
OPENAI_KEY = os.getenv('OPENAI_KEY')
|
||||||
# OpenAI & Assistant configuration
|
|
||||||
client: OpenAI = OpenAI(
|
client: OpenAI = OpenAI(
|
||||||
api_key=OPENAI_KEY,
|
api_key=OPENAI_KEY,
|
||||||
project="proj_vUQ7duhKKc1jxIqllRhy6DVJ",
|
project="proj_vUQ7duhKKc1jxIqllRhy6DVJ",
|
||||||
)
|
)
|
||||||
|
|
||||||
mr_jacobs: Assistant = client.beta.assistants.retrieve("asst_KdPdwqNAKijujfyCRrJCOgJN")
|
mr_jacobs: Assistant = client.beta.assistants.retrieve("asst_KdPdwqNAKijujfyCRrJCOgJN")
|
||||||
base_instructions: str = mr_jacobs.instructions
|
base_instructions: str = mr_jacobs.instructions
|
||||||
logger.info(f"Base instructions: {base_instructions}")
|
logger.info(f"Base instructions: {base_instructions}")
|
||||||
instructions: str = base_instructions + f"\n\nHere is a dictionary containing some of your most well known quotes, organized by their frequency. DO NOT USE THE QUOTES FREQUENTLY! JUST UTILIZE THEM TO KNOW THE TYPES OF THINGS YOU SAY! \n{QUOTES}\n\n"
|
instructions: str = base_instructions + f"\n\nHere is a dictionary containing some of your most well known quotes, organized by their frequency. DO NOT USE THE QUOTES FREQUENTLY! JUST UTILIZE THEM TO KNOW THE TYPES OF THINGS YOU SAY! \n{QUOTES}\n\n"
|
||||||
|
|
||||||
def get_run_status(run: Run) -> str:
|
# ----- Helper Functions -----
|
||||||
"""
|
def get_run_status(run: Run) -> str:
|
||||||
Retrieves the status of a run.
|
"""
|
||||||
|
Retrieves the status of a run.
|
||||||
Args:
|
|
||||||
run: The run object.
|
Args:
|
||||||
|
run: The run object.
|
||||||
Returns:
|
|
||||||
str: The status of the run.
|
Returns:
|
||||||
"""
|
str: The status of the run.
|
||||||
status: str = client.beta.threads.runs.retrieve(
|
"""
|
||||||
run.id,
|
status: str = client.beta.threads.runs.retrieve(
|
||||||
thread_id=run.thread_id
|
run.id,
|
||||||
).status
|
thread_id=run.thread_id
|
||||||
logger.info(f"Status of run {run.id} is {status}")
|
).status
|
||||||
return status
|
logger.info(f"Status of run {run.id} is {status}")
|
||||||
|
return status
|
||||||
def get_instructions(context: discord.abc.GuildChannel | discord.Message = None) -> str:
|
|
||||||
"""
|
def get_instructions(context: discord.abc.GuildChannel | discord.Message = None) -> str:
|
||||||
Retrieves the instructions for the AI based on the context.
|
"""
|
||||||
|
Retrieves the instructions for the AI based on the context.
|
||||||
Args:
|
|
||||||
context (discord.abc.GuildChannel | discord.Message | None): The context from which to derive additional instructions.
|
Args:
|
||||||
If it's a GuildChannel, includes channel name and category.
|
context (discord.abc.GuildChannel | discord.Message | None): The context from which to derive additional instructions.
|
||||||
If it's a Message, includes channel name and category from the message.
|
If it's a GuildChannel, includes channel name and category.
|
||||||
|
If it's a Message, includes channel name and category from the message.
|
||||||
Returns:
|
|
||||||
str: The instructions for the AI.
|
Returns:
|
||||||
"""
|
str: The instructions for the AI.
|
||||||
tuned_instructions: str = instructions
|
"""
|
||||||
|
tuned_instructions: str = instructions
|
||||||
if context:
|
|
||||||
tuned_instructions += "\n\nNow I will give you the context of the channel that the message is sent in, keep it in mind. Please try to remain conversational depending on the topic of the channel, and don't be worshiping Cisco so religiously outside of on-topic. It's okay to joke about it, but you still wanna goof around with the students sometimes. However, if they have an essay, you should definately get on them about it outside of on-topic, so that you can still be helpful when they are talking about things on-topic, but can scold them to do their essay in any other channel. You should take the channel info fairly seriously, please. I really hate having to make such extreme channel descriptions to see a slight change in behavior from the Cisco worshipping. :\n"
|
if context:
|
||||||
if isinstance(context, discord.Message):
|
tuned_instructions += "\n\nNow I will give you the context of the channel that the message is sent in, keep it in mind. Please try to remain conversational depending on the topic of the channel, and don't be worshiping Cisco so religiously outside of on-topic. It's okay to joke about it, but you still wanna goof around with the students sometimes. However, if they have an essay, you should definately get on them about it outside of on-topic, so that you can still be helpful when they are talking about things on-topic, but can scold them to do their essay in any other channel. You should take the channel info fairly seriously, please. I really hate having to make such extreme channel descriptions to see a slight change in behavior from the Cisco worshipping. :\n"
|
||||||
context = context.channel
|
if isinstance(context, discord.Message):
|
||||||
if not isinstance(context, discord.abc.Messageable):
|
context = context.channel
|
||||||
logger.critical(f"Literally how are we going to send a message in a channel that is not messageable? (bro how did we get here, wtf was passed as context? oh: {context}")
|
if not isinstance(context, discord.abc.Messageable):
|
||||||
return tuned_instructions
|
logger.critical(f"Literally how are we going to send a message in a channel that is not messageable? (bro how did we get here, wtf was passed as context? oh: {context}")
|
||||||
if isinstance(context, discord.TextChannel):
|
return tuned_instructions
|
||||||
tuned_instructions += f"\nChannel Category: {context.category.name}\nChannel Name:{context.name}\nChannel Description: {context.topic if isinstance(context, discord.TextChannel) else 'No Description'}"
|
if isinstance(context, discord.TextChannel):
|
||||||
elif isinstance(context, discord.DMChannel):
|
tuned_instructions += f"\nChannel Category: {context.category.name}\nChannel Name:{context.name}\nChannel Description: {context.topic if isinstance(context, discord.TextChannel) else 'No Description'}"
|
||||||
logger.warning(f"DM Channel detected, adding context to instructions.\nDM channel with {ID_TO_NAME.get(context.recipient.id, context.recipient.name)}")
|
elif isinstance(context, discord.DMChannel):
|
||||||
tuned_instructions += f"\nThis is a direct message with {STUDENTS.get(context.recipient.id) if context.recipient.id in STUDENTS else context.recipient.name + 'No extra known info on user.'}"
|
logger.warning(f"DM Channel detected, adding context to instructions.\nDM channel with {ID_TO_NAME.get(context.recipient.id, context.recipient.name)}")
|
||||||
|
tuned_instructions += f"\nThis is a direct message with {STUDENTS.get(context.recipient.id) if context.recipient.id in STUDENTS else context.recipient.name + 'No extra known info on user.'}"
|
||||||
return tuned_instructions
|
|
||||||
|
return tuned_instructions
|
||||||
async def handle_action(run: Run) -> bool:
|
|
||||||
# Define the list to store tool outputs
|
# ----- Action Handlers -----
|
||||||
tool_outputs = []
|
async def handle_action(run: Run) -> bool:
|
||||||
tool: RequiredActionFunctionToolCall
|
# Define the list to store tool outputs
|
||||||
# Loop through each tool in the required action section
|
tool_outputs = []
|
||||||
try:
|
tool: RequiredActionFunctionToolCall
|
||||||
logger.debug(f"Run.require_action is currently: {run.required_action} and tool calls are {run.required_action.submit_tool_outputs.tool_calls}")
|
# Loop through each tool in the required action section
|
||||||
for tool in run.required_action.submit_tool_outputs.tool_calls:
|
try:
|
||||||
logger.info(f"Handling action for tool {tool.id}, function {tool.function.name}")
|
logger.debug(f"Run.require_action is currently: {run.required_action} and tool calls are {run.required_action.submit_tool_outputs.tool_calls}")
|
||||||
if tool.function.name == "assign_essay":
|
for tool in run.required_action.submit_tool_outputs.tool_calls:
|
||||||
logger.debug(f"Handling action for assign_essay tool. Received arguments {tool.function.arguments}")
|
logger.info(f"Handling action for tool {tool.id}, function {tool.function.name}")
|
||||||
args = load_json(tool.function.arguments)
|
if tool.function.name == "assign_essay":
|
||||||
tool_outputs.append({
|
logger.debug(f"Handling action for assign_essay tool. Received arguments {tool.function.arguments}")
|
||||||
"tool_call_id": tool.id,
|
args = load_json(tool.function.arguments)
|
||||||
"output": assign_essay(int(run.metadata.get("user_id"))) if not tool.function.arguments else assign_essay(int(run.metadata.get("user_id")), args["topic"])
|
tool_outputs.append({
|
||||||
})
|
"tool_call_id": tool.id,
|
||||||
elif tool.function.name == "clear_essay":
|
"output": assign_essay(int(run.metadata.get("user_id"))) if not tool.function.arguments else assign_essay(int(run.metadata.get("user_id")), args["topic"])
|
||||||
logger.debug(f"Clearing the essay for student {run.metadata.get('user_id')}")
|
})
|
||||||
clear_essay(int(run.metadata.get("user_id")))
|
elif tool.function.name == "clear_essay":
|
||||||
tool_outputs.append({
|
logger.debug(f"Clearing the essay for student {run.metadata.get('user_id')}")
|
||||||
"tool_call_id": tool.id,
|
clear_essay(int(run.metadata.get("user_id")))
|
||||||
"output": "Essay cleared sucessfully, feel free to still give the student feedback on their essay because it couldn't have been perfect."
|
tool_outputs.append({
|
||||||
})
|
"tool_call_id": tool.id,
|
||||||
except AttributeError as e:
|
"output": "Essay cleared sucessfully, feel free to still give the student feedback on their essay because it couldn't have been perfect."
|
||||||
logger.error(f"Failed to handle action: {e}")
|
})
|
||||||
except Exception as e:
|
except AttributeError as e:
|
||||||
logger.critical(f"An unexpected error occurred while handling action: {e}")
|
logger.error(f"Failed to handle action: {e}")
|
||||||
finally:
|
except Exception as e:
|
||||||
logger.info(f"Tool outputs: {tool_outputs}")
|
logger.critical(f"An unexpected error occurred while handling action: {e}")
|
||||||
if tool_outputs:
|
finally:
|
||||||
try:
|
logger.info(f"Tool outputs: {tool_outputs}")
|
||||||
run = client.beta.threads.runs.submit_tool_outputs_and_poll(
|
if tool_outputs:
|
||||||
thread_id=run.thread_id,
|
try:
|
||||||
run_id=run.id,
|
run = client.beta.threads.runs.submit_tool_outputs_and_poll(
|
||||||
tool_outputs=tool_outputs
|
thread_id=run.thread_id,
|
||||||
)
|
run_id=run.id,
|
||||||
logger.info("Tool outputs submitted successfully.")
|
tool_outputs=tool_outputs
|
||||||
except Exception as e:
|
)
|
||||||
logger.error("Failed to submit tool outputs:", e)
|
logger.info("Tool outputs submitted successfully.")
|
||||||
else:
|
except Exception as e:
|
||||||
logger.warning("No tool outputs to submit.")
|
logger.error("Failed to submit tool outputs:", e)
|
||||||
|
else:
|
||||||
|
logger.warning("No tool outputs to submit.")
|
||||||
async def handle_run(run: Run) -> bool:
|
|
||||||
while True:
|
|
||||||
run = client.beta.threads.runs.retrieve(
|
# ----- Run Handlers -----
|
||||||
run.id,
|
async def handle_run(run: Run) -> bool:
|
||||||
thread_id=run.thread_id
|
"""Manages a run and all it's different possible states.
|
||||||
)
|
|
||||||
match run.status:
|
Args:
|
||||||
case "completed":
|
run (Run): The run to maintain.
|
||||||
return True
|
|
||||||
case "failed":
|
Returns:
|
||||||
logger.error(f"Run {run.id} failed.")
|
bool: Whether or not the run completed successfully.
|
||||||
return False
|
"""
|
||||||
case "requires_action":
|
while True:
|
||||||
logger.info(f"Run {run.id} requires action.")
|
run = client.beta.threads.runs.retrieve(
|
||||||
await handle_action(run)
|
run.id,
|
||||||
case _:
|
thread_id=run.thread_id
|
||||||
await asyncio.sleep(1)
|
)
|
||||||
await asyncio.sleep(1)
|
match run.status:
|
||||||
|
case "completed":
|
||||||
|
return True
|
||||||
async def run(messages: list[dict], instructions: str = instructions, user_id : int = None) -> str:
|
case "failed":
|
||||||
"""
|
logger.error(f"Run {run.id} failed.")
|
||||||
Runs the AI with the given messages and instructions.
|
return False
|
||||||
|
case "requires_action":
|
||||||
Args:
|
logger.info(f"Run {run.id} requires action.")
|
||||||
messages (list[dict]): The list of messages.
|
await handle_action(run)
|
||||||
instructions (str): The instructions for the AI.
|
case "expired":
|
||||||
user_id (int): The Discord ID of the user who initiated the run.
|
logger.error(f"Run {run.id} expired.")
|
||||||
|
return False
|
||||||
Returns:
|
case _:
|
||||||
str: The response from the AI.
|
await asyncio.sleep(1)
|
||||||
"""
|
await asyncio.sleep(1)
|
||||||
logger.debug(f"Running AI assistant with the following parameters:\nmessages={messages}\ninstructions={instructions}\nmetadata={str(user_id if user_id else -1)}")
|
|
||||||
run = client.beta.threads.create_and_run(
|
|
||||||
assistant_id=mr_jacobs.id,
|
async def run(messages: list[dict], instructions: str = instructions, user_id : int = None) -> str:
|
||||||
instructions=instructions,
|
"""
|
||||||
thread={
|
Runs the AI with the given messages and instructions.
|
||||||
"messages": messages
|
|
||||||
},
|
Args:
|
||||||
metadata={
|
messages (list[dict]): The list of messages.
|
||||||
"user_id": str(user_id if user_id else -1)
|
instructions (str): The instructions for the AI.
|
||||||
}
|
user_id (int): The Discord ID of the user who initiated the run.
|
||||||
)
|
|
||||||
response = await run_message(run)
|
Returns:
|
||||||
return response
|
str: The response from the AI.
|
||||||
|
"""
|
||||||
|
logger.debug(f"Running AI assistant with the following parameters:\nmessages={messages}\ninstructions={instructions}\nmetadata={str(user_id if user_id else -1)}")
|
||||||
async def run_message(run) -> str:
|
run = client.beta.threads.create_and_run(
|
||||||
"""
|
assistant_id=mr_jacobs.id,
|
||||||
Retrieves the response message from a run.
|
instructions=instructions,
|
||||||
|
thread={
|
||||||
Args:
|
"messages": messages
|
||||||
run: The run object.
|
},
|
||||||
|
metadata={
|
||||||
Returns:
|
"user_id": str(user_id if user_id else -1)
|
||||||
str: The response message.
|
}
|
||||||
"""
|
)
|
||||||
logger.debug(f"Retrieving response message for run ID {run.id}")
|
response = await run_message(run)
|
||||||
# ew but Python doesn't have a do while and it's less ugly than the same statement twice.
|
return response
|
||||||
await handle_run(run)
|
|
||||||
thread_messages = client.beta.threads.messages.list(run.thread_id)
|
|
||||||
for msg_ob in thread_messages.data:
|
async def run_message(run) -> str:
|
||||||
if msg_ob.id == thread_messages.first_id:
|
"""
|
||||||
response = msg_ob.content[0].text.value
|
Retrieves the response message from a run.
|
||||||
return response
|
|
||||||
logger.critical(f"Couldn't find the msg that matched with the first message ID:\nThread Messages List:\n{thread_messages}")
|
Args:
|
||||||
|
run: The run object.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The response message.
|
||||||
|
"""
|
||||||
|
logger.debug(f"Retrieving response message for run ID {run.id}")
|
||||||
|
# ew but Python doesn't have a do while and it's less ugly than the same statement twice.
|
||||||
|
await handle_run(run)
|
||||||
|
thread_messages = client.beta.threads.messages.list(run.thread_id)
|
||||||
|
for msg_ob in thread_messages.data:
|
||||||
|
if msg_ob.id == thread_messages.first_id:
|
||||||
|
response = msg_ob.content[0].text.value
|
||||||
|
return response
|
||||||
|
logger.critical(f"Couldn't find the msg that matched with the first message ID:\nThread Messages List:\n{thread_messages}")
|
||||||
|
|||||||
78
app.py
Normal file → Executable file
78
app.py
Normal file → Executable file
@@ -1,40 +1,40 @@
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
from apscheduler.schedulers.background import BackgroundScheduler
|
from apscheduler.schedulers.background import BackgroundScheduler
|
||||||
|
|
||||||
from discord_logic import bot, setup_discord_bot, send_quote, after_class
|
from discord_logic import bot, setup_discord_bot, send_quote, after_class
|
||||||
|
|
||||||
# Load environment variables
|
# Load environment variables
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
DISCORD_TOKEN = os.getenv('DISCORD_TOKEN')
|
DISCORD_TOKEN = os.getenv('DISCORD_TOKEN')
|
||||||
|
|
||||||
# Logging.
|
# Logging.
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
filename=f"./logs/jacobs.log",
|
filename=f"./logs/jacobs.log",
|
||||||
filemode="at+",
|
filemode="at+",
|
||||||
level=logging.DEBUG if os.getenv('DEBUG') == "True" else logging.INFO,
|
level=logging.DEBUG if os.getenv('DEBUG') == "True" else logging.INFO,
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||||
)
|
)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
setup_discord_bot(bot)
|
setup_discord_bot(bot)
|
||||||
|
|
||||||
@bot.event
|
@bot.event
|
||||||
async def on_ready() -> None:
|
async def on_ready() -> None:
|
||||||
"""Event handler for when the bot is initialized."""
|
"""Event handler for when the bot is initialized."""
|
||||||
logger.info(f"{bot.user.name} has connected to Discord and is ready.")
|
logger.info(f"{bot.user.name} has connected to Discord and is ready.")
|
||||||
print(f"{bot.user.name} is ready.")
|
print(f"{bot.user.name} is ready.")
|
||||||
|
|
||||||
# After successful initialization, schedule tasks.
|
# After successful initialization, schedule tasks.
|
||||||
task_scheduler = BackgroundScheduler(timezone=pytz.timezone('EDT'))
|
task_scheduler = BackgroundScheduler(timezone=pytz.timezone('EST'))
|
||||||
task_scheduler.add_job(lambda: bot.loop.create_task(send_quote()), 'cron', day_of_week='mon-fri', hour=8, minute=50)
|
task_scheduler.add_job(lambda: bot.loop.create_task(send_quote()), 'cron', day_of_week='mon-fri', hour=8, minute=50)
|
||||||
task_scheduler.add_job(lambda: bot.loop.create_task(send_quote()), 'cron', day_of_week='mon-fri', hour='8-14', minute="*/20", jitter=180)
|
task_scheduler.add_job(lambda: bot.loop.create_task(send_quote()), 'cron', day_of_week='mon-fri', hour='8-14', minute="*/20", jitter=180)
|
||||||
task_scheduler.add_job(lambda: bot.loop.create_task(after_class()), 'cron', day_of_week='mon-fri', hour=10, minute=55)
|
task_scheduler.add_job(lambda: bot.loop.create_task(after_class()), 'cron', day_of_week='mon-fri', hour=10, minute=55)
|
||||||
task_scheduler.start()
|
task_scheduler.start()
|
||||||
|
|
||||||
logger.info("Presumably successfully initialized, starting bot.")
|
logger.info("Presumably successfully initialized, starting bot.")
|
||||||
bot.run(DISCORD_TOKEN)
|
bot.run(DISCORD_TOKEN)
|
||||||
908
discord_logic.py
Normal file → Executable file
908
discord_logic.py
Normal file → Executable file
@@ -1,411 +1,497 @@
|
|||||||
import logging
|
# ----- Imports & Setup -----
|
||||||
import regex as re
|
import logging
|
||||||
from random import randint
|
import regex as re
|
||||||
from asyncio import run as asyncio_run, sleep
|
from random import randint
|
||||||
import discord
|
from asyncio import run as asyncio_run, sleep
|
||||||
from discord import Bot, Message
|
from datetime import timedelta
|
||||||
from discord.abc import GuildChannel as Channel
|
import discord
|
||||||
|
from discord import Bot, Message
|
||||||
from ai_logic import run, get_instructions
|
from discord.abc import GuildChannel as Channel
|
||||||
from quotes import select_quote, select_student
|
from discord.ext import commands
|
||||||
from students import *
|
|
||||||
|
from ai_logic import run, get_instructions
|
||||||
logger = logging.getLogger(__name__)
|
from quotes import select_quote, select_student
|
||||||
|
from students import *
|
||||||
SERVER_ID: int = 1339601046745645148
|
|
||||||
GENERAL_ID: int = 1339601047294840876
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
bot = Bot(intents=discord.Intents.all())
|
SERVER_ID: int = 1339601046745645148
|
||||||
|
GENERAL_ID: int = 1339601047294840876
|
||||||
# ---------- Discord Helper Functions ----------
|
|
||||||
async def get_channel(channel_id: int) -> Channel:
|
bot = Bot(intents=discord.Intents.all())
|
||||||
"""
|
|
||||||
Tries to get a cached channel object, if it fails it will send an API request to retrieve a new one.
|
# ----- Discord Helper Functions -----
|
||||||
|
async def get_channel(channel_id: int) -> Channel:
|
||||||
Args:
|
"""
|
||||||
channel_id (int): The ID of the channel to retrieve.
|
Tries to get a cached channel object, if it fails it will send an API request to retrieve a new one.
|
||||||
|
|
||||||
Raises:
|
Args:
|
||||||
ValueError: If the channel cannot be retrieved.
|
channel_id (int): The ID of the channel to retrieve.
|
||||||
|
|
||||||
Returns:
|
Raises:
|
||||||
discord.abc.GuildChannel: The retrieved discord channel object.
|
ValueError: If the channel cannot be retrieved.
|
||||||
"""
|
|
||||||
logger.debug(f"Attempting to get channel with ID {channel_id}")
|
Returns:
|
||||||
channel = bot.get_channel(channel_id)
|
discord.abc.GuildChannel: The retrieved discord channel object.
|
||||||
if not channel:
|
"""
|
||||||
logger.debug(f"Channel with ID {channel_id} not found in cache, fetching from API.")
|
logger.debug(f"Attempting to get channel with ID {channel_id}")
|
||||||
channel = await bot.fetch_channel(channel_id)
|
channel = bot.get_channel(channel_id)
|
||||||
if not channel:
|
if not channel:
|
||||||
logger.critical(f"Could not fetch channel with channel_id {channel_id}, fetch_channel returned None.")
|
logger.debug(f"Channel with ID {channel_id} not found in cache, fetching from API.")
|
||||||
return None
|
channel = await bot.fetch_channel(channel_id)
|
||||||
logger.info(f"Successfully retrieved {channel_id}, #{channel.name}")
|
if not channel:
|
||||||
return channel
|
logger.critical(f"Could not fetch channel with channel_id {channel_id}, fetch_channel returned None.")
|
||||||
|
return None
|
||||||
async def get_message(message: Message | int, channel_id: int = None, attempts: int = 3) -> Message:
|
logger.info(f"Successfully retrieved {channel_id}, #{channel.name}")
|
||||||
"""
|
return channel
|
||||||
Retrieves a message object either from cache or by fetching it from the channel.
|
|
||||||
|
async def get_message(message: Message | int, channel_id: int = None, attempts: int = 3) -> Message:
|
||||||
Args:
|
"""
|
||||||
message (Message | int): The message object or message ID to retrieve.
|
Retrieves a message object either from cache or by fetching it from the channel.
|
||||||
channel_id (int, optional): The ID of the channel to fetch the message from if not in cache.
|
|
||||||
attempts (int, optional): The number of attempts to retrieve the message. Defaults to 3.
|
Args:
|
||||||
|
message (Message | int): The message object or message ID to retrieve.
|
||||||
Returns:
|
channel_id (int, optional): The ID of the channel to fetch the message from if not in cache.
|
||||||
Message: The retrieved message object.
|
attempts (int, optional): The number of attempts to retrieve the message. Defaults to 3.
|
||||||
"""
|
|
||||||
logger.debug(f"Attempting to get message with ID {message if isinstance(message, int) else message.id}")
|
Returns:
|
||||||
for attempt in range(attempts):
|
Message: The retrieved message object.
|
||||||
if isinstance(message, int):
|
"""
|
||||||
got_message: Message = bot.get_message(message)
|
logger.debug(f"Attempting to get message with ID {message if isinstance(message, int) else message.id}")
|
||||||
if not got_message and channel_id:
|
for attempt in range(attempts):
|
||||||
logger.debug(f"Message with ID {message} not found in cache, fetching from channel ID {channel_id}.")
|
if isinstance(message, int):
|
||||||
channel: discord.abc.GuildChannel = await get_channel(channel_id)
|
got_message: Message = bot.get_message(message)
|
||||||
got_message: Message = await channel.fetch_message(message)
|
if not got_message and channel_id:
|
||||||
elif not got_message:
|
logger.debug(f"Message with ID {message} not found in cache, fetching from channel ID {channel_id}.")
|
||||||
logger.error(f"Message with ID {message} not found in cache and no channel ID provided.")
|
channel: discord.abc.GuildChannel = await get_channel(channel_id)
|
||||||
elif isinstance(message, Message):
|
got_message: Message = await channel.fetch_message(message)
|
||||||
got_message: Message = bot.get_message(message.id)
|
elif not got_message:
|
||||||
if not got_message:
|
logger.error(f"Message with ID {message} not found in cache and no channel ID provided.")
|
||||||
logger.debug(f"Message with ID {message.id} not found in cache, fetching from channel.")
|
elif isinstance(message, Message):
|
||||||
got_message: Message = await message.channel.fetch_message(message.id)
|
got_message: Message = bot.get_message(message.id)
|
||||||
else:
|
if not got_message:
|
||||||
logger.error(f"Couldn't retrieve message:\nMessage: {message}\nChannel ID: {channel_id}")
|
logger.debug(f"Message with ID {message.id} not found in cache, fetching from channel.")
|
||||||
return None
|
got_message: Message = await message.channel.fetch_message(message.id)
|
||||||
|
else:
|
||||||
if got_message:
|
logger.error(f"Couldn't retrieve message:\nMessage: {message}\nChannel ID: {channel_id}")
|
||||||
return got_message
|
return None
|
||||||
|
|
||||||
if attempt < attempts - 1:
|
if got_message:
|
||||||
logger.warning(f"Attempt {attempt + 1} failed. Retrying in 2 seconds...")
|
return got_message
|
||||||
await sleep(2)
|
|
||||||
|
if attempt < attempts - 1:
|
||||||
logger.error(f"Failed to retrieve message after {attempts} attempts.")
|
logger.warning(f"Attempt {attempt + 1} failed. Retrying in 2 seconds...")
|
||||||
return None
|
await sleep(2)
|
||||||
|
|
||||||
async def send_message(message: str, channel: int | Channel = GENERAL_ID) -> Message:
|
logger.error(f"Failed to retrieve message after {attempts} attempts.")
|
||||||
"""
|
return None
|
||||||
Has the bot send a message to a specified channel. If no channel is specified, the bot sends it to general.
|
|
||||||
|
async def send_message(message: str, channel: int | Channel = GENERAL_ID) -> Message:
|
||||||
Args:
|
"""
|
||||||
message (str): The message to send.
|
Has the bot send a message to a specified channel. If no channel is specified, the bot sends it to general.
|
||||||
channel (int | Message, optional): Channel to send message to, Defaults to GENERAL_ID.
|
|
||||||
|
Args:
|
||||||
Returns:
|
message (str): The message to send.
|
||||||
Message: The sent message object.
|
channel (int | Message, optional): Channel to send message to, Defaults to GENERAL_ID.
|
||||||
"""
|
|
||||||
logger.debug(f"Attempting to send message: {message}")
|
Returns:
|
||||||
if isinstance(channel, int):
|
Message: The sent message object.
|
||||||
channel = await get_channel(channel)
|
"""
|
||||||
if isinstance(channel, Channel):
|
logger.debug(f"Attempting to send message: {message}")
|
||||||
sent_message: Message = await channel.send(message)
|
if isinstance(channel, int):
|
||||||
if isinstance(sent_message, Message):
|
channel = await get_channel(channel)
|
||||||
logger.info(f"Message '{message}' successfully sent to {sent_message.channel}")
|
if isinstance(channel, Channel):
|
||||||
return sent_message
|
sent_message: Message = await channel.send(message)
|
||||||
else:
|
if isinstance(sent_message, Message):
|
||||||
logger.error(f"Message likely wasn't successfully sent, as channel.send did not return a Message.")
|
logger.info(f"Message '{message}' successfully sent to {sent_message.channel}")
|
||||||
elif isinstance(channel, None):
|
return sent_message
|
||||||
logger.error(f"Message couldn't be sent as a channel wasn't found/messagable.")
|
else:
|
||||||
|
logger.error(f"Message likely wasn't successfully sent, as channel.send did not return a Message.")
|
||||||
async def edit_message(message: Message | int, channel: int = None):
|
elif isinstance(channel, None):
|
||||||
"""
|
logger.error(f"Message couldn't be sent as a channel wasn't found/messagable.")
|
||||||
Edits a message in a channel.
|
|
||||||
|
async def edit_message(message: Message | int, channel: int = None):
|
||||||
Args:
|
"""
|
||||||
message (Message | int): The message to edit.
|
Edits a message in a channel.
|
||||||
channel (int, optional): The channel ID of the message. Defaults to None.
|
|
||||||
"""
|
Args:
|
||||||
logger.debug(f"Attempting to edit message {message if isinstance(message, int) else message.id}")
|
message (Message | int): The message to edit.
|
||||||
if isinstance(message, int):
|
channel (int, optional): The channel ID of the message. Defaults to None.
|
||||||
message = await get_message(message, channel)
|
"""
|
||||||
if isinstance(message, Message):
|
logger.debug(f"Attempting to edit message {message if isinstance(message, int) else message.id}")
|
||||||
await message.edit(content=message.content)
|
if isinstance(message, int):
|
||||||
logger.info(f"Message {message.id} successfully edited.")
|
message = await get_message(message, channel)
|
||||||
else:
|
if isinstance(message, Message):
|
||||||
logger.error(f"Message couldn't be edited as it was not found.")
|
await message.edit(content=message.content)
|
||||||
|
logger.info(f"Message {message.id} successfully edited.")
|
||||||
# ---------- Message Utility Functions ----------
|
else:
|
||||||
async def msg_is_reply(message: Message) -> tuple[bool, Message]:
|
logger.error(f"Message couldn't be edited as it was not found.")
|
||||||
"""
|
|
||||||
Checks if a message is a reply to another message.
|
# ----- Message Utility Functions -----
|
||||||
|
async def msg_is_reply(message: Message) -> tuple[bool, Message]:
|
||||||
Args:
|
"""
|
||||||
message (Message): The message to check.
|
Checks if a message is a reply to another message.
|
||||||
|
|
||||||
Returns:
|
Args:
|
||||||
tuple[bool, Message]: A tuple containing a boolean indicating if the message is a reply and the replied message.
|
message (Message): The message to check.
|
||||||
"""
|
|
||||||
logger.debug(f"Checking if message ID {message.id} is a reply.")
|
Returns:
|
||||||
if message.reference is not None:
|
tuple[bool, Message]: A tuple containing a boolean indicating if the message is a reply and the replied message.
|
||||||
replied_msg = message.reference.cached_message
|
"""
|
||||||
|
logger.debug(f"Checking if message ID {message.id} is a reply.")
|
||||||
if replied_msg is None:
|
if message.reference is not None:
|
||||||
logger.debug(f"Replied message not found in cache, fetching from channel ID {message.reference.channel_id}.")
|
replied_msg = message.reference.cached_message
|
||||||
channel = await get_channel(message.reference.channel_id)
|
|
||||||
replied_msg = await channel.fetch_message(message.reference.message_id)
|
if replied_msg is None:
|
||||||
|
logger.debug(f"Replied message not found in cache, fetching from channel ID {message.reference.channel_id}.")
|
||||||
if isinstance(replied_msg, Message):
|
channel = await get_channel(message.reference.channel_id)
|
||||||
return True, replied_msg
|
replied_msg = await channel.fetch_message(message.reference.message_id)
|
||||||
return False, None
|
|
||||||
|
if isinstance(replied_msg, Message):
|
||||||
async def get_reply_chain(msg: Message) -> list[Message]:
|
return True, replied_msg
|
||||||
"""
|
return False, None
|
||||||
Retrieves the chain of replies for a given message.
|
|
||||||
|
async def get_reply_chain(msg: Message) -> list[Message]:
|
||||||
Args:
|
"""
|
||||||
msg (Message): The message to get the reply chain for.
|
Retrieves the chain of replies for a given message.
|
||||||
|
|
||||||
Returns:
|
Args:
|
||||||
list[Message]: A list of messages in the reply chain.
|
msg (Message): The message to get the reply chain for.
|
||||||
"""
|
|
||||||
logger.debug(f"Getting reply chain for message ID {msg.id}.")
|
Returns:
|
||||||
i = 0
|
list[Message]: A list of messages in the reply chain.
|
||||||
reply_chain = [msg]
|
"""
|
||||||
while i < 10:
|
logger.debug(f"Getting reply chain for message ID {msg.id}.")
|
||||||
is_reply, msg = await msg_is_reply(msg)
|
i = 0
|
||||||
if not is_reply:
|
reply_chain = [msg]
|
||||||
break
|
while i < 10:
|
||||||
reply_chain.insert(0, msg)
|
is_reply, msg = await msg_is_reply(msg)
|
||||||
i += 1
|
if not is_reply:
|
||||||
return reply_chain
|
break
|
||||||
|
reply_chain.insert(0, msg)
|
||||||
async def gen_message_list(discord_messages: list[Message]) -> list[dict]:
|
i += 1
|
||||||
"""
|
return reply_chain
|
||||||
Generates a list of message dictionaries from a list of Discord messages.
|
|
||||||
|
async def gen_message_list(discord_messages: list[Message]) -> list[dict]:
|
||||||
Args:
|
"""
|
||||||
discord_messages (list[Message]): The list of Discord messages.
|
Generates a list of message dictionaries from a list of Discord messages.
|
||||||
|
|
||||||
Returns:
|
Args:
|
||||||
list[dict]: A list of message dictionaries.
|
discord_messages (list[Message]): The list of Discord messages.
|
||||||
"""
|
|
||||||
logger.debug("Generating message list from Discord messages.")
|
Returns:
|
||||||
messages = []
|
list[dict]: A list of message dictionaries.
|
||||||
if not isinstance(discord_messages, list):
|
"""
|
||||||
discord_messages = [discord_messages]
|
logger.debug("Generating message list from Discord messages.")
|
||||||
|
messages = []
|
||||||
for message in discord_messages:
|
if not isinstance(discord_messages, list):
|
||||||
students_mentioned = None
|
discord_messages = [discord_messages]
|
||||||
if isinstance(message, Message):
|
|
||||||
message_copy = {
|
for message in discord_messages:
|
||||||
"id": message.id,
|
students_mentioned = None
|
||||||
"content": message.content,
|
if isinstance(message, Message):
|
||||||
"author": message.author,
|
message_copy = {
|
||||||
"mentions": message.mentions,
|
"id": message.id,
|
||||||
"created_at": message.created_at,
|
"content": message.content,
|
||||||
"jump_url": message.jump_url
|
"author": message.author,
|
||||||
}
|
"mentions": message.mentions,
|
||||||
role = "assistant" if message_copy["author"].id == bot.user.id else "user"
|
"created_at": message.created_at,
|
||||||
students_mentioned = None
|
"jump_url": message.jump_url
|
||||||
|
}
|
||||||
if message_copy["mentions"] and role == "user":
|
role = "assistant" if message_copy["author"].id == bot.user.id else "user"
|
||||||
students_mentioned = [student.id for student in message_copy["mentions"] if student.id in STUDENT_IDS]
|
students_mentioned = None
|
||||||
mentions = re.findall(r'<@\d{17,19}>', message_copy["content"])
|
|
||||||
for mention in mentions:
|
if message_copy["mentions"] and role == "user":
|
||||||
mention_id = mention.replace("<@", "").replace(">", "")
|
students_mentioned = [student.id for student in message_copy["mentions"] if student.id in STUDENT_IDS]
|
||||||
student_info = ID_TO_NAME.get(int(mention_id))
|
mentions = re.findall(r'<@\d{17,19}>', message_copy["content"])
|
||||||
if student_info:
|
for mention in mentions:
|
||||||
message_copy["content"] = message_copy["content"].replace(mention, student_info.split("\n")[0])
|
mention_id = mention.replace("<@", "").replace(">", "")
|
||||||
message_copy["content"] += '\n{'
|
student_info = ID_TO_NAME.get(int(mention_id))
|
||||||
if students_mentioned:
|
if student_info:
|
||||||
message_copy["content"] += f"\n\nInfo on students mentioned (DO NOT REPEAT!): "
|
message_copy["content"] = message_copy["content"].replace(mention, student_info.split("\n")[0])
|
||||||
for student in students_mentioned:
|
message_copy["content"] += '\n{'
|
||||||
message_copy["content"] += f"\n{STUDENTS.get(student)}"
|
if students_mentioned:
|
||||||
message_copy["content"] += f"\n\nSent by: {STUDENTS.get(message_copy['author'].id) if message_copy['author'].id in STUDENT_IDS else message_copy['author'].name + ' (Author\'s details not found)'}" + '}'
|
message_copy["content"] += f"\n\nInfo on students mentioned (DO NOT REPEAT!): "
|
||||||
|
for student in students_mentioned:
|
||||||
if not message_copy["content"]:
|
message_copy["content"] += f"\n{STUDENTS.get(student)}"
|
||||||
debug = True
|
message_copy["content"] += f"\n\nSent by: {STUDENTS.get(message_copy['author'].id) if message_copy['author'].id in STUDENT_IDS else message_copy['author'].name + ' (Author\'s details not found)'}" + '}'
|
||||||
logger.error(f"No message content for message {message_copy['id']}.")
|
|
||||||
fetched_message = await get_message(message_copy["id"])
|
if not message_copy["content"]:
|
||||||
message_copy["content"] = fetched_message.content
|
debug = True
|
||||||
|
logger.error(f"No message content for message {message_copy['id']}.")
|
||||||
thread_message = {
|
fetched_message = await get_message(message_copy["id"])
|
||||||
"role": role,
|
message_copy["content"] = fetched_message.content
|
||||||
"content": message_copy["content"]
|
|
||||||
}
|
thread_message = {
|
||||||
debug = True # Delete after fix
|
"role": role,
|
||||||
if debug:
|
"content": message_copy["content"]
|
||||||
debug_dump = "Messages Dump:\n"
|
}
|
||||||
for msg_copy in discord_messages:
|
debug = True # Delete after fix
|
||||||
debug_dump += f"""
|
if debug:
|
||||||
Message: {msg_copy}
|
debug_dump = "Messages Dump:\n"
|
||||||
Message Time: {msg_copy.created_at}
|
for msg_copy in discord_messages:
|
||||||
Message ID: {msg_copy.id}
|
debug_dump += f"""
|
||||||
Message Author: {msg_copy.author}
|
Message: {msg_copy}
|
||||||
Message Content: {msg_copy.content}
|
Message Time: {msg_copy.created_at}
|
||||||
Link: {msg_copy.jump_url}
|
Message ID: {msg_copy.id}
|
||||||
"""
|
Message Author: {msg_copy.author}
|
||||||
logger.debug(debug_dump)
|
Message Content: {msg_copy.content}
|
||||||
messages.append(thread_message)
|
Link: {msg_copy.jump_url}
|
||||||
else:
|
"""
|
||||||
logger.warning(f"Argument {message} is not of type Message and will be skipped.")
|
logger.debug(debug_dump)
|
||||||
|
messages.append(thread_message)
|
||||||
return messages
|
else:
|
||||||
|
logger.warning(f"Argument {message} is not of type Message and will be skipped.")
|
||||||
# ---------- AI Interaction Functions ----------
|
|
||||||
async def send_quote(quote: str = None) -> None:
|
return messages
|
||||||
"""
|
|
||||||
Sends a quote to the general channel.
|
# ----- AI Interaction Functions -----
|
||||||
|
async def send_quote(quote: str = None) -> None:
|
||||||
Args:
|
"""
|
||||||
quote (str, optional): The quote to send. If not provided, a random quote is selected.
|
Sends a quote to the general channel.
|
||||||
"""
|
|
||||||
if not quote:
|
Args:
|
||||||
quote = select_quote()
|
quote (str, optional): The quote to send. If not provided, a random quote is selected.
|
||||||
logger.info(f"Sending quote '{quote}' in #general...")
|
"""
|
||||||
await send_message(quote)
|
if not quote:
|
||||||
|
quote = select_quote()
|
||||||
async def after_class(student: int = None) -> None:
|
logger.info(f"Sending quote '{quote}' in #general...")
|
||||||
"""
|
await send_message(quote)
|
||||||
Sends a message to a student to see the bot after class.
|
|
||||||
|
async def after_class(student: int = None) -> None:
|
||||||
Args:
|
"""
|
||||||
student (int, optional): The ID of the student. If not provided, a random student is selected.
|
Sends a message to a student to see the bot after class.
|
||||||
"""
|
|
||||||
if not student:
|
Args:
|
||||||
student = select_student()
|
student (int, optional): The ID of the student. If not provided, a random student is selected.
|
||||||
logger.info(f"Sending mention to see {student} after class to #general...")
|
"""
|
||||||
await send_message(f"Come see me after class <@{student}>")
|
if not student:
|
||||||
|
student = select_student()
|
||||||
async def has_essay(message: Message) -> bool:
|
logger.info(f"Sending mention to see {student} after class to #general...")
|
||||||
if message.author.id in ASSIGNED_ESSAY:
|
await send_message(f"Come see me after class <@{student}>")
|
||||||
async with message.channel.typing():
|
|
||||||
message = await get_message(message) if not message else message
|
async def has_essay(message: Message) -> bool:
|
||||||
message_list = await gen_message_list(message)
|
if message.author.id in ASSIGNED_ESSAY:
|
||||||
response = await run(
|
async with message.channel.typing():
|
||||||
message_list,
|
message = await get_message(message) if not message else message
|
||||||
f"{get_instructions(message)}\n\nTHIS USER HAS AN ESSAY!!! Yell at them and tell them to finish their essay on {ASSIGNED_ESSAY.get(message.author.id)}!!! Unless...they sent you the essay, in that case, you can decide whether or not it is satisfactory (it needs to be in MLA format and 300-2000 characters long, and to be relevant of course). If it is satisfactory, you can tell them they are free to go, after you run the function \"clear_essay\".",
|
message_list = await gen_message_list(message)
|
||||||
message.author.id
|
response = await run(
|
||||||
)
|
message_list,
|
||||||
await message.reply(response)
|
f"{get_instructions(message)}\n\nTHIS USER HAS AN ESSAY!!! Yell at them and tell them to finish their essay on {ASSIGNED_ESSAY.get(message.author.id)}!!! Unless...they sent you the essay, in that case, you can decide whether or not it is satisfactory (it needs to be in MLA format and 300-2000 characters long, and to be relevant of course). If it is satisfactory, you can tell them they are free to go, after you run the function \"clear_essay\".",
|
||||||
return True
|
message.author.id
|
||||||
else:
|
)
|
||||||
return False
|
await message.reply(response)
|
||||||
|
return True
|
||||||
# ---------- Discord Commands & Event Handlers ----------
|
else:
|
||||||
def setup_discord_bot(bot: Bot) -> None:
|
return False
|
||||||
"""
|
|
||||||
Sets up the Discord bot with commands and event listeners.
|
# ----- Discord Commands & Event Handlers -----
|
||||||
|
def setup_discord_bot(bot: Bot) -> None:
|
||||||
Args:
|
"""
|
||||||
bot (Bot): The Discord bot instance.
|
Sets up the Discord bot with commands and event listeners.
|
||||||
"""
|
|
||||||
@bot.slash_command(description="Talk to Mr. Jacobs!!!")
|
Args:
|
||||||
async def message(ctx: discord.ApplicationContext, text: str) -> None:
|
bot (Bot): The Discord bot instance.
|
||||||
"""
|
"""
|
||||||
Slash command to talk to Mr. Jacobs. The bot will respond to the user's message using AI.
|
@bot.slash_command(description="Talk to Mr. Jacobs!!!")
|
||||||
|
async def message(ctx: discord.ApplicationContext, text: str) -> None:
|
||||||
Args:
|
"""
|
||||||
ctx (discord.ApplicationContext): The context of the command.
|
Slash command to talk to Mr. Jacobs. The bot will respond to the user's message using AI.
|
||||||
text (str): The message text.
|
|
||||||
"""
|
Args:
|
||||||
logging.info(f"User {ctx.author.global_name} sent message {text}")
|
ctx (discord.ApplicationContext): The context of the command.
|
||||||
await ctx.defer()
|
text (str): The message text.
|
||||||
instructions = get_instructions(ctx.channel)
|
"""
|
||||||
thread_messages = [{"role": "user", "content": text}]
|
logging.info(f"User {ctx.author.global_name} sent message {text}")
|
||||||
response = await run(thread_messages, instructions, ctx.author.id)
|
await ctx.defer()
|
||||||
await ctx.respond(content=response)
|
instructions = get_instructions(ctx.channel)
|
||||||
|
thread_messages = [{"role": "user", "content": text}]
|
||||||
@bot.slash_command(
|
response = await run(thread_messages, instructions, ctx.author.id)
|
||||||
name="rps",
|
await ctx.respond(content=response)
|
||||||
description="Play \"Rock, Paper, Scissors, Essay\" with Mr. Jacobs!!!",
|
|
||||||
)
|
@bot.slash_command(name="clear_essay", description="Clear the essay assigned to a student")
|
||||||
async def rps_essay(
|
async def clear_essay_command(ctx: discord.ApplicationContext, student: str) -> None:
|
||||||
ctx: discord.ApplicationContext,
|
"""
|
||||||
choice: str = discord.Option(
|
Slash command to clear the essay assigned to a student.
|
||||||
description="Your selection for the game",
|
|
||||||
choices=[
|
Args:
|
||||||
discord.OptionChoice("Rock"),
|
ctx (discord.ApplicationContext): The context of the command.
|
||||||
discord.OptionChoice("Paper"),
|
student (str): The ID of the student whose essay is to be cleared.
|
||||||
discord.OptionChoice("Scissors")
|
"""
|
||||||
]
|
try:
|
||||||
)
|
student = int(student)
|
||||||
) -> None:
|
except Exception as e:
|
||||||
"""
|
logging.error(f"Failed to convert student ID to int: {e}")
|
||||||
Play Rock Paper Scissors with Mr. Jacobs.
|
await ctx.respond("Invalid student ID format.", ephemeral=True)
|
||||||
|
return
|
||||||
Args:
|
if not ctx.author.id == 620319269233885226:
|
||||||
ctx: Application command context
|
await ctx.respond("You don't have permission to use this command.")
|
||||||
choice: Your selection for the game
|
return
|
||||||
"""
|
if student not in STUDENT_IDS:
|
||||||
logging.info(f"{ctx.author} chose {choice}")
|
await ctx.respond("Invalid student ID.", ephemeral=True)
|
||||||
outcomes = {"Rock": "Paper", "Paper": "Scissors", "Scissors": "Rock"}
|
return
|
||||||
await ctx.respond(
|
ASSIGNED_ESSAY.pop(student, None)
|
||||||
f"I choose {outcomes[choice]}, you owe me a {randint(1,15)} page essay "
|
await ctx.respond(f"Cleared essay for <@{student}>")
|
||||||
f"on {assign_essay(ctx.author.id)}. MLA format, double spaced, "
|
|
||||||
"12pt Times New Roman!"
|
@bot.slash_command(name="get_essay", description="Get the essay assigned to a student, or all students")
|
||||||
)
|
async def get_essay_command(ctx: discord.ApplicationContext, student: str = None) -> None:
|
||||||
|
if student:
|
||||||
@bot.event
|
try:
|
||||||
async def on_message(message: Message) -> None:
|
student = int(student)
|
||||||
"""
|
except Exception as e:
|
||||||
Event listener for new messages. The bot will respond to messages that mention it or reply to its messages.
|
logging.error(f"Failed to convert student ID to int: {e}")
|
||||||
Bot disregards its own messages.
|
await ctx.respond("Invalid student ID format.", ephemeral=True)
|
||||||
|
return
|
||||||
Args:
|
await ctx.respond(get_essay(student))
|
||||||
message (Message): The new message.
|
else:
|
||||||
"""
|
await ctx.respond(get_essay(), ephemeral=True)
|
||||||
logger.debug(f"New message received: {message.content}")
|
|
||||||
|
@bot.slash_command(name="assign_essay", description="Assign an essay to a student")
|
||||||
if message.author.id == bot.user.id or message.interaction_metadata:
|
@commands.has_permissions(moderate_members=True)
|
||||||
return
|
async def assign_essay_command(
|
||||||
|
ctx: discord.ApplicationContext,
|
||||||
if await handle_reply(message):
|
student: str,
|
||||||
return
|
timeout: int | None = 0,
|
||||||
|
topic: str | None = None
|
||||||
if await handle_mentions(message):
|
) -> None:
|
||||||
return
|
"""
|
||||||
|
Slash command to assign an essay to a student.
|
||||||
async def handle_reply(message: Message) -> bool:
|
|
||||||
"""
|
Args:
|
||||||
Handles replies to the bot's messages.
|
ctx (discord.ApplicationContext): The context of the command.
|
||||||
|
student (int): The ID of the student to assign the essay to.
|
||||||
Args:
|
timeout (int | None, optional): The timeout in seconds before the essay is cleared. Defaults to 0.
|
||||||
message (Message): The new message.
|
topic (str | None, optional): The topic of the essay. If not provided, a random topic is selected.
|
||||||
|
"""
|
||||||
Returns:
|
try:
|
||||||
bool: True if the message was handled, False otherwise.
|
student = int(student)
|
||||||
"""
|
except Exception as e:
|
||||||
is_reply, first_reply = await msg_is_reply(message)
|
logging.error(f"Failed to convert student ID to int: {e}")
|
||||||
if is_reply and first_reply.author.id == bot.user.id:
|
await ctx.respond("Invalid student ID format.")
|
||||||
async with message.channel.typing():
|
return
|
||||||
if await has_essay(message):
|
if not ctx.author.id == 620319269233885226:
|
||||||
return True
|
await ctx.respond("You don't have permission to use this command.")
|
||||||
reply_chain = await get_reply_chain(message)
|
return
|
||||||
thread_messages = await gen_message_list(reply_chain)
|
if timeout <= 0:
|
||||||
await process_thread_messages(thread_messages, message)
|
timeout = None
|
||||||
return True
|
logging.info(f"Assigning essay to student {student} with timeout {timeout} and topic {topic}")
|
||||||
return False
|
if student not in STUDENT_IDS:
|
||||||
|
await ctx.respond("Invalid student ID.", ephemeral=True)
|
||||||
async def handle_mentions(message: Message) -> bool:
|
return
|
||||||
"""
|
assign_essay(student, topic)
|
||||||
Handles messages that mention the bot.
|
await ctx.respond(f"Assigned essay to <@{student}>: {ASSIGNED_ESSAY[student]}")
|
||||||
|
if timeout and topic:
|
||||||
Args:
|
timeout_until = discord.utils.utcnow() + timedelta(seconds=timeout)
|
||||||
message (Message): The new message.
|
try:
|
||||||
|
member: discord.Member = await ctx.interaction.guild.fetch_member(student)
|
||||||
Returns:
|
await member.timeout(until=timeout_until, reason=f"Assigned essay: {topic}")
|
||||||
bool: True if the message was handled, False otherwise.
|
await ctx.respond(f'{member.mention} has been timed out for {timeout}.', ephemeral=True)
|
||||||
"""
|
except discord.Forbidden:
|
||||||
if message.mentions:
|
await ctx.respond(f'Failed to timeout {member.mention}. Missing permissions.', ephemeral=True)
|
||||||
for user in message.mentions:
|
except discord.HTTPException as e:
|
||||||
if user.id == bot.user.id:
|
await ctx.respond(f'Failed to timeout {member.mention}. {e.text}', ephemeral=True)
|
||||||
async with message.channel.typing():
|
return
|
||||||
if await has_essay(message):
|
|
||||||
return True
|
@bot.slash_command(
|
||||||
thread_messages = await gen_message_list(message)
|
name="rps",
|
||||||
await process_thread_messages(thread_messages, message)
|
description="Play \"Rock, Paper, Scissors, Essay\" with Mr. Jacobs!!!",
|
||||||
return True
|
)
|
||||||
return False
|
async def rps_essay(
|
||||||
|
ctx: discord.ApplicationContext,
|
||||||
async def process_thread_messages(thread_messages: list, message: Message) -> None:
|
choice: str = discord.Option(
|
||||||
"""
|
description="Your selection for the game",
|
||||||
Processes a list of thread messages and sends a response.
|
choices=[
|
||||||
|
discord.OptionChoice("Rock"),
|
||||||
Args:
|
discord.OptionChoice("Paper"),
|
||||||
thread_messages (list): The list of thread messages.
|
discord.OptionChoice("Scissors")
|
||||||
message (Message): The original message.
|
]
|
||||||
"""
|
)
|
||||||
instructions = get_instructions(message)
|
) -> None:
|
||||||
response = await run(thread_messages, instructions, message.author.id)
|
"""
|
||||||
await message.reply(response)
|
Play Rock Paper Scissors with Mr. Jacobs.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ctx: Application command context
|
||||||
|
choice: Your selection for the game
|
||||||
|
"""
|
||||||
|
logging.info(f"{ctx.author} chose {choice}")
|
||||||
|
outcomes = {"Rock": "Paper", "Paper": "Scissors", "Scissors": "Rock"}
|
||||||
|
await ctx.respond(
|
||||||
|
f"I choose {outcomes[choice]}, you owe me a {randint(1,15)} page essay "
|
||||||
|
f"on {assign_essay(ctx.author.id)}. MLA format, double spaced, "
|
||||||
|
"12pt Times New Roman!"
|
||||||
|
)
|
||||||
|
|
||||||
|
@bot.event
|
||||||
|
async def on_message(message: Message) -> None:
|
||||||
|
"""
|
||||||
|
Event listener for new messages. The bot will respond to messages that mention it or reply to its messages.
|
||||||
|
Bot disregards its own messages.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message (Message): The new message.
|
||||||
|
"""
|
||||||
|
logger.debug(f"New message received: {message.content}")
|
||||||
|
|
||||||
|
if message.author.id == bot.user.id or message.interaction_metadata:
|
||||||
|
return
|
||||||
|
|
||||||
|
if await handle_reply(message):
|
||||||
|
return
|
||||||
|
|
||||||
|
if await handle_mentions(message):
|
||||||
|
return
|
||||||
|
|
||||||
|
async def handle_reply(message: Message) -> bool:
|
||||||
|
"""
|
||||||
|
Handles replies to the bot's messages.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message (Message): The new message.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if the message was handled, False otherwise.
|
||||||
|
"""
|
||||||
|
is_reply, first_reply = await msg_is_reply(message)
|
||||||
|
if is_reply and first_reply.author.id == bot.user.id:
|
||||||
|
async with message.channel.typing():
|
||||||
|
if await has_essay(message):
|
||||||
|
return True
|
||||||
|
reply_chain = await get_reply_chain(message)
|
||||||
|
thread_messages = await gen_message_list(reply_chain)
|
||||||
|
await process_thread_messages(thread_messages, message)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def handle_mentions(message: Message) -> bool:
|
||||||
|
"""
|
||||||
|
Handles messages that mention the bot.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message (Message): The new message.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if the message was handled, False otherwise.
|
||||||
|
"""
|
||||||
|
if message.mentions:
|
||||||
|
for user in message.mentions:
|
||||||
|
if user.id == bot.user.id:
|
||||||
|
async with message.channel.typing():
|
||||||
|
if await has_essay(message):
|
||||||
|
return True
|
||||||
|
thread_messages = await gen_message_list(message)
|
||||||
|
await process_thread_messages(thread_messages, message)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def process_thread_messages(thread_messages: list, message: Message) -> None:
|
||||||
|
"""
|
||||||
|
Processes a list of thread messages and sends a response.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
thread_messages (list): The list of thread messages.
|
||||||
|
message (Message): The original message.
|
||||||
|
"""
|
||||||
|
instructions = get_instructions(message)
|
||||||
|
response = await run(thread_messages, instructions, message.author.id)
|
||||||
|
await message.reply(response)
|
||||||
|
|||||||
16
docker-compose.yml
Normal file → Executable file
16
docker-compose.yml
Normal file → Executable file
@@ -1,9 +1,9 @@
|
|||||||
services:
|
services:
|
||||||
discord-bot:
|
discord-bot:
|
||||||
container_name: jacobs-bot
|
container_name: jacobs-bot
|
||||||
image: docker.foreverpyrite.com/jacobs-bot:beta
|
image: docker.foreverpyrite.com/jacobs-bot:beta
|
||||||
volumes:
|
volumes:
|
||||||
- ./logs/:/app/logs/
|
- ./logs/:/app/logs/
|
||||||
- ./data/:/app/data/
|
- ./data/:/app/data/
|
||||||
env_file: .env
|
env_file: .env
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
130
quotes.py
Normal file → Executable file
130
quotes.py
Normal file → Executable file
@@ -1,65 +1,65 @@
|
|||||||
import random
|
import random
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from students import STUDENT_IDS
|
from students import STUDENT_IDS
|
||||||
|
|
||||||
QUOTES = {
|
QUOTES = {
|
||||||
"NORMAL" : (
|
"NORMAL" : (
|
||||||
"Oh boy, we are about to wake up!",
|
"Oh boy, we are about to wake up!",
|
||||||
"All right 👏 🫰",
|
"All right 👏 🫰",
|
||||||
"How we doing, Paul?",
|
"How we doing, Paul?",
|
||||||
"*Drops pen*",
|
"*Drops pen*",
|
||||||
"How we doing, guys?",
|
"How we doing, guys?",
|
||||||
"Killing it!!!",
|
"Killing it!!!",
|
||||||
"*Bounces ball off wall*",
|
"*Bounces ball off wall*",
|
||||||
"Ugggghhh...",
|
"Ugggghhh...",
|
||||||
"Hmm... I see.",
|
"Hmm... I see.",
|
||||||
"What are we doing over here?",
|
"What are we doing over here?",
|
||||||
"Mmm... Okay! :clap:",
|
"Mmm... Okay! :clap:",
|
||||||
"*LOUDLY* We don't like stupid prizes, now do we? Then, don't play stupid games!",
|
"*LOUDLY* We don't like stupid prizes, now do we? Then, don't play stupid games!",
|
||||||
"You guys are killing it!",
|
"You guys are killing it!",
|
||||||
"Do we need to go back over the module again?",
|
"Do we need to go back over the module again?",
|
||||||
"Let's get it done!",
|
"Let's get it done!",
|
||||||
"Sorry, I can't hear over Devan and Connor talking.",
|
"Sorry, I can't hear over Devan and Connor talking.",
|
||||||
"That's what I like to hear!",
|
"That's what I like to hear!",
|
||||||
"*Paces*",
|
"*Paces*",
|
||||||
"*Sprints to the ringing phone*",
|
"*Sprints to the ringing phone*",
|
||||||
"*Spins lanyard*",
|
"*Spins lanyard*",
|
||||||
"Come on guys, you should know this already!",
|
"Come on guys, you should know this already!",
|
||||||
"Let me just say this...",
|
"Let me just say this...",
|
||||||
"Not trying to be mean or anything...but..."
|
"Not trying to be mean or anything...but..."
|
||||||
),
|
),
|
||||||
"RARE" : (
|
"RARE" : (
|
||||||
"Play stupid games, win big prizes! 🤑🤑",
|
"Play stupid games, win big prizes! 🤑🤑",
|
||||||
"Oooooo raahahah!",
|
"Oooooo raahahah!",
|
||||||
"It's cherry-pickin' time, y'all!",
|
"It's cherry-pickin' time, y'all!",
|
||||||
"What does the fox say?"
|
"What does the fox say?"
|
||||||
),
|
),
|
||||||
"MYTHIC" : (
|
"MYTHIC" : (
|
||||||
"I'm proud of you.",
|
"I'm proud of you.",
|
||||||
"You can take a 5-minute break.",
|
"You can take a 5-minute break.",
|
||||||
"I have somewhere to be at 9:30, so you guys will have a sub."
|
"I have somewhere to be at 9:30, so you guys will have a sub."
|
||||||
),
|
),
|
||||||
"LEGENDARY": (
|
"LEGENDARY": (
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
def select_quote() -> str:
|
def select_quote() -> str:
|
||||||
rarity = random.randint(0, 99)
|
rarity = random.randint(0, 99)
|
||||||
if rarity < 1:
|
if rarity < 1:
|
||||||
quote = random.choice(QUOTES.get("MYTHIC"))
|
quote = random.choice(QUOTES.get("MYTHIC"))
|
||||||
elif rarity < 15:
|
elif rarity < 15:
|
||||||
quote = random.choice(QUOTES.get("RARE"))
|
quote = random.choice(QUOTES.get("RARE"))
|
||||||
else:
|
else:
|
||||||
quote = random.choice(QUOTES.get("NORMAL"))
|
quote = random.choice(QUOTES.get("NORMAL"))
|
||||||
# Append log to a file (quotes.log)
|
# Append log to a file (quotes.log)
|
||||||
with open("./logs/quotes.log", "at+") as log_file:
|
with open("./logs/quotes.log", "at+") as log_file:
|
||||||
log_file.write(f"At {datetime.datetime.now()}, rarity was rolled as {rarity} and selected: {quote}\n")
|
log_file.write(f"At {datetime.datetime.now()}, rarity was rolled as {rarity} and selected: {quote}\n")
|
||||||
return quote
|
return quote
|
||||||
|
|
||||||
def select_student() -> int:
|
def select_student() -> int:
|
||||||
student = random.choice(STUDENT_IDS)
|
student = random.choice(STUDENT_IDS)
|
||||||
with open("./logs/quotes.log", "at+") as log_file:
|
with open("./logs/quotes.log", "at+") as log_file:
|
||||||
log_file.write(f"At {datetime.datetime.now()}, wanted to see {student} after class.\n")
|
log_file.write(f"At {datetime.datetime.now()}, wanted to see {student} after class.\n")
|
||||||
return student
|
return student
|
||||||
|
|||||||
0
requirements.txt
Normal file → Executable file
0
requirements.txt
Normal file → Executable file
220
students.py
Normal file → Executable file
220
students.py
Normal file → Executable file
@@ -1,97 +1,123 @@
|
|||||||
import logging
|
import logging
|
||||||
from random import choice as random_choice
|
from random import choice as random_choice
|
||||||
import pickle
|
import pickle
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
ID_TO_NAME: dict[int, str] = {
|
ID_TO_NAME: dict[int, str] = {
|
||||||
620319269233885226 : "Devan",
|
620319269233885226 : "Devan",
|
||||||
957378132061618217 : "CJ",
|
957378132061618217 : "CJ",
|
||||||
1077533292687007794 : "Connor",
|
1077533292687007794 : "Connor",
|
||||||
964563429190930465 : "Michael",
|
964563429190930465 : "Michael",
|
||||||
909865829313683536 : "Bea",
|
909865829313683536 : "Bea",
|
||||||
821891090411421747 : "Mika",
|
821891090411421747 : "Mika",
|
||||||
1093276870688133171 : "Daniel",
|
1093276870688133171 : "Daniel",
|
||||||
625861445833457694 : "Paul",
|
625861445833457694 : "Paul",
|
||||||
1336780587662446593: "Mr. Jacobs"
|
1336780587662446593: "Mr. Jacobs"
|
||||||
}
|
}
|
||||||
|
|
||||||
STUDENT_IDS: set = (620319269233885226, 957378132061618217, 1077533292687007794, 964563429190930465, 909865829313683536, 821891090411421747, 1093276870688133171, 625861445833457694)
|
STUDENT_IDS: set = (620319269233885226, 957378132061618217, 1077533292687007794, 964563429190930465, 909865829313683536, 821891090411421747, 1093276870688133171, 625861445833457694)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ASSIGNED_ESSAY: dict = pickle.load(open("./data/assigned_essay.pkl", "rb"))
|
ASSIGNED_ESSAY: dict = pickle.load(open("./data/assigned_essay.pkl", "rb"))
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
logger.warning("No assigned essays found/saved, creating new dictionary.")
|
logger.warning("No assigned essays found/saved, creating new dictionary.")
|
||||||
ASSIGNED_ESSAY: dict[int, str] = {}
|
ASSIGNED_ESSAY: dict[int, str] = {}
|
||||||
with open("./data/assigned_essay.pkl", "xb"):
|
with open("./data/assigned_essay.pkl", "xb"):
|
||||||
pass
|
pass
|
||||||
except EOFError:
|
except EOFError:
|
||||||
logger.warning("Assigned essays file is empty, creating new dictionary.")
|
logger.warning("Assigned essays file is empty, creating new dictionary.")
|
||||||
ASSIGNED_ESSAY: dict[int, str] = {}
|
ASSIGNED_ESSAY: dict[int, str] = {}
|
||||||
finally:
|
finally:
|
||||||
logger.debug(f"Assigned essays: {ASSIGNED_ESSAY}")
|
logger.debug(f"Assigned essays: {ASSIGNED_ESSAY}")
|
||||||
|
|
||||||
STUDENTS: dict[int, str] = {
|
STUDENTS: dict[int, str] = {
|
||||||
620319269233885226 : "Devan\nWhile he's not super far ahead of the class, he's still ahead enough that he isn't forced to be on top of things like the rest of the class. You remain more straightforward with him despite some more greivences with him.",
|
620319269233885226 : "Devan\nWhile he's not super far ahead of the class, he's still ahead enough that he isn't forced to be on top of things like the rest of the class. You remain more straightforward with him despite some more greivences with him.",
|
||||||
1077533292687007794 : "Connor\nYou are particularly scrutinizing to all of his behavior, especially whenever you find him 1. Playing chess. 2. On YouTube. 3. Listening to music on Spotify. Occasionally, you go beyond simply scolding him for not actively slaving away at his Cisco work, even making awkward analogies to explain how other people are being more productive than him on the other side, and that he should focus on his Cisco work. You'll wait for him to tab back to something on topic before you leave him alone.",
|
1077533292687007794 : "Connor\nYou are particularly scrutinizing to all of his behavior, especially whenever you find him 1. Playing chess. 2. On YouTube. 3. Listening to music on Spotify. Occasionally, you go beyond simply scolding him for not actively slaving away at his Cisco work, even making awkward analogies to explain how other people are being more productive than him on the other side, and that he should focus on his Cisco work. You'll wait for him to tab back to something on topic before you leave him alone.",
|
||||||
957378132061618217 : "CJ\nHe's the best student in the class by far and large. He's already almost finished with his CCNA, which makes sense as he is a senior that only has until he graduates to get his CCNA done in order to get it paid for by the school. He's a really good kid, and you treat him as such. You'll even tell him that he's done good work, which is rather infrequent for your other students.",
|
957378132061618217 : "CJ\nHe's the best student in the class by far and large. He's already almost finished with his CCNA, which makes sense as he is a senior that only has until he graduates to get his CCNA done in order to get it paid for by the school. He's a really good kid, and you treat him as such. You'll even tell him that he's done good work, which is rather infrequent for your other students.",
|
||||||
964563429190930465 : "Michael\nFor the most part you disregard him if he's not working on Cisco, occasionally requesting that he get back on track and then quickly disregarding him again and moving on. As for when he's on Cisco work, when he asks questions, you typically give him a vague non-answer, and you get easily irriated with him, ESPECIALLY if it's something that has EVER been mentioned before. Usually you'll end up yelling at him with things like \"I'm not tryna be mean, but...you should know this already.\" and then walking away.",
|
964563429190930465 : "Michael\nFor the most part you disregard him if he's not working on Cisco, occasionally requesting that he get back on track and then quickly disregarding him again and moving on. As for when he's on Cisco work, when he asks questions, you typically give him a vague non-answer, and you get easily irriated with him, ESPECIALLY if it's something that has EVER been mentioned before. Usually you'll end up yelling at him with things like \"I'm not tryna be mean, but...you should know this already.\" and then walking away.",
|
||||||
909865829313683536 : "Bea\nA student from Graphic Commerical Arts with blue hair and is not in the Computer Networking and Cybersecurity. Because of that, you can't really be mad at them for not focusing on Cisco. I suppose you would talk yourself up a little bit to students in other labs?",
|
909865829313683536 : "Bea\nA student from Graphic Commerical Arts with blue hair and is not in the Computer Networking and Cybersecurity. Because of that, you can't really be mad at them for not focusing on Cisco. I suppose you would talk yourself up a little bit to students in other labs?",
|
||||||
821891090411421747 : "Mika\nA student from Animal Care and is not in the Computer Networking and Cybersecurity. Because of that, you can't really be mad at them for not focusing on Cisco. I suppose you would talk yourself up a little bit to students in other labs?",
|
821891090411421747 : "Mika\nA student from Animal Care and is not in the Computer Networking and Cybersecurity. Because of that, you can't really be mad at them for not focusing on Cisco. I suppose you would talk yourself up a little bit to students in other labs?",
|
||||||
1093276870688133171 : "Daniel\nA student from Biotech and is not in the Computer Networking and Cybersecurity. Because of that, you can't really be mad at them for not focusing on Cisco. I suppose you would talk yourself up a little bit to students in other labs?",
|
1093276870688133171 : "Daniel\nA student from Biotech and is not in the Computer Networking and Cybersecurity. Because of that, you can't really be mad at them for not focusing on Cisco. I suppose you would talk yourself up a little bit to students in other labs?",
|
||||||
625861445833457694 : "Paul\nIt's PAUL!, and for some reason, you LOVE Paul, and you acknowledge just how talented of an artist he is! For some reason, you randomly walk up to him with 'How we doing Paul!?' You've even given him his own opurtnities ",
|
625861445833457694 : "Paul\nIt's PAUL!, and for some reason, you LOVE Paul, and you acknowledge just how talented of an artist he is! For some reason, you randomly walk up to him with 'How we doing Paul!?' You've even given him his own opurtnities ",
|
||||||
1336780587662446593: "Mr. Jacobs\nYourself."
|
1336780587662446593: "Mr. Jacobs\nYourself."
|
||||||
}
|
}
|
||||||
|
|
||||||
ESSAY_TOPICS = ("why to not throw rocks during a fire drill",
|
ESSAY_TOPICS = ("why to not throw rocks during a fire drill",
|
||||||
"how to sit in a chair properly",
|
"how to sit in a chair properly",
|
||||||
"how to keep your hands to yourself",
|
"how to keep your hands to yourself",
|
||||||
"how to be on time",
|
"how to be on time",
|
||||||
"how to take accountability",
|
"how to take accountability",
|
||||||
"why you shouldn't be wrong (be right!!!)",
|
"why you shouldn't be wrong (be right!!!)",
|
||||||
"why picking cherries is healthy for mental health",
|
"why picking cherries is healthy for mental health",
|
||||||
"why losing is bad, actually",
|
"why losing is bad, actually",
|
||||||
"why you should be responsable when the bell rings and GET OUT because i'm HUNGRY",
|
"why you should be responsable when the bell rings and GET OUT because i'm HUNGRY",
|
||||||
"why you shouldn't hitlerpost in the public discord chat",
|
"why you shouldn't hitlerpost in the public discord chat",
|
||||||
"why having your professionalism packet is essential for your future career",
|
"why having your professionalism packet is essential for your future career",
|
||||||
"why playing rock-paper-scissors over text is very productive",
|
"why playing rock-paper-scissors over text is very productive",
|
||||||
"why steak is the best food for my lunch break",
|
"why steak is the best food for my lunch break",
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_essay_topic() -> str:
|
def get_essay_topic() -> str:
|
||||||
return random_choice(ESSAY_TOPICS)
|
return random_choice(ESSAY_TOPICS)
|
||||||
|
|
||||||
def assign_essay(student_id: int, essay: str = get_essay_topic()) -> str:
|
def assign_essay(student_id: int, essay: str = get_essay_topic()) -> str:
|
||||||
"""Assigns a student an essay
|
"""Assigns a student an essay
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
student_id (int): The Discord ID of the student getting the essay.
|
student_id (int): The Discord ID of the student getting the essay.
|
||||||
essay (str, optional): The topic of the essay being assigned. Defaults to get_essay_topic().
|
essay (str, optional): The topic of the essay being assigned. Defaults to get_essay_topic().
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: If the student ID is not in STUDENT_IDS.
|
ValueError: If the student ID is not in STUDENT_IDS.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: The topic of the essay assigned.
|
str: The topic of the essay assigned.
|
||||||
"""
|
"""
|
||||||
if student_id in STUDENT_IDS:
|
if student_id in STUDENT_IDS:
|
||||||
ASSIGNED_ESSAY[student_id] = essay
|
ASSIGNED_ESSAY[student_id] = essay
|
||||||
pickle.dump(ASSIGNED_ESSAY, open("./data/assigned_essay.pkl", "wb"))
|
pickle.dump(ASSIGNED_ESSAY, open("./data/assigned_essay.pkl", "wb"))
|
||||||
return essay
|
return essay
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Student ID {student_id} is not a valid student ID.")
|
raise ValueError(f"Student ID {student_id} is not a valid student ID.")
|
||||||
|
|
||||||
def clear_essay(student_id):
|
def get_essay(student_id: int = None) -> str:
|
||||||
"""Clears an assigned essay from a student
|
"""Gets the assigned essay for a student
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
student_id (int): The Discord ID of the student to clear the essay from.
|
student_id (int): The Discord ID of the student to get the essay for.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: If the student ID is not in STUDENT_IDS.
|
ValueError: If the student ID is not in STUDENT_IDS.
|
||||||
"""
|
|
||||||
if student_id in STUDENT_IDS:
|
Returns:
|
||||||
ASSIGNED_ESSAY.pop(student_id)
|
str: The topic of the essay assigned to the student.
|
||||||
pickle.dump(ASSIGNED_ESSAY, open("./data/assigned_essay.pkl", "wb"))
|
"""
|
||||||
else:
|
if student_id:
|
||||||
raise ValueError(f"Student ID {student_id} is not a valid student ID.")
|
if student_id in STUDENT_IDS:
|
||||||
|
return ASSIGNED_ESSAY.get(student_id, "No essay assigned.")
|
||||||
|
else:
|
||||||
|
essays: str = ""
|
||||||
|
for essay in ASSIGNED_ESSAY:
|
||||||
|
essays += f"<@{essay}>: {ASSIGNED_ESSAY[essay]}\n"
|
||||||
|
return essays if essays else "No essays assigned."
|
||||||
|
|
||||||
|
|
||||||
|
def clear_essay(student_id):
|
||||||
|
"""Clears an assigned essay from a student
|
||||||
|
|
||||||
|
Args:
|
||||||
|
student_id (int): The Discord ID of the student to clear the essay from.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If the student ID is not in STUDENT_IDS.
|
||||||
|
"""
|
||||||
|
if student_id in STUDENT_IDS:
|
||||||
|
ASSIGNED_ESSAY.pop(student_id)
|
||||||
|
pickle.dump(ASSIGNED_ESSAY, open("./data/assigned_essay.pkl", "wb"))
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Student ID {student_id} is not a valid student ID.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print(get_essay())
|
||||||
Reference in New Issue
Block a user