Intial commit
هذا الالتزام موجود في:
3
.gitignore
مباع
Normal file
3
.gitignore
مباع
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
jobs.csv
|
||||||
|
.venv
|
||||||
|
__pycache__
|
68
ai.py
Normal file
68
ai.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# To run this code you need to install the following dependencies:
|
||||||
|
# pip install google-genai
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import os
|
||||||
|
from google import genai
|
||||||
|
from google.genai import types
|
||||||
|
|
||||||
|
|
||||||
|
def generate(description, instruction):
|
||||||
|
client = genai.Client(
|
||||||
|
api_key=""
|
||||||
|
)
|
||||||
|
|
||||||
|
model = "gemini-2.5-flash"
|
||||||
|
contents = [
|
||||||
|
types.Content(
|
||||||
|
role="user",
|
||||||
|
parts=[
|
||||||
|
types.Part.from_text(text=description),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
|
generate_content_config = types.GenerateContentConfig(
|
||||||
|
thinking_config=types.ThinkingConfig(
|
||||||
|
thinking_budget=0,
|
||||||
|
),
|
||||||
|
response_mime_type="application/json",
|
||||||
|
response_schema=genai.types.Schema(
|
||||||
|
type=genai.types.Type.OBJECT,
|
||||||
|
required=[
|
||||||
|
"percentage",
|
||||||
|
"why I'm I a good fit",
|
||||||
|
"what I'm I missing",
|
||||||
|
],
|
||||||
|
properties={
|
||||||
|
"percentage": genai.types.Schema(
|
||||||
|
type=genai.types.Type.INTEGER,
|
||||||
|
),
|
||||||
|
"why I'm I a good fit": genai.types.Schema(
|
||||||
|
type=genai.types.Type.STRING,
|
||||||
|
),
|
||||||
|
"what I'm I missing": genai.types.Schema(
|
||||||
|
type=genai.types.Type.STRING,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
system_instruction=[
|
||||||
|
types.Part.from_text(text=instruction),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# for chunk in client.models.generate_content_stream(
|
||||||
|
# model=model,
|
||||||
|
# contents=contents,
|
||||||
|
# config=generate_content_config,
|
||||||
|
# ):
|
||||||
|
# print(chunk.text, end="")
|
||||||
|
response = client.models.generate_content(
|
||||||
|
model=model,
|
||||||
|
contents=contents,
|
||||||
|
config=generate_content_config,
|
||||||
|
)
|
||||||
|
return response.text
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
generate()
|
48
alert.py
Normal file
48
alert.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import smtplib
|
||||||
|
from email.mime.text import MIMEText
|
||||||
|
from email.mime.multipart import MIMEMultipart
|
||||||
|
|
||||||
|
|
||||||
|
def send_email(sender, receiver, password, good_fit_jobs):
|
||||||
|
print("preparing message")
|
||||||
|
html_body = """<html>
|
||||||
|
<body style="font-family: Arial, sans-serif; line-height: 1.6;">
|
||||||
|
<h2 style="color: #2c3e50;">My Job Alert</h2>
|
||||||
|
<p><b>{count} jobs</b> have been found that match your profile:</p>
|
||||||
|
<ul style="padding-left: 20px;">
|
||||||
|
""".format(
|
||||||
|
count=len(good_fit_jobs)
|
||||||
|
)
|
||||||
|
|
||||||
|
for job in good_fit_jobs:
|
||||||
|
html_body += f"""
|
||||||
|
<li style="margin-bottom: 15px;">
|
||||||
|
<p><b>Title:</b> {job["title"]}</p>
|
||||||
|
<p><b>URL:</b> <a href="{job["url"]}">{job["url"]}</a></p>
|
||||||
|
<p><b>Percentage:</b> {job["percentage"]}</p>
|
||||||
|
<p><b>Why I'm a good fit:</b> {job["why I'm I a good fit"]}</p>
|
||||||
|
<p><b>What I'm missing:</b> {job["what I'm I missing"]}</p>
|
||||||
|
</li>
|
||||||
|
"""
|
||||||
|
|
||||||
|
html_body += """
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
message = MIMEText(html_body, "html")
|
||||||
|
message["Subject"] = f"MY JOB ALERT: {len(good_fit_jobs)} jobs has been found"
|
||||||
|
message["From"] = sender
|
||||||
|
message["To"] = receiver
|
||||||
|
|
||||||
|
print(f"Sending email with {len(good_fit_jobs)} jobs")
|
||||||
|
with smtplib.SMTP("smtp.gmail.com", timeout=60, port=587) as connection:
|
||||||
|
connection.starttls()
|
||||||
|
connection.login(user=sender, password=password)
|
||||||
|
connection.sendmail(
|
||||||
|
from_addr=sender,
|
||||||
|
to_addrs=receiver,
|
||||||
|
msg=message.as_string(),
|
||||||
|
)
|
||||||
|
connection.close()
|
42
instruction.txt
Normal file
42
instruction.txt
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
i will give you job descriptions and you tell me if i'm a good fit or not in a percentage, why I'm I a good fit and what I'm I missing
|
||||||
|
this is my cv:
|
||||||
|
Ahmed Hesham
|
||||||
|
Ahmed.hesham.farag@gmail.com
|
||||||
|
Cairo, Egypt
|
||||||
|
LinkedIn:https://www.linkedin.com/in/ahmed-hesham0/
|
||||||
|
GitHub:https://github.com/ahmedhesham301
|
||||||
|
PROFILE
|
||||||
|
DevOps Engineer with hands-on experience in automating CI/CD pipelines, containerizing
|
||||||
|
applications, and deploying scalable infrastructure on AWS using Docker, Terraform, and
|
||||||
|
Ansible. Skilled in backend development with Python and Go, building RESTful APIs and
|
||||||
|
automating system workflows.
|
||||||
|
EDUCATION
|
||||||
|
Sadat Academy | Cairo, Egypt
|
||||||
|
Computer science
|
||||||
|
Predicted Grade: Very good
|
||||||
|
Oct 2022 – June 2026
|
||||||
|
TECHNICAL SKILLS
|
||||||
|
-DevOps Tools: Jenkins, Docker, Ansible, Terraform.
|
||||||
|
-Programming & Scripting: Python, GO, Bash, YAML, JSON.
|
||||||
|
-Monitoring & Observability: Prometheus, Grafana.
|
||||||
|
-Cloud Platforms & OS: Linux, AWS, Ubuntu.
|
||||||
|
-Database: SQL, PostgreSQL.
|
||||||
|
-Version control: Git, GitHub.
|
||||||
|
Projects
|
||||||
|
•Built CI/CD pipeline using Jenkins to test and deploy Flask app to EC2 on AWS. The
|
||||||
|
pipeline included building a Docker image, pushing it to Docker Hub, provisioning
|
||||||
|
infrastructure with Terraform, and configuring the server using Ansible.
|
||||||
|
GitHub Repo: weather-app
|
||||||
|
•A Docker image for a Minecraft server that allows you to specify the desired version as
|
||||||
|
an argument. The image automatically downloads the specified version from Mojang's
|
||||||
|
website and launches the server with a single command. GitHub repo
|
||||||
|
•An Amazon price tracker that monitors the price of a specified item. It scrapes the current
|
||||||
|
price and sends you an email notification whenever the price drops below your desired
|
||||||
|
price.
|
||||||
|
EXPERIENCE
|
||||||
|
Orange Digital Centre
|
||||||
|
Jan 2025 – Feb 2025
|
||||||
|
DevOps internship
|
||||||
|
Gained hands-on experience in CI/CD pipelines using Jenkins, Ansible, and Terraform.
|
||||||
|
ADDITIONAL SKILLS
|
||||||
|
Languages: Arabic (Native), English (Fluent)Microsoft Office: Proficient in Word and PowerPoint
|
30
jobs.py
Normal file
30
jobs.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
from jobspy import scrape_jobs
|
||||||
|
|
||||||
|
|
||||||
|
def getJobs(jobTitle, results_wanted, hours_old):
|
||||||
|
jobs = scrape_jobs(
|
||||||
|
site_name=[
|
||||||
|
"indeed",
|
||||||
|
"linkedin",
|
||||||
|
# "zip_recruiter",
|
||||||
|
"google",
|
||||||
|
# "glassdoor",
|
||||||
|
# "bayt",
|
||||||
|
# "naukri",
|
||||||
|
# "bdjobs",
|
||||||
|
],
|
||||||
|
search_term=jobTitle,
|
||||||
|
location="Egypt",
|
||||||
|
results_wanted=results_wanted,
|
||||||
|
google_search_term=f"{jobTitle} jobs near Cairo since {hours_old} hours",
|
||||||
|
hours_old=hours_old,
|
||||||
|
country_indeed="Egypt",
|
||||||
|
linkedin_fetch_description=True, # gets more info such as description, direct job url (slower)
|
||||||
|
# proxies=["208.195.175.46:65095", "208.195.175.45:65095", "localhost"],
|
||||||
|
)
|
||||||
|
print(f"Found {len(jobs)} jobs")
|
||||||
|
# print(jobs)
|
||||||
|
return jobs
|
||||||
|
# jobs.to_csv(
|
||||||
|
# "jobs.csv", quoting=csv.QUOTE_NONNUMERIC, escapechar="\\", index=False
|
||||||
|
# ) # to_excel
|
68
main.py
Normal file
68
main.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
from jobs import getJobs
|
||||||
|
from ai import generate
|
||||||
|
from alert import send_email
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
|
||||||
|
SENDER = ""
|
||||||
|
PASSWORD = ""
|
||||||
|
RECEIVER = ""
|
||||||
|
|
||||||
|
good_fit_jobs = []
|
||||||
|
|
||||||
|
|
||||||
|
with open("instruction.txt", "r") as f:
|
||||||
|
CV = f.read()
|
||||||
|
|
||||||
|
|
||||||
|
def get_jobs(job_title, cv, results_wanted, hours_old):
|
||||||
|
jobs = getJobs(job_title, results_wanted, hours_old)
|
||||||
|
for i, job in jobs.iterrows():
|
||||||
|
# print(job["description"])
|
||||||
|
# print("_______________")
|
||||||
|
print("index is :", i)
|
||||||
|
|
||||||
|
if (i + 1) % 10 == 0 and i != 0:
|
||||||
|
print("Sleeping to avoid API rate limits")
|
||||||
|
time.sleep(60)
|
||||||
|
try_count = 3
|
||||||
|
while try_count > 0:
|
||||||
|
try:
|
||||||
|
cleaned_description = "\n".join(
|
||||||
|
[line for line in job["description"].splitlines() if line.strip()]
|
||||||
|
)
|
||||||
|
ai_response = generate(cleaned_description, cv)
|
||||||
|
ai_response_dict = json.loads(ai_response)
|
||||||
|
break
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
try_count -= 1
|
||||||
|
print("_______________")
|
||||||
|
print(cleaned_description)
|
||||||
|
print("_______________")
|
||||||
|
print(ai_response)
|
||||||
|
print(e)
|
||||||
|
print("Sleeping after fail to avoid API rate limits")
|
||||||
|
time.sleep(6)
|
||||||
|
else:
|
||||||
|
print("All attempts failed.")
|
||||||
|
continue
|
||||||
|
# print(ai_response_dict)
|
||||||
|
if ai_response_dict["percentage"] > 0:
|
||||||
|
# print("adding job to good_fit_jobs")
|
||||||
|
good_fit_jobs.append(
|
||||||
|
{
|
||||||
|
"title": job["title"],
|
||||||
|
"url": job["job_url"],
|
||||||
|
"percentage": ai_response_dict["percentage"],
|
||||||
|
"why I'm I a good fit": ai_response_dict["why I'm I a good fit"],
|
||||||
|
"what I'm I missing": ai_response_dict["what I'm I missing"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
get_jobs("devops", CV, results_wanted=2, hours_old=4)
|
||||||
|
# get_jobs("backend", CV, results_wanted=30, hours_old=2)
|
||||||
|
if len(good_fit_jobs) > 0:
|
||||||
|
send_email(SENDER, RECEIVER, PASSWORD, good_fit_jobs)
|
35
requirements.txt
Normal file
35
requirements.txt
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
annotated-types==0.7.0
|
||||||
|
anyio==4.11.0
|
||||||
|
beautifulsoup4==4.13.5
|
||||||
|
cachetools==5.5.2
|
||||||
|
certifi==2025.8.3
|
||||||
|
charset-normalizer==3.4.3
|
||||||
|
google-auth==2.40.3
|
||||||
|
google-genai==1.38.0
|
||||||
|
h11==0.16.0
|
||||||
|
httpcore==1.0.9
|
||||||
|
httpx==0.28.1
|
||||||
|
idna==3.10
|
||||||
|
markdownify==0.13.1
|
||||||
|
numpy==1.26.3
|
||||||
|
pandas==2.3.2
|
||||||
|
pyasn1==0.6.1
|
||||||
|
pyasn1_modules==0.4.2
|
||||||
|
pydantic==2.11.9
|
||||||
|
pydantic_core==2.33.2
|
||||||
|
python-dateutil==2.9.0.post0
|
||||||
|
python-jobspy==1.1.82
|
||||||
|
pytz==2025.2
|
||||||
|
regex==2024.11.6
|
||||||
|
requests==2.32.5
|
||||||
|
rsa==4.9.1
|
||||||
|
six==1.17.0
|
||||||
|
sniffio==1.3.1
|
||||||
|
soupsieve==2.8
|
||||||
|
tenacity==9.1.2
|
||||||
|
tls-client==1.0.1
|
||||||
|
typing-inspection==0.4.1
|
||||||
|
typing_extensions==4.15.0
|
||||||
|
tzdata==2025.2
|
||||||
|
urllib3==2.5.0
|
||||||
|
websockets==15.0.1
|
المرجع في مشكلة جديدة
حظر مستخدم