From f84605fb0dae5738b42f39140532fcb55d34b5b2 Mon Sep 17 00:00:00 2001 From: ForeverPyrite Date: Thu, 20 Feb 2025 22:48:49 -0500 Subject: [PATCH] first commit --- .gitignore | 4 ++ Dockerfile | 17 +++++++ app.py | 123 +++++++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 7 +++ quotes.py | 55 ++++++++++++++++++++ requirements.txt | Bin 0 -> 1020 bytes 6 files changed, 206 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 app.py create mode 100644 docker-compose.yml create mode 100644 quotes.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bbf56be --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/.env +/.venv +/logs +/__pycache__ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..82993e5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +# Use the official Python image from the Docker Hub +FROM python:3.12-slim + +# Set working directory in the container +WORKDIR /app + +# Copy requirements.txt +COPY requirements.txt . + +# Install the required packages +RUN pip install --no-cache-dir -r requirements.txt + +# Copy the rest of the application files +COPY . . + +# Command to run the application +CMD ["python", "app.py"] \ No newline at end of file diff --git a/app.py b/app.py new file mode 100644 index 0000000..0ee9b61 --- /dev/null +++ b/app.py @@ -0,0 +1,123 @@ +import os +import logging +from dotenv import load_dotenv + +import discord +from discord import Bot +from openai import OpenAI +from asyncio import run + +from apscheduler.schedulers.background import BackgroundScheduler +from students import STUDENTS +from quotes import QUOTES, select_quote, select_student + +# Logging. +logging.basicConfig( + filename="./logs/jacobs.log", + filemode="at+", + level=logging.DEBUG +) +logger = logging.getLogger(__name__) + +DISCORD_TOKEN = os.getenv('DISCORD_TOKEN') +OPENAI_KEY = os.getenv('OPENAI_KEY') + +# Discord +bot = Bot(intents=discord.Intents.default()) +SERVER_ID: int = 1339601046745645148 +GENERAL_ID: int = 1339601047294840876 +ch_general = None + +async def get_general(): + global ch_general + global bot + if not ch_general: + ch_general = await bot.fetch_channel(GENERAL_ID) + if not ch_general: + raise(ValueError("General channel object should *not* be none!")) + return ch_general + +async def send_quote(quote: str = None) -> None: + await get_general() + if not quote: + quote = select_quote() + logger.info(f"Sending quote {quote} in #general...") + await ch_general.send(quote) + +async def after_class(student: int = None) -> None: + global ch_general + if not student: + student = select_quote() + logger.info(f"Sending mention to see {student} after class to #general...") + await ch_general.send(f"Come see me after class <@{student}>") + + +# OpenAI & Assistant configuration +client = OpenAI( + api_key=OPENAI_KEY, + project="proj_vUQ7duhKKc1jxIqllRhy6DVJ", +) + +mr_jacobs = client.beta.assistants.retrieve("asst_KdPdwqNAKijujfyCRrJCOgJN") + +base_instructions = mr_jacobs.instructions +instructions = base_instructions + f"\n\nHere is a dictionary containing some of your most well known quotes, organized by their frequency: \n{QUOTES}\n\nStudent: " + +def get_run_status(run): + status = client.beta.threads.runs.retrieve( + run.id, + thread_id=run.thread_id + ).status + logger.info(f"Status of run {run.id} is {status}") + return status + +def get_instructions(student: int | None = None) -> str: + logging.info(f"Looking for {student} in students...") + if student in STUDENTS: + return instructions + STUDENTS.get(student) + else: + logging.warning(f"Couldn't find {student}") + return instructions + "Unknown" + +async def run_message(run): + # ew but Python doesn't have a do while and it's less ugly than the same statement twice. + while True: + complete = get_run_status(run) == "completed" + if complete: + break + 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 + raise Exception(f"bro what did you do dumbass, figure it out.:\nThread Messages List:\n{thread_messages}") + +@bot.slash_command(description="Talk to Mr. Jacobs!!!") +async def message(ctx: discord.ApplicationContext, text: str): + logging.info(f"User {ctx.author.global_name} sent message {text}") + instructions = get_instructions(ctx.author.id) + bot_reply = await ctx.respond("*hmmmm*...let me see here...") + run = client.beta.threads.create_and_run( + assistant_id=mr_jacobs.id, + instructions=instructions, + thread={ + "messages": [ + { + "role": "user", + "content": text + } + ] + } + ) + response = await run_message(run) + await bot_reply.edit_original_response(content=response) + +# After successful initilization, schedule tasks. +task_scheduler = BackgroundScheduler() +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.start() + +logger.info(f"Presumably successfully initilized, starting bot.") +bot.run(DISCORD_TOKEN) \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f763ddc --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,7 @@ +services: + discord-bot: + build: . + volumes: + - ./logs/:/app/logs/ + env_file: .env + restart: unless-stopped \ No newline at end of file diff --git a/quotes.py b/quotes.py new file mode 100644 index 0000000..1dfbf54 --- /dev/null +++ b/quotes.py @@ -0,0 +1,55 @@ +import random +import datetime + +from students import STUDENT_IDS + +QUOTES = { + "NORMAL" : ( + "Oh boy, we are about to wake up!", + "All right 👏 🫰", + "How we doing, Paul?", + "*Drops pen*", + "How we doing, guys?", + "Killing it!!!", + "*Bounces ball off wall*", + "Ugggghhh...", + "Hmm... I see.", + "What are we doing over here?", + "Mmm... Okay! :clap:", + "*Loudly* We don't like stupid prizes, now do we? Then, don't play stupid games!", + "You guys are killing it!", + "Do we need to go back over the module again?", + "Let's get it done!", + "Sorry, I can't hear over Devan and Connor talking.", + "That's what I like to hear!" + ), + "RARE" : ( + "Play stupid games, win big prizescat jacobs.sh! 🤑🤑", + "Oooooo raahahah!", + "It's cherry-pickin' time, y'all!", + "What does the fox say?" + ), + "MYTHIC" : ( + "I'm proud of you.", + "You can take a 5-minute break." + ) +} + +def select_quote(): + rarity = random.randint(0, 99) + if rarity < 1: + quote = random.choice(QUOTES.get("MYTHIC")) + elif rarity < 15: + quote = random.choice(QUOTES.get("RARE")) + else: + 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") + return quote + +def select_student(): + 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") + return student diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..6d1398d85efc21d2a1ea35988445837b72835f7a GIT binary patch literal 1020 zcmZ9LT~5P5421oR#8HZx6bd}>zzvW%Ko!zo)FvefsO{l_`Rr_h&}ylO$K&zX`}1Ac z&T89QVMm>_t^B^}|GiFYPdd5X*sbo|uI);Z$mpz72Jx9$@@>{PIda3a^)LARDwu`D& z;t6DOh+3g$t7_T6ojH4og!|}d#FBEuF%)i9oB5oE{M`Kt{X4nDT*wQDg7;lom_>L8 z8y?5J4(==7&|4`S*>lB5|7xEe%;6r9x$;v-NALTn?4q9*9Ml05+StO0JKU%eabi)2 z&WJCSUr7Nv5+ZY+*wO!(Mb8&b!I%4HMQ5NpIaZ5vB5rn)$$U9?beGh%`p$N+ALC@h zL)3NlA%=rz#x}6Q(OGQwP7E7csKTkQ!#kmNyf(6UN6Gh2dqTgwljwEa$f$VnUBuJ9 VBiNvs@9A0Jn*J+&WIoS`{Q~Sak^}$% literal 0 HcmV?d00001