JanitorAI Token Filter

Filters character cards on JanitorAI by token count

当前为 2025-04-09 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name JanitorAI Token Filter
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.26
  5. // @description Filters character cards on JanitorAI by token count
  6. // @author Fefnik
  7. // @match https://janitorai.com/*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. 'use strict';
  13.  
  14. let MIN_TOKENS = localStorage.getItem('janitorAITokenFilter') ? parseInt(localStorage.getItem('janitorAITokenFilter')) : 500;
  15. let sliderElement = null;
  16. let sliderContainer = null;
  17. let isSliderVisible = localStorage.getItem('janitorAISliderVisible') === 'true' ? true : false;
  18.  
  19. function parseTokens(tokenText) {
  20. try {
  21. let cleanText = tokenText.replace(/<!--[\s\S]*?-->/g, '').replace('tokens', '').trim();
  22. if (cleanText.includes('k')) {
  23. return parseFloat(cleanText.replace('k', '')) * 1000;
  24. }
  25. return parseInt(cleanText, 10) || 0;
  26. } catch (error) {
  27. return 0;
  28. }
  29. }
  30.  
  31. function filterCards() {
  32. const mainPageCards = document.querySelectorAll('.chakra-stack.css-1s5evre');
  33. mainPageCards.forEach(card => {
  34. const tokenElement = card.querySelector('.chakra-text.css-jccmq6');
  35. if (!tokenElement) return;
  36.  
  37. const tokenCount = parseTokens(tokenElement.textContent);
  38. const parentContainer = card.closest('.css-1sxhvxh');
  39. if (parentContainer) {
  40. parentContainer.style.display = tokenCount < MIN_TOKENS ? 'none' : '';
  41. }
  42. });
  43.  
  44. const searchPageCards = document.querySelectorAll('.chakra-stack.css-1s5evre, .css-1s5evre');
  45. searchPageCards.forEach(card => {
  46. const tokenElement = card.querySelector('.chakra-text.css-jccmq6, .css-jccmq6');
  47. if (!tokenElement) return;
  48.  
  49. const tokenCount = parseTokens(tokenElement.textContent);
  50. const parentContainer = card.closest('.css-1sxhvxh, .css-1dbw1r8');
  51. if (parentContainer) {
  52. parentContainer.style.display = tokenCount < MIN_TOKENS ? 'none' : '';
  53. }
  54. });
  55. }
  56.  
  57. function createOrUpdateSlider() {
  58. if (!sliderElement) {
  59. sliderContainer = document.createElement('div');
  60. sliderContainer.id = 'token-filter-container';
  61. sliderContainer.style.position = 'fixed';
  62. sliderContainer.style.top = '75px'; // Lowered by 25px (from 50px to 75px)
  63. sliderContainer.style.left = '0px';
  64. sliderContainer.style.zIndex = '99999';
  65. sliderContainer.style.display = 'flex';
  66. sliderContainer.style.flexDirection = 'column';
  67. sliderContainer.style.alignItems = 'center';
  68. sliderContainer.style.height = '150px';
  69. sliderContainer.style.width = '50px';
  70.  
  71. const sliderWrapper = document.createElement('div');
  72. sliderWrapper.style.width = '100px';
  73. sliderWrapper.style.height = '10px';
  74. sliderWrapper.style.transform = 'rotate(-90deg)';
  75. sliderWrapper.style.transformOrigin = 'center center';
  76. sliderWrapper.style.marginTop = '10px';
  77.  
  78. sliderElement = document.createElement('input');
  79. sliderElement.type = 'range';
  80. sliderElement.id = 'token-filter-slider';
  81. sliderElement.min = '0';
  82. sliderElement.max = '6000';
  83. sliderElement.step = '100';
  84. sliderElement.value = MIN_TOKENS;
  85. sliderElement.style.width = '100%';
  86. sliderElement.style.height = '20px';
  87. sliderElement.style.backgroundColor = '#4a4a4a';
  88. sliderElement.style.cursor = 'pointer';
  89. sliderElement.style.appearance = 'none';
  90. sliderElement.style.outline = 'none';
  91. sliderElement.style.borderRadius = '5px';
  92.  
  93. const style = document.createElement('style');
  94. style.textContent = `
  95. #token-filter-slider::-webkit-slider-thumb {
  96. -webkit-appearance: none;
  97. appearance: none;
  98. width: 20px;
  99. height: 20px;
  100. background: #ffffff;
  101. cursor: pointer;
  102. border-radius: 50%;
  103. border: 2px solid #000;
  104. }
  105. #token-filter-slider::-moz-range-thumb {
  106. width: 20px;
  107. height: 20px;
  108. background: #ffffff;
  109. cursor: pointer;
  110. border-radius: 50%;
  111. border: 2px solid #000;
  112. }
  113. `;
  114. document.head.appendChild(style);
  115.  
  116. const label = document.createElement('span');
  117. label.id = 'token-filter-label';
  118. label.style.color = '#fff';
  119. label.style.fontSize = '12px';
  120. label.style.textAlign = 'center';
  121. label.style.marginTop = '50px';
  122. label.textContent = `${MIN_TOKENS} tokens`;
  123.  
  124. sliderElement.addEventListener('input', (e) => {
  125. MIN_TOKENS = parseInt(e.target.value);
  126. label.textContent = `${MIN_TOKENS} tokens`;
  127. localStorage.setItem('janitorAITokenFilter', MIN_TOKENS);
  128. filterCards();
  129. });
  130.  
  131. sliderWrapper.appendChild(sliderElement);
  132. sliderContainer.appendChild(sliderWrapper);
  133. sliderContainer.appendChild(label);
  134.  
  135. const toggleButton = document.createElement('button');
  136. toggleButton.id = 'token-filter-toggle';
  137. toggleButton.textContent = '⚙️';
  138. toggleButton.style.position = 'fixed';
  139. toggleButton.style.top = '10px';
  140. toggleButton.style.left = '10px';
  141. toggleButton.style.zIndex = '100000';
  142. toggleButton.style.width = '20px';
  143. toggleButton.style.height = '20px';
  144. toggleButton.style.padding = '0';
  145. toggleButton.style.backgroundColor = '#4a4a4a';
  146. toggleButton.style.color = '#fff';
  147. toggleButton.style.border = 'none';
  148. toggleButton.style.borderRadius = '5px';
  149. toggleButton.style.cursor = 'pointer';
  150. toggleButton.style.display = 'flex';
  151. toggleButton.style.alignItems = 'center';
  152. toggleButton.style.justifyContent = 'center';
  153. toggleButton.style.fontSize = '14px';
  154.  
  155. toggleButton.addEventListener('click', () => {
  156. isSliderVisible = !isSliderVisible;
  157. sliderContainer.style.display = isSliderVisible ? 'flex' : 'none';
  158. localStorage.setItem('janitorAISliderVisible', isSliderVisible);
  159. });
  160.  
  161. const appendElements = () => {
  162. if (document.body) {
  163. document.body.appendChild(toggleButton);
  164. document.body.appendChild(sliderContainer);
  165. } else {
  166. setTimeout(appendElements, 500);
  167. }
  168. };
  169. appendElements();
  170. }
  171.  
  172. if (sliderContainer) {
  173. const isPageVisible = window.location.pathname === '/' || window.location.pathname === '/search';
  174. sliderContainer.style.display = isPageVisible && isSliderVisible ? 'flex' : 'none';
  175. }
  176. }
  177.  
  178. function initialize() {
  179. createOrUpdateSlider();
  180. if (window.location.pathname === '/' || window.location.pathname === '/search') {
  181. filterCards();
  182. }
  183. }
  184.  
  185. const tryInitialize = () => {
  186. if (document.body) {
  187. initialize();
  188.  
  189. let lastPath = window.location.pathname;
  190. const checkPath = () => {
  191. if (lastPath !== window.location.pathname) {
  192. lastPath = window.location.pathname;
  193. createOrUpdateSlider();
  194. if (lastPath === '/' || lastPath === '/search') {
  195. filterCards();
  196. }
  197. }
  198. };
  199.  
  200. setInterval(checkPath, 500);
  201.  
  202. const observer = new MutationObserver(() => {
  203. if (window.location.pathname === '/' || window.location.pathname === '/search') {
  204. setTimeout(filterCards, 500);
  205. }
  206. });
  207. observer.observe(document.body, { childList: true, subtree: true });
  208. } else {
  209. setTimeout(tryInitialize, 1000);
  210. }
  211. };
  212.  
  213. tryInitialize();
  214. })();