Character.AI Auto-Requester

Автоматическая отправка сообщений с настройкой интервалов

  1. // ==UserScript==
  2. // @name Character.AI Auto-Requester
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.95555556
  5. // @license GNU GPLv3
  6. // @description Автоматическая отправка сообщений с настройкой интервалов
  7. // @match https://character.ai/chat*
  8. // @author xPress
  9. // @grant GM_addStyle
  10. // ==/UserScript==
  11.  
  12. //TODO: Слайдер для .control-overlay scale
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. // Начальные значения
  18. let delayVoice = 10000;
  19. let interval = 30000;
  20. let timer;
  21.  
  22. // Стили для оверлея
  23. GM_addStyle(`
  24. .control-overlay {
  25. user-select: none;
  26. position: absolute;
  27. top: 50%; /* или любое другое фиксированное значение */
  28.  
  29. left: 50%;
  30. background: rgba(255,255,255,0.8);
  31. padding: 15px;
  32. border-radius: 8px;
  33. color: black;
  34. z-index: 9999;
  35. min-width: 250px;
  36. width: 350px;
  37. max-height: 800px; /* Максимальная высота */
  38. transition: max-height 0.4s ease-in-out;
  39. transform: translate(-50%, -100px) scale(0.9);
  40. transform-origin: top left; /* или center, в зависимости от нужного эффекта */
  41.  
  42. }
  43. .slider-container {
  44. margin: 10px 0;
  45. }
  46. input[type="range"] {
  47. width: 100%;
  48. margin: 5px 0;
  49. -webkit-appearance: none;
  50. background: transparent;
  51. }
  52. input[type="range"]::-webkit-slider-thumb {
  53. -webkit-appearance: none;
  54. height: 16px;
  55. width: 16px;
  56. border-radius: 50%;
  57. background: lime;
  58. cursor: pointer;
  59. margin-top: -6px;
  60. }
  61. input[type="range"]::-moz-range-thumb {
  62. height: 16px;
  63. width: 16px;
  64. border-radius: 50%;
  65. background: lime;
  66. cursor: pointer;
  67. }
  68. input[type="range"]::-webkit-slider-runnable-track {
  69. width: 100%;
  70. height: 4px;
  71. cursor: pointer;
  72. background: #c9c9c9;
  73. border-radius: 2px;
  74. }
  75. input[type="range"]::-moz-range-track {
  76. width: 100%;
  77. height: 4px;
  78. cursor: pointer;
  79. background: #c9c9c9;
  80. border-radius: 2px;
  81. }
  82. .value-display {
  83. font-size: 14px;
  84. margin-top: 5px;
  85. }
  86. .header {
  87. cursor: move;
  88. padding: 0 10px;
  89. background: rgba(255,255,255,0.5);
  90. border-bottom: 1px solid #333;
  91. }
  92. .minimized {
  93. max-height: 53px;
  94. overflow: hidden;
  95. }
  96.  
  97. .control-overlay:not(.minimized) .content {
  98. opacity: 1;
  99. transition: opacity 0.4s ease-in-out;
  100. }
  101.  
  102. .control-overlay.minimized .content {
  103. opacity: 0;
  104. transition: opacity 0.4s ease-in-out;
  105. }
  106.  
  107. .switch-container {
  108. margin: 10px 0;
  109. }
  110.  
  111. .switch {
  112. position: relative;
  113. display: inline-block;
  114. width: 60px;
  115. height: 34px;
  116. }
  117.  
  118. .switch-container label {
  119. margin: 15px 35px;
  120. font-size: 24px;
  121. margin-top: 23px;
  122. }
  123.  
  124. .switch input {
  125. opacity: 0;
  126. width: 0;
  127. height: 0;
  128. }
  129.  
  130. .slider {
  131. position: absolute;
  132. cursor: pointer;
  133. top: 0;
  134. left: 0;
  135. right: 0;
  136. bottom: 0;
  137. background-color: #ccc;
  138. -webkit-transition: .4s;
  139. transition: .4s;
  140. }
  141.  
  142. .slider:before {
  143. position: absolute;
  144. content: "";
  145. height: 26px;
  146. width: 26px;
  147. left: 4px;
  148. bottom: 4px;
  149. background-color: white;
  150. -webkit-transition: .4s;
  151. transition: .4s;
  152. }
  153.  
  154. input:checked + .slider {
  155. background-color: lime;
  156. }
  157.  
  158. input:focus + .slider {
  159. box-shadow: 0 0 1px lime;
  160. }
  161.  
  162. input:checked + .slider:before {
  163. -webkit-transform: translateX(26px);
  164. -ms-transform: translateX(26px);
  165. transform: translateX(26px);
  166. }
  167.  
  168. .slider.round {
  169. border-radius: 34px;
  170. }
  171.  
  172. .slider.round:before {
  173. border-radius: 50%;
  174. }
  175.  
  176. .description {
  177. font-size: 12px;
  178. text-indent: 25px;
  179. line-height: 11px;
  180. color: gray;
  181. }
  182. .description p {
  183. margin-top: 5px; /* Отступ сверху */
  184. margin-bottom: 5px; /* Отступ снизу */
  185. color: gray
  186. }
  187.  
  188. `);
  189.  
  190. const textFieldXPath = '//*[@id="chat-body"]/div[2]/div/div/div/div[1]/textarea';
  191. const sendButtonXPath = '//*[@id="chat-body"]/div[2]/div/div/div/div[2]/button';
  192. const voiceButtonXPath = '//*[@id="chat-messages"]/div[1]/div[1]/div/div/div[1]/div/div[1]/div[1]/div[2]/div[1]/div[2]/div[2]/div/button';
  193.  
  194. // Создаем элементы управления
  195. const overlay = document.createElement('div');
  196. overlay.className = 'control-overlay';
  197. overlay.innerHTML = `
  198. <div class="header">
  199. <h3 style="margin:0; display:inline-block;">Character.AI Auto-Requester</h3>
  200. <button class="minimize" style="float:right;">▼</button>
  201. </div>
  202. <div class="content">
  203. <div class="slider-container">
  204. <label>Задержка голоса:</label>
  205. <input type="range" min="1" max="60" value="${delayVoice/1000}" class="voice-delay">
  206. <div class="value-display">${delayVoice/1000} сек</div>
  207. </div>
  208. <div class="slider-container">
  209. <label>Интервал сообщений:</label>
  210. <input type="range" min="3" max="120" value="${interval/1000}" class="send-interval">
  211. <div class="value-display">${interval/1000} сек</div>
  212. </div>
  213. <div class="switch-container">
  214. <label>Вкл/Выкл</label>
  215. <label class="switch">
  216. <input type="checkbox" class="toggle-auto-send">
  217. <span class="slider round"></span>
  218. </label>
  219. </div>
  220. <div class="description">
  221. Пользуйтесь аккуратно; не совсем понятно, за какие скорости могут забанить аккаунт. В любом случае даже простое использование стороннего софта типа этого, скорее всего, не приветствуется.
  222. <p>
  223. Осторожно, смартфон, например, может не отключать экран и не блокироваться из-за работающего скрипта. Даже в состоянии "Выкл".
  224. <p>
  225. Если нужна тёмная тема - включайте DarkReader; если хочется какую-то другую - Stylus. Спасибо за понимание.
  226. </div>
  227. </div>
  228. `;
  229.  
  230. document.body.appendChild(overlay);
  231.  
  232. // Обработчики ползунков
  233. overlay.querySelector('.voice-delay').addEventListener('input', function(e) {
  234. delayVoice = e.target.value * 1000;
  235. e.target.nextElementSibling.textContent = `${e.target.value} сек`;
  236.  
  237. // Останавливаем таймер
  238. clearInterval(timer);
  239. overlay.querySelector('.toggle-auto-send').checked = false;
  240. });
  241.  
  242. let currentUrl = window.location.href;
  243.  
  244. overlay.querySelector('.send-interval').addEventListener('input', function(e) {
  245. interval = e.target.value * 1000;
  246. e.target.nextElementSibling.textContent = `${e.target.value} сек`;
  247.  
  248. // Останавливаем таймер
  249. clearInterval(timer);
  250. overlay.querySelector('.toggle-auto-send').checked = false;
  251. });
  252.  
  253. function clickVoiceButton() {
  254. const voiceButton = document.evaluate(
  255. voiceButtonXPath,
  256. document,
  257. null,
  258. XPathResult.FIRST_ORDERED_NODE_TYPE,
  259. null
  260. ).singleNodeValue;
  261. voiceButton?.click();
  262. }
  263.  
  264. function simulateSend() {
  265. if (window.location.href !== currentUrl) {
  266. // Останавливаем таймер
  267. clearInterval(timer);
  268. overlay.querySelector('.toggle-auto-send').checked = false;
  269.  
  270. currentUrl = window.location.href;
  271. return
  272. }
  273. const textField = document.evaluate(
  274. textFieldXPath,
  275. document,
  276. null,
  277. XPathResult.FIRST_ORDERED_NODE_TYPE,
  278. null
  279. ).singleNodeValue;
  280.  
  281. const sendButton = document.evaluate(
  282. sendButtonXPath,
  283. document,
  284. null,
  285. XPathResult.FIRST_ORDERED_NODE_TYPE,
  286. null
  287. ).singleNodeValue;
  288.  
  289. if (textField && sendButton && !sendButton.disabled) {
  290. const inputText = textField.value.trim();
  291.  
  292. if (inputText === '') {
  293. sendButton.click();
  294. textField.value = '';
  295. setTimeout(clickVoiceButton, delayVoice);
  296. }
  297. }
  298. }
  299.  
  300. // Вкл/Выкл
  301. overlay.querySelector('.toggle-auto-send').addEventListener('change', function(e) {
  302. if (e.target.checked) {
  303. // Включаем автоматическую отправку
  304. clearInterval(timer);
  305. timer = setInterval(simulateSend, interval);
  306.  
  307. // Запоминаем страницу, на которой запустили автоотправку
  308. let currentUrl = window.location.href;
  309. } else {
  310. // Выключаем автоматическую отправку
  311. clearInterval(timer);
  312. }
  313. });
  314.  
  315. // Перемещение оверлея
  316. let isDown = false;
  317. let offset = [0, 0];
  318.  
  319. // Функция для обработки начала касания
  320. function handleTouchStart(event) {
  321. if (event.touches.length === 1) { // Только одно касание
  322. isDown = true;
  323. const touch = event.touches[0];
  324. offset = [
  325. overlay.offsetLeft - touch.clientX,
  326. overlay.offsetTop - touch.clientY
  327. ];
  328. event.preventDefault(); // Предотвратить выделение текста
  329. }
  330. }
  331.  
  332. // Функция для обработки движения касания
  333. function handleTouchMove(event) {
  334. if (isDown) {
  335. const touch = event.touches[0];
  336. overlay.style.top = `${touch.clientY + offset[1]}px`;
  337. overlay.style.right = 'auto'; // Чтобы не было привязки к правому краю
  338. overlay.style.left = `${touch.clientX + offset[0]}px`;
  339. event.preventDefault(); // Предотвратить прокрутку страницы
  340. }
  341. }
  342.  
  343. // Функция для обработки окончания касания
  344. function handleTouchEnd() {
  345. isDown = false;
  346. }
  347.  
  348. // Добавление обработчиков событий для касаний
  349. overlay.querySelector('.header h3').addEventListener('touchstart', handleTouchStart);
  350. document.addEventListener('touchmove', handleTouchMove);
  351. document.addEventListener('touchend', handleTouchEnd);
  352.  
  353. // Добавление обработчиков событий для мыши (чтобы не потерять функциональность на компьютере)
  354. overlay.querySelector('.header h3').addEventListener('mousedown', function(event) {
  355. if (event.button === 0) { // Левая кнопка мыши
  356. isDown = true;
  357. offset = [
  358. overlay.offsetLeft - event.clientX,
  359. overlay.offsetTop - event.clientY
  360. ];
  361. event.preventDefault(); // Предотвратить выделение текста
  362. }
  363. });
  364.  
  365. document.addEventListener('mouseup', function() {
  366. isDown = false;
  367. });
  368.  
  369. document.addEventListener('mousemove', function(event) {
  370. if (isDown) {
  371. overlay.style.top = `${event.clientY + offset[1]}px`;
  372. overlay.style.right = 'auto'; // Чтобы не было привязки к правому краю
  373. overlay.style.left = `${event.clientX + offset[0]}px`;
  374. }
  375. });
  376. // Минимизация/сворачивание
  377. overlay.querySelector('.minimize').addEventListener('click', function() {
  378. if (overlay.classList.contains('minimized')) {
  379. overlay.classList.remove('minimized');
  380. this.textContent = '▼'; // Используем Unicode для символа
  381. } else {
  382. overlay.classList.add('minimized');
  383. this.textContent = '▲'; // Используем Unicode для символа
  384. }
  385. });
  386. })();