From 6ad4908689d48dcbac8cba8843835a593a950273 Mon Sep 17 00:00:00 2001 From: tsb95 Date: Wed, 18 Feb 2026 09:01:45 -1000 Subject: [PATCH] Initial commit. Project constructed without git as a standalone intially. --- .gitignore | 3 ++ get_message.py | 41 ++++++++++++++ main.py | 138 +++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 29 ++++++++++ 4 files changed, 211 insertions(+) create mode 100644 .gitignore create mode 100644 get_message.py create mode 100644 main.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7cfa555 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.env +/__pycache__ +*.log \ No newline at end of file diff --git a/get_message.py b/get_message.py new file mode 100644 index 0000000..f014c21 --- /dev/null +++ b/get_message.py @@ -0,0 +1,41 @@ +from openai import OpenAI +import os +from dotenv import load_dotenv + +load_dotenv() + +OPEN_AI_API_KEY = os.getenv("OPEN_AI_API_KEY") + +client = OpenAI(api_key=OPEN_AI_API_KEY) + +example_format = """ +Hello NAME_OF_JOB_POSTER, + +I see you're looking to get some support with WHAT_THEY_NEED_HELP_WITH, and I would be more than happy to help. My name is Taylor and I have helped many students succeed in TOPIC, and THEIR_SPECIFIC_TOPIC in particular can be tough. I'd be happy to provide intuitive, relatable explanations for these difficult concepts. When would you be available for an initial session? + +I look forward to working with you! + +Warm Regards, +Taylor +""" + +"Things about me to consider include that I have a degree in mathematics, have worked as a data analyst and software developer, have over a decade of experience working as a tutor, teacher, and classroom instructor. I have extensive experiene with all level of mathematics, general computer science, html, python, javascript, physics, R, Jupyter, Data Science, Statistics, SAT, ACT, and more." + +system_content = f""" +"You are an assistan whose goal it is to create a personalized message for a prospective client of a tutor based off a given job description, subject, and name of job poster. The job may or may not be posted by the student themselves. Your response should address the person who posted the job and take in relevant context from the job description to craft a meaningful message that will showcase the ability and excitement of the tutor to help the student with whatever the job description mentions. Some relevant information about the tutor is that I have a degree in mathematics, have worked as a data analyst and software developer, have over a decade of experience working as a tutor, teacher, and classroom instructor. I have extensive experiene with all level of mathematics, general computer science, html, python, javascript, physics, R, Jupyter, Data Science, Statistics, SAT, ACT, and more. A sample message that has been used to some success and can be used as a general template is as follows. {example_format}" + +""" + +def generate_custom_message(job_description, job_poster, subject): + + completion = client.chat.completions.create( + model="gpt-4o-mini", + messages=[ + {"role": "system", "content": system_content}, + { + "role": "user", + "content": f"Create a message based on the following. \n Job Description: {job_description} \n Job Poster: {job_poster} \n Subject: {subject}" + } + ] + ) + return completion.choices[0].message.content diff --git a/main.py b/main.py new file mode 100644 index 0000000..894ac1e --- /dev/null +++ b/main.py @@ -0,0 +1,138 @@ +from selenium import webdriver +from selenium.webdriver.chrome.service import Service +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +import time +from get_message import generate_custom_message +import logging +import random +from logging.handlers import RotatingFileHandler +import os +from dotenv import load_dotenv + +load_dotenv() + +# Constants for login +USERNAME = os.getenv("USERNAME") +PASSWORD = os.getenv("PASSWORD") + +# Set up logging + +# Set up rotating log handler +handler = RotatingFileHandler('./wyzant_logs.log', maxBytes=5000000, backupCount=5) # 5 MB per log file, keeps 5 backups +logging.basicConfig( + handlers=[handler], + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s' +) + +logging.info("Starting script...") + +REFRESH_WAIT_TIME = 120 +REFRESH_VARIANCE = 15 + +# Selenium Config +chrome_driver_path = "/usr/local/bin/chromedriver/chromedriver" +service = Service(executable_path=chrome_driver_path) +driver = webdriver.Chrome(service=service) + +# Go to login site +logging.info("Navigating to login page...") +driver.get("https://www.wyzant.com/login") + +# Login +time.sleep(3) +username_input = WebDriverWait(driver, 10).until( + EC.presence_of_element_located((By.XPATH, "//form[@class='sso-login-form']//input[@id='Username']")) +) +username_input.send_keys(USERNAME) +password_input = WebDriverWait(driver, 10).until( + EC.presence_of_element_located((By.XPATH, "//form[@class='sso-login-form']//input[@id='Password']")) +) +password_input.send_keys(PASSWORD) +logging.info("Entered username and password...") + + +login_button = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.XPATH, "//form[@class='sso-login-form']//button[@type='submit']")) +) +login_button.click() +logging.info("Clicked login button.") + +# Wait and click jobs link +time.sleep(3) +WebDriverWait(driver, 10).until( + EC.presence_of_element_located((By.XPATH, "//div[@class='profile-image round-photo']")) +) +job_count_element = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.XPATH, "//div[@data-templateid='jobCountTemplate']")) +) +job_count_element.click() +logging.info("Clicked jobs list link button.") + +# Loop over the jobs list until no more jobs are found +while True: + try: + # Wait for the jobs list to load and locate the first job link + time.sleep(2) + + first_job_description = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.CSS_SELECTOR, "#jobs-list .job-description")) + ) + + first_job_link = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.CSS_SELECTOR, "#jobs-list .row.spc-small-ew .job-details-link")) + ) + + first_student = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.CSS_SELECTOR, "#jobs-list .text-semibold.spc-zero-n.spc-tiny-s")) + ) + + # Extract job info for GPT + job_description_text = first_job_description.text + job_subject_text = first_job_link.text + job_student_text = first_student.text + + # Generate and send message + message = generate_custom_message(job_description_text, job_student_text, job_subject_text) + logging.info(f"Generated custom message for {job_student_text}, subject: {job_subject_text}.") + + first_job_link.click() + + # Wait for the message text area and send the message + time.sleep(3) + text_area = WebDriverWait(driver, 10).until( + EC.presence_of_element_located((By.ID, "personal_message")) + ) + text_area.clear() # Clears the text area if there's any existing text + text_area.send_keys(message) # Insert your message + + # Check if the hourly rate checkbox exists and click it if present + try: + checkbox = WebDriverWait(driver, 2).until( + EC.presence_of_element_located((By.ID, "agree_partner_hourly_rate")) + ) + checkbox.click() + logging.info("Clicked rate agreement checkbox.") + except: + logging.info("No rate agreement checkbox found.") + + # Submit the application + submit_button = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.CSS_SELECTOR, "input.btn.old-button-color")) + ) + submit_button.click() + logging.info("Application submitted successfully.") + + # Wait for the page to redirect to the jobs list again + time.sleep(3) + + except Exception as e: + logging.error(f"Failed find a job to apply. Waiting 1 mins") + delay = random.uniform(REFRESH_WAIT_TIME - REFRESH_VARIANCE, REFRESH_WAIT_TIME + REFRESH_VARIANCE) + time.sleep(60) + driver.refresh() + time.sleep(10) + + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..85b6902 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,29 @@ +annotated-types==0.7.0 +anyio==4.6.0 +attrs==24.2.0 +beautifulsoup4==4.12.3 +certifi==2024.8.30 +distro==1.9.0 +exceptiongroup==1.2.2 +h11==0.14.0 +httpcore==1.0.6 +httpx==0.27.2 +idna==3.10 +jiter==0.5.0 +openai==1.51.0 +outcome==1.3.0.post0 +pydantic==2.9.2 +pydantic_core==2.23.4 +PySocks==1.7.1 +python-dotenv==1.2.1 +selenium==4.25.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +soupsieve==2.6 +tqdm==4.66.5 +trio==0.26.2 +trio-websocket==0.11.1 +typing_extensions==4.12.2 +urllib3==2.2.3 +websocket-client==1.8.0 +wsproto==1.2.0