├── tests ├── __init__.py └── test_cli.py ├── jobautomate ├── __init__.py ├── __main__.py └── commandline.py ├── pytest.ini ├── cli.png ├── title.png ├── .coveragerc ├── setup.py ├── .gitignore ├── .travis.yml └── README.rst /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /jobautomate/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | testpaths = tests/test_cli.py -------------------------------------------------------------------------------- /cli.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandeep/Job-Automate/HEAD/cli.png -------------------------------------------------------------------------------- /title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandeep/Job-Automate/HEAD/title.png -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | omit = 3 | setup.py 4 | jobautomate/__main__.py 5 | tests/* 6 | */__init__.py -------------------------------------------------------------------------------- /jobautomate/__main__.py: -------------------------------------------------------------------------------- 1 | from jobautomate.commandline import cli 2 | 3 | if __name__ == '__main__': 4 | cli() 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup(name='jobautomate', 4 | version='0.18.11', 5 | author='Mandeep Bhutani', 6 | description='Automate a job search with Indeed', 7 | url='https://github.com/mandeep/Job-Automate', 8 | packages=['jobautomate'], 9 | license='GPLv3+', 10 | install_requires=[ 11 | 'indeed==0.0.4', 12 | 'selenium==2.53.6', 13 | 'click==6.6', 14 | 'xvfbwrapper==0.2.8' 15 | ], 16 | entry_points=''' 17 | [console_scripts] 18 | jobautomate=jobautomate.commandline:cli 19 | ''', 20 | classifiers=[ 21 | 'Development Status :: 7 - Inactive', 22 | 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', 23 | 'Programming Language :: Python :: 3.3', 24 | 'Programming Language :: Python :: 3.4', 25 | 'Programming Language :: Python :: 3.5', 26 | ] 27 | ) 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *.cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # Jupyter Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # SageMath parsed files 79 | *.sage.py 80 | 81 | # Environments 82 | .env 83 | .venv 84 | env/ 85 | venv/ 86 | ENV/ 87 | 88 | # Spyder project settings 89 | .spyderproject 90 | .spyproject 91 | 92 | # Rope project settings 93 | .ropeproject 94 | 95 | # mkdocs documentation 96 | /site 97 | 98 | # mypy 99 | .mypy_cache/ 100 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | notifications: 2 | email: false 3 | 4 | dist: trusty 5 | group: deprecated-2017Q2 6 | sudo: required 7 | 8 | language: python 9 | python: 10 | - '3.4' 11 | - '3.5' 12 | - '3.6' 13 | 14 | before_install: 15 | - pip install coveralls 16 | 17 | install: 18 | - pip install selenium==2.53.6 19 | - pip install py==1.4.29 20 | - pip install pytest-html==1.12.0 21 | - pip install pytest-base-url==1.2.0 22 | - pip install pytest==3.0.0 23 | - pip install pytest-cov==2.4.0 24 | - pip install pytest-selenium==1.3.1 25 | - pip install pytest-xvfb==1.0.0 26 | - pip install pytest-faulthandler==1.3.1 27 | - pip install xvfbwrapper==0.2.8 28 | - python setup.py install 29 | 30 | script: 31 | - pytest -v --cov=. --driver Firefox 32 | 33 | after_success: 34 | - coveralls 35 | 36 | deploy: 37 | user: mandeepb 38 | provider: pypi 39 | distributions: sdist bdist_wheel 40 | password: 41 | secure: "uvd3F0D7YyfUtF5WJmUccrJFLNZpV38UaQLKl3XIcVPiKrAEhYabToBizOIPMHb4LpxMBvZrnhQ7+2cigeyB9YmBSOJMuxquupLdJN67nManoIL2iAI30xXKj5bq90PsJj0QK5Yv7lcPqaijb1CBO52sjkl2NcJluAIhdZBMWHu9iXWYSfWmlL/d3Un4Hlsd0JrqIXsod11yZ4QGHc+jRJQlbXgxWMPbsO/FTkRPKtSeLCyW14h2y7UOvkjFlM2EJylofUlaO/DBXW/7FafRLQE7YL/MTMjlJQY0tpfghl3t8+zvMD4R48kvU23reHl4jMzvsn4SnfvGcIBmm2/JOVBeIPls7urmb90zqx2YMUXfW6aREWLS8K9uh8mNKazzVp+2oaF/d3XRb/IeZt4N7sWslLBtIyUZG/CeDYX7hSoz3VQnoDtCygeCCI7EUystI0tdmzPv6+NCLxT+WlyVLU5C2sceQtAOFRJNL8p8MwJf+tqStMp+YriVg/w4qhjGeUvlnKddHW8/Qjz9ieIHp+5w0h09rKWGP5Oav5xxPWWU143Nzs5nZK45CxPgnmNQhElFN+u8UjPAn4CVc0MIyyt+QV/gQJ7/EuRjNMu/v977C2/5ztwVokLu7ojLNn+zygZ1aLFpkVpwVpA54Mclc70kAk8zB8Pq+/Eb7+puG64=" 42 | true: 43 | branch: master 44 | 45 | env: 46 | global: 47 | secure: YlExAyzjh2h77a0pl2TqxO6l7wTgNutZzYL3GopOoJcNiP1WK5pirBbG+K+ImClDawTU3ju/FdUTfBBtKaPDKi1N7LQV+cGZhlbhxOkERP6zJjzahLkPbH7/c2IhZSSWcGgPemvropxnJCn2OPsbLrM90n2u52aJogxtULPKXYVgEp4WkRk52Qe6fNpcU9tdtDJ8wkNiU3AMY9yJaQxjf+t3Uf6Oy3RsfevCaDwq28u4pFZMN+MegbRRU3yd9WdFIayaPJhepx0nXXAEWm7BxjQFyBADaRpYxLLdDwQsMnsIpPJdOd/jwOY1FSVgWkRU7nH5GEh4bczW2M+/vhOJVdgy+0g6OVhpmf44ubHRwgkH7OAMX3nWpFgcNBmb2vWfYbwB1+wmOY8Bie+tQSXYTH5DAmmwUr5m6A1wSVNGYkyshq6z1KznsRyCY0OA5TqMH/QRN3Uekhl3t8Y8htpOq0t0XiZWCQjbYlr2RxgPEyYTHio0xfXNCETd0GKp4dn1Kkfe3NHPK+FDMmW5sUpCGQmnxsHmfOnjCxGK11in6MnDBYrnFwNk81yfsOTaVKa1opQtTBuXn/sCR+NhoJzCNXgGeE9sjgMudSVgSRtee6QBJGaGf5Wj8kkAmpvmjXLTqPigB7Ec/VA986nsCHejB5ucDBY34o1Vi1JY4L+67X4= -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: title.png 2 | 3 | 4 | |travis| |coverage| |dependencies| |quality| |pyversions| |pypi| |status| |wheel| |license| 5 | 6 | 7 | Due to changes in Indeed's easily apply application process. Job Automate no longer works on Indeed and thus is no longer maintained. 8 | 9 | 10 | Job Automate is a command line application written in Python using the Selenium and Click libraries. 11 | The application uses the Indeed Job Search API to find and automatically apply to 'easily apply' jobs 12 | published by employers on Indeed.com. Job Automate requires access to Indeed's API with an Indeed 13 | Publisher Key. To obtain a key, please visit: http://www.indeed.com/publisher. 14 | 15 | 16 | ************* 17 | Installation 18 | ************* 19 | 20 | To install the script merely run the following command in a command line prompt:: 21 | 22 | $ pip install jobautomate 23 | 24 | If you would rather install from source, run the following commands:: 25 | 26 | $ git clone https://github.com/mandeep/Job-Automate.git 27 | $ cd Job-Automate 28 | $ python install setup.py 29 | 30 | ************ 31 | Usage 32 | ************ 33 | 34 | Job Automate accepts the Indeed Publisher ID as an environment variable or as a command line flag. In order 35 | to be used as an environment variable, one must export API_KEY=ID to PATH. The command line application may be invoked with the following command, flags, and arguments:: 36 | 37 | Usage: jobautomate [OPTIONS] FIRST_NAME LAST_NAME EMAIL JOB_DESCRIPTION RESUME_PATH [JOB_LOCATION] 38 | 39 | Optional arguments: 40 | --help Show a help message and quit 41 | --key Use the provided publisher key to access the Indeed API 42 | --verbose Print to stdout the jobs that are not easily apply applications 43 | --vxfb Run the application in a virtual display (Linux only) 44 | 45 | Example: 46 | $ jobautomate --key 12345 "Bender" "Rodriguez" "bender@ilovebender.com" "Metalworking" "girder.doc" 47 | 48 | Once entered the script will open a Firefox WebDriver instance and search for 'easily apply' jobs in the URLs given by the Indeed API. Due to the API only allowing 25 urls at a given time, the application will prompt for continuation after 25 urls have been traversed. 49 | 50 | ************ 51 | Screenshot 52 | ************ 53 | 54 | .. image:: cli.png 55 | 56 | 57 | .. |travis| image:: https://travis-ci.org/mandeep/Job-Automate.svg?branch=master 58 | :target: https://travis-ci.org/mandeep/Job-Automate 59 | .. |coverage| image:: https://coveralls.io/repos/github/mandeep/Job-Automate/badge.svg?branch=master 60 | :target: https://coveralls.io/github/mandeep/Job-Automate?branch=master 61 | .. |dependencies| image:: https://dependencyci.com/github/mandeep/Job-Automate/badge 62 | :target: https://dependencyci.com/github/mandeep/Job-Automate 63 | .. |quality| image:: https://img.shields.io/scrutinizer/g/mandeep/Job-Automate.svg 64 | :target: https://scrutinizer-ci.com/g/mandeep/Job-Automate/ 65 | .. |pypi| image:: https://img.shields.io/pypi/v/jobautomate.svg 66 | :target: https://pypi.python.org/pypi/jobautomate 67 | .. |status| image:: https://img.shields.io/pypi/status/jobautomate.svg 68 | :target: https://pypi.python.org/pypi/jobautomate 69 | .. |pyversions| image:: https://img.shields.io/pypi/pyversions/jobautomate.svg 70 | :target: https://pypi.python.org/pypi/jobautomate 71 | .. |wheel| image:: https://img.shields.io/pypi/format/jobautomate.svg 72 | :target: https://pypi.python.org/pypi/jobautomate 73 | .. |license| image:: https://img.shields.io/pypi/l/jobautomate.svg 74 | :target: https://pypi.python.org/pypi/jobautomate 75 | -------------------------------------------------------------------------------- /tests/test_cli.py: -------------------------------------------------------------------------------- 1 | from click.testing import CliRunner 2 | import pytest 3 | 4 | import jobautomate.commandline 5 | 6 | 7 | @pytest.fixture 8 | def parameters(): 9 | """Store the parameters for use with the Indeed API.""" 10 | job_search = jobautomate.commandline.indeed_parameters('Financial Analyst', 'New York City') 11 | return job_search 12 | 13 | 14 | @pytest.fixture 15 | def application(): 16 | """Link to a working job application.""" 17 | return ('https://www.indeed.com/cmp/Indeed-Hire-Master-Account/jobs/Senior-API-Developer-6aeb0ad94f04299b') 18 | 19 | 20 | def fill(driver): 21 | """Open and fill an easily apply application.""" 22 | jobautomate.commandline.open_application(driver, 'indeed-apply-button') 23 | jobautomate.commandline.switch_frames(driver, 'iframe[name$=modal-iframe]') 24 | jobautomate.commandline.fill_application( 25 | driver, 'Homer', 'Simpson', 'Chunkylover53@aol.com', 'resume.txt') 26 | 27 | 28 | def test_indeed_api_parameters(parameters): 29 | """Test that the Indeed API returns the correct values.""" 30 | assert isinstance(parameters, dict) is True 31 | assert 'Financial Analyst' in parameters.values() 32 | assert 'New York City' in parameters.values() 33 | 34 | 35 | def test_retrieve_indeed_urls(selenium, parameters): 36 | """Test that the Indeed API returns 25 job application URLs.""" 37 | response = jobautomate.commandline.access_indeed_api(parameters) 38 | job_urls = jobautomate.commandline.retrieve_indeed_urls(response) 39 | assert len(job_urls) == 25 40 | assert all('http://' in url for url in job_urls) 41 | 42 | 43 | def test_false_api_key(parameters): 44 | """Test that an invalid API key returns an error.""" 45 | assert jobautomate.commandline.access_indeed_api( 46 | parameters, 47 | 123456789) == {'error': 'Invalid publisher number provided.'} 48 | 49 | 50 | def test_open_application(selenium, application): 51 | """Test that an easily apply application opens properly.""" 52 | selenium.get(application) 53 | jobautomate.commandline.open_application(selenium, 'indeed-apply-button') 54 | 55 | 56 | def test_fill_application(selenium, application): 57 | """Test that an easily apply application can be filled.""" 58 | selenium.get(application) 59 | fill(selenium) 60 | 61 | 62 | def test_application_submission(selenium, application): 63 | """Test that all steps up to submitting the application are completed.""" 64 | selenium.get(application) 65 | fill(selenium) 66 | jobautomate.commandline.submit_application(selenium, debug=True) 67 | 68 | 69 | def test_cli_command(): 70 | """Test the CLI.""" 71 | runner = CliRunner(echo_stdin=True) 72 | result = runner.invoke( 73 | jobautomate.commandline.cli, ['--debug', 'Homer', 'Simpson', 'Chunkylover53@aol.com', 74 | 'Nuclear Technician', '.travis.yml'], input='no') 75 | assert not result.exception 76 | 77 | 78 | def test_cli_false_key(): 79 | """Test the CLI with a false API key.""" 80 | runner = CliRunner(echo_stdin=True) 81 | result = runner.invoke( 82 | jobautomate.commandline.cli, ['--debug', '--key', 'FALSEAPIKEY', 'Homer', 'Simpson', 83 | 'Chunkylover53@aol.com', 'Nuclear Technician', 84 | '.travis.yml'], input='no') 85 | assert result.exception 86 | 87 | 88 | def test_cli_xvfb(): 89 | """Test the CLI with the --xvfb flag.""" 90 | runner = CliRunner(echo_stdin=True) 91 | xvfb_result = runner.invoke( 92 | jobautomate.commandline.cli, ['--debug', '--xvfb', 'Homer', 'Simpson', 93 | 'Chunkylover53@aol.com', 'Nuclear Technician', 94 | '.travis.yml'], input='no') 95 | assert not xvfb_result.exception 96 | 97 | 98 | def test_cli_verbose(): 99 | """Test the CLI with the --verbose flag.""" 100 | runner = CliRunner(echo_stdin=True) 101 | verbose_result = runner.invoke( 102 | jobautomate.commandline.cli, ['--debug', '--verbose', 'Homer', 'Simpson', 103 | 'Chunkylover53@aol.com', 'Nuclear Technician', 104 | '.travis.yml'], input='no') 105 | assert not verbose_result.exception 106 | -------------------------------------------------------------------------------- /jobautomate/commandline.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import click 4 | from indeed import IndeedClient 5 | from selenium import webdriver 6 | from selenium.webdriver.common.by import By 7 | from selenium.webdriver.support.ui import WebDriverWait 8 | from selenium.webdriver.support import expected_conditions as EC 9 | from selenium.common.exceptions import NoSuchElementException, ElementNotVisibleException 10 | from xvfbwrapper import Xvfb 11 | 12 | 13 | class InvalidAPIKey(Exception): 14 | """Raise when an invalid API key is supplied.""" 15 | 16 | 17 | def indeed_parameters(what, where): 18 | """Store parameters for use with the Indeed API. 19 | 20 | Positional arguments: 21 | what -- the description of the desired job 22 | where -- the location of the desired job 23 | 24 | :param q: job description 25 | :param l: job location; searches entire U.S. when left blank 26 | :param sort: sort results by relevance or date 27 | :param sr: direct hire only or include staffing agencies 28 | :param limit: number of results, 0-25 29 | :param fromage: number of days to search 30 | :param start: index of beginning search result 31 | :param userip: ip of client (required) 32 | :param useragent: user agent of client (required) 33 | """ 34 | params = {'q': what, 35 | 'l': where, 36 | 'sort': 'date', 37 | 'sr': 'directhire', 38 | 'limit': 25, 39 | 'fromage': 365, 40 | 'start': 0, 41 | 'userip': "1.2.3.4", 42 | 'useragent': ("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36" 43 | "(KHTML, like Gecko) Chrome/48.0.2564.82 Safari/537.36") 44 | } 45 | return params 46 | 47 | 48 | def access_indeed_api(parameters, publisher_key=None): 49 | """Access the Indeed API using the given parameters and publisher key. 50 | 51 | Positional argument: 52 | parameters -- a dictionary of the parameters to send to Indeed's API 53 | 54 | Keyword argument: 55 | publisher_key -- the publisher key for Indeed's API, defaults to environment variable 56 | """ 57 | if publisher_key is None: 58 | publisher_key = os.environ['API_KEY'] 59 | client = IndeedClient(publisher_key) 60 | response = client.search(**parameters) 61 | return response 62 | 63 | 64 | def retrieve_indeed_urls(response): 65 | """Use the response from Indeed's API to retrieve job application URLs. 66 | 67 | Positional argument: 68 | response -- the HTTP response from Indeed's API 69 | """ 70 | urls = [str(links['url']) for links in response['results']] 71 | return urls 72 | 73 | 74 | def open_application(driver, button_name): 75 | """Search the job page for the Apply Now button and click it if it exists. 76 | 77 | Positional arguments: 78 | driver -- the Selenium webdriver instance 79 | button_name -- the class name of the button to click 80 | 81 | When the Apply Now button is clicked, the job application dialog 82 | opens in a new frame. 83 | """ 84 | driver.implicitly_wait(1) 85 | driver.find_element_by_class_name(button_name).click() 86 | 87 | 88 | def switch_frames(driver, frame_name): 89 | """Navigate nested iFrames to select the application modal dialog. 90 | 91 | Positional arguments: 92 | driver -- the Selenium webdriver instance 93 | frame_name -- the CSS selector of the frame to switch to 94 | """ 95 | wait = WebDriverWait(driver, 15) 96 | frame = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, frame_name))) 97 | driver.switch_to.frame(frame) 98 | wait.until(EC.frame_to_be_available_and_switch_to_it(0)) 99 | 100 | 101 | def fill_application(driver, first_name, last_name, email, cv): 102 | """Fill the Indeed Easily Apply job application with the given arguments. 103 | 104 | Positional arguments: 105 | driver -- the Selenium webdriver instance 106 | first_name -- the first name to use on the job application 107 | last_name -- the last name to use on the job application 108 | email -- the email address to use on the job appication 109 | cv -- the file path of the resume to use on the job application 110 | """ 111 | job_title = driver.find_element_by_class_name("jobtitle").text 112 | company = driver.find_element_by_class_name("jobcompany").text 113 | print("Applying for: {} at {}".format(job_title, company)) 114 | driver.find_element_by_id('applicant.name').send_keys("{} {}" .format(first_name, last_name)) 115 | driver.find_element_by_id('applicant.email').send_keys(email) 116 | driver.find_element_by_id('resume').send_keys(os.path.abspath(cv)) 117 | 118 | 119 | def submit_application(driver, debug=False): 120 | """Click the apply button and submit the application. 121 | 122 | Positional argument: 123 | driver -- the Selenium webdriver instance 124 | 125 | Keyword argument: 126 | debug -- flag used for testing to omit application submission 127 | 128 | There are two types of apply methods: one applies after 129 | clicking the apply button, and the other applies after clicking the 130 | continue button and answering some questions. The try/except clause 131 | clicks the apply button if it exists, otherwise it will click the continue 132 | button and select the radio buttons that indicate 'Yes'. 133 | """ 134 | try: 135 | driver.find_element_by_link_text('Continue').click() 136 | driver.implicitly_wait(1) 137 | for radio_button in driver.find_elements_by_xpath('//*[@type="radio" and @value="0"]'): 138 | radio_button.click() 139 | if not debug: 140 | driver.find_element_by_id('apply').click() 141 | driver.implicitly_wait(1) 142 | print('Application Successful.') 143 | except (NoSuchElementException, ElementNotVisibleException): 144 | if not debug: 145 | driver.find_element_by_id('apply').click() 146 | driver.implicitly_wait(1) 147 | print('Application Successful.') 148 | finally: 149 | driver.switch_to.window(driver.window_handles[0]) 150 | 151 | 152 | @click.command() 153 | @click.option('--debug', is_flag=True, 154 | help="Disable click on apply button found on job applications.") 155 | @click.option('--key', default=None, help="Indeed Publisher ID") 156 | @click.option('--xvfb', is_flag=True, help="Run the application in a headless browser.") 157 | @click.option('--verbose', is_flag=True, 158 | help="Print to standard output the jobs that are not easily apply applications.") 159 | @click.argument('first_name') 160 | @click.argument('last_name') 161 | @click.argument('email_address') 162 | @click.argument('job_description') 163 | @click.argument('resume', type=click.Path(exists=True)) 164 | @click.argument('job_location', default='') 165 | def cli(debug, key, xvfb, verbose, first_name, last_name, email_address, 166 | job_description, resume, job_location): 167 | """Apply to jobs automatically with Job Automate. 168 | 169 | Job Automate requires the user's first name, last name, email address, job description, 170 | and resume location prior to automating a job search. The job location is an optional argument 171 | that is left blank by default. This allows Job Automate to search for jobs across the 172 | entire United States. Each of the command line arguments requires a string data type. In 173 | order to pass a string to each argument, type each argument inside quotation marks. 174 | 175 | An example usage: jobautomate --key ABC123 "Homer" "Simpson" "Chunkylover53@aol.com" 176 | "Nuclear Technician" "/path/to/resume.txt" "Springfield" 177 | 178 | When run, jobautomate will print to terminal the job position and company name tied to 179 | the easily apply application. With the --verbose flag, jobautomate will also print to 180 | terminal the applications which are not easily apply applications. 181 | """ 182 | if xvfb: 183 | vdisplay = Xvfb() 184 | vdisplay.start() 185 | 186 | user_parameters = indeed_parameters(job_description, job_location) 187 | response = access_indeed_api(user_parameters, key) 188 | 189 | if response == {'error': 'Invalid publisher number provided.'}: 190 | raise InvalidAPIKey(response) 191 | 192 | driver = webdriver.Firefox() 193 | while True: 194 | for url in retrieve_indeed_urls(response): 195 | driver.get(url) 196 | try: 197 | open_application(driver, 'indeed-apply-button') 198 | switch_frames(driver, 'iframe[name$=modal-iframe]') 199 | fill_application(driver, first_name, last_name, email_address, resume) 200 | if debug: 201 | submit_application(driver, debug=True) 202 | else: 203 | submit_application(driver) 204 | except (NoSuchElementException, ElementNotVisibleException): 205 | if verbose: 206 | print('Not an easily apply job application.') 207 | else: 208 | pass 209 | 210 | user_prompt = click.prompt('Would you like to continue searching for jobs? (yes/no)') 211 | if user_prompt == 'yes': 212 | user_parameters['start'] += 25 213 | response = access_indeed_api(user_parameters, key) 214 | else: 215 | driver.quit() 216 | if xvfb: 217 | vdisplay.stop() 218 | break 219 | --------------------------------------------------------------------------------