// ==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.');
})();