{"id":17997041,"url":"https://github.com/jstrieb/hackernews-button","last_synced_at":"2026-06-16T21:01:12.931Z","repository":{"id":37238411,"uuid":"283341030","full_name":"jstrieb/hackernews-button","owner":"jstrieb","description":"Privacy-preserving Firefox extension linking to Hacker News discussion; built with Bloom filters and WebAssembly","archived":false,"fork":false,"pushed_at":"2026-05-17T17:17:51.000Z","size":255,"stargazers_count":93,"open_issues_count":3,"forks_count":1,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-05-17T19:55:25.848Z","etag":null,"topics":["algolia-search","bloom-filter","bloom-filters","browser-extension","firefox-addon","firefox-extension","github-actions","hacker-news","hackernews","hn","privacy"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jstrieb.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-07-28T22:25:22.000Z","updated_at":"2026-02-19T01:06:32.000Z","dependencies_parsed_at":"2026-04-19T19:01:24.161Z","dependency_job_id":null,"html_url":"https://github.com/jstrieb/hackernews-button","commit_stats":null,"previous_names":[],"tags_count":1516,"template":false,"template_full_name":null,"purl":"pkg:github/jstrieb/hackernews-button","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jstrieb%2Fhackernews-button","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jstrieb%2Fhackernews-button/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jstrieb%2Fhackernews-button/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jstrieb%2Fhackernews-button/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jstrieb","download_url":"https://codeload.github.com/jstrieb/hackernews-button/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jstrieb%2Fhackernews-button/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33705207,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-30T02:00:06.278Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["algolia-search","bloom-filter","bloom-filters","browser-extension","firefox-addon","firefox-extension","github-actions","hacker-news","hackernews","hn","privacy"],"created_at":"2024-10-29T21:16:45.734Z","updated_at":"2026-05-30T19:00:51.884Z","avatar_url":"https://github.com/jstrieb.png","language":"C","funding_links":[],"categories":["privacy"],"sub_categories":[],"readme":"# Hacker News Discussion Button\n\nFirefox extension that links to the [Hacker News](https://news.ycombinator.com)\ndiscussion for the current page and preserves privacy with Bloom filters.\n\n\u003cdiv align=\"center\"\u003e\n\u003ca href=\"https://github.com/jstrieb/hackernews-button/releases/latest/download/hackernews-button.xpi\"\u003e\n\u003cimg alt=\"Screenshot\" width=\"400\" src=\"https://github.com/jstrieb/hackernews-button/blob/master/doc/screenshot.png\" /\u003e\n\u003c/a\u003e\n\u003c/div\u003e\n\n\n\n# Quick start\n\nInstall the browser extension from one of the following sources:\n- [Download from the Mozilla Add-on website](https://addons.mozilla.org/en-US/firefox/addon/hacker-news-discussion-button/)\n- [Download from GitHub](https://github.com/jstrieb/hackernews-button/releases/latest/download/hackernews-button.xpi)\n\n---\n\nThe extension will light up bright orange when the current page has previously\nbeen posted to Hacker News.\n- Clicking the extension will open the Hacker News discussion.\n- Clicking the extension with the scroll wheel will open the discussion in a\n  new tab.\n- Clicking while holding \u003ckbd\u003eCtrl\u003c/kbd\u003e or \u003ckbd\u003eShift\u003c/kbd\u003e will open the\n  discussion in a new tab or window, respectively.\n\nThere are also keyboard shortcuts.\n- \u003ckbd\u003eAlt\u003c/kbd\u003e + \u003ckbd\u003eY\u003c/kbd\u003e opens the Hacker News discussion in the current\n  page\n- \u003ckbd\u003eCtrl\u003c/kbd\u003e + \u003ckbd\u003eShift\u003c/kbd\u003e + \u003ckbd\u003eY\u003c/kbd\u003e opens the discussion in a\n  new tab.\n\nStar this project if you like it!\n\nRead the [Hacker News Discussion](https://news.ycombinator.com/item?id=26301600)\nfor this project.\n\n\n\n# How It Works\n\nWhen you visit a website, this browser extension determines whether the website\nhas been submitted to Hacker News. A naive (but effective) way to do this is to\nquery the very helpful [Algolia Search API for Hacker\nNews](https://hn.algolia.com/api) with every page visited. In fact, that's what\nthe original version of this extension did when I wrote it over the summer of\n2020! Unfortunately, there are two problems with this naive approach: you\nreveal every website you visit to Algolia, and you waste bandwidth and energy\nsending and receiving extraneous API requests.\n\nTo solve this problem, this extension uses a data structure called a [Bloom\nfilter](https://en.wikipedia.org/wiki/Bloom_filter) to protect your privacy.\nBloom filters can be thought of as a super condensed representation of the\nfingerprints of a long list of URLs. In this way, you can download the Bloom\nfilter once (with periodic updates), and check if it contains the current\nwebsite's URL fingerprint without making any requests over the Internet.\n\n\u003cdetails\u003e\n\n\u003csummary\u003eClick to read Bloom filter parameter details\u003c/summary\u003e\n\nBloom filters are probabilistic data structures, which means that when you\nquery whether a string is in the set represented by the Bloom filter, the\nresponse from the data structure is either \"no,\" or \"probably yes.\" Bloom\nfilters have two parameters that can be tuned to minimize the likelihood of\nfalse positive results: the size of the filter (the number of bits), and the\nnumber of hashes used to obtain a fingerprint of each item.\n\nBased on calculations performed using this [Bloom filter\ncalculator](https://hur.st/bloomfilter/?n=4M\u0026p=\u0026m=16MiB\u0026k=23), the Bloom\nfilters used by this Firefox extension occupy 16MB of space and use 23 hash\nfunctions. Since (at the time of this release) there are approximately 4\nmillion submitted Hacker News stories, this gives a 1 in 10 million chance of a\nfalse positive match on the Bloom filter. This probability gradually increases\nto 1 in 26,000 as the number of submissions approaches 6 million, and becomes 1\nin 850 by the time there have been 8 million Hacker News story submissions. At\nthat point, it will likely be worthwhile to consider increasing the size of the\nBloom filter.\n\n16MB was chosen as the Bloom filter size, and the number of hashes was adjusted\naround it. This size is convenient because it is not too large for an initial\ndownload of multiple Bloom filters. Additionally, 16MB Bloom filters\nrepresenting smaller time windows (e.g. submissions from the last 24 hours) are\nvery sparse, and thus compress extremely well. For example, the Bloom filter\nrepresenting submissions from the last 24 hours compresses from 16MB to about\n50KB. Though the false positive rate could be further reduced and\nfuture-proofed, doubling the Bloom filter size to 32MB is a significant\nincrease, even with compression.\n\n---\n\n\u003c/details\u003e\n\nIf the current page has been on Hacker News, the extension lights up and\nbecomes clickable. Clicking it retrieves a link to the best discussion for the\npage and navigates the browser there.\n\nBy default, the extension uses several Bloom filters to show a lower-bound on\nthe score for each page. This can be easily disabled from the \"Options\" page\nfor the extension, accessible by going to `about:addons`. It might be desirable\nto disable this if using multiple filters is too resource-intensive.\n\n\u003cdetails\u003e\n\n\u003csummary\u003eClick to read more about score thresholds\u003c/summary\u003e\n\nIt seemed reasonable to use at most five distinct Bloom filters. Because they\nbecome increasingly sparse as the number of stories in the Bloom filter\ndecreases, they compress well, so adding additional Bloom filters doesn't have\na massive impact on the total amount of data downloaded.\n\nOn the other hand, uncompressed, they total `5 * 16MB = 80MB` in memory – more\nthan this seemed unreasonable. \n\nThe five thresholds for the Bloom filters were chosen mostly by eye, but\nvalidated and tuned using analysis of the dataset. \n\n| Range | Count |\n| --- | --- |\n| 0-10\t| 3381917 |\n| 10-75 | 300300 |\n| 75-250 | 121291 |\n| 250-500 | 25739 |\n| 500+ | 7948 |\n\n\u003cimg alt=\"Bloom filter score range visualization\" src=\"https://github.com/jstrieb/hackernews-button/blob/master/doc/range-chart.svg\" /\u003e\n\nAs of February 28, 2021, the ranges have an approximately logarithmically\ndecreasing number of entries. This is desirable because this mirrors the true\ndistribution of the data, which is also approximately logarithmic. It also\nallows for acceptably sensible, informative score ranges.\n\n\u003cimg alt=\"Aggregate Hacker News story scores\" src=\"https://github.com/jstrieb/hackernews-button/blob/master/doc/scores-chart.svg\" /\u003e\n\nThe data used for this analysis can be viewed\n[here](https://docs.google.com/spreadsheets/d/1s41DRN3MrifjcqeYql88WAQH6nySIUYWs4NLUzDg7wM/edit?usp=sharing).\nIt was generated with the following BigQuery SQL query, and the thresholds were\ntuned in the spreadsheet.\n\n``` sql\nSELECT\n  score,\n  COUNT(score) AS count\nFROM\n  `bigquery-public-data.hacker_news.full`\nWHERE\n  score IS NOT NULL\n  AND score != 0\nGROUP BY\n  score\nORDER BY\n  score\n```\n\n\u003c/details\u003e\n\n## Disclaimer\n\nYou still send data to Algolia when you click the extension to visit the\ndiscussion. The improvement offered by using Bloom filters is to not send *all*\nof the sites you visit to the API, but *some* data still need to be sent to\nretrieve the link to the discussion. Moreover, by default an updated Bloom\nfilter is downloaded once every 24 hours from GitHub. It is possible that\nGitHub maintains logs of who downloads these releases.\n\n\n\n# How to Read This Code\n\nBrowser extensions have a lot of power to harm users, so it is important to\nunderstand what you are running. To that end, I provide a description of how to\nread this code. Please audit the code before running it.\n\nThis repository has three parts: \n1. Code to pull Hacker News data and generate Bloom filters from it\n2. Code for the browser extension\n3. A Bloom filter library used by the Bloom filter generator and the browser\n   extension – just one implementation used by both parts of the project\n\nEach of the three individual parts of the code are described in greater depth\nbelow. Click \"Details\" to read more.\n\nThe\n[`Makefile`](https://github.com/jstrieb/hackernews-button/blob/master/Makefile)\nis used for almost all parts of the code, and is a good place to start reading\nto understand how everything fits together.\n\n\u003cdetails\u003e\n\n\u003csummary\u003eDetails\u003c/summary\u003e\n\n## Bloom Filter Library\n\nFiles to read:\n\n- [`bloom-filter/bloom.c`](https://github.com/jstrieb/hackernews-button/blob/master/bloom-filter/bloom.c)\n- [`test/bloom-test.c`](https://github.com/jstrieb/hackernews-button/blob/master/test/bloom-test.c)\n\nThe code for Bloom filters is implemented in C. This code is used in a\ncommand-line C program to generate Bloom filters, which is compiled using\n`gcc`. It is also used by the browser extension in a wrapper library, which is\ncompiled to WebAssembly using Emscripten (`emcc` in the `Makefile`).\n\nThe [`test`](https://github.com/jstrieb/hackernews-button/tree/master/test)\nfolder includes tests for various parts of the Bloom filter library to ensure\nit is working as expected.\n\n## Generating Bloom Filters\n\nFiles to read:\n\n- [`.github/workflows/generate-bloomfilter.yml`](https://github.com/jstrieb/hackernews-button/blob/master/.github/workflows/generate-bloomfilter.yml)\n- [`canonicalize.py`](https://github.com/jstrieb/hackernews-button/blob/master/canonicalize.py)\n- [`bloom-filter/bloom-create.c`](https://github.com/jstrieb/hackernews-button/blob/master/bloom-filter/bloom-create.c)\n\nBloom filters are regularly regenerated on a schedule, mediated by a GitHub\nActions workflow. At a high level, this process pulls down relevant data from\nthe [Hacker News BigQuery\ndataset](https://console.cloud.google.com/marketplace/details/y-combinator/hacker-news),\ndoes some preprocessing, normalizes (\"canonicalizes\") URLs, and feeds them to\nthe command-line Bloom filter generator. Generated Bloom filters are uploaded\nas [GitHub Releases](https://github.com/jstrieb/hackernews-button/releases) so\nusers running the extension can download the latest ones.\n\nSince Bloom filters can only match exact strings, it is helpful to\n\"canonicalize\" URLs so that there are fewer false negative results. In other\nwords, because multiple URLs often point to the same page,\n[`canonicalize.py`](https://github.com/jstrieb/hackernews-button/blob/master/canonicalize.py)\nis useful for ensuring that slightly different URLs submitted to Hacker News\nfor the current page still match in the Bloom filter. Unfortunately, this\nprocess is inherently imperfect. Opening issues with suggested improvements to\nthe URL canonicalization process are appreciated!\n\nFor actually reading strings, adding them to Bloom filters, and writing\n(compressed) Bloom filters, we compile and use\n[`bloom-create.c`](https://github.com/jstrieb/hackernews-button/blob/master/bloom-filter/bloom-create.c).\nThis takes some command-line arguments, and then reads from standard input,\nparses the line-delimited strings, and outputs a Bloom filter.\n\n## Browser Extension\n\nFiles to read:\n\n- [`manifest.json`](https://github.com/jstrieb/hackernews-button/blob/master/manifest.json)\n- [`background.js`](https://github.com/jstrieb/hackernews-button/blob/master/background.js)\n- [`bloom-wrap.js`](https://github.com/jstrieb/hackernews-button/blob/master/bloom-wrap.js)\n- [`add-latest.js`](https://github.com/jstrieb/hackernews-button/blob/master/add-latest.js)\n\nThe\n[manifest](https://github.com/jstrieb/hackernews-button/blob/master/manifest.json)\nconnects all parts of the extension together. It attaches keyboard commands to\nevents and runs a page with background scripts, which do most of the heavy\nlifting. It also runs a small content script on `news.ycombinator.com` pages.\n\nThere are two important background scripts.\n[`background.js`](https://github.com/jstrieb/hackernews-button/blob/master/background.js)\nis responsible for displaying the browser extension and handling user\ninteraction.\n[`bloom-wrap.js`](https://github.com/jstrieb/hackernews-button/blob/master/bloom-wrap.js)\nmakes the Bloom filter library (implemented in C) easily accessible from\nJavaScript via low-level wrappers and high-level helper functions. It also\nincludes code that, when the browser starts and WebAssembly is ready, attempts\nto either load a Bloom filter from local storage, or download the latest one\nfrom GitHub. \n\nThe content script that runs on `news.ycombinator.com` pages extracts \"story\"\nURLs from the pages and adds them to the Bloom filter. This is useful because\nthe Bloom filters only update every 24 hours at most (as limited by the\nfrequency of BigQuery dataset updates), so adding stories to the Bloom filter\nthis way makes it possible to use the extension to view the discussion for\nrecently-submitted posts. This would otherwise not be possible until the Bloom\nfilter is updated many hours later.\n\nNote that the `background.html` page also loads a script `bloom.js` that is not\nin the repo. As per the\n[`Makefile`](https://github.com/jstrieb/hackernews-button/blob/d365b2a1619cd139186d3a162b9dd6de0bc13b0a/Makefile#L98-L111),\nthis script is compiled from the Bloom filter C library using Emscripten.\n\n\u003c/details\u003e\n\n\n\n# Project Status\n\nThis project is actively developed and maintained. If there have not been\ncommits long after the initial release, everything is probably running\nsmoothly!\n\nThe project is designed so that even if something were to happen to me, as long\nas my GitHub account is open, the Actions workflow should continue to release\nupdated Bloom filters.\n\nI will do my best to address issues in a timely fashion, but I'm busy and this\nis a side-project. Unsolicited pull requests are likely to be ignored. This is\nbecause releasing a browser extension means I have a (*moral*, not *legal*\n– see the\n[LICENSE](https://github.com/jstrieb/hackernews-button/blob/master/LICENSE))\nresponsibility for the security of everyone who installs it. As a result,\nvetting random pull requests is typically not worth the effort unless they\naddress an issue that has been discussed beforehand. I'm happy to have others'\nsupport, just ask first – open an issue to do so.\n\n\n\n# How to Modify This Code\n\n1. Fork your own copy of the repository\n2. [Create a new project](https://console.cloud.google.com/projectcreate) in\n   BigQuery\n3. Create a service account with the `BigQuery User` permission\n4. Generate a JSON key\n5. Enable Actions for the repository\n6. Copy the JSON key into an Actions secret called `BQ_JSON` (under Settings \u003e\n   Secrets \u003e Actions)\n7. Make your fork public if you want to be able to access it unauthenticated\n8. Change the repo to your liking, maintaining attribution and the LICENSE file!\n8. Change the repo to your liking, maintaining attribution and the LICENSE\n   file!\n\n\n\n# Known Issues\n\n- There is currently no version of this extension for Google Chrome. To read\n  more and discuss, check out the relevant issue\n  ([#1](https://github.com/jstrieb/hackernews-button/issues/1)).\n- The [URL\n  canonicalization](https://github.com/jstrieb/hackernews-button/blob/master/canonicalize.py)\n  is highly imperfect. There will inevitably be false negatives in Bloom filter\n  results. Suggestions for improving canonicalization in general, or for\n  specific sites, are welcome!\n- If the button is clicked, Algolia search tries to return the \"best\"\n  submission for a given URL. Often this is not the latest submission, but the\n  one with the most points.\n\n  This also means that if the button is clicked for very recently submitted\n  stories (when browsing [new](https://news.ycombinator.com/newest), for\n  example), Algolia may not have indexed the story yet, causing the redirect to\n  fail.\n- On my computer, the plus signs in the badge text gets cut off for three-digit\n  scores ([#2](https://github.com/jstrieb/hackernews-button/issues/2)).\n\n\n\n# Support the Project\n\nThere are a few things you can do to support the project:\n\n- Star the repository (and follow me on GitHub for more)\n- Share and upvote on sites like Twitter, Reddit, and Hacker News\n- Report any bugs, glitches, or errors that you find\n\nThese things motivate me to to keep sharing what I build, and they provide\nvalidation that my work is appreciated! They also help me improve the project.\nThanks in advance!\n\nIf you are insistent on spending money to show your support, I encourage you to\ninstead make a generous donation to one of the following organizations. By\nadvocating for Internet freedoms, organizations like these help me to feel\ncomfortable releasing work publicly on the Web.\n\n- [Electronic Frontier Foundation](https://supporters.eff.org/donate/)\n- [Signal Foundation](https://signal.org/donate/)\n- [Mozilla](https://donate.mozilla.org/en-US/)\n- [The Internet Archive](https://archive.org/donate/index.php)\n\n\n\n# Acknowledgments\n\n*This project is not affiliated with Hacker News, Y Combinator, or any Y\nCombinator-backed company.*\n\nThis project would not exist in its current form without:\n\n- Daniel Gackle ([dang](https://news.ycombinator.com/user?id=dang))\n- Logan Snow ([@lsnow99](https://github.com/lsnow99))\n- [Amy Liu](https://www.linkedin.com/in/amyjl/)\n- [Hacker News](https://news.ycombinator.com)\n- Thomas Hurst's [Bloom filter calculator](https://hur.st/bloomfilter/)\n- [zlib](https://zlib.net)\n- [MurmurHash](https://github.com/aappleby/smhasher) and Austin Appleby\n- [GitHub Actions](https://github.com/features/actions)\n- [BigQuery](https://console.cloud.google.com/marketplace/details/y-combinator/hacker-news)\n- [Algolia Hacker News Search](https://hn.algolia.com/)\n- Anyone who has asked or answered a helpful question on StackOverflow\n- [Mozilla Developer Network](https://developer.mozilla.org/en-US/)\n  documentation – my _sine qua non_ for writing anything for the Web, including\n  browser extensions\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjstrieb%2Fhackernews-button","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjstrieb%2Fhackernews-button","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjstrieb%2Fhackernews-button/lists"}