Character.AI Auto-Requester

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

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Character.AI Auto-Requester
// @namespace    http://tampermonkey.net/
// @version      2.9555
// @license       GNU GPLv3
// @description  Автоматическая отправка сообщений с настройкой интервалов
// @match        https://character.ai/chat*
// @author       xPress
// @grant        GM_addStyle
// ==/UserScript==

//TODO: Слайдер для .control-overlay scale

(function() {
    'use strict';

    // Начальные значения
    let delayVoice = 10000;
    let interval = 30000;
    let timer;

    // Стили для оверлея
    GM_addStyle(`
        .control-overlay {
            user-select: none;
            position: absolute;
            top: 50%; /* или любое другое фиксированное значение */

            left: 50%;
            background: rgba(255,255,255,0.8);
            padding: 15px;
            border-radius: 8px;
            color: black;
            z-index: 9999;
            min-width: 250px;
            width: 350px;
            max-height: 800px; /* Максимальная высота */
            transition: max-height 0.4s ease-in-out;
            transform: translate(-50%, -100px) scale(0.9);
            transform-origin: top left; /* или center, в зависимости от нужного эффекта */

        }
        .slider-container {
            margin: 10px 0;
        }
        input[type="range"] {
            width: 100%;
            margin: 5px 0;
            -webkit-appearance: none;
            background: transparent;
        }
        input[type="range"]::-webkit-slider-thumb {
            -webkit-appearance: none;
            height: 16px;
            width: 16px;
            border-radius: 50%;
            background: lime;
            cursor: pointer;
            margin-top: -6px;
        }
        input[type="range"]::-moz-range-thumb {
            height: 16px;
            width: 16px;
            border-radius: 50%;
            background: lime;
            cursor: pointer;
        }
        input[type="range"]::-webkit-slider-runnable-track {
            width: 100%;
            height: 4px;
            cursor: pointer;
            background: #c9c9c9;
            border-radius: 2px;
        }
        input[type="range"]::-moz-range-track {
            width: 100%;
            height: 4px;
            cursor: pointer;
            background: #c9c9c9;
            border-radius: 2px;
        }
        .value-display {
            font-size: 14px;
            margin-top: 5px;
        }
        .header {
            cursor: move;
            padding: 0 10px;
            background: rgba(255,255,255,0.5);
            border-bottom: 1px solid #333;
        }
        .minimized {
            max-height: 53px;
            overflow: hidden;
        }

        .control-overlay:not(.minimized) .content {
            opacity: 1;
            transition: opacity 0.4s ease-in-out;
        }

        .control-overlay.minimized .content {
            opacity: 0;
            transition: opacity 0.4s ease-in-out;
        }

        .switch-container {
            margin: 10px 0;
        }

        .switch {
            position: relative;
            display: inline-block;
            width: 60px;
            height: 34px;
        }

        .switch-container label {
            margin: 15px 35px;
            font-size: 24px;
            margin-top: 23px;
        }

        .switch input {
            opacity: 0;
            width: 0;
            height: 0;
        }

        .slider {
            position: absolute;
            cursor: pointer;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-color: #ccc;
            -webkit-transition: .4s;
            transition: .4s;
        }

        .slider:before {
            position: absolute;
            content: "";
            height: 26px;
            width: 26px;
            left: 4px;
            bottom: 4px;
            background-color: white;
            -webkit-transition: .4s;
            transition: .4s;
        }

        input:checked + .slider {
            background-color: lime;
        }

        input:focus + .slider {
            box-shadow: 0 0 1px lime;
        }

        input:checked + .slider:before {
            -webkit-transform: translateX(26px);
            -ms-transform: translateX(26px);
            transform: translateX(26px);
        }

        .slider.round {
            border-radius: 34px;
        }

        .slider.round:before {
            border-radius: 50%;
        }

        .description {
            font-size: 11px;
            text-indent: 25px;
        }
    `);

    const textFieldXPath = '//*[@id="chat-body"]/div[2]/div/div/div/div[1]/textarea';
    const sendButtonXPath = '//*[@id="chat-body"]/div[2]/div/div/div/div[2]/button';
    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';

    // Создаем элементы управления
    const overlay = document.createElement('div');
    overlay.className = 'control-overlay';
    overlay.innerHTML = `
        <div class="header">
            <h3 style="margin:0; display:inline-block;">Character.AI Auto-Requester</h3>
            <button class="minimize" style="float:right;">▼</button>
        </div>
        <div class="content">
            <div class="slider-container">
                <label>Задержка голоса:</label>
                <input type="range" min="1" max="60" value="${delayVoice/1000}" class="voice-delay">
                <div class="value-display">${delayVoice/1000} сек</div>
            </div>
            <div class="slider-container">
                <label>Интервал сообщений:</label>
                <input type="range" min="3" max="120" value="${interval/1000}" class="send-interval">
                <div class="value-display">${interval/1000} сек</div>
            </div>
            <div class="switch-container">
                <label>Вкл/Выкл</label>
                <label class="switch">
                    <input type="checkbox" class="toggle-auto-send">
                    <span class="slider round"></span>
                </label>
            </div>
            <div class="description">
                Пользуйтесь аккуратно; не совсем понятно, за какие скорости могут забанить аккаунт. В любом случае даже простое использование стороннего софта типа этого, скорее всего, не приветствуется.
                <p>
                Осторожно, смартфон, например, может не отключать экран и не блокироваться из-за работающего скрипта. Даже в состоянии "Выкл".
            </div>
        </div>
    `;

    document.body.appendChild(overlay);

    // Обработчики ползунков
    overlay.querySelector('.voice-delay').addEventListener('input', function(e) {
        delayVoice = e.target.value * 1000;
        e.target.nextElementSibling.textContent = `${e.target.value} сек`;

        // Останавливаем таймер
        clearInterval(timer);
        overlay.querySelector('.toggle-auto-send').checked = false;
    });

    let currentUrl = window.location.href;

    overlay.querySelector('.send-interval').addEventListener('input', function(e) {
        interval = e.target.value * 1000;
        e.target.nextElementSibling.textContent = `${e.target.value} сек`;

        // Останавливаем таймер
        clearInterval(timer);
        overlay.querySelector('.toggle-auto-send').checked = false;
    });

    function clickVoiceButton() {
        const voiceButton = document.evaluate(
            voiceButtonXPath,
            document,
            null,
            XPathResult.FIRST_ORDERED_NODE_TYPE,
            null
        ).singleNodeValue;
        voiceButton?.click();
    }

    function simulateSend() {
        if (window.location.href !== currentUrl) {
            // Останавливаем таймер
            clearInterval(timer);
            overlay.querySelector('.toggle-auto-send').checked = false;

            currentUrl = window.location.href;
            return
        }
        const textField = document.evaluate(
            textFieldXPath,
            document,
            null,
            XPathResult.FIRST_ORDERED_NODE_TYPE,
            null
        ).singleNodeValue;

        const sendButton = document.evaluate(
            sendButtonXPath,
            document,
            null,
            XPathResult.FIRST_ORDERED_NODE_TYPE,
            null
        ).singleNodeValue;

        if (textField && sendButton && !sendButton.disabled) {
            const inputText = textField.value.trim();

            if (inputText === '') {
                sendButton.click();
                textField.value = '';
                setTimeout(clickVoiceButton, delayVoice);
            }
        }
    }

    // Вкл/Выкл
    overlay.querySelector('.toggle-auto-send').addEventListener('change', function(e) {
        if (e.target.checked) {
            // Включаем автоматическую отправку
            clearInterval(timer);
            timer = setInterval(simulateSend, interval);

            // Запоминаем страницу, на которой запустили автоотправку
            let currentUrl = window.location.href;
        } else {
            // Выключаем автоматическую отправку
            clearInterval(timer);
        }
    });

    // Перемещение оверлея
    let isDown = false;
    let offset = [0, 0];

    // Функция для обработки начала касания
    function handleTouchStart(event) {
        if (event.touches.length === 1) { // Только одно касание
            isDown = true;
            const touch = event.touches[0];
            offset = [
                overlay.offsetLeft - touch.clientX,
                overlay.offsetTop - touch.clientY
            ];
            event.preventDefault(); // Предотвратить выделение текста
        }
    }

    // Функция для обработки движения касания
    function handleTouchMove(event) {
        if (isDown) {
            const touch = event.touches[0];
            overlay.style.top = `${touch.clientY + offset[1]}px`;
            overlay.style.right = 'auto'; // Чтобы не было привязки к правому краю
            overlay.style.left = `${touch.clientX + offset[0]}px`;
            event.preventDefault(); // Предотвратить прокрутку страницы
        }
    }

    // Функция для обработки окончания касания
    function handleTouchEnd() {
        isDown = false;
    }

    // Добавление обработчиков событий для касаний
    overlay.querySelector('.header h3').addEventListener('touchstart', handleTouchStart);
    document.addEventListener('touchmove', handleTouchMove);
    document.addEventListener('touchend', handleTouchEnd);

    // Добавление обработчиков событий для мыши (чтобы не потерять функциональность на компьютере)
    overlay.querySelector('.header h3').addEventListener('mousedown', function(event) {
        if (event.button === 0) { // Левая кнопка мыши
            isDown = true;
            offset = [
                overlay.offsetLeft - event.clientX,
                overlay.offsetTop - event.clientY
            ];
            event.preventDefault(); // Предотвратить выделение текста
        }
    });

    document.addEventListener('mouseup', function() {
        isDown = false;
    });

    document.addEventListener('mousemove', function(event) {
        if (isDown) {
            overlay.style.top = `${event.clientY + offset[1]}px`;
            overlay.style.right = 'auto'; // Чтобы не было привязки к правому краю
            overlay.style.left = `${event.clientX + offset[0]}px`;
        }
    });
    // Минимизация/сворачивание
    overlay.querySelector('.minimize').addEventListener('click', function() {
        if (overlay.classList.contains('minimized')) {
            overlay.classList.remove('minimized');
            this.textContent = '▼'; // Используем Unicode для символа
        } else {
            overlay.classList.add('minimized');
            this.textContent = '▲'; // Используем Unicode для символа
        }
    });
})();