// ==UserScript== // @name LinkedIn Jobs Enhanced ✨ // @namespace http://tampermonkey.net/ // @version 2024-06-24T04:08:56+05:30 // @description Trying to fix job filters without being paid by LinkedIn :') // @updateURL https://git.sangeeth.dev/x/userscripts/raw/branch/main/linkedin-jobs-enhanced/LinkedInJobsEnhanced.user.js // @downloadURL https://git.sangeeth.dev/x/userscripts/raw/branch/main/linkedin-jobs-enhanced/LinkedInJobsEnhanced.user.js // @author Sangeeth Sudheer // @match https://www.linkedin.com/jobs/search* // @match https://www.linkedin.com/jobs/collections* // @icon https://www.google.com/s2/favicons?sz=64&domain=linkedin.com // @grant GM_getValue // @grant GM_setValue // ==/UserScript== /** ** UTILITIES **/ function stringEnum(values) { return new Proxy( new Set(values), { get(target, prop) { if (!target.has(prop)) { throw Error(`${prop} is not a valid key!`); } return prop; } } ); }; const Logger = { prefix: "linkedin-jobs-enhanced", debug(...args) { if (Storage.get(Storage.keys.debug)) { console.log(`[${this.prefix}]`, ...args); } }, error(...args) { console.error(`[${this.prefix}]`, ...args); } }; const Storage = { _allKeys: stringEnum(["disliked", "liked", "debug", "mode"]), init() { if (!Array.isArray(this.get(this.keys.disliked))) { this.set(this.keys.disliked, []); } if (!Array.isArray(this.get(this.keys.liked))) { this.set(this.keys.liked, []); } if (!this.get(this.keys.mode)) { this.set(this.keys.mode, "hide-disliked"); } if (typeof this.get(this.keys.debug) !== 'boolean') { this.set(this.keys.debug, false); } Logger.debug(`Initialized storage`); }, get keys() { return this._allKeys; }, get(key) { return GM_getValue(key); }, set(key, value) { return GM_setValue(key, value); } }; const UI = { alert(msg) { alert(msg); } } ;(function() { 'use strict'; function buildListPatterns() { const mode = Storage.get(Storage.keys.mode); let disliked = Storage.get(Storage.keys.disliked); let liked = Storage.get(Storage.keys.liked); const trimSpaces = value => value.trim(); const isValid = value => value && value.trim(); disliked = disliked.filter(isValid).map(trimSpaces); liked = liked.filter(isValid).map(trimSpaces); if (mode === "show-liked" && liked.length === 0) { UI.alert("No valid liked companies set, check tampermonkey Storage tab"); return [null, null]; } if (disliked.length === 0) { UI.alert("No valid companies set, check tampermonkey Storage tab"); return [null, null]; } if (mode === "hide-disliked") { return [ new RegExp("(" + disliked.join("|") + ")", "i"), null ]; } if (mode === "show-liked") { return [ null, new RegExp("(" + liked.join("|") + ")", "i") ]; } throw new Error(`Unknown mode: ${mode} passed, check tampermonkey Storage tab`); } function applyJobListItemVisibility(node) { const [HIDE_DISLIKED_PATTERN, SHOW_LIKED_PATTERN] = buildListPatterns(); const mode = Storage.get(Storage.keys.mode); const companyNameEl = node.querySelector(".artdeco-entity-lockup__subtitle"); if (!companyNameEl) { Logger.debug("Skipping node because company name element not found", node); return; } const companyName = companyNameEl.textContent.trim(); const matches = mode === "hide-disliked" ? HIDE_DISLIKED_PATTERN.exec(companyName) : SHOW_LIKED_PATTERN.exec(companyName); if ((mode === "hide-disliked" && matches) || (mode === "show-liked" && !matches)) { node.style.display = 'none'; if (mode === "hide-disliked") { Logger.debug(`Hid node because found a hide-disliked match: "${matches[1]}"`, node); } else { Logger.debug(`Hid node because ${companyName} is not one of the liked companies`); } } else if (node.style.display === 'none') { delete node.style.display; Logger.debug("Node didn't match but was hidden already", node); } } function isJobListItem(node) { return node.nodeType === Node.ELEMENT_NODE && node.classList.contains("jobs-search-results__list-item"); } function handleMutations(records) { // TODO: Super crude, probably can be optimized but works fine-ish let hasChange = false; outer: for (const record of records) { for (const node of record.addedNodes) { if (isJobListItem(node)) { hasChange = true; break outer; } } if (isJobListItem(record.target)) { hasChange = true; break; } } if (!hasChange) { Logger.debug("No relevant mutations found, skipping"); return; } const jobListItems = document.querySelectorAll(".jobs-search-results__list-item"); for (const item of jobListItems) { applyJobListItemVisibility(item); } } function areWeOk() { const mode = Storage.get(Storage.keys.mode); const [HIDE_DISLIKED_PATTERN, SHOW_LIKED_PATTERN] = buildListPatterns(); if ((mode === "hide-disliked" && !HIDE_DISLIKED_PATTERN) || (mode === "show-liked" && !SHOW_LIKED_PATTERN)) { Logger.debug("Exiting since no patterns set"); return; } return true; } Storage.init(); const mode = Storage.get(Storage.keys.mode); Logger.debug(`Current mode is ${mode}`); if (!areWeOk()) { Logger.debug("Not okay! Quitting"); return; } const observer = new MutationObserver(handleMutations); observer.observe(document.body, { childList: true, subtree: true }); })();