From d028eab15acfbe58298cbac48b3752b4deefcd7c Mon Sep 17 00:00:00 2001 From: Sangeeth Sudheer Date: Mon, 24 Jun 2024 04:09:21 +0530 Subject: [PATCH] Add LinkedIn Jobs Enhanced script --- leetray/LeetRay.user.js | 2 +- .../LinkedInJobsEnhanced.user.js | 225 ++++++++++++++++++ 2 files changed, 226 insertions(+), 1 deletion(-) create mode 100644 linkedin-jobs-enhanced/LinkedInJobsEnhanced.user.js diff --git a/leetray/LeetRay.user.js b/leetray/LeetRay.user.js index d100154..77bf7f4 100644 --- a/leetray/LeetRay.user.js +++ b/leetray/LeetRay.user.js @@ -4,7 +4,7 @@ // @version 2023-01-05T01:00:39+05:30 // @description Take beautiful screenshots of your code in Leetcode instantly with Ray.so // @updateURL https://git.sangeeth.dev/x/userscripts/raw/branch/main/leetray/LeetRay.user.js -// @downloadURL https://git.sangeeth.dev/x/userscripts/raw/branch/main/leetray/LeetRay.user.js +// @downloadURL https://git.sangeeth.dev/x/userscripts/raw/branch/main/leetray/LeetRay.user.js // @author Sangeeth Sudheer // @match https://leetcode.com/problems/* // @match https://www.leetcode.com/problems/* diff --git a/linkedin-jobs-enhanced/LinkedInJobsEnhanced.user.js b/linkedin-jobs-enhanced/LinkedInJobsEnhanced.user.js new file mode 100644 index 0000000..b132830 --- /dev/null +++ b/linkedin-jobs-enhanced/LinkedInJobsEnhanced.user.js @@ -0,0 +1,225 @@ +// ==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 }); +})(); \ No newline at end of file