ChatGPT 线程删除与复选框

在ChatGPT中添加复选框,提供删除所选线程的功能。

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         ChatGPT Thread Deletion with Checkbox
// @description  Adds checkboxes to chat threads in ChatGPT and provides functionality to delete selected threads.
// @name:zh-CN   ChatGPT 线程删除与复选框
// @description:zh-CN  在ChatGPT中添加复选框,提供删除所选线程的功能。
// @name:ja  ChatGPTにスレッド削除のチャットボックスの追加
// @description:ja  チャットスレッドにチェックボックスを追加し、選択したスレッドを削除する機能を提供します。
// @name:zh-TW  ChatGPT 线程删除与复选框
// @description:zh-TW 在ChatGPT中添加复选框,提供删除所选线程的功能。
// @namespace    http://tampermonkey.net/
// @version      1.4.1
// @author       recursive
// @license      MIT
// @match        https://chat.openai.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=openai.com
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    let deleteBtn = null;
    let checkboxes = [];

    // 削除ボタンの表示・非表示を更新する関数
    function updateDeleteButtonVisibility() {
        const isChecked = checkboxes.some(cb => cb.checked);
        deleteBtn.style.display = isChecked ? 'block' : 'none';
    }

    // チェックボックスの位置を設定する関数
    function setCheckboxPosition(checkbox, icon) {
        const iconRect = icon.getBoundingClientRect();
        const iconParentRect = icon.parentElement.getBoundingClientRect();

        checkbox.style.width = `${iconRect.width}px`;
        checkbox.style.height = `${iconRect.height}px`;

        if (iconRect.bottom < iconParentRect.top || iconRect.top > iconParentRect.bottom ||
            iconRect.right < iconParentRect.left || iconRect.left > iconParentRect.right) {
            checkbox.style.display = 'none';
        } else {
            checkbox.style.display = '';
            checkbox.style.left = `${iconRect.left - iconParentRect.left}px`;
            checkbox.style.top = `${iconRect.top - iconParentRect.top}px`;
        }
    }

    // アイコンに対応するチェックボックスを作成する関数
    function createCheckboxForIcon(icon) {
        let existingCheckbox = icon.parentElement.querySelector('.thread-selector');
        if (existingCheckbox) return;

        let checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.className = 'thread-selector';
        checkbox.style.position = 'absolute';
        checkbox.style.zIndex = '10000';

        setCheckboxPosition(checkbox, icon);
        icon.parentElement.appendChild(checkbox);
        checkboxes.push(checkbox);

        checkbox.addEventListener('click', (e) => {
            e.stopPropagation();
            updateDeleteButtonVisibility();
        });

        const iconObserver = new MutationObserver(() => {
            setCheckboxPosition(checkbox, icon);
        });
        iconObserver.observe(icon, { attributes: true, childList: true, characterData: true, subtree: true });
    }

    // 全てのチャットスレッドのアイコンに対してチェックボックスを作成する関数
    function createCheckboxesForAllChatThreadIcons() {
        const svgIcons = document.querySelectorAll('#__next > div.overflow-hidden.w-full.h-full.relative.flex.z-0 > div.flex-shrink-0.overflow-x-hidden.dark.bg-gray-900 > div > div > div > nav > div.flex-col.flex-1.transition-opacity.duration-500.-mr-2.pr-2.overflow-y-auto > div > div a > svg');
        svgIcons.forEach(createCheckboxForIcon);
    }


    // 遅延処理を行う関数
    async function delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    // 選択されたスレッドを削除する関数
    async function deleteCheckedThreads() {
        let successfulDeletions = 0;
        let threadsToDelete = [];
        const totalDelayPerThread = 250;  // 100ms + 150ms for each thread deletion

        for (const checkbox of checkboxes) {
            if (!checkbox.checked) continue;

            let threadElem = checkbox.closest('a');
            if (!threadElem) continue;

            threadsToDelete.push(threadElem);
        }

        for (const threadElem of threadsToDelete) {
            threadElem.click();
            await delay(100);

            const trashButton = threadElem.querySelector('button:nth-child(2)');
            if (!trashButton) continue;

            const elementRect = trashButton.getBoundingClientRect();
            const x = elementRect.left + window.pageXOffset;
            const y = elementRect.top + window.pageYOffset;
            const clickEvent = new MouseEvent('click', {
                bubbles: true, cancelable: true, view: window, clientX: x, clientY: y
            });
            trashButton.dispatchEvent(clickEvent);

            await delay(150);

            const deleteConfirmButton = document.querySelector('div.p-4.sm\\:p-6.sm\\:pt-4 > div > div > button.btn.relative.btn-danger');
            if (deleteConfirmButton) {
                deleteConfirmButton.click();
                successfulDeletions++;
            }

            await delay(totalDelayPerThread);  // Ensure delay after each thread deletion
        }

        if (successfulDeletions > 0) {
            window.location.reload();  // Reload after all threads have been deleted
        }
    }

    // 削除ボタンをUIに追加する関数
    function addDeleteButtonToUI() {
        deleteBtn = document.createElement('div');
        deleteBtn.innerText = 'Delete';

        const referenceButton = document.querySelector('#__next > div.overflow-hidden.w-full.h-full.relative.flex.z-0 > div.flex-shrink-0.overflow-x-hidden.dark.bg-gray-900 > div > div > div > nav > div.mb-1.flex.flex-row.gap-2 > a');
        if (referenceButton) {
            const rect = referenceButton.getBoundingClientRect();
            deleteBtn.style.width = `${rect.width}px`;
            deleteBtn.style.height = `${rect.height}px`;
            deleteBtn.style.fontSize = '14px';
            deleteBtn.style.lineHeight = `${rect.height}px`;
            deleteBtn.style.textAlign = 'center';
        }

        deleteBtn.style.position = 'fixed';
        deleteBtn.style.display = 'none';
        deleteBtn.style.background = '#f00';
        deleteBtn.style.color = '#fff';
        deleteBtn.style.borderRadius = '5px';
        deleteBtn.style.cursor = 'pointer';
        deleteBtn.style.left = '10px';
        deleteBtn.style.top = '10px';
        deleteBtn.onclick = deleteCheckedThreads;

        document.body.appendChild(deleteBtn);
    }

    // アイコンがクリックされた時にチェックボックスを作成するイベントリスナーを追加する
    document.addEventListener('click', (event) => {
        const parentSelector = '#__next > div.overflow-hidden.w-full.h-full.relative.flex.z-0 > div.flex-shrink-0.overflow-x-hidden.dark.bg-gray-900 > div > div > div > nav > div.flex-col.flex-1.transition-opacity.duration-500.-mr-2.pr-2.overflow-y-auto > div > div';
        if (event.target.closest(parentSelector) && event.target.matches('svg')) {
            createCheckboxesForAllChatThreadIcons();
        }
    });


    // 削除ボタンをUIに追加する
    addDeleteButtonToUI();

    // スレッドリストの変更を監視し、アイコンに対応するチェックボックスを作成する
    const threadList = document.querySelector('#__next div.overflow-hidden.w-full.h-full.relative.flex.z-0 div.flex-shrink-0.overflow-x-hidden.dark.bg-gray-900 div > div > div > nav div.flex-col.flex-1.transition-opacity.duration-500.-mr-2.pr-2.overflow-y-auto > div > div');
    const observer = new MutationObserver(createCheckboxesForAllChatThreadIcons);
    observer.observe(threadList, { childList: true, subtree: true });
})();