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