Multi-site Chat Manager

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==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();
    }
})();