Pump Card Filter Manager

Manage blacklisted and highlighted phrases for pump cards

当前为 2024-11-24 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Pump Card Filter Manager
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1
  5. // @description Manage blacklisted and highlighted phrases for pump cards
  6. // @author bob123
  7. // @match https://neo.bullx.io/neo-vision
  8. // @grant none
  9. // @license MIT
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. 'use strict';
  14.  
  15. // Initialize from localStorage or create empty Sets
  16. let blacklistedPhrases = new Set(JSON.parse(localStorage.getItem('blacklistedPhrases') || '[]'));
  17. let highlightedPhrases = new Set(JSON.parse(localStorage.getItem('highlightedPhrases') || '[]'));
  18. let caseSensitive = JSON.parse(localStorage.getItem('caseSensitive') || 'false');
  19. let managersVisible = JSON.parse(localStorage.getItem('managersVisible') || 'true');
  20.  
  21. // Save to localStorage function
  22. function saveToStorage() {
  23. localStorage.setItem('blacklistedPhrases', JSON.stringify([...blacklistedPhrases]));
  24. localStorage.setItem('highlightedPhrases', JSON.stringify([...highlightedPhrases]));
  25. localStorage.setItem('caseSensitive', JSON.stringify(caseSensitive));
  26. localStorage.setItem('managersVisible', JSON.stringify(managersVisible));
  27. }
  28.  
  29. const styles = `
  30. .toggle-managers-btn {
  31. position: fixed;
  32. top: 10px;
  33. right: 10px;
  34. z-index: 10000;
  35. background: #2d2d2d;
  36. color: white;
  37. border: none;
  38. padding: 5px 10px;
  39. border-radius: 4px;
  40. cursor: pointer;
  41. }
  42. .toggle-managers-btn:hover {
  43. background: #404040;
  44. }
  45. .filter-manager {
  46. position: fixed;
  47. right: 10px;
  48. background: #2d2d2d;
  49. padding: 10px;
  50. border-radius: 8px;
  51. width: 200px;
  52. z-index: 9999;
  53. color: #fff;
  54. font-family: Arial, sans-serif;
  55. max-height: 180px; /* Fixed total height */
  56. display: flex;
  57. flex-direction: column;
  58. }
  59. .blacklist-manager {
  60. top: 40px;
  61. }
  62. .highlight-manager {
  63. top: 230px;
  64. }
  65. .filter-manager h3 {
  66. margin: 0 0 5px 0;
  67. color: #fff;
  68. font-size: 14px;
  69. flex-shrink: 0;
  70. }
  71. .filter-input {
  72. width: 100%;
  73. padding: 3px;
  74. margin-bottom: 5px;
  75. background: #404040;
  76. border: 1px solid #555;
  77. color: #fff;
  78. border-radius: 4px;
  79. font-size: 12px;
  80. flex-shrink: 0;
  81. }
  82. .phrases-list {
  83. flex-grow: 1;
  84. overflow-y: auto;
  85. margin-top: 5px;
  86. padding-right: 5px;
  87. /* Scrollbar styling */
  88. scrollbar-width: thin;
  89. scrollbar-color: #666 #2d2d2d;
  90. }
  91. /* WebKit scrollbar styling */
  92. .phrases-list::-webkit-scrollbar {
  93. width: 6px;
  94. }
  95. .phrases-list::-webkit-scrollbar-track {
  96. background: #2d2d2d;
  97. border-radius: 3px;
  98. }
  99. .phrases-list::-webkit-scrollbar-thumb {
  100. background-color: #666;
  101. border-radius: 3px;
  102. border: 2px solid #2d2d2d;
  103. }
  104. .phrase-item {
  105. display: flex;
  106. justify-content: space-between;
  107. align-items: center;
  108. padding: 3px;
  109. background: #404040;
  110. margin: 2px 0;
  111. border-radius: 4px;
  112. word-break: break-all;
  113. font-size: 12px;
  114. }
  115. .phrase-text {
  116. flex: 1;
  117. margin-right: 5px;
  118. }
  119. .remove-phrase {
  120. background: #ff4444;
  121. color: white;
  122. border: none;
  123. border-radius: 3px;
  124. padding: 1px 4px;
  125. cursor: pointer;
  126. flex-shrink: 0;
  127. font-size: 10px;
  128. }
  129. .case-sensitive-toggle {
  130. display: flex;
  131. align-items: center;
  132. margin-bottom: 5px;
  133. font-size: 11px;
  134. flex-shrink: 0;
  135. }
  136. .case-sensitive-toggle input {
  137. margin-right: 3px;
  138. }
  139. .highlight-manager .phrase-item {
  140. border-left: 2px solid #ff4444;
  141. }
  142. .filter-manager.hidden {
  143. display: none;
  144. }
  145. `;
  146.  
  147. function createToggleButton() {
  148. const button = document.createElement('button');
  149. button.className = 'toggle-managers-btn';
  150. button.textContent = 'Toggle Filters';
  151. button.addEventListener('click', () => {
  152. managersVisible = !managersVisible;
  153. document.querySelectorAll('.filter-manager').forEach(manager => {
  154. manager.classList.toggle('hidden', !managersVisible);
  155. });
  156. saveToStorage();
  157. });
  158. document.body.appendChild(button);
  159. }
  160.  
  161. function createFilterManager(type) {
  162. const manager = document.createElement('div');
  163. manager.className = `filter-manager ${type}-manager`;
  164. if (!managersVisible) {
  165. manager.classList.add('hidden');
  166. }
  167. const title = type === 'blacklist' ? 'Blacklist Manager' : 'Highlight Manager';
  168. const placeholder = type === 'blacklist' ? 'Enter phrase to blacklist' : 'Enter phrase to highlight';
  169.  
  170. manager.innerHTML = `
  171. <h3>${title}</h3>
  172. <div class="case-sensitive-toggle">
  173. <input type="checkbox" id="${type}CaseSensitiveToggle" ${caseSensitive ? 'checked' : ''}>
  174. <label for="${type}CaseSensitiveToggle">Case Sensitive</label>
  175. </div>
  176. <input type="text" class="filter-input" placeholder="${placeholder}">
  177. <div class="phrases-list"></div>
  178. `;
  179. document.body.appendChild(manager);
  180.  
  181. const input = manager.querySelector('.filter-input');
  182. const phrasesList = manager.querySelector('.phrases-list');
  183. const caseToggle = manager.querySelector(`#${type}CaseSensitiveToggle`);
  184.  
  185. input.addEventListener('keypress', (e) => {
  186. if (e.key === 'Enter' && input.value.trim()) {
  187. if (type === 'blacklist') {
  188. blacklistedPhrases.add(input.value.trim());
  189. } else {
  190. highlightedPhrases.add(input.value.trim());
  191. }
  192. input.value = '';
  193. updatePhrasesList(type);
  194. checkAllCards();
  195. saveToStorage();
  196. }
  197. });
  198.  
  199. caseToggle.addEventListener('change', (e) => {
  200. caseSensitive = e.target.checked;
  201. checkAllCards();
  202. saveToStorage();
  203. });
  204.  
  205. return manager;
  206. }
  207.  
  208. function updatePhrasesList(type) {
  209. const phrases = type === 'blacklist' ? blacklistedPhrases : highlightedPhrases;
  210. const phrasesList = document.querySelector(`.${type}-manager .phrases-list`);
  211. phrasesList.innerHTML = '';
  212.  
  213. phrases.forEach(phrase => {
  214. const phraseItem = document.createElement('div');
  215. phraseItem.className = 'phrase-item';
  216. phraseItem.innerHTML = `
  217. <span class="phrase-text">"${phrase}"</span>
  218. <button class="remove-phrase">×</button>
  219. `;
  220. phraseItem.querySelector('.remove-phrase').addEventListener('click', () => {
  221. phrases.delete(phrase);
  222. updatePhrasesList(type);
  223. checkAllCards();
  224. saveToStorage();
  225. });
  226. phrasesList.appendChild(phraseItem);
  227. });
  228. }
  229.  
  230. function checkText(text, phrase) {
  231. if (caseSensitive) {
  232. return text.includes(phrase);
  233. }
  234. return text.toLowerCase().includes(phrase.toLowerCase());
  235. }
  236.  
  237. function shouldHide(element) {
  238. const span = element.querySelector('span.font-normal.text-grey-200.overflow-ellipsis.line-clamp-1.text-xs.\\!leading-\\[12px\\]');
  239. if (!span) return false;
  240.  
  241. const text = span.textContent;
  242. return Array.from(blacklistedPhrases).some(phrase => checkText(text, phrase));
  243. }
  244.  
  245. function shouldHighlight(element) {
  246. const span = element.querySelector('span.font-normal.text-grey-200.overflow-ellipsis.line-clamp-1.text-xs.\\!leading-\\[12px\\]');
  247. if (!span) return false;
  248.  
  249. const text = span.textContent;
  250. return Array.from(highlightedPhrases).some(phrase => checkText(text, phrase));
  251. }
  252.  
  253. function processElement(element) {
  254. if (!element.classList.contains('pump-card') ||
  255. !element.classList.contains('group') ||
  256. !element.classList.contains('row-hover') ||
  257. (!element.classList.contains('bg-grey-900') && !element.classList.contains('bg-grey-850'))) {
  258. return;
  259. }
  260.  
  261. if (shouldHide(element)) {
  262. element.style.display = 'none';
  263. } else {
  264. element.style.display = '';
  265. if (shouldHighlight(element)) {
  266. element.style.border = '2px solid red';
  267. element.style.borderRadius = '4px';
  268. } else {
  269. element.style.border = '';
  270. element.style.borderRadius = '';
  271. }
  272. }
  273. }
  274.  
  275. function checkAllCards() {
  276. const cards = document.querySelectorAll('.pump-card.group.row-hover');
  277. cards.forEach(processElement);
  278. }
  279.  
  280. function startObserver() {
  281. const observer = new MutationObserver((mutations) => {
  282. mutations.forEach((mutation) => {
  283. mutation.addedNodes.forEach((node) => {
  284. if (node.nodeType === 1) {
  285. if (node.classList && node.classList.contains('pump-card')) {
  286. processElement(node);
  287. }
  288. const cards = node.querySelectorAll('.pump-card.group.row-hover');
  289. cards.forEach(processElement);
  290. }
  291. });
  292. });
  293. });
  294.  
  295. observer.observe(document.body, {
  296. childList: true,
  297. subtree: true
  298. });
  299.  
  300. return observer;
  301. }
  302.  
  303. function init() {
  304. const styleSheet = document.createElement('style');
  305. styleSheet.textContent = styles;
  306. document.head.appendChild(styleSheet);
  307.  
  308. createToggleButton();
  309. createFilterManager('blacklist');
  310. createFilterManager('highlight');
  311.  
  312. // Initialize lists with stored data
  313. updatePhrasesList('blacklist');
  314. updatePhrasesList('highlight');
  315.  
  316. const observer = startObserver();
  317. checkAllCards(); // Check existing cards on load
  318.  
  319. window.addEventListener('unload', () => {
  320. observer.disconnect();
  321. });
  322. }
  323.  
  324.  
  325. init();
  326. })();