Skip to content

Instantly share code, notes, and snippets.

@kristopolous
Last active July 24, 2023 04:12
Show Gist options
  • Save kristopolous/19260ae54967c2219da8 to your computer and use it in GitHub Desktop.
Save kristopolous/19260ae54967c2219da8 to your computer and use it in GitHub Desktop.
hn job query search
// Usage:
// Copy and paste all of this into a debug console window of the "Who is Hiring?" comment thread
// then use as follows:
//
// query(term | [term, term, ...], term | [term, term, ...], ...)
//
// When arguments are in an array then that means an "or" and when they are seperate that means "and"
//
// Term is of the format:
// ((-)text/RegExp) ( '-' means negation )
//
// A first argument of '+' signifies an additional pass on the filtered data as opposed to
// resetting everything.
//
// Example: Let's look for jobs in california that involve rust or python and not crypto:
//
// > query('ca', '-crypto', ['rust', 'python']);
// {filtered: '98.57%', query: 'ca AND NOT crypto AND (rust OR python)'}
//
// Then you see, "oh right, I don't care about blockchain either":
//
// > query('+', '-blockchain');
// {filtered: '98.57%', query: 'ca AND NOT crypto AND (rust OR python) AND NOT blockchain'}
//
// Another example:
// > query(['ca', 'sf', 'san jose', 'mountan view'])
// {filtered: '90.61%', query: '(ca OR sf OR san jose OR mountan view)'}
//
// COVID killed Silicon Valley. Quod Erat Demonstrandum!
//
// Changelog for 2022-08-02
//
// ADDED
//
// * Negation via '-'
//
// * Multi-pass querying via first argument being '+'
//
// * Debugging query string added in the response
//
// CHANGED
//
// * "or" and "and" works the opposite of how it did previously.
// This form seems to be more useful.
//
// * Whole word matching is default
//
// * Terms such as "c++" are properly escaped
//
// UPDATED
//
// * Rewrote as an absurd implementation.
// I had a fun afternoon writing this.
//
function query(...queryList) {
// HN is done with very unsemantic classes.
let jobList = [...document.querySelectorAll('.c5a,.cae,.c00,.c9c,.cdd,.c73,.c88')],
// Traverses up the dom stack trying to find a match of a specific class
upto = (node, klass) => node.classList.contains(klass) ? node : upto(node.parentNode, klass),
display = (node, what) => upto(node, 'athing').style.display = what,
hide = node => { display(node, 'none'); node.show = false},
show = node => { display(node, 'block'); node.show = true},
// Use RegExp as is. Otherwise make it a case insensitive RegExp
destring = what => [
what[0] === '-',
what.test ? what : new RegExp([
'\\b',
what.toString()
.replace(/^-/,'')
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'),
'\\b'
].join(''), 'i'), what
];
// This is our grand reset
if(queryList[0] !== '+') {
jobList.forEach(show);
// Have fun with that.
query.hidden = +!( query.fn = [] );
} else {
queryList.shift();
}
// The AND is an artifact of the design. It's just iterative napped subsets
query.fn = query.fn.concat(queryList.map(arg => {
// Make it an array if it isn't one and pass it through our destring
let orList = Array.of(arg).flat().map(destring);
// If we're showing the job, then go through the list of terms
// If all of them do not match, hide it, then return the length.
query.hidden += jobList.filter(node => node.show
&& orList.every(([neg, r]) => neg ^ !(node.innerHTML.search(r) + 1))
).map(hide).length;
// You're on your own here - this is just the construction of
// the debug string. There's far more reasonable ways to do this
// But what fun would that be?!
return (
' ('[+!!(orList.length - 1)] +
orList.map(([neg, ig, r]) => ['', 'NOT '][+neg] + r.slice(+neg)).join(' OR ') +
' )'[+!!(orList.length - 1)]
).trim();
}));
return {
filtered: (100 * query.hidden / jobList.length).toFixed(2) + '%',
query: query.fn.join(' AND ')
};
}
@kristopolous
Copy link
Author

Damn it

I'm still in bed. I'll look when I'm at my office

@kristopolous
Copy link
Author

kristopolous commented Sep 1, 2022

You're right. I was so careful in this. damn it. That's extremely disappointing. I apparently foolishly introduced the bug here when I was just using a phone and their textbox interface: https://gist.github.com/kristopolous/19260ae54967c2219da8/revisions#diff-63e9a5e5dead19d4e7a3ee13c24221089b165a04e534e6e675d491e9422576d1

Fixed. Thanks for the report @nemanjam

@nemanjam
Copy link

nemanjam commented Sep 1, 2022

Thank you.

@gabrielsroka
Copy link

Updated pagination code using fetch and async/await, and while instead of recursion. Forked from @Ivanca

It'd be nice to merge this into query().

(async function () {
    var more;
    while (more = document.querySelector('a.morelink')) {
        const r = await fetch(more.href);
        more.remove();
        const div = document.createElement('div');
        div.innerHTML = await r.text();
        document.querySelector('.comment-tree > tbody').innerHTML += div.querySelector('.comment-tree > tbody').innerHTML;
    }
})();

Also, maybe a bookmarklet? You can drag/drop or copy/paste to your boomarks toolbar. eg:

javascript:
/* /Say hello# */
(function () {
  alert('Hello, HN');
})();

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment