mirror of https://github.com/Bunsly/JobSpy
new handler to see my info
parent
4873082147
commit
9ffbdd5e2a
|
@ -166,6 +166,10 @@ def scrape_jobs(
|
||||||
"""
|
"""
|
||||||
filtered_jobs = []
|
filtered_jobs = []
|
||||||
remaining_jobs = []
|
remaining_jobs = []
|
||||||
|
|
||||||
|
if not filter_by_title:
|
||||||
|
return filtered_jobs, remaining_jobs
|
||||||
|
|
||||||
for job in jobs:
|
for job in jobs:
|
||||||
for filter_title in filter_by_title:
|
for filter_title in filter_by_title:
|
||||||
if re.search(filter_title, job.title, re.IGNORECASE):
|
if re.search(filter_title, job.title, re.IGNORECASE):
|
||||||
|
|
31
src/main.py
31
src/main.py
|
@ -1,9 +1,13 @@
|
||||||
from telegram import Update, ReplyKeyboardMarkup, ReplyKeyboardRemove
|
from telegram import Update, ReplyKeyboardMarkup, ReplyKeyboardRemove
|
||||||
from telegram.ext import Application, CommandHandler, ConversationHandler, \
|
from telegram.ext import Application, CommandHandler, ConversationHandler, \
|
||||||
MessageHandler, filters, ContextTypes
|
MessageHandler, filters, ContextTypes, CallbackQueryHandler
|
||||||
|
|
||||||
from config.settings import settings
|
from config.settings import settings
|
||||||
|
from jobspy import Site
|
||||||
from jobspy.scrapers.utils import create_logger
|
from jobspy.scrapers.utils import create_logger
|
||||||
|
from telegram_handler import TelegramDefaultHandler
|
||||||
|
from telegram_handler.button_callback.telegram_callback_handler import TelegramCallHandler
|
||||||
|
from telegram_handler.telegram_myinfo_handler import my_info_handler
|
||||||
from telegram_handler.telegram_start_handler import start_conv_handler
|
from telegram_handler.telegram_start_handler import start_conv_handler
|
||||||
|
|
||||||
logger = create_logger("Main")
|
logger = create_logger("Main")
|
||||||
|
@ -20,7 +24,28 @@ if __name__ == "__main__":
|
||||||
locations = ["Tel Aviv, Israel", "Ramat Gan, Israel",
|
locations = ["Tel Aviv, Israel", "Ramat Gan, Israel",
|
||||||
"Central, Israel", "Rehovot ,Israel"]
|
"Central, Israel", "Rehovot ,Israel"]
|
||||||
application.add_handler(start_conv_handler)
|
application.add_handler(start_conv_handler)
|
||||||
|
tg_callback_handler = TelegramCallHandler()
|
||||||
# application.add_handler(CommandHandler('start', start_handler.handle))
|
tg_handler_all = TelegramDefaultHandler(sites=[Site.LINKEDIN, Site.GLASSDOOR, Site.INDEED, Site.GOOZALI])
|
||||||
|
application.add_handler(CommandHandler("find", tg_handler_all.handle))
|
||||||
|
# Goozali
|
||||||
|
tg_handler_goozali = TelegramDefaultHandler(sites=[Site.GOOZALI])
|
||||||
|
application.add_handler(CommandHandler(
|
||||||
|
Site.GOOZALI.value, tg_handler_goozali.handle))
|
||||||
|
# GlassDoor
|
||||||
|
tg_handler_glassdoor = TelegramDefaultHandler(sites=[Site.GLASSDOOR])
|
||||||
|
application.add_handler(CommandHandler(
|
||||||
|
Site.GLASSDOOR.value, tg_handler_glassdoor.handle))
|
||||||
|
# LinkeDin
|
||||||
|
tg_handler_linkedin = TelegramDefaultHandler(sites=[Site.LINKEDIN])
|
||||||
|
application.add_handler(CommandHandler(
|
||||||
|
Site.LINKEDIN.value, tg_handler_linkedin.handle))
|
||||||
|
# Indeed
|
||||||
|
tg_handler_indeed = TelegramDefaultHandler(sites=[Site.INDEED])
|
||||||
|
application.add_handler(CommandHandler(
|
||||||
|
Site.INDEED.value, tg_handler_indeed.handle))
|
||||||
|
application.add_handler(CommandHandler(
|
||||||
|
"myInfo", my_info_handler.handle))
|
||||||
|
application.add_handler(CallbackQueryHandler(
|
||||||
|
tg_callback_handler.button_callback))
|
||||||
logger.info("Run polling from telegram")
|
logger.info("Run polling from telegram")
|
||||||
application.run_polling(allowed_updates=Update.ALL_TYPES)
|
application.run_polling(allowed_updates=Update.ALL_TYPES)
|
||||||
|
|
|
@ -2,31 +2,25 @@ from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
class Position(str, Enum):
|
class Position(str, Enum):
|
||||||
PRODUCT_MANAGEMENT = "Product Management"
|
BACKEND_DEVELOPER = "Backend Developer"
|
||||||
|
FULLSTACK_DEVELOPER = "Fullstack Developer"
|
||||||
|
FRONTEND_DEVELOPER = "Frontend Developer"
|
||||||
|
DATA_SCIENTIST="Data Scientist"
|
||||||
DATA_ANALYST="Data Analyst"
|
DATA_ANALYST="Data Analyst"
|
||||||
DATA_SCIENCE = "Data Science, ML & Algorithms"
|
PROJECT_MANAGER="Project Manager"
|
||||||
SOFTWARE_ENGINEERING = "Software Engineering"
|
CLOUD_ENGINEER="Cloud Engineer"
|
||||||
FULLSTACK_DEVELOPMENT = "Fullstack Development"
|
CLOUD_ARCHITECT="Cloud Architect"
|
||||||
QA = "QA"
|
UX_UI_DESIGNER="UX/UI Designer"
|
||||||
CYBERSECURITY = "Cybersecurity"
|
PRODUCT_MANAGER="Product Manager"
|
||||||
IT_AND_SYSTEM_ADMINISTRATION = "IT and System Administration"
|
DEV_OPS_ENGINEER="DevOps Engineer"
|
||||||
FRONTEND_DEVELOPMENT = "Frontend Development"
|
BUSINESS_ANALYST="Business Analyst"
|
||||||
DEV_OPS = "DevOps"
|
CYBERSECURITY_ENGINEER="Cybersecurity Engineer"
|
||||||
UI_UX = "UI/UX, Design & Content"
|
MACHINE_LEARNING_ENGINEER="Machine Learning Engineer"
|
||||||
HR_RECRUITMENT = "HR & Recruitment"
|
ARTIFICIAL_INTELLIGENCE_ENGINEER="Artificial Intelligence Engineer"
|
||||||
MOBILE_DEVELOPMENT = "Mobile Development"
|
DATABASE_ADMINISTRATOR="Database Administrator"
|
||||||
HARDWARE_ENGINEERING = "Hardware Engineering"
|
SYSTEMS_ADMINISTRATOR="Systems Administrator"
|
||||||
EMBEDDED_ENGINEERING = "Embedded, Low Level & Firmware Engineering"
|
NETWORK_ENGINEER="Network Engineer"
|
||||||
CUSTOMER_SUCCESS = "Customer Success"
|
TECHNICAL_SUPPORT_SPECIALIST="Technical Support Specialist"
|
||||||
PROJECT_MANAGEMENT = "Project Management"
|
SALES_ENGINEER="Sales Engineer"
|
||||||
OPERATIONS = "Operations"
|
SCRUM_MASTER="Scrum Master"
|
||||||
FINANCE = "Finance"
|
IT_MANAGER="IT Manager"
|
||||||
SYSTEMS_ENGINEERING = "Systems Engineering"
|
|
||||||
MARKETING = "Marketing"
|
|
||||||
SALES = "Sales"
|
|
||||||
LEGAL_POLICY = "Compliance, Legal & Policy"
|
|
||||||
C_LEVEL = "C-Level"
|
|
||||||
BUSINESS_DEVELOPMENT = "Business Development"
|
|
||||||
MECHANICAL_ENGINEERING = "Mechanical Engineering"
|
|
||||||
NATURAL_SCIENCE = "Natural Science"
|
|
||||||
OTHER = "Other"
|
|
||||||
|
|
|
@ -10,6 +10,22 @@ class User(BaseModel):
|
||||||
username: str
|
username: str
|
||||||
chat_id: Union[int, str] = None
|
chat_id: Union[int, str] = None
|
||||||
experience: Union[int, str] = None
|
experience: Union[int, str] = None
|
||||||
field: Optional[Position] = None
|
position: Optional[Position] = None
|
||||||
cities: Optional[list[str]] = None
|
cities: Optional[list[str]] = None
|
||||||
title_filters: Optional[list[str]] = None
|
title_filters: Optional[list[str]] = None
|
||||||
|
|
||||||
|
def get_myinfo_message(self):
|
||||||
|
message = "Here's your profile:\n\n"
|
||||||
|
message += f"Full Name: {self.full_name}\n"
|
||||||
|
message += f"Username: @{self.username}\n"
|
||||||
|
if self.chat_id:
|
||||||
|
message += f"Chat ID: {self.chat_id}\n"
|
||||||
|
if self.experience:
|
||||||
|
message += f"Experience: {self.experience}\n"
|
||||||
|
if self.position:
|
||||||
|
message += f"Position Level: {self.position.value}\n"
|
||||||
|
if self.cities:
|
||||||
|
message += f"Preferred Cities: {', '.join(self.cities)}\n"
|
||||||
|
if self.title_filters:
|
||||||
|
message += f"Job Title Filters: {', '.join(self.title_filters)}\n"
|
||||||
|
return message
|
|
@ -11,18 +11,9 @@ load_dotenv()
|
||||||
|
|
||||||
|
|
||||||
class JobRepository:
|
class JobRepository:
|
||||||
_instance = None
|
def __init__(self):
|
||||||
|
self._logger = create_logger("JobRepository")
|
||||||
def __new__(cls):
|
self._collection = mongo_client.get_collection('jobs')
|
||||||
|
|
||||||
if cls._instance is not None:
|
|
||||||
return cls._instance
|
|
||||||
|
|
||||||
self = super().__new__(cls)
|
|
||||||
cls._instance = self
|
|
||||||
self.logger = create_logger("JobRepository")
|
|
||||||
self.collection = mongo_client._db["jobs"]
|
|
||||||
return cls._instance
|
|
||||||
|
|
||||||
def find_by_id(self, job_id: str) -> Optional[JobPost]:
|
def find_by_id(self, job_id: str) -> Optional[JobPost]:
|
||||||
"""
|
"""
|
||||||
|
@ -34,7 +25,7 @@ class JobRepository:
|
||||||
Returns:
|
Returns:
|
||||||
The job document if found, otherwise None.
|
The job document if found, otherwise None.
|
||||||
"""
|
"""
|
||||||
result = self.collection.find_one({"id": job_id})
|
result = self._collection.find_one({"id": job_id})
|
||||||
return JobPost(**result)
|
return JobPost(**result)
|
||||||
|
|
||||||
def update(self, job: JobPost) -> bool:
|
def update(self, job: JobPost) -> bool:
|
||||||
|
@ -47,7 +38,7 @@ class JobRepository:
|
||||||
Returns:
|
Returns:
|
||||||
True if the update was successful, False otherwise.
|
True if the update was successful, False otherwise.
|
||||||
"""
|
"""
|
||||||
result = self.collection.update_one({"id": job.id}, {"$set": job.model_dump(exclude={"date_posted"})})
|
result = self._collection.update_one({"id": job.id}, {"$set": job.model_dump(exclude={"date_posted"})})
|
||||||
return result.modified_count > 0
|
return result.modified_count > 0
|
||||||
|
|
||||||
def insert_job(self, job: JobPost):
|
def insert_job(self, job: JobPost):
|
||||||
|
@ -61,8 +52,8 @@ class JobRepository:
|
||||||
Exception: If an error occurs during insertion.
|
Exception: If an error occurs during insertion.
|
||||||
"""
|
"""
|
||||||
job_dict = job.model_dump(exclude={"date_posted"})
|
job_dict = job.model_dump(exclude={"date_posted"})
|
||||||
self.collection.insert_one(job_dict)
|
self._collection.insert_one(job_dict)
|
||||||
self.logger.info(f"Inserted new job with title {job.title}.")
|
self._logger.info(f"Inserted new job with title {job.title}.")
|
||||||
|
|
||||||
def insert_many_if_not_found(self, jobs: list[JobPost]) -> tuple[list[JobPost], list[JobPost]]:
|
def insert_many_if_not_found(self, jobs: list[JobPost]) -> tuple[list[JobPost], list[JobPost]]:
|
||||||
"""
|
"""
|
||||||
|
@ -85,8 +76,8 @@ class JobRepository:
|
||||||
|
|
||||||
if operations:
|
if operations:
|
||||||
# Execute all operations in bulk
|
# Execute all operations in bulk
|
||||||
result = self.collection.bulk_write(operations)
|
result = self._collection.bulk_write(operations)
|
||||||
self.logger.info(f"Matched: {result.matched_count}, Upserts: {
|
self._logger.info(f"Matched: {result.matched_count}, Upserts: {
|
||||||
result.upserted_count}, Modified: {result.modified_count}")
|
result.upserted_count}, Modified: {result.modified_count}")
|
||||||
|
|
||||||
# Get the newly inserted jobs (those that were upserted)
|
# Get the newly inserted jobs (those that were upserted)
|
||||||
|
@ -98,3 +89,5 @@ class JobRepository:
|
||||||
old_jobs.append(job)
|
old_jobs.append(job)
|
||||||
|
|
||||||
return old_jobs, new_jobs
|
return old_jobs, new_jobs
|
||||||
|
|
||||||
|
job_repository = JobRepository()
|
|
@ -3,8 +3,8 @@ from __future__ import annotations
|
||||||
from telegram import MaybeInaccessibleMessage
|
from telegram import MaybeInaccessibleMessage
|
||||||
from telegram.constants import ReactionEmoji
|
from telegram.constants import ReactionEmoji
|
||||||
|
|
||||||
from model.job_repository import JobRepository
|
|
||||||
from jobspy import create_logger
|
from jobspy import create_logger
|
||||||
|
from model.job_repository import job_repository
|
||||||
from telegram_handler.button_callback.button_fire_strategy import FireStrategy
|
from telegram_handler.button_callback.button_fire_strategy import FireStrategy
|
||||||
from telegram_handler.button_callback.button_job_title_strategy import JobTitleStrategy
|
from telegram_handler.button_callback.button_job_title_strategy import JobTitleStrategy
|
||||||
from telegram_handler.button_callback.button_poo_strategy import PooStrategy
|
from telegram_handler.button_callback.button_poo_strategy import PooStrategy
|
||||||
|
@ -22,7 +22,6 @@ class ButtonCallBackContext:
|
||||||
self._data = data
|
self._data = data
|
||||||
self._job_id = job_id
|
self._job_id = job_id
|
||||||
self._strategy = None
|
self._strategy = None
|
||||||
self._job_repository = JobRepository()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def strategy(self) -> ButtonStrategy:
|
def strategy(self) -> ButtonStrategy:
|
||||||
|
@ -49,7 +48,7 @@ class ButtonCallBackContext:
|
||||||
elif ReactionEmoji.PILE_OF_POO.name == self._data:
|
elif ReactionEmoji.PILE_OF_POO.name == self._data:
|
||||||
self._strategy = PooStrategy(self._message)
|
self._strategy = PooStrategy(self._message)
|
||||||
elif self._data:
|
elif self._data:
|
||||||
job = self._job_repository.find_by_id(self._data)
|
job = job_repository.find_by_id(self._data)
|
||||||
if job:
|
if job:
|
||||||
chat_id = self._message.chat.id
|
chat_id = self._message.chat.id
|
||||||
self._strategy = JobTitleStrategy(chat_id, job)
|
self._strategy = JobTitleStrategy(chat_id, job)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
from telegram import MaybeInaccessibleMessage
|
from telegram import MaybeInaccessibleMessage
|
||||||
from telegram.constants import ReactionEmoji
|
from telegram.constants import ReactionEmoji
|
||||||
|
|
||||||
from model.job_repository import JobRepository
|
|
||||||
from jobspy import create_logger
|
from jobspy import create_logger
|
||||||
|
from model.job_repository import job_repository
|
||||||
from telegram_bot import TelegramBot
|
from telegram_bot import TelegramBot
|
||||||
from telegram_handler.button_callback.button_strategy import ButtonStrategy
|
from telegram_handler.button_callback.button_strategy import ButtonStrategy
|
||||||
|
|
||||||
|
@ -16,16 +16,15 @@ class FireStrategy(ButtonStrategy):
|
||||||
self._message = message
|
self._message = message
|
||||||
self._emoji = ReactionEmoji.FIRE
|
self._emoji = ReactionEmoji.FIRE
|
||||||
self._telegram_bot = TelegramBot()
|
self._telegram_bot = TelegramBot()
|
||||||
self._job_repository = JobRepository()
|
|
||||||
self._job_id = job_id
|
self._job_id = job_id
|
||||||
self._logger = create_logger("FireStrategy")
|
self._logger = create_logger("FireStrategy")
|
||||||
|
|
||||||
async def execute(self):
|
async def execute(self):
|
||||||
job = self._job_repository.find_by_id(self._job_id)
|
job = job_repository.find_by_id(self._job_id)
|
||||||
if not job:
|
if not job:
|
||||||
self._logger.error(f"Job with ID {self._job_id} not found.")
|
self._logger.error(f"Job with ID {self._job_id} not found.")
|
||||||
return
|
return
|
||||||
job.applied = True
|
job.applied = True
|
||||||
self._job_repository.update(job)
|
job_repository.update(job)
|
||||||
chat_id = self._message.chat.id
|
chat_id = self._message.chat.id
|
||||||
await self._telegram_bot.set_message_reaction(chat_id, self._message.message_id, self._emoji)
|
await self._telegram_bot.set_message_reaction(chat_id, self._message.message_id, self._emoji)
|
||||||
|
|
|
@ -4,9 +4,10 @@ from telegram.ext import (
|
||||||
ContextTypes,
|
ContextTypes,
|
||||||
)
|
)
|
||||||
|
|
||||||
from model.job_repository import JobRepository
|
|
||||||
from jobspy import Site, scrape_jobs, JobPost
|
from jobspy import Site, scrape_jobs, JobPost
|
||||||
from jobspy.scrapers.utils import create_logger
|
from jobspy.scrapers.utils import create_logger
|
||||||
|
from model.job_repository import JobRepository
|
||||||
|
from model.user_repository import user_repository
|
||||||
from telegram_bot import TelegramBot
|
from telegram_bot import TelegramBot
|
||||||
from telegram_handler.telegram_handler import TelegramHandler
|
from telegram_handler.telegram_handler import TelegramHandler
|
||||||
|
|
||||||
|
@ -33,11 +34,8 @@ def map_jobs_to_keyboard(jobs: list[JobPost]) -> InlineKeyboardMarkup:
|
||||||
|
|
||||||
|
|
||||||
class TelegramDefaultHandler(TelegramHandler):
|
class TelegramDefaultHandler(TelegramHandler):
|
||||||
def __init__(self, sites: list[Site], locations: list[str], title_filters: list[str], search_term: str):
|
def __init__(self, sites: list[Site]):
|
||||||
self.sites_to_scrap = sites
|
self.sites_to_scrap = sites
|
||||||
self.locations = locations
|
|
||||||
self.search_term = search_term
|
|
||||||
self.title_filters = title_filters
|
|
||||||
self.telegram_bot = TelegramBot()
|
self.telegram_bot = TelegramBot()
|
||||||
self.jobRepository = JobRepository()
|
self.jobRepository = JobRepository()
|
||||||
if len(sites) == 1:
|
if len(sites) == 1:
|
||||||
|
@ -51,17 +49,20 @@ class TelegramDefaultHandler(TelegramHandler):
|
||||||
chat_id = update.message.chat.id
|
chat_id = update.message.chat.id
|
||||||
await self.telegram_bot.set_message_reaction(chat_id,
|
await self.telegram_bot.set_message_reaction(chat_id,
|
||||||
update.message.message_id, ReactionEmoji.FIRE)
|
update.message.message_id, ReactionEmoji.FIRE)
|
||||||
|
user = user_repository.find_by_username(update.message.from_user.username)
|
||||||
|
|
||||||
site_names = [site.name for site in self.sites_to_scrap]
|
site_names = [site.name for site in self.sites_to_scrap]
|
||||||
site_names_print = ", ".join(site_names)
|
site_names_print = ", ".join(site_names)
|
||||||
|
locations = [location + ", Israel" for location in user.cities]
|
||||||
await self.telegram_bot.send_text(chat_id,
|
await self.telegram_bot.send_text(chat_id,
|
||||||
f"Start scarping: {site_names_print}")
|
f"Start scarping: {site_names_print}")
|
||||||
filtered_out_jobs, jobs = scrape_jobs(
|
filtered_out_jobs, jobs = scrape_jobs(
|
||||||
site_name=self.sites_to_scrap,
|
site_name=self.sites_to_scrap,
|
||||||
search_term=self.search_term,
|
search_term=user.position.value,
|
||||||
locations=self.locations,
|
locations=locations,
|
||||||
results_wanted=200,
|
results_wanted=200,
|
||||||
hours_old=48,
|
hours_old=48,
|
||||||
filter_by_title=self.title_filters,
|
filter_by_title=user.title_filters,
|
||||||
country_indeed='israel'
|
country_indeed='israel'
|
||||||
)
|
)
|
||||||
self.logger.info(f"Found {len(jobs)} jobs")
|
self.logger.info(f"Found {len(jobs)} jobs")
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
from telegram import Update
|
||||||
|
from telegram.constants import ReactionEmoji
|
||||||
|
from telegram.ext import (
|
||||||
|
ContextTypes,
|
||||||
|
)
|
||||||
|
|
||||||
|
from jobspy.scrapers.utils import create_logger
|
||||||
|
from model.user_repository import user_repository
|
||||||
|
from telegram_bot import TelegramBot
|
||||||
|
from telegram_handler.telegram_handler import TelegramHandler
|
||||||
|
|
||||||
|
|
||||||
|
class MyInfoTelegramHandler(TelegramHandler):
|
||||||
|
def __init__(self):
|
||||||
|
self.telegram_bot = TelegramBot()
|
||||||
|
self._logger = create_logger("MyInfoTelegramHandler")
|
||||||
|
|
||||||
|
async def handle(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
self._logger.info("start handling")
|
||||||
|
chat_id = update.message.chat.id
|
||||||
|
await self.telegram_bot.set_message_reaction(chat_id,
|
||||||
|
update.message.message_id, ReactionEmoji.FIRE)
|
||||||
|
user = user_repository.find_by_username(update.message.from_user.username)
|
||||||
|
await self.telegram_bot.send_text(chat_id, user.get_myinfo_message())
|
||||||
|
|
||||||
|
self._logger.info("finished handling")
|
||||||
|
|
||||||
|
|
||||||
|
my_info_handler = MyInfoTelegramHandler()
|
|
@ -68,7 +68,7 @@ class TelegramStartHandler:
|
||||||
)
|
)
|
||||||
return Flow.POSITION.value
|
return Flow.POSITION.value
|
||||||
cached_user: User = cache_manager.find(user.username)
|
cached_user: User = cache_manager.find(user.username)
|
||||||
cached_user.field = position
|
cached_user.position = position
|
||||||
cache_manager.save(cached_user.username, cached_user)
|
cache_manager.save(cached_user.username, cached_user)
|
||||||
await update.message.reply_text(LOCATION_MESSAGE)
|
await update.message.reply_text(LOCATION_MESSAGE)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue