Character.AI Auto-Requester

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

目前為 2025-02-15 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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 для символа
        }
    });
})();