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.22
  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. // Load the saved token value from localStorage, default to 500 if not set
  15. let MIN_TOKENS = localStorage.getItem('janitorAITokenFilter') ? parseInt(localStorage.getItem('janitorAITokenFilter')) : 500;
  16. let sliderElement = null;
  17.  
  18. function parseTokens(tokenText) {
  19. try {
  20. let cleanText = tokenText.replace(/<!--[\s\S]*?-->/g, '').replace('tokens', '').trim();
  21. if (cleanText.includes('k')) {
  22. return parseFloat(cleanText.replace('k', '')) * 1000;
  23. }
  24. return parseInt(cleanText, 10) || 0;
  25. } catch (error) {
  26. return 0;
  27. }
  28. }
  29.  
  30. function filterCards() {
  31. const mainPageCards = document.querySelectorAll('.chakra-stack.css-1s5evre');
  32. mainPageCards.forEach(card => {
  33. const tokenElement = card.querySelector('.chakra-text.css-jccmq6');
  34. if (!tokenElement) return;
  35.  
  36. const tokenCount = parseTokens(tokenElement.textContent);
  37. const parentContainer = card.closest('.css-1sxhvxh');
  38. if (parentContainer) {
  39. parentContainer.style.display = tokenCount < MIN_TOKENS ? 'none' : '';
  40. }
  41. });
  42.  
  43. const searchPageCards = document.querySelectorAll('.chakra-stack.css-1s5evre, .css-1s5evre');
  44. searchPageCards.forEach(card => {
  45. const tokenElement = card.querySelector('.chakra-text.css-jccmq6, .css-jccmq6');
  46. if (!tokenElement) return;
  47.  
  48. const tokenCount = parseTokens(tokenElement.textContent);
  49. const parentContainer = card.closest('.css-1sxhvxh, .css-1dbw1r8');
  50. if (parentContainer) {
  51. parentContainer.style.display = tokenCount < MIN_TOKENS ? 'none' : '';
  52. }
  53. });
  54. }
  55.  
  56. function createOrUpdateSlider() {
  57. if (!sliderElement) {
  58. const sliderContainer = document.createElement('div');
  59. sliderContainer.id = 'token-filter-container';
  60. sliderContainer.style.position = 'fixed';
  61. sliderContainer.style.top = '50px';
  62. sliderContainer.style.left = '0px';
  63. sliderContainer.style.zIndex = '99999';
  64. sliderContainer.style.display = 'flex';
  65. sliderContainer.style.flexDirection = 'column';
  66. sliderContainer.style.alignItems = 'center';
  67. sliderContainer.style.height = '150px';
  68. sliderContainer.style.width = '50px';
  69.  
  70. const sliderWrapper = document.createElement('div');
  71. sliderWrapper.style.width = '100px';
  72. sliderWrapper.style.height = '10px';
  73. sliderWrapper.style.transform = 'rotate(-90deg)';
  74. sliderWrapper.style.transformOrigin = 'center center';
  75. sliderWrapper.style.marginTop = '10px';
  76.  
  77. sliderElement = document.createElement('input');
  78. sliderElement.type = 'range';
  79. sliderElement.id = 'token-filter-slider';
  80. sliderElement.min = '0';
  81. sliderElement.max = '6000';
  82. sliderElement.step = '100';
  83. sliderElement.value = MIN_TOKENS; // Set the slider to the saved value
  84. sliderElement.style.width = '100%';
  85. sliderElement.style.height = '20px';
  86. sliderElement.style.backgroundColor = '#4a4a4a';
  87. sliderElement.style.cursor = 'pointer';
  88. sliderElement.style.appearance = 'none';
  89. sliderElement.style.outline = 'none';
  90. sliderElement.style.borderRadius = '5px';
  91.  
  92. const style = document.createElement('style');
  93. style.textContent = `
  94. #token-filter-slider::-webkit-slider-thumb {
  95. -webkit-appearance: none;
  96. appearance: none;
  97. width: 20px;
  98. height: 20px;
  99. background: #ffffff;
  100. cursor: pointer;
  101. border-radius: 50%;
  102. border: 2px solid #000;
  103. }
  104. #token-filter-slider::-moz-range-thumb {
  105. width: 20px;
  106. height: 20px;
  107. background: #ffffff;
  108. cursor: pointer;
  109. border-radius: 50%;
  110. border: 2px solid #000;
  111. }
  112. `;
  113. document.head.appendChild(style);
  114.  
  115. const label = document.createElement('span');
  116. label.id = 'token-filter-label';
  117. label.style.color = '#fff';
  118. label.style.fontSize = '12px';
  119. label.style.textAlign = 'center';
  120. label.style.marginTop = '50px';
  121. label.textContent = `${MIN_TOKENS} tokens`;
  122.  
  123. sliderElement.addEventListener('input', (e) => {
  124. MIN_TOKENS = parseInt(e.target.value);
  125. label.textContent = `${MIN_TOKENS} tokens`;
  126. // Save the new value to localStorage
  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 appendSlider = () => {
  136. if (document.body) {
  137. document.body.appendChild(sliderContainer);
  138. } else {
  139. setTimeout(appendSlider, 500);
  140. }
  141. };
  142. appendSlider();
  143. }
  144.  
  145. const container = document.getElementById('token-filter-container');
  146. if (container) {
  147. const isVisible = window.location.pathname === '/' || window.location.pathname === '/search';
  148. container.style.display = isVisible ? 'flex' : 'none';
  149. }
  150. }
  151.  
  152. function initialize() {
  153. createOrUpdateSlider();
  154. if (window.location.pathname === '/' || window.location.pathname === '/search') {
  155. filterCards();
  156. }
  157. }
  158.  
  159. const tryInitialize = () => {
  160. if (document.body) {
  161. initialize();
  162.  
  163. let lastPath = window.location.pathname;
  164. const checkPath = () => {
  165. if (lastPath !== window.location.pathname) {
  166. lastPath = window.location.pathname;
  167. createOrUpdateSlider();
  168. if (lastPath === '/' || lastPath === '/search') {
  169. filterCards();
  170. }
  171. }
  172. };
  173.  
  174. setInterval(checkPath, 500);
  175.  
  176. const observer = new MutationObserver(() => {
  177. if (window.location.pathname === '/' || window.location.pathname === '/search') {
  178. setTimeout(filterCards, 500);
  179. }
  180. });
  181. observer.observe(document.body, { childList: true, subtree: true });
  182. } else {
  183. setTimeout(tryInitialize, 1000);
  184. }
  185. };
  186.  
  187. tryInitialize();
  188. })();