Greasy Fork

Multi-site Chat Manager

Manage chats across different platforms (GitHub Copilot, Flomo, Doubao)

// ==UserScript==
// @name         Multi-site Chat Manager
// @namespace    http://tampermonkey.net/
// @version      0.3
// @description  Manage chats across different platforms (GitHub Copilot, Flomo, Doubao)
// @author       Your name
// @match        https://github.com/copilot*
// @match        https://v.flomoapp.com/mine
// @match        https://www.doubao.com/chat/thread/list*
// @icon         https://v.flomoapp.com/favicon.ico
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Common styles for buttons
    const buttonStyles = {
        base: {
            padding: '8px 16px',
            border: 'none',
            borderRadius: '6px',
            cursor: 'pointer',
            color: 'white',
            fontSize: '14px',
            fontWeight: '500',
            boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
            transition: 'all 0.3s ease',
            margin: '5px'
        },
        green: {
            backgroundColor: '#2ea44f',
            '&:hover': {
                backgroundColor: '#2c974b'
            }
        },
        red: {
            backgroundColor: '#d73a49',
            '&:hover': {
                backgroundColor: '#cb2431'
            }
        }
    };

    // Apply styles to button
    function applyButtonStyles(button, type = 'base') {
        Object.assign(button.style, buttonStyles.base);
        if (type === 'green') {
            Object.assign(button.style, buttonStyles.green);
        } else if (type === 'red') {
            Object.assign(button.style, buttonStyles.red);
        }

        // Add hover effect
        button.addEventListener('mouseenter', () => {
            button.style.transform = 'translateY(-1px)';
            button.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)';
        });

        button.addEventListener('mouseleave', () => {
            button.style.transform = 'translateY(0)';
            button.style.boxShadow = '0 2px 4px rgba(0, 0, 0, 0.1)';
        });
    }

    // Site configurations
    const siteConfigs = {
        'github.com': {
            init: function() {
                this.waitForChatList();
            },

            waitForChatList: function() {
                const observer = new MutationObserver((mutations, obs) => {
                    if (document.querySelector('.ConversationList-module__ConversationList__item--dD6z4')) {
                        this.addButtons();
                        obs.disconnect();
                    }
                });

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

            addButtons: function() {
                const buttonContainer = document.createElement('div');
                buttonContainer.style.position = 'fixed';
                buttonContainer.style.bottom = '20px';
                buttonContainer.style.left = '20px';
                buttonContainer.style.zIndex = '9999';
                buttonContainer.style.display = 'flex';
                buttonContainer.style.flexDirection = 'column';
                buttonContainer.style.gap = '10px';

                const openChatsButton = document.createElement('button');
                openChatsButton.textContent = '打开chat';
                applyButtonStyles(openChatsButton, 'green');

                const clearChatsButton = document.createElement('button');
                clearChatsButton.textContent = '清空chat';
                applyButtonStyles(clearChatsButton, 'red');

                buttonContainer.appendChild(openChatsButton);
                buttonContainer.appendChild(clearChatsButton);
                document.body.appendChild(buttonContainer);

                openChatsButton.addEventListener('click', this.openAllChats);
                clearChatsButton.addEventListener('click', this.clearAllChats);
            },

            openAllChats: function() {
                const chatLinks = document.querySelectorAll('.ConversationList-module__ConversationList__link--Byc2c');
                chatLinks.forEach(link => {
                    const newWindow = window.open(link.href, '_blank');
                    if (newWindow) {
                        newWindow.addEventListener('load', () => {
                            newWindow.scrollTo(0, 0);
                        });
                    }
                });
            },

            clearAllChats: async function() {
                const kebabButtons = document.querySelectorAll('button[data-component="IconButton"]');

                for (const button of kebabButtons) {
                    if (button.closest('.ConversationList-module__ConversationList__item--dD6z4')) {
                        button.click();
                        await new Promise(resolve => setTimeout(resolve, 1000));

                        const deleteButton = Array.from(document.querySelectorAll('li[role="menuitem"]'))
                            .find(item => item.textContent.includes('Delete'));

                        if (deleteButton) {
                            deleteButton.click();
                            await new Promise(resolve => setTimeout(resolve, 1000));
                        }
                    }
                }
            }
        },

        'flomoapp.com': {
            init: function() {
                this.addClearButton();
            },

            addClearButton: function() {
                const button = document.createElement('button');
                button.textContent = '清空笔记';
                button.style.position = 'fixed';
                button.style.bottom = '20px';
                button.style.left = '20px';
                button.style.zIndex = '9999';

                applyButtonStyles(button, 'red');

                button.onclick = () => {
                    if (confirm('确定要清空笔记吗?')) {
                        this.scrollAndCheck();
                    }
                };

                document.body.appendChild(button);
            },

            scrollToBottom: function() {
                const element = document.querySelector('.memos');
                if (element) {
                    element.scrollTop = element.scrollHeight;
                }
            },

            isScrolledToBottom: function() {
                const element = document.querySelector('.end');
                return element ? element.getBoundingClientRect().bottom <= window.innerHeight : false;
            },

            scrollAndCheck: function() {
                this.scrollToBottom();

                if (!this.isScrolledToBottom()) {
                    console.log('No element with class "end" was found, continue scrolling...');
                    setTimeout(() => this.scrollAndCheck(), 1000);
                } else {
                    console.log('页面已下滑到最底部!');
                    const elements = document.querySelectorAll('.item.danger');

                    elements.forEach(element => {
                        if (element.textContent.includes('删除')) {
                            element.click();
                        }
                    });
                }
            }
        },

        'doubao.com': {
            init: function() {
                this.addButtons();
            },

            addButtons: function() {
                const buttonContainer = document.createElement('div');
                buttonContainer.style.position = 'fixed';
                buttonContainer.style.bottom = '20px';
                buttonContainer.style.left = '20px';
                buttonContainer.style.zIndex = '9999';
                buttonContainer.style.display = 'flex';
                buttonContainer.style.flexDirection = 'column';
                buttonContainer.style.gap = '10px';

                const openChatsButton = document.createElement('button');
                openChatsButton.textContent = '打开chat';
                applyButtonStyles(openChatsButton, 'green');

                const clearChatsButton = document.createElement('button');
                clearChatsButton.textContent = '清空chat';
                applyButtonStyles(clearChatsButton, 'red');

                buttonContainer.appendChild(openChatsButton);
                buttonContainer.appendChild(clearChatsButton);
                document.body.appendChild(buttonContainer);

                openChatsButton.addEventListener('click', this.openAllChats);
                clearChatsButton.addEventListener('click', this.clearAllChats);
            },

            openAllChats: async function() {
                const chatItems = document.querySelectorAll('[data-testid="thread_detail_item"]');
                console.log(`找到 ${chatItems.length} 个聊天项`);

                if (!chatItems || chatItems.length === 0) {
                    console.log('没有找到聊天项');
                    return;
                }

                // 存储原始页面的滚动位置
                const originalScroll = window.scrollY;

                const maxItems = chatItems.length;

                for (let i = 0; i < maxItems; i++) {
                    try {
                        console.log(`\n===== 处理第 ${i + 1}/${maxItems} 个聊天 =====`);

                        const currentChatItems = document.querySelectorAll('[data-testid="thread_detail_item"]');
                        if (!currentChatItems[i]) {
                            console.log('❌ 未找到目标聊天项,刷新页面');
                            location.reload();
                            await new Promise(resolve => setTimeout(resolve, 400));
                            continue;
                        }

                        const chatItem = currentChatItems[i];

                        chatItem.scrollIntoView({ behavior: "smooth", block: "center" });
                        await new Promise(resolve => setTimeout(resolve, 200));

                        const beforeClickUrl = window.location.href;
                        console.log('点击前URL:', beforeClickUrl);

                        try {
                            chatItem.click();
                            console.log('原生点击已执行');
                        } catch (clickError) {
                            console.log('原生点击失败,尝试模拟点击事件');
                            ['mouseenter', 'mousedown', 'mouseup', 'click'].forEach(eventName => {
                                chatItem.dispatchEvent(new MouseEvent(eventName, {
                                    view: window,
                                    bubbles: true,
                                    cancelable: true,
                                    buttons: eventName === 'mousedown' ? 1 : 0
                                }));
                            });
                        }

                        let newUrl = '';
                        let attempts = 0;
                        const maxAttempts = 20;

                        while (attempts < maxAttempts) {
                            await new Promise(resolve => setTimeout(resolve, 100));
                            newUrl = window.location.href;
                            if (newUrl !== beforeClickUrl && !newUrl.includes('/thread/list')) {
                                console.log('✓ URL已变化到具体的chat页面');
                                break;
                            }
                            attempts++;
                        }

                        if (newUrl === beforeClickUrl || newUrl.includes('/thread/list')) {
                            console.log('❌ URL未能成功变化到具体chat页面,尝试重新加载页面');
                            location.reload();
                            await new Promise(resolve => setTimeout(resolve, 400));
                            continue;
                        }

                        window.open(newUrl, '_blank');

                        console.log('返回列表页面...');
                        history.back();

                        attempts = 0;
                        while (attempts < maxAttempts) {
                            await new Promise(resolve => setTimeout(resolve, 100));
                            const currentUrl = window.location.href;
                            if (currentUrl.includes('/thread/list')) {
                                console.log('✓ 成功返回列表页面');
                                await new Promise(resolve => setTimeout(resolve, 400));
                                break;
                            }
                            attempts++;
                        }

                    } catch (error) {
                        console.error('处理聊天项时出错:', error);
                        location.reload();
                        await new Promise(resolve => setTimeout(resolve, 400));
                    }
                }

                window.scrollTo(0, originalScroll);
                console.log('全部处理完成');
            },

            clearAllChats: async function() {
                const menuButtons = document.querySelectorAll('.chat-item-menu-button-outline-Ic2b7D');
                console.log(`找到 ${menuButtons.length} 个菜单按钮`);

                for (const menuButton of menuButtons) {
                    try {
                        console.log('\n===== 处理新的聊天项 =====');

                        // 点击菜单按钮
                        menuButton.querySelector('div').click();
                        await new Promise(resolve => setTimeout(resolve, 200));

                        // 查找删除按钮
                        const deleteButton = document.querySelector('li.remove-btn-TOaQi0.semi-dropdown-item');
                        if (!deleteButton) {
                            console.log('该菜单无删除按钮,点击空白处关闭菜单');
                            document.body.click();
                            await new Promise(resolve => setTimeout(resolve, 100));
                            console.log('继续处理下一个');
                            continue;
                        }

                        // 尝试多种点击方法
                        const clickMethods = [
                            // 方法1:点击整个li元素
                            () => deleteButton.click(),
                            // 方法2:点击内部的div
                            () => deleteButton.querySelector('.semi-dropdown-item-icon').click(),
                            // 方法3:完整事件模拟
                            () => {
                                ['mousedown', 'mouseup', 'click'].forEach(eventName => {
                                    deleteButton.dispatchEvent(new MouseEvent(eventName, {
                                        view: window,
                                        bubbles: true,
                                        cancelable: true,
                                        buttons: eventName === 'mousedown' ? 1 : 0
                                    }));
                                });
                            }
                        ];

                        // 尝试每种点击方法
                        let clicked = false;
                        for (const method of clickMethods) {
                            try {
                                method();
                                // 等待确认对话框
                                await new Promise(resolve => setTimeout(resolve, 100));
                                const confirmButton = document.querySelector('button.semi-button-danger');
                                if (confirmButton) {
                                    clicked = true;
                                    console.log('成功触发删除按钮');
                                    break;
                                }
                            } catch (e) {
                                console.log('点击方法失败,尝试下一种');
                            }
                        }

                        if (!clicked) {
                            console.log('自动点击失败,请手动点击删除按钮');
                            document.body.click();
                            await new Promise(resolve => setTimeout(resolve, 100));
                            continue;
                        }

                        // 点击确认删除按钮
                        const confirmButton = document.querySelector('button.semi-button-danger');
                        if (confirmButton) {
                            confirmButton.click();
                            console.log('点击确认删除');
                            await new Promise(resolve => setTimeout(resolve, 400));
                        }

                    } catch (error) {
                        console.error('删除出错:', error);
                        document.body.click();
                        await new Promise(resolve => setTimeout(resolve, 100));
                    }
                }

                console.log('\n全部处理完成!');
            },
        }
    };

    // Get current domain and execute corresponding code
    const domain = window.location.hostname.replace('www.', '').split('.').slice(-2).join('.');
    const config = siteConfigs[domain];

    if (config) {
        config.init();
    }
})();