Compare commits
2 Commits
edd63fe673
...
45b2bc12b5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45b2bc12b5 | ||
|
|
6feeaea628 |
@@ -4,3 +4,4 @@
|
||||
/__pycache__
|
||||
*.pyc
|
||||
/data
|
||||
students.pub.py
|
||||
|
||||
96
ai_logic.py
96
ai_logic.py
@@ -13,7 +13,7 @@ from students import *
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
OPENAI_KEY = os.getenv('OPENAI_KEY')
|
||||
OPENAI_KEY = os.getenv("OPENAI_KEY")
|
||||
|
||||
client: OpenAI = OpenAI(
|
||||
api_key=OPENAI_KEY,
|
||||
@@ -23,7 +23,11 @@ client: OpenAI = OpenAI(
|
||||
mr_jacobs: Assistant = client.beta.assistants.retrieve("asst_KdPdwqNAKijujfyCRrJCOgJN")
|
||||
base_instructions: str = mr_jacobs.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"
|
||||
)
|
||||
|
||||
|
||||
# ----- Helper Functions -----
|
||||
def get_run_status(run: Run) -> str:
|
||||
@@ -37,12 +41,12 @@ def get_run_status(run: Run) -> str:
|
||||
str: The status of the run.
|
||||
"""
|
||||
status: str = client.beta.threads.runs.retrieve(
|
||||
run.id,
|
||||
thread_id=run.thread_id
|
||||
run.id, thread_id=run.thread_id
|
||||
).status
|
||||
logger.info(f"Status of run {run.id} is {status}")
|
||||
return status
|
||||
|
||||
|
||||
def get_instructions(context: discord.abc.GuildChannel | discord.Message = None) -> str:
|
||||
"""
|
||||
Retrieves the instructions for the AI based on the context.
|
||||
@@ -62,16 +66,21 @@ def get_instructions(context: discord.abc.GuildChannel | discord.Message = None)
|
||||
if isinstance(context, discord.Message):
|
||||
context = context.channel
|
||||
if not isinstance(context, discord.abc.Messageable):
|
||||
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}")
|
||||
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}"
|
||||
)
|
||||
return tuned_instructions
|
||||
if isinstance(context, discord.TextChannel):
|
||||
tuned_instructions += f"\nChannel Category: {context.category.name}\nChannel Name:{context.name}\nChannel Description: {context.topic if isinstance(context, discord.TextChannel) else 'No Description'}"
|
||||
elif isinstance(context, discord.DMChannel):
|
||||
logger.warning(f"DM Channel detected, adding context to instructions.\nDM channel with {ID_TO_NAME.get(context.recipient.id, context.recipient.name)}")
|
||||
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
|
||||
|
||||
|
||||
# ----- Action Handlers -----
|
||||
async def handle_action(run: Run) -> bool:
|
||||
# Define the list to store tool outputs
|
||||
@@ -79,23 +88,39 @@ async def handle_action(run: Run) -> bool:
|
||||
tool: RequiredActionFunctionToolCall
|
||||
# Loop through each tool in the required action section
|
||||
try:
|
||||
logger.debug(f"Run.require_action is currently: {run.required_action} and tool calls are {run.required_action.submit_tool_outputs.tool_calls}")
|
||||
logger.debug(
|
||||
f"Run.require_action is currently: {run.required_action} and tool calls are {run.required_action.submit_tool_outputs.tool_calls}"
|
||||
)
|
||||
for tool in run.required_action.submit_tool_outputs.tool_calls:
|
||||
logger.info(f"Handling action for tool {tool.id}, function {tool.function.name}")
|
||||
logger.info(
|
||||
f"Handling action for tool {tool.id}, function {tool.function.name}"
|
||||
)
|
||||
if tool.function.name == "assign_essay":
|
||||
logger.debug(f"Handling action for assign_essay tool. Received arguments {tool.function.arguments}")
|
||||
logger.debug(
|
||||
f"Handling action for assign_essay tool. Received arguments {tool.function.arguments}"
|
||||
)
|
||||
args = load_json(tool.function.arguments)
|
||||
tool_outputs.append({
|
||||
"tool_call_id": tool.id,
|
||||
"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,
|
||||
"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"]
|
||||
),
|
||||
}
|
||||
)
|
||||
elif tool.function.name == "clear_essay":
|
||||
logger.debug(f"Clearing the essay for student {run.metadata.get('user_id')}")
|
||||
logger.debug(
|
||||
f"Clearing the essay for student {run.metadata.get('user_id')}"
|
||||
)
|
||||
clear_essay(int(run.metadata.get("user_id")))
|
||||
tool_outputs.append({
|
||||
"tool_call_id": tool.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,
|
||||
"output": "Essay cleared sucessfully, feel free to still give the student feedback on their essay because it couldn't have been perfect.",
|
||||
}
|
||||
)
|
||||
except AttributeError as e:
|
||||
logger.error(f"Failed to handle action: {e}")
|
||||
except Exception as e:
|
||||
@@ -105,9 +130,7 @@ async def handle_action(run: Run) -> bool:
|
||||
if tool_outputs:
|
||||
try:
|
||||
run = client.beta.threads.runs.submit_tool_outputs_and_poll(
|
||||
thread_id=run.thread_id,
|
||||
run_id=run.id,
|
||||
tool_outputs=tool_outputs
|
||||
thread_id=run.thread_id, run_id=run.id, tool_outputs=tool_outputs
|
||||
)
|
||||
logger.info("Tool outputs submitted successfully.")
|
||||
except Exception as e:
|
||||
@@ -127,10 +150,7 @@ async def handle_run(run: Run) -> bool:
|
||||
bool: Whether or not the run completed successfully.
|
||||
"""
|
||||
while True:
|
||||
run = client.beta.threads.runs.retrieve(
|
||||
run.id,
|
||||
thread_id=run.thread_id
|
||||
)
|
||||
run = client.beta.threads.runs.retrieve(run.id, thread_id=run.thread_id)
|
||||
match run.status:
|
||||
case "completed":
|
||||
return True
|
||||
@@ -148,7 +168,9 @@ async def handle_run(run: Run) -> bool:
|
||||
await asyncio.sleep(1)
|
||||
|
||||
|
||||
async def run(messages: list[dict], instructions: str = instructions, user_id : int = None) -> str:
|
||||
async def run(
|
||||
messages: list[dict], instructions: str = instructions, user_id: int = None
|
||||
) -> str:
|
||||
"""
|
||||
Runs the AI with the given messages and instructions.
|
||||
|
||||
@@ -160,17 +182,15 @@ async def run(messages: list[dict], instructions: str = instructions, user_id :
|
||||
Returns:
|
||||
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)}")
|
||||
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,
|
||||
instructions=instructions,
|
||||
thread={
|
||||
"messages": messages
|
||||
},
|
||||
metadata={
|
||||
"user_id": str(user_id if user_id else -1)
|
||||
}
|
||||
)
|
||||
assistant_id=mr_jacobs.id,
|
||||
instructions=instructions,
|
||||
thread={"messages": messages},
|
||||
metadata={"user_id": str(user_id if user_id else -1)},
|
||||
)
|
||||
response = await run_message(run)
|
||||
return response
|
||||
|
||||
@@ -193,4 +213,6 @@ async def run_message(run) -> str:
|
||||
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}")
|
||||
logger.critical(
|
||||
f"Couldn't find the msg that matched with the first message ID:\nThread Messages List:\n{thread_messages}"
|
||||
)
|
||||
|
||||
37
app.py
37
app.py
@@ -10,31 +10,54 @@ from discord_logic import bot, setup_discord_bot, send_quote, after_class
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
DISCORD_TOKEN = os.getenv('DISCORD_TOKEN')
|
||||
DISCORD_TOKEN = os.getenv("DISCORD_TOKEN")
|
||||
|
||||
# Logging.
|
||||
logging.basicConfig(
|
||||
filename=f"./logs/jacobs.log",
|
||||
filemode="at+",
|
||||
level=logging.DEBUG if os.getenv('DEBUG') == "True" else logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
level=logging.DEBUG if os.getenv("DEBUG") == "True" else logging.INFO,
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
setup_discord_bot(bot)
|
||||
|
||||
|
||||
@bot.event
|
||||
async def on_ready() -> None:
|
||||
"""Event handler for when the bot is initialized."""
|
||||
logger.info(f"{bot.user.name} has connected to Discord and is ready.")
|
||||
print(f"{bot.user.name} is ready.")
|
||||
|
||||
|
||||
# After successful initialization, schedule tasks.
|
||||
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-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 = 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-14",
|
||||
minute="*/20",
|
||||
jitter=180,
|
||||
)
|
||||
# I'm commenting this out since it doesn't really make sense to have active senior year.
|
||||
# task_scheduler.add_job(
|
||||
# lambda: bot.loop.create_task(after_class()),
|
||||
# "cron",
|
||||
# day_of_week="mon-fri",
|
||||
# hour=10,
|
||||
# minute=55,
|
||||
# )
|
||||
task_scheduler.start()
|
||||
|
||||
logger.info("Presumably successfully initialized, starting bot.")
|
||||
bot.run(DISCORD_TOKEN)
|
||||
|
||||
|
||||
165
discord_logic.py
165
discord_logic.py
@@ -20,8 +20,9 @@ GENERAL_ID: int = 1339601047294840876
|
||||
|
||||
bot = Bot(intents=discord.Intents.all())
|
||||
|
||||
|
||||
# ----- Discord Helper Functions -----
|
||||
async def get_channel(channel_id: int) -> Channel:
|
||||
async def get_channel(channel_id: int = GENERAL_ID) -> Channel:
|
||||
"""
|
||||
Tries to get a cached channel object, if it fails it will send an API request to retrieve a new one.
|
||||
|
||||
@@ -37,15 +38,22 @@ async def get_channel(channel_id: int) -> Channel:
|
||||
logger.debug(f"Attempting to get channel with ID {channel_id}")
|
||||
channel = bot.get_channel(channel_id)
|
||||
if not channel:
|
||||
logger.debug(f"Channel with ID {channel_id} not found in cache, fetching from API.")
|
||||
logger.debug(
|
||||
f"Channel with ID {channel_id} not found in cache, fetching from API."
|
||||
)
|
||||
channel = await bot.fetch_channel(channel_id)
|
||||
if not channel:
|
||||
logger.critical(f"Could not fetch channel with channel_id {channel_id}, fetch_channel returned None.")
|
||||
logger.critical(
|
||||
f"Could not fetch channel with channel_id {channel_id}, fetch_channel returned None."
|
||||
)
|
||||
return None
|
||||
logger.info(f"Successfully retrieved {channel_id}, #{channel.name}")
|
||||
return channel
|
||||
|
||||
async def get_message(message: Message | int, channel_id: int = None, attempts: int = 3) -> Message:
|
||||
|
||||
async def get_message(
|
||||
message: Message | int, channel_id: int = None, attempts: int = 3
|
||||
) -> Message:
|
||||
"""
|
||||
Retrieves a message object either from cache or by fetching it from the channel.
|
||||
|
||||
@@ -57,23 +65,33 @@ async def get_message(message: Message | int, channel_id: int = None, attempts:
|
||||
Returns:
|
||||
Message: The retrieved message object.
|
||||
"""
|
||||
logger.debug(f"Attempting to get message with ID {message if isinstance(message, int) else message.id}")
|
||||
logger.debug(
|
||||
f"Attempting to get message with ID {message if isinstance(message, int) else message.id}"
|
||||
)
|
||||
for attempt in range(attempts):
|
||||
if isinstance(message, int):
|
||||
got_message: Message = bot.get_message(message)
|
||||
if not got_message and channel_id:
|
||||
logger.debug(f"Message with ID {message} not found in cache, fetching from channel ID {channel_id}.")
|
||||
logger.debug(
|
||||
f"Message with ID {message} not found in cache, fetching from channel ID {channel_id}."
|
||||
)
|
||||
channel: discord.abc.GuildChannel = await get_channel(channel_id)
|
||||
got_message: Message = await channel.fetch_message(message)
|
||||
elif not got_message:
|
||||
logger.error(f"Message with ID {message} not found in cache and no channel ID provided.")
|
||||
logger.error(
|
||||
f"Message with ID {message} not found in cache and no channel ID provided."
|
||||
)
|
||||
elif isinstance(message, Message):
|
||||
got_message: Message = bot.get_message(message.id)
|
||||
if not got_message:
|
||||
logger.debug(f"Message with ID {message.id} not found in cache, fetching from channel.")
|
||||
logger.debug(
|
||||
f"Message with ID {message.id} not found in cache, fetching from channel."
|
||||
)
|
||||
got_message: Message = await message.channel.fetch_message(message.id)
|
||||
else:
|
||||
logger.error(f"Couldn't retrieve message:\nMessage: {message}\nChannel ID: {channel_id}")
|
||||
logger.error(
|
||||
f"Couldn't retrieve message:\nMessage: {message}\nChannel ID: {channel_id}"
|
||||
)
|
||||
return None
|
||||
|
||||
if got_message:
|
||||
@@ -86,6 +104,7 @@ async def get_message(message: Message | int, channel_id: int = None, attempts:
|
||||
logger.error(f"Failed to retrieve message after {attempts} attempts.")
|
||||
return None
|
||||
|
||||
|
||||
async def send_message(message: str, channel: int | Channel = GENERAL_ID) -> Message:
|
||||
"""
|
||||
Has the bot send a message to a specified channel. If no channel is specified, the bot sends it to general.
|
||||
@@ -103,13 +122,18 @@ async def send_message(message: str, channel: int | Channel = GENERAL_ID) -> Mes
|
||||
if isinstance(channel, Channel):
|
||||
sent_message: Message = await channel.send(message)
|
||||
if isinstance(sent_message, Message):
|
||||
logger.info(f"Message '{message}' successfully sent to {sent_message.channel}")
|
||||
logger.info(
|
||||
f"Message '{message}' successfully sent to {sent_message.channel}"
|
||||
)
|
||||
return sent_message
|
||||
else:
|
||||
logger.error(f"Message likely wasn't successfully sent, as channel.send did not return a Message.")
|
||||
logger.error(
|
||||
f"Message likely wasn't successfully sent, as channel.send did not return a Message."
|
||||
)
|
||||
elif isinstance(channel, None):
|
||||
logger.error(f"Message couldn't be sent as a channel wasn't found/messagable.")
|
||||
|
||||
|
||||
async def edit_message(message: Message | int, channel: int = None):
|
||||
"""
|
||||
Edits a message in a channel.
|
||||
@@ -118,7 +142,9 @@ async def edit_message(message: Message | int, channel: int = None):
|
||||
message (Message | int): The message to edit.
|
||||
channel (int, optional): The channel ID of the message. Defaults to None.
|
||||
"""
|
||||
logger.debug(f"Attempting to edit message {message if isinstance(message, int) else message.id}")
|
||||
logger.debug(
|
||||
f"Attempting to edit message {message if isinstance(message, int) else message.id}"
|
||||
)
|
||||
if isinstance(message, int):
|
||||
message = await get_message(message, channel)
|
||||
if isinstance(message, Message):
|
||||
@@ -127,6 +153,7 @@ async def edit_message(message: Message | int, channel: int = None):
|
||||
else:
|
||||
logger.error(f"Message couldn't be edited as it was not found.")
|
||||
|
||||
|
||||
# ----- Message Utility Functions -----
|
||||
async def msg_is_reply(message: Message) -> tuple[bool, Message]:
|
||||
"""
|
||||
@@ -143,7 +170,9 @@ async def msg_is_reply(message: Message) -> tuple[bool, Message]:
|
||||
replied_msg = message.reference.cached_message
|
||||
|
||||
if replied_msg is None:
|
||||
logger.debug(f"Replied message not found in cache, fetching from channel ID {message.reference.channel_id}.")
|
||||
logger.debug(
|
||||
f"Replied message not found in cache, fetching from channel ID {message.reference.channel_id}."
|
||||
)
|
||||
channel = await get_channel(message.reference.channel_id)
|
||||
replied_msg = await channel.fetch_message(message.reference.message_id)
|
||||
|
||||
@@ -151,6 +180,7 @@ async def msg_is_reply(message: Message) -> tuple[bool, Message]:
|
||||
return True, replied_msg
|
||||
return False, None
|
||||
|
||||
|
||||
async def get_reply_chain(msg: Message) -> list[Message]:
|
||||
"""
|
||||
Retrieves the chain of replies for a given message.
|
||||
@@ -172,6 +202,7 @@ async def get_reply_chain(msg: Message) -> list[Message]:
|
||||
i += 1
|
||||
return reply_chain
|
||||
|
||||
|
||||
async def gen_message_list(discord_messages: list[Message]) -> list[dict]:
|
||||
"""
|
||||
Generates a list of message dictionaries from a list of Discord messages.
|
||||
@@ -196,25 +227,36 @@ async def gen_message_list(discord_messages: list[Message]) -> list[dict]:
|
||||
"author": message.author,
|
||||
"mentions": message.mentions,
|
||||
"created_at": message.created_at,
|
||||
"jump_url": message.jump_url
|
||||
"jump_url": message.jump_url,
|
||||
}
|
||||
role = "assistant" if message_copy["author"].id == bot.user.id else "user"
|
||||
students_mentioned = None
|
||||
|
||||
if message_copy["mentions"] and role == "user":
|
||||
students_mentioned = [student.id for student in message_copy["mentions"] if student.id in STUDENT_IDS]
|
||||
mentions = re.findall(r'<@\d{17,19}>', message_copy["content"])
|
||||
students_mentioned = [
|
||||
student.id
|
||||
for student in message_copy["mentions"]
|
||||
if student.id in STUDENT_IDS
|
||||
]
|
||||
mentions = re.findall(r"<@\d{17,19}>", message_copy["content"])
|
||||
for mention in mentions:
|
||||
mention_id = mention.replace("<@", "").replace(">", "")
|
||||
student_info = ID_TO_NAME.get(int(mention_id))
|
||||
if student_info:
|
||||
message_copy["content"] = message_copy["content"].replace(mention, student_info.split("\n")[0])
|
||||
message_copy["content"] += '\n{'
|
||||
message_copy["content"] = message_copy["content"].replace(
|
||||
mention, student_info.split("\n")[0]
|
||||
)
|
||||
message_copy["content"] += "\n{"
|
||||
if students_mentioned:
|
||||
message_copy["content"] += f"\n\nInfo on students mentioned (DO NOT REPEAT!): "
|
||||
message_copy["content"] += (
|
||||
f"\n\nInfo on students mentioned (DO NOT REPEAT!): "
|
||||
)
|
||||
for student in students_mentioned:
|
||||
message_copy["content"] += f"\n{STUDENTS.get(student)}"
|
||||
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\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)"}"
|
||||
+ "}"
|
||||
)
|
||||
|
||||
if not message_copy["content"]:
|
||||
debug = True
|
||||
@@ -222,11 +264,8 @@ async def gen_message_list(discord_messages: list[Message]) -> list[dict]:
|
||||
fetched_message = await get_message(message_copy["id"])
|
||||
message_copy["content"] = fetched_message.content
|
||||
|
||||
thread_message = {
|
||||
"role": role,
|
||||
"content": message_copy["content"]
|
||||
}
|
||||
debug = True # Delete after fix
|
||||
thread_message = {"role": role, "content": message_copy["content"]}
|
||||
debug = True # Delete after fix
|
||||
if debug:
|
||||
debug_dump = "Messages Dump:\n"
|
||||
for msg_copy in discord_messages:
|
||||
@@ -241,10 +280,12 @@ async def gen_message_list(discord_messages: list[Message]) -> list[dict]:
|
||||
logger.debug(debug_dump)
|
||||
messages.append(thread_message)
|
||||
else:
|
||||
logger.warning(f"Argument {message} is not of type Message and will be skipped.")
|
||||
|
||||
logger.warning(
|
||||
f"Argument {message} is not of type Message and will be skipped."
|
||||
)
|
||||
return messages
|
||||
|
||||
|
||||
# ----- AI Interaction Functions -----
|
||||
async def send_quote(quote: str = None) -> None:
|
||||
"""
|
||||
@@ -254,10 +295,18 @@ async def send_quote(quote: str = None) -> None:
|
||||
quote (str, optional): The quote to send. If not provided, a random quote is selected.
|
||||
"""
|
||||
if not quote:
|
||||
# If the quote is not defined, it's likely this is a scheduled messages
|
||||
# Therefore, we are going to return early if the bot is the last person
|
||||
# who sent a message to prevent a lot of uneccassary messages in general
|
||||
# when there is no conversation
|
||||
if get_channel().last_message.message.author.id == bot.user.id:
|
||||
return
|
||||
# Since no quote is defined, we are getting a random one.
|
||||
quote = select_quote()
|
||||
logger.info(f"Sending quote '{quote}' in #general...")
|
||||
await send_message(quote)
|
||||
|
||||
|
||||
async def after_class(student: int = None) -> None:
|
||||
"""
|
||||
Sends a message to a student to see the bot after class.
|
||||
@@ -270,6 +319,7 @@ async def after_class(student: int = None) -> None:
|
||||
logger.info(f"Sending mention to see {student} after class to #general...")
|
||||
await send_message(f"Come see me after class <@{student}>")
|
||||
|
||||
|
||||
async def has_essay(message: Message) -> bool:
|
||||
if message.author.id in ASSIGNED_ESSAY:
|
||||
async with message.channel.typing():
|
||||
@@ -277,14 +327,15 @@ async def has_essay(message: Message) -> bool:
|
||||
message_list = await gen_message_list(message)
|
||||
response = await run(
|
||||
message_list,
|
||||
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.author.id
|
||||
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.author.id,
|
||||
)
|
||||
await message.reply(response)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
# ----- Discord Commands & Event Handlers -----
|
||||
def setup_discord_bot(bot: Bot) -> None:
|
||||
"""
|
||||
@@ -293,6 +344,7 @@ def setup_discord_bot(bot: Bot) -> None:
|
||||
Args:
|
||||
bot (Bot): The Discord bot instance.
|
||||
"""
|
||||
|
||||
@bot.slash_command(description="Talk to Mr. Jacobs!!!")
|
||||
async def message(ctx: discord.ApplicationContext, text: str) -> None:
|
||||
"""
|
||||
@@ -309,8 +361,12 @@ def setup_discord_bot(bot: Bot) -> None:
|
||||
response = await run(thread_messages, instructions, ctx.author.id)
|
||||
await ctx.respond(content=response)
|
||||
|
||||
@bot.slash_command(name="clear_essay", description="Clear the essay assigned to a student")
|
||||
async def clear_essay_command(ctx: discord.ApplicationContext, student: str) -> None:
|
||||
@bot.slash_command(
|
||||
name="clear_essay", description="Clear the essay assigned to a student"
|
||||
)
|
||||
async def clear_essay_command(
|
||||
ctx: discord.ApplicationContext, student: str
|
||||
) -> None:
|
||||
"""
|
||||
Slash command to clear the essay assigned to a student.
|
||||
|
||||
@@ -333,8 +389,13 @@ def setup_discord_bot(bot: Bot) -> None:
|
||||
ASSIGNED_ESSAY.pop(student, None)
|
||||
await ctx.respond(f"Cleared essay for <@{student}>")
|
||||
|
||||
@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:
|
||||
@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:
|
||||
try:
|
||||
student = int(student)
|
||||
@@ -352,7 +413,7 @@ def setup_discord_bot(bot: Bot) -> None:
|
||||
ctx: discord.ApplicationContext,
|
||||
student: str,
|
||||
timeout: int | None = 0,
|
||||
topic: str | None = None
|
||||
topic: str | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
Slash command to assign an essay to a student.
|
||||
@@ -374,7 +435,9 @@ def setup_discord_bot(bot: Bot) -> None:
|
||||
return
|
||||
if timeout <= 0:
|
||||
timeout = None
|
||||
logging.info(f"Assigning essay to student {student} with timeout {timeout} and topic {topic}")
|
||||
logging.info(
|
||||
f"Assigning essay to student {student} with timeout {timeout} and topic {topic}"
|
||||
)
|
||||
if student not in STUDENT_IDS:
|
||||
await ctx.respond("Invalid student ID.", ephemeral=True)
|
||||
return
|
||||
@@ -383,18 +446,30 @@ def setup_discord_bot(bot: Bot) -> None:
|
||||
if timeout and topic:
|
||||
timeout_until = discord.utils.utcnow() + timedelta(seconds=timeout)
|
||||
try:
|
||||
member: discord.Member = await ctx.interaction.guild.fetch_member(student)
|
||||
await member.timeout(until=timeout_until, reason=f"Assigned essay: {topic}")
|
||||
await ctx.respond(f'{member.mention} has been timed out for {timeout}.', ephemeral=True)
|
||||
member: discord.Member = await ctx.interaction.guild.fetch_member(
|
||||
student
|
||||
)
|
||||
await member.timeout(
|
||||
until=timeout_until, reason=f"Assigned essay: {topic}"
|
||||
)
|
||||
await ctx.respond(
|
||||
f"{member.mention} has been timed out for {timeout}.",
|
||||
ephemeral=True,
|
||||
)
|
||||
except discord.Forbidden:
|
||||
await ctx.respond(f'Failed to timeout {member.mention}. Missing permissions.', ephemeral=True)
|
||||
await ctx.respond(
|
||||
f"Failed to timeout {member.mention}. Missing permissions.",
|
||||
ephemeral=True,
|
||||
)
|
||||
except discord.HTTPException as e:
|
||||
await ctx.respond(f'Failed to timeout {member.mention}. {e.text}', ephemeral=True)
|
||||
await ctx.respond(
|
||||
f"Failed to timeout {member.mention}. {e.text}", ephemeral=True
|
||||
)
|
||||
return
|
||||
|
||||
@bot.slash_command(
|
||||
name="rps",
|
||||
description="Play \"Rock, Paper, Scissors, Essay\" with Mr. Jacobs!!!",
|
||||
description='Play "Rock, Paper, Scissors, Essay" with Mr. Jacobs!!!',
|
||||
)
|
||||
async def rps_essay(
|
||||
ctx: discord.ApplicationContext,
|
||||
@@ -403,9 +478,9 @@ def setup_discord_bot(bot: Bot) -> None:
|
||||
choices=[
|
||||
discord.OptionChoice("Rock"),
|
||||
discord.OptionChoice("Paper"),
|
||||
discord.OptionChoice("Scissors")
|
||||
]
|
||||
)
|
||||
discord.OptionChoice("Scissors"),
|
||||
],
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
Play Rock Paper Scissors with Mr. Jacobs.
|
||||
@@ -417,7 +492,7 @@ def setup_discord_bot(bot: Bot) -> None:
|
||||
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"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!"
|
||||
)
|
||||
|
||||
26
quotes.py
26
quotes.py
@@ -4,7 +4,7 @@ import datetime
|
||||
from students import STUDENT_IDS
|
||||
|
||||
QUOTES = {
|
||||
"NORMAL" : (
|
||||
"NORMAL": (
|
||||
"Oh boy, we are about to wake up!",
|
||||
"All right 👏 🫰",
|
||||
"How we doing, Paul?",
|
||||
@@ -27,24 +27,23 @@ QUOTES = {
|
||||
"*Spins lanyard*",
|
||||
"Come on guys, you should know this already!",
|
||||
"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! 🤑🤑",
|
||||
"Oooooo raahahah!",
|
||||
"It's cherry-pickin' time, y'all!",
|
||||
"What does the fox say?"
|
||||
"What does the fox say?",
|
||||
),
|
||||
"MYTHIC" : (
|
||||
"MYTHIC": (
|
||||
"I'm proud of you.",
|
||||
"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:
|
||||
rarity = random.randint(0, 99)
|
||||
if rarity < 1:
|
||||
@@ -55,11 +54,16 @@ def select_quote() -> str:
|
||||
quote = random.choice(QUOTES.get("NORMAL"))
|
||||
# Append log to a file (quotes.log)
|
||||
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
|
||||
|
||||
|
||||
def select_student() -> int:
|
||||
student = random.choice(STUDENT_IDS)
|
||||
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
|
||||
|
||||
@@ -5,9 +5,7 @@ import pickle
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Redacted
|
||||
ID_TO_NAME: dict[int, str] = {
|
||||
|
||||
}
|
||||
ID_TO_NAME: dict[int, str] = {}
|
||||
|
||||
# Redacted
|
||||
STUDENT_IDS: set = ()
|
||||
@@ -28,24 +26,27 @@ finally:
|
||||
# Redacted
|
||||
STUDENTS: dict[int, str] = {}
|
||||
|
||||
ESSAY_TOPICS = ("why to not throw rocks during a fire drill",
|
||||
"how to sit in a chair properly",
|
||||
"how to keep your hands to yourself",
|
||||
"how to be on time",
|
||||
"how to take accountability",
|
||||
"why you shouldn't be wrong (be right!!!)",
|
||||
"why picking cherries is healthy for mental health",
|
||||
"why losing is bad, actually",
|
||||
"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 having your professionalism packet is essential for your future career",
|
||||
"why playing rock-paper-scissors over text is very productive",
|
||||
"why steak is the best food for my lunch break",
|
||||
)
|
||||
ESSAY_TOPICS = (
|
||||
"why to not throw rocks during a fire drill",
|
||||
"how to sit in a chair properly",
|
||||
"how to keep your hands to yourself",
|
||||
"how to be on time",
|
||||
"how to take accountability",
|
||||
"why you shouldn't be wrong (be right!!!)",
|
||||
"why picking cherries is healthy for mental health",
|
||||
"why losing is bad, actually",
|
||||
"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 having your professionalism packet is essential for your future career",
|
||||
"why playing rock-paper-scissors over text is very productive",
|
||||
"why steak is the best food for my lunch break",
|
||||
)
|
||||
|
||||
|
||||
def get_essay_topic() -> str:
|
||||
return random_choice(ESSAY_TOPICS)
|
||||
|
||||
|
||||
def assign_essay(student_id: int, essay: str = get_essay_topic()) -> str:
|
||||
"""Assigns a student an essay
|
||||
|
||||
@@ -66,6 +67,7 @@ def assign_essay(student_id: int, essay: str = get_essay_topic()) -> str:
|
||||
else:
|
||||
raise ValueError(f"Student ID {student_id} is not a valid student ID.")
|
||||
|
||||
|
||||
def get_essay(student_id: int = None) -> str:
|
||||
"""Gets the assigned essay for a student
|
||||
|
||||
@@ -106,3 +108,4 @@ def clear_essay(student_id):
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(get_essay())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user