Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
hn job query search
function query() {
// HN is done with very unsemantic classes.
job_list ='.c5a,.cae,.c00,.c9c,.cdd,.c73,.c88')),
query_list =,
shown = 0, total = job_list.length;
// Traverses up the dom stack trying to find a match of a specific class
function up_to(node, klass) {
if (node.classList.contains(klass)) {
return node;
if(node === document.body) {
throw new Exception();
return up_to(node.parentNode, klass);
function display(node, what) {
up_to(node, 'athing').style.display = what;
// If we have a RegEx, return it
// Otherwise make it a case insensitive regex.
function destring(what) {
return what.test ? what : new RegExp(what.toString(), 'i');
// Hide all the postings
job_list.forEach(function(node) {
display(node, 'none');
query_list.forEach(function(query) {
if (query.forEach) {
var and_query_list =;
job_list.forEach(function(node) {
doesMatch = true,
toTest = node.innerHTML;
and_query_list.forEach(function(query) {
doesMatch &= > -1;
if(doesMatch) {
display(node, 'block');
shown ++;
} else {
query = destring(query);
job_list.forEach(function(node) {
if( > -1) {
display(node, 'block');
shown ++;
return {shown: shown, total: total}

nicnilov commented Aug 7, 2015

Nice 👍

jordsmi commented Aug 15, 2015

You are the man

Entry point? how does this know which url to search?


kristopolous commented Sep 1, 2015

On the hacker news comment thread site you need to run this script through your browser's debug console.

Go to the comment thread. Now Open up the "web console" for your browser ... usually by pressing "F12" or right clicking and going to "inspect element". Make sure you are on the "console" tab with the input field at the bottom. Copy and paste the script into there, press enter.

Then type in your query, like "query('javascript')" and press enter.

A number of posts should hide themselves, those that don't contain, in this case, the word 'javascript'.

Sweet thanks!

Version With More Powerful Syntax

(title added by original author - not a self-promotion by meiamsome)

I took your code and made some improvements (at least in my eyes):

I added some more functions, that make it so you can do more complex querys.
For example, you can do the following:
query(and('remote', or('python', 'ruby')))
to execute the query you used to do via
query(['remote', 'python'], ['remote', 'ruby'])

I also added xor and nand functionality, as well as logical inversion with not.
The functions can all be passed into each other, so if you want you can do something like:
query(and(not(or('python', 'php')), 'scala'))
If you want to find jobs that have scala but don't feature python or php.
Of course, it is more versatile than that as it can (at least, theoretically) allow any depth of search.

And, as an added bonus, Arrays still work like they used to and the query function defaults to or functionality, meaning that if you type a query in the old format it still functions in the same way.

You can find them at my gist here:

(If you want to copy that gist into this one that'd be great, I would do a pull request but they aren't yet available on gists, sadly)


kristopolous commented Sep 3, 2015

hey thanks - simply beautiful!

Think about using some markdown and give your comment an attractive title like "Updated version" or "Version with more powerful syntax"!

oh hey wait looks like I can do that

As far as replacing it with your implementation, I like the simplicity of my implementation for these purposes. If anything, I'd advocate for a far simpler, more basic and powerful system, not a more complex one. You've simplified some of it ... so a merging would be appropriate

VarunAgw commented Feb 1, 2016

I feel filter to match exact word will be nice. e.g. Searching for "intern" also match "international" which I don't want


kristopolous commented Mar 1, 2016

@VarunAgw I agree. There's two things: Case insensitivity is great except when it isn't. Perhaps I want "CA" and not "ca" and second is your thing. Probably the best way to do this would be to support RegExp along with string matching. So you could do query(/intern[^\w]/) since
/intern[^\w]/.test('international') => false and /intern[^\w]/.test('intern.') => true ...

edit: ok, done. Classic string matching is of course, still supported. easy things easy, hard things possible.

So a los angeles search now could look like

query(/LA[^\w]/, 'los angeles')

If I could get the matches stylized (say, in bold) in under 2 lines of complexity I'll do it. Otherwise I'm drifting away from KISS. Let me think...

edit on that: it's not styling it that's the challenge, it's unstyling it for a subsequent search. That sounds like a kind of cleverness that comes with bugs.

jeff303 commented May 26, 2016

Just a simple tweak, but calling with zero args could reset the view (i.e. remove filters). Add this just before query_list.forEach...

  if (query_list.length === 0) {
    // no query passed; show everything
    job_list.forEach(function(node) {
      display(node, 'block');
    return {shown: total, total: total};

kristopolous commented Jun 1, 2016

oh hey thanks ... I'll fix that soon!

I am seeing this with Jul 2016 - Who is hiring on FF and Chrome. Never happened earlier:-
query(['python', 'remote', 'open', 'source'])
VM51:14 Uncaught ReferenceError: Exception is not defined(…)

the class of TRs is now "athing comtr" so if (node.className === klass) { won't work any more.

This works in chrome:

if (node.classList.contains(klass)) {

sadokx commented Jul 14, 2016

SamHasler's answer works now. Can you update kristopolus? Thanks! :)


kristopolous commented Aug 1, 2016

OK just saw this I'm on mobile. I'll get to it today


kristopolous commented Aug 1, 2016

there you go. It's insanity that there's a specialized function for string tokenization and matching that is specific to the contents of the classname attribute on dom nodes. of course you could just use regular string functions ... great, so why the hell does this thing exist? there's not a special function for strings that contain the letter 'q' or are exactly 11 characters long ... bah, nonsense.

anyway, it's fixed.

@kristopolous thanks for fixing this... use this script every few months... great work :)

jeff303 commented Aug 3, 2016

It seems like it might be nice to have an option to only search the first line for certain terms (ex: REMOTE), since usually occurrences in other lines or subsequent paragraphs aren't actually what you're after. Does anyone else agree? If so, I could try to take a crack at that.


kristopolous commented Aug 16, 2016

@jeff303 "line" is an amorphous term. I'm not trying to be a jerk, but the computer is and always is - that jerk. Anyway, without a more solid definition (as "line" depends on font size, monitor size etc), I've got no hope here and I want to make it useful for you.

I could do something like "Of the first 50 space-separated tokens (aka, words)" or "the first paragraph that contains more than 40 characters" or something.

The nuances here is that there's essentially the following formats:










So there has to be a flexible "do what I mean, not what I say" style definition that can accommodate these various degrees of freedom. It's far too easy to have a solution that is very complex, fragile, and useless (which I know is very trendy these days, but I'm not a trendy guy like that).

What would be most useful to you?

ThomasRooney commented Sep 6, 2016

A slight divergence, but I saw this and was inspired to squeeze it into a single (ish) console command. Relies on curl, jq, pup. Further output can then be pretty easily filtered by jq or just grep'd. Outputs a big array of the top level comments of the most recent Who's hiring thread.

curl\?id\=whoishiring |
  pup 'td.title a json{}' | jq '.[] | select(.text | contains("Who is hiring")) | .href' |
  head -n 1 |
  xargs -I{} curl\{\} |
  pup '.comtr json{}' |
  jq '.[] | select(.. | select(select(.class?=="ind") | .children[0].width == "0")) |
            select(.class="comment") |
            map(..| if .tag? != "font" then .text? else null end) | map(select(. != null)) |

EDIT: To filter, pipe the output somewhere then do something like this cat results | jq 'select(.[2]? | contains("London"))'

jeff303 commented Sep 7, 2016

@kristopolous, I'm absolutely aware of the complexity in what seems like a simple thing on the surface. It was more of just a brainstorming idea (mostly driven by my own desire to brush up on Javascript), and not an urgent request for that particular functionality.

Attempting to parse the different sections based on commonly seen formats is pretty interesting, but would be fragile as you point out. I think, at a minimum, attempting to look in only the first "paragraph" (the breaks between which actually do correspond to new <p> elements) might be a start. And also, only looking at top-level posts would be useful, to filter out "Would you consider remote candidates?" type replies, where that word didn't occur in the parent posting.

gotoc commented Jun 2, 2017

place, job, terms, description... here is a crazy idea, why not have a form field, with required fields for remote/onsite.
ie, it would have to be enforced as part of hn netiquette or just plain common sense. :)

just a thought. if they want, the right people to apply, and not get flooded, with mismatched applicants,
then at the least, they should be specific about these basic items, right?

btw, @kristopolous, thanks for script, its a way better than seeing the page raw.

frosas commented Nov 3, 2017

Thanks @kristopolous and @meiamsome, browser search functionality is definitely not enough to search hundreds of job positions!

Because I wanted a mix of both scripts (i.e. nested criterias AND regular expressions being first-class objects), and because it was fun to write, I ended up creating just another version which looks like this:

// Non-Angular Javascript contract positions in London or remote
  hn.or(/(javascript|typescript)/i, /ES\d/, 'JS'),
  hn.or(hn.and('ONSITE', /london/i), 'REMOTE')

Details at

Ivanca commented Dec 2, 2017

This script loads all pages via AJAX; you may execute it before this one so you search on all pages instead of just first one

;(function ajaxLoadNextPage () {
    var more = document.querySelector('.comment-tree > tbody > tr:last-child a');
    if (more && more.innerHTML === "More") {    
        var httpRequest = new XMLHttpRequest();
        httpRequest.onreadystatechange = function () {
            if (httpRequest.readyState === XMLHttpRequest.DONE) {
                if (httpRequest.status === 200) {
                  var div = document.createElement('div');
                  div.innerHTML = httpRequest.responseText;
                  var nextHTML = div.querySelector('.comment-tree > tbody').innerHTML;
                  document.querySelector('.comment-tree > tbody').innerHTML += nextHTML;
                } else {
                  alert('There was a problem with the request to ' + more.href);
        };'GET', more.href);

BTW I'm looking for a job, developer with more than 10+ years of JavaScript & PHP experience; ping me at ivanca [ at ] gmail

janklimo commented Dec 2, 2017

Any plans to package this as an extension?

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