letter-generator / main.py
jaothan's picture
Upload 18 files
324e9cf verified
import os
import requests
import json
from typing import List
from openai import OpenAI
from dotenv import load_dotenv
from bs4 import BeautifulSoup
from IPython.display import Markdown, display, update_display
from openai import OpenAI
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, NoSuchElementException
import time
from docx import Document
# Initialize and constants
load_dotenv(override=True)
api_key = os.getenv('OPENAI_API_KEY')
if api_key and api_key.startswith('sk-proj-') and len(api_key) > 10:
print("API key looks good so far")
else:
print("There might be a problem with API key, Please Check")
MODEL_GPT = 'gpt-4o-mini'
openai = OpenAI()
class LinkedInJDScraper:
def __init__(self, email, password):
options = webdriver.ChromeOptions()
options.add_argument('--disable-blink-features=AutomationControlled')
options.add_argument('--start-maximized')
# Add these options to prevent infinite loops
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')
self.driver = webdriver.Chrome(options=options)
self.wait = WebDriverWait(self.driver, 10) # Reduced wait time
self.email = email
self.password = password
def login(self):
try:
self.driver.get("https://www.linkedin.com/login")
# Wait for email field with timeout
email_field = self.wait.until(
EC.presence_of_element_located((By.ID, "username"))
)
email_field.send_keys(self.email)
# Find password field
password_field = self.driver.find_element(By.ID, "password")
password_field.send_keys(self.password)
# Find and click login button
login_button = self.driver.find_element(By.CSS_SELECTOR, "button[type='submit']")
login_button.click()
# Wait for login to complete with timeout
time.sleep(3)
return True
except Exception as e:
print(f"Login failed: {str(e)}")
return False
def get_description(self, job_url):
max_retries = 3
retry_count = 0
while retry_count < max_retries:
try:
# Navigate to job page
self.driver.get(job_url)
# Wait for any description element to be present
self.wait.until(
EC.presence_of_element_located((By.CSS_SELECTOR, "div.jobs-description"))
)
# Try to click "Show more" if it exists
try:
show_more = self.wait.until(
EC.presence_of_element_located((By.CSS_SELECTOR, "button.show-more-less-html__button"))
)
if show_more.is_displayed():
show_more.click()
time.sleep(1)
except (TimeoutException, NoSuchElementException):
pass
# Get description text
description_element = self.driver.find_element(By.CSS_SELECTOR, "div.jobs-description")
description = description_element.text
if description:
# Find start of job description
start_index = description.lower().find("about the job")
if start_index != -1:
return description[start_index:]
return description
retry_count += 1
time.sleep(2)
except Exception as e:
print(f"Attempt {retry_count + 1} failed: {str(e)}")
retry_count += 1
time.sleep(2)
return "Failed to retrieve job description after multiple attempts"
def close(self):
try:
self.driver.quit()
except:
pass
if __name__ == "__main__":
EMAIL = os.getenv('LINKEDIN_EMAIL')
PASSWORD = os.getenv('LINKEDIN_PASSWORD')
scraper = LinkedInJDScraper(EMAIL, PASSWORD)
try:
if scraper.login():
job_url = "https://www.linkedin.com/jobs/view/4109295398"
description = scraper.get_description(job_url)
print("\nJob Description:")
print(description)
else:
print("Failed to login. Please check your credentials.")
finally:
scraper.close()
system_prompt = """
You are a career assistant specialized in crafting professional and personalized cover letters.
Your goal is to create compelling, tailored cover letters that align with the job description.
Each cover letter should emphasize the user’s qualifications, skills, and experiences while maintaining a professional tone and structure.
Ensure the letter adheres to the following format:
1. **Introduction**: A brief and enthusiastic introduction expressing interest in the role and organization.
2. **Body**: Highlight relevant skills, experiences, and achievements that align with the job description. Use specific examples when possible.
3. **Closing**: Reiterate enthusiasm for the role, express willingness to contribute to the organization, and include a polite call to action.
Maintain clarity, professionalism, and conciseness while tailoring the letter. Don't add anything like as advertised.
"""
def read_text_from_word(file_path):
"""Extracts and returns all text from a Word document."""
# Load the document
doc = Document(file_path)
# Extract text from each paragraph
text = []
for paragraph in doc.paragraphs:
if paragraph.text.strip(): # Skip empty lines
text.append(paragraph.text.strip())
return "\n".join(text)
file_path = "Sample_Resume.docx" # Replace with your file path
resume_skills = read_text_from_word(file_path)
print(resume_skills)
def get_cl_user_prompt_with_scraped_jd(job_url, scraper, resume_skills):
# Scrape job description
description = scraper.scrape_job_descriptions([job_url]).get(job_url)
# Construct the prompt
user_prompt = "You are tasked with creating a professional and tailored Cover Letter for a job application.\n"
user_prompt += "Here is a list of skills and experiences from the candidate's resume:\n"
user_prompt += f"{resume_skills}\n\n"
user_prompt += "Here is the job description for the position they are applying for:\n"
user_prompt += f"{description}\n\n"
user_prompt += (
"Using the skills from the candidate's resume, craft a CL that highlights their most relevant qualifications "
"and experiences for this job making use of job description. "
"Ensure the CV follows a professional format and aligns with the role requirements. Present the CV in markdown format.\n"
)
return user_prompt
def create_jd(system_prompt, file_path, job_url, scraper, model="gpt-4"):
try:
# Generate user prompt
user_prompt = get_cl_user_prompt_with_scraped_jd(file_path, job_url, scraper)
# OpenAI API call
completion = openai.ChatCompletion.create(
model=model,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
]
)
# Extract and display the result
result = completion.choices[0].message.content
display(Markdown(result))
except Exception as e:
print(f"An error occurred: {e}")
messages=[{"role": "system", "content": system_prompt}, {"role": "user", "content": get_cl_user_prompt_with_scraped_jd(file_path, job_url, scraper) }]
response = openai.chat.completions.create(model="gpt-4o-mini", messages=messages)
print(response.choices[0].message.content)