Bumeran Job Filter

Esconde "job cards" de la búsqueda basado en palabras clave.

  1. // ==UserScript==
  2. // @name Bumeran Job Filter
  3. // @namespace Bumeran-Job-Filter
  4. // @version 1.8
  5. // @description Esconde "job cards" de la búsqueda basado en palabras clave.
  6. // @author masterofobzene
  7. // @homepage https://github.com/masterofobzene/UserScriptRepo
  8. // @match https://www.bumeran.com.ar/*
  9. // @license GNU GPLv3
  10. // @grant GM_setValue
  11. // @grant GM_getValue
  12. // @grant GM_registerMenuCommand
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. const KEYWORDS_KEY = 'bumeran_exclude_keywords';
  19. let liveDebounce, domDebounce;
  20.  
  21. // — storage helpers —
  22. async function getKeywords() {
  23. const stored = await GM_getValue(KEYWORDS_KEY, '');
  24. return stored
  25. .split(',')
  26. .map(k => k.trim().toLowerCase())
  27. .filter(Boolean);
  28. }
  29. async function saveKeywords(keywords) {
  30. await GM_setValue(KEYWORDS_KEY, keywords.join(','));
  31. console.log('[BumeranFilter] Saved keywords:', keywords);
  32. }
  33.  
  34. // — filtering —
  35. function resetJobsDisplay() {
  36. document.querySelectorAll('a').forEach(a => {
  37. if (a.querySelector('h2')) a.style.display = '';
  38. });
  39. }
  40. function filterJobs(keywords) {
  41. resetJobsDisplay();
  42. if (!keywords.length) return;
  43. document.querySelectorAll('a')
  44. .forEach(link => {
  45. const h2 = link.querySelector('h2');
  46. if (!h2) return;
  47. const txt = h2.textContent.trim().toLowerCase();
  48. if (keywords.some(kw => txt.includes(kw))) {
  49. link.style.display = 'none';
  50. console.log(`[BumeranFilter] Hiding: "${txt}"`);
  51. }
  52. });
  53. }
  54.  
  55. // — UI panel —
  56. function addUI() {
  57. const c = document.createElement('div');
  58. Object.assign(c.style, {
  59. position: 'fixed', bottom: '10px', left: '10px', // Move to lower left
  60. zIndex: 9999, background: '#fff',
  61. border: '1px solid #ccc', padding: '10px',
  62. boxShadow: '0 2px 5px rgba(0,0,0,0.2)'
  63. });
  64. c.innerHTML = `
  65. <label><strong>Excluir keywords:</strong></label><br>
  66. <input id="keywordInput" type="text"
  67. style="width:200px"
  68. placeholder="e.g. java, senior, python" />
  69. <small style="display:block;margin-top:4px;color:#666">
  70. Los filtros se aplican dinámicamente
  71. </small>
  72. `;
  73. document.body.appendChild(c);
  74.  
  75. const input = c.querySelector('#keywordInput');
  76. input.addEventListener('input', () => {
  77. clearTimeout(liveDebounce);
  78. liveDebounce = setTimeout(async () => {
  79. const kws = input.value
  80. .split(',')
  81. .map(s => s.trim().toLowerCase())
  82. .filter(Boolean);
  83. await saveKeywords(kws);
  84. filterJobs(kws);
  85. }, 200);
  86. });
  87. }
  88.  
  89. // — run once jobs have appeared —
  90. function jobsReady(cb) {
  91. if (document.querySelector('a h2')) {
  92. cb(); return;
  93. }
  94. const obs = new MutationObserver((m,o) => {
  95. if (document.querySelector('a h2')) {
  96. o.disconnect();
  97. cb();
  98. }
  99. });
  100. obs.observe(document.body, { childList: true, subtree: true });
  101. }
  102.  
  103. // — catch URL changes if history API fails —
  104. function watchUrlChanges(onChange) {
  105. let last = location.href;
  106. setInterval(() => {
  107. if (location.href !== last) {
  108. last = location.href;
  109. onChange();
  110. }
  111. }, 500);
  112. }
  113.  
  114. // — main init —
  115. (async function init() {
  116. console.log('[BumeranFilter] v1.8 starting…');
  117. addUI();
  118.  
  119. // load + auto-apply saved filters
  120. const saved = await getKeywords();
  121. const inp = document.getElementById('keywordInput');
  122. if (saved.length) inp.value = saved.join(', ');
  123. jobsReady(() => filterJobs(saved));
  124.  
  125. // re-apply whenever the URL changes
  126. watchUrlChanges(async () => {
  127. const kws = await getKeywords();
  128. jobsReady(() => filterJobs(kws));
  129. });
  130.  
  131. // also watch DOM mutations (e.g. new jobs loaded dynamically)
  132. const mo = new MutationObserver(() => {
  133. clearTimeout(domDebounce);
  134. domDebounce = setTimeout(async () => {
  135. const kws = await getKeywords();
  136. filterJobs(kws);
  137. }, 300);
  138. });
  139. mo.observe(document.body, { childList: true, subtree: true });
  140.  
  141. // menu command to clear
  142. GM_registerMenuCommand('Borrar todas las Exclude Keywords', async () => {
  143. await GM_setValue(KEYWORDS_KEY, '');
  144. location.reload();
  145. });
  146. })();
  147.  
  148. })();