diff --git a/src/jobspy/__init__.py b/src/jobspy/__init__.py index 60980db..f176955 100644 --- a/src/jobspy/__init__.py +++ b/src/jobspy/__init__.py @@ -166,6 +166,10 @@ def scrape_jobs( """ filtered_jobs = [] remaining_jobs = [] + + if not filter_by_title: + return filtered_jobs, remaining_jobs + for job in jobs: for filter_title in filter_by_title: if re.search(filter_title, job.title, re.IGNORECASE): diff --git a/src/main.py b/src/main.py index c52ef24..4f5c782 100644 --- a/src/main.py +++ b/src/main.py @@ -1,9 +1,13 @@ from telegram import Update, ReplyKeyboardMarkup, ReplyKeyboardRemove from telegram.ext import Application, CommandHandler, ConversationHandler, \ - MessageHandler, filters, ContextTypes + MessageHandler, filters, ContextTypes, CallbackQueryHandler from config.settings import settings +from jobspy import Site 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 logger = create_logger("Main") @@ -20,7 +24,28 @@ if __name__ == "__main__": locations = ["Tel Aviv, Israel", "Ramat Gan, Israel", "Central, Israel", "Rehovot ,Israel"] application.add_handler(start_conv_handler) - - # application.add_handler(CommandHandler('start', start_handler.handle)) + tg_callback_handler = TelegramCallHandler() + 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") application.run_polling(allowed_updates=Update.ALL_TYPES) diff --git a/src/model/Position.py b/src/model/Position.py index 0464607..9bdf360 100644 --- a/src/model/Position.py +++ b/src/model/Position.py @@ -2,31 +2,25 @@ from enum import Enum class Position(str, Enum): - PRODUCT_MANAGEMENT = "Product Management" - DATA_ANALYST = "Data Analyst" - DATA_SCIENCE = "Data Science, ML & Algorithms" - SOFTWARE_ENGINEERING = "Software Engineering" - FULLSTACK_DEVELOPMENT = "Fullstack Development" - QA = "QA" - CYBERSECURITY = "Cybersecurity" - IT_AND_SYSTEM_ADMINISTRATION = "IT and System Administration" - FRONTEND_DEVELOPMENT = "Frontend Development" - DEV_OPS = "DevOps" - UI_UX = "UI/UX, Design & Content" - HR_RECRUITMENT = "HR & Recruitment" - MOBILE_DEVELOPMENT = "Mobile Development" - HARDWARE_ENGINEERING = "Hardware Engineering" - EMBEDDED_ENGINEERING = "Embedded, Low Level & Firmware Engineering" - CUSTOMER_SUCCESS = "Customer Success" - PROJECT_MANAGEMENT = "Project Management" - OPERATIONS = "Operations" - FINANCE = "Finance" - 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" + BACKEND_DEVELOPER = "Backend Developer" + FULLSTACK_DEVELOPER = "Fullstack Developer" + FRONTEND_DEVELOPER = "Frontend Developer" + DATA_SCIENTIST="Data Scientist" + DATA_ANALYST="Data Analyst" + PROJECT_MANAGER="Project Manager" + CLOUD_ENGINEER="Cloud Engineer" + CLOUD_ARCHITECT="Cloud Architect" + UX_UI_DESIGNER="UX/UI Designer" + PRODUCT_MANAGER="Product Manager" + DEV_OPS_ENGINEER="DevOps Engineer" + BUSINESS_ANALYST="Business Analyst" + CYBERSECURITY_ENGINEER="Cybersecurity Engineer" + MACHINE_LEARNING_ENGINEER="Machine Learning Engineer" + ARTIFICIAL_INTELLIGENCE_ENGINEER="Artificial Intelligence Engineer" + DATABASE_ADMINISTRATOR="Database Administrator" + SYSTEMS_ADMINISTRATOR="Systems Administrator" + NETWORK_ENGINEER="Network Engineer" + TECHNICAL_SUPPORT_SPECIALIST="Technical Support Specialist" + SALES_ENGINEER="Sales Engineer" + SCRUM_MASTER="Scrum Master" + IT_MANAGER="IT Manager" diff --git a/src/model/User.py b/src/model/User.py index cf5cee7..d2ba67f 100644 --- a/src/model/User.py +++ b/src/model/User.py @@ -10,6 +10,22 @@ class User(BaseModel): username: str chat_id: Union[int, str] = None experience: Union[int, str] = None - field: Optional[Position] = None + position: Optional[Position] = None cities: 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 \ No newline at end of file diff --git a/src/model/job_repository.py b/src/model/job_repository.py index 85ff0ca..6a8adfa 100644 --- a/src/model/job_repository.py +++ b/src/model/job_repository.py @@ -11,18 +11,9 @@ load_dotenv() class JobRepository: - _instance = None - - def __new__(cls): - - 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 __init__(self): + self._logger = create_logger("JobRepository") + self._collection = mongo_client.get_collection('jobs') def find_by_id(self, job_id: str) -> Optional[JobPost]: """ @@ -34,7 +25,7 @@ class JobRepository: Returns: 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) def update(self, job: JobPost) -> bool: @@ -47,7 +38,7 @@ class JobRepository: Returns: 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 def insert_job(self, job: JobPost): @@ -61,8 +52,8 @@ class JobRepository: Exception: If an error occurs during insertion. """ job_dict = job.model_dump(exclude={"date_posted"}) - self.collection.insert_one(job_dict) - self.logger.info(f"Inserted new job with title {job.title}.") + self._collection.insert_one(job_dict) + 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]]: """ @@ -85,8 +76,8 @@ class JobRepository: if operations: # Execute all operations in bulk - result = self.collection.bulk_write(operations) - self.logger.info(f"Matched: {result.matched_count}, Upserts: { + result = self._collection.bulk_write(operations) + self._logger.info(f"Matched: {result.matched_count}, Upserts: { result.upserted_count}, Modified: {result.modified_count}") # Get the newly inserted jobs (those that were upserted) @@ -98,3 +89,5 @@ class JobRepository: old_jobs.append(job) return old_jobs, new_jobs + +job_repository = JobRepository() \ No newline at end of file diff --git a/src/telegram_handler/button_callback/button_callback_context.py b/src/telegram_handler/button_callback/button_callback_context.py index 0a8f09a..5d06367 100644 --- a/src/telegram_handler/button_callback/button_callback_context.py +++ b/src/telegram_handler/button_callback/button_callback_context.py @@ -3,8 +3,8 @@ from __future__ import annotations from telegram import MaybeInaccessibleMessage from telegram.constants import ReactionEmoji -from model.job_repository import JobRepository 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_job_title_strategy import JobTitleStrategy from telegram_handler.button_callback.button_poo_strategy import PooStrategy @@ -22,7 +22,6 @@ class ButtonCallBackContext: self._data = data self._job_id = job_id self._strategy = None - self._job_repository = JobRepository() @property def strategy(self) -> ButtonStrategy: @@ -49,10 +48,10 @@ class ButtonCallBackContext: elif ReactionEmoji.PILE_OF_POO.name == self._data: self._strategy = PooStrategy(self._message) elif self._data: - job = self._job_repository.find_by_id(self._data) + job = job_repository.find_by_id(self._data) if job: chat_id = self._message.chat.id - self._strategy = JobTitleStrategy(chat_id,job) + self._strategy = JobTitleStrategy(chat_id, job) else: self._logger.error("Invalid enum value") return diff --git a/src/telegram_handler/button_callback/button_fire_strategy.py b/src/telegram_handler/button_callback/button_fire_strategy.py index 7930549..90f6050 100644 --- a/src/telegram_handler/button_callback/button_fire_strategy.py +++ b/src/telegram_handler/button_callback/button_fire_strategy.py @@ -1,8 +1,8 @@ from telegram import MaybeInaccessibleMessage from telegram.constants import ReactionEmoji -from model.job_repository import JobRepository from jobspy import create_logger +from model.job_repository import job_repository from telegram_bot import TelegramBot from telegram_handler.button_callback.button_strategy import ButtonStrategy @@ -16,16 +16,15 @@ class FireStrategy(ButtonStrategy): self._message = message self._emoji = ReactionEmoji.FIRE self._telegram_bot = TelegramBot() - self._job_repository = JobRepository() self._job_id = job_id self._logger = create_logger("FireStrategy") 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: self._logger.error(f"Job with ID {self._job_id} not found.") return job.applied = True - self._job_repository.update(job) + job_repository.update(job) chat_id = self._message.chat.id await self._telegram_bot.set_message_reaction(chat_id, self._message.message_id, self._emoji) diff --git a/src/telegram_handler/telegram_default_handler.py b/src/telegram_handler/telegram_default_handler.py index 5b1487e..6bb80cc 100644 --- a/src/telegram_handler/telegram_default_handler.py +++ b/src/telegram_handler/telegram_default_handler.py @@ -4,9 +4,10 @@ from telegram.ext import ( ContextTypes, ) -from model.job_repository import JobRepository from jobspy import Site, scrape_jobs, JobPost 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_handler.telegram_handler import TelegramHandler @@ -33,11 +34,8 @@ def map_jobs_to_keyboard(jobs: list[JobPost]) -> InlineKeyboardMarkup: 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.locations = locations - self.search_term = search_term - self.title_filters = title_filters self.telegram_bot = TelegramBot() self.jobRepository = JobRepository() if len(sites) == 1: @@ -51,17 +49,20 @@ class TelegramDefaultHandler(TelegramHandler): 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) + site_names = [site.name for site in self.sites_to_scrap] site_names_print = ", ".join(site_names) + locations = [location + ", Israel" for location in user.cities] await self.telegram_bot.send_text(chat_id, f"Start scarping: {site_names_print}") filtered_out_jobs, jobs = scrape_jobs( site_name=self.sites_to_scrap, - search_term=self.search_term, - locations=self.locations, + search_term=user.position.value, + locations=locations, results_wanted=200, hours_old=48, - filter_by_title=self.title_filters, + filter_by_title=user.title_filters, country_indeed='israel' ) self.logger.info(f"Found {len(jobs)} jobs") diff --git a/src/telegram_handler/telegram_myinfo_handler.py b/src/telegram_handler/telegram_myinfo_handler.py new file mode 100644 index 0000000..853a707 --- /dev/null +++ b/src/telegram_handler/telegram_myinfo_handler.py @@ -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() diff --git a/src/telegram_handler/telegram_start_handler.py b/src/telegram_handler/telegram_start_handler.py index 7d60fc9..dcf8328 100644 --- a/src/telegram_handler/telegram_start_handler.py +++ b/src/telegram_handler/telegram_start_handler.py @@ -68,7 +68,7 @@ class TelegramStartHandler: ) return Flow.POSITION.value cached_user: User = cache_manager.find(user.username) - cached_user.field = position + cached_user.position = position cache_manager.save(cached_user.username, cached_user) await update.message.reply_text(LOCATION_MESSAGE)