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