If LinkedIn won't make it easy, I'll do it myself!
Computer putting man in distress: https://www.pexels.com/photo/man-showing-distress-3777572/

If LinkedIn won't make it easy, I'll do it myself!

(I'm going for "playfully scornful" and mean no disrespect to the LinkedIn folks. That said, how about making my changes (described below) a permanent part of the service?)


The problem

There are many jobs openings at LinkedIn for UX designers right now... at least I hope so because they have some work to do. For example, I've noticed (and previously written about) the frustration of seeing jobs that don't match my requirements of location, job title, and so on.

It's just odd that, even after paying for #linkedinpremium and getting these fancy "top applicant", and "in your network" listings, they do me next to no good because they're often out of state, not remote, or titles that I clearly don't want or qualify for.

Given there's no telling when (or if) those vacant UX positions will be filled and LinkedIn gets around to fixing this, I took it on myself to write a TamperMonkey script to improve the experience now.

(If you're not familiar with TamperMonkey, here's a short tutorial on installing and using it: (6) How to install and use Tampermonkey - YouTube)

Title remover

The first culprit are the various listings that show titles I most definitely don't qualify for (or don't want). For me, this includes "engineer", "director", and "principal". For LinkedIn to show me these is not a good use of my time or their electrons. I can't do anything about their electrons, but I can at least block it off my screen and save my brainpower:

No alt text provided for this image
Screenshot of LinkedIn showing bad-titles disabled

In this photo, you can see that non-applicable jobs are greyed out and minimized, though I eventually changed it so it highlight bad ones in red instead (so you can still see and click them if you're worried about my script getting too "excited".

Location remover

In my entire career, nothing has been more important to my job selection than where that job is physically located. As a family man, I can't just pack up and fly to Iceland on a whim. I'd rather not work somewhere more than 40 minutes away (The Cat's in the Cradle...)

Ergo, anything that's not either remote or in my current state is automatically out:

screenshot of my script removing jobs that are out of state
This is "hiring in my network". It's supposed to useful, but instead it's full of non-options.

The above view, "Hiring in my network" is supposed to be a useful way to find jobs related to connections that I have, but ends up being near useless thanks to listing almost exclusively jobs in states I no longer (or never) lived in. Cleaning up that view is a HUGE time saver.

After giving my script a few seconds to work through the duds, it reduced an entire page of results down to the four that might actually be relevant!

Pay Remover

I understand companies have a pay range they can afford, but the same goes for us. If they can't/won't pay enough for me to make my rent, it's not a good use of my time to see them:

No alt text provided for this image

Saving time and energy when there isn't much to go around

This is a rough market with tons of skilled amazing people competing for jobs. That means I spend day after day on LinkedIn looking for treasure, but having to sift through dross which is doing no one any favors.

With my script in place, I'm no longer have to worry about things that LinkedIn shouldn't be showing us in the first place!

The script

Here's the script which you can cut and paste into a "new script" in Tampermonkey. You'll need to edit the const values below (there are several near the top) to match the titles you don't want to see, the state you currently live in, pay limits, etc.

Danger!

The point of my script is to clear off as much obvious fluff as possible, but it might trigger on something you'd rather have seen. I did my best and it worked well for me, but LinkedIn could change something at any time so who knows what will happen? Regardless, it doesn't remove anything - just minimizes the "bad" ones. You can still click the job title to see the details if you want. Use at your discretion.

// ==UserScript==
// @name? ? ? ? ?Linked in fixer
// @namespace? ? https://tampermonkey.net/
// @version? ? ? 0.1
// @description? LinkedIn lacks very basic features that would help so I'm going to write them myself.
// @author? ? ? ?Jeremy Duffy
// @match? ? ? ? https://www.dhirubhai.net/*
// @icon? ? ? ? ?https://www.google.com/s2/favicons?sz=64&domain=glassdoor.com
// @grant? ? ? ? none
// @require https://code.jquery.com/jquery-3.6.0.min.js
// ==/UserScript==


(function() {
? ? 'use strict';


? ? const badTitles = ['engineer','principal','director']; // lowercase titles you wouldn't want. Erase or add as needed
? ? const myStateCode = 'wa'; // Your state two letter code - lower case
? ? const badCompanies = ['coinbase','talentify'];
? ? const minWageHr = "72"; // SET TO 0 TO DISABLE THIS CHECK! hourly wage min - if it's listed and less then this, it gets culled
? ? const minWageYr = "150000"; // SET TO 0 TO DISABLE THIS CHECK! if it's listed and less than this, it gets culled.
? ? const testOnly = false; // when true, bad entries are dimmed and outlined in red instead of removed. Helpful if you worry that the script isn't working properly and will remove the wrong things. Change to "false" if you want to remove the entries instead


? ? function noGood(toTest){


? ? ? ? var testTitle = '';
? ? ? ? var testComp = '';
? ? ? ? var i;


? ? ? ? // Mark this so we don't constantly rehash it.
? ? ? ? // EDIT: nevermind. Details are added dynamically and the script misses them if it was pre-tagged as "done" before that operation finished
? ? ? ? // With no effective way to tell if Linked in is done loading information, we have to redo all visible options every time :(
? ? ? ? //$(toTest).addClass('fixChecked');


? ? ? ? testTitle = $(toTest).find('.job-card-list__title').text().toLowerCase().trim();
? ? ? ? if (testOnly) console.log('checking '+testTitle+'\n');
? ? ? ? if (!testTitle.length) return 0;


? ? ? ? for (i=0; i< badTitles.length; i++){
? ? ? ? ? ? if (testTitle.indexOf(badTitles[i]) != -1) {
? ? ? ? ? ? ? ? dumpJob(toTest,'Blacklisted title '+badTitles[i]);
? ? ? ? ? ? ? ? return 1;
? ? ? ? ? ? }
? ? ? ? }




? ? ? ? testComp = $(toTest).find('.artdeco-entity-lockup__subtitle').text().toLowerCase().trim();
? ? ? ? for (i=0; i< badCompanies.length; i++)
? ? ? ? ? ? if (testComp.indexOf(badCompanies[i]) != -1) {
? ? ? ? ? ? ? ? dumpJob(toTest,'Blacklisted company '+badCompanies[i]);
? ? ? ? ? ? ? ? return 1;
? ? ? ? ? ? }




? ? ? ? // If survived title, check location


? ? ? ? var city = null;
? ? ? ? var state = null;
? ? ? ? var remote = null;
? ? ? ? var company = null;
? ? ? ? var cityState = null;


? ? ? ? $(toTest).find('.job-card-container__metadata-item').each(function() {
? ? ? ? ? ? var jobItem = this;


? ? ? ? ? ? // Only check for this once. If we already resolved it, other fields aren't relevant for location
? ? ? ? ? ? if (!remote && !city) {
? ? ? ? ? ? ? ? remote = $(jobItem).text().toLowerCase().trim().match(/\((remote)\)/m);
? ? ? ? ? ? ? ? // If it's remote, we're good. Only check state if it's not
? ? ? ? ? ? ? ? if (!remote) {
? ? ? ? ? ? ? ? ? ? cityState = $(jobItem).text().toLowerCase().trim().match(/^([a-z ]+), ([a-z]{2})$/m);
? ? ? ? ? ? ? ? ? ? if (!cityState) // one more format to try
? ? ? ? ? ? ? ? ? ? ? ? cityState = $(jobItem).text().toLowerCase().trim().match(/^([a-z ]+), ([a-z]{2})[^a-z]/m);
? ? ? ? ? ? ? ? ? ? //console.log(cityState);
? ? ? ? ? ? ? ? ? ? // We only test location if we recognized it's pattern. If we didn't, just leave it alone to be safe
? ? ? ? ? ? ? ? ? ? if (cityState) {
? ? ? ? ? ? ? ? ? ? ? ? city = cityState[1];
? ? ? ? ? ? ? ? ? ? ? ? state = cityState[2];
? ? ? ? ? ? ? ? ? ? ? ? if ((state && state != myStateCode)) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? dumpJob(toTest,'Not remote or in my state ('+myStateCode+' vs '+state+')');
? ? ? ? ? ? ? ? ? ? ? ? ? ? return 1;
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }


? ? ? ? ? ? if (minWageHr) {
? ? ? ? ? ? ? ? var thePay = 0;
? ? ? ? ? ? ? ? var payHr = $(jobItem).text().toLowerCase().trim().match(/\$(.+?)\/hr/g);
? ? ? ? ? ? ? ? if (payHr) {
? ? ? ? ? ? ? ? ? ? // Last matching number is the high range. If solo, it will just be the wage
? ? ? ? ? ? ? ? ? ? // Capture groups are ignored when using the global flag. so stupid...
? ? ? ? ? ? ? ? ? ? thePay = payHr.pop().replace(/[\$\/hr]/g,'');
? ? ? ? ? ? ? ? ? ? thePay = parseFloat(thePay);
? ? ? ? ? ? ? ? ? ? if (thePay < minWageHr) {
? ? ? ? ? ? ? ? ? ? ? ? dumpJob(toTest,"Hourly too low! Desired: "+minWageHr+" vs actual: "+thePay);
? ? ? ? ? ? ? ? ? ? ? ? return 1;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? if (minWageYr) {
? ? ? ? ? ? ? ? var payYr = $(jobItem).text().toLowerCase().trim().match(/\$(.+?k?)\/yr/g);
? ? ? ? ? ? ? ? var temp;
? ? ? ? ? ? ? ? if (payYr) {
? ? ? ? ? ? ? ? ? ? // Last matching number is the high range. If solo, it will just be the wage
? ? ? ? ? ? ? ? ? ? // Capture groups are ignored when using the global flag. so stupid...
? ? ? ? ? ? ? ? ? ? thePay = payYr.pop().replace(/[\$\/yr]/gi,'');
? ? ? ? ? ? ? ? ? ? if (thePay.indexOf('k')!=-1) {
? ? ? ? ? ? ? ? ? ? ? ? thePay = thePay.replace('k','');
? ? ? ? ? ? ? ? ? ? ? ? thePay = parseFloat(thePay)*1000;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? else
? ? ? ? ? ? ? ? ? ? ? ? thePay = parseFloat(thePay);
? ? ? ? ? ? ? ? ? ? console.log(thePay);
? ? ? ? ? ? ? ? ? ? if (thePay < minWageYr) {
? ? ? ? ? ? ? ? ? ? ? ? dumpJob(toTest,"Yearly too low! Desired: "+minWageYr+" vs actual: "+thePay);
? ? ? ? ? ? ? ? ? ? ? ? return 1;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? });


? ? ? ? return 0;
? ? }
? ? // In a function to more easily switch between testing and actual removing.
? ? function dumpJob(toDump,reason) {
? ? ? ? var title = $(toDump).find('.job-card-list__title').text().toLowerCase().trim();
? ? ? ? console.log('Dumping '+title+'\n::Reason: '+reason);
? ? ? ? if (testOnly) {
? ? ? ? ? ? // no sense in dumping it twice.
? ? ? ? ? ? if (!$(toDump).hasClass('ddisabled'))
? ? ? ? ? ? ? ? $(toDump).addClass('ddisabled').append('Removal reason: '+reason); // For testing - just make it smaller and hard to see
? ? ? ? }
? ? ? ? else {
? ? ? ? ? ? $(toDump).addClass('rremoved');
? ? ? ? ? ? var grabLink = $(toDump).find('.job-card-container__link');
? ? ? ? ? ? $(toDump).html('Dumping '+grabLink[0].outerHTML+'<br>Removal reason: '+reason); // For real, just get rid of it!
? ? ? ? }
? ? }


? ? $(document).ready(function(){


? ? ? ? $("body").append(`


? ? ? ? <style>


? ? ? ? .ddisabled {
? ? ? ? ? ? opacity: .5;
? ? ? ? ? ? border: 1px solid red;
? ? ? ? }
? ? ? ? .rremoved {
? ? ? ? ? ? font-size: 11px;
? ? ? ? ? ? padding: 10px !important;
? ? ? ? ? ? border: 1px solid rgba(8,8,8,.35);
? ? ? ? }
? ? ? ? </style>
? ? ? ? `);


? ? ? ? function fixLinkedIn(){
? ? ? ? ? ? //if (testOnly) console.log('fixing');
? ? ? ? ? ? $(document).find('.jobs-search-results__list-item').each(function(){
? ? ? ? ? ? ? ? // Check for my custom class so we're not thrashing on the same stuff we already checked
? ? ? ? ? ? ? ? if ($(this).hasClass('fixChecked')) return;
? ? ? ? ? ? ? ? noGood(this);
? ? ? ? ? ? });
? ? ? ? }


? ? ? ? var jobCatcher;
? ? ? ? var scrollT;
? ? ? ? jobCatcher = setInterval(function(){
? ? ? ? ? ? $('.jobs-search-results-list').each(function(){
				// We've already dealt with this one
				if ($(this).hasClass('scrollFixer')) return;
				if ($(document).find('.jobs-search-results__list-item').length) {
					//if (testOnly) console.log('attaching');
					// Do it for the first set of things on screen
? ? ? ? ? ? ? ? ? ? // but there's a race condition because there's no way to know when the dynamic data is loaded. ugh... who designs this way?
? ? ? ? ? ? ? ? ? ? setTimeout(function(){fixLinkedIn();},200);
					// Attach scroll event
					$('.jobs-search-results-list').on('scroll',function(){
						// Scroll is called mutliple times in a single scrolling event
						// Prevent it from overcalling the fixer by killing any pending calls
						clearTimeout(scrollT);
						// Set a new call. Eventually, if this is the last one and scroll isn't called again, it will launch the fixer function
						scrollT = setTimeout(function(){
? ? ? ? ? ? ? ? ? ? ? ? ? ? fixLinkedIn();
? ? ? ? ? ? ? ? ? ? ? ? ? ? if (!testOnly)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // The act of removing entries makes some others come into view that weren't there before.
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // Run it again to catch those.
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? setTimeout(function(){fixLinkedIn();},1000);


? ? ? ? ? ? ? ? ? ? ? ? },100);
					});
				}
				// Mark it as done
				$(this).addClass('scrollFixer');
? ? ? ? ? ? });
? ? ? ? },2000);
? ? });


})();

        
Angel Butler

Senior Contract Specialist @ SBA | Government Contracting Expert

1 年

Thanks for sharing! I will try this!

回复

要查看或添加评论,请登录

Jeremy Duffy的更多文章

  • Don't let YOU be the reason you fail.

    Don't let YOU be the reason you fail.

    Many years ago, I worked for someone so awful, it spurred me to silly (but necessary) coping mechanism just to get by…

    2 条评论
  • 80% Identity Theft Protection for Free

    80% Identity Theft Protection for Free

    (The following is based on US law and history only) Back in the early 2000's I was really worried about ID theft. I…

    7 条评论
  • Indeed Fixer for Job Hunters

    Indeed Fixer for Job Hunters

    Finding a new job is hard enough without the tools fighting you every step of the way. That's why I built my "LinkedIn…

  • When the obvious isn't obvious enough: prepping for an interview.

    When the obvious isn't obvious enough: prepping for an interview.

    We all face it. We've all experienced it.

    3 条评论
  • Next-level LinkedIn

    Next-level LinkedIn

    What LinkedIn Does As many recently have, I brushed off my LinkedIn profile and go to hunting about 3 months ago. Since…

    4 条评论

社区洞察

其他会员也浏览了