Gmail Bulk Delete

Bulk delete Gmail messages before a specified date

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

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

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

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

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

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

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Gmail Bulk Delete
// @namespace    http://tampermonkey.net/
// @version      3.0
// @description  Bulk delete Gmail messages before a specified date
// @author       Liminality Dreams
// @match        https://mail.google.com/*
// @icon         https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fwww.iconsdb.com%2Ficons%2Fdownload%2Fpurple%2Fgmail-login-512.png&f=1&nofb=1&ipt=58f1f097af09cca95119782f0ac6c01fd525f248fe858e72e9364340d8a5d4f7
// @grant        none
// @license GNU 3.0
// ==/UserScript==

(function() {
    'use strict';

    let isDeleting = false;
    let stopRequested = false;
    let deletedCount = 0;
    let cutoffDate = null;

    // Create GUI
    function createGUI() {
        const gui = document.createElement('div');
        gui.id = 'gmail-bulk-delete-gui';
        gui.innerHTML = `
            <div style="position: fixed; top: 20px; right: 20px; background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #1a1a2e 100%); border: 2px solid #a855f7; border-radius: 12px; padding: 24px; z-index: 10000; box-shadow: 0 0 20px rgba(168, 85, 247, 0.5), 0 4px 12px rgba(0,0,0,0.3); width: 340px; font-family: 'Google Sans', Roboto, Arial, sans-serif;">
                <div style="display: flex; align-items: center; margin-bottom: 20px;">
                    <svg style="width: 24px; height: 24px; margin-right: 12px; fill: #a855f7; filter: drop-shadow(0 0 8px rgba(168, 85, 247, 0.6));" viewBox="0 0 24 24">
                        <path d="M15 4V3H9v1H4v2h1v13c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V6h1V4h-5zm-8 15V7h10v12H7z"/>
                    </svg>
                    <h3 style="margin: 0; color: #ffffff; font-size: 18px; font-weight: 600; text-shadow: 0 0 10px rgba(168, 85, 247, 0.5);">Gmail Bulk Delete</h3>
                </div>

                <div style="margin-bottom: 16px;">
                    <label style="display: block; margin-bottom: 8px; color: #e9d5ff; font-size: 14px; font-weight: 500;">Delete emails before (start):</label>
                    <input type="date" id="cutoff-date" style="width: 100%; padding: 10px; border: 2px solid #7c3aed; border-radius: 6px; font-size: 14px; color: #ffffff; background: #0f172a; box-sizing: border-box; box-shadow: 0 0 10px rgba(124, 58, 237, 0.3);">
                </div>

                <div style="margin-bottom: 16px;">
                    <label style="display: block; margin-bottom: 8px; color: #e9d5ff; font-size: 14px; font-weight: 500;">Stop at date (optional):</label>
                    <input type="date" id="stop-date" style="width: 100%; padding: 10px; border: 2px solid #7c3aed; border-radius: 6px; font-size: 14px; color: #ffffff; background: #0f172a; box-sizing: border-box; box-shadow: 0 0 10px rgba(124, 58, 237, 0.3);">
                    <small style="color: #c4b5fd; font-size: 12px; margin-top: 4px; display: block;">Leave empty to delete all emails before start date</small>
                </div>

                <div style="margin-bottom: 16px;">
                    <label style="display: block; margin-bottom: 8px; color: #e9d5ff; font-size: 14px; font-weight: 500;">Search query (optional):</label>
                    <input type="text" id="search-query" placeholder="e.g., from:[email protected]" style="width: 100%; padding: 10px; border: 2px solid #7c3aed; border-radius: 6px; font-size: 14px; color: #ffffff; background: #0f172a; box-sizing: border-box; box-shadow: 0 0 10px rgba(124, 58, 237, 0.3);">
                    <small style="color: #c4b5fd; font-size: 12px; margin-top: 4px; display: block;">Leave empty to delete all emails</small>
                </div>

                <div style="margin-bottom: 20px; padding: 12px; background: rgba(168, 85, 247, 0.1); border-radius: 8px; border: 2px solid #7c3aed; box-shadow: 0 0 15px rgba(124, 58, 237, 0.3);">
                    <div style="font-size: 13px; color: #e9d5ff; margin-bottom: 4px;"><strong>Status:</strong> <span id="status" style="color: #a855f7;">Ready</span></div>
                    <div style="font-size: 13px; color: #e9d5ff;"><strong>Deleted:</strong> <span id="deleted-count" style="color: #a855f7;">0</span> emails</div>
                </div>

                <div style="display: flex; gap: 8px; margin-bottom: 16px;">
                    <button id="start-btn" style="flex: 1; padding: 12px 24px; background: linear-gradient(135deg, #a855f7 0%, #7c3aed 100%); color: white; border: 2px solid #a855f7; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 600; font-family: 'Google Sans', Roboto, Arial, sans-serif; transition: all 0.3s; box-shadow: 0 0 15px rgba(168, 85, 247, 0.5);">
                        <svg style="width: 14px; height: 14px; margin-right: 8px; fill: white; vertical-align: text-bottom; display: inline-block;" viewBox="0 0 24 24">
                            <path d="M15 4V3H9v1H4v2h1v13c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V6h1V4h-5zm-8 15V7h10v12H7z"/>
                        </svg>
                        Start Delete
                    </button>
                    <button id="stop-btn" style="flex: 1; padding: 12px 24px; background: rgba(30, 30, 46, 0.8); color: #c4b5fd; border: 2px solid #6b21a8; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 600; font-family: 'Google Sans', Roboto, Arial, sans-serif; transition: all 0.3s;" disabled>Stop</button>
                </div>

                <div style="padding: 12px; background: rgba(168, 85, 247, 0.15); border: 2px solid #a855f7; border-radius: 6px; font-size: 12px; color: #e9d5ff; line-height: 1.5; box-shadow: 0 0 10px rgba(168, 85, 247, 0.3);">
                    ⚠️ <strong style="color: #c4b5fd;">Warning:</strong> This will permanently delete emails. They go to Trash first (30-day recovery). Make sure you're on the correct account!
                </div>
            </div>
        `;
        document.body.appendChild(gui);

        // Set default dates
        const dateInput = document.getElementById('cutoff-date');
        const stopDateInput = document.getElementById('stop-date');
        const today = new Date().toISOString().split('T')[0];
        dateInput.value = today;
        // Leave stop date empty by default

        // Event listeners
        document.getElementById('start-btn').addEventListener('click', startDeletion);
        document.getElementById('stop-btn').addEventListener('click', stopDeletion);

        // Hover effects
        const startBtn = document.getElementById('start-btn');
        const stopBtn = document.getElementById('stop-btn');

        startBtn.addEventListener('mouseover', () => {
            if (!startBtn.disabled) {
                startBtn.style.boxShadow = '0 0 25px rgba(168, 85, 247, 0.8)';
                startBtn.style.transform = 'translateY(-2px)';
            }
        });
        startBtn.addEventListener('mouseout', () => {
            if (!startBtn.disabled) {
                startBtn.style.boxShadow = '0 0 15px rgba(168, 85, 247, 0.5)';
                startBtn.style.transform = 'translateY(0)';
            }
        });

        stopBtn.addEventListener('mouseover', () => {
            if (!stopBtn.disabled) {
                stopBtn.style.background = 'rgba(107, 33, 168, 0.3)';
                stopBtn.style.boxShadow = '0 0 15px rgba(168, 85, 247, 0.4)';
            }
        });
        stopBtn.addEventListener('mouseout', () => {
            if (!stopBtn.disabled) {
                stopBtn.style.background = 'rgba(30, 30, 46, 0.8)';
                stopBtn.style.boxShadow = 'none';
            }
        });
    }

    function updateStatus(status) {
        document.getElementById('status').textContent = status;
    }

    function updateDeletedCount() {
        document.getElementById('deleted-count').textContent = deletedCount;
    }

    function startDeletion() {
        const dateInput = document.getElementById('cutoff-date').value;
        const stopDateInput = document.getElementById('stop-date').value;
        const searchQuery = document.getElementById('search-query').value.trim();

        if (!dateInput) {
            alert('Please select a cutoff date!');
            return;
        }

        cutoffDate = new Date(dateInput);

        isDeleting = true;
        stopRequested = false;
        deletedCount = 0;

        document.getElementById('start-btn').disabled = true;
        document.getElementById('stop-btn').disabled = false;

        updateStatus('Running...');
        updateDeletedCount();

        // Build search query with proper Gmail date format
        const startParts = dateInput.split('-'); // YYYY-MM-DD
        let query = `before:${startParts[0]}/${startParts[1]}/${startParts[2]}`;

        if (stopDateInput) {
            const stopParts = stopDateInput.split('-');
            query += ` after:${stopParts[0]}/${stopParts[1]}/${stopParts[2]}`;
        }

        if (searchQuery) {
            query += ' ' + searchQuery;
        }

        console.log('Search query:', query);

        // Navigate to search results
        window.location.href = `https://mail.google.com/mail/u/0/#search/${encodeURIComponent(query)}`;

        // Start deletion after page loads
        setTimeout(deleteEmails, 3000);
    }

    function stopDeletion() {
        stopRequested = true;
        isDeleting = false;
        document.getElementById('start-btn').disabled = false;
        document.getElementById('stop-btn').disabled = true;
        updateStatus('Stopped');
    }

    async function deleteEmails() {
        if (!isDeleting || stopRequested) {
            updateStatus('Stopped');
            return;
        }

        try {
            updateStatus('Checking for emails...');

            // Check if we've reached "No messages matched your search"
            const noMessagesText = document.body.textContent;
            if (noMessagesText.includes('No messages matched your search') ||
                noMessagesText.includes('No conversations') ||
                noMessagesText.includes('Nothing in')) {
                // All done!
                updateStatus('Complete!');
                isDeleting = false;
                document.getElementById('start-btn').disabled = false;
                document.getElementById('stop-btn').disabled = true;
                alert(`✅ Deletion complete! ${deletedCount} emails deleted.`);
                return;
            }

            // Click "Select all" checkbox
            const selectAllCheckbox = document.querySelector('span[role="checkbox"][aria-checked="false"]');
            if (!selectAllCheckbox) {
                // No checkbox but also no "no messages" - page might be loading
                updateStatus('Waiting for page to load...');
                setTimeout(deleteEmails, 2000);
                return;
            }

            updateStatus('Selecting emails...');
            selectAllCheckbox.click();
            await sleep(1000);

            // Check if "Select all conversations" banner appears and click it
            const selectAllBanner = Array.from(document.querySelectorAll('span')).find(el =>
                el.textContent.includes('Select all conversations') ||
                el.textContent.includes('Select all')
            );

            if (selectAllBanner) {
                updateStatus('Selecting all conversations...');
                selectAllBanner.click();
                await sleep(800);
            }

            updateStatus('Finding delete button...');

            // Find the delete button - it's next to "Report spam"
            // Try multiple methods to find it
            let deleteButton = null;

            // Method 1: Find by the specific class
            deleteButton = document.querySelector('div[act="10"]');

            if (!deleteButton) {
                // Method 2: Find all buttons with the trash icon class
                deleteButton = document.querySelector('div.ar9.T-I-J3.J-J5-Ji');
            }

            if (!deleteButton) {
                // Method 3: Find the toolbar and get the delete button (usually 3rd button)
                const toolbar = document.querySelector('div[role="toolbar"]');
                if (toolbar) {
                    const buttons = toolbar.querySelectorAll('div[role="button"]');
                    // Delete is typically the 3rd button (after Archive and Report Spam)
                    if (buttons.length >= 3) {
                        deleteButton = buttons[2];
                    }
                }
            }

            if (!deleteButton) {
                // Method 4: Find by data-tooltip="Delete"
                deleteButton = document.querySelector('div[data-tooltip="Delete"]');
            }

            if (!deleteButton) {
                // Method 5: Search through all toolbar buttons
                const allToolbarButtons = document.querySelectorAll('div[role="toolbar"] div[role="button"]');
                for (const btn of allToolbarButtons) {
                    const tooltip = btn.getAttribute('data-tooltip');
                    const ariaLabel = btn.getAttribute('aria-label');
                    if (tooltip?.includes('Delete') || ariaLabel?.includes('Delete')) {
                        deleteButton = btn;
                        break;
                    }
                }
            }

            if (deleteButton) {
                updateStatus('Clicking delete button...');
                console.log('Found delete button:', deleteButton);

                // Use multiple click methods to ensure it works
                // Method 1: Direct click
                deleteButton.click();

                // Method 2: Focus and trigger
                deleteButton.focus();
                await sleep(100);

                // Method 3: MouseDown and MouseUp events
                const mouseDownEvent = new MouseEvent('mousedown', {
                    bubbles: true,
                    cancelable: true,
                    view: window
                });
                const mouseUpEvent = new MouseEvent('mouseup', {
                    bubbles: true,
                    cancelable: true,
                    view: window
                });

                deleteButton.dispatchEvent(mouseDownEvent);
                await sleep(50);
                deleteButton.dispatchEvent(mouseUpEvent);
                await sleep(50);

                // Method 4: Trigger another click just to be sure
                deleteButton.click();

                // Try to get actual count from selection
                const selectedSpan = document.querySelector('span.Dj');
                let batchCount = 50; // default
                if (selectedSpan) {
                    const match = selectedSpan.textContent.match(/(\d+)/);
                    if (match) {
                        batchCount = parseInt(match[1]);
                    }
                }

                deletedCount += batchCount;
                updateDeletedCount();
                updateStatus(`Deleted ${batchCount}, waiting for next batch...`);

                // Wait for Gmail to process the deletion and load next batch
                await sleep(4000);

                // Automatically continue - the page should refresh with next batch
                setTimeout(deleteEmails, 1500);
            } else {
                updateStatus('Error: Delete button not found');
                console.log('Could not find delete button. Available toolbar buttons:',
                    Array.from(document.querySelectorAll('div[role="toolbar"] div[role="button"]')).map(b => ({
                        aria: b.getAttribute('aria-label'),
                        tooltip: b.getAttribute('data-tooltip'),
                        act: b.getAttribute('act'),
                        classes: b.className
                    }))
                );
                stopDeletion();
            }
        } catch (error) {
            console.error('Error during deletion:', error);
            updateStatus('Error: ' + error.message);
            stopDeletion();
        }
    }

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

    // Wait for Gmail to load, then create GUI
    function init() {
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', () => {
                setTimeout(createGUI, 2000);
            });
        } else {
            setTimeout(createGUI, 2000);
        }
    }

    init();
})();