userscripts/linkedin-jobs-enhanced/LinkedInJobsEnhanced.user.js

225 lines
6.3 KiB
JavaScript
Raw Normal View History

2024-06-23 22:39:21 +00:00
// ==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 });
})();