Multi-site Chat Manager

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

目前为 2025-01-15 提交的版本。查看 最新版本

// ==UserScript==
// @name         Multi-site Chat Manager
// @namespace    http://tampermonkey.net/
// @version      0.2
// @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.deleteAllChatItems);
      },
      
      openAllChats: async function () {
        const menuButtons = document.querySelectorAll('.chat-item-menu-button-outline-Ic2b7D');
        for (const menuButton of menuButtons) {
          try {
            // 点击菜单按钮
            menuButton.querySelector('div').click();
            await new Promise(resolve => setTimeout(resolve, 1000));
            
            // 查找并点击分享按钮
            const shareButton = document.querySelector('[data-testid="chat_item_menu_thread_share_icon"]')?.closest('.semi-dropdown-item');
            if (shareButton) {
              shareButton.click();
              await new Promise(resolve => setTimeout(resolve, 1000));
              
              // 获取当前URL并在新标签页打开
              window.open(window.location.href, '_blank');
              
              // 等待一下再处理下一个
              await new Promise(resolve => setTimeout(resolve, 1000));
            }
          } catch (error) {
            console.error('Error processing chat item:', error);
          }
        }
      },
      
      deleteAllChatItems: async function () {
        // 工具函数:检查元素是否存在且可见
        const isElementVisible = (element) => {
          return element && element.offsetParent !== null;
        };
        
        // 工具函数:等待元素出现
        const waitForElement = async (selector, maxAttempts = 20, interval = 50) => {
          for (let i = 0; i < maxAttempts; i++) {
            const element = document.querySelector(selector);
            if (isElementVisible(element)) {
              return element;
            }
            await new Promise(resolve => setTimeout(resolve, interval));
          }
          return null;
        };
        
        // 工具函数:模拟真实点击
        const simulateClick = (element) => {
          if (!element) return false;
          try {
            ['mouseenter', 'mousedown', 'mouseup', 'click'].forEach(eventName => {
              element.dispatchEvent(new MouseEvent(eventName, {
                view: window,
                bubbles: true,
                cancelable: true,
                buttons: eventName === 'mousedown' ? 1 : 0
              }));
            });
            return true;
          } catch (e) {
            console.log('[Error] 点击模拟失败:', e);
            return false;
          }
        };
        
        // 工具函数:处理确认按钮
        const handleConfirmButton = async (maxAttempts = 3) => {
          for (let i = 0; i < maxAttempts; i++) {
            const confirmButton = await waitForElement('.semi-modal-content button.semi-button-danger', 10, 50);
            if (confirmButton) {
              console.log('[Confirm] 找到确认按钮,尝试点击');
              if (simulateClick(confirmButton)) {
                return true;
              }
            }
            await new Promise(resolve => setTimeout(resolve, 50));
          }
          return false;
        };
        
        const menuButtons = document.querySelectorAll('[class*="chat-item-menu-button-outline"]');
        let successCount = 0;
        let skipCount = 0;
        let failCount = 0;
        
        for (const menuButton of menuButtons) {
          try {
            // 检查是否有未关闭的modal,如果有,优先处理
            const existingModal = document.querySelector('.semi-modal-content button.semi-button-danger');
            if (isElementVisible(existingModal)) {
              console.log('[Modal] 发现未关闭的确认框,优先处理');
              if (await handleConfirmButton()) {
                successCount++;
                await new Promise(resolve => setTimeout(resolve, 100));
                continue;
              }
            }
            
            // 点击菜单按钮
            if (!simulateClick(menuButton.querySelector('div'))) {
              console.log('[Skip] 菜单按钮点击失败');
              skipCount++;
              continue;
            }
            
            // 等待并查找删除按钮
            await new Promise(resolve => setTimeout(resolve, 50));
            const deleteButton = Array.from(document.querySelectorAll('.semi-dropdown-item'))
            .find(item => item.querySelector('[data-testid="chat_item_menu_remove_icon"]'));
            
            if (!deleteButton) {
              console.log('[Skip] 没有找到删除按钮');
              skipCount++;
              continue;
            }
            
            console.log('[Delete] 找到删除按钮,准备点击');
            
            // 尝试点击删除按钮,带重试机制
            let deleteSuccess = false;
            for (let attempt = 0; attempt < 3 && !deleteSuccess; attempt++) {
              if (simulateClick(deleteButton)) {
                deleteSuccess = true;
                break;
              }
              await new Promise(resolve => setTimeout(resolve, 50));
            }
            
            if (!deleteSuccess) {
              console.log('[Error] 删除按钮点击失败');
              failCount++;
              continue;
            }
            
            // 处理确认按钮
            if (await handleConfirmButton()) {
              console.log('[Success] 删除操作执行成功');
              successCount++;
            } else {
              console.log('[Error] 确认按钮处理失败');
              failCount++;
            }
            
            // 短暂等待确保操作完成
            await new Promise(resolve => setTimeout(resolve, 1000));
            
          } catch (error) {
            console.error('[Error] 操作出错:', error);
            failCount++;
          }
        }
        
        console.log(`[Complete] 操作完成统计: 成功=${successCount}, 跳过=${skipCount}, 失败=${failCount}`);
      }
    }
  };
  
  // 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();
  }
})();