JanitorAI Enhanced UI with CSS Toggle and Auto Pagination

Adds UI controls, hides buttons, toggles custom CSS, and auto-paginates on JanitorAI

  1. // ==UserScript==
  2. // @name JanitorAI Enhanced UI with CSS Toggle and Auto Pagination
  3. // @namespace http://tampermonkey.net/
  4. // @version 3.7
  5. // @description Adds UI controls, hides buttons, toggles custom CSS, and auto-paginates on JanitorAI
  6. // @author Fefnik
  7. // @match https://janitorai.com/*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. 'use strict';
  13.  
  14. let MIN_TOKENS = parseInt(localStorage.getItem('janitorAITokenFilter')) || 500;
  15. let isSidebarHidden = localStorage.getItem('janitorAISidebarHidden') === 'true';
  16. let isAutoScrollEnabled = localStorage.getItem('janitorAIAutoScroll') !== 'false';
  17. let isMenuVisible = false;
  18. let isCustomCssDisabled = localStorage.getItem('janitorAICustomCssDisabled') === 'true';
  19. let isAutoPaginationEnabled = localStorage.getItem('janitorAIAutoPagination') !== 'false'; // Новая настройка
  20.  
  21. let sliderElement = null;
  22. let sliderContainer = null;
  23. let controlPanel = null;
  24. let controlsContainer = null;
  25. let emblaSlide = null;
  26.  
  27. const isAllowedPage = () => {
  28. const path = window.location.pathname;
  29. return path === '/' || path.startsWith('/search') || path === '/my_characters' || path.startsWith('/profiles/');
  30. };
  31.  
  32. const isChatsPage = () => window.location.pathname.startsWith('/chats');
  33.  
  34. const parseTokens = (text) => {
  35. try {
  36. text = text.replace(/<!--[\s\S]*?-->/g, '').replace('tokens', '').trim();
  37. return text.includes('k') ? parseFloat(text.replace('k', '')) * 1000 : parseInt(text, 10) || 0;
  38. } catch {
  39. return 0;
  40. }
  41. };
  42.  
  43. const filterCards = () => {
  44. document.querySelectorAll('.chakra-stack.css-1s5evre, .css-1s5evre').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 parent = card.closest('.css-1sxhvxh, .css-1dbw1r8');
  50. if (parent) parent.style.display = tokenCount < MIN_TOKENS ? 'none' : '';
  51. });
  52. };
  53.  
  54. const setupPaginationScroll = () => {
  55. document.querySelectorAll('.css-kzd6o0').forEach(button => {
  56. button.removeEventListener('click', handlePaginationClick);
  57. button.addEventListener('click', handlePaginationClick);
  58. });
  59. };
  60.  
  61. const handlePaginationClick = () => {
  62. if (isAutoScrollEnabled) {
  63. setTimeout(() => window.scrollTo({ top: 0, behavior: 'smooth' }), 300);
  64. }
  65. };
  66.  
  67. // Функция для поиска следующей страницы
  68. const getNextPageElement = () => {
  69. const currentPage = document.querySelector('.css-1xdrgup');
  70. if (!currentPage) return null;
  71.  
  72. let nextElement = currentPage.nextElementSibling;
  73. while (nextElement) {
  74. if (nextElement.classList.contains('css-kzd6o0') || nextElement.classList.contains('css-15aspjy')) {
  75. return nextElement;
  76. }
  77. nextElement = nextElement.nextElementSibling;
  78. }
  79. return null;
  80. };
  81.  
  82. // Проверка полного достижения конца страницы
  83. const isAtVeryBottom = () => {
  84. const scrollPosition = window.scrollY + window.innerHeight;
  85. const pageHeight = document.documentElement.scrollHeight;
  86. return pageHeight - scrollPosition <= 1;
  87. };
  88.  
  89. // Логика автоперехода
  90. const setupAutoPagination = () => {
  91. let isNavigating = false;
  92. let scrollCount = 0;
  93. const requiredScrolls = 3;
  94.  
  95. window.addEventListener('wheel', function(event) {
  96. if (isAutoPaginationEnabled && event.deltaY > 0 && isAtVeryBottom() && !isNavigating) {
  97. scrollCount++;
  98. if (scrollCount >= requiredScrolls) {
  99. const nextPage = getNextPageElement();
  100. if (nextPage) {
  101. isNavigating = true;
  102. nextPage.click();
  103. setTimeout(() => {
  104. isNavigating = false;
  105. scrollCount = 0;
  106. }, 2000);
  107. }
  108. }
  109. } else if (!isAtVeryBottom()) {
  110. scrollCount = 0;
  111. }
  112. }, { passive: true });
  113. };
  114.  
  115. const toggleSidebar = () => {
  116. const sidebar = document.querySelector('.css-h988mi');
  117. const css70qvj9 = document.querySelector('.css-70qvj9');
  118.  
  119. if (sidebar) {
  120. isSidebarHidden = !isSidebarHidden;
  121. sidebar.style.display = isSidebarHidden ? 'none' : '';
  122.  
  123. if (!emblaSlide) {
  124. emblaSlide = document.querySelector('.is-in-view.is-snapped.embla__slide');
  125. }
  126. if (emblaSlide) {
  127. emblaSlide.style.display = isSidebarHidden ? 'none' : '';
  128. }
  129.  
  130. if (css70qvj9) {
  131. css70qvj9.style.display = isSidebarHidden ? 'none' : '';
  132. }
  133.  
  134. localStorage.setItem('janitorAISidebarHidden', isSidebarHidden);
  135. updateControlText();
  136. }
  137. };
  138.  
  139. const toggleAutoScroll = () => {
  140. isAutoScrollEnabled = !isAutoScrollEnabled;
  141. localStorage.setItem('janitorAIAutoScroll', isAutoScrollEnabled);
  142. updateControlText();
  143. };
  144.  
  145. const toggleAutoPagination = () => {
  146. isAutoPaginationEnabled = !isAutoPaginationEnabled;
  147. localStorage.setItem('janitorAIAutoPagination', isAutoPaginationEnabled);
  148. updateControlText();
  149. };
  150.  
  151. const toggleMenu = () => {
  152. isMenuVisible = !isMenuVisible;
  153. updateElementsVisibility();
  154. updateControlText();
  155. };
  156.  
  157. const toggleCustomCss = () => {
  158. isCustomCssDisabled = !isCustomCssDisabled;
  159. localStorage.setItem('janitorAICustomCssDisabled', isCustomCssDisabled);
  160. applyCustomCssToggle();
  161. updateControlText();
  162. };
  163.  
  164. const applyCustomCssToggle = () => {
  165. if (isCustomCssDisabled) {
  166. removeCustomStyles();
  167. blockCustomElements();
  168. }
  169. };
  170.  
  171. const removeCustomStyles = () => {
  172. const styles = document.querySelectorAll('body style');
  173. styles.forEach(style => style.remove());
  174. };
  175.  
  176. const blockCustomElements = () => {
  177. document.querySelectorAll('.css-1bn1yyx').forEach(element => {
  178. element.style.display = 'none';
  179. });
  180.  
  181. document.querySelectorAll('*').forEach(element => {
  182. const style = window.getComputedStyle(element);
  183. const bgImage = style.backgroundImage;
  184. if (bgImage && bgImage.includes('ella.janitorai.com/background-image/')) {
  185. element.style.backgroundImage = 'none';
  186. }
  187. });
  188. };
  189.  
  190. const updateControlText = () => {
  191. const sidebarText = document.getElementById('sidebar-toggle-text');
  192. const scrollText = document.getElementById('auto-scroll-text');
  193. const paginationText = document.getElementById('auto-pagination-text');
  194. const cssToggleText = document.getElementById('css-toggle-text');
  195. if (sidebarText) {
  196. sidebarText.textContent = isSidebarHidden ? 'Topbar: OFF' : 'Topbar: ON';
  197. sidebarText.style.color = isSidebarHidden ? '#fff' : '#ccc';
  198. }
  199. if (scrollText) {
  200. scrollText.textContent = `Auto-Scroll: ${isAutoScrollEnabled ? 'ON' : 'OFF'}`;
  201. scrollText.style.color = isAutoScrollEnabled ? '#fff' : '#ccc';
  202. }
  203. if (paginationText) {
  204. paginationText.textContent = `Auto-Page: ${isAutoPaginationEnabled ? 'ON' : 'OFF'}`;
  205. paginationText.style.color = isAutoPaginationEnabled ? '#fff' : '#ccc';
  206. }
  207. if (cssToggleText) {
  208. cssToggleText.textContent = `Custom CSS: ${isCustomCssDisabled ? 'OFF' : 'ON'}`;
  209. cssToggleText.style.color = isCustomCssDisabled ? '#fff' : '#ccc';
  210. }
  211. };
  212.  
  213. const createControlPanel = () => {
  214. if (controlPanel) return;
  215.  
  216. controlPanel = document.createElement('div');
  217. controlPanel.id = 'janitor-control-panel';
  218. Object.assign(controlPanel.style, {
  219. position: 'fixed',
  220. top: '75px',
  221. left: '10px',
  222. zIndex: '100000',
  223. display: 'flex',
  224. flexDirection: 'column',
  225. gap: '5px',
  226. alignItems: 'flex-start'
  227. });
  228.  
  229. const settingsButton = document.createElement('button');
  230. settingsButton.id = 'token-filter-toggle';
  231. settingsButton.textContent = '⚙️';
  232. Object.assign(settingsButton.style, {
  233. width: '30px',
  234. height: '30px',
  235. padding: '0',
  236. backgroundColor: 'rgba(74, 74, 74, 0.7)',
  237. color: '#fff',
  238. border: 'none',
  239. borderRadius: '5px',
  240. cursor: 'pointer',
  241. display: 'flex',
  242. alignItems: 'center',
  243. justifyContent: 'center',
  244. fontSize: '16px',
  245. transition: 'background-color 0.2s'
  246. });
  247. settingsButton.addEventListener('click', toggleMenu);
  248.  
  249. controlsContainer = document.createElement('div');
  250. controlsContainer.id = 'controls-container';
  251. Object.assign(controlsContainer.style, {
  252. display: 'none',
  253. flexDirection: 'column',
  254. gap: '5px',
  255. backgroundColor: 'rgba(74, 74, 74, 0.7)',
  256. padding: '5px',
  257. borderRadius: '5px',
  258. zIndex: '100001'
  259. });
  260.  
  261. const sidebarText = document.createElement('span');
  262. sidebarText.id = 'sidebar-toggle-text';
  263. sidebarText.style.cursor = 'pointer';
  264. sidebarText.style.fontSize = '12px';
  265. sidebarText.addEventListener('click', toggleSidebar);
  266.  
  267. const scrollText = document.createElement('span');
  268. scrollText.id = 'auto-scroll-text';
  269. scrollText.style.cursor = 'pointer';
  270. scrollText.style.fontSize = '12px';
  271. scrollText.addEventListener('click', toggleAutoScroll);
  272.  
  273. const paginationText = document.createElement('span');
  274. paginationText.id = 'auto-pagination-text';
  275. paginationText.style.cursor = 'pointer';
  276. paginationText.style.fontSize = '12px';
  277. paginationText.addEventListener('click', toggleAutoPagination);
  278.  
  279. const cssToggleText = document.createElement('span');
  280. cssToggleText.id = 'css-toggle-text';
  281. cssToggleText.style.cursor = 'pointer';
  282. cssToggleText.style.fontSize = '12px';
  283. cssToggleText.addEventListener('click', toggleCustomCss);
  284.  
  285. controlsContainer.appendChild(sidebarText);
  286. controlsContainer.appendChild(scrollText);
  287. controlsContainer.appendChild(paginationText);
  288. controlsContainer.appendChild(cssToggleText);
  289. controlPanel.appendChild(settingsButton);
  290. controlPanel.appendChild(controlsContainer);
  291. document.body.appendChild(controlPanel);
  292. updateControlText();
  293. };
  294.  
  295. const createOrUpdateSlider = () => {
  296. if (sliderElement) return;
  297.  
  298. sliderContainer = document.createElement('div');
  299. sliderContainer.id = 'token-filter-container';
  300. Object.assign(sliderContainer.style, {
  301. position: 'fixed',
  302. top: '75px',
  303. left: '50px',
  304. zIndex: '100002',
  305. display: 'none',
  306. flexDirection: 'row',
  307. alignItems: 'center',
  308. gap: '10px',
  309. padding: '5px',
  310. backgroundColor: 'rgba(74, 74, 74, 0.7)',
  311. borderRadius: '5px'
  312. });
  313.  
  314. sliderElement = document.createElement('input');
  315. sliderElement.type = 'range';
  316. sliderElement.id = 'token-filter-slider';
  317. Object.assign(sliderElement, {
  318. min: '0',
  319. max: '6000',
  320. step: '100',
  321. value: MIN_TOKENS
  322. });
  323. Object.assign(sliderElement.style, {
  324. width: '150px',
  325. height: '20px',
  326. backgroundColor: '#4a4a4a',
  327. cursor: 'pointer',
  328. appearance: 'none',
  329. outline: 'none',
  330. borderRadius: '5px',
  331. padding: '0',
  332. zIndex: '100003'
  333. });
  334.  
  335. const style = document.createElement('style');
  336. style.textContent = `
  337. #token-filter-slider {
  338. -webkit-appearance: none;
  339. appearance: none;
  340. background: #4a4a4a;
  341. border-radius: 5px;
  342. z-index: 100003;
  343. }
  344. #token-filter-slider::-webkit-slider-thumb {
  345. -webkit-appearance: none;
  346. appearance: none;
  347. width: 10px;
  348. height: 20px;
  349. background: #ffffff;
  350. cursor: pointer;
  351. border-radius: 50%;
  352. border: 2px solid #000;
  353. box-shadow: 0 0 2px rgba(0,0,0,0.5);
  354. transform: translateY(0px);
  355. z-index: 100004;
  356. }
  357. #token-filter-slider::-moz-range-thumb {
  358. width: 20px;
  359. height: 20px;
  360. background: #ffffff;
  361. cursor: pointer;
  362. border-radius: 50%;
  363. border: 2px solid #000;
  364. box-shadow: 0 0 2px rgba(0,0,0,0.5);
  365. transform: translateY(-10px);
  366. z-index: 100004;
  367. }
  368. #token-filter-slider::-webkit-slider-runnable-track {
  369. height: '10px',
  370. background: #4a4a4a;
  371. border-radius: 5px;
  372. }
  373. #token-filter-slider::-moz-range-track {
  374. height: '10px',
  375. background: #4a4a4a;
  376. border-radius: 5px;
  377. }
  378. `;
  379. document.head.appendChild(style);
  380.  
  381. const label = document.createElement('span');
  382. label.id = 'token-filter-label';
  383. label.style.color = '#fff';
  384. label.style.fontSize = '12px';
  385. label.style.minWidth = '60px';
  386. label.textContent = `${MIN_TOKENS} tokens`;
  387.  
  388. sliderElement.addEventListener('input', (e) => {
  389. MIN_TOKENS = parseInt(e.target.value);
  390. label.textContent = `${MIN_TOKENS} tokens`;
  391. localStorage.setItem('janitorAITokenFilter', MIN_TOKENS);
  392. filterCards();
  393. });
  394.  
  395. sliderContainer.appendChild(sliderElement);
  396. sliderContainer.appendChild(label);
  397. document.body.appendChild(sliderContainer);
  398. };
  399.  
  400. const applySidebarState = () => {
  401. const sidebar = document.querySelector('.css-h988mi');
  402. const css70qvj9 = document.querySelector('.css-70qvj9');
  403. emblaSlide = document.querySelector('.is-in-view.is-snapped.embla__slide');
  404.  
  405. if (sidebar && isSidebarHidden) {
  406. sidebar.style.display = 'none';
  407. if (emblaSlide) emblaSlide.style.display = 'none';
  408. if (css70qvj9) css70qvj9.style.display = 'none';
  409. }
  410. };
  411.  
  412. const updateElementsVisibility = () => {
  413. const shouldShow = isAllowedPage() && !isChatsPage();
  414. if (controlPanel) controlPanel.style.display = shouldShow ? 'flex' : 'none';
  415. if (sliderContainer) sliderContainer.style.display = shouldShow && isMenuVisible ? 'flex' : 'none';
  416. if (controlsContainer) controlsContainer.style.display = shouldShow && isMenuVisible ? 'flex' : 'none';
  417. };
  418.  
  419. const initialize = () => {
  420. createControlPanel();
  421. createOrUpdateSlider();
  422. applySidebarState();
  423. updateElementsVisibility();
  424. setupAutoPagination(); // Инициализация автоперехода
  425.  
  426. if (isAllowedPage() && !isChatsPage()) {
  427. filterCards();
  428. setupPaginationScroll();
  429. applyCustomCssToggle();
  430.  
  431. new MutationObserver(() => {
  432. filterCards();
  433. setupPaginationScroll();
  434. applyCustomCssToggle();
  435. applySidebarState();
  436. }).observe(document.body, { childList: true, subtree: true });
  437.  
  438. const originalAppendChild = Element.prototype.appendChild;
  439. Element.prototype.appendChild = function(node) {
  440. if (isCustomCssDisabled && node.tagName === 'STYLE' && this.tagName === 'HEAD') {
  441. return node;
  442. }
  443. return originalAppendChild.call(this, node);
  444. };
  445. }
  446. };
  447.  
  448. const tryInitialize = () => {
  449. if (document.body) {
  450. initialize();
  451. let lastPath = window.location.pathname;
  452. setInterval(() => {
  453. if (lastPath !== window.location.pathname) {
  454. lastPath = window.location.pathname;
  455. updateElementsVisibility();
  456. if (isAllowedPage() && !isChatsPage()) {
  457. filterCards();
  458. setupPaginationScroll();
  459. applyCustomCssToggle();
  460. applySidebarState();
  461. }
  462. }
  463. }, 500);
  464. } else {
  465. setTimeout(tryInitialize, 1000);
  466. }
  467. };
  468.  
  469. tryInitialize();
  470. })();