Gerenciador de chats ChatGPT

Gerenciamento em massa de conversas em plataformas de IA

// ==UserScript==
// @name         Gerenciador de chats ChatGPT
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Gerenciamento em massa de conversas em plataformas de IA
// @author       luascfl
// @match        https://chat.openai.com/*
// @match        https://chatgpt.com/*
// @icon         https://cdn-icons-png.flaticon.com/512/16459/16459818.png
// @home         https://github.com/luascfl/manage-chats-chatgpt
// @supportURL   https://github.com/luascfl/manage-chats-chatgpt/issues
// @license      MIT
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    /**
     * Configuração específica para cada plataforma
     * Facilita a adaptação para outras plataformas de IA no futuro
     */
    const PLATFORMS = {
        'chat.openai.com': {
            name: 'ChatGPT',
            selectors: {
                chatList: '.flex.flex-col.gap-2.text-token-text-primary',
                chatItems: 'ol > li',
                chatLink: 'a[href^="/c/"]',
                chatTitle: 'div[dir="auto"]'
            },
            api: {
                base: window.location.origin,
                tokenEndpoint: '/api/auth/session',
                conversationEndpoint: '/backend-api/conversation/',
                tokenExtractor: (data) => data.accessToken
            },
            priorityEmoji: '❗'
        },
        // Modelo para adicionar outras plataformas
        /*
        'exemplo.com': {
            name: 'Nome da Plataforma',
            selectors: {
                chatList: '.seletor-lista-chats',
                chatItems: '.seletor-item-chat',
                chatLink: '.seletor-link-chat',
                chatTitle: '.seletor-titulo-chat'
            },
            api: {
                base: 'https://api.exemplo.com',
                tokenEndpoint: '/auth/token',
                conversationEndpoint: '/api/conversations/',
                tokenExtractor: (data) => data.token
            },
            priorityEmoji: '⭐'
        }
        */
    };

    // Detecta a plataforma atual
    const getCurrentPlatform = () => {
        const hostname = window.location.hostname;
        return PLATFORMS[hostname] || PLATFORMS['chat.openai.com']; // Padrão para ChatGPT
    };

    const PLATFORM = getCurrentPlatform();
    const API_BASE = PLATFORM.api.base;
    const SELECTOR = PLATFORM.selectors;
    const PRIORITY_EMOJI = PLATFORM.priorityEmoji;

    /**
     * Gerenciador de UI
     */
    class UIManager {
        constructor() {
            this.addStyles();
        }

        addStyles() {
            const styleEl = document.createElement('style');
            styleEl.innerHTML = `
              .mass-actions {
                background-color: var(--surface-primary);
                padding: 10px;
                border-radius: 8px;
                margin-bottom: 16px;
                box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
              }

              .mass-actions-title {
                font-weight: bold;
                margin-bottom: 10px;
                font-size: 14px;
                color: var(--text-primary);
              }

              .mass-actions-btn {
                padding: 6px 12px;
                border-radius: 6px;
                font-size: 13px;
                font-weight: 500;
                cursor: pointer;
                transition: all 0.2s;
                border: 1px solid var(--border-primary);
                margin-bottom: 5px;
              }

              .mass-actions-btn:hover {
                opacity: 0.9;
              }

              .btn-select-all {
                background-color: var(--surface-secondary);
              }

              .btn-deselect-all {
                background-color: var(--surface-secondary);
              }

              .btn-select-without-emoji {
                background-color: var(--surface-secondary);
              }

              .btn-archive {
                background-color: var(--surface-tertiary);
              }

              .btn-delete {
                background-color: rgba(255, 76, 76, 0.1);
                color: #ff4c4c;
              }

              .checkbox-container {
                position: absolute;
                left: 8px;
                top: 0;
                bottom: 0;
                display: flex;
                align-items: center;
                z-index: 10;
              }

              .dialog-checkbox {
                cursor: pointer;
                width: 16px;
                height: 16px;
              }

              .chat-item-container {
                position: relative;
              }

              .chat-link-padded {
                padding-left: 30px !important;
              }

              .chat-action-status {
                position: fixed;
                top: 20px;
                right: 20px;
                padding: 12px 16px;
                background: var(--surface-primary);
                border-radius: 8px;
                box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
                z-index: 1000;
                display: flex;
                align-items: center;
                font-size: 14px;
              }

              .status-icon {
                margin-right: 8px;
                font-size: 18px;
              }

              .status-success {
                color: #4caf50;
              }

              .status-error {
                color: #f44336;
              }

              .status-loading {
                color: #2196f3;
              }

              @keyframes spin {
                0% { transform: rotate(0deg); }
                100% { transform: rotate(360deg); }
              }

              .loading-spinner {
                animation: spin 1s linear infinite;
                display: inline-block;
              }

              .select-count {
                margin-left: 8px;
                font-size: 13px;
                color: var(--text-secondary);
              }
            `;
            document.head.appendChild(styleEl);
        }

        showStatus(message, type = 'loading') {
            // Remove qualquer status existente
            const existingStatus = document.querySelector('.chat-action-status');
            if (existingStatus) existingStatus.remove();

            const statusEl = document.createElement('div');
            statusEl.className = 'chat-action-status';

            let icon = '';
            if (type === 'loading') {
                icon = '<span class="status-icon status-loading"><span class="loading-spinner">⟳</span></span>';
            } else if (type === 'success') {
                icon = '<span class="status-icon status-success">✓</span>';
            } else if (type === 'error') {
                icon = '<span class="status-icon status-error">✕</span>';
            }

            statusEl.innerHTML = `${icon}${message}`;
            document.body.appendChild(statusEl);

            if (type !== 'loading') {
                setTimeout(() => {
                    statusEl.remove();
                }, 3000);
            }

            return statusEl;
        }

        updateSelectedCount() {
            const selectedCount = document.querySelectorAll('.dialog-checkbox:checked').length;
            const countElement = document.querySelector('.selected-count');
            if (countElement) {
                countElement.textContent = selectedCount > 0 ? `${selectedCount} selecionado${selectedCount > 1 ? 's' : ''}` : '';
            }
        }

        createCheckbox(chatItem) {
            // Verifica se já existe um checkbox
            if (chatItem.querySelector('.checkbox-container')) return;

            // Adiciona classe ao container do chat
            chatItem.classList.add('chat-item-container');

            // Encontra o link de chat
            const chatLink = chatItem.querySelector(SELECTOR.chatLink);
            if (!chatLink) return;

            // Adiciona classe ao link para dar espaço ao checkbox
            chatLink.classList.add('chat-link-padded');

            // Cria container do checkbox
            const checkboxContainer = document.createElement('div');
            checkboxContainer.className = 'checkbox-container';

            // Cria o checkbox
            const checkbox = document.createElement('input');
            checkbox.type = 'checkbox';
            checkbox.className = 'dialog-checkbox';
            checkbox.addEventListener('change', () => this.updateSelectedCount());
            checkbox.addEventListener('click', (e) => {
                e.stopPropagation(); // Impede que o clique no checkbox navegue para o chat
            });

            // Adiciona checkbox ao container
            checkboxContainer.appendChild(checkbox);

            // Adiciona o container ao item de chat
            chatItem.appendChild(checkboxContainer);
        }

        ensureCorrectCheckboxes() {
            document.querySelectorAll(SELECTOR.chatItems).forEach(chatItem => {
                // Remove qualquer checkbox antigo que possa estar dentro do link
                const oldCheckbox = chatItem.querySelector('a .dialog-checkbox');
                if (oldCheckbox) {
                    oldCheckbox.remove();
                }

                // Cria um novo checkbox corretamente posicionado
                this.createCheckbox(chatItem);
            });
        }

        setupControlPanel() {
            const chatList = document.querySelector(SELECTOR.chatList);
            if (!chatList || chatList.querySelector('.mass-actions')) return;

            const controls = document.createElement('div');
            controls.className = 'mass-actions';
            controls.innerHTML = `
              <div class="mass-actions-title">Gerenciamento em massa</div>
              <div class="flex gap-2 flex-wrap">
                <button class="mass-actions-btn btn-select-all">Selecionar tudo</button>
                <button class="mass-actions-btn btn-select-without-emoji">Selecionar sem ${PRIORITY_EMOJI}</button>
                <button class="mass-actions-btn btn-deselect-all">Desmarcar tudo</button>
                <button class="mass-actions-btn btn-archive">Arquivar selecionados</button>
                <button class="mass-actions-btn btn-delete">Excluir selecionados</button>
                <span class="selected-count select-count"></span>
              </div>
            `;

            // Configura os eventos dos botões
            this.setupButtonHandlers(controls);

            chatList.prepend(controls);

            // Adiciona checkboxes a todos os itens de chat existentes
            document.querySelectorAll(SELECTOR.chatItems).forEach(chatItem => this.createCheckbox(chatItem));
            this.updateSelectedCount();
        }

        setupButtonHandlers(controls) {
            controls.querySelector('.btn-select-all').addEventListener('click', () => {
                document.querySelectorAll('.dialog-checkbox').forEach(cb => cb.checked = true);
                this.updateSelectedCount();
            });

            controls.querySelector('.btn-select-without-emoji').addEventListener('click', () => {
                chatManager.selectChatsWithoutPriorityEmoji();
                this.updateSelectedCount();
            });

            controls.querySelector('.btn-deselect-all').addEventListener('click', () => {
                document.querySelectorAll('.dialog-checkbox').forEach(cb => cb.checked = false);
                this.updateSelectedCount();
            });

            controls.querySelector('.btn-archive').addEventListener('click', () => {
                if (confirm('Deseja arquivar todas as conversas selecionadas?')) {
                    chatManager.updateChats({ is_archived: true });
                }
            });

            controls.querySelector('.btn-delete').addEventListener('click', () => {
                if (confirm('Deseja excluir todas as conversas selecionadas? Esta ação não pode ser desfeita.')) {
                    chatManager.updateChats({ is_visible: false });
                }
            });
        }
    }

    /**
     * Gerenciador de Chats
     */
    class ChatManager {
        constructor(uiManager) {
            this.ui = uiManager;
        }

        async getAccessToken() {
            try {
                const response = await fetch(`${API_BASE}${PLATFORM.api.tokenEndpoint}`);
                const data = await response.json();
                return PLATFORM.api.tokenExtractor(data);
            } catch (error) {
                console.error('Erro ao obter token:', error);
                return null;
            }
        }

        getChatId(element) {
            const chatItem = element.closest('li');
            const link = chatItem.querySelector(SELECTOR.chatLink);
            return link ? new URL(link.href).pathname.split('/').pop() : null;
        }

        hasPriorityEmoji(chatItem) {
            const link = chatItem.querySelector(SELECTOR.chatLink);
            if (!link) return false;

            const titleDiv = link.querySelector(SELECTOR.chatTitle);
            return titleDiv && titleDiv.textContent.includes(PRIORITY_EMOJI);
        }

        selectChatsWithoutPriorityEmoji() {
            const chatItems = document.querySelectorAll(SELECTOR.chatItems);

            chatItems.forEach(chatItem => {
                const checkbox = chatItem.querySelector('.dialog-checkbox');
                if (checkbox) {
                    // Marca o checkbox apenas se NÃO tiver o emoji de prioridade
                    checkbox.checked = !this.hasPriorityEmoji(chatItem);
                }
            });
        }

        async updateChats(body) {
            const checkboxes = document.querySelectorAll('.dialog-checkbox:checked');
            if (checkboxes.length === 0) {
                this.ui.showStatus('Nenhuma conversa selecionada', 'error');
                return;
            }

            const action = body.is_archived ? 'arquivando' : 'excluindo';
            const statusEl = this.ui.showStatus(`${action.charAt(0).toUpperCase() + action.slice(1)} ${checkboxes.length} conversas...`);

            const accessToken = await this.getAccessToken();
            if (!accessToken) {
                this.ui.showStatus('Token de acesso não encontrado', 'error');
                return;
            }

            try {
                let processed = 0;
                await Promise.all(Array.from(checkboxes).map(async (checkbox) => {
                    const chatId = this.getChatId(checkbox);
                    if (!chatId) return;

                    const response = await fetch(`${API_BASE}${PLATFORM.api.conversationEndpoint}${chatId}`, {
                        method: 'PATCH',
                        headers: {
                            Authorization: `Bearer ${accessToken}`,
                            'Content-Type': 'application/json'
                        },
                        body: JSON.stringify(body)
                    });

                    if (!response.ok) throw new Error(`HTTP ${response.status}`);
                    checkbox.closest('li').style.opacity = '0.5';
                    processed++;

                    // Atualizar status com progresso
                    statusEl.innerHTML = `<span class="status-icon status-loading"><span class="loading-spinner">⟳</span></span>${action.charAt(0).toUpperCase() + action.slice(1)} conversas... (${processed}/${checkboxes.length})`;
                }));

                this.ui.showStatus(`${processed} conversas ${body.is_archived ? 'arquivadas' : 'excluídas'} com sucesso!`, 'success');

                // Recarregar a página após um breve atraso para mostrar o status
                setTimeout(() => {
                    window.location.reload();
                }, 1500);
            } catch (error) {
                console.error('Erro ao processar conversas:', error);
                this.ui.showStatus(`Erro ao processar conversas: ${error.message}`, 'error');
            }
        }
    }

    /**
     * Classe principal que orquestra tudo
     */
    class ChatManagerApp {
        constructor() {
            this.uiManager = new UIManager();
            this.chatManager = new ChatManager(this.uiManager);

            // Expõe o chatManager para uso nos event handlers
            window.chatManager = this.chatManager;
        }

        init() {
            // Inicialização com delay para garantir que a página carregou completamente
            setTimeout(() => {
                this.uiManager.setupControlPanel();
                this.uiManager.ensureCorrectCheckboxes();
                this.setupObserver();
            }, 1000);
        }

        setupObserver() {
            // Observador para detectar mudanças na lista de chats
            const observer = new MutationObserver((mutations) => {
                const chatList = document.querySelector(SELECTOR.chatList);
                if (chatList) {
                    this.uiManager.setupControlPanel();
                    this.uiManager.ensureCorrectCheckboxes();

                    // Adiciona checkboxes a novos itens
                    mutations.forEach(mutation => {
                        if (mutation.addedNodes.length) {
                            mutation.addedNodes.forEach(node => {
                                if (node.nodeType === 1 && node.matches(SELECTOR.chatItems)) {
                                    this.uiManager.createCheckbox(node);
                                } else if (node.nodeType === 1) {
                                    node.querySelectorAll(SELECTOR.chatItems).forEach(item =>
                                        this.uiManager.createCheckbox(item)
                                    );
                                }
                            });
                        }
                    });
                }
            });

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

    // Inicializa a aplicação
    const app = new ChatManagerApp();
    app.init();
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址