225 lines
6.3 KiB
JavaScript
225 lines
6.3 KiB
JavaScript
|
// ==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 });
|
||
|
})();
|