[
  {
    "path": "README.md",
    "content": "# LinkedIn Easy Apply Bot\nAutomatically apply to LinkedIn Easy Apply jobs. This bot answers the application questions as well!\n\nThis is for educational purposes only. I am not responsible if your LinkedIn account gets suspended or for anything else.\n\nThis bot is written in Python using Selenium.\n\n## Setup \n\nTo run the bot, open the command line in the cloned repo directory and install the requirements using pip with the following command:\n```bash\npip install -r requirements.txt\n```\n\nNext, you need to fill out the config.yaml file. Most of this is self-explanatory but if you need explanations please see the end of this README.\n\n```yaml\nemail: email@domain.com\npassword: yourpassword\n\ndisableAntiLock: False\n\nremote: False\n\nexperienceLevel:\n internship: False\n entry: True\n associate: False\n mid-senior level: False\n director: False\n executive: False\n \njobTypes:\n full-time: True\n contract: False\n part-time: False\n temporary: False\n internship: False\n other: False\n volunteer: False\n \ndate:\n all time: True\n month: False\n week: False\n 24 hours: False\n \npositions:\n #- First position\n #- A second position\n #- A third position\n #- ...\nlocations:\n #- First location\n #- A second location\n #- A third location\n #- ...\ndistance: 25\n\noutputFileDirectory: C:\\Users\\myDirectory\\\n\ncompanyBlacklist:\n #- company\n #- company2\n\ntitleBlacklist:\n #- word1\n #- word2\n\nuploads:\n resume: C:\\Users\\myDirectory\\Resume.pdf\n # Cover letter is optional\n #coverLetter: C:\\Users\\myDirectory\\CoverLettter.pdf\n\n\n# ------------ QA section -------------------\n\n# ------------ Additional parameters: checkboxes ---------------\ncheckboxes:\n # Do you have a valid driver's license? (yes/no checkbox)\n driversLicence: True\n # Will you now, or in the future, require sponsorship for employment visa status (e.g. H-1B visa status)? (yes/no checkbox)\n # This is relative to the location and your citizenship applying above, and same with legallyAuthorized.\n requireVisa: False\n # Are you legally authorized to work in COUNTRY? (yes/no checkbox)\n legallyAuthorized: True\n # We must fill this position urgently. Can you start immediately? (yes/no checkbox)\n urgentFill: True\n # Are you comfortable commuting to this job's location? (yes/no checkbox)\n commute: True\n # Have you completed the following level of education: DEGREE TYPE? (yes/no checkbox)\n degreeCompleted:\n  - High School Diploma\n  - Bachelor's Degree\n  # - Associate's Degree\n  # - Master's Degree\n  # - Master of Business Administration\n  # - Doctor of Philosophy\n  # - Doctor of Medicine\n  # - Doctor of Law\n # Are you willing to undergo a background check, in accordance with local law/regulations?\n backgroundCheck: True\n\n# ------------ Additional parameters: univeristyGpa ---------------\nuniversityGpa: 4.0\n\n# ------------ Additional parameters: languages ---------------\nlanguages:\n english: Native or bilingual # None, Conversational, Professional, Native or bilingual\n\n\n# ------------ Additional parameters: years of INDUSTRY experience ---------------\n# How many years of TECHNOLOGY experience do you currently have? (whole numbers only)\nindustry:\n # normal ones\n Accounting/Auditing: 0\n Administrative : 0\n Advertising : 0\n Analyst : 0\n Art/Creative: 0\n Business Development: 0\n Consulting: 0\n Customer Service: 0\n Distribution Design: 0\n Education: 0\n Engineering: 0\n Finance: 0\n General Business: 0\n Health Care Provider: 0\n Human Resources: 0\n Information Technology: 0\n Legal: 0\n Management: 0\n Manufacturing: 0\n Marketing: 0\n Public Relations: 0\n Purchasing: 0\n Product Management: 0\n Project Management: 0\n Production: 0\n Quality Assurance: 0\n Research: 0\n Sales: 0\n Science: 0\n Strategy/Planning: 0\n Supply Chain: 0\n Training: 0\n Writing/Editing: 0\n # end normal ones\n # put your custom ones here\n #C++: 0\n #Python: 1\n # default to put for any skill that you did not list\n default: 0\n # end custom ones\n\n\n# ------------ Additional parameters: years of technology experience ---------------\n# How many years of work experience do you have using TECHNOLOGY? (whole numbers only)\ntechnology:\n #python: 0\n #selenium: 0\n # default to put for any skill that you did not list\n default: 0\n# ------------ Additional parameters: personal info ---------------\npersonalInfo:\n First Name: FirstName\n Last Name: LastName\n Phone Country Code: Canada (+1) # See linkedin for your country code, must be exact\n Mobile Phone Number: 1234567890\n Street address: 123 Fake Street\n City: Red Deer, Alberta # Include the state/province as well!\n State: YourState\n Zip: YourZip/Postal\n Linkedin: https://www.linkedin.com/in/my-linkedin-profile\n Website: https://www.my-website.com # github/website is interchangable here\n\n# ------------ Additional parameters: USA employment crap ---------------\neeo:\n gender: None\n race: None\n vetran: None\n disability: None\n citizenship: Canadian\n```\n\n\n## Execute\n\nTo run the bot, run the following in the command line:\n```\npython3 main.py\n```\n\n## Config.yaml Explanations\nJust fill in your email and password for linkedin.\n```yaml\nemail: email@domain.com\npassword: yourpassword\n```\nThis prevents your computer from going to sleep so the bot can keep running when you are not using it. Set this to True if you want this disabled.\n```yaml\ndisableAntiLock: False\n```\nSet this to True if you want to look for remote jobs only.\n```yaml\nremote: False\n```\nThis is for what level of jobs you want the search to contain. You must choose at least one.\n```yaml\nexperienceLevel:\n internship: False\n entry: True\n associate: False\n mid-senior level: False\n director: False\n executive: False\n```\nThis is for what type of job you are looking for. You must choose at least one.\n```yaml\njobTypes:\n full-time: True\n contract: False\n part-time: False\n temporary: False\n internship: False\n other: False\n volunteer: False\n```\nHow far back you want to search. You must choose only one.\n```yaml\ndate:\n all time: True\n month: False\n week: False\n 24 hours: False\n ```\nA list of positions you want to apply for. You must include at least one.\n```yaml\npositions:\n #- First position\n #- A second position\n #- A third position\n #- ...\n ```\nA list of locations you are applying to. You must include at least one.\n```yaml\nlocations:\n #- First location\n #- A second location\n #- A third location\n #- ...\n ```\nHow far out of the location you want your search to go. You can only input 0, 5, 10, 25, 50, 100 miles.\n```yaml\ndistance: 25\n ```\nThis is the directory where all the job application stats will go to.\n```yaml\noutputFileDirectory: C:\\Users\\myDirectory\\\n ```\nA list of companies to not apply to.\n```yaml\ncompanyBlacklist:\n #- company\n #- company2\n ```\nA list of words that will be used to skip over jobs with any of these words in there.\n```yaml\ntitleBlacklist:\n #- word1\n #- word2\n ```\nA path to your resume and cover letter.\n```yaml\nuploads:\n resume: C:\\Users\\myDirectory\\Resume.pdf\n # Cover letter is optional\n #coverLetter: C:\\Users\\myDirectory\\CoverLettter.pdf\n ```\nAnswer these questions with regards to the company you are applying to. \nFor the degrees part uncomment which degrees you have, and do not add other ones since the linkedin questions are generic.\n```yaml\n# ------------ Additional parameters: checkboxes ---------------\ncheckboxes:\n # Do you have a valid driver's license? (yes/no checkbox)\n driversLicence: True\n # Will you now, or in the future, require sponsorship for employment visa status (e.g. H-1B visa status)? (yes/no checkbox)\n # This is relative to the location and your citizenship applying above, and same with legallyAuthorized.\n requireVisa: False\n # Are you legally authorized to work in COUNTRY? (yes/no checkbox)\n legallyAuthorized: True\n # We must fill this position urgently. Can you start immediately? (yes/no checkbox)\n urgentFill: True\n # Are you comfortable commuting to this job's location? (yes/no checkbox)\n commute: True\n # Have you completed the following level of education: DEGREE TYPE? (yes/no checkbox)\n degreeCompleted:\n  - High School Diploma\n  - Bachelor's Degree\n  #- Associate's Degree\n  #- Master's Degree\n  #- Master of Business Administration\n  #- Doctor of Philosophy\n  #- Doctor of Medicine\n  #- Doctor of Law\n # Are you willing to undergo a background check, in accordance with local law/regulations?\n backgroundCheck: True\n ```\nInput your university gpa. Must be a decimal value to one decimal point.\n```yaml\n# ------------ Additional parameters: univeristyGpa ---------------\nuniversityGpa: 4.0\n ```\nList all your languages. You must put the profinenciy as either: None, Conversational, Professional, Native or bilingual\n```yaml\n# ------------ Additional parameters: languages ---------------\nlanguages:\n english: Native or bilingual # None, Conversational, Professional, Native or bilingual\n ```\nAnswer the following question for the default industries.\nNext, input your custom ones. This can include technologies, programming languages, frameworks, etc.\nThe years of experience needs to be a whole number. Fill in the default for industries you did not list (keep in mind if it's not zero, you will get your application seen more often).\n```yaml\n# ------------ Additional parameters: years of INDUSTRY experience ---------------\n# How many years of TECHNOLOGY experience do you currently have? (whole numbers only)\nindustry:\n # normal ones\n Accounting/Auditing: 0\n Administrative : 0\n Advertising : 0\n Analyst : 0\n Art/Creative: 0\n Business Development: 0\n Consulting: 0\n Customer Service: 0\n Distribution Design: 0\n Education: 0\n Engineering: 0\n Finance: 0\n General Business: 0\n Health Care Provider: 0\n Human Resources: 0\n Information Technology: 0\n Legal: 0\n Management: 0\n Manufacturing: 0\n Marketing: 0\n Public Relations: 0\n Purchasing: 0\n Product Management: 0\n Project Management: 0\n Production: 0\n Quality Assurance: 0\n Research: 0\n Sales: 0\n Science: 0\n Strategy/Planning: 0\n Supply Chain: 0\n Training: 0\n Writing/Editing: 0\n # put your custom ones here\n #C++: 0\n #Python: 1\n # end custom ones\n # default to put for any skill that you did not list\n default: 0\n ```\nAnswer the following question for your tools and technologies. \nThings like programming languages, frameworks, etc.\nThe years of experience needs to be a whole number.\nFill in the default for technologies you did not list (keep in mind if it's not zero, you will get your application seen more often).\n```yaml\n# ------------ Additional parameters: years of technology experience ---------------\n# How many years of work experience do you have using TECHNOLOGY? (whole numbers only)\ntechnology:\n #python: 0\n #selenium: 0\n # default to put for any skill that you did not list\n default: 0\n  ```\nInput your personal info. Include the state/province in the city name to not get the wrong city when choosing from a dropdown.\nThe phone country code needs to be exact for the one that is on linkedin.\nThe website is interchangable for github/portfolio/website.\n```yaml\n# ------------ Additional parameters: personal info ---------------\npersonalInfo:\n First Name: FirstName\n Last Name: LastName\n Phone Country Code: Canada (+1) # See linkedin for your country code, must be exact\n Mobile Phone Number: 1234567890\n Street address: 123 Fake Street\n City: Red Deer, Alberta # Include the state/province as well!\n State: YourState\n Zip: YourZip/Postal\n Linkedin: https://www.linkedin.com/in/my-linkedin-profile\n Website: https://www.my-website.com # github/website is interchangable here\n  ```\nThis is unused at the moment. For the EEO the bot will try to decine to answer for everything.\n```yaml\n# ------------ Additional parameters: USA employment crap ---------------\neeo:\n gender: None\n race: None\n vetran: None\n disability: None\n citizenship: Canadian\n```\n\n"
  },
  {
    "path": "config.yaml",
    "content": "email: email@domain.com\npassword: yourpassword\n\ndisableAntiLock: False\n\nremote: False\n\nexperienceLevel:\n internship: False\n entry: True\n associate: False\n mid-senior level: False\n director: False\n executive: False\n \njobTypes:\n full-time: True\n contract: False\n part-time: False\n temporary: False\n internship: False\n other: False\n volunteer: False\n \ndate:\n all time: True\n month: False\n week: False\n 24 hours: False\n \npositions:\n #- First position\n #- A second position\n #- A third position\n #- ...\nlocations:\n #- First location\n #- A second location\n #- A third location\n #- ...\ndistance: 25\n\noutputFileDirectory: C:\\Users\\myDirectory\\\n\ncompanyBlacklist:\n #- company\n #- company2\n\ntitleBlacklist:\n #- word1\n #- word2\n\nuploads:\n resume: C:\\Users\\myDirectory\\Resume.pdf\n # Cover letter is optional\n #coverLetter: C:\\Users\\myDirectory\\CoverLettter.pdf\n\n\n# ------------ QA section -------------------\n\n# ------------ Additional parameters: checkboxes ---------------\ncheckboxes:\n # Do you have a valid driver's license? (yes/no checkbox)\n driversLicence: True\n # Will you now, or in the future, require sponsorship for employment visa status (e.g. H-1B visa status)? (yes/no checkbox)\n # This is relative to the location and your citizenship applying above, and same with legallyAuthorized.\n requireVisa: False\n # Are you legally authorized to work in COUNTRY? (yes/no checkbox)\n legallyAuthorized: True\n # We must fill this position urgently. Can you start immediately? (yes/no checkbox)\n urgentFill: True\n # Are you comfortable commuting to this job's location? (yes/no checkbox)\n commute: True\n # Have you completed the following level of education: DEGREE TYPE? (yes/no checkbox)\n degreeCompleted:\n  - High School Diploma\n  - Bachelor's Degree\n  # - Associate's Degree\n  # - Master's Degree\n  # - Master of Business Administration\n  # - Doctor of Philosophy\n  # - Doctor of Medicine\n  # - Doctor of Law\n # Are you willing to undergo a background check, in accordance with local law/regulations?\n backgroundCheck: True\n\n# ------------ Additional parameters: univeristyGpa ---------------\nuniversityGpa: 4.0\n\n# ------------ Additional parameters: languages ---------------\nlanguages:\n english: Native or bilingual # None, Conversational, Professional, Native or bilingual\n\n\n# ------------ Additional parameters: years of INDUSTRY experience ---------------\n# How many years of TECHNOLOGY experience do you currently have? (whole numbers only)\nindustry:\n # normal ones\n Accounting/Auditing: 0\n Administrative : 0\n Advertising : 0\n Analyst : 0\n Art/Creative: 0\n Business Development: 0\n Consulting: 0\n Customer Service: 0\n Distribution Design: 0\n Education: 0\n Engineering: 0\n Finance: 0\n General Business: 0\n Health Care Provider: 0\n Human Resources: 0\n Information Technology: 0\n Legal: 0\n Management: 0\n Manufacturing: 0\n Marketing: 0\n Public Relations: 0\n Purchasing: 0\n Product Management: 0\n Project Management: 0\n Production: 0\n Quality Assurance: 0\n Research: 0\n Sales: 0\n Science: 0\n Strategy/Planning: 0\n Supply Chain: 0\n Training: 0\n Writing/Editing: 0\n # end normal ones\n # put your custom ones here\n #C++: 0\n #Python: 1\n # default to put for any skill that you did not list\n default: 0\n # end custom ones\n\n\n# ------------ Additional parameters: years of technology experience ---------------\n# How many years of work experience do you have using TECHNOLOGY? (whole numbers only)\ntechnology:\n #python: 0\n #selenium: 0\n # default to put for any skill that you did not list\n default: 0\n# ------------ Additional parameters: personal info ---------------\npersonalInfo:\n First Name: FirstName\n Last Name: LastName\n Phone Country Code: Canada (+1) # See linkedin for your country code, must be exact\n Mobile Phone Number: 1234567890\n Street address: 123 Fake Street\n City: Red Deer, Alberta # Include the state/province as well!\n State: YourState\n Zip: YourZip/Postal\n Linkedin: https://www.linkedin.com/in/my-linkedin-profile\n Website: https://www.my-website.com # github/website is interchangable here\n\n# ------------ Additional parameters: USA employment crap ---------------\neeo:\n gender: None\n race: None\n vetran: None\n disability: None\n citizenship: Canadian"
  },
  {
    "path": "linkedineasyapply.py",
    "content": "import time, random, csv, pyautogui, pdb, traceback, sys\nfrom selenium.common.exceptions import TimeoutException\nfrom selenium.webdriver.common.keys import Keys\nfrom selenium.webdriver.common.by import By\nfrom selenium.webdriver.support.ui import Select\nfrom datetime import date\nfrom itertools import product\n\n\nclass LinkedinEasyApply:\n    def __init__(self, parameters, driver):\n        self.browser = driver\n        self.email = parameters['email']\n        self.password = parameters['password']\n        self.disable_lock = parameters['disableAntiLock']\n        self.company_blacklist = parameters.get('companyBlacklist', []) or []\n        self.title_blacklist = parameters.get('titleBlacklist', []) or []\n        self.positions = parameters.get('positions', [])\n        self.locations = parameters.get('locations', [])\n        self.base_search_url = self.get_base_search_url(parameters)\n        self.seen_jobs = []\n        self.file_name = \"output\"\n        self.output_file_directory = parameters['outputFileDirectory']\n        self.resume_dir = parameters['uploads']['resume']\n        if 'coverLetter' in parameters['uploads']:\n            self.cover_letter_dir = parameters['uploads']['coverLetter']\n        else:\n            self.cover_letter_dir = ''\n        self.checkboxes = parameters.get('checkboxes', [])\n        self.university_gpa = parameters['universityGpa']\n        self.languages = parameters.get('languages', [])\n        self.industry = parameters.get('industry', [])\n        self.technology = parameters.get('technology', [])\n        self.personal_info = parameters.get('personalInfo', [])\n        self.eeo = parameters.get('eeo', [])\n        self.technology_default = self.technology['default']\n        self.industry_default = self.industry['default']\n\n\n    def login(self):\n        try:\n            self.browser.get(\"https://www.linkedin.com/login\")\n            time.sleep(random.uniform(5, 10))\n            self.browser.find_element_by_id(\"username\").send_keys(self.email)\n            self.browser.find_element_by_id(\"password\").send_keys(self.password)\n            self.browser.find_element_by_css_selector(\".btn__primary--large\").click()\n            time.sleep(random.uniform(5, 10))\n        except TimeoutException:\n            raise Exception(\"Could not login!\")\n\n    def security_check(self):\n        current_url = self.browser.current_url\n        page_source = self.browser.page_source\n\n        if '/checkpoint/challenge/' in current_url or 'security check' in page_source:\n            input(\"Please complete the security check and press enter in this console when it is done.\")\n            time.sleep(random.uniform(5.5, 10.5))\n\n    def start_applying(self):\n        searches = list(product(self.positions, self.locations))\n        random.shuffle(searches)\n\n        page_sleep = 0\n        minimum_time = 60*15\n        minimum_page_time = time.time() + minimum_time\n\n        for (position, location) in searches:\n            location_url = \"&location=\" + location\n            job_page_number = -1\n\n            print(\"Starting the search for \" + position + \" in \" + location + \".\")\n\n            try:\n                while True:\n                    page_sleep += 1\n                    job_page_number += 1\n                    print(\"Going to job page \" + str(job_page_number))\n                    self.next_job_page(position, location_url, job_page_number)\n                    time.sleep(random.uniform(1.5, 3.5))\n                    print(\"Starting the application process for this page...\")\n                    self.apply_jobs(location)\n                    print(\"Applying to jobs on this page has been completed!\")\n\n                    time_left = minimum_page_time - time.time()\n                    if time_left > 0:\n                        print(\"Sleeping for \" + str(time_left) + \" seconds.\")\n                        time.sleep(time_left)\n                        minimum_page_time = time.time() + minimum_time\n                    if page_sleep % 5 == 0:\n                        sleep_time = random.randint(500, 900)\n                        print(\"Sleeping for \" + str(sleep_time/60) + \" minutes.\")\n                        time.sleep(sleep_time)\n                        page_sleep += 1\n            except:\n                traceback.print_exc()\n                pass\n\n            time_left = minimum_page_time - time.time()\n            if time_left > 0:\n                print(\"Sleeping for \" + str(time_left) + \" seconds.\")\n                time.sleep(time_left)\n                minimum_page_time = time.time() + minimum_time\n            if page_sleep % 5 == 0:\n                sleep_time = random.randint(500, 900)\n                print(\"Sleeping for \" + str(sleep_time/60) + \" minutes.\")\n                time.sleep(sleep_time)\n                page_sleep += 1\n\n\n    def apply_jobs(self, location):\n        no_jobs_text = \"\"\n        try:\n            no_jobs_element = self.browser.find_element_by_class_name('jobs-search-two-pane__no-results-banner--expand')\n            no_jobs_text = no_jobs_element.text\n        except:\n            pass\n        if 'No matching jobs found' in no_jobs_text:\n            raise Exception(\"No more jobs on this page\")\n\n        if 'unfortunately, things aren' in self.browser.page_source.lower():\n            raise Exception(\"No more jobs on this page\")\n\n        try:\n            job_results = self.browser.find_element_by_class_name(\"jobs-search-results\")\n            self.scroll_slow(job_results)\n            self.scroll_slow(job_results, step=300, reverse=True)\n\n            job_list = self.browser.find_elements_by_class_name('jobs-search-results__list')[0].find_elements_by_class_name('jobs-search-results__list-item')\n        except:\n            raise Exception(\"No more jobs on this page\")\n\n        if len(job_list) == 0:\n            raise Exception(\"No more jobs on this page\")\n\n        for job_tile in job_list:\n            job_title, company, job_location, apply_method, link = \"\", \"\", \"\", \"\", \"\"\n\n            try:\n                job_title = job_tile.find_element_by_class_name('job-card-list__title').text\n                link = job_tile.find_element_by_class_name('job-card-list__title').get_attribute('href').split('?')[0]\n            except:\n                pass\n            try:\n                company = job_tile.find_element_by_class_name('job-card-container__company-name').text\n            except:\n                pass\n            try:\n                job_location = job_tile.find_element_by_class_name('job-card-container__metadata-item').text\n            except:\n                pass\n            try:\n                apply_method = job_tile.find_element_by_class_name('job-card-container__apply-method').text\n            except:\n                pass\n\n            contains_blacklisted_keywords = False\n            job_title_parsed = job_title.lower().split(' ')\n\n            for word in self.title_blacklist:\n                if word.lower() in job_title_parsed:\n                    contains_blacklisted_keywords = True\n                    break\n\n            if company.lower() not in [word.lower() for word in self.company_blacklist] and \\\n               contains_blacklisted_keywords is False and link not in self.seen_jobs:\n                try:\n                    job_el = job_tile.find_element_by_class_name('job-card-list__title')\n                    job_el.click()\n\n                    time.sleep(random.uniform(3, 5))\n\n\n                    try:\n                        done_applying = self.apply_to_job()\n                        if done_applying:\n                            print(\"Done applying to the job!\")\n                        else:\n                            print('Already applied to the job!')\n                    except:\n                        temp = self.file_name\n                        self.file_name = \"failed\"\n                        print(\"Failed to apply to job! Please submit a bug report with this link: \" + link)\n                        print(\"Writing to the failed csv file...\")\n                        try:\n                            self.write_to_file(company, job_title, link, job_location, location)\n                        except:\n                            pass\n                        self.file_name = temp\n\n                    try:\n                        self.write_to_file(company, job_title, link, job_location, location)\n                    except Exception:\n                        print(\"Could not write the job to the file! No special characters in the job title/company is allowed!\")\n                        traceback.print_exc()\n                except:\n                    traceback.print_exc()\n                    print(\"Could not apply to the job!\")\n                    pass\n            else:\n                print(\"Job contains blacklisted keyword or company name!\")\n            self.seen_jobs += link\n\n    def apply_to_job(self):\n        easy_apply_button = None\n\n        try:\n            easy_apply_button = self.browser.find_element_by_class_name('jobs-apply-button')\n        except:\n            return False\n\n        try:\n            job_description_area = self.browser.find_element_by_class_name(\"jobs-search__job-details--container\")\n            self.scroll_slow(job_description_area, end=1600)\n            self.scroll_slow(job_description_area, end=1600, step=400, reverse=True)\n        except:\n            pass\n\n        print(\"Applying to the job....\")\n        easy_apply_button.click()\n\n        button_text = \"\"\n        submit_application_text = 'submit application'\n        while submit_application_text not in button_text.lower():\n            retries = 3\n            while retries > 0:\n                try:\n                    self.fill_up()\n                    next_button = self.browser.find_element_by_class_name(\"artdeco-button--primary\")\n                    button_text = next_button.text.lower()\n                    if submit_application_text in button_text:\n                        try:\n                            self.unfollow()\n                        except:\n                            print(\"Failed to unfollow company!\")\n                    time.sleep(random.uniform(1.5, 2.5))\n                    next_button.click()\n                    time.sleep(random.uniform(3.0, 5.0))\n\n                    if 'please enter a valid answer' in self.browser.page_source.lower() or 'file is required' in self.browser.page_source.lower():\n                        retries -= 1\n                        print(\"Retrying application, attempts left: \" + str(retries))\n                    else:\n                        break\n                except:\n                    traceback.print_exc()\n                    raise Exception(\"Failed to apply to job!\")\n            if retries == 0:\n                traceback.print_exc()\n                self.browser.find_element_by_class_name('artdeco-modal__dismiss').click()\n                time.sleep(random.uniform(3, 5))\n                self.browser.find_elements_by_class_name('artdeco-modal__confirm-dialog-btn')[1].click()\n                time.sleep(random.uniform(3, 5))\n                raise Exception(\"Failed to apply to job!\")\n\n        closed_notification = False\n        time.sleep(random.uniform(3, 5))\n        try:\n            self.browser.find_element_by_class_name('artdeco-modal__dismiss').click()\n            closed_notification = True\n        except:\n            pass\n        try:\n            self.browser.find_element_by_class_name('artdeco-toast-item__dismiss').click()\n            closed_notification = True\n        except:\n            pass\n        time.sleep(random.uniform(3, 5))\n\n        if closed_notification is False:\n            raise Exception(\"Could not close the applied confirmation window!\")\n\n        return True\n\n    def home_address(self, element):\n        try:\n            groups = element.find_elements_by_class_name('jobs-easy-apply-form-section__grouping')\n            if len(groups) > 0:\n                for group in groups:\n                    lb = group.find_element_by_tag_name('label').text.lower()\n                    input_field = group.find_element_by_tag_name('input')\n                    if 'street' in lb:\n                        self.enter_text(input_field, self.personal_info['Street address'])\n                    elif 'city' in lb:\n                        self.enter_text(input_field, self.personal_info['City'])\n                        time.sleep(3)\n                        input_field.send_keys(Keys.DOWN)\n                        input_field.send_keys(Keys.RETURN)\n                    elif 'zip' in lb or 'postal' in lb:\n                        self.enter_text(input_field, self.personal_info['Zip'])\n                    elif 'state' in lb or 'province' in lb:\n                        self.enter_text(input_field, self.personal_info['State'])\n                    else:\n                        pass\n        except:\n            pass\n\n    def get_answer(self, question):\n        if self.checkboxes[question]:\n            return 'yes'\n        else:\n            return 'no'\n\n    def additional_questions(self):\n        #pdb.set_trace()\n        frm_el = self.browser.find_elements_by_class_name('jobs-easy-apply-form-section__grouping')\n        if len(frm_el) > 0:\n            for el in frm_el:\n                # Radio check\n                try:\n                    radios = el.find_element_by_class_name('jobs-easy-apply-form-element').find_elements_by_class_name('fb-radio')\n\n                    radio_text = el.text.lower()\n                    radio_options = [text.text.lower() for text in radios]\n                    answer = \"yes\"\n\n                    if 'driver\\'s licence' in radio_text or 'driver\\'s license' in radio_text:\n                        answer = self.get_answer('driversLicence')\n                    elif 'gender' in radio_text or 'veteran' in radio_text or 'race' in radio_text or 'disability' in radio_text or 'latino' in radio_text:\n                        answer = \"\"\n                        for option in radio_options:\n                            if 'prefer' in option.lower() or 'decline' in option.lower() or 'don\\'t' in option.lower() or 'specified' in option.lower() or 'none' in option.lower():\n                                answer = option\n\n                        if answer == \"\":\n                            answer = radio_options[len(radio_options) - 1]\n                    elif 'north korea' in radio_text:\n                        answer = 'no'\n                    elif 'sponsor' in radio_text:\n                        answer = self.get_answer('requireVisa')\n                    elif 'authorized' in radio_text or 'authorised' in radio_text or 'legally' in radio_text:\n                        answer = self.get_answer('legallyAuthorized')\n                    elif 'urgent' in radio_text:\n                        answer = self.get_answer('urgentFill')\n                    elif 'commuting' in radio_text:\n                        answer = self.get_answer('commute')\n                    elif 'background check' in radio_text:\n                        answer = self.get_answer('backgroundCheck')\n                    elif 'level of education' in radio_text:\n                        for degree in self.checkboxes['degreeCompleted']:\n                            if degree.lower() in radio_text:\n                                answer = \"yes\"\n                                break\n                    elif 'level of education' in radio_text:\n                        for degree in self.checkboxes['degreeCompleted']:\n                            if degree.lower() in radio_text:\n                                answer = \"yes\"\n                                break\n                    elif 'data retention' in radio_text:\n                        answer = 'no'\n                    else:\n                        answer = radio_options[len(radio_options) - 1]\n\n                    i = 0\n                    to_select = None\n                    for radio in radios:\n                        if answer in radio.text.lower():\n                            to_select = radios[i]\n                        i += 1\n\n                    if to_select is None:\n                        to_select = radios[len(radios)-1]\n\n                    self.radio_select(to_select, answer, len(radios) > 2)\n\n                    if radios != []:\n                        continue\n                except:\n                    pass\n                # Questions check\n                try:\n                    question = el.find_element_by_class_name('jobs-easy-apply-form-element')\n                    question_text = question.find_element_by_class_name('fb-form-element-label').text.lower()\n\n                    txt_field_visible = False\n                    try:\n                        txt_field = question.find_element_by_class_name('fb-single-line-text__input')\n\n                        txt_field_visible = True\n                    except:\n                        try:\n                            txt_field = question.find_element_by_class_name('fb-textarea')\n\n                            txt_field_visible = True\n                        except:\n                            pass\n\n                    if txt_field_visible != True:\n                        txt_field = question.find_element_by_class_name('multi-line-text__input')\n\n                    text_field_type = txt_field.get_attribute('name').lower()\n                    if 'numeric' in text_field_type:\n                        text_field_type = 'numeric'\n                    elif 'text' in text_field_type:\n                        text_field_type = 'text'\n\n                    to_enter = ''\n                    if 'experience do you currently have' in question_text:\n                        no_of_years = self.industry_default\n\n                        for industry in self.industry:\n                            if industry.lower() in question_text:\n                                no_of_years = self.industry[industry]\n                                break\n\n                        to_enter = no_of_years\n                    elif 'many years of work experience do you have using' in question_text:\n                        no_of_years = self.technology_default\n\n                        for technology in self.technology:\n                            if technology.lower() in question_text:\n                                no_of_years = self.technology[technology]\n\n                        to_enter = no_of_years\n                    elif 'grade point average' in question_text:\n                        to_enter = self.university_gpa\n                    elif 'first name' in question_text:\n                        to_enter = self.personal_info['First Name']\n                    elif 'last name' in question_text:\n                        to_enter = self.personal_info['Last Name']\n                    elif 'name' in question_text:\n                        to_enter = self.personal_info['First Name'] + \" \" + self.personal_info['Last Name']\n                    elif 'phone' in question_text:\n                        to_enter = self.personal_info['Mobile Phone Number']\n                    elif 'linkedin' in question_text:\n                        to_enter = self.personal_info['Linkedin']\n                    elif 'website' in question_text or 'github' in question_text or 'portfolio' in question_text:\n                        to_enter = self.personal_info['Website']\n                    else:\n                        if text_field_type == 'numeric':\n                            to_enter = 0\n                        else:\n                            to_enter = \" ‏‏‎ \"\n\n                    if text_field_type == 'numeric':\n                        if not isinstance(to_enter, (int, float)):\n                            to_enter = 0\n                    elif to_enter == '':\n                        to_enter = \" ‏‏‎ \"\n\n                    self.enter_text(txt_field, to_enter)\n                    continue\n                except:\n                    pass\n                # Date Check\n                try:\n                    date_picker = el.find_element_by_class_name('artdeco-datepicker__input ')\n                    date_picker.clear()\n                    date_picker.send_keys(date.today().strftime(\"%m/%d/%y\"))\n                    time.sleep(3)\n                    date_picker.send_keys(Keys.RETURN)\n                    time.sleep(2)\n                    continue\n                except:\n                    pass\n                # Dropdown check\n                try:\n                    question = el.find_element_by_class_name('jobs-easy-apply-form-element')\n                    question_text = question.find_element_by_class_name('fb-form-element-label').text.lower()\n\n                    dropdown_field = question.find_element_by_class_name('fb-dropdown__select')\n\n                    select = Select(dropdown_field)\n\n                    options = [options.text for options in select.options]\n\n                    if 'proficiency' in question_text:\n                        proficiency = \"Conversational\"\n\n                        for language in self.languages:\n                            if language.lower() in question_text:\n                                proficiency = self.languages[language]\n                                break\n\n                        self.select_dropdown(dropdown_field, proficiency)\n                    elif 'country code' in question_text:\n                        self.select_dropdown(dropdown_field, self.personal_info['Phone Country Code'])\n                    elif 'north korea' in question_text:\n\n                        choice = \"\"\n\n                        for option in options:\n                            if 'no' in option.lower():\n                                choice = option\n\n                        if choice == \"\":\n                            choice = options[len(options) - 1]\n\n                        self.select_dropdown(dropdown_field, choice)\n                    elif 'sponsor' in question_text:\n                        answer = self.get_answer('requireVisa')\n\n                        choice = \"\"\n\n                        for option in options:\n                            if answer == 'yes':\n                                choice = option\n                            else:\n                                if 'no' in option.lower():\n                                    choice = option\n\n                        if choice == \"\":\n                            choice = options[len(options) - 1]\n\n                        self.select_dropdown(dropdown_field, choice)\n                    elif 'authorized' in question_text or 'authorised' in question_text:\n                        answer = self.get_answer('legallyAuthorized')\n\n                        choice = \"\"\n\n                        for option in options:\n                            if answer == 'yes':\n                                # find some common words\n                                choice = option\n                            else:\n                                if 'no' in option.lower():\n                                    choice = option\n\n                        if choice == \"\":\n                            choice = options[len(options) - 1]\n\n                        self.select_dropdown(dropdown_field, choice)\n                    elif 'citizenship' in question_text:\n                        answer = self.get_answer('legallyAuthorized')\n\n                        choice = \"\"\n\n                        for option in options:\n                            if answer == 'yes':\n                                if 'no' in option.lower():\n                                    choice = option\n\n                        if choice == \"\":\n                            choice = options[len(options) - 1]\n\n                        self.select_dropdown(dropdown_field, choice)\n                    elif 'gender' in question_text or 'veteran' in question_text or 'race' in question_text or 'disability' in question_text or 'latino' in question_text:\n\n                        choice = \"\"\n\n                        for option in options:\n                            if 'prefer' in option.lower() or 'decline' in option.lower() or 'don\\'t' in option.lower() or 'specified' in option.lower() or 'none' in option.lower():\n                                choice = option\n\n                        if choice == \"\":\n                            choice = options[len(options) - 1]\n\n                        self.select_dropdown(dropdown_field, choice)\n                    else:\n                        choice = \"\"\n\n                        for option in options:\n                            if 'yes' in option.lower():\n                                choice = option\n\n                        if choice == \"\":\n                            choice = options[len(options) - 1]\n\n                        self.select_dropdown(dropdown_field, choice)\n                    continue\n                except:\n                    pass\n\n                # Checkbox check for agreeing to terms and service\n                try:\n                    question = el.find_element_by_class_name('jobs-easy-apply-form-element')\n\n                    clickable_checkbox = question.find_element_by_tag_name('label')\n\n                    clickable_checkbox.click()\n                except:\n                    pass\n\n    def unfollow(self):\n        try:\n            follow_checkbox = self.browser.find_element(By.XPATH, \"//label[contains(.,\\'to stay up to date with their page.\\')]\").click()\n            follow_checkbox.click()\n        except:\n            pass\n\n    def send_resume(self):\n        try:\n            file_upload_elements = (By.CSS_SELECTOR, \"input[name='file']\")\n            if len(self.browser.find_elements(file_upload_elements[0], file_upload_elements[1])) > 0:\n                input_buttons = self.browser.find_elements(file_upload_elements[0], file_upload_elements[1])\n                for upload_button in input_buttons:\n                    upload_type = upload_button.find_element(By.XPATH, \"..\").find_element(By.XPATH, \"preceding-sibling::*\")\n                    if 'resume' in upload_type.text.lower():\n                        upload_button.send_keys(self.resume_dir)\n                    elif 'cover' in upload_type.text.lower():\n                        if self.cover_letter_dir != '':\n                            upload_button.send_keys(self.cover_letter_dir)\n                        elif 'required' in upload_type.text.lower():\n                            upload_button.send_keys(self.resume_dir)\n        except:\n            print(\"Failed to upload resume or cover letter!\")\n            pass\n\n\n    def enter_text(self, element, text):\n        element.clear()\n        element.send_keys(text)\n\n    def select_dropdown(self, element, text):\n        select = Select(element)\n        select.select_by_visible_text(text)\n\n    # Radio Select\n    def radio_select(self, element, label_text, clickLast=False):\n        label = element.find_element_by_tag_name('label')\n        if label_text in label.text.lower() or clickLast == True:\n            label.click()\n        else:\n            pass\n\n    # Contact info fill-up\n    def contact_info(self):\n        frm_el = self.browser.find_elements_by_class_name('jobs-easy-apply-form-section__grouping')\n        if len(frm_el) > 0:\n            for el in frm_el:\n                text = el.text.lower()\n                if 'email address' in text:\n                    continue\n                elif 'phone number' in text:\n                    try:\n                        country_code_picker = el.find_element_by_class_name('fb-dropdown__select')\n                        self.select_dropdown(country_code_picker, self.personal_info['Phone Country Code'])\n                    except:\n                        print(\"Country code \" + self.personal_info['Phone Country Code'] + \" not found! Make sure it is exact.\")\n                    try:\n                        phone_number_field = el.find_element_by_class_name('fb-single-line-text__input')\n                        self.enter_text(phone_number_field, self.personal_info['Mobile Phone Number'])\n                    except:\n                        print(\"Could not input phone number.\")\n\n    def fill_up(self):\n        try:\n            easy_apply_content = self.browser.find_element_by_class_name('jobs-easy-apply-content')\n            b4 = easy_apply_content.find_element_by_class_name('pb4')\n            pb4 = easy_apply_content.find_elements_by_class_name('pb4')\n            if len(pb4) > 0:\n                for pb in pb4:\n                    try:\n                        label = pb.find_element_by_tag_name('h3').text.lower()\n                        try:\n                            self.additional_questions()\n                        except:\n                            pass\n\n                        try:\n                            self.send_resume()\n                        except:\n                            pass\n\n                        if 'home address' in label:\n                            self.home_address(pb)\n                        elif 'contact info' in label:\n                            self.contact_info()\n                    except:\n                        pass\n        except:\n            pass\n\n    def write_to_file(self, company, job_title, link, location, search_location):\n        to_write = [company, job_title, link, location]\n        file_path = self.output_file_directory + self.file_name + search_location + \".csv\"\n\n        with open(file_path, 'a') as f:\n            writer = csv.writer(f)\n            writer.writerow(to_write)\n\n    def scroll_slow(self, scrollable_element, start=0, end=3600, step=100, reverse=False):\n        if reverse:\n            start, end = end, start\n            step = -step\n\n        for i in range(start, end, step):\n            self.browser.execute_script(\"arguments[0].scrollTo(0, {})\".format(i), scrollable_element)\n            time.sleep(random.uniform(1.0, 2.6))\n\n    def avoid_lock(self):\n        if self.disable_lock:\n            return\n\n        pyautogui.keyDown('ctrl')\n        pyautogui.press('esc')\n        pyautogui.keyUp('ctrl')\n        time.sleep(1.0)\n        pyautogui.press('esc')\n\n    def get_base_search_url(self, parameters):\n        remote_url = \"\"\n\n        if parameters['remote']:\n            remote_url = \"f_CF=f_WRA\"\n\n        level = 1\n        experience_level = parameters.get('experienceLevel', [])\n        experience_url = \"f_E=\"\n        for key in experience_level.keys():\n            if experience_level[key]:\n                experience_url += \"%2C\" + str(level)\n            level += 1\n\n        distance_url = \"?distance=\" + str(parameters['distance'])\n\n        job_types_url = \"f_JT=\"\n        job_types = parameters.get('experienceLevel', [])\n        for key in job_types:\n            if job_types[key]:\n                job_types_url += \"%2C\" + key[0].upper()\n\n        date_url = \"\"\n        dates = {\"all time\": \"\", \"month\": \"&f_TPR=r2592000\", \"week\": \"&f_TPR=r604800\", \"24 hours\": \"&f_TPR=r86400\"}\n        date_table = parameters.get('date', [])\n        for key in date_table.keys():\n            if date_table[key]:\n                date_url = dates[key]\n                break\n\n        easy_apply_url = \"&f_LF=f_AL\"\n\n        extra_search_terms = [distance_url, remote_url, job_types_url, experience_url]\n        extra_search_terms_str = '&'.join(term for term in extra_search_terms if len(term) > 0) + easy_apply_url + date_url\n\n        return extra_search_terms_str\n\n    def next_job_page(self, position, location, job_page):\n        self.browser.get(\"https://www.linkedin.com/jobs/search/\" + self.base_search_url +\n                         \"&keywords=\" + position + location + \"&start=\" + str(job_page*25))\n\n        self.avoid_lock()\n\n"
  },
  {
    "path": "main.py",
    "content": "import yaml, pdb\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\nfrom webdriver_manager.chrome import ChromeDriverManager\nfrom linkedineasyapply import LinkedinEasyApply\nfrom validate_email import validate_email\n\ndef init_browser():\n    browser_options = Options()\n    options = ['--disable-blink-features', '--no-sandbox', '--start-maximized', '--disable-extensions',\n               '--ignore-certificate-errors', '--disable-blink-features=AutomationControlled']\n\n    for option in options:\n        browser_options.add_argument(option)\n\n    driver = webdriver.Chrome(ChromeDriverManager().install(), chrome_options=browser_options)\n\n    driver.set_window_position(0, 0)\n    driver.maximize_window()\n\n    return driver\n\n\ndef validate_yaml():\n    with open(\"config.yaml\", 'r') as stream:\n        try:\n            parameters = yaml.safe_load(stream)\n        except yaml.YAMLError as exc:\n            raise exc\n\n    mandatory_params = ['email', 'password', 'disableAntiLock', 'remote', 'experienceLevel', 'jobTypes', 'date',\n                        'positions', 'locations', 'distance', 'outputFileDirectory', 'checkboxes', 'universityGpa',\n                        'languages', 'industry', 'technology', 'personalInfo', 'eeo', 'uploads']\n\n    for mandatory_param in mandatory_params:\n        if mandatory_param not in parameters:\n            raise Exception(mandatory_param + ' is not inside the yml file!')\n\n    assert validate_email(parameters['email'])\n    assert len(str(parameters['password'])) > 0\n\n    assert isinstance(parameters['disableAntiLock'], bool)\n\n    assert isinstance(parameters['remote'], bool)\n\n    assert len(parameters['experienceLevel']) > 0\n    experience_level = parameters.get('experienceLevel', [])\n    at_least_one_experience = False\n    for key in experience_level.keys():\n        if experience_level[key]:\n            at_least_one_experience = True\n    assert at_least_one_experience\n\n    assert len(parameters['jobTypes']) > 0\n    job_types = parameters.get('jobTypes', [])\n    at_least_one_job_type = False\n    for key in job_types.keys():\n        if job_types[key]:\n            at_least_one_job_type = True\n    assert at_least_one_job_type\n\n    assert len(parameters['date']) > 0\n    date = parameters.get('date', [])\n    at_least_one_date = False\n    for key in date.keys():\n        if date[key]:\n            at_least_one_date = True\n    assert at_least_one_date\n\n    approved_distances = {0, 5, 10, 25, 50, 100}\n    assert parameters['distance'] in approved_distances\n\n    assert len(parameters['positions']) > 0\n    assert len(parameters['locations']) > 0\n\n    assert len(parameters['uploads']) >= 1 and 'resume' in parameters['uploads']\n\n    assert len(parameters['checkboxes']) > 0\n\n    checkboxes = parameters.get('checkboxes', [])\n    assert isinstance(checkboxes['driversLicence'], bool)\n    assert isinstance(checkboxes['requireVisa'], bool)\n    assert isinstance(checkboxes['legallyAuthorized'], bool)\n    assert isinstance(checkboxes['urgentFill'], bool)\n    assert isinstance(checkboxes['commute'], bool)\n    assert isinstance(checkboxes['backgroundCheck'], bool)\n    assert 'degreeCompleted' in checkboxes\n\n    assert isinstance(parameters['universityGpa'], (int, float))\n\n    languages = parameters.get('languages', [])\n    language_types = {'none', 'conversational', 'professional', 'native or bilingual'}\n    for language in languages:\n        assert languages[language].lower() in language_types\n\n    industry = parameters.get('industry', [])\n\n    for skill in industry:\n        assert isinstance(industry[skill], int)\n    assert 'default' in industry\n\n    technology = parameters.get('technology', [])\n\n    for tech in technology:\n        assert isinstance(technology[tech], int)\n    assert 'default' in technology\n\n    assert len(parameters['personalInfo'])\n    personal_info = parameters.get('personalInfo', [])\n    for info in personal_info:\n        assert personal_info[info] != ''\n\n    assert len(parameters['eeo'])\n    eeo = parameters.get('eeo', [])\n    for survey_question in eeo:\n        assert eeo[survey_question] != ''\n\n    return parameters\n\n\nif __name__ == '__main__':\n    parameters = validate_yaml()\n    browser = init_browser()\n\n    bot = LinkedinEasyApply(parameters, browser)\n    bot.login()\n    bot.security_check()\n    bot.start_applying()\n\n\n\n\n"
  },
  {
    "path": "requirements.txt",
    "content": "selenium\npyautogui\nwebdriver_manager\nPyYAML\nvalidate_email"
  }
]