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