From f632db05262e9f276901f4ed62fad5d05a95526f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 24 Apr 2026 08:04:42 +0000 Subject: [PATCH] Code quality: Remove trailing whitespace and fix PEP 8 formatting - Remove trailing whitespace from linkedin.py (lines 32, 45, 58, 66, 93, 105, 113, 139, 145, 168, 181, 208, 213, 219, 228, 259, 273, 290, 316, 328, 340, 350) - Remove trailing whitespace from utils.py (lines 92, 94, 110, 316, 318, 320, 329) - Fix PEP 8: Remove unnecessary parentheses in conditional statements - linkedin.py: chromeBrowserOptions(), jobsToPages() - utils.py: chooseResume() method comparisons, getJobProperties() blacklist checks - Fix spacing in control flow statements - linkedin.py line 66: 'try: ' -> 'try:' - linkedin.py line 118: 'try: ' -> 'try:' - Fix logic bug: Replace 'type(len(resumes)) != int' with 'len(resumes) == 0' in chooseResume() - Clean up duplicate print() statements in getJobProperties() (lines 340, 350) - Improve code consistency and readability Co-authored-by: Ongun Demirag --- linkedin.py | 90 ++++++++++++++++++++++++++--------------------------- utils.py | 31 +++++++++--------- 2 files changed, 60 insertions(+), 61 deletions(-) diff --git a/linkedin.py b/linkedin.py index 4171b94..7652fb4 100755 --- a/linkedin.py +++ b/linkedin.py @@ -29,7 +29,7 @@ class Linkedin: def __init__(self) -> None: utils.prYellow("🤖 Thanks for using Easy Apply Jobs bot, for more information you can visit our site - www.automated-bots.com") utils.prYellow("🌐 Bot will run in Chrome browser and log in Linkedin for you.") - + # Fix for WinError 193: Explicitly construct chromedriver path try: chrome_install = ChromeDriverManager().install() @@ -42,7 +42,7 @@ def __init__(self) -> None: if config.displayWarnings: utils.prYellow(f"⚠️ Warning: Could not use explicit chromedriver path, using default: {str(e)[0:50]}") self.driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=utils.chromeBrowserOptions()) - + # Apply stealth mode if available if STEALTH_AVAILABLE: try: @@ -55,7 +55,7 @@ def __init__(self) -> None: fix_hairline=True) except Exception as e: utils.prYellow(f"⚠️ Warning: Could not apply stealth mode: {str(e)}") - + self.cookies_path = f"{os.path.join(os.getcwd(),'cookies')}/{self.getHash(config.email)}.pkl" self.driver.get('https://www.linkedin.com') self.loadCookies() @@ -63,7 +63,7 @@ def __init__(self) -> None: if not self.isLoggedIn(): self.driver.get("https://www.linkedin.com/login?trk=guest_homepage-basic_nav-header-signin") utils.prYellow("🔄 Trying to log in Linkedin...") - try: + try: self.driver.find_element("id","username").send_keys(config.email) time.sleep(2) self.driver.find_element("id","password").send_keys(config.password) @@ -90,11 +90,11 @@ def saveCookies(self) -> None: try: # Get the directory path for cookies cookies_dir = os.path.dirname(self.cookies_path) - + # Create cookies directory if it doesn't exist if cookies_dir and not os.path.exists(cookies_dir): os.makedirs(cookies_dir, exist_ok=True) - + # Save cookies to file with open(self.cookies_path, "wb") as f: pickle.dump(self.driver.get_cookies(), f) @@ -102,7 +102,7 @@ def saveCookies(self) -> None: if config.displayWarnings: utils.prYellow(f"⚠️ Warning: Could not save cookies: {str(e)[0:100]}") # Don't raise the exception - cookie saving is not critical for bot operation - + def isLoggedIn(self) -> bool: self.driver.get('https://www.linkedin.com/feed') try: @@ -110,12 +110,12 @@ def isLoggedIn(self) -> bool: return True except Exception: pass - return False - + return False + def generateUrls(self) -> None: if not os.path.exists('data'): os.makedirs('data') - try: + try: with open('data/urlData.txt', 'w',encoding="utf-8" ) as file: linkedinJobLinks = utils.LinkedinUrlGenerate().generateUrlLinks() for url in linkedinJobLinks: @@ -136,13 +136,13 @@ def linkJobApply(self) -> None: urlData = utils.getUrlDataFile() - for url in urlData: + for url in urlData: self.driver.get(url) time.sleep(random.uniform(1, constants.botSpeed)) # Handle case where no jobs are found (//small element doesn't exist) try: - totalJobs = self.driver.find_element(By.XPATH,'//small').text + totalJobs = self.driver.find_element(By.XPATH,'//small').text except Exception as e: urlWords = utils.urlToKeywords(url) lineToWrite = "\n Category: " + urlWords[0] + ", Location: " + urlWords[1] + ", No jobs found for this search criteria. Skipping..." @@ -165,7 +165,7 @@ def linkJobApply(self) -> None: offersPerPage = self.driver.find_elements(By.XPATH, '//li[@data-occludable-job-id]') offerIds = [] - + # Extract all offer IDs immediately to avoid stale element references for offer in offersPerPage: try: @@ -176,9 +176,9 @@ def linkJobApply(self) -> None: if config.displayWarnings: utils.prYellow(f"⚠️ Warning: Could not get offer ID: {str(e)[0:50]}") continue - + time.sleep(random.uniform(1, constants.botSpeed)) - + # Check for "Applied" status by re-finding elements to avoid stale references try: offersPerPage = self.driver.find_elements(By.XPATH, '//li[@data-occludable-job-id]') @@ -205,18 +205,18 @@ def linkJobApply(self) -> None: countJobs += 1 jobProperties = self.getJobProperties(countJobs) - if "blacklisted" in jobProperties: + if "blacklisted" in jobProperties: countBlacklisted += 1 lineToWrite = jobProperties + " | " + "* 🤬 Blacklisted Job, skipped!: " +str(offerPage) self.displayWriteResults(lineToWrite) - - else : + + else: easyApplybutton = self.easyApplyButton() if easyApplybutton is not None: easyApplybutton.click() time.sleep(random.uniform(1, constants.botSpeed)) - + # Fix for issue #72: LinkedIn added an extra "Continue to next step" button after Easy Apply try: continue_button = self.driver.find_element(By.CSS_SELECTOR, "button[aria-label='Continue to next step']") @@ -226,7 +226,7 @@ def linkJobApply(self) -> None: except Exception: # If button doesn't exist, continue normally pass - + try: self.chooseResume() # Fill phone number before submitting @@ -256,7 +256,7 @@ def linkJobApply(self) -> None: self.chooseResume() comPercentage = self.driver.find_element(By.XPATH,'html/body/div[3]/div/div/div[2]/div/div/span').text percenNumber = int(comPercentage[0:comPercentage.index("%")]) - + # For multi-step forms, respect dry-run as well. if config.dryRun: result = "* 🧪 DRY RUN - Would go through multi-step application: " + str(offerPage) @@ -269,8 +269,8 @@ def linkJobApply(self) -> None: countApplied += 1 if config.maxApplicationsPerRun and countApplied >= config.maxApplicationsPerRun: reachedCap = True - - except Exception: + + except Exception: countCannotApply += 1 self.chooseResume() lineToWrite = jobProperties + " | " + "* 🥵 Cannot apply to this Job! " +str(offerPage) @@ -289,7 +289,7 @@ def linkJobApply(self) -> None: utils.prYellow("Category: " + urlWords[0] + "," +urlWords[1]+ " applied: " + str(countApplied) + " jobs out of " + str(countJobs) + ".") - + if reachedCap: utils.prYellow("🛑 Reached max applications per run limit (" + str(config.maxApplicationsPerRun) + "). Stopping.") durationSec = time.time() - startTime @@ -304,11 +304,11 @@ def chooseResume(self) -> None: By.CLASS_NAME, "jobs-document-upload__title--is-required") resumes = self.driver.find_elements( By.XPATH, "//div[contains(@class, 'ui-attachment--pdf')]") - if (len(resumes) == 1 and resumes[0].get_attribute("aria-label") == "Select this resume"): + if len(resumes) == 1 and resumes[0].get_attribute("aria-label") == "Select this resume": resumes[0].click() - elif (len(resumes) > 1 and resumes[config.preferredCv-1].get_attribute("aria-label") == "Select this resume"): + elif len(resumes) > 1 and resumes[config.preferredCv-1].get_attribute("aria-label") == "Select this resume": resumes[config.preferredCv-1].click() - elif (type(len(resumes)) != int): + elif len(resumes) == 0: utils.prRed( "❌ No resume has been selected please add at least one resume to your Linkedin account.") except Exception: @@ -321,23 +321,22 @@ def getJobProperties(self, count: int) -> str: try: jobTitle = self.driver.find_element(By.XPATH, "//h1[contains(@class, 'job-title')]").get_attribute("innerHTML").strip() - res = [blItem for blItem in config.blackListTitles if (blItem.lower() in jobTitle.lower())] - if (len(res) > 0): + res = [blItem for blItem in config.blackListTitles if blItem.lower() in jobTitle.lower()] + if len(res) > 0: jobTitle += "(blacklisted title: " + ' '.join(res) + ")" except Exception as e: - if (config.displayWarnings): + if config.displayWarnings: utils.prYellow("⚠️ Warning in getting jobTitle: " + str(e)[0:50]) jobTitle = "" try: time.sleep(5) jobDetail = self.driver.find_element(By.XPATH, "//div[contains(@class, 'job-details-jobs')]//div").text.replace("·", "|") - res = [blItem for blItem in config.blacklistCompanies if (blItem.lower() in jobTitle.lower())] - if (len(res) > 0): + res = [blItem for blItem in config.blacklistCompanies if blItem.lower() in jobTitle.lower()] + if len(res) > 0: jobDetail += "(blacklisted company: " + ' '.join(res) + ")" except Exception as e: - if (config.displayWarnings): - print(e) + if config.displayWarnings: utils.prYellow("⚠️ Warning in getting jobDetail: " + str(e)[0:100]) jobDetail = "" @@ -347,8 +346,7 @@ def getJobProperties(self, count: int) -> str: jobLocation = jobLocation + " | " + span.text except Exception as e: - if (config.displayWarnings): - print(e) + if config.displayWarnings: utils.prYellow("⚠️ Warning in getting jobLocation: " + str(e)[0:100]) jobLocation = "" @@ -370,7 +368,7 @@ def fillPhoneNumber(self) -> None: try: # Get phone number from config or additionalQuestions.yaml phone_number = "" - + # Try to get from config.Phone first if hasattr(config, 'Phone') and config.Phone and config.Phone.strip(): phone_number = config.Phone.strip() @@ -385,10 +383,10 @@ def fillPhoneNumber(self) -> None: phone_number = questions['inputField'].get('Phone Number', '').strip() except Exception: pass - + if not phone_number: return # No phone number configured, skip filling - + # Try multiple selectors to find phone number input fields phone_selectors = [ "input[type='tel']", @@ -399,9 +397,9 @@ def fillPhoneNumber(self) -> None: "input[data-test-single-line-text-input]", "input[class*='phone']" ] - + phone_filled = False - + # Also try XPath selectors for case-insensitive matching xpath_selectors = [ "//input[contains(translate(@name, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), 'phone')]", @@ -409,7 +407,7 @@ def fillPhoneNumber(self) -> None: "//input[contains(translate(@aria-label, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), 'phone')]", "//input[contains(translate(@placeholder, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), 'phone')]" ] - + # Try CSS selectors first for selector in phone_selectors: try: @@ -433,7 +431,7 @@ def fillPhoneNumber(self) -> None: break except Exception: continue - + # Try XPath selectors if CSS didn't work if not phone_filled: for xpath in xpath_selectors: @@ -457,13 +455,13 @@ def fillPhoneNumber(self) -> None: break except Exception: continue - + except Exception as e: if config.displayWarnings: utils.prYellow(f"⚠️ Warning: Error in fillPhoneNumber: {str(e)[0:50]}") def applyProcess(self, percentage: int, offerPage: str) -> str: - applyPages = math.floor(100 / percentage) - 2 + applyPages = math.floor(100 / percentage) - 2 result = "" for pages in range(applyPages): # Fill phone number before continuing to next step @@ -479,7 +477,7 @@ def applyProcess(self, percentage: int, offerPage: str) -> str: result = "* 🧪 DRY RUN - Would apply to this job: " + str(offerPage) return result - self.driver.find_element( By.CSS_SELECTOR, "button[aria-label='Review your application']").click() + self.driver.find_element(By.CSS_SELECTOR, "button[aria-label='Review your application']").click() time.sleep(random.uniform(1, constants.botSpeed)) if config.followCompanies is False: diff --git a/utils.py b/utils.py index ce3dbd2..4c6801b 100755 --- a/utils.py +++ b/utils.py @@ -18,21 +18,21 @@ def chromeBrowserOptions(): options.add_argument("--disable-extensions") options.add_argument('--disable-gpu') options.add_argument('--disable-dev-shm-usage') - if(config.headless): + if config.headless: options.add_argument("--headless") options.add_argument("--start-maximized") options.add_argument("--disable-blink-features") options.add_argument("--disable-blink-features=AutomationControlled") options.add_experimental_option('useAutomationExtension', False) options.add_experimental_option("excludeSwitches", ["enable-automation"]) - if(len(config.chromeProfilePath)>0): + if len(config.chromeProfilePath) > 0: # Handle both Windows (\) and Unix (/) path separators # Normalize path separators to handle mixed separators normalized_path = config.chromeProfilePath.replace('\\', os.sep).replace('/', os.sep) - + # Find the last path separator (works for both Windows and Unix) last_sep_index = normalized_path.rfind(os.sep) - + if last_sep_index != -1: initialPath = normalized_path[:last_sep_index] profileDir = normalized_path[last_sep_index + 1:] @@ -41,7 +41,7 @@ def chromeBrowserOptions(): # and use parent directory as user-data-dir initialPath = os.path.dirname(normalized_path) profileDir = os.path.basename(normalized_path) - + options.add_argument('--user-data-dir=' + initialPath) options.add_argument("--profile-directory=" + profileDir) else: @@ -80,7 +80,8 @@ def jobsToPages(numOfJobs: str) -> int: totalJobs = (numOfJobs[0:spaceIndex]) totalJobs_int = int(totalJobs.replace(',', '')) number_of_pages = math.ceil(totalJobs_int/constants.jobsPerPage) - if (number_of_pages > 40 ): number_of_pages = 40 + if number_of_pages > 40: + number_of_pages = 40 else: number_of_pages = int(numOfJobs) @@ -89,9 +90,9 @@ def jobsToPages(numOfJobs: str) -> int: def urlToKeywords(url: str) -> List[str]: keywordUrl = url[url.index("keywords=")+9:] - keyword = keywordUrl[0:keywordUrl.index("&") ] + keyword = keywordUrl[0:keywordUrl.index("&")] locationUrl = url[url.index("location=")+9:] - location = locationUrl[0:locationUrl.index("&") ] + location = locationUrl[0:locationUrl.index("&")] return [keyword, location] def writeResults(text: str) -> None: @@ -103,14 +104,14 @@ def writeResults(text: str) -> None: for line in file: if "----" not in line: lines.append(line) - + with open("data/" +fileName, 'w' ,encoding="utf-8") as f: f.write("---- Applied Jobs Data ---- created at: " +timeStr+ "\n" ) f.write("---- Number | Job Title | Company | Location | Work Place | Posted Date | Applications | Result " +"\n" ) - for line in lines: + for line in lines: f.write(line) f.write(text+ "\n") - + except Exception: with open("data/" +fileName, 'w', encoding="utf-8") as f: f.write("---- Applied Jobs Data ---- created at: " +timeStr+ "\n" ) @@ -313,11 +314,11 @@ def salary(self) -> str: case "$140,000+": salary = "f_SB2=6&" case "$160,000+": - salary = "f_SB2=7&" + salary = "f_SB2=7&" case "$180,000+": - salary = "f_SB2=8&" + salary = "f_SB2=8&" case "$200,000+": - salary = "f_SB2=9&" + salary = "f_SB2=9&" return salary def sortBy(self) -> str: @@ -326,5 +327,5 @@ def sortBy(self) -> str: case "Recent": sortBy = "sortBy=DD" case "Relevent": - sortBy = "sortBy=R" + sortBy = "sortBy=R" return sortBy