Gemini Bulk Delete

Bulk delete Gemini conversations

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

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

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name         Gemini Bulk Delete
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  Bulk delete Gemini conversations
// @author       Antigravity
// @match        https://gemini.google.com/app*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=gemini.google.com
// @noframes
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    // --- 1. Mini-Framework Utils ---

    // Feature detection for Trusted Types
    const policy = window.trustedTypes && window.trustedTypes.createPolicy ?
        window.trustedTypes.createPolicy('geminiBulkDeletePolicy', { createHTML: s => s }) :
        { createHTML: s => s };

    // Tagged Template Literal for safe HTML
    function html(strings, ...values) {
        const raw = strings.reduce((acc, str, i) => acc + str + (values[i] || ''), '');
        return policy.createHTML(raw);
    }

    // Reactivity System
    function reactive(target, onChange) {
        return new Proxy(target, {
            set(obj, prop, value) {
                if (obj[prop] !== value) {
                    obj[prop] = value;
                    onChange(prop, value);
                }
                return true;
            }
        });
    }

    // --- 2. Styles ---
    const CHECKBOX_CLASS = 'gemini-bulk-del-checkbox';

    function addStyles() {
        if (document.getElementById('gemini-bulk-style')) return;
        const style = document.createElement('style');
        style.id = 'gemini-bulk-style';
        style.textContent = `
            :root {
                --gemini-bulk-bg: rgba(30, 30, 30, 0.9);
                --gemini-bulk-border: rgba(255, 255, 255, 0.1);
                --gemini-bulk-text: #e3e3e3;
                --gemini-bulk-accent: #8ab4f8;
                --gemini-bulk-accent-hover: #aecbfa;
                --gemini-bulk-danger: #ea4335;
                --gemini-bulk-danger-hover: #f28b82;
            }
            .${CHECKBOX_CLASS} {
                appearance: none;
                -webkit-appearance: none;
                width: 20px;
                height: 20px;
                border: 2px solid var(--gemini-bulk-border);
                border-radius: 6px;
                margin-right: 12px;
                cursor: pointer;
                position: relative;
                transition: all 0.2s ease;
                background-color: transparent;
                flex-shrink: 0;
                z-index: 1000;
            }
            .${CHECKBOX_CLASS}:checked {
                background-color: var(--gemini-bulk-accent);
                border-color: var(--gemini-bulk-accent);
            }
            .${CHECKBOX_CLASS}:checked::after {
                content: '';
                position: absolute;
                left: 50%;
                top: 50%;
                width: 5px;
                height: 10px;
                border: solid #1e1e1e;
                border-width: 0 2px 2px 0;
                transform: translate(-50%, -50%) rotate(45deg);
                margin-top: -2px;
            }
            .${CHECKBOX_CLASS}:hover {
                border-color: var(--gemini-bulk-accent-hover);
            }
            .gemini-bulk-selected {
                background-color: var(--gemini-bulk-border);
            }
            /* Floating Bar Component */
            .gemini-floating-bar {
                position: fixed;
                bottom: 30px;
                left: 50%;
                transform: translateX(-50%) translateY(100px);
                background: var(--gemini-bulk-bg);
                backdrop-filter: blur(12px);
                -webkit-backdrop-filter: blur(12px);
                color: var(--gemini-bulk-text);
                padding: 12px 24px;
                border-radius: 16px;
                display: flex;
                align-items: center;
                gap: 20px;
                box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
                border: 1px solid var(--gemini-bulk-border);
                z-index: 9999;
                font-family: 'Google Sans', Roboto, sans-serif;
                font-size: 14px;
                font-weight: 500;
                transition: transform 0.4s cubic-bezier(0.2, 0.8, 0.2, 1), opacity 0.4s ease;
                opacity: 0;
                pointer-events: none;
            }
            .gemini-floating-bar.visible {
                transform: translateX(-50%) translateY(0);
                opacity: 1;
                pointer-events: auto;
            }
            .gemini-floating-bar button {
                background: var(--gemini-bulk-danger);
                color: #fff;
                border: none;
                padding: 10px 20px;
                border-radius: 20px;
                cursor: pointer;
                font-weight: 600;
                font-size: 14px;
                letter-spacing: 0.5px;
                transition: background-color 0.2s, transform 0.1s;
                box-shadow: 0 2px 8px rgba(234, 67, 53, 0.3);
            }
            .gemini-floating-bar button:hover {
                background: var(--gemini-bulk-danger-hover);
                box-shadow: 0 4px 12px rgba(234, 67, 53, 0.4);
            }
            .gemini-floating-bar button:active {
                transform: scale(0.96);
            }
        `;
        document.head.appendChild(style);
    }

    // --- 3. App Core ---

    class FloatingBar {
        constructor(store, onDelete) {
            this.store = store;
            this.onDelete = onDelete;
            this.el = null;
        }

        template() {
            const count = this.store.selectedCount;
            const visibleClass = count > 0 ? 'visible' : '';

            return html`
                <div id="gemini-bulk-del-bar" class="gemini-floating-bar ${visibleClass}">
                    <span>${count} selected</span>
                    <button id="gemini-bulk-del-btn">Delete</button>
                </div>
            `;
        }

        mount(container) {
            const wrapper = document.createElement('div');
            wrapper.innerHTML = this.template();
            this.el = wrapper.firstElementChild;
            container.appendChild(this.el);
            this.bindEvents();
        }

        update() {
            if (!this.el) {
                return;
            }

            const count = this.store.selectedCount;
            const countSpan = this.el.querySelector('span');
            if (countSpan) {
                countSpan.textContent = `${count} selected`;
            }

            if (count > 0) {
                this.el.classList.add('visible');
            } else {
                this.el.classList.remove('visible');
            }
        }

        bindEvents() {
            const btn = this.el.querySelector('#gemini-bulk-del-btn');
            if (btn) btn.addEventListener('click', this.onDelete);
        }
    }

    class App {
        constructor() {
            this.store = reactive({ selectedCount: 0 }, () => this.update());
            this.floatingBar = new FloatingBar(this.store, () => this.deleteSelectedItems());
        }

        init() {
            console.log('[Bulk Delete] Vanilla App Init');
            addStyles();
            this.floatingBar.mount(document.body);
            this.initObserver();
            this.injectCheckboxes();
        }

        update() {
            this.floatingBar.update();
        }

        initObserver() {
            const observer = new MutationObserver((mutations) => {
                let shouldUpdate = false;
                for (const m of mutations) {
                    if (m.addedNodes.length > 0) {
                        shouldUpdate = true;
                        break;
                    }
                }
                if (shouldUpdate) this.injectCheckboxes();
            });
            observer.observe(document.body, { childList: true, subtree: true });
        }

        injectCheckboxes() {
            const links = document.querySelectorAll('a[href^="/app/"][data-test-id="conversation"]');
            links.forEach(link => {
                if (link.dataset.bulkDeleteProcessed) return;

                const checkbox = document.createElement('input');
                checkbox.type = 'checkbox';
                checkbox.className = CHECKBOX_CLASS;

                // Bind events
                checkbox.addEventListener('click', (e) => e.stopPropagation());
                checkbox.addEventListener('change', (e) => this.handleCheckboxChange(e));

                link.insertBefore(checkbox, link.firstChild);
                link.dataset.bulkDeleteProcessed = 'true';
                link.style.display = 'flex';
                link.style.alignItems = 'center';
            });
        }

        handleCheckboxChange(e) {
            const link = e.target.closest('a');
            if (e.target.checked) {
                link.classList.add('gemini-bulk-selected');
            } else {
                link.classList.remove('gemini-bulk-selected');
            }
            this.syncState();
        }

        syncState() {
            this.store.selectedCount = document.querySelectorAll(`.${CHECKBOX_CLASS}:checked`).length;
        }

        async deleteSelectedItems() {
            console.log('[Bulk Delete] Starting deletion...');

            const checkboxes = document.querySelectorAll(`.${CHECKBOX_CLASS}:checked`);
            if (!checkboxes.length) {
                return;
            }

            for (const checkbox of checkboxes) {
                const menuButton = checkbox
                    .closest('div')
                    .querySelector('button[data-test-id="actions-menu-button"]');

                if (menuButton) {
                    await this.deleteItem(menuButton);
                }
            }

            this.syncState();

            console.log('[Bulk Delete] Finished');
        }

        async deleteItem(menuButton) {
            menuButton.click();

            await new Promise(resolve => setTimeout(resolve, 100));

            const deleteOption = document.querySelector('[role="menuitem"][data-test-id="delete-button"]');
            if (!deleteOption) {
                return;
            }

            deleteOption.click();

            await new Promise(resolve => setTimeout(resolve, 100));

            const confirmButton = document.querySelector('button[data-test-id="confirm-button"]');
            if (!confirmButton) {
                return;
            }

            confirmButton.click();
            await new Promise(resolve => setTimeout(resolve, 100));
        }
    }

    // --- Boot ---
    const app = new App();
    if (document.readyState === 'complete' || document.readyState === 'interactive') {
        app.init();
    } else {
        window.addEventListener('load', () => app.init());
    }
})();