Character.AI Auto-Requester

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

当前为 2025-02-15 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Character.AI Auto-Requester
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.9
  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: 11px;
  178. }
  179. `);
  180.  
  181. const textFieldXPath = '//*[@id="chat-body"]/div[2]/div/div/div/div[1]/textarea';
  182. const sendButtonXPath = '//*[@id="chat-body"]/div[2]/div/div/div/div[2]/button';
  183. 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';
  184.  
  185. // Создаем элементы управления
  186. const overlay = document.createElement('div');
  187. overlay.className = 'control-overlay';
  188. overlay.innerHTML = `
  189. <div class="header">
  190. <h3 style="margin:0; display:inline-block;">Character.AI Auto-Requester</h3>
  191. <button class="minimize" style="float:right;">▼</button>
  192. </div>
  193. <div class="content">
  194. <div class="slider-container">
  195. <label>Задержка голоса:</label>
  196. <input type="range" min="1" max="60" value="${delayVoice/1000}" class="voice-delay">
  197. <div class="value-display">${delayVoice/1000} сек</div>
  198. </div>
  199. <div class="slider-container">
  200. <label>Интервал сообщений:</label>
  201. <input type="range" min="3" max="120" value="${interval/1000}" class="send-interval">
  202. <div class="value-display">${interval/1000} сек</div>
  203. </div>
  204. <div class="switch-container">
  205. <label>Вкл/Выкл</label>
  206. <label class="switch">
  207. <input type="checkbox" class="toggle-auto-send">
  208. <span class="slider round"></span>
  209. </label>
  210. </div>
  211. <div class="description">
  212. Пользуйтесь аккуратно; не совсем понятно, за какие скорости могут забанить аккаунт. В любом случае даже простое использование стороннего софта типа этого, скорее всего, не приветствуется.
  213. </div>
  214. </div>
  215. `;
  216.  
  217. document.body.appendChild(overlay);
  218.  
  219. // Обработчики ползунков
  220. overlay.querySelector('.voice-delay').addEventListener('input', function(e) {
  221. delayVoice = e.target.value * 1000;
  222. e.target.nextElementSibling.textContent = `${e.target.value} сек`;
  223.  
  224. // Останавливаем таймер
  225. clearInterval(timer);
  226. overlay.querySelector('.toggle-auto-send').checked = false;
  227. });
  228.  
  229. let currentUrl = window.location.href;
  230.  
  231. overlay.querySelector('.send-interval').addEventListener('input', function(e) {
  232. interval = e.target.value * 1000;
  233. e.target.nextElementSibling.textContent = `${e.target.value} сек`;
  234.  
  235. // Останавливаем таймер
  236. clearInterval(timer);
  237. overlay.querySelector('.toggle-auto-send').checked = false;
  238. });
  239.  
  240. function clickVoiceButton() {
  241. const voiceButton = document.evaluate(
  242. voiceButtonXPath,
  243. document,
  244. null,
  245. XPathResult.FIRST_ORDERED_NODE_TYPE,
  246. null
  247. ).singleNodeValue;
  248. voiceButton?.click();
  249. }
  250.  
  251. function simulateSend() {
  252. if (window.location.href !== currentUrl) {
  253. // Останавливаем таймер
  254. clearInterval(timer);
  255. overlay.querySelector('.toggle-auto-send').checked = false;
  256.  
  257. currentUrl = window.location.href;
  258. return
  259. }
  260. const textField = document.evaluate(
  261. textFieldXPath,
  262. document,
  263. null,
  264. XPathResult.FIRST_ORDERED_NODE_TYPE,
  265. null
  266. ).singleNodeValue;
  267.  
  268. const sendButton = document.evaluate(
  269. sendButtonXPath,
  270. document,
  271. null,
  272. XPathResult.FIRST_ORDERED_NODE_TYPE,
  273. null
  274. ).singleNodeValue;
  275.  
  276. if (textField && sendButton && !sendButton.disabled) {
  277. const inputText = textField.value.trim();
  278.  
  279. if (inputText === '') {
  280. sendButton.click();
  281. textField.value = '';
  282. setTimeout(clickVoiceButton, delayVoice);
  283. }
  284. }
  285. }
  286.  
  287. // Вкл/Выкл
  288. overlay.querySelector('.toggle-auto-send').addEventListener('change', function(e) {
  289. if (e.target.checked) {
  290. // Включаем автоматическую отправку
  291. clearInterval(timer);
  292. timer = setInterval(simulateSend, interval);
  293.  
  294. // Запоминаем страницу, на которой запустили автоотправку
  295. let currentUrl = window.location.href;
  296. } else {
  297. // Выключаем автоматическую отправку
  298. clearInterval(timer);
  299. }
  300. });
  301.  
  302. // Перемещение оверлея
  303. let isDown = false;
  304. let offset = [0, 0];
  305.  
  306. // Функция для обработки начала касания
  307. function handleTouchStart(event) {
  308. if (event.touches.length === 1) { // Только одно касание
  309. isDown = true;
  310. const touch = event.touches[0];
  311. offset = [
  312. overlay.offsetLeft - touch.clientX,
  313. overlay.offsetTop - touch.clientY
  314. ];
  315. event.preventDefault(); // Предотвратить выделение текста
  316. }
  317. }
  318.  
  319. // Функция для обработки движения касания
  320. function handleTouchMove(event) {
  321. if (isDown) {
  322. const touch = event.touches[0];
  323. overlay.style.top = `${touch.clientY + offset[1]}px`;
  324. overlay.style.right = 'auto'; // Чтобы не было привязки к правому краю
  325. overlay.style.left = `${touch.clientX + offset[0]}px`;
  326. event.preventDefault(); // Предотвратить прокрутку страницы
  327. }
  328. }
  329.  
  330. // Функция для обработки окончания касания
  331. function handleTouchEnd() {
  332. isDown = false;
  333. }
  334.  
  335. // Добавление обработчиков событий для касаний
  336. overlay.querySelector('.header h3').addEventListener('touchstart', handleTouchStart);
  337. document.addEventListener('touchmove', handleTouchMove);
  338. document.addEventListener('touchend', handleTouchEnd);
  339.  
  340. // Добавление обработчиков событий для мыши (чтобы не потерять функциональность на компьютере)
  341. overlay.querySelector('.header h3').addEventListener('mousedown', function(event) {
  342. if (event.button === 0) { // Левая кнопка мыши
  343. isDown = true;
  344. offset = [
  345. overlay.offsetLeft - event.clientX,
  346. overlay.offsetTop - event.clientY
  347. ];
  348. event.preventDefault(); // Предотвратить выделение текста
  349. }
  350. });
  351.  
  352. document.addEventListener('mouseup', function() {
  353. isDown = false;
  354. });
  355.  
  356. document.addEventListener('mousemove', function(event) {
  357. if (isDown) {
  358. overlay.style.top = `${event.clientY + offset[1]}px`;
  359. overlay.style.right = 'auto'; // Чтобы не было привязки к правому краю
  360. overlay.style.left = `${event.clientX + offset[0]}px`;
  361. }
  362. });
  363. // Минимизация/сворачивание
  364. overlay.querySelector('.minimize').addEventListener('click', function() {
  365. if (overlay.classList.contains('minimized')) {
  366. overlay.classList.remove('minimized');
  367. this.textContent = '▼'; // Используем Unicode для символа
  368. } else {
  369. overlay.classList.add('minimized');
  370. this.textContent = '▲'; // Используем Unicode для символа
  371. }
  372. });
  373. })();