Auto Leave Conversations

Автоматически покидает выбранные переписки.

// ==UserScript==
// @name         Auto Leave Conversations
// @namespace    http://tampermonkey.net/
// @version      1.3
// @description  Автоматически покидает выбранные переписки.
// @author       eretly
// @match        https://zelenka.guru/conversations/*
// @match        https://lolz.guru/conversations/*
// @match        https://lolz.live/conversations/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const scriptNavigation = sessionStorage.getItem('scriptNavigation') === 'true';

    if (!scriptNavigation) {
        localStorage.removeItem('selectedConversations');
        localStorage.removeItem('leavingInProgress');
        localStorage.removeItem('selectedRadioOption');
    } else {
        sessionStorage.removeItem('scriptNavigation');
    }

    const selectedConversations = JSON.parse(localStorage.getItem('selectedConversations')) || [];
    const leavingInProgress = localStorage.getItem('leavingInProgress') === 'true';
    let isLeaving = false;
    const selectedRadioOption = localStorage.getItem('selectedRadioOption') || 'delete';

    const leaveFormHTML = `
        <div id="leave-conversation-form" style="border: 1px solid #ccc; border-radius: 6px; padding: 20px; width: 300px; background: #272727; position: fixed; top: 50px; right: 20px; z-index: 9999; display: none;">
            <h2 class="heading h1">Покинуть переписку:</h2>
            <p>Если покинуть переписку, она исчезнет из Вашего списка.</p>

            <dl class="ctrlUnit">
                <dt><label for="delete_type_delete">Обработка новых ответов:</label></dt>
                <dd>
                    <ul>
                        <li>
                            <label for="delete_type_delete">
                                <input type="radio" name="delete_type" value="delete" id="delete_type_delete" ${selectedRadioOption === 'delete' ? 'checked' : ''}>
                                Принимать последующие сообщения
                            </label>
                            <p class="hint">Если появятся новые ответы, то переписка будет восстановлена у Вас во входящих.</p>
                        </li>
                        <li>
                            <label for="delete_type_delete_ignore">
                                <input type="radio" name="delete_type" value="delete_ignore" id="delete_type_delete_ignore" ${selectedRadioOption === 'delete_ignore' ? 'checked' : ''}>
                                Игнорировать последующие сообщения
                            </label>
                            <p class="hint">Вы не будете получать уведомления о новых ответах, а переписка будет оставаться удалённой.</p>
                        </li>
                    </ul>
                </dd>
            </dl>

            <dl class="ctrlUnit submitUnit">
                <dt></dt>
                <dd>
                    <button id="submit-leave" class="button primary">Покинуть переписку</button>
                    <button id="cancel-leave" class="button OverlayCloser">Отмена</button>
                </dd>
            </dl>
        </div>
    `;

    document.body.insertAdjacentHTML('beforeend', leaveFormHTML);

    // Утилиты для надежной работы
    function waitForElement(selector, timeout = 5000, parent = document) {
        return new Promise((resolve, reject) => {
            const element = parent.querySelector(selector);
            if (element) {
                resolve(element);
                return;
            }

            const observer = new MutationObserver((mutations, obs) => {
                const element = parent.querySelector(selector);
                if (element) {
                    obs.disconnect();
                    resolve(element);
                }
            });

            observer.observe(parent, {
                childList: true,
                subtree: true
            });

            setTimeout(() => {
                observer.disconnect();
                reject(new Error(`Элемент ${selector} не найден за ${timeout}мс`));
            }, timeout);
        });
    }

    async function safeClick(element, maxRetries = 3) {
        for (let i = 0; i < maxRetries; i++) {
            try {
                if (element && typeof element.click === 'function') {
                    element.click();
                    return true;
                } else if (element) {
                    const event = new MouseEvent('click', {
                        view: window,
                        bubbles: true,
                        cancelable: true
                    });
                    element.dispatchEvent(event);
                    return true;
                }
            } catch (error) {
                console.log(`Попытка клика ${i + 1} неудачна:`, error);
                if (i < maxRetries - 1) {
                    await delay(200);
                }
            }
        }
        return false;
    }

    function delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    function getCurrentConversationId() {
        const urlMatch = window.location.pathname.match(/\/conversations\/(\d+)/);
        return urlMatch ? urlMatch[1] : null;
    }

    function getBaseUrl() {
        return window.location.origin;
    }

    document.addEventListener('click', (e) => {
        if (e.ctrlKey && e.target.closest('.conversationItem')) {
            e.preventDefault();
            toggleConversationSelection(e.target.closest('.conversationItem'));
        }
    });

    // Обработчик для выбора/снятия всех переписок
    document.addEventListener('mousedown', (e) => {
        if (e.ctrlKey && e.button === 1) { // Средняя кнопка мыши + Ctrl
            e.preventDefault();
            const conversationList = document.querySelector('#ConversationListItems');
            if (conversationList && e.target.closest('#ConversationListItems')) {
                toggleAllConversations();
            }
        }
    });

    document.addEventListener('keydown', (e) => {
        if (e.ctrlKey && e.key === 'Enter') {
            e.preventDefault();
            if (selectedConversations.length > 0) {
                showLeaveForm();
            } else {
                console.log("Нет выбранных переписок для покидания.");
            }
        }
    });

    document.getElementById('submit-leave').addEventListener('click', async () => {
        const selectedRadio = document.querySelector('input[name="delete_type"]:checked');
        if (selectedRadio) {
            localStorage.setItem('selectedRadioOption', selectedRadio.value);
            await leaveSelectedConversations();
            hideLeaveForm();
        }
    });

    document.getElementById('cancel-leave').addEventListener('click', hideLeaveForm);

    if (leavingInProgress) {
        setTimeout(() => {
            leaveSelectedConversations();
        }, 1000);
    }

    function toggleConversationSelection(conversationItem) {
        const conversationId = conversationItem.dataset.cid;

        if (selectedConversations.includes(conversationId)) {
            selectedConversations.splice(selectedConversations.indexOf(conversationId), 1);
            conversationItem.style.backgroundColor = '';
        } else {
            selectedConversations.push(conversationId);
            conversationItem.style.backgroundColor = '#48b04cb2';
        }

        localStorage.setItem('selectedConversations', JSON.stringify(selectedConversations));
    }

    function toggleAllConversations() {
        const conversationItems = document.querySelectorAll('.conversationItem');

        if (conversationItems.length === 0) {
            console.log("Переписки не найдены");
            return;
        }

        const allConversationIds = Array.from(conversationItems).map(item => item.dataset.cid);
        const allSelected = allConversationIds.every(id => selectedConversations.includes(id));

        if (allSelected) {
            console.log("Снимаем выделение со всех переписок");
            conversationItems.forEach(item => {
                const cid = item.dataset.cid;
                const index = selectedConversations.indexOf(cid);
                if (index > -1) {
                    selectedConversations.splice(index, 1);
                }
                item.style.backgroundColor = '';
            });
        } else {
            console.log("Выбираем все переписки");
            conversationItems.forEach(item => {
                const cid = item.dataset.cid;
                if (!selectedConversations.includes(cid)) {
                    selectedConversations.push(cid);
                }
                item.style.backgroundColor = '#48b04cb2';
            });
        }

        localStorage.setItem('selectedConversations', JSON.stringify(selectedConversations));
        console.log(`Выбрано переписок: ${selectedConversations.length}`);
    }

    async function leaveSelectedConversations() {
        if (isLeaving) return;
        if (selectedConversations.length === 0) {
            console.log("Нет выбранных переписок.");
            return;
        }

        isLeaving = true;
        localStorage.setItem('leavingInProgress', 'true');

        const remainingConversations = [...selectedConversations];
        const baseUrl = getBaseUrl();

        for (const cid of remainingConversations) {
            try {
                console.log(`Начинаем обработку переписки с ID: ${cid}`);

                const currentCid = getCurrentConversationId();
                if (currentCid !== cid) {
                    console.log(`Переходим к переписке ${cid}`);

                    sessionStorage.setItem('scriptNavigation', 'true');

                    window.location.href = `${baseUrl}/conversations/${cid}/`;

                    await new Promise(resolve => {
                        const checkLoad = () => {
                            if (document.readyState === 'complete' && getCurrentConversationId() === cid) {
                                resolve();
                            } else {
                                setTimeout(checkLoad, 100);
                            }
                        };
                        checkLoad();
                    });

                    await delay(300);
                }

                console.log('Ищем кнопку меню...');
                const menuButton = await waitForElement('.membersAndActions .PopupControl', 3000);
                console.log(`Найдена кнопка меню для переписки с ID: ${cid}`);

                const menuClicked = await safeClick(menuButton);
                if (!menuClicked) {
                    console.error(`Не удалось кликнуть по кнопке меню для переписки ${cid}`);
                    continue;
                }

                await delay(200);

                console.log('Ищем ссылку выхода...');
                const leaveLink = await waitForElement(`a[href*="conversations/${cid}/leave"]`, 2000);
                console.log(`Найдена ссылка выхода для переписки с ID: ${cid}`);

                const leaveLinkClicked = await safeClick(leaveLink);
                if (!leaveLinkClicked) {
                    console.error(`Не удалось кликнуть по ссылке выхода для переписки ${cid}`);
                    continue;
                }

                await delay(200);

                console.log('Ищем форму...');
                const form = await waitForElement('form.xenForm', 3000);
                console.log(`Найдена форма для переписки с ID: ${cid}`);

                const selectedOption = localStorage.getItem('selectedRadioOption');
                if (selectedOption) {
                    try {
                        const radioOption = await waitForElement(
                            `input[name="delete_type"][value="${selectedOption}"]`,
                            1000,
                            form
                        );
                        console.log(`Выбираем радиокнопку: ${selectedOption}`);
                        await safeClick(radioOption);
                        await delay(200);
                    } catch (error) {
                        console.log(`Радиокнопка не найдена или не обязательна: ${error.message}`);
                    }
                }

                console.log('Ищем кнопку отправки...');
                const submitButton = await waitForElement('input[type="submit"]', 2000, form);
                console.log(`Найдена кнопка отправки для переписки с ID: ${cid}`);

                const submitClicked = await safeClick(submitButton);
                if (!submitClicked) {
                    console.error(`Не удалось кликнуть по кнопке отправки для переписки ${cid}`);
                    continue;
                }

                console.log(`Покинута переписка с ID: ${cid}`);

                const index = selectedConversations.indexOf(cid);
                if (index > -1) {
                    selectedConversations.splice(index, 1);
                    localStorage.setItem('selectedConversations', JSON.stringify(selectedConversations));
                }

                await delay(500);

            } catch (error) {
                console.error(`Ошибка при обработке переписки ${cid}:`, error);
                continue;
            }
        }

        localStorage.removeItem('leavingInProgress');
        isLeaving = false;
        console.log('Завершена обработка всех переписок');

        window.location.href = `${baseUrl}/conversations/`;
    }

    function showLeaveForm() {
        document.getElementById('leave-conversation-form').style.display = 'block';
    }

    function hideLeaveForm() {
        document.getElementById('leave-conversation-form').style.display = 'none';
    }

    function simulateClick(element) {
        const mouseEvent = new MouseEvent('click', {
            view: window,
            bubbles: true,
            cancelable: true
        });
        element.dispatchEvent(mouseEvent);
    }
})();