Multi-site Chat Manager

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

当前为 2025-01-15 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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