您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Highlights fresh job postings on Indeed.com based on user-specified days. Optimized for Brave browser and logged-in users.
// ==UserScript== // @name Indeed Fresh Job Highlighter with User Input (Brave-Optimized) // @namespace http://tampermonkey.net/ // @version 4.1 // @description Highlights fresh job postings on Indeed.com based on user-specified days. Optimized for Brave browser and logged-in users. // @author Grok // @match https://*.indeed.com/* // @grant none // @run-at document-idle // @license PNDT // ==/UserScript== (function() { 'use strict'; let currentMaxDays = localStorage.getItem('indeedFreshMaxDays') ? parseInt(localStorage.getItem('indeedFreshMaxDays'), 10) : 1; let isInitialized = false; // Function to parse days from date text function getDaysFromDateText(dateText) { if (!dateText) return Infinity; const normalizedText = dateText.toLowerCase().trim(); if (normalizedText.includes('today') || normalizedText.includes('just posted') || normalizedText.includes('new') || normalizedText.includes('active today')) { return 0; } const match = normalizedText.match(/(\d+)\s*(days?|hrs?|hours?)\s*(ago)?/i); if (match) { const num = parseInt(match[1], 10); if (match[2].toLowerCase().startsWith('h') || normalizedText.includes('active')) { return 0; } return num; } if (normalizedText.includes('30+') || normalizedText.includes('<30')) { return normalizedText.includes('30+') ? Infinity : 29; } const postedMatch = normalizedText.match(/posted\s+(\d+)\s+days?\s+ago/i); return postedMatch ? parseInt(postedMatch[1], 10) : Infinity; } // Function to check if a job is fresh function isFreshJob(dateText, maxDays) { const days = getDaysFromDateText(dateText); return days <= maxDays && days !== Infinity; } // Function to clear previous highlights function clearHighlights() { const selectors = [ '[data-jk]', '.jobsearch-SerpJobCard', '.job_seen_beacon', 'div[role="article"]', '.css-1cp6a5z', '.jobCard_main', '.slider_container [data-jk]' ].join(', '); const jobCards = document.querySelectorAll(selectors); jobCards.forEach(card => { card.style.border = ''; card.style.backgroundColor = ''; card.style.padding = ''; card.style.marginBottom = ''; card.style.borderRadius = ''; const freshLabel = card.querySelector('span.fresh-label'); if (freshLabel) freshLabel.remove(); }); console.log('Highlights cleared. Processed:', jobCards.length, 'elements.'); } // Function to highlight fresh jobs function highlightFreshJobs(maxDays) { clearHighlights(); currentMaxDays = maxDays; localStorage.setItem('indeedFreshMaxDays', maxDays); // Persist user preference const selectors = [ '[data-jk]', '.jobsearch-SerpJobCard', '.job_seen_beacon', 'div[role="article"]', '.css-1cp6a5z', '.jobCard_main', '.slider_container [data-jk]' ].join(', '); let jobCards = document.querySelectorAll(selectors); // Narrowed fallback selector to reduce false positives if (jobCards.length === 0) { jobCards = document.querySelectorAll('div:has(a[href*="/viewjob"][data-jk])'); console.warn('Using fallback selectors. Found:', jobCards.length, 'potential cards.'); } console.log(`Found ${jobCards.length} potential job cards.`); let highlightedCount = 0; jobCards.forEach((card, index) => { let dateElement = card.querySelector('.date, time, [datetime], span[aria-label*="posted"], .css-1saizt3, .jobsearch-JobBeacon span:last-child, .postedTime'); if (!dateElement) { const allSpans = card.querySelectorAll('span, time, div'); for (let elem of allSpans) { const text = elem.textContent || elem.getAttribute('aria-label') || ''; if (getDaysFromDateText(text) < Infinity) { dateElement = elem; console.log(`Fallback date found in card ${index}:`, text); break; } } } if (dateElement) { const dateText = dateElement.textContent.trim() || dateElement.getAttribute('aria-label') || ''; console.log(`Card ${index} date text: "${dateText}" -> ${getDaysFromDateText(dateText)} days`); if (isFreshJob(dateText, maxDays)) { card.style.border = '2px solid #00cc00'; card.style.backgroundColor = '#e6ffe6'; card.style.padding = '5px'; card.style.marginBottom = '10px'; card.style.borderRadius = '5px'; let titleElement = card.querySelector('h2 a, .title a, .jobTitle a, h2, .title, .jobTitle'); if (!titleElement) { titleElement = card.querySelector('a[href*="/viewjob"]') || card.querySelector('h2'); } if (titleElement && !titleElement.querySelector('span.fresh-label')) { const freshLabel = document.createElement('span'); const daysAgo = getDaysFromDateText(dateText); freshLabel.textContent = ` Fresh! (${daysAgo === 0 ? 'Today' : daysAgo + ' days ago'})`; freshLabel.style.cssText = ` color: #00cc00; font-weight: bold; margin-left: 10px; background-color: #ccffcc; padding: 2px 5px; border-radius: 3px; `; freshLabel.className = 'fresh-label'; freshLabel.setAttribute('aria-live', 'polite'); // Accessibility titleElement.appendChild(freshLabel); highlightedCount++; } } } else { console.log(`No date element found in card ${index}.`); } }); const statusElement = document.getElementById('fresh-status'); if (statusElement) { if (jobCards.length === 0) { statusElement.textContent = 'No job listings found. Perform a search or disable Brave Shields.'; statusElement.style.color = '#ff0000'; } else { statusElement.textContent = `Highlighted ${highlightedCount} fresh jobs (≤ ${maxDays} days) out of ${jobCards.length}.`; statusElement.style.color = '#00cc00'; } } } // Create control panel function createControlPanel() { if (document.getElementById('fresh-panel')) return; const panel = document.createElement('div'); panel.id = 'fresh-panel'; panel.setAttribute('role', 'region'); panel.setAttribute('aria-label', 'Fresh Job Highlighter Control Panel'); panel.style.cssText = ` position: fixed; top: 10px; right: 10px; background: #fff; border: 1px solid #ccc; padding: 10px; z-index: 10000; box-shadow: 0 2px 10px rgba(0,0,0,0.2); border-radius: 5px; font-family: Arial, sans-serif; font-size: 12px; max-width: 250px; min-width: 200px; `; const title = document.createElement('div'); title.textContent = 'Fresh Job Highlighter (Brave)'; title.style.cssText = 'font-weight: bold; margin-bottom: 5px; color: #333; font-size: 13px;'; const instructions = document.createElement('div'); instructions.textContent = '1. Search for jobs. 2. Enter days. 3. Click Apply.'; instructions.style.cssText = 'font-size: 10px; color: #666; margin-bottom: 5px;'; const label = document.createElement('label'); label.setAttribute('for', 'days-input'); label.textContent = 'Jobs within (days): '; label.style.cssText = 'display: block; margin-bottom: 5px;'; const input = document.createElement('input'); input.type = 'number'; input.min = '0'; input.value = currentMaxDays.toString(); input.id = 'days-input'; input.setAttribute('aria-label', 'Number of days for fresh jobs'); input.style.cssText = 'width: 50px; margin-bottom: 5px;'; const applyButton = document.createElement('button'); applyButton.textContent = 'Apply Filter'; applyButton.setAttribute('aria-label', 'Apply fresh job filter'); applyButton.style.cssText = ` background: #00cc00; color: #fff; border: none; padding: 5px 10px; cursor: pointer; border-radius: 3px; width: 100%; margin-bottom: 5px; `; const clearButton = document.createElement('button'); clearButton.textContent = 'Clear Highlights'; clearButton.setAttribute('aria-label', 'Clear job highlights'); clearButton.style.cssText = ` background: #ff6666; color: #fff; border: none; padding: 5px 10px; cursor: pointer; border-radius: 3px; width: 100%; margin-bottom: 5px; `; const statusElement = document.createElement('div'); statusElement.id = 'fresh-status'; statusElement.setAttribute('aria-live', 'polite'); statusElement.textContent = 'Ready. Search for jobs to highlight.'; statusElement.style.cssText = 'margin-top: 5px; font-size: 11px; color: #666;'; // Event listeners applyButton.addEventListener('click', () => { const maxDays = parseInt(input.value, 10); if (isNaN(maxDays) || maxDays < 0 || input.value.trim() === '') { alert('Please enter a valid number of days (0 or greater).'); input.value = currentMaxDays; // Reset to last valid value return; } highlightFreshJobs(maxDays); }); clearButton.addEventListener('click', () => { clearHighlights(); statusElement.textContent = 'Highlights cleared.'; statusElement.style.color = '#666'; }); // Append elements panel.appendChild(title); panel.appendChild(instructions); panel.appendChild(label); panel.appendChild(input); panel.appendChild(applyButton); panel.appendChild(clearButton); panel.appendChild(statusElement); document.body.appendChild(panel); console.log('Fresh Job Highlighter panel created.'); } // Wait for job listings function waitForJobListings(callback, maxWait = 30000) { let waited = 0; const checkInterval = setInterval(() => { waited += 1000; const hasJobs = document.querySelectorAll('[data-jk], .jobsearch-SerpJobCard').length > 0; const isSearchPage = window.location.search.includes('q=') || document.querySelector('.jobsearch-SerpJobCard'); if (hasJobs || waited >= maxWait) { clearInterval(checkInterval); if (hasJobs) { console.log('Job listings detected. Initializing...'); callback(); } else { console.warn('Timeout: No jobs found. Try disabling Brave Shields or performing a search.'); } } }, 1000); } // Initialize if (!isInitialized) { isInitialized = true; waitForJobListings(() => { createControlPanel(); setTimeout(() => highlightFreshJobs(currentMaxDays), 1000); }, 30000); // Reduced timeout to 30s for faster feedback if (window.location.search.includes('q=')) { setTimeout(() => { createControlPanel(); highlightFreshJobs(currentMaxDays); }, 2000); } } // MutationObserver for dynamic content const observer = new MutationObserver((mutations) => { let shouldRehighlight = false; mutations.forEach((mutation) => { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { const hasNewJobs = Array.from(mutation.addedNodes).some(node => node.nodeType === 1 && ( (node.matches && node.matches('[data-jk], .jobsearch-SerpJobCard')) || (node.querySelector && node.querySelector('[data-jk], .jobsearch-SerpJobCard')) ) ); if (hasNewJobs) shouldRehighlight = true; } }); if (shouldRehighlight) { console.log('New jobs detected. Rehighlighting...'); setTimeout(() => { const input = document.getElementById('days-input'); const maxDays = input ? parseInt(input.value, 10) : currentMaxDays; highlightFreshJobs(maxDays); }, 1000); } }); // Observe specific containers to reduce performance impact const containers = document.querySelectorAll('#mosaic-provider-jobcards, #resultsCol, .jobsearch-ResultsList, .slider_container'); if (containers.length > 0) { containers.forEach(container => { observer.observe(container, { childList: true, subtree: true }); }); } else { observer.observe(document.body, { childList: true, subtree: true }); } console.log('Indeed Fresh Job Highlighter v4.1 loaded.'); })();