Hacker News Show HN Open Source Filter

Filters Hacker News Show submissions to only show open-source projects, removes clutter, and re-ranks items.

  1. // ==UserScript==
  2. // @name Hacker News Show HN Open Source Filter
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.4
  5. // @description Filters Hacker News Show submissions to only show open-source projects, removes clutter, and re-ranks items.
  6. // @author sm18lr88 (https://github.com/sm18lr88)
  7. // @license MIT
  8. // @match https://news.ycombinator.com/show
  9. // @match https://news.ycombinator.com/show*
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. const OPEN_SOURCE_HOSTS = [
  17. "github.com",
  18. "gitlab.com",
  19. "bitbucket.org",
  20. "sourceforge.net",
  21. "github.io",
  22. "codeberg.org",
  23. // Add more known open-source hosting domain suffixes if needed
  24. ];
  25.  
  26. /**
  27. * Removes the "Show HN: " prefix from titles.
  28. */
  29. function removeShowHnPrefix() {
  30. const titles = document.querySelectorAll('#hnmain .titleline > a');
  31. titles.forEach(title => {
  32. if (title.textContent.startsWith('Show HN: ')) {
  33. title.textContent = title.textContent.replace('Show HN: ', '');
  34. }
  35. });
  36. }
  37.  
  38. /**
  39. * Checks if a URL points to a known open-source repository or project page.
  40. * @param {string} urlString - The URL to check.
  41. * @returns {boolean} True if it's considered an open-source link, false otherwise.
  42. */
  43. function isRepoLink(urlString) {
  44. if (!urlString) return false;
  45. try {
  46. const url = new URL(urlString); // Handles absolute URLs
  47. const hostname = url.hostname.toLowerCase();
  48. // Check if the hostname or a parent domain is in our list
  49. if (OPEN_SOURCE_HOSTS.some(host => hostname === host || hostname.endsWith('.' + host))) {
  50. return true;
  51. }
  52. } catch (e) {
  53. // Likely a relative URL (e.g., "item?id=...") or invalid.
  54. // These are not considered direct open-source repo links.
  55. return false;
  56. }
  57. return false;
  58. }
  59.  
  60. /**
  61. * Filters submissions to keep only those linking to open-source projects.
  62. * Removes the submission row, its subtext row, and its spacer row if not open source.
  63. */
  64. function filterSubmissions() {
  65. const submissions = document.querySelectorAll('#hnmain tr.athing.submission');
  66. submissions.forEach(submissionRow => {
  67. const titleLink = submissionRow.querySelector('.titleline > a');
  68. if (titleLink) {
  69. const url = titleLink.href;
  70. if (!isRepoLink(url)) {
  71. const subtextRow = submissionRow.nextElementSibling; // This is the row with score, user, comments
  72. const spacerRow = subtextRow ? subtextRow.nextElementSibling : null; // This is the <tr class="spacer">
  73.  
  74. submissionRow.remove();
  75. if (subtextRow) {
  76. subtextRow.remove();
  77. }
  78. // Ensure spacerRow is indeed a spacer before removing
  79. if (spacerRow && spacerRow.classList.contains('spacer')) {
  80. spacerRow.remove();
  81. }
  82. }
  83. }
  84. });
  85. }
  86.  
  87. /**
  88. * Removes unnecessary clutter from the page, like the intro "rules" div and footer links.
  89. */
  90. function removePageClutter() {
  91. // Remove the "Please read the Show HN rules..." div
  92. // This div is a child of a TD and contains a link to 'showhn.html'
  93. const allTdDivs = document.querySelectorAll('#hnmain td > div');
  94. allTdDivs.forEach(div => {
  95. if (div.querySelector('a[href="showhn.html"]') && div.textContent.includes("Please read the Show HN rules")) {
  96. div.remove();
  97. }
  98. });
  99.  
  100. // Remove the footer yclinks (Guidelines, FAQ, Lists, API, etc.)
  101. const yclinksSpan = document.querySelector('span.yclinks');
  102. if (yclinksSpan) {
  103. // Optionally remove the <br> tags and Search form if the whole center block is to be cleaned.
  104. // For now, just removing the yclinks span itself.
  105. yclinksSpan.remove();
  106. // If you want to remove the surrounding <center> tag that also contains the search bar:
  107. // const centerContainer = yclinksSpan.closest('center');
  108. // if (centerContainer) centerContainer.remove();
  109. }
  110. }
  111.  
  112. /**
  113. * Re-numbers the rank of the remaining visible items.
  114. */
  115. function updateRanks() {
  116. const remainingRanks = document.querySelectorAll('#hnmain tr.athing.submission .rank');
  117. remainingRanks.forEach((rankSpan, index) => {
  118. rankSpan.textContent = `${index + 1}.`;
  119. });
  120. }
  121.  
  122. // --- Execute the functions ---
  123. removeShowHnPrefix();
  124. filterSubmissions();
  125. removePageClutter();
  126. updateRanks();
  127.  
  128. })();